SpringAOP系列-1、核心标签注解使用&代理创建流程

当我们聊Spring AOP(Aspect Oriented Programming)这个面向方面编程的时候,我们应该考虑哪些问题?

  • AOP的存在是为了帮助spring解决什么问题?(OOP面向对象编程要想引入一个公共行为时,需要维护大量的继承关系or编写很多重复代码,so...)
  • Spring 提供了哪些AOP的配置、AspectJ注解?(xml和注解,注解包括:@Aspect注解标注POJO,@Piontecut标注切点,@Before\@After\@Around标注advice通知,<aop:aspectj-autoproxy/>标签开启AOP的AspectJ功能,同时使用proxy-target-class、expose-proxy属性定义代理方式)
  • Spring何时、以何种方式将通知植入到目标方法上?即Spring是如何将IOC和AOP整合到一起的?(扩展点BeanPostProcessor植入,最终会生成目标bean的代理对象放入到容器中)
  • Spring AOP默认使用何种代理方式生成bean的代理对象的依据是什么?
  • Spring源码内部根据AOP自定义配置标签&AspectJ注解去创建代理入口&过程?(AspectJAutoProxyBeanDefinitonParser做AOP标签的解析入口);

AOP做了哪些事情?

     当我们需要为多个不具备继承关系的对象引入同一个公共行为时,例如日志、安全监测、事务、监控等;我们只有在每个对象里引入公共行为,致使程序出现大量重复代码,不便维护;所以就有了AOP面向切面编程,所关注的方向是横向的,不同于OOP是纵向的。

AOP提供了哪些自定义标签&属性&注解?

Spring2.0开始使用@AspectJ注解,可以非常容易的定义一个切面;通过切点表达式语法进行切点定义,可以通过切点函数、运算符、通配符等进行切点定义,拥有强大的连接点描述能力。当然xml标签配置的方式仍然支持;

Spring提供了以下几个AspectJ注解:

  • @Aspect:定义一个切面,整合了切点和通知两个模块,切点解决了 where 问题,通知解决了 when 和 how 问题。切面把两者整合起来,就可以解决 对什么方法(where)在何时(when - 前置还是后置,或者环绕)执行什么样的横切逻辑(how)的三连发问题。
  • @Pointer:定义一个切点,切点是用来选择连接点的(每个业务方法的可以看作一个连接点,连接点执行的就是各种通知拦截器);Pointcut 接口中定义了两个接口,分别用于返回类型过滤器和方法匹配器来选择连接点
  • @After:定义一种目标方法执行后的通知( Advice通知即我们定义的横切逻辑,Spring提供了5中类型的通知)
  • @Before:定义一种目标方法执行前的通知,
  • @Around:定义一种目标方法执行前后的环绕通知,
  • @AfterReturn:目标方法执行成功后的通知;
  • @AfterThrowing: 目标方法抛出异常后的通知;

开启AOP功能的标签&配置属性:

<aop:aspectj-autoproxy proxy-target-class="true" expose-proxy="true"/>

spring通过<aop:aspectj-autoproxy />标签开启一个aop注解的支持,同时该标签中定义了两个属性:

  • proxy-target-class:spring AOP部分使用JDK动态代理或者DGLIB来为目标对象创建代理。如果被代理目标对象至少实现一个接口,则会使用JDK动态代理。所有目标类型实现的接口都将被代理。若目标对象没有实现任何接口,则必须创建一个cglib代理。如果你希望强制使用cglib动态代理,也可以通过设置proxy-target-class="true"实现,需要考虑两个问题:(1)、cglig代理无法通知final方法,因为它们不能被复写;(2)、使用cglib动态代理需要将cglib的二进制发行包放在classpath下面。
  • expose-proxy:是为了解决目标对象内部的自我调用无法通过切面进行增强的问题的;例如:
    public interface Aservice{
        public void a();
        public void b();
    }
    
    
    public class AserviceImpl implements Aservice{
        @Transaction(propagation=Propagation.REQUIRED)
        public void a(){
            this.b();        
        }
        
        @Transaction(propagation=Propagation.REQUIRES_NEW)
        public void b(){
            ....
        }
    
    }

    此时,目标对象Aservice内部的自我调用this.b(),无法执行b事务切面,即不会进行事务增强;若果想让this.b()执行事务增强;需要设置expose-proxy="true",同时修改this.b()为((Aservice)AopContext.currentProxy()).b(); 即可完成a和b方法的同时增强;

这里再啰嗦几句jdk动态代理和cglib动态代理的区别:

  • JDK动态代理:其代理对象必须是某个接口的实现,他是通过在运行期间创建一个接口的实现类来完成目标对象的代理。(接口实现关系)
  • CGLIB动态代理:实现原理类似JDK动态代理,都是在运行期间扩展目标对象;只是他是在运行期间生成目标对象的扩展子类(继承关系),底层是通过ASM(开源java字节码编辑类库)操作字节码实现的,性能比JDK强。

AOP如何将通知(advice)织入到目标bean方法中的?

       有了连接点、切点、通知,以及切面等,可谓万事俱备,但是还差了一股东风。这股东风是什么呢?没错,就是织入。所谓织入就是在切点的引导下,将通知逻辑插入到方法调用上,使得我们的通知逻辑在方法调用时得以执行。说完织入的概念,现在来说说 Spring 是通过何种方式将通知织入到目标方法上的。先来说说以何种方式进行织入,这个方式就是通过实现后置处理器 BeanPostProcessor 接口。该接口是 Spring 提供的一个拓展接口,通过实现该接口,用户可在 bean 初始化前后做一些自定义操作。

        那么 Spring 是在何时进行织入操作的呢?答案是在 bean 初始化完成后,即 bean 执行完初始化方法(init-method)。Spring通过切点对 bean 类中的方法进行匹配。若匹配成功,则会为该 bean 生成代理对象,并将代理对象返回给容器。容器向后置处理器输入 bean 对象,得到 bean 对象的代理,这样就完成了织入过程。

Spring源码利用自定义AOP的开启标签&AspectJ注解创建代理对象过程?

先找入口,上面我们聊到spring aop是如何向目标bean中织入通知的,即spring如何AOP和IOC整合到一块儿的;即通过拓展点 BeanPostProcessor 接口。Spring AOP 抽象代理创建器实现了 BeanPostProcessor 接口,并在 bean 初始化后置处理过程中向 bean 中织入通知。相关源码如下:

public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport
        implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware {
    
    /** 
     * bean 初始化后置处理方法 
     */
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (bean != null) {
            Object cacheKey = getCacheKey(bean.getClass(), beanName);
            if (!this.earlyProxyReferences.contains(cacheKey)) {
                // 如果需要被代理,为 bean 生成代理对象
                return wrapIfNecessary(bean, beanName, cacheKey);
            }
        }
        return bean;
    }
    
    protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
        if (beanName != null && this.targetSourcedBeans.contains(beanName)) {
            return bean;
        }
        if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
            return bean;
        }

        /*
         * 如果是基础设施类(Pointcut、Advice、Advisor 等接口的实现类),或是应该跳过的类,
         * 则不应该生成代理,此时直接返回 bean
         */ 
        if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
            // 将 <cacheKey, FALSE> 键值对放入缓存中,供上面的 if 分支使用
            this.advisedBeans.put(cacheKey, Boolean.FALSE);
            return bean;
        }

        // 1、为目标 bean 查找合适的通知器(增强器)
        Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
        /*
         * 若 specificInterceptors != null,即 存在通知器 specificInterceptors != 
         * DO_NOT_PROXY,则为 bean 生成代理对象,否则直接返回 bean
         */ 
        if (specificInterceptors != DO_NOT_PROXY) {
            this.advisedBeans.put(cacheKey, Boolean.TRUE);
            //2、 创建代理
            Object proxy = createProxy(
                    bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
            this.proxyTypes.put(cacheKey, proxy.getClass());
            /*
             * 返回代理对象,此时 IOC 容器输入 bean,得到 proxy。此时,
             * beanName 对应的 bean 是代理对象,而非原始的 bean
             */ 
            return proxy;
        }

        this.advisedBeans.put(cacheKey, Boolean.FALSE);
        // specificInterceptors = null,直接返回 bean
        return bean;
    }
}

这里postProcessAfterInitialization()初始化bean后执行的方法,其实主要做了2件事儿:

  • getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null)获取目标bean的增强器(通知器);通过切点信息为目标bean生成增强器,不同的通知advice注解生成不同的增强器;
  • 如果增强器存在,通过相应的增强器创建代理对象;

(如何匹配增强器?如何根据增强器信息创建代理这里先不用详细知道,后续单独总结)

提供一些推荐参考文档:

发布了42 篇原创文章 · 获赞 6 · 访问量 2622

猜你喜欢

转载自blog.csdn.net/a1290123825/article/details/105182658