Spring:助你跟面试官侃一个小时的AOP

477 阅读10分钟
在这里插入图片描述
在这里插入图片描述

使用

代理模式 是常用的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. 数据库操作
  4. ....

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(43);
  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想获取可以关注我公众号【SoWhat1412】回复AOP获取高清图。 在这里插入图片描述

参考

通俗说AOP AOP简单讲解

本文使用 mdnice 排版