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

posted @ 2020-04-12 11:14  sowhat1412  阅读(178)  评论(0编辑  收藏  举报