2019独角兽企业重金招聘Python工程师标准>>>

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/beanshttp://www.springframework.org/schema/beans/spring-beans-4.1.xsdhttp://www.springframework.org/schema/aophttp://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) {// 注册 AnnotationAwareAspectJAutoProxyCreatorAopNamespaceUtils.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的// BeanDefinitionBeanDefinition beanDefinition = AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(parserContext.getRegistry(), parserContext.extractSource(sourceElement));// 对于 proxy-target-class 以及 expose-proxy 属性的处理useClassProxyingIfNecessary(parserContext.getRegistry(), sourceElement);// 注册组件并通知,便于监听器作进一步处理// 其中 beanDefinition 的 className 为 AnnotationAwareAspectJAutoProxyCreatorregisterComponentIfNecessary(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月于杭州)

转载于:https://my.oschina.net/kaywu123/blog/632486

Spring 源码分析(三) —— AOP(二)Spring AOP 整体架构相关推荐

  1. spring源码分析系列(二)AOP应用

    这里讲AOP应用 先来说说一个比较虚无缥缈的问题:什么是AOP? 1.OOP 在说AOP之前,我们需要先看看什么是OOP:Object Oriented Programming,翻译过来就是面向对象编 ...

  2. Spring源码分析(三)

    Spring源码分析 第三章 手写Ioc和Aop 文章目录 Spring源码分析 前言 一.模拟业务场景 (一) 功能介绍 (二) 关键功能代码 (三) 问题分析 二.使用ioc和aop重构 (一) ...

  3. Spring 源码分析(三) —— AOP(五)创建代理

    2019独角兽企业重金招聘Python工程师标准>>> 创建代理 代理的定义其实非常简单,就是改变原来目标对象方法调用的运行轨迹.这种改变,首先会对这些方法进行拦截,从而为这些方法提 ...

  4. spring源码分析01-(前期准备)spring核心原理解析和手写简易spring

    1.本文主要介绍内容 本文会把Spring中核心知识点大概解释下.可以对Spring的底层有一个整体的大致了解.主要内容包括: 手写简易spring框架,帮助更好理解spring. 代码点击链接自取 ...

  5. 【转】ABP源码分析三十二:ABP.SignalR

    Realtime Realtime是ABP底层模块提供的功能,用于管理在线用户.它是使用SignalR实现给在线用户发送通知的功能的前提 IOnlineClient/OnlineClient: 封装在 ...

  6. Spring源码分析之ProxyFactoryBean方式实现Aop功能的分析

    实现Aop功能有两种方式, 1. ProxyFactoryBean方式: 这种方式是通过配置实现 2. ProxyFactory方式:这种方式是通过编程实现 这里只说ProxyFactoryBean方 ...

  7. Spring源码分析之BOP/IOC/DI/AOP

    Spring是什么? 轻量级:零配置编程.API使用简单 面向Bean:只需要编写普通的对象 轻耦合:充分利用AOP(面向切面)的思想 设计模式:使用java中经典的设计模式 面向Bean-BOP:通 ...

  8. Spring源码分析(十二)autowire和@Autowired 依赖注入源码解析总结

    XML的autowire自动注入 在XML中,我们可以在定义一个Bean时去指定这个Bean的自动注入模式: byType byName constructor default no 比如: < ...

  9. Spring源码分析【4】-Spring扫描basePackages注解

    org.springframework.beans.factory.support.DefaultListableBeanFactory 重要数据结构 /** Map of bean definiti ...

最新文章

  1. android上下文关系,Android Context上下文的理解 Hua
  2. Win2008 R2 RemoteApp深度体验之四,RemoteApp程序测试
  3. linux 查端口 三种,Linux查看端口常用的三种用例
  4. Android代码实现新建文件夹,并将文件保存到新建的文件夹中
  5. 开发备必:WEB前端开发规范文档
  6. Redis中的zset 存储结构(实现)原理
  7. Fat Jar Eclipse Plug-In Tutorial
  8. PYTHON知识梳理
  9. 计算机基础在线阅读,TOP16[定稿]计算机基础教案(上下册).doc文档免费在线阅读...
  10. weblogic domain creation
  11. php 环境优化,[笔记] 使用 opcache 优化生产环境 PHP
  12. 两年前端历程回顾的思考与总结
  13. ERROR: cuda requested, but not all dependencies are satisfied: ffnvcodec
  14. 家庭理财系统 -- 功能清单
  15. tree.js 制作酷炫照片墙
  16. 这一年,CyberMiles曾经走过的路
  17. 更改Anaconda下载源,提高下载速度
  18. SAP - 采购价格确定 ①
  19. 乔治·布尔二百周年:数理逻辑奠基者其人其事
  20. allegro如何删除没有网络的走线,查还没有连的网络线

热门文章

  1. Codeforces Round #379 (Div. 2) A. Anton and Danik 水题
  2. python 2.7中urllib 2 与python 3.5中 urllib的区别。
  3. volley 框架的使用
  4. XML DataBase之Xindice(二)
  5. 【ROS学习笔记】(七)服务端Server的实现
  6. 淮阴工学院计算机系在哪个校区,2021年淮阴工学院有几个校区,大一新生在哪个校区...
  7. 以前是传xml的吗_李菲儿参加《浪姐2》,与黄晓明同台互动!不怕baby吃醋吗?...
  8. python怎么用split字符串全部分开_python实现字符串完美拆分split()的方法
  9. if __name__ == __main___一文搞懂 if __name__ == #39;__main__#39;
  10. weex android 交互,weex项目接入到Android studio中