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简单讲解

  • 相关阅读:
    metal的gpu query
    体积雾 global fog unity 及改进
    hdr rt format对颜色的影响
    unity deferred lighting
    unity linear space时 photoshop blend的正确设置
    unity linear work flow
    一些数据 bandwidth之类
    deferred rendering with msaa
    unity 显示mipmaplevel
    【转】在C#中使用SendMessage
  • 原文地址:https://www.cnblogs.com/sowhat1412/p/12734067.html
Copyright © 2011-2022 走看看