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