写在前面的话

相关背景及资源:

曹工说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 Boot源码(16)-- Spring从xml文件里到底得到了什么(aop:config完整解析【上】)

曹工说Spring Boot源码(17)-- Spring从xml文件里到底得到了什么(aop:config完整解析【中】)

工程代码地址 思维导图地址

工程结构图:

概要

本篇是接着前两篇讲的,为了避免不必要的重复,请大家先看下。

曹工说Spring Boot源码(16)-- Spring从xml文件里到底得到了什么(aop:config完整解析【上】)

曹工说Spring Boot源码(17)-- Spring从xml文件里到底得到了什么(aop:config完整解析【中】)

本篇主要讲解spring如何在bean生成过程中,完成“狸猫换太子”操作的。(利用代理对象,替换ioc容器中的原有bean)。

回顾前文

spring解析的xml如下:

<!--目标对象-->
<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 id="myAspect" ref="performAspect">
<aop:after method="afterPerform" pointcut-ref="mypointcut"/>
</aop:aspect>
</aop:config>

经前文的理解,总共生成了如下几个bean definition。

bean definition 中bean class 备注
PerformAspect 通知
Performer 要切的目标
AspectJExpressionPointcut 切点,即<aop:pointcut />那一行
org.springframework.aop.aspectj.AspectJPointcutAdvisor advisor,解析<aop:after method="afterPerform" pointcut-ref="mypointcut"/> 这一行得到
AspectJAwareAdvisorAutoProxyCreator 实现了BeanPostProcessor接口,解析<aop:config>得到

spring大致启动流程

  1. 解析xml、注解,通过各种渠道,得到bean definition;

  2. 从bean definition集合中,找出bean class实现了BeanFactoryPostProcessor接口的子集,然后通过getBean来获取这部分特殊的bean,然后依次调用其postProcessBeanFactory方法

    来对其余的bean definition进行处理;

    public interface BeanFactoryPostProcessor {
    
    	/**
    * Modify the application context's internal bean factory after its standard
    * initialization. All bean definitions will have been loaded, but no beans
    * will have been instantiated yet. This allows for overriding or adding
    * properties even to eager-initializing beans.
    * @param beanFactory the bean factory used by the application context
    * @throws org.springframework.beans.BeansException in case of errors
    */
    void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory);
    }
  3. 从bean definition集合中,找出bean class实现了BeanPostProcessor接口的子集,然后通过getBean来获取这部分特殊的bean,然后保存起来。

  4. 找出不具有特殊功能的(没实现BeanFactoryPostProcessor,也没实现BeanPostProcessor)bean definition 集合,再过滤出单例bean,且lazy-init属性为false(说明要在spring容器初始化过程中,实例化的bean)的这部分,作为子集;然后依次调用其getBean方法,在这个过程中,会使用第三步获取到的beanPostProcessor来处理这些bean,时机大致包括:实例化前后,初始化前后。

以上就是spring比较粗的流程,当然,很多细节没说,不过核心流程是这样的。

spring aop如何生效

1. 实例化AspectJAwareAdvisorAutoProxyCreator

在前面的spring流程中,第三步,就会去查找实现了BeanPostProcessor的bean definition集合,其中,就会包含AspectJAwareAdvisorAutoProxyCreator 这个bean definition;然后通过getBean方法,将这个bean definition实例化为bean。(这个过程,类似于通过class来创建对象)

这个步骤的具体实现入口在:

public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh(); // Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); /**
* 配置beanFactory
*/
prepareBeanFactory(beanFactory); try {
postProcessBeanFactory(beanFactory); // Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory); // 入口在这里!这里面会去 getBean
registerBeanPostProcessors(beanFactory); // Initialize message source for this context.
initMessageSource(); // Initialize other special beans in specific context subclasses.
onRefresh(); // Check for listener beans and register them.
registerListeners(); /**
* 这里对单例bean、且lazy-init=false进行实例化
*/
finishBeanFactoryInitialization(beanFactory); // Last step: publish corresponding event.
finishRefresh();
}
}
}

具体跟进去后,在如下位置,809行,就通过getBean,获取到了AspectJAwareAdvisorAutoProxyCreator这个bean了:

2.AspectJAwareAdvisorAutoProxyCreator对target bean进行后置处理,生成代理

在“spring大致启动流程”中的第四步,去预先实例化bean时,会进入以下逻辑:

public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
... try {
... // Check for listener beans and register them.
registerListeners(); /**
* 入口在这里。这里对单例bean、且lazy-init=false进行实例化
*/
finishBeanFactoryInitialization(beanFactory); // Last step: publish corresponding event.
finishRefresh();
}
}
}

然后会进入以下逻辑:

org.springframework.beans.factory.support.DefaultListableBeanFactory#preInstantiateSingletons
public void preInstantiateSingletons() throws BeansException {
if (this.logger.isInfoEnabled()) {
this.logger.info("Pre-instantiating singletons in " + this);
}
// 其中 this.beanDefinitionNames 保存了所有的bean definition名称
for (String beanName : this.beanDefinitionNames) {
RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
// 实例化那些:非抽象、单例、非lazy-init的bean definition
if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
if (isFactoryBean(beanName)) {
...
if (isEagerInit) {
getBean(beanName);
}
}
else {
getBean(beanName);
}
}
}
}

所以,这里就是对那些:非抽象、单例、非lazy-init的bean definition 进行实例化。

我们这里,大家看下当前容器中包含的全部的bean definition:

我这边的demo,第一个要实例化的bean,就是performer。在getBean()处理performer这个bean的过程中,会经历以下的流程:

其中,AspectJAwareAdvisorAutoProxyCreator 作为beanPostProcessor,在实例化performer这个bean时,有两个时间点参与进来。

哪两个时间点呢,大家再看看,AspectJAwareAdvisorAutoProxyCreator 的类图:

其实它不止实现了BeanPostProcessor接口,它还实现了InstantiationAwareBeanPostProcessor接口。这个多出来的接口,干嘛的?大家看看它的方法就知道了:

public interface InstantiationAwareBeanPostProcessor extends BeanPostProcessor {

	// 注意,before Instantiation的意思是,实例化之前调用
Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException; // 这个,实例化之后调用
boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException; ...无关方法
}

所以,InstantiationAwareBeanPostProcessor扩展了两个时间点出来,一个是实例化前,一个是实例化后。

什么叫实例化?把一个对象new出来,通俗来讲,就叫做实例化。

在spring的getBean流程里,实例化肯定是早于初始化的。所以,一个bean,会经历如下顺序(大家直接看前面点的图,更清晰):

  1. InstantiationAwareBeanPostProcessor的postProcessBeforeInstantiation;

  2. 根据bean definition来实例化,假设bean class为A,bean definition中的constructorArgumentValues参数,如果为空,则使用A的默认构造函数;如果constructorArgumentValues有值,表示:在此之前,需要先获取到相应的构造函数值,才能去反射通过构造器来创建对象

    这里面,会涉及到bean的递归调用。

    比如,以前一篇和开头提到的,AspectJPointcutAdvisor 这个bean definition来说,其中它的构造函数,就要求一个AbstractAspectJAdvice 对象:

    public AspectJPointcutAdvisor(AbstractAspectJAdvice advice) {
    Assert.notNull(advice, "Advice must not be null");
    this.advice = advice;
    this.pointcut = advice.buildSafePointcut();
    }

    那AbstractAspectJAdvice 这个对象要怎么生成,这个在我们的场景下,是被抽象成一个内部bean的,bean class为AspectJAfterAdvice。AspectJAfterAdvice这个类呢,构造函数又是下面这样的:

    public AspectJAfterAdvice(
    Method aspectJBeforeAdviceMethod, AspectJExpressionPointcut pointcut, AspectInstanceFactory aif) { super(aspectJBeforeAdviceMethod, pointcut, aif);
    }

    又依赖了一堆其他的参数,这三个参数,其中2个也是被定义为了内部bean definition,一个为bean 引用。(具体请参照前一篇)。

    所以,这个bean的实例化过程就相对繁琐,涉及到bean的递归生成。

  3. InstantiationAwareBeanPostProcessor的postProcessAfterInstantiation;

  4. 属性注入,此时是autowired等发挥作用的地方;

  5. BeanPostProcessor的postProcessBeforeInitialization

  6. BeanPostProcessor#postProcessAfterInitialization

这里面6个步骤,AspectJAwareAdvisorAutoProxyCreator 在其中两个地方,实现了自己的业务逻辑。

2.1 InstantiationAwareBeanPostProcessor的postProcessBeforeInstantiation

具体实现在:

org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator
public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
Object cacheKey = getCacheKey(beanClass, beanName); if (beanName == null || !this.targetSourcedBeans.contains(beanName)) {
if (this.advisedBeans.containsKey(cacheKey)) {
return null;
}
// 入口在这:shouldSkip
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.
if (beanName != null) {
TargetSource targetSource = getCustomTargetSource(beanClass, beanName);
if (targetSource != null) {
this.targetSourcedBeans.add(beanName);
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(beanClass, beanName, targetSource);
Object proxy = createProxy(beanClass, beanName, specificInterceptors, targetSource);
this.proxyTypes.put(cacheKey, proxy.getClass());
return proxy;
}
} return null;
}

以上逻辑中,我们需要进入到shouldSkip方法,重点是下面的findCandidateAdvisors方法:

@Override
protected boolean shouldSkip(Class beanClass, String beanName) {
// TODO: Consider optimization by caching the list of the aspect names
/**
* 这里调用了父类中实现的findCandidateAdvisors,获取候选的advisor bean;这里也是真正根据bean
* definition去生成advisor bean的地方
*/
List<Advisor> candidateAdvisors = **findCandidateAdvisors()**;
for (Advisor advisor : candidateAdvisors) {
/**
* 如果当前要检查的bean,就是advisor里的通知类,则跳过
*/
if (advisor instanceof AspectJPointcutAdvisor) {
if (((AbstractAspectJAdvice) advisor.getAdvice()).getAspectName().equals(beanName)) {
return true;
}
}
}
return super.shouldSkip(beanClass, beanName);
}

下面会找出ioc容器中,实现了Advisor接口的bean definition,并全部实例化。Advisor是什么?

我们前面提到的AspectJPointcutAdvisor,就实现了这个接口。

public List<Advisor> findAdvisorBeans() {
// Determine list of advisor bean names, if not cached already.
String[] advisorNames = null;
synchronized (this) {
advisorNames = this.cachedAdvisorBeanNames;
if (advisorNames == null) {
// 1.从spring容器查找Advisor类型的bean definition
advisorNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
this.beanFactory, Advisor.class, true, false);
this.cachedAdvisorBeanNames = advisorNames;
}
}
if (advisorNames.length == 0) {
return new LinkedList<Advisor>();
} List<Advisor> advisors = new LinkedList<Advisor>();
for (String name : advisorNames) {
if (isEligibleBean(name) && !this.beanFactory.isCurrentlyInCreation(name)) {
// 遍历那些bean definition,通过getBean,来获取bean
advisors.add(this.beanFactory.getBean(name, Advisor.class));
}
}
return advisors;
}

再啰嗦一句,advisor差不多是aop的核心数据结构,你通过Aspect注解方式,最终也是解析为一个个的Advisor。

一个切点+一个切面方法 基本就等于一个Advisor对象。比如,一个before方法,算一个;在一个after,又算一个。

2.2 BeanPostProcessor的postProcessAfterInitialization

这是第二个时间点,在这里,检查bean要不要被拦截,生成代理。大家可以简单理解,因为每个bean的创建过程,都要被它处理,它呢,就会检查,这个bean,是不是匹配切点,如果匹配,就生成代理。

举个例子,以前看过一个小说,是京官去很偏远的地方上任,路上被人杀了,并被另外一个人拿了印章,到了地方,靠着印章,招摇撞骗,当了地方官。假设切点是:每个过路人;切面:偷天换日。那这个PostProcessor,就是那伙人,拦截每个过路人,并判断是不是那个倒霉的京官,如果是,就杀了并且换个人拿了印章去当地方官。

public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean != null) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
if (!this.earlyProxyReferences.contains(cacheKey)) {
// 返回生成的代理对象
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}

这里的下面这句,bean是原始bean,wrapIfNecessary返回的,就作为最终的bean。如果你不需要切这个bean,那你就返回它本身;如果要代理,你就返回另一个代理对象即可。

return wrapIfNecessary(bean, beanName, cacheKey);

我们仔细看这个方法内部:

protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
if (beanName != null && this.targetSourcedBeans.contains(beanName)) {
return bean;
}
if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
return bean;
}
if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
} // 获取前面2.1章节里的Advisor对象,并保存到Object[] specificInterceptors 里
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
if (specificInterceptors != null) {
this.advisedBeans.put(cacheKey, Boolean.TRUE);
// 创建代理,注意,Advisor数组,已经被传进去了。
Object proxy = createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
this.proxyTypes.put(cacheKey, proxy.getClass());
return proxy;
} this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}

大家看上面的注释吧,这里获取了ioc容器里的Advisor对象,如果advisor数组不为null,则创建代理,并返回代理。大家看下面的debug图就理解了。

创建代理的过程就简单了,基本就是:有接口就搞个jdk 动态代理,否则就cglib代理。

我做了个小测试,

protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
if (bean.getClass().getName().equals("foo.Performer")) {
return "hahha";
}
...其他代码省略,主要加了上面那个if
return bean;
}
public final class Main {

    public static void main(String[] args) {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(
"context-namespace-test-aop.xml"); Perform performer = (Perform) ctx.getBean(Perform.class);
performer.sing();
}
}

然后在倒数第二行,获取Perform类型的bean时,报错了。为啥呢?因为本来foo.Performer实现了Perform接口,但现在,我用一个string作为代理返回去了,所以就没有实现Perform接口的bean存在了

Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [foo.Perform] is defined
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:296)
at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1196)
at foo.Main.main(Main.java:23)

总结

spring aop这个东西,还是不简单,我本来打算这一讲把全部内容说清楚;但现在发现,创建代理这部分,还是得放到下一篇。

至于源码那些,在第16篇里给了地址的,大家可以看看。有啥问题,及时联系我。

曹工说Spring Boot源码(18)-- Spring AOP源码分析三部曲,终于快讲完了 (aop:config完整解析【下】)的更多相关文章

  1. 曹工说Spring Boot源码(17)-- Spring从xml文件里到底得到了什么(aop:config完整解析【中】)

    写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享 曹工说Spring Boot源码(2)-- Bean ...

  2. 快速开发架构Spring Boot 从入门到精通 附源码

    导读 篇幅较长,干货十足,阅读需花费点时间.珍惜原创,转载请注明出处,谢谢! Spring Boot基础 Spring Boot简介 Spring Boot是由Pivotal团队提供的全新框架,其设计 ...

  3. Spring Boot核心技术之Rest映射以及源码的分析

    Spring Boot核心技术之Rest映射以及源码的分析 该博客主要是Rest映射以及源码的分析,主要是思路的学习.SpringBoot版本:2.4.9 环境的搭建 主要分两部分: Index.ht ...

  4. spring boot实战(第十三篇)自动配置原理分析

    前言 spring Boot中引入了自动配置,让开发者利用起来更加的简便.快捷,本篇讲利用RabbitMQ的自动配置为例讲分析下Spring Boot中的自动配置原理. 在上一篇末尾讲述了Spring ...

  5. spring boot 2.0.3+spring cloud (Finchley)3、声明式调用Feign

    Feign受Retrofix.JAXRS-2.0和WebSocket影响,采用了声明式API接口的风格,将Java Http客户端绑定到他的内部.Feign的首要目标是将Java Http客户端调用过 ...

  6. Spring Boot 必须先说说 Spring 框架!

    现在 Spring Boot 非常火,各种技术文章,各种付费教程,多如牛毛,可能还有些不知道 Spring Boot 的,那它到底是什么呢?有什么用?今天给大家详细介绍一下. Spring Boot ...

  7. Spring Boot配置篇(基于Spring Boot 2.0系列)

    1:概述 SpringBoot支持外部化配置,配置文件格式如下所示: properties files yaml files environment variables command-line ar ...

  8. Spring Boot (五)Spring Data JPA 操作 MySQL 8

    一.Spring Data JPA 介绍 JPA(Java Persistence API)Java持久化API,是 Java 持久化的标准规范,Hibernate是持久化规范的技术实现,而Sprin ...

  9. (转)Spring Boot 2 (八):Spring Boot 集成 Memcached

    http://www.ityouknow.com/springboot/2018/09/01/spring-boot-memcached.html Memcached 介绍 Memcached 是一个 ...

随机推荐

  1. Spring中的beanPostProcess的作用

    BeanPostProcessor是Spring框架中非常重要的bean之一.贯穿在Spring容器中bean的初始化的整个过程. Spring中的beanpostProcess体系结构如下: 可以看 ...

  2. 记录一下第一次用markdown写博客回滚过程

    前面写博客,一直用的是博客园的TinyMCE编辑器, 今天不知道哪根筋搭牢了,想试试用Markdown写. 于是在“选项”里面把默认编辑器修改为“Markdown”,鉴于本人有一定的Markdown基 ...

  3. js对象转换为json格式时,js对象属性中有值为null和undefined注意事项

    当属性值为null时: 当属性值为undefined时: 只有当属性值为未定义时, js对象转换成json格式时会忽略该属性.

  4. Java多线程的创建(二)

    前言: 虽然java的API中说创建多线程的方式只有两种(There are two ways to create a new thread of execution),分别是继承Thread类创建和 ...

  5. 使用 OAS(OpenAPI标准)来描述 Web API

    无论哪种类型的Web API, 都可能需要给其他开发者使用. 所以API的开发者体验是很重要的. API的开发者体验, 简写为 API DX (Developer Experience). 它包含很多 ...

  6. git 其它补充(版本)

    1.给源码贡献力量(当自己比较牛逼时) pull request 2..gitignore文件 在创建仓库的时候可以进行设置 3.版本 git tag -a v1.0 -m '版本介绍' 本地创建Ta ...

  7. 一张图快速上手Xmind思维导图

    使用软件:Xmind8 pro版(可在网上找破解版),应用广泛,功能强大

  8. typedef声明变量也是一种求值过程

    前言: 什么叫做:声明变量是求值过程?请看下面的声明, int i; 很简单,声明了个整型变量i,再看如下声明, int *p; 也很简单,立刻反应出来它是指向整型的指针,但是具体如何推倒出来的呢?其 ...

  9. Python的Excel操作及数据可视化

    Excel表操作 python操作excel主要用到xlrd和xlwt这两个库,即xlrd是读excel,xlwt是写excel的库. 安装xlrd pip install xlrd 简单的表格读取 ...

  10. java8新特性Lambda和Stream

    Java8出来已经4年,但还是有很多人用上了jdk8,但并没用到里面的新东西,那不就等于没用?jdk8有许多的新特性,详细可看下面脑图 我只讲两个最重要的特性Lambda和Stram,配合起来用可以极 ...