Spring源码之AOP的使用
Spring往期精彩文章
前言
我们都知道Java是一门面向对象(OOP
)的语言,所谓万物皆对象。但是它也存在着一些个弊端:当你需要给多个不具有继承关系的对象引入同一个公共的行为的时候,例如日志,安全检测等等,我们只能在每个对象中去引入这个公共行为,这样就产生了大量的重复代码,并且耦合度也会很高,不利于维护。正因如此就产生了面向切面(AOP
)编程。可以说有了AOP使得面向对象更加完善,是对其的一个补充,AOP所关注的方式是横向的,不同于OOP的纵向,接下来我们详细讲解一下spring中的AOP。
AOP的使用
我们先从动态AOP开始
- 首先引入
Aspect
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.7</version>
</dependency>
- 创建用于拦截的测试Bean
package com.vipbbo.selfdemo.spring.aop.test;
public class TestBean {
private String message = "Test Message";
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public void test(){
System.out.println(this.message);
}
}
- 创建
Advisor
spring中一改以往摒弃了它最原始的繁杂的配置方式,目前采用@AspectJ
注解的方式对POJO进行标注,使得AOP的工作大大简化。例如在AspectJTest类中,我们要做的就是在所有类的test方法执行前在控制台输出beforeTest,在所有类的test方法执行后打印afterTest,同时又使用环绕通知的方式在所有类的方法执行前后在此分别打印around......before和around......after
AspectJTest代码
@Aspect
public class AspectJTest {
@Pointcut("execution(* *.test(..))")
public void test(){
}
@Before("test()")
public void beforeTest(){
System.out.println("beforeTest");
}
@Around("test()")
public Object aroundTest(ProceedingJoinPoint joinPoint){
System.out.println("around.........before");
Object proceed = null;
try {
proceed = joinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
System.out.println("around.........after");
return proceed;
}
@After("test()")
public void afterTest(){
System.out.println("afterTest");
}
}
- 创建配置文件
<?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.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"
>
<!--开启AOP 自动配置-->
<aop:aspectj-autoproxy/>
<bean id="testBean" class="com.vipbbo.selfdemo.spring.aop.test.TestBean">
<property name="message" value="一个苦逼的程序员"/>
</bean>
<bean id="aspect" class="com.vipbbo.selfdemo.spring.aop.test.AspectJTest"/>
</beans>
在编写配置文件中要注意图中的声明、命名空间:
- 测试类
public class Test {
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("spring-aop.xml");
TestBean testBean = (TestBean) ac.getBean("testBean");
testBean.test();
}
}
运行结果如下:
通过上述代码可以看出,Spring实现了对所有类的test方法进行了增强,使得辅助功能(日志等)可以独立出来,也做到了解耦和对程序的扩展。那么Spring是如何实现AOP的呢?实现我们知道,Spring是由一个配置文件控制是否支持注解的AOP,也就是<aop:aspectj-autoproxy/>
,当配置文件有了这句声明的时候,Spring就会支持注解的AOP,那么分析从这里开始。
AOP自定义注解源码解读
我们知道Spring中的自定义注解,如果声明了自定义注解,那么在Spring中的一个地方一定注册了对应的解析器,我们从aspectj-autoProxy
入手:
在Spring源码中全局搜索,我们发现了在包`org.springframework.aop.config`下的`AopNamespaceHandler`,然后我们打开这个类
在AopNamespaceHandler
类中我们发现了这个init
函数
public class AopNamespaceHandler extends NamespaceHandlerSupport {
/**
* Register the {@link BeanDefinitionParser BeanDefinitionParsers} for the
* '{@code config}', '{@code spring-configured}', '{@code aspectj-autoproxy}'
* and '{@code scoped-proxy}' tags.
*/
@Override
public void init() {
// In 2.0 XSD as well as in 2.5+ XSDs
registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser());
registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());
registerBeanDefinitionDecorator("scoped-proxy", new ScopedProxyBeanDefinitionDecorator());
// Only in 2.0 XSD: moved to context namespace in 2.5+
registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
}
}
从上述代码可以看出,在解析配置文件的时候,一旦遇到aspectj-autoproxy
就会使用AspectJAutoProxyBeanDefinitionParser
解析器进行解析,接下来我们该函数的具体实现:
注册AnnotationAwareAspectJAutoProxyCreator
所有的解析器都是对接口BeanDefinitionParser
的实现,入口都是从parse
函数开始的,AnnotationAwareAspectJAutoProxyCreator的parse函数如下:
- 看源码(具体实现在
AspectJAutoProxyBeanDefinitionParser.class
)
@Override
@Nullable
public BeanDefinition parse(Element element, ParserContext parserContext) {
// 注册AnnotationAwareAspectJAutoProxyCreator
AopNamespaceUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(parserContext, element);
// 对于注解中的子类进行处理
extendBeanDefinition(element, parserContext);
return null;
}
从上述代码我们又看出具体实现逻辑是在registerAspectJAnnotationAutoProxyCreatorIfNecessary
方法中实现的,继续进入到函数方法体内:
- 看源码(具体实现在
AopNamespaceUtils.class
)
/**
* 注册AnnotationAwareAspectJAutoProxyCreator
* @param parserContext
* @param sourceElement
*/
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);
// 注册组件并通知,便于监听器作进一步处理
registerComponentIfNecessary(beanDefinition, parserContext);
}
看上述源码可知在函数registerAspectJAnnotationAutoProxyCreatorIfNecessary
中主要做了三件事,基本是每行代码做了一件。接下来我们一一解析:
函数体内的registerAspectJAnnotationAutoProxyCreatorIfNecessary 方法
注册或升级AnnotationAwareAspectJAutoProxyCreator
对于AOP的实现基本都是靠AnnotationAwareAspectJAutoProxyCreator来完成的,它可以根据@Pointcut
注解定义的节点来自动代理相匹配的bean,但是为了配置简单,Spring使用了自动配置来帮我们自动注册AnnotationAwareAspectJAutoProxyCreator,其过程就是在这里实现的。我们继续跟进方法内部:
- 看源码(具体实现在
AopConfigUtils.class
)
@Nullable
public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(
BeanDefinitionRegistry registry, @Nullable Object source) {
return registerOrEscalateApcAsRequired(AnnotationAwareAspectJAutoProxyCreator.class, registry, source);
}
在上面代码中我们看到了函数registerOrEscalateApcAsRequired
继续跟进:
- 看源码(具体实现在
AopConfigUtils.class
)
@Nullable
private static BeanDefinition registerOrEscalateApcAsRequired(
Class<?> cls, BeanDefinitionRegistry registry, @Nullable Object source) {
Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
// 如果已经存在了自动代理创建器 且存在的自动代理创建器与现在的不一致,那么需要根据优先级判断到底需要使用哪一个
if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {
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;
}
// 注册BeanDefinition,Class为AnnotationAwareAspectJAutoProxyCreator.class,beanName为internalAutoProxyCreator
RootBeanDefinition beanDefinition = new RootBeanDefinition(cls);
beanDefinition.setSource(source);
beanDefinition.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE);
beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
registry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME, beanDefinition);
return beanDefinition;
}
同时我们也要看一下AopConfigUtils
类中的这部分代码:
/**
* The bean name of the internally managed auto-proxy creator.
*/
public static final String AUTO_PROXY_CREATOR_BEAN_NAME =
"org.springframework.aop.config.internalAutoProxyCreator";
以上代码实现了自动注册AnnotationAwareAspectJAutoProxyCreator
类的功能,同时这里还设计到一个优先级的问题,假设如果已经存在了自动代理创建器,而且存在的自动代理创建器与现在的不一致,那么需要根据优先级来判断到底使用哪一个
处理proxy-target-class以及expose-proxy属性
useClassProxyingIfNecessary(parserContext.getRegistry(), sourceElement);这部分做了对proxy-target-class以及expose-proxy属性的处理。
- 看源码(具体实现在
AopNamespaceUtils。class
)
private static void useClassProxyingIfNecessary(BeanDefinitionRegistry registry, @Nullable Element sourceElement) {
if (sourceElement != null) {
// 实现了对 proxy-target-class的处理
boolean proxyTargetClass = Boolean.parseBoolean(sourceElement.getAttribute(PROXY_TARGET_CLASS_ATTRIBUTE));
if (proxyTargetClass) {
AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
}
// 对expose-proxy的处理
boolean exposeProxy = Boolean.parseBoolean(sourceElement.getAttribute(EXPOSE_PROXY_ATTRIBUTE));
if (exposeProxy) {
AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
}
}
}
在上述代码中使用到了**两个强制使用的方法**分别是`forceAutoProxyCreatorToUseClassProxying`和`forceAutoProxyCreatorToExposeProxy`,强制使用的过程其实也是一个属性设置的过程,两个函数的具体实现如下(具体实现在`AopConfigUtils.class`):
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);
}
}
public 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);
}
}
接下来让我们说一下proxy-target-class
和expose-proxy
这两个属性
proxy-target-proxy :Spring AOP部分使用的
JDK动态代理
或者是CGLIB
代理来为目标对象创建代理。(这里建议尽量使用JDK动态代理),如果被代理的目标对象实现了至少一个接口,则会使用JDK动态代理。所有目标类型实现的接口都将被代理;倘若目标对象没有实现任何接口,则会创建一个CGLIB代理。另外如果你想强制使用CGLIB代理的话,(例如希望代理目标对象的所有方法,而不只是实现子接口的方法)那也是可以的,但是需要考虑两个问题。
- 无法通知(advise)Final方法,因为它们不能被重写
- 你需要将CGLIB二进制发行包放在classpath下面
与之相比较,JDK本身就提供了动态代理,强制使用CGLIB代理需要将
<aop-config>
中的proxy-target-class属性设置为true。<aop:config proxy-target-class="true"/>
当你使用CGLIB代理
和@AspectJ
自动代理支持,可以按照以下方式设置
<aop:aspectj-autoproxy proxy-target-class="true"/>
- expose-proxy:有时候目标对象内部的自我调用将无法实施切面中的增强,如下:
public interface AService {
public void a();
public void b();
}
@Service()
public class AServicelmpll implements AService {
@Transactional(propagation = Propagation.REQUIRED)
public void a() {
this.b{);
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void b() {
}
}
此处的this指向目标对象,因此调用this.b将不会执行b的事务切面,即不会执行事务增强,因此b方法的事务定义` @Transactional(propagation = Propagation.REQUIRES_NEW) `将不会实施,为了解决这个问题,我们可以这样做:
<aop:aspectj-autoproxy expose-proxy = "true"/>
然后将以上代码中的`this.b()`修改为`((AService)AopContext.currentProxy()).b()`即可
通过以上的修改便可完成对a
和b
方法的同时增强
简单说一下JDK动态代理的CGLIB代理
- JDK动态代理:其对象必须是某个接口的实现,它是通过在运行期间创建一个接口的实现类来完成对目标对象的创建。
- CGLIB代理:实现原理类似于JDK动态代理,只是它在运行期间生成的代理对象是针对目标类扩展的子类。CGLIB是高效的代码生成包,底层是依靠
ASM
(开源的Java字节码编辑类库)操作字节码类实现的,性能要比JDK强
微信搜索【码上遇见你】获取更多精彩文章,以及学习资料
Spring源码之AOP的使用的更多相关文章
- Spring 源码学习——Aop
Spring 源码学习--Aop 什么是 AOP 以下是百度百科的解释:AOP 为 Aspect Oriented Programming 的缩写,意为:面向切面编程通过预编译的方式和运行期动态代理实 ...
- 专治不会看源码的毛病--spring源码解析AOP篇
昨天有个大牛说我啰嗦,眼光比较细碎,看不到重点.太他爷爷的有道理了!要说看人品,还是女孩子强一些.原来记得看到一个男孩子的抱怨,说怎么两人刚刚开始在一起,女孩子在心里就已经和他过完了一辈子.哥哥们,不 ...
- Spring源码解析-AOP简单分析
AOP称为面向切面编程,在程序开发中主要用来解决一些系统层面上的问题,比如日志,事务,权限等等,不需要去修改业务相关的代码. 对于这部分内容,同样采用一个简单的例子和源码来说明. 接口 public ...
- 面试真题--------spring源码解析AOP
接着上一章对IOC的理解之后,再看看AOP的底层是如何工作的. 1.实现AOP的过程 首先我们要明白,Spring中实现AOP,就是生成一个代理,然后在使用的时候调用代理. 1.1 创建代理工厂 ...
- spring源码解读-aop
aop是指面向切面编程,ProxyFactoryBean是spring aop的底层实现与源头,为什么这么说呢?首先我们看一段配置: 1.target是目标对象,需要对其进行切面增强 2.proxyI ...
- Spring源码学习
Spring源码学习--ClassPathXmlApplicationContext(一) spring源码学习--FileSystemXmlApplicationContext(二) spring源 ...
- spring源码分析(二)Aop
创建日期:2016.08.19 修改日期:2016.08.20-2016.08.21 交流QQ:992591601 参考资料:<spring源码深度解析>.<spring技术内幕&g ...
- spring源码学习之路---深入AOP(终)
作者:zuoxiaolong8810(左潇龙),转载请注明出处,特别说明:本博文来自博主原博客,为保证新博客中博文的完整性,特复制到此留存,如需转载请注明新博客地址即可. 上一章和各位一起看了一下sp ...
- spring源码学习之路---AOP初探(六)
作者:zuoxiaolong8810(左潇龙),转载请注明出处,特别说明:本博文来自博主原博客,为保证新博客中博文的完整性,特复制到此留存,如需转载请注明新博客地址即可. 最近工作很忙,但当初打算学习 ...
随机推荐
- 【java虚拟机】Java内存模型
作者:平凡希 原文地址:https://www.cnblogs.com/xiaoxi/p/7518259.html 一.什么是Java内存模型 Java虚拟机规范中试图定义一种Java内存模型(Jav ...
- python matplotlib 绘图+显示数值
参考:https://www.jb51.net/article/152685.htm 用plt.text函数 import numpy as np import matplotlib.mlab as ...
- 一、自动化监控利器-Zabbix
目录 1. 监控的作用 1.1 为何需要监控系统 1.2 监控系统的实现 1.3 常用的监控软件 2. Zabbix简介 2.1 选择Zabbix的理由 2.2 Zabbix的功能特性 3. Zabb ...
- Java调用腾讯云短信接口,完成验证码的发送(不成功你来砍我!!)
一.前言 我们在一些网站注册页面,经常会见到手机验证码的存在,这些验证码一般的小公司都是去买一些大的厂家的短信服务,自己开发对小公司的成本花费太大了!今天小编就带着大家来学习一下腾讯云的短信接口,体验 ...
- 将JAVA API接口 改写成 Python
AsinSeedApi 不写注释的程序员-加密 将JAVA API接口 改写成 Python JAVA import com.alibaba.fastjson.JSON; import com.ali ...
- Haproxy搭建web集群
目录: 一.常见的web集群调度器 二.Haproxy应用分析 三.Haproxy调度算法原理 四.Haproxy特性 五.Haproxy搭建 Web 群集 一.常见的web集群调度器 目前常见的we ...
- 深度探索-Redis复制
1.前言 本文介绍了Redis复制的主要流程和设计思想.通过本文的阅读,您大致能理解复制在软件架构方面的通用思想.在阅读本文之前,希望读者首先对Redis有一定的认识,对Redis的事件类型.和事件处 ...
- CodeForce-801C Voltage Keepsake(二分)
题目大意:有n个装备,每个设备耗能为每单位时间耗能ai,初始能量为bi;你有一个充电宝,每单位时间可以冲p能量,你可以在任意时间任意拔冲. 如果可以所有设备都可以一直工作下去,输出-1:否则,输出所有 ...
- Java技术开发专题系列之【Guava RateLimiter】针对于限流器的入门到精通(针对于源码分析介绍)
Guava包中限流实现分析 RateLimiter 之前的文章中已经介绍了常用的限流算法,而google在Java领域中使用Guava包中的限流工具进行服务限流. 回顾使用案例 Google开源工具包 ...
- 解决使用tomcat服务器发布web项目时出现URL中文乱码的问题
打开Tomcat的安装路径 打开server.xml文件 在修改端口号的一行既是下图中位置添加 URIEncoding="UTF-8" 就能替换在用eclipse或者myeclip ...