Spring AOP源码分析(二):AOP的三种配置方式与内部解析实现
AOP配置的核心元素为:pointcut,advisor,aspect,pointcut用于定义需要该辅助功能的类或方法集合;advisor则是将advice和pointcut结合起来,在spring的IOC容器启动时,为pointcut匹配的类生成代理对象,使用拦截器拦截对应的方法的执行,将辅助功能advice添加进去;aspect表示一个完整切面,即在aspect对应的类中定义辅助方法advice,然后在aspect中组装到pointcut拦截的方法集合中。
package com.yzxie.demo.aop; /*
* 切面支持类
*/
public class AnnotationAOPXmlConfig { // 切点的方法执行前执行
public void beforeAdivce(){
System.out.println("注解类型前置增强");
} // 切点的方法执行后执行
public void afterAdivce(){
System.out.println("注解类型后置增强");
} // 切点的方法返回后执行
public void afterReturningAdivce(){
System.out.println("方法返回后执行");
} // 切点的方法执行抛异常时执行
public void afterThrowingAdivce(){
System.out.println("方法执行抛异常");
} // 切点的方法执行前后均执行
public void aroundAdvice(ProceedingJoinPoint pjp) throws Throwable{
System.out.println("注解类型环绕增强,方法执行前"); //执行实际的目标方法
pjp.proceed(); System.out.println("注解类型环绕增强,方法执行后");
}
}
xml配置:基于aspect配置
<!-- 辅助方法定义类,在内部包含beforeAdivce,afterAdivce等方法定义 -->
<bean id="annotationAOPXmlConfig"
class="com.yzxie.demo.aop.AnnotationAOPXmlConfig" /> <aop:config> <!-- 切点配置 -->
<aop:pointcut id="targetMethod"
expression="execution(* *.testAOP(..))" /> <!-- 基于aspect配置一个完整切面 -->
<aop:aspect ref="annotationAOPXmlConfig">
<!-- 声明前置增强 (在切点方法被执行前调用)-->
<aop:before method="beforeAdivce"
pointcut-ref="targetMethod"/>
<!-- 声明后置增强 (在切点方法被执行后调用)-->
<aop:after method="afterAdivce"
pointcut-ref="targetMethod"/>
<!-- 环绕增强 -->
<aop:around method="afterAdivce"
pointcut-ref="embark"/>
<!-- 方法返回增强 -->
<aop:returning method="afterReturningAdivce"
pointcut-ref="targetMethod"/>
<!-- 方法抛异常增强 -->
<aop:throwing method="afterThrowingAdivce"
pointcut-ref="targetMethod"/>
</aop:aspect>
</aop:config>
基于advisor配置
与aspect不同的是,需要对每个advice都定义一个类,然后使用advisor组装到pointcut拦截的方法。
辅助方法对应的类定义:其中一个环绕增强AroundAdvice定义。
package com.yzxie.demo.aop.advice; import java.lang.reflect.Method;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation; // 切点的方法执行前后均执行
public class AroundAdvice implements MethodInterceptor { public Object invoke(MethodInvocation arg0) throws Throwable {
System.out.println("注解类型环绕增强,方法执行前"); //执行实际的目标方法
pjp.proceed(); System.out.println("注解类型环绕增强,方法执行后");
}
}
xml配置:
<bean id="aroundAdvice" class="com.yzxie.demo.aop.advice.AroundAdvice" /> <!-- 配置切面 -->
<aop:config>
<!-- 切点配置 -->
<aop:pointcut id="targetMethod"
expression="execution(* *.testAOP(..))" /> <!-- 配置环绕增强advisor -->
<aop:advisor advice-ref="aroundAdvice"
pointcut-ref="targetMethod"/> <!-- 配置其他增强advisor -->
</aop:config>
<?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/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd"> <!-- 开启注解扫描 -->
<context:component-scan base-package="com.yzxie.demo.aop"/> <!-- 开启aop注解方式,此步骤不能少,这样java类中的aop注解才会生效 -->
<aop:aspectj-autoproxy/>
</beans>
基于以上配置,在com.yzxie.demo.aop包下定义一个AOP配置类,其中需要同时使用@Component和@Aspect注解,如下:
package com.yzxie.demo.aop; import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component; @Component
@Aspect
public class AnnotationAOPConfig { //定义切点,即拦截所有方法名为testAOP的方法的执行
@Pointcut("execution(* *.testAOP(..))")
public void targetMethod(){} // 切点的方法执行前执行
@Before("targetMethod()")
public void beforeAdivce(){
System.out.println("注解类型前置增强");
} // 切点的方法执行后执行
@After("targetMethod()")
public void afterAdivce(){
System.out.println("注解类型后置增强");
} // 切点的方法返回后执行
@AfterRunning("targetMethod()")
public void afterReturningAdivce(){
System.out.println("方法返回后执行");
} // 切点的方法执行抛异常时执行
@AfterThrowing("targetMethod()")
public void afterThrowingAdivce(){
System.out.println("方法执行抛异常");
} // 切点的方法执行前后均执行
@Around("targetMethod()")
public void aroundAdvice(ProceedingJoinPoint pjp) throws Throwable{
System.out.println("注解类型环绕增强,方法执行前"); //执行实际的目标方法
pjp.proceed(); System.out.println("注解类型环绕增强,方法执行后");
}
}
http\://www.springframework.org/schema/aop=org.springframework.aop.config.AopNamespaceHandler
AopNamespaceHandler在spring-aop的config包内定义,定义如下:
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.1 XSD.
// aop:config
registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser());
// aop:aspectj-autoproxy
registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());
// aop:scoped-proxy
registerBeanDefinitionDecorator("scoped-proxy", new ScopedProxyBeanDefinitionDecorator());
// Only in 2.0 XSD: moved to context namespace as of 2.1
// aop:spring-configured
registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
}
}
aop:config标签解析
aop:config的解析器为ConfigBeanDefinitionParser,主要用于解析aop:config内部各标签并生成对应的bean对象,注册到spring的IOC容器中,具体逻辑在parse方法定义:
public BeanDefinition parse(Element element, ParserContext parserContext) {
CompositeComponentDefinition compositeDef =
new CompositeComponentDefinition(element.getTagName(), parserContext.extractSource(element));
parserContext.pushContainingComponent(compositeDef);
// 配置用于将pointcut匹配的bean生成对应的Proxy代理对象的BeanPostProcessor,
// 该BeanPostProcessor具体为AspectJAwareAdvisorAutoProxyCreator
configureAutoProxyCreator(parserContext, element);
List<Element> childElts = DomUtils.getChildElements(element);
for (Element elt: childElts) {
String localName = parserContext.getDelegate().getLocalName(elt);
// 处理pointcut
if (POINTCUT.equals(localName)) {
parsePointcut(elt, parserContext);
}
// 处理advisor
else if (ADVISOR.equals(localName)) {
parseAdvisor(elt, parserContext);
}
// 处理aspect
else if (ASPECT.equals(localName)) {
parseAspect(elt, parserContext);
}
}
parserContext.popAndRegisterContainingComponent();
return null;
}
// 在bean对象实例的创建过程中,在创建bean对象实例之前,先调用这个方法,看是否需要创建一个AOP代理对象直接返回
@Override
public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) {
Object cacheKey = getCacheKey(beanClass, beanName); // 返回null,则表示不是AOP的目标对象,不需要创建代理对象
if (!StringUtils.hasLength(beanName) || !this.targetSourcedBeans.contains(beanName)) {
if (this.advisedBeans.containsKey(cacheKey)) {
return null;
}
if (isInfrastructureClass(beanClass) || shouldSkip(beanClass, beanName)) {
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return null;
}
} // Create proxy here if we have a custom TargetSource.
// Suppresses unnecessary default instantiation of the target bean:
// The TargetSource will handle target instances in a custom fashion.
TargetSource targetSource = getCustomTargetSource(beanClass, beanName);
if (targetSource != null) {
if (StringUtils.hasLength(beanName)) {
this.targetSourcedBeans.add(beanName);
} // specificInterceptors类型为Advisor[],是当前bean需要的辅助功能列表
// 因为Advisor集成了pointcut和advice,故可以知道当前bean是否在pointcut拦截范围内,
// 如果在获取配置对应的advice列表,该列表作为代理对象的interceptor方法拦截器
// getAdvicesAndAdvisorsForBean由子类实现
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(beanClass, beanName, targetSource); // 基于以上辅助功能列表,创建该bean对应的代理对象proxy
Object proxy = createProxy(beanClass, beanName, specificInterceptors, targetSource);
this.proxyTypes.put(cacheKey, proxy.getClass()); // 只有创建了proxy,才不返回null
return proxy;
} return null;
}
protected List<Advisor> findCandidateAdvisors() {
// Add all the Spring advisors found according to superclass rules.
List<Advisor> advisors = super.findCandidateAdvisors();
// Build Advisors for all AspectJ aspects in the bean factory.
if (this.aspectJAdvisorsBuilder != null) {
// 检查@Aspect注解的类并解析其内部使用了@Around, @Before,
// @After, @AfterReturning, @AfterThrowing注解的方法作为advisors
advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());
}
return advisors;
}
this.aspectJAdvisorsBuilder.buildAspectJAdvisors()的方法核心实现:从spring的IOC容器中获取使用了@Aspect注解的bean,然后解析该bean对象内部使用了@Around, @Before, @After, @AfterReturning, @AfterThrowing注解的方法并生成对应的advisors。
// 遍历spring的IOC容器的所有bean对象,如使用了@Component注解的类就在其中
for (String beanName : beanNames) { ... // 检查给定的bean对象所在类是否使用了@Aspect注解
if (this.advisorFactory.isAspect(beanType)) {
aspectNames.add(beanName);
AspectMetadata amd = new AspectMetadata(beanType, beanName);
if (amd.getAjType().getPerClause().getKind() == PerClauseKind.SINGLETON) {
MetadataAwareAspectInstanceFactory factory =
new BeanFactoryAspectInstanceFactory(this.beanFactory, beanName); // 获取所有使用了@Around, @Before, @After, @AfterReturning, @AfterThrowing注解的方法
List<Advisor> classAdvisors = this.advisorFactory.getAdvisors(factory);
if (this.beanFactory.isSingleton(beanName)) {
this.advisorsCache.put(beanName, classAdvisors);
}
else {
this.aspectFactoryCache.put(beanName, factory);
}
advisors.addAll(classAdvisors);
}
else { ... MetadataAwareAspectInstanceFactory factory =
new PrototypeAspectInstanceFactory(this.beanFactory, beanName);
this.aspectFactoryCache.put(beanName, factory); // 获取所有使用了@Around, @Before, @After, @AfterReturning, @AfterThrowing注解的方法
advisors.addAll(this.advisorFactory.getAdvisors(factory));
}
}
}
喜欢的可以关注我,会不定时发表java相关知识-spring源码系列,高并发,分布式,多线程,微服务等的文章及JAVA 面试题集
获取更多学习资料,可以加群:473984645或扫描下方二维码

Spring AOP源码分析(二):AOP的三种配置方式与内部解析实现的更多相关文章
- 5.2 Spring5源码--Spring AOP源码分析二
目标: 1. 什么是AOP, 什么是AspectJ 2. 什么是Spring AOP 3. Spring AOP注解版实现原理 4. Spring AOP切面原理解析 一. 认识AOP及其使用 详见博 ...
- 5.2 spring5源码--spring AOP源码分析二--切面的配置方式
目标: 1. 什么是AOP, 什么是AspectJ 2. 什么是Spring AOP 3. Spring AOP注解版实现原理 4. Spring AOP切面原理解析 一. 认识AOP及其使用 详见博 ...
- 精尽Spring MVC源码分析 - HandlerMapping 组件(三)之 AbstractHandlerMethodMapping
该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...
- 精尽Spring MVC源码分析 - HandlerAdapter 组件(三)之 HandlerMethodArgumentResolver
该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...
- spring源码分析(二)Aop
创建日期:2016.08.19 修改日期:2016.08.20-2016.08.21 交流QQ:992591601 参考资料:<spring源码深度解析>.<spring技术内幕&g ...
- Spring AOP源码分析(三):基于JDK动态代理和CGLIB创建代理对象的实现原理
AOP代理对象的创建 AOP相关的代理对象的创建主要在applyBeanPostProcessorsBeforeInstantiation方法实现: protected Object applyBea ...
- 精尽Spring MVC源码分析 - 文章导读
该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...
- 精尽Spring MVC源码分析 - ViewResolver 组件
该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...
- spring aop 源码分析(二) 代理方法的执行过程分析
在上一篇aop源码分析时,我们已经分析了一个bean被代理的详细过程,参考:https://www.cnblogs.com/yangxiaohui227/p/13266014.html 本次主要是分析 ...
随机推荐
- Golang 标准库提供的Log(一)
原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者信息和本声明.否则将追究法律责任.http://gotaly.blog.51cto.com/8861157/1405754 G ...
- error LNK2001: unresolved external symbol "public: __thiscall CWinAppEx::CWinAppEx(int)" (??0CWinApp
1.这个是网上找的别人的解决办法,本人没试过 解决办法: 网上大多说的这类似问题是解决自定义类的,可是这个连接是系统CWinAppEx,网上查了很久也没找到原因,重装了vs也没解决.后面在google ...
- (53)C# 工具
https://docs.microsoft.com/zh-cn/dotnet/framework/tools/ildasm-exe-il-disassembler 一.Visual Studio的开 ...
- GIT 学习第二天 (二)
工作区和暂存区 工作区: 就是你在电脑里能看到的目录,比如:webgit 文件夹 就是一个工作区 版本库: 工作区有一个隐藏目录 .git ,这个不算工作区,而是Git的版本库 Git的版本库里存了很 ...
- Editor REST Client
Editor REST Client 制作一个http请求 请求行 GET https://example.com/comments/1 HTTP/1.1 POST https://example.c ...
- UI自动化ADB出现devices offline的解决方法
终端运行如下命令即可解决: adb kill-server adb start-server adb remount
- SQL登录报错
在安装完SQL后,发现报出了error40和53的错误,作为小白的我也是一脸懵逼,明明一切都是按照默认加下一步安装的,为什么到了连接数据库的时候就出现了问题呢? 后来经过调查,发现需要将sql配置管理 ...
- Box-Cox变换
简介 编辑 Box-Cox变换的一般形式为: 式中 为经Box-Cox变换后得到的新变量, 为原始连续因变量, 为变换参数.以上变换要求原始变量 取值为正,若取值为负时,可先对 ...
- [转载]python异常如何全面捕获
写在前面:最近写python程序,进场遇到异常的问题,因此需要捕获异常.查阅了下资料,整理如下: 常见的异常处理的方法: 假设有下面的一段程序: try: 语句1 语句2 . ...
- Java微服务(Spring-boot+MyBatis+Maven)入门教程
1,项目创建 新建maven项目,如下图: 选择路径,下一步 输入1和2的内容,点完成 项目创建完毕,结构如下图所示: 填写pom.xml里内容,为了用于打包,3必须选择jar,4和5按图上填写 ...