1. 程式人生 > 實用技巧 >搞懂aop一(基礎)

搞懂aop一(基礎)

aop術語:

1、連線點(Joinpoint): 需要增強的具體位置比如某一個方法呼叫前,呼叫後,異常後
2、切點(pointcut): 用於定位連線點。
3、增強(advice):是植入連線點的一段程式碼
4、目標物件(target):連線點所在的類的例項
5、引介(introduction):可以為類新增屬性和方法
6、織入(weaving):將增強新增到具體的目標物件的連線點上。(編譯器、類裝載期、執行期)
7、代理(Proxy):被增強後的新物件,這個物件融合了原類和增強邏輯的代理類。
8、切面(Aspect): 由切點和增強組成

代理基礎:

1、jdk只支援有介面的代理,這個是jdk動態代理的短板。

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * @author liangjunhui
 * @date Created in 2020-08-19 9:37
 */
public class JDKProxy<T> implements InvocationHandler {
    private T userService;
    /**
     *
     * @param proxy 代理後的物件
     * 
@param method 連線點對應的方法 * @param args 方法引數 * @return 方法返回結果 * @throws Throwable */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("方法前增強"); method.invoke(userService,args); System.out.println(
"方法後增強"); return null; } public T getProxy(T userService){ this.userService = userService; return (T)Proxy.newProxyInstance(userService.getClass().getClassLoader(),userService.getClass().getInterfaces(),this); } public static void main(String[] args) { JDKProxy<UserServiceImpl> jdkProxy = new JDKProxy(); UserServiceImpl userService = new UserServiceImpl(); UserService proxy = jdkProxy.getProxy(userService); proxy.test("sdfds"); } }
JDKProxy

2、cglib代理,可以支援對無介面物件的代理

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * @author liangjunhui
 * @date Created in 2020-08-19 9:53
 */
public class CglibProxy<T> implements MethodInterceptor {
    private Enhancer enhancer = new Enhancer();
    public T getUserService(Class<T> cls){
        enhancer.setSuperclass(cls);
        enhancer.setCallback(this);
        return (T)enhancer.create();
    }
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("方法前增強");
        methodProxy.invokeSuper(o,objects);
        System.out.println("方法後增強");
        return null;
    }

    public static void main(String[] args) {
        CglibProxy<UserServiceImpl> cglibProxy = new CglibProxy();
        UserServiceImpl userService = cglibProxy.getUserService(UserServiceImpl.class);
        userService.test("sed");
    }
}
View Code

增強:

/**
 * 注意這些增強只是植入了方法的執行位置,這個植入是無差別的,全部的方法都會植入
 * @author liangjunhui
 * @date Created in 2020-08-19 10:17
 */
@Configuration
public class AdviceConfig {
    @Bean
    public ProxyFactoryBean userServiceProxy(){
        ProxyFactoryBean factoryBean = new ProxyFactoryBean();
        // 引介增強需要設定介面名
        // 並強制使用cglib代理,要不然會報ClassCastException
        factoryBean.setInterfaces(Flag.class);
        factoryBean.setProxyTargetClass(true);
        factoryBean.setTargetName("userService");
        factoryBean.setInterceptorNames("afterAdvice","beforeAdvice","aroundAdive","throwingAdvice","introductionAdvice");
        return factoryBean;
    }
    @Bean
    public MethodBeforeAdvice beforeAdvice(){
        // 建立一個前置增強
        return (method, args, target) -> System.out.println("前置增強");
    }
    @Bean
    public AfterReturningAdvice afterAdvice(){
        // 後置通知這個是方法結束的時候做的出發,如果方法執行未完成則不會觸發
        return (returnValue, method, args, target) -> System.out.println("後置增強");
    }
    @Bean
    public UserService userService(){
        return new UserService();
    }
    @Bean
    public MethodInterceptor aroundAdive(){
        return invocation -> {
            System.out.println("環繞通知前");
            // 執行目標方法
            invocation.proceed();
            System.out.println("環繞通知後");
            return null;
        };
    }
    @Bean
    public ThrowsAdvice throwingAdvice(){
        /**
         * 這個介面沒有定義任何方法,使用該介面,方法名必須是
         *  afterThrowing,引數3個選填,一個必填(Exception)。
         */
        return new ThrowsAdvice(){
            public void afterThrowing(Method method, Object[] args, Object target, Exception ex){
                System.out.println("方法發生異常");
            }
        };
    }
    @Bean
    public IntroductionAdvice introductionAdvice(){
        return new IntroductionAdvice();
    }
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AdviceConfig.class);
        UserService userServiceProxy = (UserService)context.getBean("userServiceProxy");
        userServiceProxy.test("sdf");
        System.out.println("=========================觸發引介邏輯===================================");
        ((Flag) userServiceProxy).flag(true);
        userServiceProxy.test("引介");
        System.out.println("=========================有異常出現===================================");
        userServiceProxy.test(null);

    }
}
class UserService {
    public void test(String name){
        System.out.println(name+name.length());
    }
}
interface Flag {
    Boolean flag(Boolean b);
}
class IntroductionAdvice extends DelegatingIntroductionInterceptor implements Flag{
    private ThreadLocal<Boolean> threadLocal = new ThreadLocal<>();
    @Override
    public Object invoke(MethodInvocation mi) throws Throwable {
        if(threadLocal.get()!=null && threadLocal.get()){
            System.out.println("執行期特殊處理");
        }
        return super.invoke(mi);
    }

    @Override
    public Boolean flag(Boolean b) {
        threadLocal.set(b);
        return b;
    }
}
AdviceConfig

切點pointcut

  介面定義:

ClassFilter getClassFilter();
MethodMatcher getMethodMatcher();

  連線點方法匹配

    1、靜態:僅對方法簽名做匹配(方法名和方法引數及順序),這種只會判別一次
    2、動態:可以判斷方法執行時的入參,這種需要每一次做判別
  切點型別(spring提供了六種)
    1、靜態方法切點{@link StaticMethodMatcherPointcut}
    2、動態方法切點{@link DynamicMethodMatcherPointcut}
    3、註解式切點 {@link AnnotationMatchingPointcut}
    4、表示式切點 {@link ExpressionPointcut} 這個是為了支援Aspectj切點表示式語法定義的介面
    5、流程切丁 {@link ControlFlowPointcut} 這個是根據堆疊資訊判斷目標方法是否由一個方法直接或間接呼叫,以此判斷是否為連線點
    6、複合切點 {@link ComposablePointcut} 可以定義多個切點,每個切點都是返回這個類的物件,因此這個類可以鏈式呼叫方法

切面(切點和增強):定義切點和增強,然後通過ProxyFactoryBean配置一個代理物件。這個是一個工廠bean,會為容器注入兩個bean,一個是工廠bean本身,一個是工廠bean#getObject()方法獲取的物件。建立代理物件是org.springframework.aop.framework.DefaultAopProxyFactory#createAopProxy這個方法決定的

@Configuration
public class AdvisorConfig {
    private MethodBeforeAdvice beforeAdvice = (method, args, target) -> System.out.println("前置增強");
     // 靜態切面2 正則匹配方法名
    @Bean
    public RegexpMethodPointcutAdvisor regexpMethodPointcutAdvisor(){
        RegexpMethodPointcutAdvisor advisor = new RegexpMethodPointcutAdvisor();
        advisor.setAdvice(beforeAdvice);
        advisor.setPattern(".*tes.*");
        return advisor;
    }
    @Bean
    public ProxyFactoryBean regexpMethodPointcutAdvisorProxy(){
        ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean();
        proxyFactoryBean.setTargetName("a");
        proxyFactoryBean.setInterceptorNames("regexpMethodPointcutAdvisor");
        return proxyFactoryBean;
    }
    // 動態切面 是由 DefaultPointcutAdvisor 和 DynamicMethodPointcut兩個支援的
    @Bean
    public DefaultPointcutAdvisor dynamicMethodPointcutAdvisor(){
        DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor();
        advisor.setPointcut(new MyDynamicMethodMatcherPointcut());
        advisor.setAdvice(beforeAdvice);
        return advisor;
    }
    // 動態代理
    @Bean
    public ProxyFactoryBean dynamicMethodPointcutAdvisorProxy(){
        ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean();
        proxyFactoryBean.setTargetName("a");
        proxyFactoryBean.setInterceptorNames("dynamicMethodPointcutAdvisor");
        return proxyFactoryBean;
    }
}
AdvisorConfig

注意:上面是手動建立一個代理,但是這樣太過於麻煩(如果有多個切面的,就需手動要配多個),一般不這樣做,大多時候都是通過自動代理來實現業務功能,這裡知識介紹一下怎麼建立一個代理物件。更多請直接參考原始碼