Spring框架相关知识
spring系列包含非常多的项目,可以满足java开发中的方方面面。
【20200125】SpringBoot   面试题及基础知识 - 图1【20200125】SpringBoot   面试题及基础知识 - 图2
一、常用的5个spring框架
1.spring framework
也就是我们经常说的spring框架,包括了ioc依赖注入,Context上下文、bean管理、springmvc等众多功能模块,其它spring项目比如spring boot也会依赖spring框架。
2.spring boot
它的目标是简化Spring应用和服务的创建、开发与部署,简化了配置文件,使用嵌入式web服务器,含有诸多开箱即用的微服务功能,可以和spring cloud联合部署。
Spring Boot的核心思想是约定大于配置,应用只需要很少的配置即可,简化了应用开发模式。
3.Spring Data
是一个数据访问及操作的工具集,封装了多种数据源的操作能力,包括:jdbc、Redis、MongoDB等。
4.Spring Cloud
是一套完整的微服务解决方案,是一系列不同功能的微服务框架的集合。Spring Cloud基于Spring Boot,简化了分布式系统的开发,集成了服务发现、配置管理、消息总线、负载均衡、断路器、数据监控等各种服务治理能力。比如sleuth提供了全链路追踪能力,Netflix套件提供了hystrix熔断器、zuul网关等众多的治理组件。config组件提供了动态配置能力,bus组件支持使用RabbitMQ、kafka、Activemq等消息队列,实现分布式服务之间的事件通信。
▌5. Spring Security
主要用于快速构建安全的应用程序和服务,在Spring Boot和Spring Security OAuth2的基础上,可以快速实现常见安全模型,如单点登录,令牌中继和令牌交换。你可以了解一下oauth2授权机制和jwt认证方式。oauth2是一种授权机制,规定了完备的授权、认证流程。JWT全称是JSON Web Token,是一种把认证信息包含在token中的认证实现,oauth2授权机制中就可以应用jwt来作为认证的具体实现方法。
二、Struts的具体作用
struts是曾经非常火爆的web组合ssh中的控制层。我们知道web服务一般都采用MVC分层模型构建,就是model层负责内部数据模型,controller负责请求的分发控制,view层负责返回给用户展示的视图。struts实现的就是其中控制层的角色。
Struts采用Filter实现,针对类进行拦截,每次请求就会创建一个Action。使用struts的SSH组合已经逐渐被使用springMVC的SSM组合代替,也就是Spring-MVC+Spring+MyBatis的组合,一方面原因是由于struts对几次安全漏洞的处理,让大家对struts的信心受到影响;另一方面,springmvc更加的灵活,不需要额外配置,不存在和spring整合等问题,使用更加方便,所以建议以SSM框架的学习为主。
三、ORM框架
ORM就是对象关系匹配,是为了解决面向对象与关系数据库存在的互不匹配的问题。简单来说,就是把关系数据库中的数据转换成面向对象程序中的对象。
常用的ORM框架有Hibernate和MyBatis,也就是ssh组合和ssm组合中的h与m。
它们的特点和区别如下:
Hibernate对数据库结构提供了完整的封装,实现了POJO对象与数据库表之间的映射,能够自动生成并执行SQL语句。只要定义了POJO 到数据库表的映射关系,就可以通过Hibernate提供的方法完成数据库操作。Hibernate符合JPA规范,就是Java持久层API。
mybatis通过映射配置文件,将SQL所需的参数和返回的结果字段映射到指定对象,mybatis不会自动生成sql,需要自己定义sql语句,不过更方便对sql语句进行优化。
总结起来: 1.hibernate配置要比mybatis复杂的多,学习成本也比mybatis高。mybatis,简单、高效、灵活,但是需要自己维护sql;2.hibernate功能强大、全自动、适配不同数据库,但是非常复杂,灵活性稍差。 四、Netty简介
Netty是一个高性能的异步事件驱动的网络通信框架,Netty对JDK原生NIO进行封装,简化了网络服务的开发。下文会详细讲解
另外,同类型的框架还有mina、grizzly,不过目前使用的相对较少,一般不会在面试中出现,可以作为兴趣简单了解。
五、RPC服务
Motan、Dubbo、gRPC都是比较常用的高性能rpc框架,可以提供完善的服务治理能力,java版本的通信层都是基于前面提到的Netty实现。它们的特点稍后介绍。
六、其他常用框架
jersy和restEasy都是可以快速开发restful服务的框架。
和springmvc相比,这两个框架都是基于jax-rs标准,而springmvcs基于servlet,使用自己构建的API,是两个不同的标准。
shiro框架是一个与spring security类似的开源的权限管理框架,用于访问授权、认证、加密及会话管理。能够支持单机与分布式session管理。
相比security,shiro更加简单易用。
接下来看看一些知识点
一、spring基本概念
【20200125】SpringBoot   面试题及基础知识 - 图3【20200125】SpringBoot   面试题及基础知识 - 图4Sping中的基本概念
涉及的流程与实现默认都是基于最新的5.x版本。
spring中的几个重要概念如下:
1.IOC
IOC,就是控制反转,如最左边,拿公司招聘岗位来举例:
假设一个公司有产品、研发、测试等岗位。如果是公司根据岗位要求,逐个安排人选,如图中向下的箭头,这是正向流程。如果反过来,不用公司来安排候选人,而是由第三方猎头来匹配岗位和候选人,然后进行推荐,如图中向上的箭头,这就是控制反转。
在spring中,对象的属性是由对象自己创建的,就是正向流程;如果属性不是对象创建,而是由spring来自动进行装配,就是控制反转。这里的DI也就是依赖注入,就是实现控制反转的方式。正向流程导致了对象于对象之间的高耦合,IOC可以解决对象耦合的问题,有利于功能的复用,能够使程序的结构变得非常灵活。
2.context上下文和bean
spring进行IOC实现时使用的有两个概念:context上下文和bean。
如中间图所示,所有被spring管理的、由spring创建的、用于依赖注入的对象,就叫做一个bean。Spring创建并完成依赖注入后,所有bean统一放在一个叫做context的上下文中进行管理。
3.AOP
AOP就是面向切面编程。如右面的图,一般程序执行流程是从controller层调用service层、然后service层调用DAO层访问数据,最后在逐层返回结果。
这个是图中向下箭头所示的按程序执行顺序的纵向处理。但是,一个系统中会有多个不同的服务,例如用户服务、商品信息服务等等,每个服务的controller层都需要验证参数,都需要处理异常,如果按照图中红色的部分,对不同服务的纵向处理流程进行横切,在每个切面上完成通用的功能,例如身份认证、验证参数、处理异常等等、这样就不用在每个服务中都写相同的逻辑了,这就是AOP思想解决的问题。
AOP以功能进行划分,对服务顺序执行流程中的不同位置进行横切,完成各服务共同需要实现的功能。
二、spring框架
【20200125】SpringBoot   面试题及基础知识 - 图5【20200125】SpringBoot   面试题及基础知识 - 图6Spring框架组件
上图列出了spring框架主要包含的组件。这张图来自spring4.x的文档。目前最新的5.x版本中右面的portlet组件已经被废弃掉,同时增加了用于异步响应式处理的WebFlux组件。
并不需要对所有的组件都详细了解,只需重点了解最常用的几个组件实现,以及知道每个组件用来实现哪一类功能。
图中红框是比较重要的组件,core组件是spring所有组件的核心;bean组件和context组件我刚才提到了,是实现IOC和依赖注入的基础;AOP组件用来实现面向切面编程;web组件包括springmvc是web服务的控制层实现。
三、spring中机制和实现
【20200125】SpringBoot   面试题及基础知识 - 图7【20200125】SpringBoot   面试题及基础知识 - 图8
1.AOP
AOP的实现是通过代理模式,在调用对象的某个方法时,执行插入的切面逻辑。实现的方式有动态代理也叫运行时增强,比如jdk代理、CGLIB;静态代理是在编译时进行织入或类加载时进行织入,比如AspectJ。
关于AOP还需要了解一下对应的Aspect、pointcut、advice等注解和具体使用方式。
2.placeHolder动态替换
主要需要了解替换发生的时间,是在bean definition创建完成后,bean初始化之前,是通过实现BeanFactoryPostProcessor接口实现的。主要实现方式有PropertyPlaceholderConfigurer和PropertySourcesPlaceholderConfigurer。这两个类实现逻辑不一样,spring boot使用PropertySourcesPlaceholderConfigurer实现。
3.事务
需要了解spring 中对事务规定的隔离类型和事务传播类型。要知道事务的隔离级别是由具体的数据库来实现的,在数据库部分我会详细介绍。
事务的传播类型,可以重点了解最常用的REQUIRED和SUPPORTS类型。
4.核心接口类

  • ApplicationContext保存了ioc的整个应用上下文,可以通过其中的beanfactory获取到任意到bean;
  • BeanFactory主要的作用是根据bean definition来创建具体的bean;
  • BeanWrapper是对Bean的包装,一般情况下是在spring ioc内部使用,提供了访问bean的属性值、属性编辑器注册、类型转换等功能,方便ioc容器用统一的方式来访问bean的属性;
  • FactoryBean通过getObject方法返回实际的bean对象,例如motan框架中referer对service的动态代理就是通过FactoryBean来实现的。

5.Scope
bean的scope是指bean的作用域,默认情况下是单例模式,这也是使用最多的一种方式;多例模式,即每次从beanFactory中获取bean都会创建一个新的bean。
request、session、global-session是在web服务中使用的scope,request每次请求都创建一个实例,session是在一个会话周期内保证只有一个实例。
global-session在5.x版本中已经不在使用,同时增加了Application和Websocket两种scope,分别保证在一个ServletContext与一个WebSocket中只创建一个实例。
6.事件机制
spring的事件机制需要知道spring定义的五种标准事件,具体事件可见上图,了解如何自定义事件和实现对应的applicationListener来处理自定义事件。
三、spring应用相关
【20200125】SpringBoot   面试题及基础知识 - 图9【20200125】SpringBoot   面试题及基础知识 - 图10
1.常用注释
a.类型类注释:
类型类注释包括controller、service等,需要重点了解
其中component和bean注解的区别如下:

  • @Component注解在类上使用表明这个类是个组件类,需要Spring为这个类创建bean。
  • @Bean注解使用在方法上,告诉Spring这个方法将会返回一个Bean对象,需要把返回的对象注册到Spring的应用上下文中。

b.设置类注解
重点了解@Autowire和@Qualifier以及bytype、byname等不同的自动装配机制。
c.web类注解
主要以了解为主,关注@RequestMapping、@GetMapping、@PostMapping等路径匹配注解,以及@PathVariable、@RequestParam 等参数获取注解。
d.功能类注解
包括@ImportResource引用配置、@ComponentScan注解自动扫描、@Transactional事务注解等等,这里不一一介绍了。
2.配置方式
需要了解配置spring的几种方式,xml文件配置、注解配置和使用api进行配置。
自动装配机制需要了解按类型匹配进行自动装配,按bean名称进行自动装配,构造器中的自动装配和自动检测等主要的四种方式。
还需要了解一下list、set、map等集合类属性的配置方式以及内部bean的使用。
四、Spring的Context的初始化流程
【20200125】SpringBoot   面试题及基础知识 - 图11【20200125】SpringBoot   面试题及基础知识 - 图12Spring Context初始化流程
图中左上角是三种类型的context,xml配置方式的context、springboot的context和web服务的context。不论哪种context,创建后都会调用到AbstractApplicationContext类的refresh方法,这个方法是我们要重点分析的。
refresh方法中,操作共分13步:
第1步:对刷新进行准备,包括设置开始时间、设置激活状态、初始化context环境中的占位符,这个动作根据子类的需求由子类来执行,然后验证是否缺失必要的properties;
第2步:刷新并获得内部的bean factory;
第3步:对bean factory进行准备工作,比如设置类加载器和后置处理器、配置不进行自动装配的类型、注册默认的环境bean;
第4步:为context的子类提供后置处理bean factory的扩展能力。如果子类想在bean定义加载完成后,开始初始化上下文之前做一些特殊逻辑,可以复写这个方法;
第5步,执行context中注册的bean factory后缀处理器;
注:这里有两种后置处理器,一种是可以注册bean的后缀处理器,另一种是针对bean factory进行处理的后置处理器。执行的顺序是,先按优先级执行可注册bean的处理器,在按优先级执行针对beanfactory的处理器。
对springboot来说,这一步会进行注解bean definition的解析。流程如右面小框中所示,由ConfigurationClassPostProcessor触发、由ClassPathBeanDefinitionScanner解析并注册到bean factory。
第6步:按优先级顺序在beanfactory中注册bean的后缀处理器,bean后置处理器可以在bean初始化前、后执行处理;
第7步:初始化消息源,消息源用来支持消息的国际化;
第8步:初始化应用事件广播器。事件广播器用来向applicationListener通知各种应用产生的事件,是一个标准的观察者模式;
第9步,是留给子类的扩展步骤,用来让特定的context子类初始化其他的bean;
第10步,把实现了ApplicationListener的bean注册到事件广播器,并对广播器中的早期未广播事件进行通知;
第11步,冻结所有bean描述信息的修改,实例化非延迟加载的单例bean;
第12步,完成上下文的刷新工作,调用LifecycleProcessor的onFresh()方法以及发布ContextRefreshedEvent事件;
第13步:在finally中,执行第十三步,重置公共的缓存,比如ReflectionUtils中的缓存、AnnotationUtils中的缓存等等;
至此,spring的context初始化完成。这里仅介绍了最主要的主流程,建议课后阅读源码来复习这个知识点,补全细节。
五:Spring中bean的生命周期
【20200125】SpringBoot   面试题及基础知识 - 图13【20200125】SpringBoot   面试题及基础知识 - 图14Spring中bean的生命周期
面试中经常问到的bean的生命周期,先看绿色的部分,bean的创建过程:
第1步:调用bean的构造方法创建bean;
第2步:通过反射调用setter方法进行属性的依赖注入;
第3步:如果实现BeanNameAware接口的话,会设置bean的name;
第4步:如果实现了BeanFactoryAware,会把bean factory设置给bean;
第5步:如果实现了ApplicationContextAware,会给bean设置ApplictionContext;
第6步:如果实现了BeanPostProcessor接口,则执行前置处理方法;
第7步:实现了InitializingBean接口的话,执行afterPropertiesSet方法;
第8步:执行自定义的init方法;
第9步:执行BeanPostProcessor接口的后置处理方法。
这时,就完成了bean的创建过程。
在使用完bean需要销毁时,会先执行DisposableBean接口的destroy方法,然后在执行自定义的destroy方法。
这部分也建议阅读源码加深理解。
六:Spring扩展接口
【20200125】SpringBoot   面试题及基础知识 - 图15【20200125】SpringBoot   面试题及基础知识 - 图16Spring扩展接口
对spring进行定制化功能扩展时,可以选择如下一些扩展点:
1.BeanFactoryPostProcessor
是beanFactory后置处理器,支持在bean factory标准初始化完成后,对bean factory进行一些额外处理。在讲context初始化流程时介绍过,这时所有的bean的描述信息已经加载完毕,但是还没有进行bean初始化。例如前面提到的PropertyPlaceholderConfigurer,就是在这个扩展点上对bean属性中的占位符进行替换。
2.BeanDefinitionRegistryPostProcessor
它扩展自BeanFactoryPostProcessor,在执行BeanFactoryPostProcessor的功能前,提供了可以添加bean definition的能力,允许在初始化一般bean前,注册额外的bean。例如可以在这里根据bean的scope创建一个新的代理bean。
3.BeanPostProcessor
提供了在bean初始化之前和之后插入自定义逻辑的能力。与BeanFactoryPostProcessor的区别是处理的对象不同,BeanFactoryPostProcessor是对beanfactory进行处理,BeanPostProcessor是对bean进行处理。
注:上面这三个扩展点,可以通过实现Ordered和PriorityOrdered接口来指定执行顺序。实现PriorityOrdered接口的processor会先于实现Ordered接口的执行。
4.ApplicationContextAware
可以获得ApplicationContext及其中的bean,当需要在代码中动态获取bean时,可以通过实现这个接口来实现。
5.InitializingBean
可以在bean初始化完成,所有属性设置完成后执行特定逻辑,例如对自动装配对属性进行验证等等。
6.DisposableBean
用于在bean被销毁前执行特定的逻辑,例如做一些回收工作等。
7.ApplicationListener
用来监听spring的标准应用事件或者自定义事件。
来源:https://zhuanlan.zhihu.com/p/59327709
编辑于 2019-06-11
赞同 949
26 条评论
分享
收藏喜欢收起

更多回答

【20200125】SpringBoot   面试题及基础知识 - 图17
bravo1988
专栏有零基础Java、日语、英语学习路线
1,695 人赞同了该回答
最近看了点Spring的源码,正好来稍微扯一扯,帮一部分培训班的朋友撕开一道口子,透透气。我自己都是看的培训班视频,所以也算培训班出身吧。所以下文开口闭口“培训班”,不要觉得是我在贬低培训班,完全没有。
(默认题主说的Spring是Spring framework,而不是Spring家族…)
主要内容:

  • 盲点
  • Spring说,万物皆可定义
  • 默默付出的后置处理器
  • 利用后置处理器返回代理对象

盲点

如果你恰好非科班转行且从未独立看过源码(和我一样),那么你很可能至今都不曾注意某两个概念。
你以为我会说IOC和AOP?No。
看到这里,一部分读者心里一惊:卧槽,说的啥玩意,Spring不就IOC和AOP吗?!这两个都不说,你这篇文章为啥能写这么长?
不错,我就是这么长。其实我要讲的是:

  • BeanDefinition
  • BeanPostProcessor

大部分人一听到“请你谈谈对Spring的理解”,就会下意识搬出IOC和AOP两座大山,赶紧糊弄过去。大概是这样的:
IOC
所谓的控制反转。通俗地讲,就是把原本需要程序员自己创建和维护的一大堆bean统统交由Spring管理。
【20200125】SpringBoot   面试题及基础知识 - 图18【20200125】SpringBoot   面试题及基础知识 - 图19各位将就看一下,此乃在下早期画作,色彩运用有点泛滥…
也就是说,Spring将我们从盘根错节的依赖关系中解放了。当前对象如果需要依赖另一个对象,只要打一个@Autowired注解,Spring就会自动帮你安装上。
【20200125】SpringBoot   面试题及基础知识 - 图20【20200125】SpringBoot   面试题及基础知识 - 图21

AOP
所谓的面向切面编程。通俗地讲,它一般被用来解决一些系统交叉业务的织入,比如日志啦、事务啥的。打个比方,UserService的method1可能要打印日志,BrandService的method2可能也需要。亦即:一个交叉业务就是要切入系统的一个方面。具体用代码展示就是:
【20200125】SpringBoot   面试题及基础知识 - 图22【20200125】SpringBoot   面试题及基础知识 - 图23这个切面,可以是日志,也可以是事务
交叉业务的编程问题即为面向切面编程。AOP的目标就是使交叉业务模块化。做法是将切面代码移动到原始方法的周围:
【20200125】SpringBoot   面试题及基础知识 - 图24【20200125】SpringBoot   面试题及基础知识 - 图25
原先不用AOP时(图一),交叉业务的代码直接硬编码在方法内部的前后,而AOP则是把交叉业务写在方法调用前后。那么,为什么AOP不把代码也写在方法内部的前后呢?两点原因:

  • 首先,这与AOP的底层实现方式有关:动态代理其实就是代理对象调用目标对象的同名方法,并在调用前后加增强代码。

【20200125】SpringBoot   面试题及基础知识 - 图26【20200125】SpringBoot   面试题及基础知识 - 图27

  • 其次,这两种最终运行效果是一样的,所以没什么好纠结的。

而所谓的模块化,我个人的理解是将切面代码做成一个可管理的状态。比如日志打印,不再是直接硬编码在方法中的零散语句,而是做成一个切面类,通过通知方法去执行切面代码。
我相信大部分培训班出来的朋友也就言尽于此,讲完上面内容就准备收拾打卡下班了。
怎么说呢,IOC按上面的解释,虽然很浅,但也马马虎虎吧。然而AOP,很多人对它的认识是非常片面的…
这样吧,我问你一个问题,现在我自己写了一个UserController,以及UserServiceImpl implements UserService,并且在UserController中注入Service层对象:

  1. @Autowired
  2. private UserService userService;

那么,这个userService一定是我们写的UserServiceImpl的实例吗?
如果你听不懂我要问什么,说明你对Spring的AOP理解还是太少了。
实际上,Spring依赖注入的对象并不一定是我们自己写的类的实例,也可能是userServiceImpl的代理对象。下面分别演示这两种情况:

  • 注入userServiceImpl对象

【20200125】SpringBoot   面试题及基础知识 - 图28【20200125】SpringBoot   面试题及基础知识 - 图29注入的是UserServiceImpl类型

  • 注入userServiceImpl的代理对象(CGLib动态代理)

【20200125】SpringBoot   面试题及基础知识 - 图30【20200125】SpringBoot   面试题及基础知识 - 图31注入的是CGLib动态代理生成的userServiceImpl的代理对象
为什么两次注入的对象不同?
因为第二次我给UserServiceImpl加了@Transactional 注解。
【20200125】SpringBoot   面试题及基础知识 - 图32【20200125】SpringBoot   面试题及基础知识 - 图33
此时Spring读取到这个注解,便知道我们要使用事务。而我们编写的UserService类中并没有包含任何事务相关的代码。如果给你,你会怎么做?动态代理嘛!
看到这里,我仿佛听到有一部分兄弟默默说了句:卧槽…
但是,上面对IOC和AOP的理解,也仅仅是应用级别,是一个面。仅仅了解到这个程度,对Spring的了解还是非常扁平的,不够立体。


Spring说,万物皆可定义

上帝说,要有光。于是特斯拉搞出了交流电。
Java说,万物皆对象。但是Spring另外搞了BeanDefinition…
什么BeanDefinition呢?其实它是bean定义的一个顶级接口:
【20200125】SpringBoot   面试题及基础知识 - 图34【20200125】SpringBoot   面试题及基础知识 - 图35正如BeanDefinition的类注释所言:一个BeanDefinition是用来描述一个bean实例的
哎呀卧槽,啥玩意啊。描述一个bean实例?我咋想起了Class类呢。
其实,两者并没有矛盾。
【20200125】SpringBoot   面试题及基础知识 - 图36【20200125】SpringBoot   面试题及基础知识 - 图37
Class只是描述了一个类有哪些字段、方法,但是无法描述如何实例化这个bean!如果说,Class类描述了一块猪肉,那么BeanDefinition就是描述如何做红烧肉:

  • 单例吗?
  • 是否需要延迟加载?
  • 需要调用哪个初始化方法/销毁方法?

大部分初学者以为Spring解析或者@Bean后,就直接搞了一个bean存到一个大Map中,其实并不是。

  • Spring首先会扫描解析指定位置的所有的类得到Resources(可以理解为.Class文件)
  • 然后依照TypeFilter和@Conditional注解决定是否将这个类解析为BeanDefinition
  • 稍后再把一个个BeanDefinition取出实例化成Bean

就好比什么呢?你从海里吊了一条鱼,但是你还没想好清蒸还是红烧,那就干脆先晒成鱼干吧。一条咸鱼,其实蕴藏着无线可能,因为它可能会翻身!


默默付出的后置处理器

接下来,我们讨论一下咸鱼如何翻身。
最典型的例子就是AOP。上面AOP的例子中我说过了,如果不加@Transactional,那么Controller层注入的就是普通的userServiceImpl,而加了以后返回的实际是代理对象。
为什么要返回代理对象?因为我们压根就没在UserServiceImpl中写任何commit或者rollback等事务相关的代码,但是此时此刻代理对象却能完成事务操作。毫无疑问,这个代理对象已经被Spring加了佐料。
那么Spring是何时何地加佐料的呢?说来话长。
大部分人把Spring比作容器,其实潜意识里是将Spring完全等同于一个Map了。其实,真正存单例对象的map,只是Spring中很小很小的一部分,仅仅是BeanFactory的一个字段,我更习惯称它为“单例池”。

  1. /** Cache of singleton objects: bean name --> bean instance */
  2. private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256);

【20200125】SpringBoot   面试题及基础知识 - 图38【20200125】SpringBoot   面试题及基础知识 - 图39
这里的ApplicationContext和BeanFactory是接口,实际上都有各自的子类。比如注解驱动开发时,Spring中最关键的就是AnnotationConfigApplicationContext和DefaultListableBeanFactory。
所以,很多人把Spring理解成一个大Map,还是太浅了。就拿ApplicationContext来讲,它也实现了BeanFactory接口,但是作为容器,其实它是用来包含各种各样的组件的,而不是存bean:
【20200125】SpringBoot   面试题及基础知识 - 图40【20200125】SpringBoot   面试题及基础知识 - 图41
那么,Spring是如何给咸鱼加佐料(事务代码的织入)的呢?关键就在于后置处理器。
后置处理器其实可以分好多种,属于Spring的扩展点之一。我从最高赞那边抄了一幅图,请不要告诉它:
【20200125】SpringBoot   面试题及基础知识 - 图42【20200125】SpringBoot   面试题及基础知识 - 图43引用:https://www.zhihu.com/question/48427693/answer/691483076
上面BeanFactory、BeanDefinitionRegistryPostProcessor、BeanPostProcessor都算是后置处理器,这里篇幅有限,只介绍一下BeanPostProcessor。
【20200125】SpringBoot   面试题及基础知识 - 图44【20200125】SpringBoot   面试题及基础知识 - 图45
BeanFactoryPostProcessor是用来干预BeanFactory创建的,而BeanPostProcessor是用来干预Bean的实例化。不知道大家有没有试过在普通Bean中注入ApplicationContext实例?你第一时间想到的是:

  1. @Autowired
  2. ApplicationContext annotationConfigApplicationContext;

除了利用Spring本身的IOC容器自动注入以外,你还有别的办法吗?
我们可以让Bean实现ApplicationContextAware接口:
【20200125】SpringBoot   面试题及基础知识 - 图46【20200125】SpringBoot   面试题及基础知识 - 图47
后期,Spring会调用setApplicationContext()方法传入ApplicationContext实例。 Spring官方文档:一般来说,您应该避免使用它,因为它将代码耦合到Spring中,并且不遵循控制反转样式。 这是我认为Spring最牛逼的地方:代码具有高度的可扩展性,甚至你自己都懵逼,为什么实现了一个接口,这个方法就被莫名其妙调用,还传进了一个对象…
这其实就是后置处理器的工作!
什么意思呢?
就是说啊,明面上我们看得见的地方只要实现一个接口,但是背地里Spring在自己框架的某一处搞了个for循环,遍历所有的BeanPostProcessor,其中就包括处理实现了ApplicationContextAware接口的bean的后置处理器:ApplicationContextAwareProcessor。
上面这句话有点绕,大家停下来多想几遍。
【20200125】SpringBoot   面试题及基础知识 - 图48【20200125】SpringBoot   面试题及基础知识 - 图49
也就是说,要扩展的类是不确定的,但是处理扩展类的流程是写死的。总有一个要定下来吧。也就是说,在这个Bean实例化的某一紧要处,必然要经过很多BeanPostProcessor。但是,BeanPostProcessor也不是谁都处理,有时也会做判断。比如:

  1. if (bean instanceof Aware) {
  2. if (bean instanceof EnvironmentAware) {
  3. ((EnvironmentAware) bean).setEnvironment(this.applicationContext.getEnvironment());
  4. }
  5. if (bean instanceof EmbeddedValueResolverAware) {
  6. ((EmbeddedValueResolverAware) bean).setEmbeddedValueResolver(this.embeddedValueResolver);
  7. }
  8. if (bean instanceof ResourceLoaderAware) {
  9. ((ResourceLoaderAware) bean).setResourceLoader(this.applicationContext);
  10. }
  11. if (bean instanceof ApplicationEventPublisherAware) {
  12. ((ApplicationEventPublisherAware) bean).setApplicationEventPublisher(this.applicationContext);
  13. }
  14. if (bean instanceof MessageSourceAware) {
  15. ((MessageSourceAware) bean).setMessageSource(this.applicationContext);
  16. }
  17. if (bean instanceof ApplicationContextAware) {
  18. ((ApplicationContextAware) bean).setApplicationContext(this.applicationContext);
  19. }
  20. }

所以,此时此刻一个类实现ApplicationContextAware接口,有两层含义:

  • 作为后置处理器的判断依据,只有你实现了该接口我才处理你
  • 提供被后置处理器调用的方法

【20200125】SpringBoot   面试题及基础知识 - 图50【20200125】SpringBoot   面试题及基础知识 - 图51


利用后置处理器返回代理对象

大致了解Spring Bean的创建流程后,接下来我们尝试着用BeanPostProcessor返回当前Bean的代理对象。
pom.xml

  1. <dependencies>
  2. <dependency>
  3. <groupId>org.springframework</groupId>
  4. <artifactId>spring-context</artifactId>
  5. <version>4.3.12.RELEASE</version>
  6. </dependency>
  7. </dependencies>

AppConfig

  1. @Configuration //JavaConfig方式,即当前配置类相当于一个applicationConotext.xml文件
  2. @ComponentScan //默认扫描当前配置类(AppConfig)所在包及其子包
  3. public class AppConfig {
  4. }

Calculator

  1. public interface Calculator {
  2. public void add(int a, int b);
  3. }

CalCulatorImpl

  1. @Component
  2. public class CalculatorImpl implements Calculator {
  3. public void add(int a, int b) {
  4. System.out.println(a+b);
  5. }
  6. }

后置处理器MyAspectJAutoProxyCreator
使用步骤:

  1. 实现BeanPostProcessor
  2. @Component加入Spring容器

    1. @Component
    2. public class MyAspectJAutoProxyCreator implements BeanPostProcessor {
    3. public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
    4. return bean;
    5. }
    6. public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    7. final Object obj = bean;
    8. //如果当前经过BeanPostProcessors的Bean是Calculator类型,我们就返回它的代理对象
    9. if (bean instanceof Calculator) {
    10. Object proxyObj = Proxy.newProxyInstance(
    11. this.getClass().getClassLoader(),
    12. bean.getClass().getInterfaces(),
    13. new InvocationHandler() {
    14. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    15. System.out.println("开始计算....");
    16. Object result = method.invoke(obj, args);
    17. System.out.println("结束计算...");
    18. return result;
    19. }
    20. }
    21. );
    22. return proxyObj;
    23. }
    24. //否则返回本身
    25. return obj;
    26. }
    27. }

    测试类

    1. public class TestPostProcessor {
    2. public static void main(String[] args) {
    3. System.out.println("容器启动成功!");
    4. AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
    5. String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
    6. //打印当前容器所有BeanDefinition
    7. for (String beanDefinitionName : beanDefinitionNames) {
    8. System.out.println(beanDefinitionName);
    9. }
    10. System.out.println("============");
    11. //取出Calculator类型的实例,调用add方法
    12. Calculator calculator = (Calculator) applicationContext.getBean(Calculator.class);
    13. calculator.add(1, 2);
    14. }

    先把MyAspectJAutoProxyCreator的@Component注释掉,此时Spring中没有我们自定义的后置处理器,那么返回的就是CalculatorImpl:
    【20200125】SpringBoot   面试题及基础知识 - 图52【20200125】SpringBoot   面试题及基础知识 - 图53
    把@Component加上,此时MyAspectJAutoProxyCreator加入到Spring的BeanPostProcessors中,会拦截到CalculatorImpl,并返回代理对象:
    【20200125】SpringBoot   面试题及基础知识 - 图54【20200125】SpringBoot   面试题及基础知识 - 图55代理对象的add()方法被增强:前后打印日志