终于把 Spring Boot 3.0 写成书了!

Posted Java技术栈

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了终于把 Spring Boot 3.0 写成书了!相关的知识,希望对你有一定的参考价值。

大家好,我是R哥。

我的新书《Spring Boot 3 核心技术与最佳实战》打磨一年多,今天终于上市了,定价 158 元,今天刚上市搞 5 折促销,80 元不到上车,这可能是全网最便宜的时候了,机会难得,想拥抱 Spring Boot 3.0 的不要错过。

文章还没发,已经有老铁粉丝上车了,真爱啊。。。

为什么要学 Spring Boot?

Spring 作为 Java 开发界的万能框架,曾经和 Struts2、Hibernate 框架组成 SSH,成为 Java Web 开发的三驾马车。大概在 2013 年左右,又和 Spring MVC、MyBatis 框架组成 SSM,成为新一代的 Web 开发框架全家桶,一直流行延续至今。

而为了简化 Spring 框架的上手难度,Spring Boot 框架于 2014 年诞生,可以帮助开发者更加轻松、快捷地使用 Spring 的组件,它是 Spring、Spring MVC 等框架更上一层的框架,它需要依赖于 Spring、Spring MVC 等原生框架,而不能独立存在。

学会 Spring Boot,可以简化使用 Spring 基础组件的难度,还是学习 Spring Cloud 微服务框架的基础,因为 Spring Cloud 的基础就是 Spring Boot。

Vaadin 最近发布了一份有关企业中 Java 现状的研究报告:

https://pages.vaadin.com/java-survey-2023

其中,Spring Boot 已经超越 Spring 成为最受欢迎的 Java 后端框架:

虽然 Spring Boot 是基于 Spring 的,但这个图表想表达的意思是,现在越来越多的开发者第一选择是基于开箱即用的 Spring Boot 来使用 Spring,而不是直接使用原始的 Spring。

既然 Spring Boot 代表了企业的真实需求,那么,它就会表现在 Java 工程师、架构师的求职面试技能清单上,Spring Boot 几乎是必备技能。

所以,要成为合格的 Java 程序员,要学习 Spring 全家桶,Spring Boot 则是必经之路。

本书缘起

R哥最初接触和学习 Spring Boot 框架是在 2016 年,工作之余,茶余饭后经常会和同事们聊聊最新的 Java 技术,Spring Boot 框架就是其中之一,那时候虽然应用还不是很广泛,但却很火热,后来就逐渐成为了炙手可热的 Java 框架。

自 2016 起,R哥有幸负责和参与公司的多个系统重构,这其中就包括由 SSM 框架更新到以 Spring Boot 框架为基础的转型,也包括以 Spring Cloud 框架为基础的微服务系统的设计和研发,Spring Cloud 框架的基础就是基于 Spring Boot 框架构建。

在多年的实际开发和架构工作中,R哥积累了大量的 Spring Boot 框架使用经验,也见证了 Spring Boot 1.x ~ 3.x 的发展历史。

为什么要写这本书?

1、Spring Boot 至今已经发展了近十年,最新的版本也已经发布到了 3.x,其底层实现逻辑、使用方式已经发生了翻天覆地的变化,同时在 Spring Boot 各个版本接二连三地停止维护的状况下,大部分版本已经不能满足技术更新的需要了。

2、虽然R哥熟练掌握了 Spring Boot 框架,但在其不断地发展下,某些知识点更新了也会浑然不知,直到在使用出错时才后知后觉,非常影响工作效率。

3、市面上很多书籍、博客都是基于 Spring Boot 低版本的应用,即使有最新的 Spring Boot 技术,也都是四处零乱,并没有对最新的技术要点进行系统地梳理、解读及应用,如果还继续学习低版本的应用,就会给很多初学者带来没有必要的知识更新的成本,也会走很多弯路。

鉴于以上种种原因,以及市面上关于 Spring Boot 3.x 的系统性学习资料比较匮乏,故R哥撰写本书以补空白,帮助国内 Java 开发者能够正确的学习、理解和使用最新的 Spring Boot 技术。

R哥从 2017 年开始,陆续写了一些 Spring Boot 框架的博客教程,本以为出版此书会相当顺利,结果远没有想象中那么简单。写博客没有什么太重的思想负担,所以写得比较随意、零乱,也不成体系,而要出版成体系的书籍还相差甚远,而且博客部分内容已经过时,所以仅能作为一个基石。

为了写成此书,R哥又花费了近一年的巨量时间,以 Spring Boot 3.0 里程碑版本作为出发点,在其基础上再进行深度打磨、加强和完善,并新增了 90% 以上的新内容,正式版本发布后再核对修正,最后形成了本书的大纲和学习体系。

本书目录

本书一共为 12 章,这是一个由浅入深、循序渐进的学习过程。

第 1 章为 Spring Boot 基础入门,主要介绍 Spring Boot 的基础知识,包括背景、介绍、核心特性、核心模块、核心思想、版本选择,以及 Maven、Spring Boot CLI 等相关工具使用,还会带来简单的应用案例。

第 2 章为 Spring Boot 配置管理,主要介绍 Spring Boot 应用中的各种配置技巧,包括配置类、配置文件、外部化配置、配置绑定、随机配置、导入配置、多文档配置、Profiles 配置、加载机制、配置加密、配置迁移,有助于后续章节的的学习融会贯通。

第 3 章为 Spring Boot Starters 与自动配置,主要介绍 Spring Boot Starter 的全方面介绍和应用,包括命名规范、分类、自动配置原理及扩展机制,还会带来邮件 Starter 的介绍和应用,以及如何自定义一个 Spring Boot Starter。

第 4 章为 Spring Boot 启动过程与扩展应用,主要介绍 Spring Boot 的启动过程,包括引导方式、启动入口方法、启动流程源码分析,以及启动过程中的丰富的扩展应用,包括启动日志、启动图案、启动失败分析、启动事件和监听器、全局懒加载、启动运行器等等。

第 5 章为 Spring Boot 日志管理,主要介绍 Spring Boot 日志的使用,包括日志格式、日志文件、日志级别、日志分组、日志归档、自定义日志配置文件、切换日志框架、输出彩色日志等等。

第 6 章为 Spring Boot Web 核心应用,这是 Spring Boot 项目最基本最核心的部分,包括嵌入式容器、Web 组件的注册、静态资源处理、模板引擎、异常处理、参数效验、国际化、跨域、分布式会话、安全性、REST 服务调用等常用的 Web 技术的原理介绍、集成与应用。

第 7 章为 Spring Boot 数据访问,主要介绍 Spring Boot 与关系、NoSQL数据库的集成应用,包括数据源、连接池、事务管理、Spring Data JPA、MyBatis、MyBatis-Plus、Redis、MongoDB、Elasticsearch 的原理介绍、集成与应用。

第 8 章为 Spring Boot 计划任务,主要介绍划计划任务在 Spring Boot 中的应用,包括 Spring 中的计划任务和 Quartz 计划任务的原理介绍、集成与应用。

第 9 章为 Spring Boot 缓存与消息队列,主要介绍缓存和消息队列在 Spring Boot 的应用,缓存原理机制介绍及 Redis 缓存的实现与集成,消息队列原理介绍及 ActiveMQ、RabbitMQ、Kafka 的原理介绍、集成与应用。

第 10 章为 Spring Boot 调试与单元测试,主要介绍 Spring Boot 应用的调试方法、开发者工具的详细介绍与使用,以及如何在 Spring Boot 应用中做单元测试。

第 11 章为 Spring Boot 打包与部署,主要介绍 Spring Boot 应用的打包方式,以及如何将 Spring Boot 应用包以不同的方式运行、以不同的方式部署到 Linux 和 Docker 容器等,包括 Spring Boot 3.0 支持的构建 GraalVM 原生镜像应用方式。

第 12 章为 Spring Boot 监控与报警,主要介绍 Spring Boot Actuator 监控模块的详细介绍和使用,包括端点、指标,以及 Spring Boot 3.0 支持的可观测性技术的介绍与应用,还包括第三方监控平台 Spring Boot Admin、Prometheus + Grafana 的集成与应用。

本书特色

本书以最新的 Spring Boot 3.0 版本为出发点,解读 Spring Boot 最核心的技术,包括最新的核心知识点介绍、技术原理、应用方式、与第三方主流技术集成的应用等,再到服务的测试、调试、部署和监控等,一条龙学习和掌握最新的 Spring Boot 核心技术及应用实战。

本书具有以下几点特色。

1. 全面

本书全面介绍了 Spring Boot 框架,覆盖了 Spring Boot 所有主流知识点,包括基础入门、配置管理、Starters 与自动配置、启动过程及扩展应用、Web 核心应用、Logging 日志、关系数据库、NoSQL、计划任务、缓存、消息队列、调试、单元测试、打包与部署、监控与报警等 12 章核心内容。

2. 全新

本书介绍和实战使用的是 Spring Boot 最新主版本 3.0.0,书中带有 "Spring Boot 3.0 新变化"、"Spring Boot 3.0+" 等标识,方便读者可以对 Spring Boot 3.0 新特性和变更项有一个更醒目的认识,读者从 Spring Boot 2.x 迁移到 3.x 也可以有一个对比参考。

3. 实用

本书不但介绍了 Spring Boot 的理论知识,还提供了大量的底层原理分析,并为核心知识点、第三方主流技术的集成与应用提供了大量实战案例,理论和实践相结合,清晰易懂,使读者可以更好的吸收和理解。

4. 权威

本书以 Spring Boot 官方文档和 Spring Boot 框架源码分析作为主要参考依据,包括里程碑版本和正式版本的文档、框架源码,然后再再辅以知识点实践和验证,以确保知识点的权威性和正确性。

本书实拍图

本书核心内容使用了养眼的绿色文字和底色,学习的同时,还能护眼,不会容易累,阅读体验极佳,这也是本书成本高的主要原因之一。

R哥前几天提前拿到样书了,下面给大家展示几张实拍图,

本书汇聚了作者多年经验,都是满满的干货,在学习 Spring Boot 的同时,还能学习到其他 Java 核心技术,并且提供所有实战源代码,建议人手一本。

感谢各位专家的推荐,排名不分先后!

首发半价优惠

本书定价 158 元,今天刚上市搞 5 折促销,80 元不到上车,这可能是全网最便宜的时候了,机会难得,想拥抱 Spring Boot 3.0 的不要错过。

最后,感谢大家的支持!

PS:最近恰逢 423 世界图书日,错过这波优惠,后续还想要 5 折优惠,就不知道要什么时候了,因为身为作者的我也没有控价权,所以想学习要趁早,在 423 世界图书日之前入手一本大家一起学习进阶吧!

近期热文推荐:

1.1,000+ 道 Java面试题及答案整理(2022最新版)

2.劲爆!Java 协程要来了。。。

3.Spring Boot 2.x 教程,太全了!

4.别再写满屏的爆爆爆炸类了,试试装饰器模式,这才是优雅的方式!!

5.《Java开发手册(嵩山版)》最新发布,速速下载!

觉得不错,别忘了随手点赞+转发哦!

曹工说Spring Boot源码(18)-- Spring AOP源码分析三部曲,终于快讲完了 (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 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 3.0 写成书了!的主要内容,如果未能解决你的问题,请参考以下文章

spring boot实战(第一篇)第一个案例

Spring Boot 2.5 终于对数据源动刀了!

Spring Boot 学习总结(32)—— Spring Boot 3.0 正式发布

Spring Boot 学习总结(32)—— Spring Boot 3.0 正式发布

Spring Boot 学习总结(32)—— Spring Boot 3.0 正式发布

Spring Boot 3.0