本人本科毕业,21届毕业生,一年工作经验,简历专业技能如下,现根据简历,并根据所学知识复习准备面试。
记录日期:2022.1.21
大部分知识点只做大致介绍,具体内容根据推荐博文链接进行详细复习。
文章目录
框架原理 - Spring(二) 之 Spring AOP 基于xml的<aop:config />配置标签解析
引入
当前使用 Spring 版本 5.2.5.RELEASE
Spring 2.0 开始采用 @AspectJ
注解对 POJO
标注,使用切点表达式语法进行切点定义。
Spring支持注解的 AOP,需要在xml配置文件中配置 <aop:aspectj-autoproxy />
。
在 Spring 中自定义注解
和自定义标签
都会在Spring中找到注册该注解或者标签的对应解析器
。
而在 Spring 2.0 之前,一般都是使用 xml配置文件
进行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:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">
<!-- 切面 -->
<bean id="myAspect" class="com.test.aop.MyAspect"></bean>
<bean id="userDao" class="com.test.dao.UserDao"></bean>
<!-- 切入点 -->
<aop:config>
<aop:aspect id="asp1" ref="myAspect">
<aop:before method="authority" pointcut="execution(* com.test.dao.*.*(..))" />
<aop:after method="release" pointcut="execution(* com.test.dao.*.*(..))" />
<aop:after-returning method="log" pointcut="execution(* com.test.dao.*.*(..))" returning="rvt" />
<aop:around method="processTx" pointcut="execution(* com.test.dao.*.*(..))" />
</aop:aspect>
</aop:config>
</beans>
自定义标签的解析,在备战面试日记(4.2.4)- (框架.Spring【四】之Spring IOC 源码obtainFreshBeanFactory())看过源码实现。所以知道,对于自定义标签的解析是在 parseCustomElement()
方法中完成的,不同扩展标签的解析,是根据该标签的本地名称去从 NamespaceHandlerSupport
的 parsers
缓存中获取对应的 BeanDefinitionParser
解析器来完成的。
对于 aop命名空间
下的系列的标签的解析器,都是通过AopNamespaceHandler注册到 parsers
缓存中的,从该类中我们能知道所有aop系列标签及其子标签的解析器。
init()
引入AOP功能时,Spring会从 META-INF/spring.handlers
配置文件中拿到该注解对应的 NamespaceHandlerSupport:AopNamespaceHandler
。
http\://www.springframework.org/schema/aop=org.springframework.aop.config.AopNamespaceHandler
在 AopNamespaceHandler
的 init()
方***给该注解注册对应的解析器,aspectj-autoproxy
对应的解析器是:AspectJAutoProxyBeanDefinitionParser
。
// AOP 命名空间处理器
public class AopNamespaceHandler extends NamespaceHandlerSupport {
/* 注册不同的BeanDefinitionParser,用于解析对应的自定义标签: 1. <aop:config/>标签及其子标签 2. <aop:aspectj-autoproxy/>标签及其子标签 3. <aop:scoped-proxy/>标签及其子标签 4. <aop:spring-configured/>标签及其子标签 */
@Override
public void init() {
// 在2.0以及2.5之后的xsd文件中均有的标签
registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser());
registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());
registerBeanDefinitionDecorator("scoped-proxy", new ScopedProxyBeanDefinitionDecorator());
// 2.5及其之后的xsd文件中,该标签被移除,因此Spring 5.x版本的XML配置中不能使用该标签了【被移动到context命名空间了】
registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
}
}
这里对这几种标签进行简单介绍:
<aop:config />
:用于基于 XML 配置 AOP。<aop:aspectj-autoproxy />
:用于基于XML开启AOP注解
自动配置的支持,也就是支持@Aspect
切面类及其内部的AOP注解
,另外还有@EnableAspectJAutoProxy
注解也能够开启AOP注解
自动配置的支持,用于彻底摆脱XML文件。<aop:scoped-proxy />
:是作用域代理标签,用于装饰bean,改变其生命周期,将暴露出来的bean的生命周期控制在正确的范围类,用的比较少。<spring-configured />
:主要是通过Spring管理AnnotationBeanConfigurerAspect
切面, 具体的工作由该切面完成。
<aop:config /> 标签解析
registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser());
首先,我们从 ConfigBeanDefinitionParser#parse()
方法的源码作为入口。
ConfigBeanDefinitionParser#parse()
parser()
方法的目的就是解析 <aop:config />
标签及其子标签,将这些标签封装为对应类型的 Bean 定义。
/** * 解析<aop:config/>标签及其子标签 * * @param element <aop:config/>标签元素 * @param parserContext 解析上下文 * @return 返回null */
@Override
@Nullable
public BeanDefinition parse(Element element, ParserContext parserContext) {
// 1. 新建一个CompositeComponentDefinition类型的bean定义,名称就是标签名 aop:config
// 内部保存了多个ComponentDefinition,基于XML的 source 默认为null
CompositeComponentDefinition compositeDef =
new CompositeComponentDefinition(element.getTagName(), parserContext.extractSource(element));
// 2. 存入解析上下文内部的 containingComponents 集合中,入栈顶
parserContext.pushContainingComponent(compositeDef);
// 3. 尝试向容器注入或者升级AspectJAwareAdvisorAutoProxyCreator类型的自动代理创建者bean定义,专门用于后续创建AOP代理对象
configureAutoProxyCreator(parserContext, element);
// 4. 获取<aop:config/>标签下的所有子标签结点元素
List<Element> childElts = DomUtils.getChildElements(element);
// 遍历解析
for (Element elt: childElts) {
// 获取子标签本地名称,即去除"aop:"前缀,例如"aop:pointcut" -> "pointcut"
String localName = parserContext.getDelegate().getLocalName(elt);
//如果是 <aop:pointcut/> 标签
if (POINTCUT.equals(localName)) {
// 5. 调用parsePointcut方法解析 <aop:pointcut/> 标签
parsePointcut(elt, parserContext);
}
//如果是 <aop:advisor/> 标签
else if (ADVISOR.equals(localName)) {
// 6. 调用parseAdvisor方法解析 <aop:advisor/> 标签
parseAdvisor(elt, parserContext);
}
//如果是 <aop:aspect/> 标签
else if (ASPECT.equals(localName)) {
// 7. 调用parseAspect方法解析 <aop:aspect/> 标签
parseAspect(elt, parserContext);
}
}
// 8. 出栈并注册,并不是注册到注册表中,可能什么也不做
parserContext.popAndRegisterContainingComponent();
// 返回null
return null;
}
pushContainingComponent() & popAndRegisterContainingComponent()
在 ConfigBeanDefinitionParser#parse()
代码块的 2
和 8
中。
这两个方法是在 ParserContext
实现的,这个类是用来在bean定义解析过程中传递的上下文,封装所有相关的配置和状态。
public final class ParserContext {
// 使用 ArrayDeque 提供栈操作,用于存放一系列的组件bean定义
private final Deque<CompositeComponentDefinition> containingComponents = new ArrayDeque<>();
// 存入containingComponents栈顶
public void pushContainingComponent(CompositeComponentDefinition containingComponent) {
this.containingComponents.push(containingComponent);
}
// 使栈顶元素出栈并注册
public void popAndRegisterContainingComponent() {
//注册组件
registerComponent(popContainingComponent());
}
// 栈顶元素出栈
public CompositeComponentDefinition popContainingComponent() {
return this.containingComponents.pop();
}
// 注册组件,并不是注册到缓存中
public void registerComponent(ComponentDefinition component) {
// peek 最新栈顶元素
CompositeComponentDefinition containingComponent = getContainingComponent();
// 如果不为空,那么当前组件加入到栈顶元素的内部集合中
if (containingComponent != null) {
// 栈顶元素将pop出的元素嵌套进去
// nestedComponents = new ArrayList<>();
containingComponent.addNestedComponent(component);
}
// 否则,通过readerContext发布组件注册事件,默认空实现 no-op
else {
this.readerContext.fireComponentRegistered(component);
}
}
// 获取但不移除最新栈顶元素
@Nullable
public CompositeComponentDefinition getContainingComponent() {
return this.containingComponents.peek();
}
}
configureAutoProxyCreator()
在 ConfigBeanDefinitionParser#parse()
代码块的 3
中。
configureAutoProxyCreator
方法用于尝试向容器注入或者升级一个 AspectJAwareAdvisorAutoProxyCreator
类型的自动代理创建者bean定义,beanName为 "org.springframework.aop.config.internalAutoProxyCreator"
,专门用于后续创建AOP代理对象。
我们可以来看一下 AspectJAwareAdvisorAutoProxyCreator
的类图:
我们发现,这个类还实现了比如 BeanClassLoaderAware
、 BeanFactoryAware
、 SmartInstantiationAwareBeanPostProcessor
、 InstantiationAwareBeanPostProcessor
、 BeanPostProcessor
等一系列的自动回调接口,它们在创建代理对象的过程中非常有用,这些接口也是Spring的强扩展性的体现。
<aop:config />
标签使用 AspectJAwareAdvisorAutoProxyCreator
创建代理,实际上还有很多创建者可以用于创建代理对象,比如<aop:aspectj-autoproxy />
以及 @EnableAspectJAutoProxy
使用 AnnotationAwareAspectJAutoProxyCreator
,<tx:annotation-driven />
以及 @EnableTransactionManagement
使用 InfrastructureAdvisorAutoProxyCreator
,不同的标签或者注解使用不同的创建者,但容器最终只会创建一个bean定义,采用优先级最高的自动代理创建者的类型,后面会详细说明。
// 通过<aop:config/>标签的解析触发调用,尝试配置AspectJAwareAdvisorAutoProxyCreator类型的自动代理创建者的bean定义到容器
private void configureAutoProxyCreator(ParserContext parserContext, Element element) {
AopNamespaceUtils.registerAspectJAutoProxyCreatorIfNecessary(parserContext, element);
}
实际上是调用了 AopNamespaceUtils.registerAspectJAutoProxyCreatorIfNecessary()
方法。
这个方法我们可以将它分为三个步骤:
-
调用
AopConfigUtils.registerAspectJAutoProxyCreatorIfNecessary()
方法,尝试注册或者升级一个名为"org.springframework.aop.config.internalAutoProxyCreator"
,类型为AspectJAwareAdvisorAutoProxyCreator
的自动代理创建者的bean定义。 -
调用
useClassProxyingIfNecessary()
方法,解析proxy-target-class
与expose-proxy
属性。-
proxy-target-class
用于设置代理模式,默认是优先JDK动态代理
,其次CGLIB代理
,可以指定为CGLIB代理。 -
expose-proxy
用于暴露代理对象,主要用来解决同一个目标类的方法互相调用时代理不生效的问题。
-
-
调用
registerComponentIfNecessary()
方法注册组件,这里的注册是指存放到外层方法新建的CompositeComponentDefinition
对象的内部集合中或者广播事件,而不是注册到缓存中。
public static void registerAspectJAutoProxyCreatorIfNecessary(
ParserContext parserContext, Element sourceElement) {
/* 1. 尝试注册或者升级一个名为"org.springframework.aop.config.internalAutoProxyCreator", 类型为AspectJAwareAdvisorAutoProxyCreator的自动代理创建者的bean定义。 */
BeanDefinition beanDefinition = AopConfigUtils.registerAspectJAutoProxyCreatorIfNecessary(
parserContext.getRegistry(), parserContext.extractSource(sourceElement));
/* 2. 解析proxy-target-class与expose-proxy属性 */
useClassProxyingIfNecessary(parserContext.getRegistry(), sourceElement);
/* 3. 注册组件 */
registerComponentIfNecessary(beanDefinition, parserContext);
}
1. registerAspectJAutoProxyCreatorIfNecessary()
registerAspectJAutoProxyCreatorIfNecessary()
方法用于注册/升级自动代理创建者。
内部继续调用 registerOrEscalateApcAsRequired
方法,由于解析的 <aop:config />
标签,因此第一个参数是 AspectJAwareAdvisorAutoProxyCreator.class
类型。
@Nullable
public static BeanDefinition registerAspectJAutoProxyCreatorIfNecessary(
BeanDefinitionRegistry registry, @Nullable Object source) {
return registerOrEscalateApcAsRequired(AspectJAwareAdvisorAutoProxyCreator.class, registry, source);
}
registerOrEscalateApcAsRequired()
这是实现 “升级” 或 “注册” 的核心方法,简单梳理逻辑:
- 首先尝试获取名为
"org.springframework.aop.config.internalAutoProxyCreator"
的bean定义,如果已存在,那么比较bean定义中的自动代理创建者的类型和当前参数传递的自动代理创建者的类型的优先级。- 如果当前参数传递的自动代理创建者的类型的优先级更高,那么bean定义的beanClass属性设置为使用当前参数传递的自动代理创建者的类型的className,即升级bean定义,最后返回 null。设置bean定义的order属性优先级最高,也就是说将会最先被应用并尝试创建代理对象。
- 如果没有该名字的bean定义,那么使用当前参数class类型,也就是
AspectJAwareAdvisorAutoProxyCreator.class
类型作为beanClass,新建一个RootBeanDefinition
类型的bean定义,以"org.springframework.aop.config.internalAutoProxyCreator"
为beanName,通过registerBeanDefinition
注册到容器中。最后返回新增的bean定义。
从逻辑看出,【容器最终只会创建一个自动代理创建者bean定义,采用优先级最高的自动代理创建者的类型。】
registerBeanDefinition
方法在备战面试日记(4.2.4)- (框架.Spring【四】之Spring IOC 源码obtainFreshBeanFactory())中讲过了。
@Nullable
private static BeanDefinition registerOrEscalateApcAsRequired(
Class<?> cls, BeanDefinitionRegistry registry, @Nullable Object source) {
Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
// 1. 查看是否包含名为"org.springframework.aop.config.internalAutoProxyCreator"的bean定义
if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {
// 1.1 获取bean定义
BeanDefinition apcDefinition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
// 1.2 如果当前bean定义的类型不是参数的类型,那么选择优先级最高的类型
if (!cls.getName().equals(apcDefinition.getBeanClassName())) {
// 1.3 获取当前bean定义中的自动代理创建者的类型优先级,实际上就是存储在APC_PRIORITY_LIST集合的索引位置
int currentPriority = findPriorityForClass(apcDefinition.getBeanClassName());
// 1.4 获取当前参数传递的自动代理创建者的类型优先级,实际上就是存储在APC_PRIORITY_LIST集合的索引位置
int requiredPriority = findPriorityForClass(cls);
// 如果bean定义中的自动代理创建者的类型优先级 < 当前参数传递的自动代理创建者的类型优先级
if (currentPriority < requiredPriority) {
// 1.5 bean定义的beanClass属性设置为 当前参数传递的自动代理创建者的类型的className,即"升级"
apcDefinition.setBeanClassName(cls.getName());
}
}
return null;
}
// 如果不包含,则需要创建新的bean定义
// 2. 新建一个RootBeanDefinition类型的bean定义,beanClass使用当前参数class类型
RootBeanDefinition beanDefinition = new RootBeanDefinition(cls);
beanDefinition.setSource(source);
// 设置order属性优先级最高,也就是说将会最先被应用并尝试创建代理对象
beanDefinition.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE);
beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
// 2.1. 注册新的BeanDefinition到缓存
registry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME, beanDefinition);
return beanDefinition;
}
findPriorityForClass()
在 registerOrEscalateApcAsRequired()
代码块的 1.3
和 1.4
中。
该用于获取自动类型创建者的优先级。实际上就是从 AopConfigUtils
的 APC_PRIORITY_LIST
集合中查找该class的索引位置并返回,找不到就抛出异常。
private static int findPriorityForClass(@Nullable String className) {
for (int i = 0; i < APC_PRIORITY_LIST.size(); i++) {
Class<?> clazz = APC_PRIORITY_LIST.get(i);
if (clazz.getName().equals(className)) {
return i;
}
}
throw new IllegalArgumentException(
"Class name [" + className + "] is not a known auto-proxy creator class");
}
APC_PRIORITY_LIST
集合在 AopConfigUtils
类加载的时候就在静态块中按顺序初始化了一系列的自动代理创建者的类型,索引位置就是优先级,因此优先级从小到大排序为:
InfrastructureAdvisorAutoProxyCreator.class
AspectJAwareAdvisorAutoProxyCreator.class
AnnotationAwareAspectJAutoProxyCreator.class
所以当我们使用不同的标签时,就会注册不同的自动代理创建者:
- 如果是解析
<tx:annotation-driven />
标签或者@EnableTransactionManagement
事务注解,那么cls参数是InfrastructureAdvisorAutoProxyCreator.class
- 如果是解析
<aop:config />
标签,那么cls参数是AspectJAwareAdvisorAutoProxyCreator.class
- 如果是解析
<aop:aspectj-autoproxy />
标签或者@EnableAspectJAutoProxy
注解,那么cls参数是AnnotationAwareAspectJAutoProxyCreator.class
最终根据优先级只创建唯一一个优先级最高的自动代理创建者:
比如说:如果我们设置了 <aop:config />
和 <aop:aspectj-autoproxy />
两个标签,那么最终会注册 AnnotationAwareAspectJAutoProxyCreator
类型的自动代理创建者。
// 按升序顺序存储的自动代理创建者的类型集合,默认有三种类型,优先级就是比较索引顺序的大小。
private static final List<Class<?>> APC_PRIORITY_LIST = new ArrayList<>(3);
static {
// 按先后顺序添加三种类型
APC_PRIORITY_LIST.add(InfrastructureAdvisorAutoProxyCreator.class);
APC_PRIORITY_LIST.add(AspectJAwareAdvisorAutoProxyCreator.class);
APC_PRIORITY_LIST.add(AnnotationAwareAspectJAutoProxyCreator.class);
}
2. useClassProxyingIfNecessary()
useClassProxyingIfNecessary()
方法用于解析、设置 proxy-target-class
和 expose-proxy
属性到这个bean定义的 proxyTargetClass
和 exposeProxy
属性中。
proxy-target-class
属性:用于设置代理模式,默认为false,即优先JDK动态代理,其次CGLIB代理,可以设置为true,表示强制为CGLIB代理。expose-proxy
属性:用于暴露代理对象,主要用来解决同一个目标类的方法互相调用时代理不生效的问题。默认值为false表示不开启,设置为true表示开启,可以在代码中通过AopContext.currentProxy()
获取当前代理类对象。
private static void useClassProxyingIfNecessary(
BeanDefinitionRegistry registry, @Nullable Element sourceElement) {
if (sourceElement != null) {
// 1. 获取proxy-target-class属性值,默认false,即优先采用JDK动态代理,不满足则采用 CGLIB代理
boolean proxyTargetClass = Boolean.parseBoolean(sourceElement.getAttribute(PROXY_TARGET_CLASS_ATTRIBUTE));
// 1.1 如果设置为 true,那么强制走 CGLIB代理
if (proxyTargetClass) {
AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
}
// 2. 获取expose-proxy属性值,默认false,即不暴露代理对象
boolean exposeProxy = Boolean.parseBoolean(sourceElement.getAttribute(EXPOSE_PROXY_ATTRIBUTE));
// 2.2 如果设置为true,那么暴露代理对象
if (exposeProxy) {
AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
}
}
}
forceAutoProxyCreatorToUseClassProxying()
在 useClassProxyingIfNecessary()
代码块的 1.1
中。
如果 proxy-target-class
属性为true,那么强制使用 CGLIB
创建代理对象,这里仅仅是设置bean定义的 proxyTargetClass
属性值为true,后面才会用到。这实际上是顶级父类 ProxyConfig
的属性。
// AopConfigUtils 103
// 强迫AutoProxyCreator使用基于类的代理,也就是CGLIB代理
public static void forceAutoProxyCreatorToUseClassProxying(BeanDefinitionRegistry registry) {
// 如果包含名为"org.springframework.aop.config.internalAutoProxyCreator"的bean定义
if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {
// 获取该bean定义
BeanDefinition definition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
// 添加属性proxyTargetClass设置值为true,表示强制使用CGLIB代理
definition.getPropertyValues().add("proxyTargetClass", Boolean.TRUE);
}
}
forceAutoProxyCreatorToExposeProxy()
在 useClassProxyingIfNecessary()
代码块的 2.2
中。
如果 expose-proxy
属性为true,那么强制暴露代理对象,这里仅仅是设置bean定义的 exposeProxy
属性值为true,后面才会用到。这实际上是顶级父类 ProxyConfig
的属性。
// AopConfigUtils 110
// 强迫AutoProxyCreator暴露代理对象
public static void forceAutoProxyCreatorToExposeProxy(BeanDefinitionRegistry registry) {
// 如果包含名为"org.springframework.aop.config.internalAutoProxyCreator"的bean定义
if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {
// 获取该bean定义
BeanDefinition definition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
// 添加属性exposeProxy,设置值为true,表示强制暴露代理对象
definition.getPropertyValues().add("exposeProxy", Boolean.TRUE);
}
}
3. registerComponentIfNecessary()
如果bean定义不为null,即新增了bean定义,那么注册组件,这里的注册是指存放到外层方法新建的 CompositeComponentDefinition
对象的内部集合中,或者广播事件,而不是注册到缓存中。
private static void registerComponentIfNecessary(
@Nullable BeanDefinition beanDefinition, ParserContext parserContext) {
// 如果bean定义不为null就注册组件
if (beanDefinition != null) {
// 实际上是存入parserContext的containingComponents集合的栈顶元素的内部集合中
parserContext.registerComponent(
new BeanComponentDefinition(beanDefinition, AopConfigUtils.AUTO_PROXY_CREATOR_BEAN_NAME));
}
}
parsePointcut()
在 ConfigBeanDefinitionParser#parse()
代码块的 5
中。
用于解析<aop:pointcut />
切入点标签,并将一个 <aop:pointcut />
标签封装成为一个bean定义并且注册到IOC容器的缓存中。
private static final String EXPRESSION = "expression";
private static final String ID = "id";
private ParseState parseState = new ParseState();
// 解析<aop:pointcut/> 标签,也就是切入点表达式标签,生成bean定义注册到容器中
private AbstractBeanDefinition parsePointcut(Element pointcutElement, ParserContext parserContext) {
// 获取id属性值
String id = pointcutElement.getAttribute(ID);
// 获取expression属性值,也就是切入点表达式字符串
String expression = pointcutElement.getAttribute(EXPRESSION);
// <aop:pointcut /> 标签对应的bean定义
AbstractBeanDefinition pointcutDefinition = null;
try {
// 1. 新建一个PointcutEntry点位,压入parseState栈
this.parseState.push(new PointcutEntry(id));
/* 2. 创建切入点表达式的bean定义对象 bean定义类型为RootBeanDefinition beanClass类型为AspectJExpressionPointcut scope属性设置为prototype:原型 synthetic属性设置为true:表示它是一个合成的而不是不是由程序本身定义的bean 设置expression属性的值为切入点表达式字符串【必须属性】 */
pointcutDefinition = createPointcutDefinition(expression);
// 3. 设置源
pointcutDefinition.setSource(parserContext.extractSource(pointcutElement));
//切入点bean定义的默认名字设置为id
String pointcutBeanName = id;
// 4. 如果设置了id属性
if (StringUtils.hasText(pointcutBeanName)) {
/* 4.1 那么将beanName和BeanDefinition注册到registry的缓存中 */
parserContext.getRegistry().registerBeanDefinition(pointcutBeanName, pointcutDefinition);
}
// 如果没有设置id属性
else {
// 4.2 那么生成beanName,随后同样注册到registry的缓存中,返回生成的beanName
/* 采用DefaultBeanNameGenerator作为beanName生成器来生成beanName, 生成的beanName类似于"org.springframework.aop.aspectj.AspectJExpressionPointcut#0" */
pointcutBeanName = parserContext.getReaderContext().registerWithGeneratedName(pointcutDefinition);
}
// 5. 注册组件,这里的注册是指存放到外层方法新建的CompositeComponentDefinition对象的内部集合中或者广播事件,而不是注册到缓存中
parserContext.registerComponent(
new PointcutComponentDefinition(pointcutBeanName, pointcutDefinition, expression));
}
finally {
// PointcutEntry点位出栈
this.parseState.pop();
}
//返回创建的bean定义
return pointcutDefinition;
}
createPointcutDefinition()
在 parsePointcut()
代码块的 2
中。
用于创建一个切入点bean定义,用来描述、解析一个 <aop:pointcut />
切入点标签。
// 使用给定的切入点表达式创建AspectJExpressionPointcut类型的bean定义对象
protected AbstractBeanDefinition createPointcutDefinition(String expression) {
// 新建RootBeanDefinition类型的bean定义,beanClass类型为AspectJExpressionPointcut
RootBeanDefinition beanDefinition = new RootBeanDefinition(AspectJExpressionPointcut.class);
// 设置scope属性为prototype
beanDefinition.setScope(BeanDefinition.SCOPE_PROTOTYPE);
// 设置synthetic属性为true,这表示它是一个合成的而不是不是由程序本身定义的bean
beanDefinition.setSynthetic(true);
// 添加expression属性值为参数的切入点表达式字符串
beanDefinition.getPropertyValues().add(EXPRESSION, expression);
// 返回创建的bean定义
return beanDefinition;
}
它的
synthetic
属性为true,这表示切入点bean定义是一个合成的而不是不是由程序本身定义的bean。之前 IOC 源码学习时遇到的isSynthetic()
方法,就是用于判断某个bean定义是不是合成的bean定义。
registerWithGeneratedName()
在 parsePointcut()
代码块的 4.2
中。
如果没有设置id,那么生成beanName,采用的生成器默认就是 DefaultBeanNameGenerator
,规则如下:
- 外部bean的beanName生成规则就是:“类的全路径名#0”、“类的全路径名#1”、“类的全路径名#2”……。
- 有n个没有命名的同类型外部bean,那么名字后面就是从[0,n-1]类似于索引递增的进行命名,如果中途遇到同名bean,那么跳过这个索引,使用下一个。
所以说,如果没指定id,那么生成的切入点beean定义的beanName就是 "org.springframework.aop.aspectj. AspectJExpressionPointcut#0"
、 “org.springframework.aop.aspectj. AspectJExpressionPointcut#1”
等…
// 调用给定的 bean 名称生成器生成beanName,并注册 bean 定义,最后返回生成的beanName
public String registerWithGeneratedName(BeanDefinition beanDefinition) {
/* 生成beanName,采用的生成器默认就是DefaultBeanNameGenerator DefaultBeanNameGenerator的生成规则就是基于XML的beanName生成规则,我们在"IoC容器初始化(3)"的文章中已经讲过了 生成的beanName类似于 "org.springframework.aop.aspectj.AspectJExpressionPointcut#0" */
String generatedName = generateBeanName(beanDefinition);
// 调用registerBeanDefinition注册bean定义
getRegistry().registerBeanDefinition(generatedName, beanDefinition);
// 返回生成的beanName
return generatedName;
}
看一下 generateBeanName
方法,默认使用的就是DefaultBeanNameGenerator
的 generateBeanName
方法:
public String generateBeanName(BeanDefinition beanDefinition) {
return this.reader.getBeanNameGenerator().generateBeanName(beanDefinition, getRegistry());
}
private BeanNameGenerator beanNameGenerator = DefaultBeanNameGenerator.INSTANCE;
@Override
public BeanNameGenerator getBeanNameGenerator() {
return this.beanNameGenerator;
}
DefaultBeanNameGenerator.generateBeanName() 的具体实现就不看了,也是比较简单的。
parseAdvisor()
在 ConfigBeanDefinitionParser#parse()
代码块的 6
中。
<aop:advisor />
通知器标签,它和 <aop:aspect />
类似,相当于一个小切面,它的 advice-ref
属性可以指向一个通知类bean,该bean要求实现Advice相关接口,不需要单独配置通知,但接口只有前置通知
、后置通知
和异常通知
的方法。通常,advisor被用来管理事物,它的 advice-ref
属性配置对一个 <tx:advice />
的id引用,跟Spring事务相关。
parseAdvisor()
方法用于解析 <aop:advisor />
通知器标签,并将一个 <aop:advisor />
标签封装成为一个bean定义并且注册到IOC容器缓存中。
梳理一下 parseAdvisor()
方法的大致逻辑:
- 根据
<aop:advisor/>
标签创建一个bd。
- bean定义类型为
RootBeanDefinition
- beanClass类型为
DefaultBeanFactoryPointcutAdvisor
advice-ref
属性是必须的,将会设置给bean定义的adviceBeanName
属性,值就是advice-ref
属性的值封装的一个RuntimeBeanNameReference
,将会在运行时解析。- 如果设置了
order
属性,那么为bean定义设置order
属性,值就是order
属性的值,用于通知器的排序。
-
设置通知器bean定义的默认名字。
- 如果设置id属性,则设置通知器bean定义的默认名字为id。
- 如果没设置id属性,那么采用
DefaultBeanNameGenerator
作为beanName生成器来生成beanName,生成的beanName类似于“org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor#0”
。
-
对于
<aop:advisor />
标签的pointcut
以及pointcut-ref
属性,必须且只能设置其中一个:- 如果设置了
pointcut
属性,那么通过createPointcutDefinition
创建一个切入点bean定义,这个的切入点bean定义作为内部bean,不会被注册到容器中。 - 如果设置了
pointcut-ref
属性,那么表示通过id引入外部的切入点。那么将ID字符串封装为一个RuntimeBeanReference
,将会在运行时解析。
- 如果设置了
-
最终会将切入点bean定义或者
RuntimeBeanReference
设置给bean定义的pointcut
属性。
// 解析<aop:advisor/> 标签,也就是通知器标签,一并解析内部的<aop:pointcut /> 标签,生成bean定义并注册到容器注册表缓存中
private void parseAdvisor(Element advisorElement, ParserContext parserContext) {
/* 1. 根据<aop:advisor/> 标签创建一个bd bean定义类型为RootBeanDefinition类型的bean定义 beanClass类型为DefaultBeanFactoryPointcutAdvisor。 解析advice-ref和order属性,设置到bean定义的adviceBeanName和order属性中 */
AbstractBeanDefinition advisorDef = createAdvisorBeanDefinition(advisorElement, parserContext);
// 获取ID的属性值
String id = advisorElement.getAttribute(ID);
try {
// 2. 新建一个AdvisorEntry点位,压入parseState栈
this.parseState.push(new AdvisorEntry(id));
// 通知器bean定义的默认名字设置为id
String advisorBeanName = id;
if (StringUtils.hasText(advisorBeanName)) {
// 3.1 如果设置了id属性,那么直接将beanName和BeanDefinition注册到registry缓存中
parserContext.getRegistry().registerBeanDefinition(advisorBeanName, advisorDef);
}
else {
// 3.2 如果没有设置id属性,那么通过DefaultBeanNameGenerator生成beanName,随后同样注册到registry的缓存中
advisorBeanName = parserContext.getReaderContext().registerWithGeneratedName(advisorDef);
}
// 4. 解析当前<aop:pointcut/> 标签的pointcut或者pointcut-ref属性,获取切入点
// 可能是一个切入点bean定义或者一个切入点bean定义的id
Object pointcut = parsePointcutProperty(advisorElement, parserContext);
// 5.1 如果是一个切入点bean定义,那么表示设置了pointcut属性,返回的就是根据切入点表达式创建的一个切入点bean定义
if (pointcut instanceof BeanDefinition) {
// 5.1.1 为bean定义设置pointcut属性,值就是解析后的切入点bean定义
advisorDef.getPropertyValues().add(POINTCUT, pointcut);
// 5.1.2 注册组件,同之前
parserContext.registerComponent(
new AdvisorComponentDefinition(advisorBeanName, advisorDef, (BeanDefinition) pointcut));
}
// 5.2 如果是一个字符串,那么表示设置了pointcut-ref属性,返回的就是该属性的值,表示引入的其他切入点bean定义的id
else if (pointcut instanceof String) {
// 5.2.1 为bean定义设置pointcut属性,值就是pointcut-ref属性的值封装的一个RuntimeBeanReference,将会在运行时解析
advisorDef.getPropertyValues().add(POINTCUT, new RuntimeBeanReference((String) pointcut));
// 5.2.2 注册组件,同之前
parserContext.registerComponent(
new AdvisorComponentDefinition(advisorBeanName, advisorDef));
}
}
finally {
// 6. 出栈
this.parseState.pop();
}
}
createAdvisorBeanDefinition()
在 parseAdvisor()
代码块 1
中。
该方法用于创建一个通知器bean定义,用来描述、解析一个 <aop:advisor />
通知器标签。
private static final String ADVICE_REF = "advice-ref";
private static final String ORDER_PROPERTY = "order";
private static final String ADVICE_BEAN_NAME = "adviceBeanName";
private AbstractBeanDefinition createAdvisorBeanDefinition(Element advisorElement, ParserContext parserContext) {
// 1. 新建RootBeanDefinition类型的bean定义,beanClass类型为DefaultBeanFactoryPointcutAdvisor
RootBeanDefinition advisorDefinition = new RootBeanDefinition(DefaultBeanFactoryPointcutAdvisor.class);
// 2. 设置源
advisorDefinition.setSource(parserContext.extractSource(advisorElement));
/* 3. 获取advice-ref属性的值 advice-ref可以传递一个id指向一个<tx:advice />标签,用来管理事务 或者可以传递一个id或者name,指向一个实现了Advice接口的bean定义 */
String adviceRef = advisorElement.getAttribute(ADVICE_REF);
// 4. 如果没有设置这个属性,那就抛出异常
if (!StringUtils.hasText(adviceRef)) {
parserContext.getReaderContext().error(
"'advice-ref' attribute contains empty value.", advisorElement, this.parseState.snapshot());
}
else {
// 5. 为bean定义设置adviceBeanName属性,值就是advice-ref属性的值封装的一个RuntimeBeanNameReference,将会在运行时解析
advisorDefinition.getPropertyValues().add(
ADVICE_BEAN_NAME, new RuntimeBeanNameReference(adviceRef));
}
// 6. 如果设置了order属性,那么为bean定义设置order属性,值就是order属性的值
if (advisorElement.hasAttribute(ORDER_PROPERTY)) {
advisorDefinition.getPropertyValues().add(
ORDER_PROPERTY, advisorElement.getAttribute(ORDER_PROPERTY));
}
// 返回bean定义
return advisorDefinition;
}
parsePointcutProperty()
在 parseAdvisor()
代码块 4
中。
该方法用于解析当前 <aop:advisor />
标签的 pointcut
或者 pointcut-ref
属性,返回的切入点可能是一个切入点bean定义或者一个切入点bean定义的id字符串。
pointcut
或者 pointcut-ref
属性只能且必须设置其中一个:
- 如果设置了
pointcut
属性,那么解析为一个切入点bean定义对象并返回。 - 如果设置了
pointcut-ref
属性,那么直接返回pointcut-ref
属性的值,其他情况抛出异常。
这里的切入点bean定义作为内部bean,不会被注册到容器中。
private static final String POINTCUT = "pointcut";
private static final String POINTCUT_REF = "pointcut-ref";
private Object parsePointcutProperty(Element element, ParserContext parserContext) {
// 1. 如果当前<aop:advisor/>标签同时具有pointcut和pointcut-ref属性,则抛出异常
if (element.hasAttribute(POINTCUT) && element.hasAttribute(POINTCUT_REF)) {
parserContext.getReaderContext().error(
"Cannot define both 'pointcut' and 'pointcut-ref' on <advisor> tag.",
element, this.parseState.snapshot());
return null;
}
// 2. 否则,如果具有pointcut属性,那么一定没有pointcut-ref属性
else if (element.hasAttribute(POINTCUT)) {
// 2.1 获取pointcut属性的值,也就是切入点表达式字符串
String expression = element.getAttribute(POINTCUT);
// 2.2 创建切入点bean定义对象,这个方法在解析<aop:pointcut/>标签的时候就分析过
AbstractBeanDefinition pointcutDefinition = createPointcutDefinition(expression);
// 2.3 设置源,属于当前<aop:advisor/>标签元素
pointcutDefinition.setSource(parserContext.extractSource(element));
//返回新建的切入点bean定义对象
return pointcutDefinition;
}
// 3. 如果具有pointcut-ref属性,那么一定没有pointcut属性
else if (element.hasAttribute(POINTCUT_REF)) {
// 3.1 获取pointcut-ref属性的值,也就是其他地方的<aop:pointcut/> 标签的id,表示引入其他外部切入点
String pointcutRef = element.getAttribute(POINTCUT_REF);
// 3.2 如果是空白字符之类的无意义字符串,那么抛出异常
if (!StringUtils.hasText(pointcutRef)) {
parserContext.getReaderContext().error(
"'pointcut-ref' attribute contains empty value.", element, this.parseState.snapshot());
return null;
}
// 直接返回pointcut-ref属性的值
return pointcutRef;
}
// 4. 否则,表示没有设置这两个属性的任何一个,同样抛出异常
else {
parserContext.getReaderContext().error(
"Must define one of 'pointcut' or 'pointcut-ref' on <advisor> tag.",
element, this.parseState.snapshot());
return null;
}
}
parseAspect()
在 ConfigBeanDefinitionParser#parse()
代码块的 7
中。
<aop:aspect />
标签用于配置切面,其内部可以定义具体的应用到哪些切入点的通知,这是一个非常重要的标签,解析的源码也很多。
梳理一下 parseAspect()
方法的大致逻辑:
- 解析所有
<aop:declare-parents />
引介增强子标签,主要方法是parseDeclareParents()
。bean定义的beanClass类型为DeclareParentsAdvisor
,会注册到缓存中。 - 解析所有advice通知子标签,包括
<aop:before />
、<aop:after />
、<aop:after-returning />
、<aop:after-throwing />
、<aop:around />
,并且设置通知顺序,主要方法是parseAdvice()
。最终bean定义的beanClass类型为AspectJPointcutAdvisor
,会注册到缓存中,它内部还有一些内部bean,不会被注册。 - 解析所有
<aop:pointcut />
切入点子标签,主要方法是parsePointcut()
这个方法我们前面就讲过了。bean定义的beanClass类型为AspectJExpressionPointcut
,会注册到缓存中。
可以看到,这个
<aop:aspect />
标签本身并不会被注册成为一个bean定义,而是对于它内部的子标签分别注册成为bean定义。
private static final String ID = "id";
private static final String REF = "ref";
private static final String POINTCUT = "pointcut";
private static final String DECLARE_PARENTS = "declare-parents";
private static final int METHOD_INDEX = 0;
private void parseAspect(Element aspectElement, ParserContext parserContext) {
//获取id属性的值,表示切面的id
String aspectId = aspectElement.getAttribute(ID);
//获取ref属性的值,表示引用一个通知类bean定义,内部具有的通知方法可以通知方法名直接被通知标签引用
String aspectName = aspectElement.getAttribute(REF);
try {
// 新建一个AspectEntry点位,压入存入parseState栈
this.parseState.push(new AspectEntry(aspectId, aspectName));
//<aop:aspect/> 标签解析到的bean定义集合
List<BeanDefinition> beanDefinitions = new ArrayList<>();
//<aop:aspect/> 标签解析到的bean定义引用集合
List<BeanReference> beanReferences = new ArrayList<>();
// 1. 解析所有<aop:declare-parents />引介增强子标签
// 1.1 获取全部<aop:declare-parents />子标签元素集合,也就是引介增强标签
List<Element> declareParents = DomUtils.getChildElementsByTagName(aspectElement, DECLARE_PARENTS);
// 1.2 从0遍历declareParents集合
for (int i = METHOD_INDEX; i < declareParents.size(); i++) {
// 1.2.1 获取每一个<aop:declare-parents/>子标签元素
Element declareParentsElement = declareParents.get(i);
/* 1.2.2 通过parseDeclareParents解析<aop:declare-parents/>子标签元素: 新建RootBeanDefinition类型的bean定义,beanClass类型为DeclareParentsAdvisor 解析各种属性并赋值,default-impl和delegate-ref属性有且只能由其中一个 随后将新建的bean定义同样注册到缓存容器中,最后将返回的bean定义加入到beanDefinitions集合中 */
beanDefinitions.add(parseDeclareParents(declareParentsElement, parserContext));
}
/* 2. 解析所有advice通知标签 包括<aop:before/>、<aop:after/>、<aop:after-returning/>、<aop:after-throwing/>、<aop:around/> 并且设置通知顺序 */
/* 2.1 获取所有子节点元素 对于标签之间的空白换行符号/n也会算作一个Node节点 -> DeferredTextImpl 对于标签之间被注释的语句也会算作一个Node节点 -> DeferredCommentImpl */
NodeList nodeList = aspectElement.getChildNodes();
// 标志位,判断有没有发现任何通知标签,默认false
boolean adviceFoundAlready = false;
// 2.2 遍历所有子节点元素
for (int i = 0; i < nodeList.getLength(); i++) {
// 2.2.1 获取每一个节点
Node node = nodeList.item(i);
// 2.2.2 如果是任何一个通知标签节点元素,那么就是true
if (isAdviceNode(node, parserContext)) {
// 如果此前还没有解析到任何一个通知节点
if (!adviceFoundAlready) {
// adviceFoundAlready改为true
adviceFoundAlready = true;
// 2.2.3 如果<aop:aspect/>标签的ref属性的没有设置值或者是空白字符等无效值,则抛出异常
if (!StringUtils.hasText(aspectName)) {
parserContext.getReaderContext().error(
"<aspect> tag needs aspect bean reference via 'ref' attribute when declaring advices.",
aspectElement, this.parseState.snapshot());
return;
}
// 2.2.4 如果设置了ref属性值,那么包装成为一个RuntimeBeanReference,加入到beanReferences集合中
beanReferences.add(new RuntimeBeanReference(aspectName));
}
/* 2.2.5 解析该通知标签 获取生成的通知bean定义,该bean定义已被注册到容器中,beanClass类型为AspectJPointcutAdvisor */
AbstractBeanDefinition advisorDefinition = parseAdvice(
aspectName, i, aspectElement, (Element) node, parserContext, beanDefinitions, beanReferences);
// 2.2.6 加入到beanDefinitions集合中
beanDefinitions.add(advisorDefinition);
}
}
// 3. 创建解析当前<aop:aspect/>标签的AspectComponentDefinition类型的bean定义,内部包含了解析出来的全部bean定义和bean引用
AspectComponentDefinition aspectComponentDefinition = createAspectComponentDefinition(
aspectElement, aspectId, beanDefinitions, beanReferences, parserContext);
// 存入解析上下文内部的containingComponents集合中,入栈顶
parserContext.pushContainingComponent(aspectComponentDefinition);
// 4. 解析所有<aop:pointcut/>切入点子标签
// 4.1 获取全部<aop:pointcut/>子标签元素集合
List<Element> pointcuts = DomUtils.getChildElementsByTagName(aspectElement, POINTCUT);
for (Element pointcutElement : pointcuts) {
/* 调用parsePointcut方法解析 <aop:pointcut/> 标签,该方法前面解析过 */
parsePointcut(pointcutElement, parserContext);
}
//出栈并注册,并不是注册到缓存中,可能什么也不做,同之前
parserContext.popAndRegisterContainingComponent();
}
finally {
// 点位出栈
this.parseState.pop();
}
}
parseDeclareParents()
在 parseAspect()
代码块的 1.2.2
中。
该方法用于解析 <aop:declare-parents />
子标签元素,解析的bean定义同样会注册到缓存中。
梳理一下 parseDeclareParents()
方法的大致逻辑:新建 RootBeanDefinition
类型的bean定义,beanClass类型为 DeclareParentsAdvisor
,解析各种属性并赋值,其中 default-impl
和 delegate-ref
属性有且只能由其中一个。如果 <aop:declare-parents />
标签没有id或者name属性,直接采用 DefaultBeanNameGenerator
作为beanName生成器来生成beanName,生成的beanName类似于 “org.springframework.aop.aspectj.DeclareParentsAdvisor#0”
。
< aop:declare-parents/>引介增强标签,可以在不修改源代码的前提下,在运行期为类动态地添加一些额外的方法或属性,又被称为Introduction(引介)。
private static final String IMPLEMENT_INTERFACE = "implement-interface";
private static final String TYPE_PATTERN = "types-matching";
private static final String DEFAULT_IMPL = "default-impl";
private static final String DELEGATE_REF = "delegate-ref";
private AbstractBeanDefinition parseDeclareParents(Element declareParentsElement, ParserContext parserContext) {
// 1. 新建RootBeanDefinition类型的bean定义,beanClass类型为DeclareParentsAdvisor
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(DeclareParentsAdvisor.class);
// 获取implement-interface和types-matching属性的值,并设置具有索引的bean定义构造器集合的前两位
builder.addConstructorArgValue(declareParentsElement.getAttribute(IMPLEMENT_INTERFACE));
builder.addConstructorArgValue(declareParentsElement.getAttribute(TYPE_PATTERN));
// 获取default-impl和delegate-ref属性的值,也就是增强类
String defaultImpl = declareParentsElement.getAttribute(DEFAULT_IMPL);
String delegateRef = declareParentsElement.getAttribute(DELEGATE_REF);
// 2.1 如果设置了default-impl并且没有设置delegate-ref
if (StringUtils.hasText(defaultImpl) && !StringUtils.hasText(delegateRef)) {
// 那么将该属性的值加入具有索引的bean定义构造器集合的第三位
builder.addConstructorArgValue(defaultImpl);
}
// 2.2 如果设置了default-impl并且没有设置delegate-ref
else if (StringUtils.hasText(delegateRef) && !StringUtils.hasText(defaultImpl)) {
// 那么将该属性的值封装成一个RuntimeBeanReference对象,加入具有索引的bean定义构造器集合的第三位
builder.addConstructorArgReference(delegateRef);
}
// 2.3 如果同时设置或者没设置这两个属性,则抛出异常
else {
parserContext.getReaderContext().error(
"Exactly one of the " + DEFAULT_IMPL + " or " + DELEGATE_REF + " attributes must be specified",
declareParentsElement, this.parseState.snapshot());
}
// 获取bean定义
AbstractBeanDefinition definition = builder.getBeanDefinition();
// 设置源
definition.setSource(parserContext.extractSource(declareParentsElement));
// <aop:declare-parents/>标签没有id或者name属性,通过DefaultBeanNameGenerator生成beanName
// 随后同样注册到registry的缓存中,返回生成的beanName
parserContext.getReaderContext().registerWithGeneratedName(definition);
// 返回bean定义
return definition;
}
isAdviceNode()
在 parseAspect()
代码块的 2.2.2
中。
该方法用于判断是不是通知标签节点,如果是任何一个通知标签节点元素,那么就返回true,否则返回false。
通知标签是指 <aop:before />
、<aop:after />
、<aop:after-returning />
、<aop:after-throwing />
、<aop:around />
这五个标签中的任何一个。
private static final String BEFORE = "before";
private static final String AFTER = "after";
private static final String AFTER_RETURNING_ELEMENT = "after-returning";
private static final String AFTER_THROWING_ELEMENT = "after-throwing";
private static final String AROUND = "around";
private boolean isAdviceNode(Node aNode, ParserContext parserContext) {
if (!(aNode instanceof Element)) {
return false;
}
else {
String name = parserContext.getDelegate().getLocalName(aNode);
return (BEFORE.equals(name) || AFTER.equals(name) || AFTER_RETURNING_ELEMENT.equals(name) ||
AFTER_THROWING_ELEMENT.equals(name) || AROUND.equals(name));
}
}
parseAdvice()【重点】
在 parseAspect()
代码块的 2.2.5
中。
该方法用于解析所有advice通知标签,包括<aop:before />
、<aop:after />
、<aop:after-returning />
、<aop:after-throwing />
、<aop:around />
,解析的最终bean定义同样会注入到容器中。
梳理一下 parseAdvice()
方法的大致逻辑:
- 首先创建
RootBeanDefinition
类型的通知方法bean定义,beanClass类型为MethodLocatingFactoryBean
,它实现了FactoryBean
接口,是一个方法工厂,专门用于获取通知对应的Method对象。在第三步会被用于构造通知bean定义。- 设置bean定义的
targetBeanName
属性,值就是外部<aop:aspect />
标签的ref属性值,也就是引用的通知类bean定义的id。 - 设置bean定义的
methodName
属性,值就是method属性值。 - 设置bean定义的
synthetic
,值为true,这表示它是一个合成的而不是不是由程序本身定义的bean。 - 这个bean定义不会被注册到容器中,也就是内部bean。
- 设置bean定义的
- 创建
RootBeanDefinition
类型的切面实例类bean定义,beanClass类型为SimpleBeanFactoryAwareAspectInstanceFactory
,它实现了AspectInstanceFactory
接口,是一个实例工厂,专门用于获取切面实例对象,也就是通知类对象。在第三步会被用于构造通知bean定义。- 设置bean定义的
aspectBeanName
属性,值就是外部<aop:aspect />
标签的ref属性值,也就是引用的通知类bean定义的id。 - 设置bean定义的
synthetic
,值为true,这表示它是一个合成的而不是不是由程序本身定义的bean。 - 这个bean定义不会被注册到容器中,也就是内部bean。
- 设置bean定义的
- 调用
createAdviceDefinition()
方法,根据前两个bean定义构建advice通知bean定义名为"adviceDef"
,并解析通知标签的各种属性。通知bean定义也不会被注册到容器中,也就是内部bean。 - 创建
RootBeanDefinition
类型的切入点通知器bean定义名为"advisorDefinition"
,beanClass类型为AspectJPointcutAdvisor
,这个bean定义才是该通知标签最终的bean定义。- 设置bean定义的构造器参数,值就是上面创建的advice通知bean定义名为
"adviceDef"
。 - 如果外部
<aop:aspect />
标签元素具有order属性,那么设置bean定义的order属性值,这用来控制切入点方法的执行优先级。
- 设置bean定义的构造器参数,值就是上面创建的advice通知bean定义名为
- 向容器注册最终的切入点通知器bean定义名为
"advisorDefinition"
,通过DefaultBeanNameGenerator
生成beanName。最后返回advisorDefinition
。
private static final String ORDER_PROPERTY = "order";
private AbstractBeanDefinition parseAdvice(
String aspectName, // 外部<aop:aspect/>标签的ref属性值,也就是引用的通知类bean定义的id
int order, // 顺序,实际上就是在当前外部<aop:aspect/>标签中的所有节点的定义顺序由上而下的索引值
Element aspectElement, // 外部<aop:aspect />标签元素
Element adviceElement, // advice通知标签元素
ParserContext parserContext, // 解析上下文
List<BeanDefinition> beanDefinitions, // beanDefinitions集合
List<BeanReference> beanReferences // beanReferences集合
) {
try {
// 新建一个AdviceEntry点位,压入parseState栈
this.parseState.push(new AdviceEntry(parserContext.getDelegate().getLocalName(adviceElement)));
// 1. 创建通知方法bean定义,用于获取通知对应的Method对象,在第三步会被用于构造通知bean定义
// 新建RootBeanDefinition类型的bean定义,beanClass类型为MethodLocatingFactoryBean
// MethodLocatingFactoryBean实现了FactoryBean接口,是一个方法工厂,专门用于获取通知对应的Method对象
RootBeanDefinition methodDefinition = new RootBeanDefinition(MethodLocatingFactoryBean.class);
// 设置bean定义的targetBeanName属性
methodDefinition.getPropertyValues().add("targetBeanName", aspectName);
// 设置bean定义的methodName属性
methodDefinition.getPropertyValues().add("methodName", adviceElement.getAttribute("method"));
// 设置bean定义的synthetic为true
methodDefinition.setSynthetic(true);
// 2. 创建切面实例类bean定义,用于获取切面实例对象,也就是通知类对象,在第三步会被用于构造通知bean定义
//新建RootBeanDefinition类型的bean定义,beanClass类型为SimpleBeanFactoryAwareAspectInstanceFactory
//SimpleBeanFactoryAwareAspectInstanceFactory实现了AspectInstanceFactory接口,是一个实例工厂,专门用于获取切面实例对象,也就是通知类对象
RootBeanDefinition aspectFactoryDef =
new RootBeanDefinition(SimpleBeanFactoryAwareAspectInstanceFactory.class);
// 设置bean定义的aspectBeanName属性
aspectFactoryDef.getPropertyValues().add("aspectBeanName", aspectName);
// 设置bean定义的synthetic为true
aspectFactoryDef.setSynthetic(true);
// 3. 创建advice通知bean定义
AbstractBeanDefinition adviceDef = createAdviceDefinition(
adviceElement,
parserContext,
aspectName,
order,
methodDefinition,
aspectFactoryDef,
beanDefinitions,
beanReferences);
// 4. 创建切入点通知器bean定义
// 新建RootBeanDefinition类型的bean定义,beanClass类型为AspectJPointcutAdvisor
RootBeanDefinition advisorDefinition = new RootBeanDefinition(AspectJPointcutAdvisor.class);
// 设置源
advisorDefinition.setSource(parserContext.extractSource(adviceElement));
// 设置bean定义的构造器参数,值就是上面创建的advice通知bean定义adviceDef
advisorDefinition.getConstructorArgumentValues().addGenericArgumentValue(adviceDef);
// 如果外部<aop:aspect/>标签元素具有order属性
if (aspectElement.hasAttribute(ORDER_PROPERTY)) {
//设置bean定义的order属性,值就是外部<aop:aspect/>标签元素的order属性值
//用来控制切入点方法的优先级
advisorDefinition.getPropertyValues().add(
ORDER_PROPERTY, aspectElement.getAttribute(ORDER_PROPERTY));
}
// 5. 注册通知器bean定义
// 通过DefaultBeanNameGenerator生成beanName,随后将最终得到的切入点通知器bean定义同样注册到registry的缓存中
parserContext.getReaderContext().registerWithGeneratedName(advisorDefinition);
// 返回通知器bean定义
return advisorDefinition;
}
finally {
// 点位出栈
this.parseState.pop();
}
}
createAdviceDefinition()
在 parseAdvice()
代码块的 3
中。
该方法解析当前通知标签的各种属性,并且将前两个bean定义作为构造器参数构建通知bean定义。
梳理一下 createAdviceDefinition()
方法的大致逻辑:
- 新建
RootBeanDefinition
类型的bean定义名为"adviceDefinition
,beanClass类型为该通知标签对应的实现类类型。- 设置bean定义的
aspectName
属性,值就是外部<aop:aspect />
标签的ref属性值,也就是引用的通知类bean定义的id。 - 设置bean定义的
declarationOrder
属性,也就是“声明顺序”,值就是当前的通知的标签在外部<aop:aspect />
标签中的所有节点的定义顺序由上而下的索引值,后面用于拦截器排序。
- 设置bean定义的
- 如果具有
returning
属性,说明是后置通知。设置bean定义的returningName
属性,值就是returning
属性的值。 - 如果具有
throwing
属性,说明是异常通知。设置bean定义的throwingName
属性,值就是throwing
属性的值。 - 如果具有
arg-names
属性,这表示接收目标方法的参数。设置bean定义的argumentNames
属性,值就是arg-names
属性的值。 - 为
adviceDefinition
设置构造器第一个参数属性,值为methodDef
,也就是此前构建的通知方法bean定义。 - 调用
parsePointcutProperty
方法,解析当前通知标签的pointcut
或者pointcut-ref
属性,获取切入点,返回值可能是一个切入点bean定义或者一个切入点bean定义的id,这个方法在之前做过分析。这个的切入点bean定义作为内部bean,不会被注册到容器中。 - 为
adviceDefinition
设置构造器第二个参数属性,值就是上一步解析返回的切入点bean定义,或者一个切入点bean定义的id封装的一个RuntimeBeanReference
。 - 为
adviceDefinition
设置构造器第三个参数属性,值就是切面通知类bean定义,也就是此前构建的切面通知类bean定义。 - 返回构建好的通知bean定义
adviceDefinition
。
private static final String ASPECT_NAME_PROPERTY = "aspectName";
private static final String DECLARATION_ORDER_PROPERTY = "declarationOrder";
private static final String RETURNING = "returning";
private static final String RETURNING_PROPERTY = "returningName";
private static final String THROWING = "throwing";
private static final String THROWING_PROPERTY = "throwingName";
private static final String ARG_NAMES = "arg-names";
private static final String ARG_NAMES_PROPERTY = "argumentNames";
private static final int METHOD_INDEX = 0;
private static final int POINTCUT_INDEX = 1;
private static final int ASPECT_INSTANCE_FACTORY_INDEX = 2;
private AbstractBeanDefinition createAdviceDefinition(
Element adviceElement, // advice通知标签元素
ParserContext parserContext,
String aspectName,
int order,
RootBeanDefinition methodDef, // 通知方法bean定义
RootBeanDefinition aspectFactoryDef, // 切面通知类bean定义
List<BeanDefinition> beanDefinitions,
List<BeanReference> beanReferences
) {
// 1. 新建RootBeanDefinition类型的bean定义,beanClass类型为该通知标签对应的实现类类型
RootBeanDefinition adviceDefinition = new RootBeanDefinition(getAdviceClass(adviceElement, parserContext));
// 设置源
adviceDefinition.setSource(parserContext.extractSource(adviceElement));
// 1.1 设置bean定义的aspectName属性
adviceDefinition.getPropertyValues().add(ASPECT_NAME_PROPERTY, aspectName);
// 1.2 设置bean定义的declarationOrder属性
adviceDefinition.getPropertyValues().add(DECLARATION_ORDER_PROPERTY, order);
// 2.1 如果具有returning属性,说明是后置通知
if (adviceElement.hasAttribute(RETURNING)) {
// 设置bean定义的returningName属性,值就是returning属性的值
adviceDefinition.getPropertyValues().add(
RETURNING_PROPERTY, adviceElement.getAttribute(RETURNING));
}
// 2.2 如果具有throwing属性,说明是异常通知
if (adviceElement.hasAttribute(THROWING)) {
// 设置bean定义的throwingName属性,值就是throwing属性的值
adviceDefinition.getPropertyValues().add(
THROWING_PROPERTY, adviceElement.getAttribute(THROWING));
}
// 2.3 如果具有arg-names属性,这表示接收目标方法的参数
if (adviceElement.hasAttribute(ARG_NAMES)) {
// 设置bean定义的argumentNames属性,值就是arg-names属性的值
adviceDefinition.getPropertyValues().add(
ARG_NAMES_PROPERTY, adviceElement.getAttribute(ARG_NAMES));
}
// 获取构造器参数
ConstructorArgumentValues cav = adviceDefinition.getConstructorArgumentValues();
// 设置构造器第一个参数属性,值为methodDef,也就是此前构建的通知方法bean定义
cav.addIndexedArgumentValue(METHOD_INDEX, methodDef);
// 解析当前通知标签的pointcut或者pointcut-ref属性,获取切入点,返回值可能是一个切入点bean定义或者一个切入点bean定义的id
Object pointcut = parsePointcutProperty(adviceElement, parserContext);
// 如果是一个切入点bean定义,那么表示设置了pointcut属性,返回的就是根据切入点表达式创建的一个切入点bean定义
if (pointcut instanceof BeanDefinition) {
// 为bean定义设置构造器第二个参数属性,值就是解析后的切入点bean定义
cav.addIndexedArgumentValue(POINTCUT_INDEX, pointcut);
// 加入beanDefinitions集合
beanDefinitions.add((BeanDefinition) pointcut);
}
// 如果是一个字符串,那么表示设置了pointcut-ref属性,返回的就是该属性的值,表示引入的其他切入点bean定义的id
else if (pointcut instanceof String) {
// 为bean定义设置构造器第二个参数属性,值就是pointcut-ref属性的值封装的一个RuntimeBeanReference,将会在运行时解析
RuntimeBeanReference pointcutRef = new RuntimeBeanReference((String) pointcut);
cav.addIndexedArgumentValue(POINTCUT_INDEX, pointcutRef);
// 加入beanReferences集合
beanReferences.add(pointcutRef);
}
// 为bean定义设置构造器第三个参数属性,值就是切面通知类bean定义,也就是此前构建的切面通知类bean定义
cav.addIndexedArgumentValue(ASPECT_INSTANCE_FACTORY_INDEX, aspectFactoryDef);
// 返回bean定义
return adviceDefinition;
}
getAdviceClass()
在 createAdviceDefinition()
代码块的 1
中。
该方法用于获取给定通知标签对应的通知类的beanClass的类型。
- 如果是
<aop:before />
通知,那么返回AspectJMethodBeforeAdvice.class
。 - 如果是
<aop:after />
通知,那么返回AspectJAfterAdvice.class
。 - 如果是
<aop:after-returning />
通知,那么返回AspectJAfterReturningAdvice.class
。 - 如果是
<aop:after-throwing />
通知,那么返回AspectJAfterThrowingAdvice.class
。 - 如果是
<aop:around />
通知,那么返回AspectJAroundAdvice.class
。
private static final String BEFORE = "before";
private static final String AFTER = "after";
private static final String AFTER_RETURNING_ELEMENT = "after-returning";
private static final String AFTER_THROWING_ELEMENT = "after-throwing";
private static final String AROUND = "around";
private Class<?> getAdviceClass(Element adviceElement, ParserContext parserContext) {
// 获取该通知标签的本地名称
String elementName = parserContext.getDelegate().getLocalName(adviceElement);
// 根据不同的通知类型返回不同的Class
// 如果是<aop:before/>通知,那么返回AspectJMethodBeforeAdvice.class
if (BEFORE.equals(elementName)) {
return AspectJMethodBeforeAdvice.class;
}
// 如果是<aop:after/>通知,那么返回AspectJAfterAdvice.class
else if (AFTER.equals(elementName)) {
return AspectJAfterAdvice.class;
}
// 如果是<aop:after-returning/>通知,那么返回AspectJAfterReturningAdvice.class
else if (AFTER_RETURNING_ELEMENT.equals(elementName)) {
return AspectJAfterReturningAdvice.class;
}
// 如果是<aop:after-throwing/>通知,那么返回AspectJAfterThrowingAdvice.class
else if (AFTER_THROWING_ELEMENT.equals(elementName)) {
return AspectJAfterThrowingAdvice.class;
}
// 如果是<aop:around/>通知,那么返回AspectJAroundAdvice.class
else if (AROUND.equals(elementName)) {
return AspectJAroundAdvice.class;
}
// 其他情况,抛出异常
else {
throw new IllegalArgumentException("Unknown advice kind [" + elementName + "].");
}
}
至此,采用XML配置Spring AOP的方式就结束了,该方式需要配置< aop:config/>标签就行了,当前全部标签解析完毕,仅仅是向容器中注册了一些bean定义,还没有进行真正的创建代理对象操作,创建代理对象的操作实际上是在自动代理创建者
AspectJAwareAdvisorAutoProxyCreator
内部完成的。