Spring5源码13-AOP源码分析(上)

1. Aop介绍及使用

Aop 即面向切面编程,而 Aspect 是Aop 思想的一种实现。并不是所有的AOP框架都相同,它们在连接点模型上可能有强弱之分,有些允许在字段修饰符级别的应用通知,有些只支持方法调用相关的连接点。需要注意的是 Spring 只支持方法级别的连接点

Spring 提供了4种类型的AOP支持:

  • 基于代理的经典Spring Aop
  • 纯Pojo切面
  • @Aspect注解驱动的切面
  • 注入式的Aspectj的切面

前三种都是Spring AOP 实现的变体,Spring AOP 构建在动态代理之上,因此Spring 对Aop的支持局限于方法拦截。

本文中会介绍 经典的的Aop使用(ProxyFactoryBean)和 @Aspect注解驱动的切面。

二者的区别个人理解在于:AspectJ的代理模式:解析@Aspect注解的类,根据方法上的不同注解,来动态封装成Advisor (顾问),里面包含了方法对应的Advice 和指定的切点(这里的切点方法匹配是根据逻辑表达式来,而传统的SpringAop需要自己写判断逻辑,其实也可以写一个逻辑表达式判断,所以这里是一样的,只是实现方式的不同而已)

Spring AopAspectJ 的关系: AspectJ一套AOP框架,是对java语言语法和语义的扩展,所以他自己提供了一套关键字,这也就是说,如果在没有安装 AspectJ的情况下,是无法使用 AspectJ 的。这里需要注意的是,在 Spring框架中使用的 @Aspect 注解实现的 Aop 功能并不是上面所说的AspectJ 框架, 在Spring中使用 @Aspect 注解实现的AOP 功能,其底层实现还是 Spring Aop。

1.1 经典Spring Aop

1.1.1 基本释义

经典的 Spring Aop 中有几个关键类

  • Advisor : 顾问,顾问是Spring提供的另一种切面。可以完成更为复杂的切面织入功能。PointcutAdvisor是顾问的一种,可以指定具体的切入点。顾问将通知进行了包装,会根据不同的通知 类型,在不同的时间点,将切面织入到不同的切入点。通知和顾问都是切面的实现方式增强点, 包含AdvicePointcut。个人认为是 Spring AOP完成增强动作的最小单元。
  • Advice : 通知,通知是Spring提供的一种切面(Aspect)。但是其功能过于简单,只能讲切面织入到目标类的所有目标方法中,无法完成讲切面织入到指定目标方法中。通知实际上使用具体的增强操作,即切面织入之后的实际操作。 Pointcut :切点信息, 这个主要是用来确定切入点在那,即在那切入。

Advisor两个子接口PointcutAdvisorIntroductionAdvisor :

IntroductionAdvisor与PointcutAdvisor 最本质上的区别就是,IntroductionAdvisor只能应用于类级别的拦截,只能使用Introduction型的Advice。而不能像PointcutAdvisor那样,可以使用任何类型的Pointcut,以及几乎任何类型的Advice。

PointCutAdvisor接口 比较常用的两个实现类:

  • 根据切入点(主业务方法)名称织入切面NameMatchMethodPointCutAdvisor
  • 根据自定义的正则表达式织入切面RegexpMethodPointoutAdvisor

1.1.2 简单Demo

声明两个通知类型 。一个是 MethodBeforeAdvice ,一个是 AfterReturningAdvice,见名知意,一个在方法执行前调用,一个在方法执行后调用

@Component
public class DemoAfterReturnAdvice implements AfterReturningAdvice {
    @Override
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        System.out.println("DemoAfterReturnAdvice.afterReturning");
    }
}
@Component
public class DemoBeforeAdvice implements MethodBeforeAdvice {

    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println("DemoBeforeAdvice.before");
    }
}

被代理的目标类 DemoController:

public class DemoController {

    public void hello(String msg) {
        System.out.println("hello " + msg);
    }
}

配置了将代理对象注入,这里可以看到注入的是ProxyFactoryBean 。我们知道FactoryBean会将 getObject 方法的返回值作为结果注入到Spring容器中。这里不难猜测,ProxyFactoryBeangetObject 方法中必定做了代理。

@Configuration
public class AopConfig {

    @Bean("demoController")
    public ProxyFactoryBean proxyFactoryBean() {
        ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean();
        // 设置代理的目标类
        proxyFactoryBean.setTarget(new DemoController());
        // 设置通知拦截器
        proxyFactoryBean.setInterceptorNames("demoAfterReturnAdvice", "demoBeforeAdvice");
        return proxyFactoryBean;
    }
}

我们来调用试试:

@SpringBootApplication
public class AopDemoApplication {
    public static void main(String[] args) {
        ConfigurableApplicationContext run = SpringApplication.run(AopDemoApplication.class, args);
        DemoController demoController = (DemoController) run.getBean("demoController");
        demoController.hello("123");
    }
}

输出结果如下: 

综上:可以看到 经典Spring AOP 起了作用。 核心逻辑就是在 ProxyFactoryBean 中的getObject方法中。

 

@Override
@Nullable
public Object getObject() throws BeansException {
        // 初始化连接器链路
        initializeAdvisorChain();
        if (isSingleton()) {
                // 获取代理类
                return getSingletonInstance();
        }
        else {
                if (this.targetName == null) {
                        logger.info("Using non-singleton proxies with singleton targets is often undesirable. " +
                                        "Enable prototype proxies by setting the 'targetName' property.");
                }
                return newPrototypeInstance();
        }
}

主要步骤:

  1. 在getObject时会调用 initializeAdvisorChain() 根据InterceptorNames来初始化拦截器。
  2. 将拦截器包装成Advisor。(目前看来, Spring AOP一个增强功能最基本的实现单元就是Advisor)
  3. Advisor保存到this.advisors 集合中
  4. newPrototypeInstance();创建代理对象。这里的创建逻辑和@Aspect注解的AOP 实现逻辑基本相同(毕竟@Aspect使用的就是Spring AOP实现的) 5.根据某些条件选择cglib(CglibAopProxy)或者jdk(JdkDynamicAopProxy)代理方式。

关于详细的代码解读,后续会有文章进行解读。

1.2 @Aspect注解驱动的切面

  • 切面(Aspect) :官方的抽象定义为“一个关注点的模块化,这个关注点可能会横切多个对象”。
  • 连接点(Joinpoint) :程序执行过程中的某一行为。
  • 通知(Advice) :“切面”对于某个“连接点”所产生的动作。
  • 切入点(Pointcut) :匹配连接点的断言,在AOP中通知和一个切入点表达式关联。
  • 目标对象(Target Object) :被一个或者多个切面所通知的对象。 AOP代理(AOP Proxy) 在Spring AOP中有两种代理方式,JDK动态代理和CGLIB代理。

1.2.1 定义切点

Spring 借助 AspectJ 的切点表达式语言来定义 Spring 切面

下面是用来定义切点的的描述符:

AspectJ指示器 描述
arg() 限制连接点匹配参数为指定类型的执行方法
@args() 限制连接点匹配参数由指定注解标注的执行方法
execution() 用于匹配是连接点的执行方法
this() 限制连接点匹配 AOP代理的bean引用为指定类型的类
target 限制连接点匹配的目标对象为指定类型的类
@target() 限制连接点匹配特定的执行对象,这些对象对应的类要具有指定类型的注解
within() 限制连接点匹配指定的类型
@within() 限制连接匹配指定注解所标注的类型(当使用 Spring Aop 时,方法定义在由指定的注解所标注的类里)
@annotation 限定匹配带有指定注解的连接点
bean() 限定 bean 的id

上述指示器中,只有 execution() 指示器是实际匹配执行的,其余都是限制匹配的。

execution (* com.kingfish.AopTest.test(..) && within(com.kingfish.AopTest.*))

其中,使用execution() 执行器,选择了 com.kingfish.AopTest.test() 作为切点, * 代表这个方法可以返回任意类型。 .. 代表 这个方法可以使用任意参数。&& 代表与,也可以使用 and(与之类似的还有 || 代表或(or)、!代表非( not )。注意在xml配置中因为 & 具有其他含义,所以可以使用and代替&&) ,within() 代表 连接的一个操作。 within(com.kingfish.AopTest.*) 代表 com.kingfish.AopTest 类 的任意方法被调用。即这个切点的整个意义是, com.kingfish.AopTest.test方法被调用(这个方法可以传递任意参数,也可返回任意类型的返回值) 并且 com.kingfish.AopTest的任意方法被调用。

execution (* com.kingfish.AopTest.test(..) && bean('aop'))

这个切点的整个意义是, com.kingfish.AopTest.test方法被调用(这个方法可以传递任意参数,也可返回任意类型的返回值)但限定bean的Id为 aop

1.2.2 定义切面

Spring 使用 AspectJ 注解来声明通知方法:

注解 通知
@After 通知方法会在目标方法返回或抛出异常后调用
@AfterReturning 通知方法会在目标方法返回后调用
@AfterThrowing 通知方法会在目标方法抛出异常后调用
@Around 通知方法会将目标方法封装起来,环绕通知方式(后续实例中讲解)
@Before 通知方法会在目标方法调用之前执行

1.3 代码实践

上面说的很混乱,如果没有代码对没有接触的过的人不好理解,这里通过代码来进行进一步的分析.

引入依赖:

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.8.5</version>
</dependency>

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjrt</artifactId>
    <version>1.8.9</version>
</dependency>

实体类:

@Component  //切面存在的化就会返回代理对象
public class HelloService {

   public HelloService(){
      System.out.println("....");
   }

   public String sayHello(String name){
      String result = "你好:"+name;
      System.out.println(result);
      int length = name.length();
      return result + "---" + length;
   }
}

编写切面类并定义切点:

/**
 * Spring5以后顺序就一切正常
 * 正常:前置通知===目标方法===返回通知===后置通知
 * 异常: 前置通知===目标方法===异常通知===后置通知
 * try{
 *     前置通知
 *     目标方法的执行
 *     返回通知
 * }catch(){
 *     异常通知
 * }finally{
 *     后置通知
 * }
 */
@Component  //切面也是容器中的组件
@Aspect //说明这是切面
public class LogAspect {

   public LogAspect(){
      System.out.println("LogAspect...");
   }

   //前置通知  增强方法/增强器
   @Before("execution(* com.hsf.spring.aop.HelloService.sayHello(..))")
   public void logStart(JoinPoint joinPoint){
      String name = joinPoint.getSignature().getName();
      System.out.println("logStart()==>"+name+"....【args: "+ Arrays.asList(joinPoint.getArgs()) +"】");
   }

   //返回通知
   @AfterReturning(value = "execution(* com.hsf.spring.aop.HelloService.sayHello(..))",returning = "result")
   public void logReturn(JoinPoint joinPoint,Object result){
      String name = joinPoint.getSignature().getName();
      System.out.println("logReturn()==>"+name+"....【args: "+ Arrays.asList(joinPoint.getArgs()) +"】【result: "+result+"】");
   }


   //后置通知
   @After("execution(* com.hsf.spring.aop.HelloService.sayHello(..))")
   public void logEnd(JoinPoint joinPoint){
      String name = joinPoint.getSignature().getName();
      System.out.println("logEnd()==>"+name+"....【args: "+ Arrays.asList(joinPoint.getArgs()) +"】");
   }


   //异常
   @AfterThrowing(value = "execution(* com.hsf.spring.aop.HelloService.sayHello(..))",throwing = "e")
   public void logError(JoinPoint joinPoint,Exception e){
      String name = joinPoint.getSignature().getName();
      System.out.println("logError()==>"+name+"....【args: "+ Arrays.asList(joinPoint.getArgs()) +"】【exception: "+e+"】");
   }
}

配置类中 开启 AspectJ 代理:

@Configuration
//@Import({Person.class}) // 导入某个bean,默认使用无参构造器创建对象
//@Import(value = {MainConfig.MyImportBeanDefinitionRegistrar.class})
@ComponentScan("com.hsf.spring")  // 默认扫描当前路径下的bean
@EnableAspectJAutoProxy
public class MainConfig {
   @Bean
   public Person getPerson() {
      Person person = new Person();
      person.setName("张三");
      return person;
   }
}

测试类:

public class AnnotationMainTest {
   public static void main(String[] args) {

      AnnotationConfigApplicationContext context =
            new AnnotationConfigApplicationContext(MainConfig.class);

      //AOP,原理测试
      HelloService helloService = context.getBean(HelloService.class);
      helloService.sayHello("zhangsan");
    }
}

运行结果如下:

logStart()==>sayHello....【args: [zhangsan]】
你好:zhangsan
logReturn()==>sayHello....【args: [zhangsan]】【result: 你好:zhangsan---8】
logEnd()==>sayHello....【args: [zhangsan]】

环绕通知的使用:在上面的Demo基础上,我们来看看环绕通知的使用。我们需要继续编写 AopDemo类。仅需在 AopDemo 中添加如下方法

@Around("execution(* com.hsf.spring.aop.HelloService.sayHello(..))")
public void around(ProceedingJoinPoint joinPoint) {
   System.out.println("around start");
   try {
      System.out.println("around before");
      joinPoint.proceed();    // 放行切点的方法,不放行则会阻塞调用
      System.out.println("around after");
   } catch (Throwable throwable) {
      throwable.printStackTrace();
      System.out.println("around throwable");
   }

}

测试结果可以看到如下:

around start
around before
logStart()==>sayHello....【args: [zhangsan]】
你好:zhangsan
logReturn()==>sayHello....【args: [zhangsan]】【result: 你好:zhangsan---8】
logEnd()==>sayHello....【args: [zhangsan]】
around after

1.4 MethodInterceptor和HandlerInterceptor区别:

  • HandlerInterceptor :是Spring mvc提供的拦截器,这种拦截器的生效时机实在DispatcherServlet分发请求时生效。并非是依赖于SpringAOP功能。正因如此,是只能拦截Controller层的方法请求。使用时需要重写WebMvcConfigurerAdapter.addInterceptors方法,来添加指定的拦截器。

  • org.springframework.cglib.proxy.MethodInterceptor :这个是 Cglib 进行代理时所使用的拦截器。

  • org.aopalliance.intercept.MethodInterceptor :就是利用Spring AOP生成的拦截器。所以实际上MethodInterceptor的实现也就是Spring Aop的实现,和之前写的ProxyFactoryBean的用法相同,可以拦截所有层面的方法。其实现也继承了 Advice 接口,结构如下:

2. AOP注入了那些组件?@EnableAspectJAutoProxy

在使用AspectJ AOP 功能时,我们需要使用注解 @EnableAspectJAutoProxy 来开启Aop 功能。那么我们的分析入口自然是从这个注解开始。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {
   // 是否代理目标对象:即是否使用cglib 代理
   boolean proxyTargetClass() default false;
   
   // 是否暴露代理对象
   boolean exposeProxy() default false;
}

 

分析了这么久的源码,从上面我们可以看到 @EnableAspectJAutoProxy 注解 中使用了 @Import(AspectJAutoProxyRegistrar.class) 注解引入了AspectJAutoProxyRegistrar 类,因此我们下面来看看 AspectJAutoProxyRegistrar 类的实现。其继承图:

AspectJAutoProxyRegistrar 实现了 ImportBeanDefinitionRegistrar 接口,那么我们自然要看看他的registerBeanDefinitions 方法了,registerBeanDefinitions 方法的作用是在Spring进入下一步动作之前可以添加BeanDefinition,而Spring Aop 在这里将会将自动代理创建器 AbstractAutoProxyCreator添加到Spring容器中,AbstractAutoProxyCreator 是Spring 实现Aop的核心类。(Spring 在 ConfigurationClassPostProcessor 中完成了对 ImportBeanDefinitionRegistrar 接口的处理,主要功能还是将BeanDefinition保存,等待Spring解析加载到容器中。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值