文档章节

Spring 源码分析(三) —— AOP(二)Spring AOP 整体架构

水门-kay
 水门-kay
发布于 2016/03/08 03:50
字数 2268
阅读 2733
收藏 12

Spring AOP 架构

        先是生成代理对象,然后是拦截器的作用,最后是编织的具体实现。这是AOP实现的三个步骤,当然Spring AOP也是一样。

        而从Spring AOP整体架构上看其核心都是建立在代理上的。当我们建立增强实例时,我们必须先使用 ProxyFactory 类加入我们需要织入该类的所有增强,然后为该类创建代理。一般而言,AOP实现代理的方法有三种,而 Spring 内部用到了其中的两种方法:动态代理和静态代理,而动态代理又分为JDK动态代理和CGLIB代理。而前面提到的 ProxyFactory 类控制着 Spring AOP 中的织入和创建代理的过程。在真正创建代理之前,我们必须指定被增强对象或者目标对象。我们是通过 setTarget() 方法来完成这个步骤的。而 ProxyFactory 内部将生成代理的过程全部转给 DefaultAopProxyFactory 对象来完成,然后根据设置转给其他的类来完成。下面是 Spring AOP 生成代理的原理图:

        而特别需要注意的是,在 Spring AOP 中,一个 Advisor(通知者,也翻作 通知器或者增强器)就是一个切面,他的主要作用是整合切面增强设计(Advice)和切入点设计(Pointcut)Advisor有两个子接口:IntroductionAdvisor 和 PointcutAdvisor,基本所有切入点控制的 Advisor 都是由 PointcutAdvisor 实现的。而下面的是Spring AOP的整体架构图

        乍一看,非常的复杂,但如果结合上面的Spring AOP生成代理的原理图一起看,也就那么回事,只是丰富的许多属性了,我们会慢慢介绍的。


Spring AOP 使用范例

        项目结构

        分析 Spring AOP 源码的话,直接从最简单业务代码入手,一步步的对源码进行解析。希望借此能看清楚 Spring AOP 纵向和横向代码之间的联系,而且思维也不会太跳跃。下面就是 Spring AOP 测试程序 Test  项目的结构图:

        注:Spring 源码版本是 Spring-4.1.6,我的源码分析也是基于这一版本的,JDK 版本是 1.8,aspect 是用的 aspectjrt-1.7.4 和 aspectjweaver-1.7.4。


        创建用于拦截的Bean

        在实际项目中,Bean应该可以算是核心逻辑了,其中的 printTest 方法中可能会封装整个的核心业务逻辑,如此重要的地方我们想在printTest方法前后加入日志来进行跟踪或者调试也是很自然地想法,但是如果直接修改源码是不符合面向对象的设计原则的,它并不符合面向对象的设计方式,而且随意的改动原有的代码也会造成一定的风险,这里就体现 AOP 的优越性了。

package test;

/**
 * 测试Bean
 *
 * @Auther kay
 * @Date   2016-03-08
 */
public class TestBean {

    private String testStr = "testStr";

    public String getTestStr(){
        return testStr;
    }

    public void setTestStr(String testStr){
        this.testStr = testStr;
    }

    public void printTest(){
        System.out.println("test Bean");
    }
}


        创建Advisor

        Spring AOP 实现的核心,这里采用了@AspectJ注释对POJO进行标注,使AOP的工作大大简化。

package test;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;

/**
 * Aspect切面
 *
 * @Auther kay
 * @Date   2016-03-08
 */
@Aspect
public class AspectTest {

    /**
     * 配置切入点,主要为方便同类中其他方法使用此处配置的切入点
     */
    private final String POINT_CUT = "execution(* test.TestBean.*(..))";

    /**
     * 配置前置通知,使用在方法aspect()上注册的切入点
     * 同时接受JoinPoint切入点对象,可以没有该参数
     */
    @Before(POINT_CUT)
    public void beforeTest(){
        System.out.println("before Test");
    }

    /**
     * 配置后置通知,使用在方法aspect()上注册的切入点
     */
    @After(POINT_CUT)
    public void afterTest(){
        System.out.println("after Test");
    }

    /**
     * 配置环绕通知,使用在方法aspect()上注册的切入点
     *
     * @param point JoinPoint的支持接口
     * @return Object
     */
    @Around(POINT_CUT)
    public Object arountTest(ProceedingJoinPoint point){
        System.out.println("before1");
        Object object = null;
        
        try{
            object = point.proceed();
        }
        catch (Throwable e){
            e.printStackTrace();
        }
        System.out.println("after1");

        return object;
    }
}


        配置文件

        XML是Spring的基础,尽管Spring一再简化配置,并且大有使用注解取代XML配置之势,但无论如何XML还是Spring的基石。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation=" http://www.springframework.org/schema/beans
                            http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
                            http://www.springframework.org/schema/aop
                            http://www.springframework.org/schema/aop/spring-aop-4.1.xsd">

    <!-- 激活自动代理功能 -->
    <aop:aspectj-autoproxy />

    <!-- 业务逻辑切面配置 -->
    <bean id="test" class = "test.TestBean" />
    <bean class="test.AspectTest" />

</beans>


        测试

        测试程序,验证效果。

package test;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * 测试程序
 *
 * @Auther kay
 * @Date   2016-03-08
 */
public class Test {

    public static void main(String[] arge){
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
        TestBean bean = context.getBean("test", TestBean.class);

        bean.printTest();
    }
}


        打印结果

        控制台打印如下代码:

        Spring AOP是如何实现AOP的?首先我们知道,Spring是否支持注释的AOP是由一个配置文件来控制的,也就是<aop:aspectj-autoproxy />,下面我们分析就从这句配置开始。


Spring AOP 的入口

        BeanDefinition 的解析

        首先spring-config.xml配置文件里的<aop:aspectj-autoproxy>进行解析,发现其如果不是bean标签,则会用不同的类来解析。解析AOP的是:http\://www.springframework.org/schema/aop=org.springframeworl.aop.config.AopNamespaceHandler。也就是  AopNamespaceHandler 类来解析,我们对 AopNamespaceHandler 类进行分析

        发现 AopNamespaceHandler 的一段代码是 <aop:aspectj-autoproxy> 解析的入口

AopNamespaceHandler.java

        registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());

                

        BeanDefinition 的载入

        由此,我们知道只要配置文件中出现“aspectj-autoproxy”的注解时就会使用解析器对 AspectJAutoProxyBeanDefinitionParser 进行解析

        所有解析器,都是由 BeanDefinitionParser 接口的统一实现,入口都是从 parse函数开始的,AspectJAutoProxyBeanDefinitionParser 的 parse 函数如下:

AspectJAutoProxyBeanDefinitionParser .java

public BeanDefinition parse(Element element, ParserContext parserContext) {

   // 注册 AnnotationAwareAspectJAutoProxyCreator
   AopNamespaceUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(parserContext, element);
   
   // 对于注释中子类进行处理
   extendBeanDefinition(element, parserContext);
   return null;
}

        其中的 registerAspectJAnnotationAutoProxyCreatorIfNecessary 函数是 AnnotationAwareAspectJAutoProxyCreator 注册的地方,也是注册的主要逻辑实现的地方。

AopNamespaceUtils.java

public static void registerAspectJAnnotationAutoProxyCreatorIfNecessary(
      ParserContext parserContext, Element sourceElement) {

   // 注册或升级 AutoProxyCreator 定义 beanName 为 org.Springframework.aop.config.internalAutoProxyCreator的
   // BeanDefinition
   BeanDefinition beanDefinition = AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(
         parserContext.getRegistry(), parserContext.extractSource(sourceElement));
         
   // 对于 proxy-target-class 以及 expose-proxy 属性的处理
   useClassProxyingIfNecessary(parserContext.getRegistry(), sourceElement);
   
   // 注册组件并通知,便于监听器作进一步处理
   // 其中 beanDefinition 的 className 为 AnnotationAwareAspectJAutoProxyCreator
   registerComponentIfNecessary(beanDefinition, parserContext);
}


        BeanDefinition 的注册

        在对于AOP实现上,基本上都是靠 AnnotationAwareAspectJAutoProxyCreator 去完成的,它可以根据@Point 注解定义的切点来自动代理相匹配的 bean。但是为了配置简便,Spring 使用了自定义配置来帮我们自动注册 AnnotationAwareAspectJAutoProxyCreator。具体实现如下

AopNamespaceUtils.java

public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry, Object source) {
   return registerOrEscalateApcAsRequired(AnnotationAwareAspectJAutoProxyCreator.class, registry, source);
}

AopConfigUtils.java

private static BeanDefinition registerOrEscalateApcAsRequired(Class<?> cls, BeanDefinitionRegistry registry, Object source) {
   Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
   
   // 如果已经存在自动代理创建器且存在的自动代理创建器与现在的不一致那么需要根据优先级来判断到底需要任何使用
   if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {
      // AUTO_PROXY_CREATOR_BEAN_NAME="org.springframework.aop.config.internalAutoProxyCreator"
      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) {
            // 改变bean 最重要的就是改变bean 所对应的 className 属性
            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);
   // AUTO_PROXY_CREATOR_BEAN_NAME="org.springframework.aop.config.internalAutoProxyCreator"
   registry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME, beanDefinition);
   return beanDefinition;
}

        以上代码中实现了自动注册 AnnotationAwareAspectJAutoProxyCreator 类的功能,同时这里还涉及了一个优先级的问题,如果已经存在了自动代理创建器,而且存在的自动代理创建器与现在的不一致,那么就需要根据优先级来判断到底需要使用哪一个?    

            

属性处理

        一般而言,Spring AOP 内部使用 JDK 动态代理或者 CGLIB 来为目标对象创建代理。如果被代理的目标对象实现了至少一个接口,则会使用 JDK 动态代理。所有该目标类型实现的接口都将被代理。若该目标对象没有实现任何接口,则创建一个 CGLIB 代理。一般情况下,使用 CGLIB 需要考虑增强(advise)Final 方法不能被复写以及需要指定 CGLIB 包的位置,尽管 CGLIB 效率更高,但还是推荐使用 JDK 动态代理。

        而 AOP 配置中的 prioxy-target-class 和 expose-proxy 属性在代理生成中起到了至关重要的。prioxy-target-class 主要负责上面两种代理的实现,而 expose-proxy 则负责自我调用切面中的增强。

AopNamespaceUtils.java

private static void useClassProxyingIfNecessary(BeanDefinitionRegistry registry, Element sourceElement) {
   if (sourceElement != null) {
      // 对于 proxy-target-class 属性的处理
      boolean proxyTargetClass = Boolean.valueOf(sourceElement.getAttribute(PROXY_TARGET_CLASS_ATTRIBUTE));
      if (proxyTargetClass) {
         AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
      }
      // 对于 expose-proxy 属性的处理
      boolean exposeProxy = Boolean.valueOf(sourceElement.getAttribute(EXPOSE_PROXY_ATTRIBUTE));
      if (exposeProxy) {
         AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
      }
   }
}

AopConfigUtils.java

public static void forceAutoProxyCreatorToUseClassProxying(BeanDefinitionRegistry registry) {
   // 强制使用,其实也是一个属性设置
   if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {
      BeanDefinition definition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
      definition.getPropertyValues().add("proxyTargetClass", Boolean.TRUE);
   }
}

static void forceAutoProxyCreatorToExposeProxy(BeanDefinitionRegistry registry) {
   if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {
      BeanDefinition definition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
      definition.getPropertyValues().add("exposeProxy", Boolean.TRUE);
   }
}

        这些,基本就是Spring AOP部分的实现框架了,下节就要对AOP的主要实现类 AnnotationAwareAspectJAutoProxyCreator 进行分析。



——水门(2016年3月于杭州

© 著作权归作者所有

水门-kay
粉丝 460
博文 19
码字总数 59660
作品 0
杭州
后端工程师
私信 提问
加载中

评论(2)

水门-kay
水门-kay 博主

引用来自“孙宇龙”的评论

你uml用的是idea的什么插件?

回复@孙宇龙 : e
孙宇龙
孙宇龙
你uml用的是idea的什么插件?
那些年,我们一起追的Spring

学无止境,但仍需及时总结。 自去年开始写作以来,写了一些关于Spring的文章,今天将它们汇总起来,一方面方便大家阅读,另一方面,也是一次小的复盘总结。 IOC 首先是Spring的IOC,也就是控...

SexyCode
2018/08/14
0
0
Java程序员从笨鸟到菜鸟之(六十七)细谈Spring(一)spring简介

Spring 是一个开源框架,是为了解决企业应用程序开发复杂性而创建的。框架的主要优势之一就是其分层架构,分层架构允许您选择使用哪一个组件,同时为 J2EE 应用程序开发提供集成的框架。 然而...

长平狐
2012/11/12
135
0
面试必问的Spring AOP原理、SpringMVC过程

Spring AOP ,SpringMVC ,这两个应该是国内面试必问题,网上有很多答案,其实背背就可以。但今天笔者带大家一起深入浅出源码,看看他的原理。以期让印象更加深刻,面试的时候游刃有余。 Sp...

Java干货分享
2018/10/25
316
0
Aspectj与Spring AOP比较

1、简介 今天有多个可用的 AOP 库, 它们需要能够回答许多问题: 是否与用户现有的或新的应用程序兼容? 在哪里可以实现 AOP? 与自己的应用程序集成多快? 性能开销是多少? 在本文中, 我们将...

沈渊
2018/04/18
0
0
Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密...

小致Daddy
2018/08/03
21.4K
1

没有更多内容

加载失败,请刷新页面

加载更多

浅谈Visitor访问者模式

一、前言 什么叫访问,如果大家学过数据结构,对于这点就很清晰了,遍历就是访问的一般形式,单独读取一个元素进行相应的处理也叫作访问,读取到想要查看的内容+对其进行处理就叫作访问,那么...

青衣霓裳
22分钟前
5
0
JS内嵌多个页面,页面之间如何更快捷的查找相关联的页面

假设parent为P页面, P页面有两个子页面,分别为B页面和C页面; B页面和C页面分别内嵌一个iframe,分别为:D页面和E页面 现在通过B页面的内嵌页面D的方法refreshEpage(eUrl)来加载内嵌页面E的内容...

文文1
23分钟前
6
0
Hibernate 5 升级后 getProperties 错误

升级到 Hibernate 5 后,提示有错误: org.hibernate.engine.spi.SessionFactoryImplementor.getProperties()Ljava/util/Map; 完整的错误栈为: java.lang.NoSuchMethodError: org.hibernate......

honeymoose
25分钟前
4
0
mysql-connector-java升级到8.0后保存时间到数据库出现了时差

在一个新项目中用到了新版的mysql jdbc 驱动 <dependency>     <groupId>mysql</groupId>     <artifactId>mysql-connector-java</artifactId>     <version>8.0.18</version> ......

ValSong
28分钟前
6
0
Spring中BeanFactory与FactoryBean的区别

在Spring中有BeanFactory和FactoryBean这2个接口,从名字来看很相似,比较容易搞混。 一、BeanFactory BeanFactory是一个接口,它是Spring中工厂的顶层规范,是SpringIoc容器的核心接口,它定...

大王叫下
30分钟前
5
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部