zoukankan      html  css  js  c++  java
  • 【Spring】3.助你跟面试官侃一个小时的AOP

    在这里插入图片描述

    使用

    代理模式 是常用的java设计模式,他的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。按照代理的创建时期代理类可以分为两种。

    1. 静态代理:

    原理:由程序员创建或特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在了。

    原理:在编译期,切面直接以字节码形式编译到目标字节码文件中
    优缺点:对系统性能无影响但是不够灵活

    2.动态AOP

    1. 动态代理

    原理:在运行期,目标类加载,通过反射机制为接口动态生成代理类。将切面织入到代理类中
    优缺点:更灵活,但是切入的关注点要实现接口,如果没有实现接口则不能使用JDK代理。使用反射大量生成类文件可能引起Full GC造成性能影响,因为字节码文件加载后会存放在JVM运行时区的方法区(或者叫持久代)中,当方法区满的时候,会引起Full GC,所以当你大量使用动态代理时,可以将持久代设置大一些,减少Full GC次数。

    1. Cglib 动态字节码生成

    原理:在运行期,目标类加载后,动态构建字节码文件生成目标类的子类,将切面逻辑加入到子类中
    优缺点:没有接口也可以织入,扩展类的实例方法为final时,无法进行织入。

    1. 自定义类加载器

    原理:在运行期,目标加载前,将切面逻辑加到目标字节码里,Javassist
    优缺点:可以对绝大部分类进行织入,代码中若使用了其它类加载器,则这些类将不会被织入。

    1. 字节码转换

    原理:在运行期,所有类加载器加载字节码前进行拦截。
    优缺点:可以对所有类进行织入

    在这里插入图片描述

    Spring AOP使用

    Aop(Aspect Oriented Programming),面向切面编程,这是对面向对象思想的一种补充。
    面向切面编程,就是在程序运行时,不改变程序源码的情况下,动态的增强方法的功能,常见的使用场景非常多:

    1. 日志
    2. 事务
    3. 数据库操作

    Spring默认采取的动态代理机制实现AOP,当动态代理不可用时(代理类无接口)会使用CGlib机制。但Spring的AOP有一定的缺点,第一个只能对方法进行切入,不能对接口,字段,静态代码块进行切入(切入接口的某个方法,则该接口下所有实现类的该方法将被切入)。第二个同类中的互相调用方法将不会使用代理类。因为要使用代理类必须从Spring容器中获取Bean。第三个性能不是最好的,使用自定义类加载器,性能要优于动态代理和CGlib。

    工程中业务代码前后,无一例外,都有很多模板化的代码,而解决模板化代码,消除臃肿就是 Aop 的强项。Spring概述 说过Spring的一个核心就是引入了切面概念,先看下如何用的。

    1. 引入aspects 包
    		<dependency>
    			<groupId>org.springframework</groupId>
    			<artifactId>spring-aspects</artifactId>
    			<version>5.0.6.RELEASE</version>
    		</dependency>
    
    1. 真正的实体化方法
    // 计算类
    public class Calculator {
    	//业务逻辑方法
    	public int div(int i, int j)  {
    		System.out.println("--------");
    		return i/j;
    	}
    }
    
    //日志切面类
    @Aspect
    public class LogAspects {
        @Pointcut("execution(public int com.enjoy.cap10.aop.Calculator.*(..))")
        public void pointCut() {
        }
    
        //@before代表在目标方法执行前切入, 并指定在哪个方法前切入  获得方法名, 方法参数列表
        @Before("pointCut()")
        public void logStart(JoinPoint joinPoint) {
            System.out.println(joinPoint.getSignature().getName() + "除法运行.Before 参数列表是:{" + Arrays.asList(joinPoint.getArgs()) + "}");
        }
    
        @After("pointCut()")
        public void logEnd(JoinPoint joinPoint) {
            System.out.println(joinPoint.getSignature().getName() + "除法结束.After.....");
    
        }
         // 结果获得	
        @AfterReturning(value = "pointCut()", returning = "result")
        public void logReturn(Object result) {
            System.out.println("除法正常返回..AfterReturning..运行结果是:{" + result + "}");
        }
    
        @AfterThrowing(value = "pointCut()", throwing = "exception")
        public void logException(Exception exception) {
            System.out.println("运行异常. AfterThrowing...异常信息是:{" + exception + "}");
        }
    	
    	@Around("pointCut()")
    	public Object Around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
    		System.out.println("@Arount:执行目标方法之前...");
    		Object obj = proceedingJoinPoint.proceed(); // 相当于开始调div地
    		System.out.println("@Arount:执行目标方法之后...");
    		return obj;
    	}
    }
    
    1. 系统调用
    /*
     * 日志切面类的方法需要动态感知到div()方法运行, 
     *  通知方法:
     *     前置通知:logStart(); 在我们执行div()除法之前运行(@Before)
     *     后置通知:logEnd();在我们目标方法div运行结束之后 ,不管有没有异常(@After)
     *     返回通知:logReturn();在我们的目标方法div正常返回值后运行(@AfterReturning)
     *     异常通知:logException();在我们的目标方法div出现异常后运行(@AfterThrowing)
     *     环绕通知:动态代理, 需要手动执行joinPoint.procced()(其实就是执行我们的目标方法div,), 执行之前div()相当于前置通知, 执行之后就相当于我们后置通知(@Around)
     */
    @Configuration
    @EnableAspectJAutoProxy
    public class AspectTest {
    	@Bean
    	public Calculator calculator(){
    		return new Calculator();
    	}
    	@Bean  // 切记要将切面注册到容器中
    	public LogAspects logAspects(){
    		return new LogAspects();
    	}
    	public static void main(String[] args) {
    		AnnotationConfigApplicationContext app = new AnnotationConfigApplicationContext(AspectTest.class);
    
    		Calculator c = app.getBean(Calculator.class);
    		int result = c.div(4, 3);
    		System.out.println(result);
    		app.close();
    	}
    }
    

    在这里插入图片描述
    小结: AOP看起来很麻烦, 只要3步就可以了:

    1. 将业务逻辑组件和切面类都加入到容器中, 告诉spring哪个是切面类(@Aspect)
    2. 在切面类上的每个通知方法上标注通知注解, 告诉Spring何时运行(写好切入点表达式,参照官方文档)
    3. 开启基于注解的AOP模式 @EableXXXX

    AOP源码跟踪

    目的:看AOP给容器中注册了什么组件,这个组件什么时候工作,这个组件的功能是什么?
    入口@EnableAspectJAutoProxy,核心从这个入手,AOP整个功能要启作用,就是靠这个,加入它才有AOP

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Import(AspectJAutoProxyRegistrar.class) // 此乃重点 引入此类。
    public @interface EnableAspectJAutoProxy {
        //默认false,采用JDK动态代理织入增强(实现接口的方式);
        // 如果设为true,则采用CGLIB动态代理织入增强
    	boolean proxyTargetClass() default false;
    	//通过aop框架暴露该代理对象,aopContext能够访问
    	boolean exposeProxy() default false;
    }
    

    AspectJAutoProxyRegistrar, 并实现了ImportBeanDefinitionRegistrar接口,ImportBeanDefinitionRegistrar能给容器中自定义注册组件。

    class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {
    	@Override
    	public void registerBeanDefinitions(
    			AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    			AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);
    			// 上面这个是重点
    			....
             }
    }
    
    	@Nullable
    	public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry) {
    		return registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry, null);
    	}
    
    	@Nullable
    	public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry,
    			@Nullable Object source) {
    
    		return registerOrEscalateApcAsRequired(AnnotationAwareAspectJAutoProxyCreator.class, registry, source);// 进入
    	}
    
    	@Nullable  // cls = AnnotationAwareAspectJAutoProxyCreator.class
    	private static BeanDefinition registerOrEscalateApcAsRequired(Class<?> cls, BeanDefinitionRegistry registry,@Nullable Object source) {
    
    		Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
    
    		if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {
    			BeanDefinition apcDefinition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
    			if (!cls.getName().equals(apcDefinition.getBeanClassName())) {
    				int currentPriority = findPriorityForClass(apcDefinition.getBeanClassName());
    				int requiredPriority = findPriorityForClass(cls);
    				if (currentPriority < requiredPriority) {
    					apcDefinition.setBeanClassName(cls.getName());
    				}
    			}
    			return null;
    		}
    
    		RootBeanDefinition beanDefinition = new RootBeanDefinition(cls);
    		beanDefinition.setSource(source);
    		beanDefinition.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE);
    		beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
    		registry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME, beanDefinition); // 重点注册
    // name = internalAutoProxyCreator class 就是 AnnotationAwareAspectJAutoProxyCreator
    		return beanDefinition;
    	}
    

    结论:因此我们要重点研究AnnotationAwareAspectJAutoProxyCreator组件(ASPECT自动代理创建器), 研究这个透了, 整个原理也就明白了, 所有的原理就是看容 器注册了什么组件, 这个组件什么时候工作, 及工作时候的功能是什么? 只要把这几个研究清楚了,原理就都清楚了。
    在这里插入图片描述
    那我们来分析做为beanPostProcessor后置处理器做了哪些工作, 做为BeanFactoryAware又做了哪些工作。

    现有个这样的思想声明跟创建还有注入,AOP的Bean相比与普通的Bean 区别无非就是用RootBeanDefinitionAUTO_PROXY_CREATOR_BEAN_NAME = AnnotationAwareAspectJAutoProxyCreator声明下,然后在我们创建Bean的时候相比于普通Bean的创建,它的创建时间比较早。
    一句话就是将切面核心类声明然后注入到容器中,并且要早于业务普通Bean

    创建和注册AnnotationAwareAspectJAutoProxyCreator的流程

    用人话简单说下思路无非就是先把我们需要的Bean 声明下,然后再进行创建跟注册。下面是AOP核心类的过程。

    1. register()传入配置类,准备创建ioc容器
    2. 注册配置类,调用refresh()刷新创建容器;
    3. registerBeanPostProcessors(beanFactory);注册bean的后置处理器来方便拦截bean的创建(主要是分析创建AnnotationAwareAspectJAutoProxyCreator);
    1. 先获取ioc容器已经定义了的需要创建对象的所有BeanPostProcessor
    2. 给容器中加别的BeanPostProcessor
    3. 优先注册实现了PriorityOrdered接口的BeanPostProcessor;
    4. 再给容器中注册实现了Ordered接口的BeanPostProcessor;
    5. 注册没实现优先级接口的BeanPostProcessor;
    6. 注册BeanPostProcessor,实际上就是创建BeanPostProcessor对象,保存在容器中;创建internalAutoProxyCreator的BeanPostProcessor【其实就是AnnotationAwareAspectJAutoProxyCreator】
    1. 创建Bean的实例
    2. populateBean;给bean的各种属性赋值
    3. initializeBean:初始化bean;
    1. invokeAwareMethods():处理Aware接口的方法回调
    2. iapplyBeanPostProcessorsBeforeInitialization():应用后置处理器的postProcessBeforeInitialization()
    3. invokeInitMethods();执行自定义的初始化方法
    4. applyBeanPostProcessorsAfterInitialization();执行后置处理器的postProcessAfterInitialization()
    1. BeanPostProcessor(AnnotationAwareAspectJAutoProxyCreator)创建成功:aspectJAdvisorsBuilder
    1. 把BeanPostProcessor注册到BeanFactory中:beanFactory.addBeanPostProcessor(postProcessor);

    Calculator的装载跟使用

    有点绕,懒的写了,基本的思想就是进行业务类代码的单实例装载,期间会有各种判断,最终会对该类中的一些方法进行增强(AnnotationAwareAspectJAutoProxyCreator就是干这个事的,会选择性的判断我们的业务类是否需要增强),然后最终搞成了一个目标类,此时我们再Calculator c这样再通过c调用方法的时候就不是简单的方法调用了。大致就是下面两个步骤:

    1. 获取拦截链–MethodInterceptor
    2. 链式调用通知方法

    在这里插入图片描述

    AOP核心源码流程图

    网上找的,跟着源码一步步看还挺通俗易懂的(AOP源码流程图),PS想获取可以关注我公众号回复AOP获取高清图。
    在这里插入图片描述

    参考

    通俗说AOP
    AOP简单讲解

  • 相关阅读:
    Java编程思想读书笔记 第十章 内部类
    利用lambda和条件表达式构造匿名递归函数
    概率论与数理统计-课程小报告
    leetcode226 翻转二叉树
    leetcode199 二叉树的右视图
    leetcode114- 二叉树展开为链表
    leetcode145 二叉树的后序遍历 特别注意迭代
    leet144 二叉树的前序遍历
    leetcode113 路径总和2 特别关注
    leetcode 112 路径总和 特别关注
  • 原文地址:https://www.cnblogs.com/sowhat1412/p/12734067.html
Copyright © 2011-2022 走看看