Spring源码情操陶冶-AOP之ConfigBeanDefinitionParser解析器
- aop-Aspect Oriented Programming,面向切面编程。根据百度百科的解释,其通过预编译方式和运行期动态代理实现程序功能的一种技术。主要目的是为了程序间的解耦,常用于日志记录、事务管理等方面。
- spring中常用
<aop-config>来配置aop代理
AOP概念梳理
- 切面(Aspect)
- 连接点(Joinpoint)
- 通知(Advice)
- 切入点(PointCut)
- 目标对象
- AOP代理
具体的AOP解释以及用法可详情参考专业人士写的博客>>>Spring Aop详尽教程。总的来说AOP是基于java设计模式中的代理模式来实现的。
AOP简单例子
配置一发与springboot结合的代码例子,简洁又易懂
package com.jing.springboot.aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
/**
* 请求拦截器,使用aop来处理
*
* @author jtj
*
*/
@Aspect
@Component
public class RequestAopInterceptor {
private static final Logger REQ_LOGGER = LoggerFactory.getLogger(RequestAopInterceptor.class);
// 切入点,代表其关注哪些方法行为
// 匹配语法为:注解 修饰符 返回值类型 类名 方法名(参数列表) 异常列表 具体可查看
// http://blog.csdn.net/wangpeng047/article/details/8556800
@Pointcut("execution(* com.jing.springboot.controller..*(..)) and @annotation(org.springframework.web.bind.annotation.RequestMapping)")
public void interceptor() {
}
// 指定声明的pointcut,前置通知
@Before("interceptor()")
public void before() {
REQ_LOGGER.info("before the requestMapping");
}
// 后置通知
@After("interceptor()")
public void after() {
REQ_LOGGER.info("requestMapping is over");
}
// 返回通知
@AfterReturning(pointcut = "interceptor()", returning = "result")
public void doAfterReturing(String result) {
REQ_LOGGER.info("doAfterReturing-->请求返回的信息为: " + result);
}
// 环绕通知,此处的joinPoint代表了连接点
@Around("interceptor()")
public Object doRound(ProceedingJoinPoint joinPoint) {
Object result = null;
REQ_LOGGER.info("doRound before");
try {
result = joinPoint.proceed();
} catch (Throwable e) {
e.printStackTrace();
}
REQ_LOGGER.info("doRound after");
return result;
}
}
上述是拦截controller层的所有方法,此处博主举例HelloWorldCtrl.class的某个方法以作测试
@RequestMapping(value = "/", method = RequestMethod.GET)
public String hello() {
return "hello world";
}
最后请求得到的控制台打印结果为如下
2017-10-18 20:01:31.821 INFO 21445 --- [nio-9901-exec-1] c.j.s.aop.RequestAopInterceptor : doRound before
2017-10-18 20:01:31.822 INFO 21445 --- [nio-9901-exec-1] c.j.s.aop.RequestAopInterceptor : before the requestMapping
2017-10-18 20:01:31.831 INFO 21445 --- [nio-9901-exec-1] c.j.s.aop.RequestAopInterceptor : doRound after
2017-10-18 20:01:31.832 INFO 21445 --- [nio-9901-exec-1] c.j.s.aop.RequestAopInterceptor : requestMapping is over
2017-10-18 20:01:31.832 INFO 21445 --- [nio-9901-exec-1] c.j.s.aop.RequestAopInterceptor : doAfterReturing-->请求返回的信息为: hello world
由上得出规律:
- 执行顺序为JointPoint之前-->前置通知-->JointPoint-->JoinPoint之后-->后置通知-->后置返回通知
- 通知依赖于切入点,即Advice依赖于PointCut。
PointCut是切面必须指定的
ConfigBeanDefinitionParser-切面aop在Spring的解析器
我们直接看下其主要的parse()方法源码
@Override
public BeanDefinition parse(Element element, ParserContext parserContext) {
CompositeComponentDefinition compositeDef =
new CompositeComponentDefinition(element.getTagName(), parserContext.extractSource(element));
parserContext.pushContainingComponent(compositeDef);
// 注册自动代理模式创建器,其作用于<aop:config>
configureAutoProxyCreator(parserContext, element);
// 解析其aop:config子节点下的aop:pointcut/aop:advisor/aop:aspect
List<Element> childElts = DomUtils.getChildElements(element);
for (Element elt: childElts) {
String localName = parserContext.getDelegate().getLocalName(elt);
if (POINTCUT.equals(localName)) {
parsePointcut(elt, parserContext);
}
else if (ADVISOR.equals(localName)) {
parseAdvisor(elt, parserContext);
}
else if (ASPECT.equals(localName)) {
parseAspect(elt, parserContext);
}
}
parserContext.popAndRegisterContainingComponent();
return null;
}
由以上源码可以得知aop:config节点的解析器主要做的工作如下:
- 注册自动代理模式创建器
- 解析子节点
aop:pointcut/aop:advisor/aop:aspect
那我们接下来按上述步骤解读下
ConfigBeanDefinitionParser#configureAutoProxyCreator()-注册自动代理创建器
直接上源码
/**
* Configures the auto proxy creator needed to support the {@link BeanDefinition BeanDefinitions}
* created by the '{@code <aop:config/>}' tag. Will force class proxying if the
* '{@code proxy-target-class}' attribute is set to '{@code true}'.
* @see AopNamespaceUtils
*/
private void configureAutoProxyCreator(ParserContext parserContext, Element element) {
AopNamespaceUtils.registerAspectJAutoProxyCreatorIfNecessary(parserContext, element);
}
直接通过AopNamespaceUtils工具类完成自动代理注册工作,并且提到aop:config如果指定proxy-target-class为true,则将采用类代理。有点稀里糊涂的,我们还是继续看AopNamespaceUtils#registerAspectJAutoProxyCreatorIfNecessary()的方法源码
public static void registerAspectJAutoProxyCreatorIfNecessary(
ParserContext parserContext, Element sourceElement) {
// 注册名为org.springframework.aop.config.internalAutoProxyCreator的beanDefinition,其中的class类为`AspectJAwareAdvisorAutoProxyCreator`,其也会被注册到bean工厂中
BeanDefinition beanDefinition = AopConfigUtils.registerAspectJAutoProxyCreatorIfNecessary(
parserContext.getRegistry(), parserContext.extractSource(sourceElement));
// 如果指定proxy-target-class=true,则使用CGLIB代理,否则使用JDK代理
// 其实其为AspectJAwareAdvisorAutoProxyCreator类的proxyTargetClass属性
useClassProxyingIfNecessary(parserContext.getRegistry(), sourceElement);
// 注册到spring的bean工厂中,再次校验是否已注册
registerComponentIfNecessary(beanDefinition, parserContext);
}
此处最需要关注的是
AspectJAwareAdvisorAutoProxyCreator代理创建类,其会对Advisor类做如何的代理操作,详情可参阅>>>SpringAop源码情操陶冶-AspectJAwareAdvisorAutoProxyCreator
aop:config的属性proxy-target-class含义
- true。表示针对目标对象为接口类,则采取JDK代理;对于其他的类,则采取CGLIB代理
- false。表示进行JDK代理
ConfigBeanDefinitionParser#parsePointcut()-解析切入点
我们先简单的看下源码
/**
* Parses the supplied {@code <pointcut>} and registers the resulting
* Pointcut with the BeanDefinitionRegistry.
*/
private AbstractBeanDefinition parsePointcut(Element pointcutElement, ParserContext parserContext) {
// 切入点的唯一标识
String id = pointcutElement.getAttribute(ID);
// 获取切入点的表达式
String expression = pointcutElement.getAttribute(EXPRESSION);
AbstractBeanDefinition pointcutDefinition = null;
try {
// 采用栈保存切入点
this.parseState.push(new PointcutEntry(id));
// 创建切入点bean对象
// beanClass为AspectJExpressionPointcut.class。并且设置属性expression到该beanClass中
pointcutDefinition = createPointcutDefinition(expression);
pointcutDefinition.setSource(parserContext.extractSource(pointcutElement));
String pointcutBeanName = id;
if (StringUtils.hasText(pointcutBeanName)) {
// 注册bean对象
parserContext.getRegistry().registerBeanDefinition(pointcutBeanName, pointcutDefinition);
}
else {
pointcutBeanName = parserContext.getReaderContext().registerWithGeneratedName(pointcutDefinition);
}
parserContext.registerComponent(
new PointcutComponentDefinition(pointcutBeanName, pointcutDefinition, expression));
}
finally {
// 创建后移除
this.parseState.pop();
}
return pointcutDefinition;
}
aop:point-cut对应的beanClass为AspectJExpressionPointcut。内部也含有expression属性
ConfigBeanDefinitionParser#parseAspect()-解析切面
切面内可以有多个切入点(pointcut)和对应的多个通知(advice)。下面直接查看源码
private void parseAspect(Element aspectElement, ParserContext parserContext) {
// <aop:aspect> id属性
String aspectId = aspectElement.getAttribute(ID);
// aop ref属性,必须配置。代表切面
String aspectName = aspectElement.getAttribute(REF);
try {
this.parseState.push(new AspectEntry(aspectId, aspectName));
List<BeanDefinition> beanDefinitions = new ArrayList<BeanDefinition>();
List<BeanReference> beanReferences = new ArrayList<BeanReference>();
// 解析<aop:aspect>下的declare-parents节点
// 采用的是DeclareParentsAdvisor作为beanClass加载
List<Element> declareParents = DomUtils.getChildElementsByTagName(aspectElement, DECLARE_PARENTS);
for (int i = METHOD_INDEX; i < declareParents.size(); i++) {
Element declareParentsElement = declareParents.get(i);
beanDefinitions.add(parseDeclareParents(declareParentsElement, parserContext));
}
// We have to parse "advice" and all the advice kinds in one loop, to get the
// ordering semantics right.
// 解析其下的advice节点
NodeList nodeList = aspectElement.getChildNodes();
boolean adviceFoundAlready = false;
for (int i = 0; i < nodeList.getLength(); i++) {
Node node = nodeList.item(i);
// 是否为advice:before/advice:after/advice:after-returning/advice:after-throwing/advice:around节点
if (isAdviceNode(node, parserContext)) {
// 校验aop:aspect必须有ref属性,否则无法对切入点进行观察操作
if (!adviceFoundAlready) {
adviceFoundAlready = true;
if (!StringUtils.hasText(aspectName)) {
parserContext.getReaderContext().error(
"<aspect> tag needs aspect bean reference via 'ref' attribute when declaring advices.",
aspectElement, this.parseState.snapshot());
return;
}
beanReferences.add(new RuntimeBeanReference(aspectName));
}
// 解析advice节点并注册到bean工厂中
AbstractBeanDefinition advisorDefinition = parseAdvice(
aspectName, i, aspectElement, (Element) node, parserContext, beanDefinitions, beanReferences);
beanDefinitions.add(advisorDefinition);
}
}
AspectComponentDefinition aspectComponentDefinition = createAspectComponentDefinition(
aspectElement, aspectId, beanDefinitions, beanReferences, parserContext);
parserContext.pushContainingComponent(aspectComponentDefinition);
// 解析aop:point-cut节点并注册到bean工厂
List<Element> pointcuts = DomUtils.getChildElementsByTagName(aspectElement, POINTCUT);
for (Element pointcutElement : pointcuts) {
parsePointcut(pointcutElement, parserContext);
}
parserContext.popAndRegisterContainingComponent();
}
finally {
this.parseState.pop();
}
}
aop:aspect切面配置,必须有ref属性,其可以是普通的class类,可定义很多的方法,用于aop:before/aop:after等节点引用来绑定切入点
aop:aspect内部的节点aop:ponit-cut是必须配置的,而且其可以配置更多的切入点供aop:before/aop:after等节点选择使用对
aop:before/aop:after节点的解析可见>>>ConfigBeanDefinitionParser#parseAdvice()-解析Advice通知类对通知类的解析最终都会保存到
AspectJPointcutAdvisor.class,其内部属性包含Advice和Ponitcut信息。与下面的ConfigBeanDefinitionParser#parseAdvisor()是类似的
ConfigBeanDefinitionParser#parseAdvisor()-解析Advisor顾问类
Advisor的功能其实与Aspect的功能类似,相当于是特殊的Aspect。
用法如下
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="save*" propagation="REQUIRED"/>
<tx:method name="delete*" propagation="REQUIRED"/>
<tx:method name="get*" read-only="true"/>
<tx:method name="find*" read-only="true"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:advisor advice-ref="txAdvice" pointcut="execution(* com.XXX..*.*Dao.*(..))" />
<aop:advisor advice-ref="txAdvice" pointcut="execution(* com.XXX..*.*Service.*(..))" />
<aop:advisor advice-ref="txAdvice" pointcut="execution(* com.XXX..*.*Controller.*(..))" />
<aop:config>
接着我们直接看下其中的源码
private void parseAdvisor(Element advisorElement, ParserContext parserContext) {
// 解析<aop:advisor>节点,最终创建的beanClass为`DefaultBeanFactoryPointcutAdvisor`
// 另外advice-ref属性必须定义,其与内部属性adviceBeanName对应
AbstractBeanDefinition advisorDef = createAdvisorBeanDefinition(advisorElement, parserContext);
String id = advisorElement.getAttribute(ID);
try {
// 注册到bean工厂
this.parseState.push(new AdvisorEntry(id));
String advisorBeanName = id;
if (StringUtils.hasText(advisorBeanName)) {
parserContext.getRegistry().registerBeanDefinition(advisorBeanName, advisorDef);
}
else {
advisorBeanName = parserContext.getReaderContext().registerWithGeneratedName(advisorDef);
}
// 解析point-cut属性并赋值到DefaultBeanFactoryPointcutAdvisor#pointcut内部属性
Object pointcut = parsePointcutProperty(advisorElement, parserContext);
if (pointcut instanceof BeanDefinition) {
advisorDef.getPropertyValues().add(POINTCUT, pointcut);
parserContext.registerComponent(
new AdvisorComponentDefinition(advisorBeanName, advisorDef, (BeanDefinition) pointcut));
}
else if (pointcut instanceof String) {
advisorDef.getPropertyValues().add(POINTCUT, new RuntimeBeanReference((String) pointcut));
parserContext.registerComponent(
new AdvisorComponentDefinition(advisorBeanName, advisorDef));
}
}
finally {
this.parseState.pop();
}
}
可以看到
aop:advisor的解析最终包装为beanClass为DefaultBeanFactoryPointcutAdvisor的beanDefinition对象,其内部的属性advisorBeanName和pointcut是必须被赋值的,对应的节点信息为<aop:advisor advice-ref="" pointcut="">
aop:aspect和aop:advisor最终解析成的beanClass均为org.springframework.aop.PointcutAdvisor接口类的实现类。内部都包含两个属性类org.springframework.aop.Pointcut接口实现类和org.aopalliance.aop.Advice接口实现类
aop:advisor基本类似于aop:aspect,只是其没有后者分的那么细的通知即aop:before/aop:after等节点。
小结
1.aop相关节点解析后对应的beanClass作下汇总
aop:point-cut对应的beanClass为org.springframework.aop.aspectj.AspectJExpressionPointcutaop:before/aop:after等对应的beanClass为org.springframework.aop.aspectj.AbstractAspectJAdvice的子类aop:advisor对应的beanClass为org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisoraop:aspect对应的beanClass为org.springframework.aop.aspectj.AspectJPointcutAdvisor2.aop最终会对point-cut表达式提及的类以及方法进行代理操作,默认采取JDK代理,如果指定
proxy-target-class属性为true,则也会采用CGLIB代理
Spring源码情操陶冶-AOP之ConfigBeanDefinitionParser解析器的更多相关文章
- Spring源码情操陶冶-AnnotationConfigBeanDefinitionParser注解配置解析器
本文承接前文Spring源码情操陶冶-自定义节点的解析,分析spring中的context:annotation-config节点如何被解析 源码概览 对BeanDefinitionParser接口的 ...
- Spring源码情操陶冶-ComponentScanBeanDefinitionParser文件扫描解析器
承接前文Spring源码情操陶冶-自定义节点的解析,本文讲述spring通过context:component-scan节点干了什么事 ComponentScanBeanDefinitionParse ...
- Spring源码情操陶冶-PropertyPlaceholderBeanDefinitionParser注解配置解析器
本文针对spring配置的context:property-placeholder作下简单的分析,承接前文Spring源码情操陶冶-自定义节点的解析 spring配置文件应用 <context: ...
- Spring源码情操陶冶-AOP之Advice通知类解析与使用
阅读本文请先稍微浏览下上篇文章Spring源码情操陶冶-AOP之ConfigBeanDefinitionParser解析器,本文则对aop模式的通知类作简单的分析 入口 根据前文讲解,我们知道通知类的 ...
- Spring源码情操陶冶-自定义节点的解析
本文承接前文Spring源码情操陶冶-DefaultBeanDefinitionDocumentReader#parseBeanDefinitions,特开辟出一块新地来啃啃这块有意思的骨头 自定义节 ...
- SpringMVC源码情操陶冶-ResourcesBeanDefinitionParser静态资源解析器
解析mvc:resources节点,控制对静态资源的映射访问 查看官方注释 /** * {@link org.springframework.beans.factory.xml.BeanDefinit ...
- Spring源码情操陶冶-tx:advice解析器
承接Spring源码情操陶冶-自定义节点的解析.本节关于事务进行简单的解析 spring配置文件样例 简单的事务配置,对save/delete开头的方法加事务,get/find开头的设置为不加事务只读 ...
- Spring源码情操陶冶#task:executor解析器
承接Spring源码情操陶冶-自定义节点的解析.线程池是jdk的一个很重要的概念,在很多的场景都会应用到,多用于处理多任务的并发处理,此处借由spring整合jdk的cocurrent包的方式来进行深 ...
- Spring源码情操陶冶-AbstractApplicationContext#finishBeanFactoryInitialization
承接前文Spring源码情操陶冶-AbstractApplicationContext#registerListeners 约定web.xml配置的contextClass为默认值XmlWebAppl ...
随机推荐
- logback:logback和slf4j中的:appender、logger、encoder、layout
(1)appender 1.appender标签是logback配置文件中重要的组件之一.在logback配置文件中使用appender标签进行定义.可 以包含0个或多个appender标签. 2.a ...
- 《Java从入门到放弃》JavaSE入门篇:文件操作
Java中的文件操作还有点小复杂··· 不过没关系,我会把它讲得很简单,嘿嘿嘿!!! 在讲Java中的文件操作前,先了解一个概念--"流",比如我们把一个杯子的水倒到另一个同样大小 ...
- Linux-exec命令试验驱动(12)
对于做驱动经常会使用exec来试验驱动,通过exec将-sh进程下的描述符指向我们的驱动,来实现调试 -sh进程常用描述符号: 0:标准输入 1:标准输出 2:错误信息 5:中断服务 exec命令使用 ...
- JSON的基本结构和数据交换原理
0.补充的写在前面的话 2017.03.29 补充内容 最近看到这篇博客的阅读量,想来应该是有部分网友来过想要了解JSON的基本概念,这篇博文写得可能不是那么好,所以现在再补充贴一位老师的文章,希望能 ...
- Linux 虚拟机安装后的配置和一些命令符笔记
一.安装后的配置 1.设在终端的字体为等宽字体 比如:DejaVu Sans Mono 2.将当前的普通用户加入到sudo用户组adduser en sudochmod +w /etc/sudoers ...
- 用static声明的函数和变量小结
static 声明的变量在C语言中有两方面的特征: 1).变量会被放在程序的全局存储区中,这样可以在下一次调用的时候还可以保持原来的赋值.这一点是它与堆栈变量和堆变量的区别. 2).变量用static ...
- wget下载整个网站
wget下载整个网站wget下载整个网站可以使用下面的命令 wget -r -p -k -np http://hi.baidu.com/phps , -r 表示递归下载,会下载所有的链接,不过要注意的 ...
- javaWeb正则表达式
对于web来说,字符串的处理特别重要,而正则表达式是对字符串处理的利器,在字符过滤,验证方面都能看到她的身影. 今天需要处理一段json字符串,在用String.replaceAll的过程中,遇到了正 ...
- HDFS概述(6)————用户手册
目的 本文档是使用Hadoop分布式文件系统(HDFS)作为Hadoop集群或独立通用分布式文件系统的一部分的用户的起点.虽然HDFS旨在在许多环境中"正常工作",但HDFS的工作 ...
- U盘中毒无限蓝屏重启的解决办法
开门见山,这个帖子只针对U盘中毒导致的以下两种症状: 1.win10系统无法进入并且要求初始化,卸载所有第三方应用 2.win7系统无限蓝屏重启): 其他的硬件故障不在本次讨论范围之内. 说明以下.上 ...