SpringAop也不难

作者: 关捷 | 来源:发表于2018-12-01 09:16 被阅读7次

    AOP像OOP一样,是一种独立于语言的编程范式,实现AOP协议的方式多种多样,包括:运行时、编译器植入、代理等,而SpringAop的采用的是动态代理与Cglib静态植入。

    添加依赖

    IOC容器的基础上添加Aop相关依赖。

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aop</artifactId>
    </dependency>
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.8.9</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aspects</artifactId>
    </dependency>
    

    编程式Aop演示

    ProxyFactory生成代理类是SpringAop底层真正的实现

    目标类与接口

    interface RunIntf {
        void work();
        void work1();
    }
    static class SimpleRun implements RunIntf {
        @Override
        public void work() {
            System.out.println("SimpleRun.work()");
        }
        @Override
        public void work1() {
            System.out.println("SimpleRun.work1()");
        }
    }
    

    通知

    static class SimpleAdvice implements MethodInterceptor {
        @Override
        public Object invoke(MethodInvocation invocation) throws Throwable {
            System.out.println("SimpleAdvice.before");
            return invocation.proceed();
        }
    }
    

    切面

    static class SimplePointCutAdvisor extends AbstractPointcutAdvisor {
        @Override
        public Advice getAdvice() {
            return new SimpleAdvice();
        }
    
        @Override
        public Pointcut getPointcut() {
            JdkRegexpMethodPointcut jdkRegexpMethodPointcut = new JdkRegexpMethodPointcut();
            jdkRegexpMethodPointcut.setPattern(".*work1.*");
            return jdkRegexpMethodPointcut;
        }
    }
    

    运行

    public static void main(String[] args) {
        ProxyFactory proxyFactory = new ProxyFactory();
        proxyFactory.addAdvisors(new SimplePointCutAdvisor());
        proxyFactory.setTarget(new SimpleRun());
        proxyFactory.setInterfaces(RunIntf.class);
        RunIntf runIntf = (RunIntf) proxyFactory.getProxy();
        runIntf.work();
        runIntf.work1();
    }
    

    结果

    SimpleRun.work()
    SimpleAdvice.before
    SimpleRun.work1()
    

    可以看到,work1方法被植入了切面逻辑。由此可见,我们通过提供目标类、目标类接口、切面逻辑,通过ProxyFactory生成了带有切面功能的代理类。

    AOP概念:

    • Aspect:切面,包括切入点和切面,SimplePointCutAdvisor就是一个切面。
    • Advice:通知,指的是切面提供的处理逻辑,例如上例中的SimpleAdvice。
    • Pointcut:切入点,指的是如何定位切面的手段,在Spring中指的是切面表达式。

    分析

    ProxyFactory是Spring中用来生成代理类的工厂,包装了生成代理类的细节,包括:Cglib方式和Jdk动态代理方式。

    接下来我们说一说通过JDK动态代理方式生成代理类的JdkDynamicAopProxy,而这是建立在熟悉Jdk动态代理基础上的:动态代理系列(二)JDK动态代理

    调用切面通知链

    执行切面逻辑,很简单,如下图:

    说白了,依次执行切面逻辑,最后执行目标类方法。

    如果每个切面方法都返回布尔类型,我们可以顺序遍历切面逻辑,根据返回值来判断是否继续传递,这是链式处理比较常见的方式,实现也比较简单,比较容易想到。

    但Spring并不是使用的这种方式,切面方法也并没有返回布尔类型。

    那么Spring是如何执行切面逻辑的呢?

    在代理类中,通过把目标类和切面包装为MethodInvocation对象,切面方法的入参正好也是这个对象。
    代理类被调用时,会先执行MethodInvocation.proceed(),执行切面逻辑,而切面是否继续传递就看是否继续执行MethodInvocation.proceed()方法,实现如下:

    public class SimpleMethodInvocation implements MethodInvocation {
        Object target;
        List<MethodInterceptor> list;
        int i = 0;
        @Override
        public Object proceed() throws Throwable {
            if (i != list.size()) {
                list.get(i++).invoke(this); //调用切面逻辑
            } else {
                return 反射调用(target);
            }
            return null;
        }
    }
    

    基于IOC的Aop

    先通过例子展示基于IOC的Aop的实现

    业务类

    package com.esy.stu.aop;
    import org.springframework.stereotype.Component;
    
    @Component
    public class Car {
        public void work() {
            System.out.println("Car动了");
        }
    }
    

    切面逻辑

    package com.esy.stu.aop;
    
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.springframework.stereotype.Component;
    
    @Component
    @Aspect
    public class SimpleAspact {
        @Before("within(com.esy.stu.aop.Car)")
        public void t1() {
            System.out.println("before");
        }
    }
    

    启动类

    package com.esy.stu.aop;
    
    import org.springframework.context.annotation.AnnotationConfigApplicationContext;
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.EnableAspectJAutoProxy;
    
    @Configuration
    @EnableAspectJAutoProxy
    @ComponentScan(basePackages = "com.esy.stu.aop")
    public class Boot {
        public static void main(String[] args) {
            AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(Boot.class);
            applicationContext.getBean(Car.class).work();
        }
    }
    

    执行结果:

    before
    Car动了
    

    分析

    从上面可以发现,与一般IOC容器启动相比较,多了一个使用@Aspect标记的切面逻辑的类,并且配置类上面也多了一个注解EnableAspectJAutoProxy,先分析下这个配置注解。

    @Import(AspectJAutoProxyRegistrar.class)
    public @interface EnableAspectJAutoProxy {
    

    EnableAspectJAutoProxy注解其实是引入AspectJAutoProxyRegistrar注册代理类创建器,如下图:

    它Aop的关键,这张图也把AnnotationAwareAspectJAutoProxyCreator分为两个部分:

    1. BeanPostProcessor(AOP就是对业务类进行代理,是由Bean处理器来触发的)
    2. ProxyCreator(代理类生成工厂)

    创建代理

    如果了解IOC实例化过程,就会知道,Bean完全初始化之前可以作为半成品被使用,并且能够被其他bean注入。由此可以推测,在引用加入半成品之前,对象就已经被代理对象所替换了,不然其他bean注入就该是目标类了。

    下面是核心的源码:

    protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args)
          throws BeanCreationException {
       // 反射生成Bean
       BeanWrapper instanceWrapper = createBeanInstance(beanName, mbd, args);
       
    //这里将完成AOP替换,将目标类的引用替换成代理类,并存储到半成品容器中
          addSingletonFactory(beanName, new ObjectFactory<Object>() {
             @Override
             public Object getObject() throws BeansException {
                return getEarlyBeanReference(beanName, mbd, bean);
             }
          });
       
       // 装配Bean
       populateBean(beanName, mbd, instanceWrapper);
       return exposedObject;
    }
    

    getEarlyBeanReference方法中调用组件,其中就有AbstractAutoProxyCreator#getEarlyBeanReference
    使用的ProxyFactory生成代理类。

    关键点

    找到切面声明

    BeanFactoryAspectJAdvisorsBuilder#buildAspectJAdvisors
    遍历所有的bean定义,找到Aspect注解标注的类

    获取advice

    通过Around、Before、After、AfterReturning、AfterThrowing这几种不同的注解包装方法成为不同的通知对象。

    注解 通知对象
    Around AspectJAroundAdvice
    Before AspectJMethodBeforeAdvice
    After AspectJAfterAdvice
    AfterReturning AspectJAfterReturningAdvice
    AfterThrowing AspectJAfterThrowingAdvice

    如果研究就会发现AspectJMethodBeforeAdvice、AspectJAfterReturningAdvice、AspectJAfterThrowingAdvice并不满足MethodInterceptor接口,需要进一部包装成AfterReturningAdviceAdapter、MethodBeforeAdviceAdapter、ThrowsAdviceAdapter。

    构造Advisor

    ReflectiveAspectJAdvisorFactory#getAdvisors
    包装通知和切面表达式为InstantiationModelAwarePointcutAdvisorImpl。

    可以说整个Aop的核心就是ProxyFactory,而IOC容器提供的功能就是构造advice和pointcut。

    相关文章

      网友评论

        本文标题:SpringAop也不难

        本文链接:https://www.haomeiwen.com/subject/aeboqqtx.html