zoukankan      html  css  js  c++  java
  • spring注解-aop

    一、动态代理

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

    • 导入aop依赖(spring-aspects)
    • 业务逻辑组件和切面类生成的实例对象都必须是注册在ioc容器中的(自己创建的对象是不会执行切面方法的);在定义的切面类上标注@Aspect、配置类上标注@EnableAspectJAutoProxy开启基于注解的aop模式(等同于xml配置<aop:aspectj-autoproxy></aop:aspectj-autoproxy>)
    • 在切面类上的每一个(通知)方法上标注通知注解,并且在通知注解上定义切入点表达式(告诉Spring在哪些类、方法以及方法运行前、后执行通知方法)
      • 切入点表达式:程序执行到哪些类哪些方法时要进行切入;通过表达式进行定义
      • 运行时机:程序执行方法的时机;通过通知注解进行标注

    代码实现

      定义日志切面类(LogAspects):在程序运行到某个阶段的时候能够通过切面类里的方法介入进去给切面类的目标方法标注何时何地运行(通知注解)

    • 前置通知(@Before):在目标方法运行之前执行
    • 后置通知(@After):在目标方法运行结束之后运行(无论方法正常结束还是异常结束)
    • 返回通知(@AfterReturning):在目标方法正常返回之后运行
    • 异常通知(@AfterThrowing):在目标方法出现异常以后运行
    • 环绕通知(@Around):动态代理,手动推进目标方法运行(joinPoint.procced())
    /**
     * @Aspect: 告诉Spring当前类是一个切面类
     */
    @Aspect
    public class LogAspects {
        
        //抽取公共的切入点表达式
        @Pointcut("execution(public int com.atguigu.aop.MathCalculator.*(..))")
        public void pointCut(){};
        
        //本类引用(切入点表达式) @Before("public int com.atguigu.aop.MathCalculator.*(..)")
      @Before("pointCut()") 

      public void logStart(JoinPoint joinPoint){
          Object[] args = joinPoint.getArgs();
          System.out.println(""+joinPoint.getSignature().getName()+"运行。。。@Before:参数列表是:{"+Arrays.asList(args)+"}");
    }

       //其他的切面引用 
      @After("com.atguigu.aop.LogAspects.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+"}"); }

     

    AOP原理

    【看给容器中注册了什么组件(能给我们带来什么),这个组件什么时候工作(在程序的哪个步骤中执行),这个组件的功能是什么(执行的目的)?】

    @EnableAspectJAutoProxy

      这步的作用就是在容器中添加一个AnnotationAwareAspectJAutoProxyCreator的bean定义信息

      标注@EnableAspectJAutoProxy后 -》 @Import(AspectJAutoProxyRegistrar.class),它实现了ImportBeanDefinitionRegistrar接口,registerBeanDefinitions()作用就是给容器中添加一个BeanDefinition

      registerBeanDefinitions()调用其他方法判断BeanDefinitionRegistry是否有org.springframework.aop.config.internalAutoProxyCreator的beanName,如果没有则通过传入的AnnotationAwareAspectJAutoProxyCreator.class构建一个RootBeanDefinition对象,beanName就是org.springframework.aop.config.internalAutoProxyCreator   

    AnnotationAwareAspectJAutoProxyCreator.class
    
      AnnotationAwareAspectJAutoProxyCreator -》 AnnotationAwareAspectJAutoProxyCreator -》 AspectJAwareAdvisorAutoProxyCreator -》 AbstractAdvisorAutoProxyCreator -》 AbstractAutoProxyCreator implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware(一个是后置处理器,一个是自动装配BeanFactory)
    
      AbstractAutoProxyCreator有setBeanFactory(),也有beanPostProcessor的后置处理器的逻辑
    
      AbstractAdvisorAutoProxyCreator重写了setBeanFactory()并且该方法中还调用一个initBeanFactory(),没有重写父类beanPostProcessor的方法
    
      AnnotationAwareAspectJAutoProxyCreator重写了initBeanFactory()

    整体流程

    1. 传入配置类,创建ioc容器
    2. 注册配置类,调用refresh()刷新容器
    3. refresh()中先执行registerBeanPostProcessors(这一步的作用其实就是提前创建好所有BeanPostProcessor对象)
      • 先获取spring底层定义好的所有BeanPostProcessor(它的所有实现类们,通过配置类决定是否启用它们并创建相应的BeanPostProcessor对象),之后获取我们自定义实现了BeanPostProcessor接口的class类(加入到集合中BeanPostProcessors)
      • 优先注册实现了PriorityOrdered接口的BeanPostProcessor,再给容器中注册实现了Ordered接口的BeanPostProcessor,最后注册没实现优先级接口的BeanPostProcessor
      • 根据BeanPostProcessor的优先级调用beanFactory.getBean("定义信息")获取实例,如果获取不到就创建beanPostProcessor对象,并将返回的beanPostProcessor对象保存到beanFactory中(beanFactory.addBeanPostProcessor(postProcessor))
    4. refresh()中后执行finishBeanFactoryInitialization完成BeanFactory初始化工作(创建所有单实例bean对象)
      • 遍历获取容器中所有的BeanDefinition,依次创建对象

      • 先根据beanDefinition定义信息从beanFactory中获取当前bean(创建好的Bean会放入到beanFactory保存起来),如果能获取到说明bean是之前被创建过的,直接使用,否则走创建流程(getBean("beanName")->doGetBean()->getSingleton()->createBean())
      • 每一个bean创建之前在createBean()中会先执行resolveBeforeInstantiation(beanName, mbdToUse),该方法会拿到所有后置处理器判断当前bean如果是InstantiationAwareBeanPostProcessor类型就会先执行它的处理器方法postProcessBeforeInstantiation()
        • 判断当前bean是否在advisedBeans中(保存了所有需要增强bean);如果判断为true直接return null
        • 判断当前bean是否是基础类型的Advice、Pointcut、Advisor、AopInfrastructureBean或者是否是切面(@Aspect)|| 获取候选的增强器(也就是切面里面的通知方法),根据增强器类型判断(永远返回false);如果判断为true将当前bean保存到advisedBeans后return null
      • 继续调用doCreateBean创建对象,bean的创建流程见下:
        • 调用构造器创建Bean的实例
        • populateBean():给bean的各种属性赋值(@AutoWired、@Value。。。)
        • initializeBean():初始化bean;初始顺序过程:
          • invokeAwareMethods():如果实现了XXXAware接口就处理相应接口的方法回调
          • applyBeanPostProcessorsBeforeInitialization():应用后置处理器的postProcessBeforeInitialization()
          • invokeInitMethods();执行自定义的初始化方法
          • applyBeanPostProcessorsAfterInitialization();执行后置处理器的postProcessAfterInitialization() aop就是在这实现
            • 找到候选的所有的增强器-》获取到能在bean使用的增强器(哪些增强器根据切入点匹配当前bean方法的)-》给增强器排序-》最终获取当前bean的所有增强器(通知方法)
            • 保存当前bean在advisedBeans中
            • 如果当前bean需要增强创建它的代理对象(获取所有增强器保存到proxyFactory,创建代理对象);返回当前组件使用cglib增强了的代理对象;以后容器中获取到的就是这个组件的代理对象,执行目标方法的时候,代理对象就会执行通知方法的流程

    目标方法执行

      容器中保存了组件的代理对象(cglib增强后的对象),这个对象里面保存了详细信息(比如增强器,目标对象,xxx);

    • CglibAopProxy.intercept();拦截目标方法的执行
    • 根据ProxyFactory对象获取将要执行的目标方法拦截器链(每一个通知方法又被包装为方法拦截器,利用MethodInterceptor机制)
      • List<Object> interceptorList保存所有拦截器
        • 遍历所有的增强器将它们转换成MethodInterceptor数组registry.getInterceptors(advisor) ,该方法中创建一个List<MethodInterceptor>的集合,如果是MethodInterceptor直接加入到集合中;如果不是使用AdvisorAdapter将增强器转为MethodInterceptor再加入到集合;转换完成返回MethodInterceptor数组并保存到interceptorList里;
    • 如果没有拦截器链,直接执行目标方法
    • 如果有拦截器链,把需要执行的目标对象、目标方法、拦截器链等信息传入创建一个 CglibMethodInvocation 对象并调用 Object retVal =  mi.proceed()
      • 如果没有拦截器执行执行目标方法,或者拦截器的索引和拦截器数组-1大小一样(指定到了最后一个拦截器)执行目标方法
      • 链式获取每一个拦截器,拦截器执行invoke方法,每一个拦截器等待下一个拦截器执行完成返回以后再来执行;拦截器链的机制(递归),保证通知方法与目标方法的执行顺序

    bean的创建过程

    BeanPostProcessor的后置处理器方法是在Bean对象创建完成初始化方法前后调用的

    InstantiationAwareBeanPostProcessor的后置处理器方法是在Bean实例创建方法之前先尝试用后置处理器返回一个代理对象

    总结

    1. @EnableAspectJAutoProxy 开启AOP功能(会给容器中添加AnnotationAwareAspectJAutoProxyCreator的定义信息)
    2. 容器启动的refresh()方法里registerBeanPostProcessors()进行注册后置处理器,创建AnnotationAwareAspectJAutoProxyCreator对象(它实现了InstantiationAwareBeanPostProcessor)
    3. refresh()的finishBeanFactoryInitialization()初始化剩下的单实例bean
      • 每个bean创建都会先执行postProcessBeforeInstantiation(),该方法判断bean是否是增强或者是切面相关的类,如果是将它保存到advisedBeans中返回null
      • 创建bean-》属性注入-》XxxAware接口-》applyBeanPostProcessorsBeforeInitialization-》init()-》applyBeanPostProcessorsAfterInitialization
      • 通过afterInitialization()判断组件是否需要增强,如果需要将切面的通知方法包装成增强器保存到proxyFactory并创建代理对象
    4. 代理对象执行目标方法时就会通过CglibAopProxy.intercept()进行拦截;它先获取所有的增强器将他们包装成拦截器MethodInterceptor返回一个拦截器链,利用拦截器的链式机制(递归),依次进入每一个拦截器进行执行(注意afterInitialization()的时候对增强器进行了排序)
      • 正常执行:前置通知-》目标方法-》后置通知-》返回通知
      • 出现异常:前置通知-》目标方法-》后置通知-》异常通知

      

      

     

  • 相关阅读:
    九、Shell 流程控制
    八、Shell test 命令
    七、Shell printf 命令
    六、Shell echo命令
    五、Shell 基本运算符
    四、Shell 数组
    三、Shell 传递参数
    二、Shell 变量
    一、Shell 教程
    KVM 介绍(1):简介及安装
  • 原文地址:https://www.cnblogs.com/edda/p/13458735.html
Copyright © 2011-2022 走看看