AOP配置
在应用代码中,可以通过在spring的XML配置文件applicationContext.xml或者基于注解方式来配置AOP。
AOP配置的核心元素为:pointcut,advisor,aspect,pointcut用于定义需要该辅助功能的类或方法集合;advisor则是将advice和pointcut结合起来,在spring的IOC容器启动时,为pointcut匹配的类生成代理对象,使用拦截器拦截对应的方法的执行,将辅助功能advice添加进去;aspect表示一个完整切面,即在aspect对应的类中定义辅助方法advice,然后在aspect中组装到pointcut拦截的方法集合中。
一、XML配置方式
在applicationContext.xml配置文件中配置AOP,所有配置都在<aop:config >命名空间内,其中可以通过aspect或者advisor两种方式来配置,二者是等价的。
基于aspect配置
 
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>
二、基于注解的配置方式
为了在spring中启动对@AspectJ注解支持,需要在类加载路径下新增两个AspectJ库:aspectjweaver.jar和aspectjrt.jar。除此之外,Spring AOP还需要依赖一个aopalliance.jar包
基于注解的配置通常也需要先在spring的XML配置文件中配置来开启基于注解的配置开关。如下:
<?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("注解类型环绕增强,方法执行后");
}
}
内部源码解析
在内部源码实现当中,主要是在spring容器启动,加载解析applicationContext.xml时,解析对应的标签来完成AOP相关组件的加载。
与spring的其他标签解析规则一样,在spring-aop源码包的META-INF目录的spring.handlers文件中定义aop命令空间解析器:
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;
}
核心逻辑为:首先创建一个BeanPostProcessor接口的实现类AspectJAwareAdvisorAutoProxyCreator的对象注册到spring中,具体为BeanPostProcessor的子接口InstantiationAwareBeanPostProcessor接口的实现类。
AspectJAwareAdvisorAutoProxyCreator的作用是在spring创建每个bean对象实例时,都检查一下是否在pointcut的拦截范围内,如果存在则需要结合advisor获取辅助方法为该bean对象创建对应的代理对象来注册到spring的IOC容器,即实际注册到spring的IOC容器的不是bean对象自身而是该代理对象,AspectJAwareAdvisorAutoProxyCreator的核心方法为postProcessBeforeInstantiation:
// 在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;
}
其次是解析aop:config内部的aop:pointcut,aop:advisor,aop:aspect子标签,生成的advisors注册到spring的IOC容器中。advisors是在上面AspectJAwareAdvisorAutoProxyCreator的postProcessBeforeInstantiation方法中创建目标bean对象的代理对象时使用。
aop:aspect-autoproxy标签解析和@AspectJ注解处理
aop:aspect-autoproxy标签解析器为AspectJAutoProxyBeanDefinitionParser,AspectJAutoProxyBeanDefinitionParser也是通过注册一个BeanPostProcess接口的实现类AnnotationAwareAspectJAutoProxyCreator到spring容器。其中AnnotationAwareAspectJAutoProxyCreator继承于AspectJAwareAdvisorAutoProxyCreator,由上面分析可知AspectJAwareAdvisorAutoProxyCreator是aop:config的解析器。
由于继承于AspectJAwareAdvisorAutoProxyCreator,所以也是在postProcessBeforeInstantiation方法中判断是否需要为给定的bean对象创建对应的代理对象注册到spring的IOC容器。
由上面的分析可知,基于aop:config配置的方式,由于aop:pointcut, aop:advisor, aop:aspect都已在applicationContext.xml配置文件中配置好了,所以直接是在parser,即ConfigBeanDefinitionParser中解析生成对应的advisors。
而基于@AspectJ注解方式配置pointcut, advisor时,是使用懒加载的方式,即在spring的IOC容器创建bean对象时,通过AnnotationAwareAspectJAutoProxyCreator来获取@AspectJ注解的类并获取其内部的poitcut,advice等配置,即PointCut,@Before,@After等注解的处理,创建对应的advisors集合并缓存起来实现只懒加载一次,之后这些advisors可以直接使用。具体在findCandidateAdvisors方法定义,如下,然后在创建代理对象时使用。
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的三种配置方式与内部解析实现的更多相关文章

  1. 5.2 Spring5源码--Spring AOP源码分析二

    目标: 1. 什么是AOP, 什么是AspectJ 2. 什么是Spring AOP 3. Spring AOP注解版实现原理 4. Spring AOP切面原理解析 一. 认识AOP及其使用 详见博 ...

  2. 5.2 spring5源码--spring AOP源码分析二--切面的配置方式

    目标: 1. 什么是AOP, 什么是AspectJ 2. 什么是Spring AOP 3. Spring AOP注解版实现原理 4. Spring AOP切面原理解析 一. 认识AOP及其使用 详见博 ...

  3. 精尽Spring MVC源码分析 - HandlerMapping 组件(三)之 AbstractHandlerMethodMapping

    该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...

  4. 精尽Spring MVC源码分析 - HandlerAdapter 组件(三)之 HandlerMethodArgumentResolver

    该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...

  5. spring源码分析(二)Aop

    创建日期:2016.08.19 修改日期:2016.08.20-2016.08.21 交流QQ:992591601 参考资料:<spring源码深度解析>.<spring技术内幕&g ...

  6. Spring AOP源码分析(三):基于JDK动态代理和CGLIB创建代理对象的实现原理

    AOP代理对象的创建 AOP相关的代理对象的创建主要在applyBeanPostProcessorsBeforeInstantiation方法实现: protected Object applyBea ...

  7. 精尽Spring MVC源码分析 - 文章导读

    该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...

  8. 精尽Spring MVC源码分析 - ViewResolver 组件

    该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...

  9. spring aop 源码分析(二) 代理方法的执行过程分析

    在上一篇aop源码分析时,我们已经分析了一个bean被代理的详细过程,参考:https://www.cnblogs.com/yangxiaohui227/p/13266014.html 本次主要是分析 ...

随机推荐

  1. 机器学习技法笔记:02 Dual Support Vector Machine、KKT

    原文地址:https://www.jianshu.com/p/58259cdde0e1 Roadmap Motivation of Dual SVM Lagrange Dual SVM Solving ...

  2. pcre2 正则库

    \S+ 不能匹配到字符串末尾的最后一个字段

  3. 2019秋Java课程总结&实验总结一

    1.打印输出所有的"水仙花数",所谓"水仙花数"是指一个3位数,其中各位数字立方和等于该数本身.例如,153是一个"水仙花数". 实验源码: ...

  4. 高级UI晋升之View渲染机制(二)

    更多Android高级架构进阶视频学习请点击:https://space.bilibili.com/474380680 优化性能一般从渲染,运算与内存,电量三个方面进行,今天开始说聊一聊Android ...

  5. what codes does sudo command do in Linux?

    sometime, to make your change of configuration file be effective to web application, we have to rest ...

  6. 项目警告:There are multiple modules with names that only differ in casing.This can lead to unexpected behavior when compiling on a filesystem with other case-semantic.Use equal casing. Compare these modul

    记录个自己遇到的问题: 上星期项目刚拉取下来的时候运行没有任何警告,晚上回去vscode提示更新新的东西,当时没管就立即更新了,第二天重启项目直接一大堆警告冒了出来: There are multip ...

  7. java oop第15章_Socket网络编程

    一.   TCP/IP协议(Transmission Control Protocol/Internet Protocol)传输控制协议/Internet协议,是通信领域的基础.核心协议, 其他的协议 ...

  8. 带你彻底理解RSA算法原理,很简单的

    1. 什么是RSA RSA算法是现今使用最广泛的公钥密码算法,也是号称地球上最安全的加密算法. 在了解RSA算法之前,先熟悉下几个术语 根据密钥的使用方法,可以将密码分为 对称密码 和 公钥密码 对称 ...

  9. BCZM : 1.16

    24点游戏 解法一:穷举法 解法二:分治法

  10. Q:微信小程序一次性订阅消息(前台收集)

    说明:官方文档(https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/subscribe-message.ht ...