1. 程式人生 > >SpringAOP使用及原始碼分析(SpringBoot下)

SpringAOP使用及原始碼分析(SpringBoot下)

## 一、SpringAOP應用 1. 先搭建一個SpringBoot專案 ``` ``` 2. 定義一個業務邏輯類,作為切面 ``` public interface CalculationService { /** * 加法運算 * @param x * @param y * @return */ public Integer add(Integer x,Integer y); } /** * @description: * @author: mmc * @create: 2020-06-01 14:22 **/ @Service public class CalculationServiceImpl implements CalculationService { @Override public Integer add(Integer x, Integer y) { if(x==null||y==null){ throw new NullPointerException("引數不能為空"); } return x+y; } } ``` 3. 定義一個切面類,新增通知方法 - 前置通知(@Before):logStart:在目標方法(div)執行之前執行 - 後置通知(@After):logEnd:在目標方法(add)執行結束之後執行(無論方法正常結束還是異常結束) - 返回通知(@AfterReturning):logReturn:在目標方法(add)正常返回之後執行 - 異常通知(@AfterThrowing):logException:在目標方法(add)出現異常以後執行 - 環繞通知(@Around):動態代理,手動推進目標方法執行(joinPoint.procced()) ``` /** * @description: 切面類 * @author: mmc * @create: 2020-06-01 14:24 **/ @Aspect @Component public class LogAspects { //抽取公共的切入點表示式 //1、本類引用 //2、其他的切面引用 @Pointcut("execution(public Integer com.mmc.springbootstudy.service.CalculationService.*(..))") public void pointCut(){}; @Before("pointCut()") public void logStart(JoinPoint joinPoint){ Object[] args = joinPoint.getArgs(); System.out.println(""+joinPoint.getSignature().getName()+"執行。。。@Before:引數列表是:{"+Arrays.asList(args)+"}"); } @After("pointCut()") public void logEnd(JoinPoint joinPoint){ System.out.println(""+joinPoint.getSignature().getName()+"結束。。。@After"); } //JoinPoint一定要出現在引數表的第一位 @AfterReturning(value="pointCut()",returning="result") public void logReturn(JoinPoint joinPoint,Object result){ System.out.println(""+joinPoint.getSignature().getName()+"正常返回。。。@AfterReturning:執行結果:{"+result+"}"); } @AfterThrowing(value="pointCut()",throwing="exception") public void logException(JoinPoint joinPoint,Exception exception){ System.out.println(""+joinPoint.getSignature().getName()+"異常。。。異常資訊:{"+exception+"}"); } } ``` 4. 寫一個controller測試 ``` @RequestMapping("/testaop") @ResponseBody public Integer testaop(Integer x,Integer y){ Integer result = calculationService.add(x, y); return result; } ``` 5. 測試 > add執行。。。@Before:引數列表是:{[2, 3]} add結束。。。@After add正常返回。。。@AfterReturning:執行結果:{5} ## 二、原始碼分析 主線流程圖: --- ![](https://img2020.cnblogs.com/blog/1178991/202006/1178991-20200601225130559-1898808695.png) 1. spring.factories檔案裡引入了AopAutoConfiguration類 ``` @Configuration @ConditionalOnClass({ EnableAspectJAutoProxy.class, Aspect.class, Advice.class, AnnotatedElement.class }) @ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true) public class AopAutoConfiguration { @Configuration @EnableAspectJAutoProxy(proxyTargetClass = false) //看配置檔案,如果配置的spring.aop.proxy-target-class為false則引入JdkDynamicAutoProxyConfiguration @ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false", matchIfMissing = false) public static class JdkDynamicAutoProxyConfiguration { } @Configuration //開啟AspectJAutoProxy @EnableAspectJAutoProxy(proxyTargetClass = true) //看配置檔案,如果配置的spring.aop.proxy-target-class為true則引入CglibAutoProxyConfiguration @ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true", matchIfMissing = true) public static class CglibAutoProxyConfiguration { } } ``` 在包目錄下找到配置檔案,並且發現他的值為true ![](https://img2020.cnblogs.com/blog/1178991/202006/1178991-20200601215002020-477866118.png) 在上面的方法上有EnableAspectJAutoProxy註解,並傳入了proxyTargetClass=true 2. 進入@EnableAspectJAutoProxy註解 ``` @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented //引入了AspectJAutoProxyRegistrar @Import({AspectJAutoProxyRegistrar.class}) public @interface EnableAspectJAutoProxy { boolean proxyTargetClass() default false; boolean exposeProxy() default false; } ``` 3. 進入AspectJAutoProxyRegistrar類 ``` class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar { AspectJAutoProxyRegistrar() { } public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { //註冊了自動自動代理類 AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry); AnnotationAttributes enableAspectJAutoProxy = AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class); if (enableAspectJAutoProxy != null) { if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) { AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry); } if (enableAspectJAutoProxy.getBoolean("exposeProxy")) { AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry); } } } } ``` 4. 進入registerAspectJAnnotationAutoProxyCreatorIfNecessary方法裡面 ``` public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry, @Nullable Object source) { return registerOrEscalateApcAsRequired(AnnotationAwareAspectJAutoProxyCreator.class, registry, source); } ``` 可以看到返回了一個BeanDefinition,裡面的BeanClass型別是AnnotationAwareAspectJAutoProxyCreator,這個類看名字是一個AOP的動態代理建立類,裡面沒有啥可疑的方法。在IDEA裡按Ctrl+H看他的繼承結構。有一個父類AbstractAutoProxyCreator,這個類實現了BeanPostProcessor介面。這個介面是Bean的擴充套件介面,在bean初始化完成後會呼叫到他的postProcessAfterInitialization(Object bean, String beanName)方法。 --- ![](https://img2020.cnblogs.com/blog/1178991/202006/1178991-20200601215909344-484837677.png) 5. 方法內容如下 ``` public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) { if (bean != null) { Object cacheKey = this.getCacheKey(bean.getClass(), beanName); if (this.earlyProxyReferences.remove(cacheKey) != bean) { //如果有必要,進行包裝 return this.wrapIfNecessary(bean, beanName, cacheKey); } } return bean; } protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) { if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) { return bean; } else if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) { return bean; } else if (!this.isInfrastructureClass(bean.getClass()) && !this.shouldSkip(bean.getClass(), beanName)) { //獲取切面的方法,第9點那裡展開討論 Object[] specificInterceptors = this.getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, (TargetSource)null); if (specificInterceptors != DO_NOT_PROXY) { this.advisedBeans.put(cacheKey, Boolean.TRUE); //建立動態代理 Object proxy = this.createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean)); this.proxyTypes.put(cacheKey, proxy.getClass()); return proxy; } else { this.advisedBeans.put(cacheKey, Boolean.FALSE); return bean; } } else { this.advisedBeans.put(cacheKey, Boolean.FALSE); return bean; } } ``` 6. 可以看出這裡已經在開始建立動態代理了 ``` protected Object createProxy(Class beanClass, @Nullable String beanName, @Nullable Object[] specificInterceptors, TargetSource targetSource) { if (this.beanFactory instanceof ConfigurableListableBeanFactory) { AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory)this.beanFactory, beanName, beanClass); } //動態代理工廠 ProxyFactory proxyFactory = new ProxyFactory(); proxyFactory.copyFrom(this); if (!proxyFactory.isProxyTargetClass()) { if (this.shouldProxyTargetClass(beanClass, beanName)) { proxyFactory.setProxyTargetClass(true); } else { this.evaluateProxyInterfaces(beanClass, proxyFactory); } } Advisor[] advisors = this.buildAdvisors(beanName, specificInterceptors); //切面那裡的方法 proxyFactory.addAdvisors(advisors); proxyFactory.setTargetSource(targetSource); this.customizeProxyFactory(proxyFactory); proxyFactory.setFrozen(this.freezeProxy); if (this.advisorsPreFiltered()) { proxyFactory.setPreFiltered(true); } //獲取動態代理類 return proxyFactory.getProxy(this.getProxyClassLoader()); } ``` 7. 學過AOP的人都知道動態代理的方式有兩種,一種JDK代理,一種CGLIB動態代理。那麼Spring裡面是怎麼選擇的呢?答案就在這裡: ``` public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException { // 1.config.isOptimize()是否使用優化的代理策略,目前使用與CGLIB // config.isProxyTargetClass() 是否目標類本身被代理而不是目標類的介面 // hasNoUserSuppliedProxyInterfaces()是否存在代理介面 if (!config.isOptimize() && !config.isProxyTargetClass() && !this.hasNoUserSuppliedProxyInterfaces(config)) { return new JdkDynamicAopProxy(config); } else { Class targetClass = config.getTargetClass(); if (targetClass == null) { throw new AopConfigException("TargetSource cannot determine target class: Either an interface or a target is required for proxy creation."); } else { //目標類不是介面或不是代理類就使用cglib代理 return (AopProxy)(!targetClass.isInterface() && !Proxy.isProxyClass(targetClass) ? new ObjenesisCglibAopProxy(config) : new JdkDynamicAopProxy(config)); } } } ``` 8. Cglib的代理類是CglibAopProxy、ObjenesisCglibAopProxy,JDK的代理類是JdkDynamicAopProxy。在這些類裡面對目標類進行了代理,在執行方法的時候就是執行的代理類的方法,而實現了切面程式設計的效果。 9. 主線流程就是這些了,還有一個沒說的就是我們如何獲取的切面方法,@Before("pointCut()")這些註解又是如何生效的?再回到AbstractAutoProxyCreator的wrapIfNecessary()方法 裡面有這句程式碼: ``` Object[] specificInterceptors = this.getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, (TargetSource)null); @Nullable protected Object[] getAdvicesAndAdvisorsForBean(Class beanClass, String beanName, @Nullable TargetSource targetSource) {