曹工说Spring Boot源码(16)-- Spring从xml文件里到底得到了什么(aop:config完整解析【上】)
写在前面的话
相关背景及资源:
曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享
曹工说Spring Boot源码(2)-- Bean Definition到底是什么,咱们对着接口,逐个方法讲解
曹工说Spring Boot源码(3)-- 手动注册Bean Definition不比游戏好玩吗,我们来试一下
曹工说Spring Boot源码(4)-- 我是怎么自定义ApplicationContext,从json文件读取bean definition的?
曹工说Spring Boot源码(5)-- 怎么从properties文件读取bean
曹工说Spring Boot源码(6)-- Spring怎么从xml文件里解析bean的
曹工说Spring Boot源码(7)-- Spring解析xml文件,到底从中得到了什么(上)
曹工说Spring Boot源码(8)-- Spring解析xml文件,到底从中得到了什么(util命名空间)
曹工说Spring Boot源码(9)-- Spring解析xml文件,到底从中得到了什么(context命名空间上)
曹工说Spring Boot源码(10)-- Spring解析xml文件,到底从中得到了什么(context:annotation-config 解析)
曹工说Spring Boot源码(11)-- context:component-scan,你真的会用吗(这次来说说它的奇技淫巧)
曹工说Spring Boot源码(12)-- Spring解析xml文件,到底从中得到了什么(context:component-scan完整解析)
曹工说Spring Boot源码(13)-- AspectJ的运行时织入(Load-Time-Weaving),基本内容是讲清楚了(附源码)
曹工说Spring Boot源码(14)-- AspectJ的Load-Time-Weaving的两种实现方式细细讲解,以及怎么和Spring Instrumentation集成
曹工说Spring Boot源码(15)-- Spring从xml文件里到底得到了什么(context:load-time-weaver 完整解析)
工程结构图:
概要
本篇是spring源码的第16篇,前面已经把context命名空间下,常用的几个元素讲解差不多了,包括:
context:property-placeholder
context:property-override
context:annotation-config
context:component-scan
context:load-time-weaver
接下来,着重讲解aop命名空间。该命名空间下,有以下几个元素:
<aop:config></aop:config>
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
<aop:scoped-proxy></aop:scoped-proxy>
本讲讲解aop:config,在没有注解的时代,大家的aop就是这么配的,如下所示:
<!--目标对象-->
<bean id="performer" class="foo.Performer"/>
<!--切面-->
<bean id="performAspect" class="foo.PerformAspect"/>
<!--配置切入点-->
<aop:config>
<aop:pointcut id="mypointcut" expression="execution(public * foo.Perform.sing(..))"/>
<aop:aspect ref="performAspect">
<aop:after method="afterPerform" pointcut-ref="mypointcut"/>
</aop:aspect>
</aop:config>
大家可能觉得xml落伍了,没错,我也这么觉得,但我深入了解后发现,通过注解配置切面,和通过xml配置切面,最终其实殊途同归,最终都转变为了内部结构List<org.springframework.aop.Advisor>,无非是读取配置的方式不同。
public interface Advisor {
Advice getAdvice();
}
Advice这个东西,通俗来说是"通知",我截取了spring官网说明:
- Join point: A point during the execution of a program, such as the execution of a method or the handling of an exception. In Spring AOP, a join point always represents a method execution.
- Advice: Action taken by an aspect at a particular join point. Different types of advice include “around”, “before” and “after” advice. (Advice types are discussed later.) Many AOP frameworks, including Spring, model an advice as an interceptor and maintain a chain of interceptors around the join point.
大概翻译就是:
连接点:程序执行过程中的一个执行点,比如方法调用,或者异常处理。在spring aop里,永远指方法调用
通知:切面在一个特定的连接点所采取的动作。advice包含了多种类型,包括"around"、“before”、“after”。很多aop框架,包括spring,将advice建模为一个拦截器,在切点处维护一个拦截器链。
Advice,在文档里,都是说,表示的是在连接点所采取的动作,比如性能检测、记录日志、事务等。
但是,我要说明的是,在源码里,是不太一样的。针对前面提到的各种类型的advice,"around"、“before”、“after”等,其在spring里,是使用以下几个类来表示的。
我们随便找个org.springframework.aop.aspectj.AspectJAfterAdvice来看看,这个是代表after类型的advice:
public AspectJAfterAdvice(
Method aspectJBeforeAdviceMethod, AspectJExpressionPointcut pointcut, AspectInstanceFactory aif) {
super(aspectJBeforeAdviceMethod, pointcut, aif);
}
以上是该类,唯一的构造函数,其中将3个参数,直接传给了父类的构造函数。
protected final Method aspectJAdviceMethod;
private final AspectJExpressionPointcut pointcut;
private final AspectInstanceFactory aspectInstanceFactory;
public AbstractAspectJAdvice(
Method aspectJAdviceMethod, AspectJExpressionPointcut pointcut, AspectInstanceFactory aspectInstanceFactory) {
this.aspectJAdviceMethod = aspectJAdviceMethod;
this.pointcut = pointcut;
this.aspectInstanceFactory = aspectInstanceFactory;
}
这里面,三个字段,其中,aspectJAdviceMethod就是我们的通知方法,其类型是JDK里的Method,即我们自定义的那些性能、日志等aop业务逻辑所在之处;pointcut,代表了切点,我们默认写的表达式是使用AspectJ的语法写的,想想,是不是不会写的时候,有时候查着查着,就查到aspectJ的官网去了;aspectInstanceFactory,里面封装了切面对象。
这几个属性,有啥关系?
切面 = 切点 + 通知,即,在什么时间,干什么事。
那用这几个属性,怎么表达呢? 简单来说,是不是,在每个方法执行时,匹配是否和pointcut匹配,如果匹配,则执行:
return this.aspectJAdviceMethod.invoke(this.aspectInstanceFactory.getAspectInstance(), actualArgs);
没错,上面那个代码其实就是spring里来的,spring在"干什么事"这部分,就是这么做的:
#org.springframework.aop.aspectj.AbstractAspectJAdvice
protected Object invokeAdviceMethodWithGivenArgs(Object[] args) throws Throwable {
Object[] actualArgs = args;
if (this.aspectJAdviceMethod.getParameterTypes().length == 0) {
actualArgs = null;
}
try {
ReflectionUtils.makeAccessible(this.aspectJAdviceMethod);
return this.aspectJAdviceMethod.invoke(this.aspectInstanceFactory.getAspectInstance(), actualArgs);
}
}
说了半天,主要就是说,spring aop源码里的advice,不只是文档里提到的advice,而是包含了完整的切点和切面的逻辑,这里的advice,其实也是狭义的,即spring aop里的方法级别的advice。
啰嗦了半天,我们马上进入正题。
使用
目标类和接口如下:
package foo;
public class Performer implements Perform {
@Override
public void sing() {
System.out.println("男孩在唱歌");
}
}
package foo;
public interface Perform {
void sing();
}
切面如下:
package foo;
public class PerformAspect {
public void afterPerform() {
System.out.println("表演之后要行礼");
}
}
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: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.xsd">
<!--目标对象-->
<bean id="performer" class="foo.Performer"/>
<!--切面-->
<bean id="performAspect" class="foo.PerformAspect"/>
<!--配置切入点-->
<aop:config>
<aop:pointcut id="mypointcut" expression="execution(public * foo.Perform.sing(..))"/>
<aop:aspect ref="performAspect">
<aop:after method="afterPerform" pointcut-ref="mypointcut"/>
</aop:aspect>
</aop:config>
</beans>
测试代码如下:
package foo;
public final class Main {
public static void main(String[] args) {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(
"context-namespace-test-aop.xml");
// json输出bean definition
List<BeanDefinition> list =
ctx.getBeanFactory().getBeanDefinitionList();
MyFastJson.printJsonStringForBeanDefinitionList(list);
Perform performer = (Perform) ctx.getBean(Perform.class);
performer.sing();
}
}
执行结果如下:
男孩在唱歌
表演之后要行礼
简略源码说明
上面那个例子,很简单就实现了aop,但是spring为此做了很多工作,总结起来,有以下几步:
步骤1:解析xml文件,获取bean definition
bean definition 中bean class | 备注 |
---|---|
PerformAspect | 通知 |
Performer | 要切的目标 |
AspectJExpressionPointcut | 切点,即<aop:pointcut />那一行 |
org.springframework.aop.aspectj.AspectJPointcutAdvisor | advisor,请翻到文章开头,实际表达一个:切点+切面方法; |
AspectJAwareAdvisorAutoProxyCreator | 实现了BeanPostProcessor接口,在spring getBean过程中,检查是否匹配切点,匹配则创建代理,并使用代理对象替换ioc容器中真实的bean |
步骤2:AspectJAwareAdvisorAutoProxyCreator 狸猫换太子
这个bean definition,本来也很普通,但让它变得不普通的是,这个bean class,实现了BeanPostProcessor接口。BeanPostProcessor接口的功能,看下面就明白:
public interface BeanPostProcessor {
Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
}
这两个方法,在spring创建bean时,被调用,一个是在初始化之前,一个是初始化之后。
关于生命周期,大家可以看上图,一定要搞明白,什么叫实例化,什么叫初始化,什么叫属性注入,我们这里,
AspectJAwareAdvisorAutoProxyCreator 生效的地方,主要是在 初始化之后。它实现了postProcessAfterInitialization方法,这个方法,其return的结果,就会取代原有的bean,来存放到ioc容器中。
后续,如果有其他bean,依赖这个bean的话,拿到的也是代理之后的了。
大家可以看看其实现:
AspectJAwareAdvisorAutoProxyCreator.java
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean != null) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
if (!this.earlyProxyReferences.contains(cacheKey)) {
// 这里根据原来的bean,创建动态代理,并返回给ioc容器,完成狸猫换太子操作
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}
而我们创建的代理对象,其实是会包含要应用的advisor的,大家看下图,其中specificInterceptors的第二个元素,就是前面我们步骤1解析的那个bean definition。
当然了,大家熟知的,有接口时创建jdk代理,没接口时创建cglib代理,就是在这个步骤发生的,下一篇会细讲。
步骤3:花非花,雾非雾
运行时,看似调用target,实际调用代理的对应方法:
可以看到,这里拿到的,已经是代理对象,而不是真实对象了,调用代理对象时,就会像tomcat的filter链那样,tomcat是filter链进行链式处理,直到最后调用servlet;这里是interceptor链先挨个调用自己在target方法之前要执行的逻辑,然后调用target,最后调用要在target之后执行的逻辑。
总结
今天这篇算是aop的开胃菜,前面只说了大概的步骤,并没有讲透,详细的源码分析实在不适合揉到一篇来讲,所以会分到下一讲或两讲。
希望对大家有所帮助,谢谢。
曹工说Spring Boot源码(16)-- Spring从xml文件里到底得到了什么(aop:config完整解析【上】)的更多相关文章
- Spring mybatis源码篇章-Mybatis的XML文件加载
通过阅读源码对实现机制进行了解有利于陶冶情操,承接前文Spring mybatis源码篇章-Mybatis主文件加载 前话 前文主要讲解了Mybatis的主文件加载方式,本文则分析不使用主文件加载方式 ...
- 曹工说Spring Boot源码系列开讲了(1)-- Bean Definition到底是什么,附spring思维导图分享
写在前面的话&&About me 网上写spring的文章多如牛毛,为什么还要写呢,因为,很简单,那是人家写的:网上都鼓励你不要造轮子,为什么你还要造呢,因为,那不是你造的. 我不是要 ...
- 曹工说Spring Boot源码(14)-- AspectJ的Load-Time-Weaving的两种实现方式细细讲解,以及怎么和Spring Instrumentation集成
写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享 曹工说Spring Boot源码(2)-- Bean ...
- 曹工说Spring Boot源码(17)-- Spring从xml文件里到底得到了什么(aop:config完整解析【中】)
写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享 曹工说Spring Boot源码(2)-- Bean ...
- 曹工说Spring Boot源码(18)-- Spring AOP源码分析三部曲,终于快讲完了 (aop:config完整解析【下】)
写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享 曹工说Spring Boot源码(2)-- Bean ...
- 曹工说Spring Boot源码(19)-- Spring 带给我们的工具利器,创建代理不用愁(ProxyFactory)
写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享 曹工说Spring Boot源码(2)-- Bean ...
- 曹工说Spring Boot源码(20)-- 码网灰灰,疏而不漏,如何记录Spring RedisTemplate每次操作日志
写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享 曹工说Spring Boot源码(2)-- Bean ...
- 曹工说Spring Boot源码(21)-- 为了让大家理解Spring Aop利器ProxyFactory,我已经拼了
写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享 曹工说Spring Boot源码(2)-- Bean ...
- 曹工说Spring Boot源码(22)-- 你说我Spring Aop依赖AspectJ,我依赖它什么了
写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享 曹工说Spring Boot源码(2)-- Bean ...
随机推荐
- (一)Angular+spring-security-cas前后端分离(基于ticket)认证时序图
一.静态资源认证时序图 2. ajax请求认证拦截时序图 3.退出登录时序图
- springmvc接收json数据的常见方式
经常使用Ajax异步请求来进行数据传输,传的数据是json数据,json数据又有对象,数组.所有总结下springmvc获取前端传来的json数据方式:1.以RequestParam接收前端传来的是j ...
- css3让元素自适应高度
知识点: viewport:可视窗口,也就是浏览器.vw Viewport宽度, 1vw 等于viewport宽度的1%vh Viewport高度, 1vh 等于viewport高的的1% calc( ...
- XSS基础学习
XSS基础学习 By:Mirror王宇阳 什么是XSS XSS攻击是指在网页中嵌入一段恶意的客户端Js脚本代码片段,JS脚本恶意代码可以获取用户的Cookie.URL跳转.内容篡改.会话劫持--等. ...
- 2018南京现场赛D 模拟退火
题目链接:https://codeforces.com/gym/101981/attachments 给你n个城市的三维坐标,叫你求得一个坐标使这个坐标到其他城市的最大距离最小,并输出这个距离(距离不 ...
- 洛谷p1502窗口的星星 扫描线
题目链接:https://www.luogu.org/problem/P1502 扫描线的板子题,把每个点看成矩形,存下边(x,y,y+h-1,li)和(x+w-1,y,y+h-1),在按横坐标扫线段 ...
- 基于iTextSharp的PDF操作(PDF打印,PDF下载)
基于iTextSharp的PDF操作(PDF打印,PDF下载) 准备 1. iTextSharp的简介 iTextSharp是一个移植于java平台的iText项目,被封装成c#的组件来用于C#生成P ...
- scrapy在存储数据到json文件中时,中文变成为\u开头的字符串的处理方法
在settings.py文件中添加 FEED_EXPORT_ENCODING = 'utf-8'
- C++模板编程与宏编程经验谈
C++模板编程与宏编程经验谈 有人说C 与C++的不同主要是因为C++支持模板,不要说区别是面向对象化编程,因为C同样能很好的实现对象化编程,面向对象化其实只是思想,在很多语言中都能实现,区别在于实现 ...
- 【python系统学习08】for循环知识点合集
for循环 for简介 [循环]:就是依照某些我们编写的特定规则,重复的做一件事. 当你需要重复的"搬砖"的时候,可以用for循环进行遍历,让机器循环的帮你去"搬砖&qu ...