Spring 注解是什么_AOP

2022-08-01,,

该系列学习笔记均是笔者通过学习某站雷丰阳老师的相关课程并结合 spring 2.* 版本源码自行整理出来的。如果有叙述不到位或者有误的地方烦请各位读者评论区给予指正,大家共同学习。

AOP:即在程序运行期间动态的将某段代码切入到指定方法的指定位置进行运行的编程方式

一、使用方式

  • 编写目标类和切面类,并使用注解标识切面类以及对应的通知方法
    /**
     * 目标类 MathCalculator
     * 目标方法 div()
     */
    public class MathCalculator {
    
        public int div(int i, int j) {
            System.err.println("MathCalculator::add()");
            return i / j;
        }
    }
    
    
    /**
     * 使用 @Aspect 标识该类为切面类
     *
     * 通知方法的执行顺序:
     * try {
     *     @Before
     *     method.invoke(obj, args);
     *     @AfterReturning
     * } catch (Exception e) {
     *     @AfterThrowing
     * } finally {
     *    @After
     * }
     *
     * 正常执行:
     * @Before(前置通知) -> @After(后置通知) -> @AfterReturning(返回通知)
     * 异常执行:
     * @Before(前置通知) -> @After(后置通知) -> @AfterThrowing(异常通知)
     */
    @Aspect
    public class LogAspects {
    
        // 使用 @Pointcut() 定义切入点表达式
        @Pointcut(value = "execution(* com.blackpearl.aopconfig.MathCalculator.div(..))")
        public void pointCut(){}
    
        // 前置通知
        @Before(value = "pointCut()")
        public void logStart(JoinPoint joinPoint) {
            System.err.println(joinPoint.getSignature().getName() + " @Before");
        }
    
        // 后置通知
        @After(value = "pointCut()")
        public void logEnd(JoinPoint joinPoint) {
            System.err.println(joinPoint.getSignature().getName() + " @After");
        }
    
        // 返回通知
        @AfterReturning(value = "pointCut()", returning = "result")
        public void logReturning(JoinPoint joinPoint, Integer result) {
            System.err.println(joinPoint.getSignature().getName() + " @AfterReturning Result: " + result);
        }
    
        // 异常通知
        @AfterThrowing(value = "pointCut()", throwing = "exception")
        public void logThrowing(JoinPoint joinPoint, Exception exception) {
            System.err.println(joinPoint.getSignature().getName() + " @AfterThrowing Exception: " + exception.getMessage());
        }
    }
    
  • 通过 Spring 配置类注入目标类和切面类,同时开启基于注解的 AOP 模式
    /**
     * @EnableAspectJAutoProxy 开启基于注解的 AOP
     */
    @EnableAspectJAutoProxy
    @Configuration
    public class SpringAOPConfig {
    
        // 注入目标类
        @Bean
        public MathCalculator mathCalculator() {
            return new MathCalculator();
        }
    
        // 注入切面类
        @Bean
        public LogAspects logAspects() {
            return new LogAspects();
        }
    }
    

二、原理解析

注解形式的 AOP 需要使用 @EnableAspectJAutoProxy 注解

  • @EnableAspectJAutoProxy

    @Import(AspectJAutoProxyRegistrar.class)
    public @interface EnableAspectJAutoProxy {}
    
    • AspectJAutoProxyRegistrar 类会向容器中注入 org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator 类,该类在容器中的 name 属性为 org.springframework.aop.config.internalAutoProxyCreator
      class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {
        
         @Override
         public void registerBeanDefinitions(
               AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
      
            AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(
              AnnotationAwareAspectJAutoProxyCreator.class, registry, (Object)null);
      
            ...
         }
      
      }
      
      public abstract class AopConfigUtils {
      
          @Nullable
          private static BeanDefinition registerOrEscalateApcAsRequired(Class<?> cls, BeanDefinitionRegistry registry, @Nullable Object source) {
              // 判断 Spring 容器中是否已经存在 org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator 
              if (registry.containsBeanDefinition("org.springframework.aop.config.internalAutoProxyCreator")) {
                  BeanDefinition apcDefinition = registry.getBeanDefinition("org.springframework.aop.config.internalAutoProxyCreator");
                  if (!cls.getName().equals(apcDefinition.getBeanClassName())) {
                      int currentPriority = findPriorityForClass(apcDefinition.getBeanClassName());
                      int requiredPriority = findPriorityForClass(cls);
                      if (currentPriority < requiredPriority) {
                          apcDefinition.setBeanClassName(cls.getName());
                      }
                  }
      
                  return null;
              } else {
                  RootBeanDefinition beanDefinition = new RootBeanDefinition(cls);
                  beanDefinition.setSource(source);
                  beanDefinition.getPropertyValues().add("order", -2147483648);
                  beanDefinition.setRole(2);
                  // 不存在,使用 BeanDefinitionRegistry::registerBeanDefinition 进行注入
                  // org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator 对应的 name 属性为 org.springframework.aop.config.internalAutoProxyCreator
                  registry.registerBeanDefinition("org.springframework.aop.config.internalAutoProxyCreator", beanDefinition);
                  return beanDefinition;
              }
          }
        
      }
      
  • AnnotationAwareAspectJAutoProxyCreator 类根据继承树,该类 implements InstantiationAwareBeanPostProcessor, BeanFactoryAware

    public interface InstantiationAwareBeanPostProcessor extends BeanPostProcessor {
      
       @Nullable
       Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException;
    
       boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException;
    
    }
    
    public class AnnotationAwareAspectJAutoProxyCreator implements InstantiationAwareBeanPostProcessor, BeanFactoryAware {
    
        @Nullable
        private AspectJAdvisorFactory aspectJAdvisorFactory;
    
        @Nullable
        private BeanFactoryAspectJAdvisorsBuilder aspectJAdvisorsBuilder;
    
        @Nullable
        private BeanFactoryAdvisorRetrievalHelper advisorRetrievalHelper;
    
        @Nullable
        private BeanFactory beanFactory;
    
        // BeanFactoryAware::setBeanFactory
        @Override
        public void setBeanFactory(BeanFactory beanFactory) {
            this.beanFactory = beanFactory;
            initBeanFactory((ConfigurableListableBeanFactory) beanFactory);
        }
    
        @Override
        protected void initBeanFactory(ConfigurableListableBeanFactory beanFactory) {
            this.advisorRetrievalHelper = new BeanFactoryAdvisorRetrievalHelperAdapter(beanFactory);
            if (this.aspectJAdvisorFactory == null) {
                this.aspectJAdvisorFactory = new ReflectiveAspectJAdvisorFactory(beanFactory);
            }
            this.aspectJAdvisorsBuilder =
                    new BeanFactoryAspectJAdvisorsBuilderAdapter(beanFactory, this.aspectJAdvisorFactory);
        }
    
        // InstantiationAwareBeanPostProcessor::postProcessBeforeInstantiation
        @Override
        public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) {
            Object cacheKey = getCacheKey(beanClass, beanName);
    
            if (!StringUtils.hasLength(beanName) || !this.targetSourcedBeans.contains(beanName)) {
                if (this.advisedBeans.containsKey(cacheKey)) {
                    return null;
                }
                if (isInfrastructureClass(beanClass) || shouldSkip(beanClass, beanName)) {
                    this.advisedBeans.put(cacheKey, Boolean.FALSE);
                    return null;
                }
            }
    
            TargetSource targetSource = getCustomTargetSource(beanClass, beanName);
            if (targetSource != null) {
                if (StringUtils.hasLength(beanName)) {
                    this.targetSourcedBeans.add(beanName);
                }
                Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(beanClass, beanName, targetSource);
                Object proxy = createProxy(beanClass, beanName, specificInterceptors, targetSource);
                this.proxyTypes.put(cacheKey, proxy.getClass());
                return proxy;
            }
    
            return null;
        }
    
        // InstantiationAwareBeanPostProcessor::postProcessAfterInstantiation
        @Override
        public boolean postProcessAfterInstantiation(Object bean, String beanName) {
            return true;
        }
    
        // BeanPostProcessor::postProcessBeforeInitialization
        @Override
        public Object postProcessBeforeInitialization(Object bean, String beanName) {
            return bean;
        }
    
        // BeanPostProcessor::postProcessAfterInitialization
        @Override
        public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
            if (bean != null) {
                Object cacheKey = getCacheKey(bean.getClass(), beanName);
                if (this.earlyProxyReferences.remove(cacheKey) != bean) {
                    return wrapIfNecessary(bean, beanName, cacheKey);
                }
            }
            return bean;
        }
    
    }
    

    由于 AnnotationAwareAspectJAutoProxyCreator 类 implements BeanPostProcessor,因此会在容器中 Bean 对象初始化方法之前/后工作,具体流程如下:

    • 创建容器,传入自定义配置类,刷新容器
      public AnnotationConfigApplicationContext(Class<?>... componentClasses) {
         this();
         register(componentClasses);
         refresh();
      }
      
    • AbstractApplicationContext::refresh() 刷新容器
      public abstract class AbstractApplicationContext extends DefaultResourceLoader
            implements ConfigurableApplicationContext {
      
         @Override
         public void refresh() throws BeansException, IllegalStateException {
            synchronized (this.startupShutdownMonitor) {
      
               try {
                 
                  ...
      
                  // 注册 BeanPostProcessor 用于拦截容器中 Bean 对象的创建
                  registerBeanPostProcessors(beanFactory);
      
                  ...
               }
      
               catch (BeansException ex) {}
      
               finally {}
            }
         }
        
      }
      
      • registerBeanPostProcessors(beanFactory) 注册 BeanPostProcessor
        该方法会将容器中需要注入的 BeanPostProcessor 按等级按批次进行注册,之前注册的 BeanPostProcessor 可以为之后注册的 BeanPostProcessor 提供 postProcessBeforeInitialization / postProcessAfterInitialization 回调函数

        public static void registerBeanPostProcessors(
              ConfigurableListableBeanFactory beanFactory, AbstractApplicationContext applicationContext) {
        
           String[] postProcessorNames = beanFactory.getBeanNamesForType(BeanPostProcessor.class, true, false);
        
           // Separate between BeanPostProcessors that implement PriorityOrdered, Ordered, and the rest.
           List<BeanPostProcessor> priorityOrderedPostProcessors = new ArrayList<>();
           List<String> orderedPostProcessorNames = new ArrayList<>();
           List<String> nonOrderedPostProcessorNames = new ArrayList<>();
        
           // First, register the BeanPostProcessors that implement PriorityOrdered.
           sortPostProcessors(priorityOrderedPostProcessors, beanFactory);
           registerBeanPostProcessors(beanFactory, priorityOrderedPostProcessors);
        
           // Next, register the BeanPostProcessors that implement Ordered.
           List<BeanPostProcessor> orderedPostProcessors = new ArrayList<>(orderedPostProcessorNames.size());
           for (String ppName : orderedPostProcessorNames) {
              // AnnotationAwareAspectJAutoProxyCreator implements Ordered
              // 因此 AnnotationAwareAspectJAutoProxyCreator 会在此进行注册
              BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);
              orderedPostProcessors.add(pp);
              if (pp instanceof MergedBeanDefinitionPostProcessor) {
                 internalPostProcessors.add(pp);
              }
           }
           sortPostProcessors(orderedPostProcessors, beanFactory);
           // 将注册的 AnnotationAwareAspectJAutoProxyCreator 添加到 beanFactory 中 => 进入容器
           registerBeanPostProcessors(beanFactory, orderedPostProcessors);
        
           // Now, register all regular BeanPostProcessors.
           List<BeanPostProcessor> nonOrderedPostProcessors = new ArrayList<>(nonOrderedPostProcessorNames.size());
           for (String ppName : nonOrderedPostProcessorNames) {
              BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);
              nonOrderedPostProcessors.add(pp);
              if (pp instanceof MergedBeanDefinitionPostProcessor) {
                 internalPostProcessors.add(pp);
              }
           }
           registerBeanPostProcessors(beanFactory, nonOrderedPostProcessors);
        
           // Finally, re-register all internal BeanPostProcessors.
           sortPostProcessors(internalPostProcessors, beanFactory);
           registerBeanPostProcessors(beanFactory, internalPostProcessors);
        
           // Re-register post-processor for detecting inner beans as ApplicationListeners,
           // moving it to the end of the processor chain (for picking up proxies etc).
           beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(applicationContext));
        }
        
      • 所谓注册 BeanPostProcessor 即创建对应的 BeanPostProcessor 并注入容器
        BeanPostProcessor 的创建流程位于 AbstractAutowireCapableBeanFactory::doCreateBean

        public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory
                implements AutowireCapableBeanFactory {
        
        
            protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args) throws BeanCreationException {
        
                // Instantiate the bean.
                BeanWrapper instanceWrapper = null;
                // 如果 Bean 对象为单实例
                if (mbd.isSingleton()) {
                    // 先尝试从缓存中获取
                    instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
                }
                if (instanceWrapper == null) {
                    // 获取失败则执行 Bean 对象构造方法 => 创建 Bean 对象
                    instanceWrapper = createBeanInstance(beanName, mbd, args);
                }
        
                // Initialize the bean instance.
                Object exposedObject = bean;
                try {
                    // 为创建的 Bean 对象进行属性赋值
                    populateBean(beanName, mbd, instanceWrapper);
                    // 执行 Bean 对象的初始化方法
                    exposedObject = initializeBean(beanName, exposedObject, mbd);
                }
                catch (Throwable ex) {}
        
                // Register bean as disposable.
                try {
                    // 将单实例 Bean 对象加入到 list 中,在容器销毁时会调用这个 list 中所有 Bean 对象的销毁方法
                    registerDisposableBeanIfNecessary(beanName, bean, mbd);
                }
                catch (BeanDefinitionValidationException ex) {}
        
                return exposedObject;
            }
        
          
            protected Object initializeBean(final String beanName, final Object bean, @Nullable RootBeanDefinition mbd) 
            {
                // 由于 AnnotationAwareAspectJAutoProxyCreator implements BeanFactoryAware,因此需要回调 BeanFactoryAware::setBeanFactory() 方法缓存 beanFactory
                invokeAwareMethods(beanName, bean);
              
                Object wrappedBean = bean;
                if (mbd == null || !mbd.isSynthetic()) {
                    // 执行容器中所有的 BeanPostProcessor 执行其 postProcessBeforeInitialization 方法 
                    wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
                }
        
                try {
                    // 执行 Bean 对象的初始化方法
                    invokeInitMethods(beanName, wrappedBean, mbd);
                }
                catch (Throwable ex) {}
                if (mbd == null || !mbd.isSynthetic()) {
                    // 执行容器中所有的 BeanPostProcessor 执行其 postProcessAfterInitialization 方法 
                    wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
                }
        
                return wrappedBean;
            }
        }
        

    至此,AnnotationAwareAspectJAutoProxyCreator 类已经加入容器

  • 以后,容器再创建 Bean 对象之前,会先调用 AnnotationAwareAspectJAutoProxyCreator::postProcessBeforeInstantiation 方法

    @Override
    protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
          throws BeanCreationException {
    
       RootBeanDefinition mbdToUse = mbd;
      
       try {
          // 尝试通过容器中所有的 BeanPostProcessor 获取 Bean 的代理对象
          Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
          if (bean != null) {
             // 获取成功直接 return
             return bean;
          }
       }
       catch (Throwable ex) {}
    
       try {
          // 获取失败才开始创建 Bean
          Object beanInstance = doCreateBean(beanName, mbdToUse, args);
          return beanInstance;
       }
       catch (BeanCreationException | ImplicitlyAppearedSingletonException ex) {}
       catch (Throwable ex) {}
    }
    
    @Nullable
    protected Object resolveBeforeInstantiation(String beanName, RootBeanDefinition mbd) {
        Object bean = null;
        Class<?> targetType = determineTargetType(beanName, mbd);
        if (targetType != null) {
            // 尝试调用容器中所有的 BeanPostProcessor::postProcessBeforeInstantiation
            bean = applyBeanPostProcessorsBeforeInstantiation(targetType, beanName);
            if (bean != null) {
                bean = applyBeanPostProcessorsAfterInitialization(bean, beanName);
            }
        }
    
        return bean;
    }
    
    @Nullable
    protected Object applyBeanPostProcessorsBeforeInstantiation(Class<?> beanClass, String beanName) {
       for (BeanPostProcessor bp : getBeanPostProcessors()) {
          // 仅 implements InstantiationAwareBeanPostProcessor 的 BeanPostProcessor 才有 postProcessBeforeInstantiation 方法
          if (bp instanceof InstantiationAwareBeanPostProcessor) {
             InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
             // 调用 InstantiationAwareBeanPostProcessor::postProcessBeforeInstantiation
             Object result = ibp.postProcessBeforeInstantiation(beanClass, beanName);
             if (result != null) {
                return result;
             }
          }
       }
       return null;
    }
    
    public class AnnotationAwareAspectJAutoProxyCreator implements InstantiationAwareBeanPostProcessor, BeanFactoryAware {
    
        @Nullable
        private AspectJAdvisorFactory aspectJAdvisorFactory;
    
        @Nullable
        private BeanFactoryAspectJAdvisorsBuilder aspectJAdvisorsBuilder;
    
        @Nullable
        private BeanFactoryAdvisorRetrievalHelper advisorRetrievalHelper;
    
        @Nullable
        private BeanFactory beanFactory;
    
        // BeanFactoryAware::setBeanFactory
        @Override
        public void setBeanFactory(BeanFactory beanFactory) {
            this.beanFactory = beanFactory;
            initBeanFactory((ConfigurableListableBeanFactory) beanFactory);
        }
    
        @Override
        protected void initBeanFactory(ConfigurableListableBeanFactory beanFactory) {
            this.advisorRetrievalHelper = new BeanFactoryAdvisorRetrievalHelperAdapter(beanFactory);
            if (this.aspectJAdvisorFactory == null) {
                this.aspectJAdvisorFactory = new ReflectiveAspectJAdvisorFactory(beanFactory);
            }
            this.aspectJAdvisorsBuilder =
                    new BeanFactoryAspectJAdvisorsBuilderAdapter(beanFactory, this.aspectJAdvisorFactory);
        }
    
        // InstantiationAwareBeanPostProcessor::postProcessBeforeInstantiation
        @Override
        public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) {
            Object cacheKey = getCacheKey(beanClass, beanName);
    
            if (!StringUtils.hasLength(beanName) || !this.targetSourcedBeans.contains(beanName)) {
                // 判断当前 Bean 是否属于需要增强的 Bean 对象(即需要被动态插入执行代码的 Bean)
                if (this.advisedBeans.containsKey(cacheKey)) {
                    return null;
                }
                // isInfrastructureClass 用于判断当前 Bean 是否 implements Advice / Pointcut / Advisor / AopInfrastructureBean 或 指定了 @Aspect 注解
                // shouldSkip 用于判断容器中的增强器(即为通知方法)的类型
                if (isInfrastructureClass(beanClass) || shouldSkip(beanClass, beanName)) {
                    this.advisedBeans.put(cacheKey, Boolean.FALSE);
                    return null;
                }
            }
    
            TargetSource targetSource = getCustomTargetSource(beanClass, beanName);
            if (targetSource != null) {
                if (StringUtils.hasLength(beanName)) {
                    this.targetSourcedBeans.add(beanName);
                }
                Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(beanClass, beanName, targetSource);
                Object proxy = createProxy(beanClass, beanName, specificInterceptors, targetSource);
                this.proxyTypes.put(cacheKey, proxy.getClass());
                return proxy;
            }
    
            return null;
        }
    
        // 执行完容器中所有后置处理器的 postProcessBeforeInstantiation 方法后,会进行 Bean 对象的创建,在执行完 Bean 对象的初始化方法后,会执行 BeanPostProcessor::postProcessAfterInitialization 方法
        // BeanPostProcessor::postProcessAfterInitialization
        @Override
        public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
            if (bean != null) {
                Object cacheKey = getCacheKey(bean.getClass(), beanName);
                if (this.earlyProxyReferences.remove(cacheKey) != bean) {
                    // 如果该 Bean 对象属于需要增强的 Bean 对象则向容器注入该 Bean 对象的代理对象
                    return wrapIfNecessary(bean, beanName, cacheKey);
                }
            }
            return bean;
        }
    		
        protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
          
            // 获取当前 Bean 对象需要使用的增强器(即需要动态插入到该 Bean 对象目标方法执行前后的通知方法)
            Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
            if (specificInterceptors != DO_NOT_PROXY) {
                // 缓存当前 Bean 为需要增强的 Bean 对象
                this.advisedBeans.put(cacheKey, Boolean.TRUE);
                // 创建当前 Bean 的代理对象,Spring 通过判断该 Bean 是否实现接口决定创建 JdkDynamicAopProxy / ObjenesisCglibAopProxy
                Object proxy = createProxy(
                        bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
                this.proxyTypes.put(cacheKey, proxy.getClass());
                return proxy;
            }
    
            this.advisedBeans.put(cacheKey, Boolean.FALSE);
            return bean;
        }
    
    }
    

    至此,对于需要增强的 Bean 对象,Spring 为其创建了代理对象同时也保存了该 Bean 对象需要使用的所有增强器(即通知方法)

  • 以后,对于需要增强的 Bean 对象,Spring 使用其创建的代理对象,其中保存了增强器(即通知方法)和目标方法的详细信息

    针对每一个增强器,Spring 将其包装为方法拦截器 MethodInterceptor,组成 list 集合

    由图可见,list 集合中元素顺序为 @AfterThrowing / @AfterReturning / @After / @Before,集合中拦截器执行顺序为逆序执行,并且在执行目标方法时如果报错,拦截器调用链会将异常抛出

    public class AfterReturningAdviceInterceptor implements MethodInterceptor, AfterAdvice, Serializable {
    
       @Override
       public Object invoke(MethodInvocation mi) throws Throwable {
          Object retVal = mi.proceed(); // 拦截器使用 proceed 进行方法调用,如果目标方法抛异常,则此处会出现异常
          // 异常会被抛出,this.advice.afterReturning() 不会执行,即不会执行 @AfterReturning 返回通知方法
          this.advice.afterReturning(retVal, mi.getMethod(), mi.getArguments(), mi.getThis());
          return retVal;
       }
    
    }
    
    public class AspectJAfterThrowingAdvice extends AbstractAspectJAdvice
          implements MethodInterceptor, AfterAdvice, Serializable {
    
       @Override
       public Object invoke(MethodInvocation mi) throws Throwable {
          try {
             return mi.proceed(); // 出现异常
          }
          catch (Throwable ex) {
             if (shouldInvokeOnThrowing(ex)) {
                // 出现异常会调用该方法执行 @AfterThrowing 异常通知方法
                invokeAdviceMethod(getJoinPointMatch(), null, ex);
             }
             throw ex;
          }
       }
      
    }
    

本文地址:https://blog.csdn.net/qq_43415605/article/details/107478432

《Spring 注解是什么_AOP.doc》

下载本文的Word格式文档,以方便收藏与打印。