spring5 源码深度解析----- AOP的使用及AOP自定义标签
我们知道在面向对象OOP编程存在一些弊端,当需要为多个不具有继承关系的对象引入同一个公共行为时,例如日志,安全检测等,我们只有在每个对象里引入公共行为,这样程序中就产生了大量的重复代码,所以有了面向对象编程的补充,面向切面编程(AOP),AOP所关注的方向是横向的,不同于OOP的纵向。接下来我们就详细分析下spring中的AOP。首先我们从动态AOP的使用开始。
AOP的使用
在开始前,先引入Aspect。
<!-- aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>${aspectj.version}</version>
</dependency>
创建用于拦截的bean
public class TestBean {
private String message = "test bean"; 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,同时又使用环绕的方式在所有类的方法执行前后在此分别打印before1和after1,以下是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 p){
System.out.println("around.....before");
Object o = null;
try{
o = p.proceed();
}catch(Throwable e){
e.printStackTrace();
}
System.out.println("around.....after");
return o;
} @After("test()")
public void afterTest()
{
System.out.println("afterTest");
}
}
创建配置文件
要在Spring中开启AOP功能,,还需要在配置文件中作如下声明:
<?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:context="http://www.springframework.org/schema/context"
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/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <aop:aspectj-autoproxy/>
<bean id="test" class="com.yhl.myspring.demo.aop.TestBean">
<property name="message" value="这是一个苦逼的程序员"/>
</bean>
<bean id="aspect" class="com.yhl.myspring.demo.aop.AspectJTest"/>
</beans>
测试
public class Test {
public static void main(String[] args) {
ApplicationContext bf = new ClassPathXmlApplicationContext("aspectTest.xml");
TestBean bean = (TestBean)bf.getBean("test");
bean.test();
}
}
执行后输出如下:
Spring实现了对所有类的test方法进行增强,使辅助功能可以独立于核心业务之外,方便与程序的扩展和解耦。
那么,Spring是如何实现AOP的呢?首先我们知道,SPring是否支持注解的AOP是由一个配置文件控制的,也就是<aop:aspectj-autoproxy/>,当在配置文件中声明了这句配置的时候,Spring就会支持注解的AOP,那么我们的分析就从这句注解开始。
AOP自定义标签
之前讲过Spring中的自定义注解,如果声明了自定义的注解,那么就一定会在程序中的某个地方注册了对应的解析器。我们搜索 aspectj-autoproxy 这个代码,尝试找到注册的地方,全局搜索后我们发现了在org.springframework.aop.config包下的AopNamespaceHandler中对应着这样一段函数:
@Override
public void init() {
// In 2.0 XSD as well as in 2.1 XSD.
registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser());
registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());
registerBeanDefinitionDecorator("scoped-proxy", new ScopedProxyBeanDefinitionDecorator()); // Only in 2.0 XSD: moved to context namespace as of 2.1
registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
}
这里我们就不再对spring中的自定义注解方式进行讨论了。从这段代码中我们可以得知,在解析配置文件的时候,一旦遇到了aspectj-autoproxy注解的时候会使用解析器AspectJAutoProxyBeanDefinitionParser进行解析,接下来我们就详细分析下其内部实现。
注册AnnotationAwareAspectJAutoProxyCreator
所有解析器,因为都是对BeanDefinitionParser接口的统一实现,入口都是从parse函数开始的,AspectJAutoProxyBeanDefinitionParser的parse函数如下:
@Override
@Nullable
public BeanDefinition parse(Element element, ParserContext parserContext) {
// 注册AnnotationAwareAspectJAutoProxyCreator
AopNamespaceUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(parserContext, element);
// 对于注解中子类的处理
extendBeanDefinition(element, parserContext);
return null;
}
通过代码可以了解到函数的具体逻辑是在registerAspectJAnnotationAutoProxyCreatorIfNecessary方法中实现的,继续进入到函数体内:
/**
* 注册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);
}
在registerAspectJAnnotationAutoProxyCreatorIfNeccessary方法中主要完成了3件事情,基本上每行代码都是一个完整的逻辑。接下来我们详细分析每一行代码。
注册或升级AnnotationAwareAspectJAutoProxyCreator
对于AOP的实现,基本上都是靠AnnotationAwareAspectJAutoProxyCreator去完成,它可以根据@Point注解定义的切点来自动代理相匹配的bean。但是为了配置简便,Spring使用了自定义配置来帮助我们自动注册AnnotationAwareAspectJAutoProxyCreator,其注册过程就是在这里实现的。我们继续跟进到方法内部:
public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry,
@Nullable Object source) {
return registerOrEscalateApcAsRequired(AnnotationAwareAspectJAutoProxyCreator.class, registry, source);
} public static final String AUTO_PROXY_CREATOR_BEAN_NAME = "org.springframework.aop.config.internalAutoProxyCreator"; 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;
}
以上代码实现了自动注册AnnotationAwareAspectJAutoProxyCreator类的功能,同时这里还涉及了一个优先级的问题,如果已经存在了自动代理创建器,而且存在的自动代理创建器与现在的不一致,那么需要根据优先级来判断到底需要使用哪个。
处理proxy-target-class以及expose-proxy属性
useClassProxyingIfNecessary实现了proxy-target-class属性以及expose-proxy属性的处理,进入到方法内部:
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);
}
}
}
在上述代码中用到了两个强制使用的方法,强制使用的过程其实也是一个属性设置的过程,两个函数的方法如下:
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: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">...</aop:config>
当需要使用CGLIB代理和@AspectJ自动代理支持,可以按照以下方式设罝<aop:aspectj- autoproxy>的 proxy-target-class 属性:
<aop:aspectj-autoproxy proxy-target-class = "true"/>
- JDK动态代理:其代理对象必须是某个接口的实现,它是通过在运行期间创建一个接口的实现类来完成对目标对象的代理。
- CGIJB代理:实现原理类似于JDK动态代理,只是它在运行期间生成的代理对象是针对目标类扩展的子类。CGLIB是高效的代码生成包,底层是依靠ASM (开源的Java字节码编辑类库)操作字节码实现的,性能比JDK强。
- 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方法的同时增强。
spring5 源码深度解析----- AOP的使用及AOP自定义标签的更多相关文章
- spring5 源码深度解析----- 被面试官给虐懵了,竟然是因为我不懂@Configuration配置类及@Bean的原理
@Configuration注解提供了全新的bean创建方式.最初spring通过xml配置文件初始化bean并完成依赖注入工作.从spring3.0开始,在spring framework模块中提供 ...
- spring5 源码深度解析----- Spring事务 是怎么通过AOP实现的?(100%理解Spring事务)
此篇文章需要有SpringAOP基础,知道AOP底层原理可以更好的理解Spring的事务处理. 自定义标签 对于Spring中事务功能的代码分析,我们首先从配置文件开始人手,在配置文件中有这样一个配置 ...
- spring5 源码深度解析-----ApplicationContext容器refresh过程
在之前的博文中我们一直以BeanFactory接口以及它的默认实现类XmlBeanFactory为例进行分析,但是Spring中还提供了另一个接口ApplicationContext,用于扩展Bean ...
- spring5 源码深度解析----- @Transactional注解的声明式事物介绍(100%理解事务)
面的几个章节已经分析了spring基于@AspectJ的源码,那么接下来我们分析一下Aop的另一个重要功能,事物管理. 事务的介绍 1.数据库事物特性 原子性多个数据库操作是不可分割的,只有所有的操作 ...
- spring5 源码深度解析----- 事务增强器(100%理解事务)
上一篇文章我们讲解了事务的Advisor是如何注册进Spring容器的,也讲解了Spring是如何将有配置事务的类配置上事务的,实际上也就是用了AOP那一套,也讲解了Advisor,pointcut验 ...
- spring5 源码深度解析----- 事务的回滚和提交(100%理解事务)
上一篇文章讲解了获取事务,并且通过获取的connection设置只读.隔离级别等,这篇文章讲解剩下的事务的回滚和提交 回滚处理 之前已经完成了目标方法运行前的事务准备工作,而这些准备工作最大的目的无非 ...
- spring5 源码深度解析----- AOP代理的生成
在获取了所有对应bean的增强后,便可以进行代理的创建了.回到AbstractAutoProxyCreator的wrapIfNecessary方法中,如下所示: protected static fi ...
- spring5 源码深度解析----- 创建AOP代理之获取增强器
在上一篇的博文中我们讲解了通过自定义配置完成了对AnnotationAwareAspectJAutoProxyCreator类型的自动注册,那么这个类到底做了什么工作来完成AOP的操作呢?首先我们看看 ...
- spring5 源码深度解析----- AOP目标方法和增强方法的执行(100%理解AOP)
上一篇博文中我们讲了代理类的生成,这一篇主要讲解剩下的部分,当代理类调用时,目标方法和代理方法是如何执行的,我们还是接着上篇的ReflectiveMethodInvocation类Proceed方法来 ...
随机推荐
- 五.Linux基础
Linux是运行在pc机上类似unix风格的操作系统,由众多程序员开发.开放源代码! 由于开放源代码,所以现在的Linux比windows漏洞更少! linux操作系统由内核及应用程序组成,有很多发行 ...
- 05_指针之New()函数的使用
1.new函数是一个内置函数,表达式new(T)创建一个未命名的T类型变量,初始化为T类型的零值,并返回其地址(地址类型为*T)2.p:=new(int),q:=new(int)==>p!=q ...
- CSAPP - Ch 1 - 计算机系统漫游
目录 0 序言及摘要 1 信息就是位+上下文 2 程序被其他程序翻译成不同的格式 3 了解编译系统如何工作是大有益处的 0 序言及摘要 (1) 序言: CS:APP -- Computer Syste ...
- String类的intern()方法,随常量池发生的变化
JVM的知识这里总结的很详细:https://github.com/doocs/jvm/blob/master/README.md,因此在本博客也不会再对其中的东西重复总结了. intern的作用 简 ...
- nginx-springboot-vue前后端分离跨域配置
nginx-springboot-vue前后端分离跨域配置 引言 接着上篇--简单的springboot-vue前后端分离登录Session拦截的demo,其中跨域是通过springboot后端全局设 ...
- 深入理解 Handler 消息机制
记得很多年前的一次面试中,面试官问了这么一个问题,你在项目中一般如何实现线程切换? 他的本意应该是考察 RxJava 的使用,只是我的答案是 Handler,他也就没有再追问下去了.在早期 Andro ...
- 【转载】Windows api数据类型
最近在接触windows api函数,看到了很多之前没有看到过的数据类型,发现“个人图书馆”中有个帖子说的挺详细的,特地搬运过来 Windows 数据类型 Delphi 数据类型 描述 LPSTR P ...
- cogs 1377. [NOI2011] NOI嘉年华 (dp
题意:给你n个活动的起止时间,要你从中选一些活动在2个会场安排(不能有两个活动在两个会场同时进行),使活动较少的会场活动数最大,以及在某个活动必须选择的前提下,求该答案. 思路:由于n很小,时间很大, ...
- lightoj 1061 - N Queen Again(状压dp)
题目链接:http://www.lightoj.com/volume_showproblem.php?problem=1061 题解:显然能满足情况的8皇后的摆法不多,于是便可以用题目给出的状态来匹配 ...
- J farm
#include<iostream> #include<vector> #include<algorithm> #include<cstring> #i ...