【Spring】3.助你跟面试官侃一个小时的AOP
使用
代理模式 是常用的java设计模式,他的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。按照代理的创建时期
,代理类可以分为两种。
- 静态代理:
原理:由程序员创建或特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在了。
原理:在
编译期
,切面直接以字节码形式编译到目标字节码文件中
优缺点:对系统性能无影响但是不够灵活
2.动态AOP
原理:在
运行期
,目标类加载后
,通过反射机制
为接口动态生成代理类。将切面织入到代理类中
优缺点:更灵活,但是切入的关注点要实现接口,如果没有实现接口则不能使用JDK代理。使用反射大量生成类文件可能引起Full GC造成性能影响,因为字节码文件加载后会存放在JVM运行时区的方法区(或者叫持久代)中,当方法区满的时候,会引起Full GC,所以当你大量使用动态代理时,可以将持久代设置大一些,减少Full GC次数。
- Cglib 动态字节码生成
原理:在
运行期
,目标类加载后,动态构建字节码文件生成目标类的子类,将切面逻辑加入到子类中
优缺点:没有接口也可以织入,扩展类的实例方法为final时,无法进行织入。
- 自定义类加载器
原理:在
运行期
,目标加载前,将切面逻辑加到目标字节码里,Javassist。
优缺点:可以对绝大部分类进行织入,代码中若使用了其它类加载器,则这些类将不会被织入。
- 字节码转换
原理:在
运行期
,所有类加载器加载字节码前进行拦截。
优缺点:可以对所有类进行织入
Spring AOP使用
Aop(Aspect Oriented Programming),面向切面编程,这是对面向对象思想的一种补充。
面向切面编程,就是在程序运行时,不改变程序源码的情况下,动态的增强方法的功能,常见的使用场景非常多:
- 日志
- 事务
- 数据库操作
- …
Spring默认采取的动态代理
机制实现AOP,当动态代理不可用时(代理类无接口)会使用CGlib
机制。但Spring的AOP有一定的缺点,第一个只能对方法进行切入,不能对接口,字段,静态代码块进行切入(切入接口的某个方法,则该接口下所有实现类的该方法将被切入)。第二个同类中的互相调用方法将不会使用代理类。因为要使用代理类必须从Spring容器中获取Bean。第三个性能不是最好的,使用自定义类加载器,性能要优于动态代理和CGlib。
工程中业务代码前后,无一例外,都有很多模板化的代码,而解决模板化代码,消除臃肿就是 Aop 的强项。Spring概述 说过Spring的一个核心就是引入了切面概念,先看下如何用的。
- 引入aspects 包
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.0.6.RELEASE</version>
</dependency>
- 真正的实体化方法
// 计算类
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;
}
}
- 系统调用
/*
* 日志切面类的方法需要动态感知到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步就可以了:
- 将业务逻辑组件和切面类都加入到容器中, 告诉spring哪个是切面类(@Aspect)
- 在切面类上的每个通知方法上标注通知注解, 告诉Spring何时运行(写好切入点表达式,参照官方文档)
- 开启基于注解的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 区别无非就是用RootBeanDefinition
将AUTO_PROXY_CREATOR_BEAN_NAME = AnnotationAwareAspectJAutoProxyCreator
声明下,然后在我们创建Bean的时候相比于普通Bean的创建,它的创建时间比较早。
一句话就是将切面核心类声明然后注入到容器中,并且要早于业务普通Bean
创建和注册AnnotationAwareAspectJAutoProxyCreator的流程
用人话简单说下思路无非就是先把我们需要的Bean 声明下,然后再进行创建跟注册。下面是AOP核心类的过程。
- register()传入配置类,准备创建ioc容器
- 注册配置类,调用refresh()刷新创建容器;
- registerBeanPostProcessors(beanFactory);注册bean的后置处理器来方便拦截bean的创建(主要是分析创建AnnotationAwareAspectJAutoProxyCreator);
- 先获取ioc容器已经定义了的需要创建对象的所有BeanPostProcessor
- 给容器中加别的BeanPostProcessor
- 优先注册实现了PriorityOrdered接口的BeanPostProcessor;
- 再给容器中注册实现了Ordered接口的BeanPostProcessor;
- 注册没实现优先级接口的BeanPostProcessor;
- 注册BeanPostProcessor,实际上就是创建BeanPostProcessor对象,保存在容器中;创建internalAutoProxyCreator的BeanPostProcessor【其实就是AnnotationAwareAspectJAutoProxyCreator】
- 创建Bean的实例
- populateBean;给bean的各种属性赋值
- initializeBean:初始化bean;
- invokeAwareMethods():处理Aware接口的方法回调
- iapplyBeanPostProcessorsBeforeInitialization():应用后置处理器的postProcessBeforeInitialization()
- invokeInitMethods();执行自定义的初始化方法
- applyBeanPostProcessorsAfterInitialization();执行后置处理器的postProcessAfterInitialization()
- BeanPostProcessor(AnnotationAwareAspectJAutoProxyCreator)创建成功:aspectJAdvisorsBuilder
- 把BeanPostProcessor注册到BeanFactory中:beanFactory.addBeanPostProcessor(postProcessor);
Calculator的装载跟使用
有点绕,懒的写了,基本的思想就是进行业务类代码的单实例装载,期间会有各种判断,最终会对该类中的一些方法进行增强(AnnotationAwareAspectJAutoProxyCreator就是干这个事的,会选择性的判断我们的业务类是否需要增强),然后最终搞成了一个目标类,此时我们再Calculator c
这样再通过c
调用方法的时候就不是简单的方法调用了。大致就是下面两个步骤:
- 获取拦截链–MethodInterceptor
- 链式调用通知方法
AOP核心源码流程图
网上找的,跟着源码一步步看还挺通俗易懂的(AOP源码流程图),PS想获取可以关注我公众号回复AOP获取高清图。