深入理解 Spring
以下所有内容并不是深入 spring 源码,而是理解 spring 的运行机制,大致结构,设计思想等
本文参考自【黑马程序员】spring高级49讲教程整理而来,教程链接 黑马程序员Spring视频教程,深度讲解spring5底层原理_哔哩哔哩_bilibili
容器接口
BeanFactory & ApplicationContext
org.springframework.beans.factory.BeanFactory
和 org.springframework.context.ApplicationContext
都是 Spring 中最核心的接口
其中BeanFactory
是用于访问 spring bean 容器的根接口,而ApplicationContext
间接继承了BeanFactory
,如下类图所示
通过这种继承关系,说明ApplicationContext
是对BeanFactory
的功能扩展,而 spring bean 容器的基础功能依然在BeanFactory
中定义
BeanFactory 功能
在BeanFactory
接口中,主要定义的都是getBean()
相关的方法,如下
但实际上,控制反转,基本的依赖注入,生命周期等,都由其子实现类org.springframework.beans.factory.support.DefaultListableBeanFactory
提供了,如下图
官方文档对此类的定义
Spring的可配置列表的默认实现可以是一个工厂和 bean 定义注册表接口:一个基于bean定义元数据的成熟的bean工厂,可通过后处理器进行扩展。
典型的用法是在访问bean之前,首先注册所有bean定义(可能从bean定义文件中读取)。因此,按名称查找Bean是本地Bean定义表中的一种廉价操作,它对预先解析的Bean定义元数据对象进行操作。
注意,特定bean定义格式的读取器通常是单独实现的,而不是作为bean工厂子类:参见例如org.springframework.beans.factory.xml.XmlBeanDefinitionReader
对于org.springframework.beans.factory的另一个替代实现。看看静态列表成为工厂,它管理现有的bean实例,而不是基于bean定义创建新的实例。
如上类图所示,DefaultListableBeanFactory
又间接继承了org.springframework.beans.factory.support.DefaultSingletonBeanRegistry
,这是 spring 中所有单例 bean 容器所属的类,官方文档定义其为:共享 bean 实例的通用注册表
spring 中单例 bean 的容器在此类中被作为属性,定义如下
/** Cache of singleton objects: bean name to bean instance. */
// 通过 bean 名称对应 bean 实例的形式缓存所有的单例 bean
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
而在ApplicationContext
的实现类org.springframework.context.support.GenericApplicationContext
中,又通过组合的方式,持有一个DefaultListableBeanFactory
的实例,如下
private final DefaultListableBeanFactory beanFactory;
这说明ApplicationContext
既具备了BeanFacory
中getBean()
的能力,同时又通过组合DefaultListableBeanFactory
的方式具备了管理所有 bean 实例的能力
spring 中通过组合、继承、实现等方式,将不同的功能交给不同的类扩展和复用,遵循了开闭原则,达到解耦的效果
ApplicationContext 功能
org.springframework.context.ApplicationContext
通过继承的方式,包含了 spring 中许多的重要功能
ApplicationContext
的继承体系如下
通过继承父接口,ApplicationContext
主要扩展了以下功能
- 国际化相关,在其父接口
org.springframework.context.MessageSource
中定义 - 事件发布相关,在其父接口
org.springframework.context.ApplicationEventPublisher
中定义 - 资源解析相关,在其父接口
org.springframework.core.io.support.ResourcePatternResolver
中定义 - 环境信息(例如系统环境变量)相关,在其父接口
org.springframework.core.env.EnvironmentCapable
中定义
针对上述功能,使用 spring-boot 进行简单演示
国际化
// spring-boot 完全启动后, 会返回 ConfigurableApplicationContext 类型的应用容器, 此类也继承了 ApplicationContext
// 因此可以直接通过 context 来调用 ApplicationContext 的扩展功能
ConfigurableApplicationContext context = SpringApplication.run(SpringDemoApplication.class, args);
// 会输出指定的 key 对应配置的值, 达到翻译的效果, 例如 hi=你好, hi=hello 等不同语言的配置
// 前提是在 maven 工程的 resources 目录下, 存在 messages.properties(必须) 以及 messages_zh_CN.properties(可选) 这种不同语言对应的配置文件
System.out.println(context.getMessage("hi", null, Locale.CHINA));
资源解析
// 解析类路径下的文件资源, classpath 代表类路径, 加上 * 代表匹配外部 jar 包中的类路径资源, 否则的话只能解析对其项目的类路径
Resource[] resources = context.getResources("classpath*:META-INF/spring.factories");
for (Resource resource : resources) {
System.out.println(resource);
}
环境
// 获取当前主机的系统环境变量
context.getEnvironment().getSystemEnvironment().forEach((k, v) -> System.out.println(k + "===" + v));
事件发布
定义事件对象
import org.springframework.context.ApplicationEvent;
/**
* 事件对象
*
* @author dhj
* @date 2022/10/3
*/
public class OrderEvent extends ApplicationEvent {
/**
* @param source 指定事件的发布源,也就是谁发的事件
*/
public OrderEvent(Object source) {
super(source);
}
}
定义对应事件的事件监听器
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
/**
* 事件监听器 , spring 中任意组件都可作为事件的监听器
*
* @author dhj
* @date 2022/10/3
*/
@Component
public class OrderListener {
/**
* 接收事件的方法
*
* @param orderEvent 事件对象
*/
@EventListener // 标识此方法作为事件监听的方法(如果有多个接收相同事件对象的事件监听器,届时都会同时收到此事件)
public void listenerOrder(OrderEvent orderEvent) {
System.out.println("1:" + orderEvent);
}
}
发布事件
ConfigurableApplicationContext context = SpringApplication.run(SpringDemoApplication.class, args);
// 发布事件,同时指定事件源
context.publishEvent(new OrderEvent(context));
这里为了方便,直接使用 context 对象来发布事件,如果在其他 spring 组件中,可以直接注入事件发布器来发布事件,如下
@Autowired private ApplicationEventPublisher publisher;
小结
BeanFactory
和ApplicationContext
之间并不只是简单的继承关系,ApplicationContext
通过组合的形式复用了BeanFactory
中定义的功能,并且又通过多继承其他接口的方式,扩展了BeanFactory
BeanFactory 实现
BeanFactoryPostProcessor
BeanFactory
的实现有很多,其中最主要的是org.springframework.beans.factory.support.DefaultListableBeanFactory
实现类
下面是DefaultListableBeanFactory
的简单使用演示
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Arrays;
/**
* @author dhj
* @date 2022/10/5
*/
public class PlainMainTest {
public static void main(String[] args) {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
/*
spring 中通过 bean 的定义(class,scope,初始化方法,销毁方法等)作为创建 bean 实例的依据
所以需要先将 bean 定义信息提供给 bean 工厂
*/
// 创建一个 bean 定义实例
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder
.genericBeanDefinition(Config.class) // bean 的类型
.setScope("singleton") // scope(单例 bean)
.getBeanDefinition(); // 返回 bean 定义实例
// 注册 bean 定义信息到 beanFactory,其内部通过 Map 结构维护多个 bean 定义信息
beanFactory.registerBeanDefinition("config", beanDefinition);
// 打印当前 beanFactory 中的所有 bean 定义信息
Arrays.stream(beanFactory.getBeanDefinitionNames()).forEach(System.out::println);
}
@Configuration
static class Config {
@Bean
public Bean1 bean1() {
return new Bean1();
}
@Bean
public Bean2 bean2() {
return new Bean2();
}
}
static class Bean1 {
public Bean1() {
System.out.println("Bean1()");
}
}
static class Bean2 {
public Bean2() {
System.out.println("Bean2()");
}
}
}
最终的打印结果为config
,也就是说只注册了配置类,但是配置类中通过@Bean
注解标识的组件的【bean 定义信息】没有被注册到BeanFactory
中,这意味着后续也无法获取到对应的实例
这是因为原始的BeanFactory
并没有诸如解析配置类,解析注解的能力,这需要通过后处理器来实现,通过如下代码为BeanFactory
提供功能增强
// 为 beanFactory 添加一些后处理器
AnnotationConfigUtils.registerAnnotationConfigProcessors(beanFactory);
再次运行,打印结果如下
config
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
可以看到,当前的BeanFactory
中多出了几个 bean 定义信息,这些就是对BeanFactory
进行功能增强的后处理器
但目前这些后处理器还未起作用,还需要手动的应用后处理器的增强方法,最终代码如下
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
/*
spring 中通过 bean 的定义(class, scope, 初始化方法, 销毁方法)作为创建 bean 实例的依据
所以需要先将 bean 定义信息提供给 bean 工厂
*/
// 创建一个 bean 定义实例
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder
.genericBeanDefinition(Config.class) // bean 的类型
.setScope("singleton") // scope(单例 bean)
.getBeanDefinition(); // 返回 bean 定义实例
// 注册 bean 定义信息, 内部通过 Map 结构维护多个 bean 定义信息
beanFactory.registerBeanDefinition("config", beanDefinition);
// 为 beanFactory 添加一些后处理器, 可以用于 beanFactory 的功能增强
AnnotationConfigUtils.registerAnnotationConfigProcessors(beanFactory);
// BeanFactoryPostProcessor 后处理器, 主要是补充一些 bean 定义信息, 如 @Bean @Configuration
// 这里直接执行其中的后处理方法, 使用其提供的增强功能
beanFactory.getBeansOfType(BeanFactoryPostProcessor.class).values().forEach(beanFactoryProcessor -> {
beanFactoryProcessor.postProcessBeanFactory(beanFactory);
});
// 打印当前 beanFactory 中的所有 bean 定义信息
Arrays.stream(beanFactory.getBeanDefinitionNames()).forEach(System.out::println);
BeanPostProcessor
通过以上方式注册 bean 之后,就可以通过BeanFactory
中的getBean()
根据beanName
,beanType
来获取 bean 实例了,但目前 bean 实例化的过程中,并没有处理 bean 之间的依赖关系,如下代码
@Configuration
static class Config {
@Bean
public Bean1 bean1() {
return new Bean1();
}
@Bean
public Bean2 bean2() {
return new Bean2();
}
}
static class Bean1 {
// Bean1 依赖 Bean2
@Autowired
private Bean2 bean2;
public Bean1() {
System.out.println("Bean1()");
}
}
static class Bean2 {
public Bean2() {
System.out.println("Bean2()");
}
}
通过beanFactory.getBean(Bean1.class).bean2
获取的 bean2 此时为null
,这说明在Bean1
中通过@Autowired
申明的依赖关系没有生效,换句话说,当前的BeanFactory
没有【依赖注入】的功能
这是因为针对当前的BeanFactory
,并没有为其手动的扩展【依赖注入】相关的后处理器,需要通过如下代码进行扩展
// BeanPostProcessor 后处理器, 针对 bean 生命周期的各个阶段提供扩展功能
// 这里是通过获取 BeanPostProcessor 类型的 bean 实例, 将其添加到 beanFactory 中
// 后续通过 beanFactory 调用 getBean() 时, 便会应用这些后处理器逻辑, 其中就会包含【依赖注入】
beanFactory.getBeansOfType(BeanPostProcessor.class).values().forEach(beanFactory::addBeanPostProcessor);
// 可以设置提前把单例创建好, 否则默认会在使用到 getBean() 时, 才会去创建对应的 bean
beanFactory.preInstantiateSingletons();
System.out.println(beanFactory.getBean(Bean1.class).bean2);
小结
截至目前,可以了解到的是,
BeanFactory
只具备基本的getBean()
功能,而 spring 中一系列的依赖注入,配置解析等能力,主要通过针对BeanFactory
进行功能增强的不同类型的后处理器实现例如
BeanFactoryPostProcessor
,主要用于补充 bean 的定义信息;BeanPostProcessor
主要针对 bean 生命周期的各个阶段进行功能扩展而这些功能扩展目前都是手动添加的,在
ApplicationContext
中,则对这些扩展功能进行了封装
后处理器排序
针对原始的BeanFactory
,添加的后处理器有多个,如果不进行排序操作,默认是按照添加的顺序执行的,例如在调用AnnotationConfigUtils.registerAnnotationConfigProcessors(beanFactory);
注册后处理器时,部分源码如下
public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(
BeanDefinitionRegistry registry, @Nullable Object source) {
DefaultListableBeanFactory beanFactory = unwrapDefaultListableBeanFactory(registry);
if (beanFactory != null) {
if (!(beanFactory.getDependencyComparator() instanceof AnnotationAwareOrderComparator)) {
beanFactory.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE);
}
if (!(beanFactory.getAutowireCandidateResolver() instanceof ContextAnnotationAutowireCandidateResolver)) {
beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());
}
}
// LinkedHashSet 能够维护元素添加的顺序
Set<BeanDefinitionHolder> beanDefs = new LinkedHashSet<>(8);
if (!registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) {
RootBeanDefinition def = new RootBeanDefinition(ConfigurationClassPostProcessor.class);
def.setSource(source);
beanDefs.add(registerPostProcessor(registry, def, CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME));
}
// Autowired 注解的后处理器 bean 定义信息先被添加
if (!registry.containsBeanDefinition(AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME)) {
RootBeanDefinition def = new RootBeanDefinition(AutowiredAnnotationBeanPostProcessor.class);
def.setSource(source);
beanDefs.add(registerPostProcessor(registry, def, AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
}
// CommonAnnotationBeanPostProcessor 后处理器, 主要用于处理 @Resource 注解
// Check for JSR-250 support, and if present add the CommonAnnotationBeanPostProcessor.
if (jsr250Present && !registry.containsBeanDefinition(COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)) {
RootBeanDefinition def = new RootBeanDefinition(CommonAnnotationBeanPostProcessor.class);
def.setSource(source);
beanDefs.add(registerPostProcessor(registry, def, COMMON_ANNOTATION_PROCESSOR_BEAN_NAME));
}
........
可以看出,AutowiredAnnotationBeanPostProcessor
的定义信息在CommonAnnotationBeanPostProcessor
之前被添加,实际生效的顺序也排在前面,因为在实际添加后置处理器实例时,也是通过遍历的方式顺序添加的,例如
// 顺序遍历添加后处理器
beanFactory.getBeansOfType(BeanPostProcessor.class).values().forEach(beanFactory::addBeanPostProcessor);
考虑如下注册 bean 的结构
@Configuration
static class Config {
@Bean
public Bean1 bean1() {
return new Bean1();
}
@Bean
public Teacher teacher() {
return new Teacher();
}
@Bean
public Student student() {
return new Student();
}
}
static class Bean1 {
@Autowired
private Person person;
}
interface Person {
}
static class Student implements Person {
}
static class Teacher implements Person {
}
此时的 bean 容器中,Person
类型的 bean 有多个,如果只是单纯的声明@Autowired
,那么Bean1
会创建失败,因为容器中相同类型的 bean 有多个,spring 无法确定到底注入哪一个Person
类型的 bean,可如果将变量名称改为与@Bean
对应的方法名称一致,如下
@Autowired
// @Qualifier("student") 或者使用 Qualifier 指定要注入的 bean 的名称
private Person student; // 变量名称与注入的 bean 名称保持一致
spring 会在找到多个相同类型的 bean 实例时,根据变量的名称与 bean 实例的名称进行匹配,选择合适的进行注入,可见@Autowired
会先通过类型查找对应的 bean,如果有多个结果,再根据名称来决定,可如果连对应类型都没有查找到,后续也不会根据名称进行匹配
而@Resource
注解则恰恰相反,此注解优先对 bean 名称匹配的实例进行注入,尽管此实例类型与变量的类型不一致;以下代码在注入时会抛出异常
@Bean
public Teacher teacher() {
return new Teacher();
}
@Bean
public Integer student() {
return Integer.valueOf("1");
}
@Resource
private Person student;
哪怕student()
返回值类型为Integer
,@Resource
也会在变量名称与方法名称一致的情况下尝试进行注入,最后导致抛出异常
@Autowire
在遇到这种情况时,会更加智能一些,它会察觉到student()
方法名称虽然和变量名称一致,但返回值类型并不符合,因此会选择注入teacher()
方法返回的实例
现在考虑一下极端情况,注入的变量上同时被标注了@Autowired
和@Resource
注解,如下
@Bean
public Teacher teacher() {
return new Teacher();
}
@Bean
public Student student() {
return new Student();
}
@Autowired
@Resource(name = "teacher")
private Person student;
这实际上就是后处理器执行先后的问题了,如果是AutowiredAnnotationBeanPostProcessor
后处理器先被执行,那么最终注入的实例类型为Student
,否则的话会注入Teacher
类型
spring 中,组件可以通过实现org.springframework.core.Ordered
接口,来标识自身的优先级,此外,通过org.springframework.core.annotation.Order
注解也可以指定当前组件的优先级,Ordered
接口中定义了getOrder()
方法,其返回的值越小(int 类型),代表优先级越高
AutowiredAnnotationBeanPostProcessor
后处理器定义了一个order
属性定义自身的优先级,如下
private int order = Ordered.LOWEST_PRECEDENCE - 2;
CommonAnnotationBeanPostProcessor
也在构造方法中通过setOrder
设置了优先级
public CommonAnnotationBeanPostProcessor() {
setOrder(Ordered.LOWEST_PRECEDENCE - 3); // 优先级设置
......
}
可以看出AutowiredAnnotationBeanPostProcessor
的优先级要比CommonAnnotationBeanPostProcessor
的优先级低,因为其order
值更大
那么只要在添加后处理器时,先排序,就可以调整后处理器添加的顺序,如下代码
beanFactory.getBeansOfType(BeanPostProcessor.class).values()
.stream()
.sorted(beanFactory.getDependencyComparator()) // 根据 order 值排序(升序)
.forEach(beanFactory::addBeanPostProcessor);
经过排序后,CommonAnnotationBeanPostProcessor
就会先执行,最终注入的结果为Teacher
类型
ApplicationContext 实现
ApplicationContext
的实现类依旧众多,最经典的有如下几个
org.springframework.context.support.ClassPathXmlApplicationContext
基于 classpath 下 xml 格式的配置文件创建 beanorg.springframework.context.support.FileSystemXmlApplicationContext
基于磁盘路径下 xml 格式的配置文件创建 beanorg.springframework.context.annotation.AnnotationConfigApplicationContext
基于注解配置来创建 beanorg.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext
基于注解配置来创建 bean,用于 web 环境
ClassPathXmlApplicationContext & FileSystemXmlApplicationContext
下面对ClassPathXmlApplicationContext
和FileSystemXmlApplicationContext
这两个实现的基本使用进行演示,从演示中可以大概窥探出 spring 通过 xml 配置文件解析 bean 定义信息的大致原理
先定义 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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="bean1" class="com.example.spring.test.ApplicationContextMainText1.Bean1">
<property name="bean2" ref="bean2"/>
</bean>
<bean id="bean2" class="com.example.spring.test.ApplicationContextMainText1.Bean2"/>
</beans>
分别使用ClassPathXmlApplicationContext
和FileSystemXmlApplication
加载配置文件并获取 bean 实例
// ClassPathXmlApplicationContext 基于类路径的 xml 配置 bean
public static void testClassPathXmlApplication() {
ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
Bean1 bean = context.getBean(Bean1.class);
System.out.println(bean.bean2);
}
// FileSystemXmlApplication 基于文件系统的 xml 配置 bean
public static void testFileSystemXmlApplicationContext() {
ApplicationContext context =
new FileSystemXmlApplicationContext("D:\\projectCode\\spring-understand\\src\\main\\resources\\bean1.xml");
Bean1 bean = context.getBean(Bean1.class);
System.out.println(bean.bean2);
}
而这两个类之所以能够通过配置文件就能创建出 bean 实例,依靠的不仅仅是xml
解析技术,bean 的实例化过程实际上是由BeanFactory
提供的,其内部执行流程大致如下
// BeanFactory 的实例
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
// 通过 XmlBeanDefinitionReader 来解析 xml
XmlBeanDefinitionReader xmlBeanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
// 将 xml 文件中的解析出的 bean 定义信息注册到 beanFactory 中
xmlBeanDefinitionReader.loadBeanDefinitions("bean1.xml");
Arrays.stream(beanFactory.getBeanDefinitionNames()).forEach(System.out::println);
上述代码可以体现出,ClassPathXmlApplicationContext
和FileSystemXmlApplicationContext
只是解析了 xml 配置文件,如果想要注册 bean 定义信息,至少得提供 BeanFactory 的实例,而这在二者的父类org.springframework.context.support.AbstractRefreshableApplicationContext
中,则通过组合的方式持有了一个BeanFactory
的实例,定义如下
/** Bean factory for this context. */
@Nullable
private volatile DefaultListableBeanFactory beanFactory;
这样一切都说得通了,ClassPathXmlApplicationContext
和FileSystemXmlApplicationContext
自身提供了解析 xml 配置的能力
再通过组合BeanFactory
的方式,执行后续的 bean 定义注册,bean 实例化逻辑,这其中BeanFactory
又扩展一系列后处理器的增强逻辑
AnnotationConfigApplicationContext
此实现类可以通过注解配置类的形式来配置 bean,如下代码
注解配置类
@Configuration
static class Config {
@Bean
public Bean1 bean1(Bean2 bean2) {
Bean1 bean1 = new Bean1();
bean1.setBean2(bean2);
return bean1;
}
@Bean
public Bean2 bean2() {
return new Bean2();
}
}
static class Bean1 {
private Bean2 bean2;
public void setBean2(Bean2 bean2) {
this.bean2 = bean2;
}
}
static class Bean2 {
}
使用 AnnotationConfigApplicationContext 加载注解配置类
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
Bean1 bean = context.getBean(Bean1.class);
System.out.println(bean.bean2);
AnnotationConfigServletWebServerApplicationContext
此实现类主要用于 web 环境中的主要 bean 配置,通过此类就可以搭建起基本的 web 环境,这也是 spring boot 帮我们做的事情,使用演示如下
web 配置类
@Configuration
static class WebConfig {
// 配置 web 容器
@Bean
public ServletWebServerFactory servletWebServerFactory() {
return new TomcatServletWebServerFactory();
}
// 配置前控制器
@Bean
public DispatcherServlet dispatcherServlet() {
return new DispatcherServlet();
}
// 注册【前控制器】到 web 容器
@Bean
public DispatcherServletRegistrationBean registrationBean(DispatcherServlet dispatcherServlet) {
// "/" 匹配所有请求
return new DispatcherServletRegistrationBean(dispatcherServlet, "/");
}
// 手动注册一个控制器,这相当于使用 @Controller 注解注册了一个控制器
@Bean("/hello") // 如果 bean 名称以 / 开头, 那么此控制器的访问路径就为完整的 bean 名称
public Controller controllerOne() {
return (request, response) -> {
response.getWriter().println("hello");
return null;
};
}
}
使用 AnnotationConfigServletWebServerApplicationContext 加载 web 配置类
ApplicationContext context = new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);
容器启动完成后,内嵌的tomcat
默认会在8080
端口监听,可以访问localhost:8080/hello
测试配置是否加载成功
Bean 生命周期
生命周期及后处理器
在 spring 中,一个 bean 通常都会经过【实例化】➡【依赖注入】➡【初始化】➡【销毁】这几个生命周期,如下代码
/**
* @author dhj
* @date 2022/10/7
*/
@Component
public class LifeBean {
private static final Logger log = LoggerFactory.getLogger(LifeBean.class);
public LifeBean() {
log.info("实例化");
}
@Autowired
public void autowire(@Value("${JAVA_HOME}") String javaHome) {
log.info("依赖注入: {}", javaHome);
}
@PostConstruct
public void init() {
log.info("初始化");
}
@PreDestroy
public void destroy() {
log.info("销毁");
}
}
启动容器后关闭,以演示 bean 销毁的生命周期
ConfigurableApplicationContext context = SpringApplication.run(SpringDemoApplication.class, args);
context.close();
打印结果如下
实例化
依赖注入: D:\JDK\jdk-8\jdk8
初始化
销毁
而在这几个生命周期中,又穿插了各种后处理器,在之前,有关BeanFactory
的后处理器BeanFactoryPostProcessor
,主要是向 BeanFactory
中提供一些 bean 定义的补充信息
而针对 bean 生命周期的后处理器BeanPostProcessor
,主要是提供和 bean 相关的一些扩展增强功能,下面是自定义的后处理器,用以观察后处理器相对于生命周期执行的时机
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.PropertyValues;
import org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor;
import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor;
import org.springframework.stereotype.Component;
/**
* 自定义的 bean 后处理器
*
* @author dhj
* @date 2022/10/7
*/
@Component
public class MyBeanPostProcessor implements InstantiationAwareBeanPostProcessor, DestructionAwareBeanPostProcessor {
private static final Logger LOGGER = LoggerFactory.getLogger(MyBeanPostProcessor.class);
@Override
public void postProcessBeforeDestruction(Object bean, String beanName) throws BeansException {
if ("lifeBean".equals(beanName)) {
LOGGER.info("销毁之前执行, 例如被 @PreDestroy 标识的方法");
}
}
@Override
public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
if ("lifeBean".equals(beanName)) {
LOGGER.info("实例化之前执行, 这里返回的对象会替换掉原本的 bean");
}
// 返回 null 则不会替换
return null;
}
@Override
public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
if ("lifeBean".equals(beanName)) {
LOGGER.debug("实例化之后执行, 如果返回 false 会跳过依赖注入的环节");
//return false;
}
return true;
}
@Override
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) throws BeansException {
if ("lifeBean".equals(beanName)) {
LOGGER.info("依赖注入阶段执行, 如 @Autowired @Value @Resource");
}
return pvs;
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if ("lifeBean".equals(beanName)) {
LOGGER.info("初始化之前执行, 这里的返回的对象会替换掉原本的 bean, 如 @PostConstruct @ConfigurationProperties");
}
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if ("lifeBean".equals(beanName)) {
LOGGER.info("初始化之后执行, 这里返回的对象会替换掉原本的 bean, 例如返回代理后的对象");
}
return bean;
}
}
打印结果
实例化之前执行, 这里返回的对象会替换掉原本的 bean
实例化
依赖注入阶段执行, 如 @Autowired @Value @Resource
依赖注入: D:\JDK\jdk-8\jdk8
初始化之前执行,这里的返回的对象会替换掉原本的 bean, 如 @PostConstruct @ConfigurationProperties
初始化
初始化之后执行, 这里返回的对象会替换掉原本的 bean, 例如返回代理后的对象
销毁之前执行, 例如被 @PreDestroy 标识的方法
销毁
可以看到,在 bean 生命周期各个阶段的前后,都可以通过后处理器穿插各种逻辑,这也是 spring 能够提供各种增强功能的基础
模板方法
通过BeanFactory
调用getBean()
时,不同的后处理逻辑会在 bean 生命周期的各个阶段被执行,这些逻辑不可能全部耦合到getBean()
中,而是使用模板方法的设计模式来扩展不同的后处理逻辑,因为 bean 生命周期的几个阶段是固有的逻辑,变化的只是后处理器,将这些后处理的行为抽象出来,交给子类去实现不同的后处理器,然后只需要在getBean()
中统一调用抽象接口,就可以实现不同后处理逻辑的调用,如下代码
/**
* @author dhj
* @date 2022/10/8
*/
@Slf4j
public class TemplateMethod {
public static void main(String[] args) {
BeanFactory beanFactory = new BeanFactory();
// 添加扩展的后处理逻辑
beanFactory.addBeanPostProcessor(new BeanFactory.BeanPostProcessor() {
@Override
public void postProcessBeforeInitialization(Object bean) {
log.info("postProcessor:解析 @Autowire,解析 @Resource...");
}
@Override
public void postProcessAfterInitialization(Object bean) {
log.info("postProcessor:初始化后的后处理逻辑...");
}
});
String bean = beanFactory.getBean(String.class);
System.out.println(bean);
}
static class BeanFactory {
private final List<BeanPostProcessor> processorList = new ArrayList<>(5);
public <T> T getBean(Class<T> clazz) {
try {
// 不变的部分
log.info("实例化");
T bean = clazz.getDeclaredConstructor().newInstance();
log.info("依赖注入");
// 变化和扩展的部分, 通过 processorList 统一维护, 动态扩展【processorList.add()】而无需修改原始代码
this.processorList.forEach(processor -> {
processor.postProcessBeforeInitialization(bean);
});
log.info("初始化");
this.processorList.forEach(processor -> {
processor.postProcessAfterInitialization(bean);
});
return bean;
} catch (Exception e) {
e.printStackTrace();
}
log.error("bean 初始化失败");
return null;
}
// 外部调用, 添加扩展的逻辑
public void addBeanPostProcessor(BeanPostProcessor processor) {
this.processorList.add(processor);
}
// 定义抽象的需要扩展的部分
interface BeanPostProcessor {
void postProcessBeforeInitialization(Object bean);
void postProcessAfterInitialization(Object bean);
}
}
}
常见 Bean 后处理器
前面已经了解了 spring 如何通过模板方法模式,将不同的 bean 后处理器用于 bean 生命周期的各个阶段,这里继续演示 spring 中的几个常见后处理器及其作用
有如下代码结构
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.support.GenericApplicationContext;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;
/**
* @author dhj
* @date 2022/10/8
*/
public class BeanPostProcessorMainTest {
public static void main(String[] args) {
// 一个纯粹的 bean 容器, 没有额外的功能
GenericApplicationContext context = new GenericApplicationContext();
// 注册 bean
context.registerBean("bean1", Bean1.class);
context.registerBean("bean2", Bean2.class);
context.registerBean("bean3", Bean3.class);
// 初始化容器
context.refresh();
// 关闭容器
context.close();
}
static class Bean1 {
private static final Logger LOGGER = LoggerFactory.getLogger(Bean1.class);
private Bean2 bean2;
private Bean3 bean3;
private String javaHome;
@Autowired
public void setBean2(Bean2 bean2) {
LOGGER.info("@Autowire 生效");
this.bean2 = bean2;
}
@Resource
public void setBean3(Bean3 bean3) {
LOGGER.info("@Resource 生效");
this.bean3 = bean3;
}
@Autowired
public void setJavaHome(@Value("${JAVA_HOME}") String javaHome) {
LOGGER.info("@Value 生效");
this.javaHome = javaHome;
}
@PostConstruct
public void init() {
LOGGER.info("@PostConstruct 生效");
}
@PreDestroy
public void destroy() {
LOGGER.info("@PreDestroy 生效");
}
}
static class Bean2 {
}
static class Bean3 {
}
}
以上是一个最基本的 spring 容器的启动过程,但因为使用了GenericApplicationContext
,此类中没有注册任何后处理器,所以各项功能如【依赖诸如】【bean 生命周期钩子】等功能都没有生效,现在分别来逐步添加各种后处理器,就可以很明显的观察到 spring 中不同后处理器产生的效果了
AutowiredAnnotationBeanPostProcessor
// 添加 @Qualifier @Value @Lazy 等注解的解析器, 否则 @Value 的值注入会失败,
context.getDefaultListableBeanFactory().setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());
// 添加 @Autowire, @Value 注解的后处理器 AutowiredAnnotationBeanPostProcessor 到 context 容器
context.registerBean(AutowiredAnnotationBeanPostProcessor.class);
AutowiredAnnotationBeanPostProcessor 主要用于解析@Autowired
以及@Value
注解,完成依赖注入,关于ContextAnnotationAutowireCandidateResolver
,可参考 Spring依赖注入(DI)核心接口AutowireCandidateResolver深度分析
CommonAnnotationBeanPostProcessor
// 添加 CommonAnnotationBeanPostProcessor 到 context 容器
context.registerBean(CommonAnnotationBeanPostProcessor.class);
CommonAnnotationBeanPostProcessor 主要用于解析@Resource
@PostConstruct
@PreDestroy
注解
ConfigurationPropertiesBindingPostProcessor
// 添加 @ConfigurationProperties 注解的后处理器到 context 容器
ConfigurationPropertiesBindingPostProcessor.register(context);
主要用于解析@ConfigurationProperties
注解
Autowired Bean 后处理器
用于处理@Autowired
注解的处理器比较常用,下面单独进行分析,代码如下
类结构
static class Bean1 {
private static final Logger LOGGER = LoggerFactory.getLogger(Bean1.class);
private Bean2 bean2;
private Bean3 bean3;
private String javaHome;
public Bean1() {
System.out.println("bean1 实例化");
}
@Autowired
public void setBean2(Bean2 bean2) {
LOGGER.info("@Autowire 生效,bean2:{}", bean2);
this.bean2 = bean2;
}
@Resource
public void setBean3(Bean3 bean3) {
LOGGER.info("@Resource 生效,bean3:{}", bean3);
this.bean3 = bean3;
}
@Autowired
public void setJavaHome(@Value("${JAVA_HOME}") String javaHome) {
LOGGER.info("@Value 生效,value:{}", javaHome);
this.javaHome = javaHome;
}
@PostConstruct
public void init() {
LOGGER.info("@PostConstruct 生效");
}
@PreDestroy
public void destroy() {
LOGGER.info("@PreDestroy 生效");
}
}
static class Bean2 {
public Bean2() {
System.out.println("bean2 实例化");
}
}
static class Bean3 {
public Bean3() {
System.out.println("bean3 实例化");
}
}
通过后处理器完成依赖注入
// 创建 beanFactory 实例
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
// 先注册 bean1 依赖的 bean 实例, 方便测试
beanFactory.registerSingleton("bean2", new Bean2());
beanFactory.registerSingleton("bean3", new Bean3());
beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());
// 添加占位符解析器, 如 ${}
beanFactory.addEmbeddedValueResolver(new StandardEnvironment()::resolvePlaceholders);
// @Autowired @Value 注解解析的后处理器
AutowiredAnnotationBeanPostProcessor autowiredBeanPostProcessor = new AutowiredAnnotationBeanPostProcessor();
// 设置后处理器的 beanFactory 引用, 因为 autowiredBeanPostProcessor 需要从 beanFactory 中获取实例化好的 bean, 用于依赖注入
autowiredBeanPostProcessor.setBeanFactory(beanFactory);
// 创建需要被依赖注入的目标 bean 实例, 此实例中的成员变量, 方法参数存在被 @Autowired @Value 修饰的情况
Bean1 bean1 = new Bean1();
/*
【针对 bean1 执行依赖注入的后处理逻辑】
参数一 pvs 代表【使用指定的值注入】, 这里不需要
参数二 代表需要被注入的目标 bean
参数三 代表目标 bean 的名称
*/
autowiredBeanPostProcessor.postProcessProperties(null, bean1, "bean1");
System.out.println(bean1.javaHome);
关键在于postProcessProperties()
方法,AutowiredAnnotationBeanPostProcessor
只通过此方法就完成对目标 bean 的依赖注入,此方法源码如下
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
// findAutowiringMetadata() 查找需要依赖注入的数据
InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
try {
metadata.inject(bean, beanName, pvs);
}
catch (BeanCreationException ex) {
throw ex;
}
catch (Throwable ex) {
throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", ex);
}
return pvs;
}
findAutowiringMetadata()
方法会查找目标 bean 中被@Autowired
@Value
标识的属性或方法入参,将其封装为一个InjectionMetadata
类型的对象,如下
InjectionMetadata
中通过集合的形式保存了目标 bean 中所有的需要依赖注入的【方法】或【属性】
将postProcessProperties
中的依赖注入代码使用手动方式实现如下
// 创建 beanFactory 实例
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
// 先注册 bean1 依赖的 bean 实例, 方便测试
beanFactory.registerSingleton("bean2", new Bean2());
beanFactory.registerSingleton("bean3", new Bean3());
beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());
// 添加占位符解析器, 如有 ${}
beanFactory.addEmbeddedValueResolver(new StandardEnvironment()::resolvePlaceholders);
// @Autowired @Value 注解解析的后处理器
AutowiredAnnotationBeanPostProcessor autowiredBeanPostProcessor
= new AutowiredAnnotationBeanPostProcessor();
// 设置后处理器的 beanFactory 引用, 因为 autowiredBeanPostProcessor 需要从 beanFactory 中获取实例化好的 bean, 用于依赖注入
autowiredBeanPostProcessor.setBeanFactory(beanFactory);
// 创建需要被依赖注入的目标 bean 实例, 此实例中的成员变量, 方法参数存在被 @Autowired @Value 修饰的情况
Bean1 bean1 = new Bean1();
// =================== 手动依赖注入 =========================
Class<AutowiredAnnotationBeanPostProcessor> autowiredAnnotationBeanPostProcessorClass
= AutowiredAnnotationBeanPostProcessor.class;
Method postProcessPropertiesMethod = autowiredAnnotationBeanPostProcessorClass
.getDeclaredMethod("findAutowiringMetadata", String.class, Class.class, PropertyValues.class);
postProcessPropertiesMethod.setAccessible(true);
InjectionMetadata meta = (InjectionMetadata) postProcessPropertiesMethod.invoke(autowiredBeanPostProcessor, "bean1", Bean1.class, null);
// 对 bean1 执行依赖注入
meta.inject(bean1, "bean1", null);
System.out.println(bean1.javaHome);
总结
spring 中的
@Autowired
@Value
等注解的解析和自动注入功能,由AutowiredAnnotationBeanPostProcessor
提供,只需要调用其postProcessProperties()
方法就可以完成依赖注入的后处理逻辑
postProcessProperties()
内部又分为两个步骤来完成依赖注入
- 解析目标 bean 中标注了
@Autowired
和@Value
注解的属性或方法,保存到集合中- 遍历集合,针对这些属性和方法所需的依赖,根据 beanFactory 中的 bean,,环境变量信息,配置文件等,执行注入
常见工厂后处理器
相对于BeanPostProcessor
,工厂后处理器BeanFactoryPostProcessor
主要用于对BeanFactory
进行的组件扫描、注册提供增强
现有如下类结构,都位于com.example.spring.bean.scan
包下
import org.slf4j.LoggerFactory;
/**
* @author dhj
* @date 2022/10/15
*/
public class Bean1 {
private static final Logger LOGGER = LoggerFactory.getLogger(Bean1.class);
public Bean1() {
LOGGER.info("{}-实例化", Bean1.class.getName());
}
}
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
/**
* @author dhj
* @date 2022/10/15
*/
@Component
public class Bean2 {
private static final Logger LOGGER = LoggerFactory.getLogger(Bean2.class);
public Bean2() {
LOGGER.info("{}-实例化", Bean2.class.getName());
}
}
配置类
@Configuration
@ComponentScan(basePackages = "com.example.spring.bean.scan") // 扫描包路径
static class Config {
@Bean
public Bean1 bean1() {
return new Bean1();
}
@Bean
public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource) {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource);
return sqlSessionFactoryBean;
}
@Bean(initMethod = "init")
public DruidDataSource dataSource() {
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setUrl("jdbc:mysql://localhost:3306/xlf_db");
druidDataSource.setUsername("root");
druidDataSource.setPassword("111111");
return druidDataSource;
}
}
演示代码
import com.alibaba.druid.pool.DruidDataSource;
import com.example.spring.bean.scan.Bean1;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.GenericApplicationContext;
import javax.sql.DataSource;
import java.util.Arrays;
/**
* 常见 BeanFactoryPostProcessor 演示
*
* @author dhj
* @date 2022/10/15
*/
public class BeanFactoryPostProcessorMainTest {
private static final Logger LOGGER = LoggerFactory.getLogger(BeanFactoryPostProcessorMainTest.class);
public static void main(String[] args) {
GenericApplicationContext context = new GenericApplicationContext();
// 注册配置类
context.registerBean("config", Config.class);
// 初始化容器
context.refresh();
// 打印容器中的 bean 定义
Arrays.stream(context.getBeanDefinitionNames()).forEach(System.out::println);
// 容器销毁
context.close();
}
}
以上代码的打印结果中,只会包含config
,也就是我们手动注册的配置类,但配置类上指定的包扫描以及其中通过@Bean
注册的组件通通都没有生效,因为这些功能是由BeanFactoryPostProcessor
提供的
要想使Config
类中标注的注解生效,需要在容器初始化之前注册org.springframework.context.annotation.ConfigurationClassPostProcessor
类型的BeanFactoryPostProcessor
,如下
// 注册 BeanFactoryPostProcessor
// @Configuration @Bean @ComponentScan 等注解的解析
context.registerBean(ConfigurationClassPostProcessor.class);
// 初始化容器
context.refresh();
这样就能够将Config
配置类所配置的相关 bean 注册到 BeanFactory 中,这种扩展功能,正是由ConfigurationClassPostProcessor
提供
再举一个例子,spring 整合 mybatis 时,需要指定@Mapper
注解的扫描包路径,之后此包之下标注了@Mapper
注解的 bean 定义信息都会被注册到 beanFactory 中,但因为Mapper
都是接口类型,容器中的组件实际上是Mapper
接口对应的代理实现类,这不是重点
重点是扫描Mapper
接口所在包以及注册 bean 定义信息的org.mybatis.spring.mapper.MapperScannerConfigurer
,也是一个BeanFactoryPostProcessor
,其类定义如下
public class MapperScannerConfigurer
implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
.....
MapperScannerConfigurer
实现了BeanDefinitionRegistryPostProcessor
接口,这说明只需要在初始化容器之前,注册此类到BeanFactory
中,即可实现Mapper
接口的扫描和注册,这也是@MapperScan
注解所做的事,如下
准备一个 Mapper 接口
/**
* @author dhj
* @date 2022/10/15
*/
@Mapper
public interface Mapper1 {
Logger LOGGER = LoggerFactory.getLogger(Mapper1.class);
}
手动注册 Mapper 接口扫描的 BeanFactoryPostProcessor
// MyBatis 的 Mapper 注解扫描 BeanFactoryProcessor
context.registerBean(MapperScannerConfigurer.class, customBeanDefinition -> {
customBeanDefinition.getPropertyValues()
.add("basePackage", "com.example.spring.mapper"); // 自定义的 BeanDefinition 属性, 这里是指定扫描的包路径
});
获取注册的 Mapper 接口实例
Mapper1 mapperProxy = context.getBean(Mapper1.class);
通过 debug 可以看到,最终获取的Mapper
接口对应的实例,是一个由 mybatis 提供的代理对象
【组件扫描 && 注册】模拟实现
为了更好的理解BeanFactoryPostProcessor
的运行原理,下面对BeanFactoryPostProcessor
的组件扫描 && 注册功能进行一个模拟实现
spring 中对组件资源的加载主要分为扫描和注册
- 首先是根据
@ComponentScan
注解,加载指定package(类路径)
下的.class
资源,再检测每个资源是否被标识为了一个 spring 组件 - 如果是被
@Component
直接或间接标识,那么会将其封装为BeanDefinition
,注册到 beanFactory 中 - 容器在刷新时,则会将 beanFactory 中注册的
BeanDefinition
实例化为可用的组件
组件资源扫描 && 注册
GenericApplicationContext context = new GenericApplicationContext();
context.registerBean("config", Config.class);
// 查找 Config 类上标注的 ComponentScan 注解
ComponentScan componentScan = AnnotationUtils.findAnnotation(Config.class, ComponentScan.class);
Assert.notNull(componentScan, "not null");
String[] packages = componentScan.basePackages();
// 用于读取 Resource 的元信息
CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory();
for (String packageStr : packages) {
// 将扫描注解上指定的包路径转化为 classpath*:/xxx/xxx/xxx/**/*.class 资源路径用于扫描
String resourcePath = "classpath*:" + packageStr.replace(".", "/") + "/**/*.class";
// 加载指定类路径下的资源,实际会将 .class 文件加载为 IO 输入流
Resource[] resources = context.getResources(resourcePath);
// 组件名称的生成器,可以根据 BeanDefinition 生成 beanName
AnnotationBeanNameGenerator beanNameGenerator = new AnnotationBeanNameGenerator();
DefaultListableBeanFactory beanFactory = context.getDefaultListableBeanFactory();
for (Resource resource : resources) {
// 拿到一个 metaData 的读取器
MetadataReader metadataReader = factory.getMetadataReader(resource);
// 读取读取 class 的注解元数据,判断其是否加了 @Component 注解(只能是 @Component 注解)
boolean directComponent = metadataReader.getAnnotationMetadata().hasAnnotation(Component.class.getName());
// 判断是否加了 @Component 的派生注解,例如 @Controller
boolean indirectComponent = metadataReader.getAnnotationMetadata().hasMetaAnnotation(Component.class.getName());
// 直接或间接的加了 @Component 注解
if (directComponent || indirectComponent) {
// 根据组件的 className 创建对应的 BeanDefinition
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(metadataReader.getClassMetadata().getClassName())
.getBeanDefinition();
// 生成 beanName
String beanName = beanNameGenerator.generateBeanName(beanDefinition, beanFactory);
// 注册 beanDefinition
context.getDefaultListableBeanFactory()
.registerBeanDefinition(beanName, beanDefinition);
}
}
}
// 容器刷新
context.refresh();
// 打印被扫描出的组件名称
Arrays.stream(context.getBeanDefinitionNames()).forEach(System.out::println);
实际上,上述代码就是 spring 中组件扫描对应的BeanFactoryPostProcessor
执行的基本逻辑
@Bean 模拟实现
有时候我们所定义的组件不是通过@ComponentScan
指定扫描的包路径来自动注册到 spring 容器的,因为可能需要自己手动创建一个实例注册到 spring 容器中(例如数据库源对象的创建,需要指定 url username password 等参数)
此时就可以通过Configuration
声明一个配置类,在配置类中使用@Bean
标识某个方法, 被标识方法的返回值实例则会被添加到 spring 容器中,例如
@Bean
public DruidDataSource dataSource() {
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setUrl("jdbc:mysql://localhost:3306/xlf_db");
druidDataSource.setUsername("root");
druidDataSource.setPassword("111111");
return druidDataSource;
}
这实际上是工厂方法
模式的体现,配置类作为工厂,@Bean
注解标识的方法则作为工厂方法
下面对 spring 中解析@Bean
注解,注册组件到容器的过程进行一个模拟,代码如下
// @Bean 模拟实现
private static void beanTest() throws Exception {
GenericApplicationContext context = new GenericApplicationContext();
// 注册配置类
context.registerBean("config", BeanFactoryPostProcessorMainTestConfig.class);
// 用于读取指定资源的元数据
CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory();
// 读取 BeanFactoryPostProcessorMainTestConfig 配置类的元数据
MetadataReader reader = factory.getMetadataReader(BeanFactoryPostProcessorMainTestConfig.class.getName());
// 通过配置类的元信息拿到配置类中标注了 @Bean 注解的方法元数据集合
Set<MethodMetadata> methods = reader.getAnnotationMetadata().getAnnotatedMethods(Bean.class.getName());
// 遍历方法元数据集合(实际上就是遍历配置类中所有被标注了 @Bean 注解的方法)
methods.forEach(method -> {
// 创建一个 BeanDefinitionBuilder
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();
// 设置工厂方法(也就是当前遍历到的被 @Bean 标识的方法)
// 底层实际上是设置了 BeanDefinition 的 factoryMethodName 属性以及 factoryBeanName 属性
builder.setFactoryMethodOnBean(method.getMethodName(), BeanFactoryPostProcessorMainTestConfig.class.getName());
// 拿到 BeanDefinition
AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();
// 注册 BeanDefinition
context.getDefaultListableBeanFactory().registerBeanDefinition(method.getMethodName(), beanDefinition);
});
// 初始化容器
context.refresh();
}
上述代码在初始化容器时会报错 No bean named 'com.example.spring.config.BeanFactoryPostProcessorMainTestConfig' available
,这是因为在setFactoryMethodOnBean()
调用时,需要指定
method.getMethodName()
工厂方法的名称BeanFactoryPostProcessorMainTestConfig.class.getName()
调用工厂方法的 bean 名称
问题就在 bean 名称这里,这个 bean 实际上就是配置类注册到容器中的名称,而配置类我们在一开始就已经注册过了:context.registerBean("config", BeanFactoryPostProcessorMainTestConfig.class);
所以setFactoryMethodOnBean()
方法的第二个参数,应该为config
此问题解决后,初始化容器时继续报错:Error creating bean with name 'sqlSessionFactoryBean': Unsatisfied dependency expressed through method 'sqlSessionFactoryBean
sqlSessionFactoryBean
的工厂方法相较于其他普通工厂方法,多了方法的入参,如下
@Bean
public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource);
return sqlSessionFactoryBean;
}
针对于工厂方法的入参以及构造方法的入参,如果需要自动注入,可以指定自动注入的模式为AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR
最终代码如下
private static void beanTest() throws Exception {
GenericApplicationContext context = new GenericApplicationContext();
// 注册配置类
context.registerBean("config", BeanFactoryPostProcessorMainTestConfig.class);
// 用于读取指定资源的元数据
CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory();
// 读取 BeanFactoryPostProcessorMainTestConfig 配置类的元数据
MetadataReader reader = factory.getMetadataReader(BeanFactoryPostProcessorMainTestConfig.class.getName());
// 通过配置类的元信息拿到配置类中标注了 @Bean 注解的方法元数据集合
Set<MethodMetadata> methods = reader.getAnnotationMetadata().getAnnotatedMethods(Bean.class.getName());
// 遍历方法元数据集合(实际上就是遍历配置类中所有被标注了 @Bean 注解的方法)
methods.forEach(method -> {
// 创建一个 BeanDefinitionBuilder
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();
// 设置工厂方法(也就是当前遍历到的被 @Bean 标识的方法)
// 底层实际上是设置了 BeanDefinition 的 factoryMethodName 属性以及 factoryBeanName 属性
builder.setFactoryMethodOnBean(method.getMethodName(), "config");
// 设置自动注入的模式,在 @Bean 标识的方法有入参时,可以自动注入参数实例,默认为 AUTOWIRE_NO(不自动注入)
// 而对于构造方法和工厂方法的注入,都使用的是 AUTOWIRE_CONSTRUCTOR 模式
builder.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR);
// 拿到 BeanDefinition
AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();
// 注册 BeanDefinition
context.getDefaultListableBeanFactory().registerBeanDefinition(method.getMethodName(), beanDefinition);
});
// 初始化容器
context.refresh();
}
实际上,还可以将此逻辑封装为一个BeanFactoryPostProcessor
,如下代码
import com.example.spring.config.BeanFactoryPostProcessorMainTestConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.core.type.MethodMetadata;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import java.util.Set;
public class AtBeanBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
private static final Logger LOGGER = LoggerFactory.getLogger(AtBeanBeanFactoryPostProcessor.class);
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
try {
// 用于读取指定资源的元数据
CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory();
// 读取 BeanFactoryPostProcessorMainTestConfig 配置类的元数据
MetadataReader reader = factory.getMetadataReader(BeanFactoryPostProcessorMainTestConfig.class.getName());
// 通过配置类的元信息拿到配置类中标注了 @Bean 注解的方法元数据集合
Set<MethodMetadata> methods = reader.getAnnotationMetadata().getAnnotatedMethods(Bean.class.getName());
// 遍历方法元数据集合(实际上就是遍历配置类中所有被标注了 @Bean 注解的方法)
methods.forEach(method -> {
// 创建一个 BeanDefinitionBuilder
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();
// 设置工厂方法(也就是当前遍历到的被 @Bean 标识的方法)
// 底层实际上是设置了 BeanDefinition 的 factoryMethodName 属性以及 factoryBeanName 属性
builder.setFactoryMethodOnBean(method.getMethodName(), "config");
// 设置自动注入的模式,在 @Bean 标识的方法有入参时,可以自动注入参数实例,默认为 AUTOWIRE_NO(不自动注入)
// 而对于构造方法和工厂方法的注入,都使用的是 AUTOWIRE_CONSTRUCTOR 模式
builder.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR);
// 拿到 BeanDefinition
AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();
// 注册 BeanDefinition
DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) configurableListableBeanFactory;
beanFactory.registerBeanDefinition(method.getMethodName(), beanDefinition);
});
} catch (Exception e) {
LOGGER.error("异常:", e);
}
}
}
使用时,直接注册AtBeanBeanFactoryPostProcessor
到容器中,就可实现@Bean
的功能,如下代码
GenericApplicationContext context = new GenericApplicationContext();
// 注册配置类
context.registerBean("config", BeanFactoryPostProcessorMainTestConfig.class);
// 注册自定义的 @Bean 工厂后处理器
context.registerBean(AtBeanBeanFactoryPostProcessor.class);
// 初始化容器
context.refresh();
Arrays.stream(context.getBeanDefinitionNames()).forEach(System.out::println);
存在的问题
- 没有处理
initMethod
属性- 配置类被写死为
BeanFactoryPostProcessorMainTestConfig
,应该扫描所有标注了@Configuration
注解的配置类
@Mapper 模拟实现
在之前,直接通过MapperScannerConfigurer
完成了扫描标注@Mapper
注解标识的接口,注册其代理对象的实例到容器中的功能,这是借助了mybatis-spring
依赖提供的封装好的BeanFactoryPostProcessor
而实现的,下面来手动实现其功能
首先需要了解的是最基本的Mapper
接口注入,有如下配置类
import com.alibaba.druid.pool.DruidDataSource;
import com.example.spring.mapper.Mapper1;
import com.example.spring.mapper.Mapper2;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.mapper.MapperFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
@Configuration
public class MapperConfig {
@Bean
public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource);
return sqlSessionFactoryBean;
}
@Bean(initMethod = "init")
public DruidDataSource dataSource() {
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setUrl("jdbc:mysql://localhost:3306/xlf_db");
druidDataSource.setUsername("root");
druidDataSource.setPassword("111111");
return druidDataSource;
}
// Mapper 对象的创建工厂 Bean, 这里是指定创建 Mapper1 类型的对象实例
@Bean
public MapperFactoryBean<Mapper1> mapper1(SqlSessionFactory sqlSessionFactory) {
MapperFactoryBean<Mapper1> factory = new MapperFactoryBean<>(Mapper1.class);
factory.setSqlSessionFactory(sqlSessionFactory);
return factory;
}
// Mapper 对象的创建工厂 Bean ,这里是指定创建 Mapper2 类型的对象实例
@Bean
public MapperFactoryBean<Mapper2> mapper2(SqlSessionFactory sqlSessionFactory) {
MapperFactoryBean<Mapper2> factory = new MapperFactoryBean<>(Mapper2.class);
factory.setSqlSessionFactory(sqlSessionFactory);
return factory;
}
}
MapperFactoryBean,顾名思义,也就是生产 Mapper 对象实例的工厂,这里向容器中注入了 Mapper 对象工厂,在获取 Mapper 实例时,则通过对应的 Mapper 对象工厂来创建
注册配置类,启动容器
GenericApplicationContext context = new GenericApplicationContext();
// 注册配置类
context.registerBean("config", MapperConfig.class);
// 自定义的 @Bean 工厂后处理器
context.registerBean(AtBeanBeanFactoryPostProcessor.class);
context.refresh();
Arrays.stream(context.getBeanDefinitionNames()).forEach(System.out::println);
Mapper1 bean = context.getBean(Mapper1.class);
System.out.println(bean);
context.close();
需要注意的是,这里使用了自定义的
@Bean
工厂后处理器AtBeanBeanFactoryPostProcessor
,这里面的配置类目前是写死的,需要手动将AtBeanBeanFactoryPostProcessor
中@Bean
涉及到的相关配置类修改为context.registerBean("config", MapperConfig.class);
中注册的配置类:MapperConfig
;注册的配置类和
AtBeanBeanFactoryPostProcessor
实际使用的配置类,二者需要保持一致,使得AtBeanBeanFactoryPostProcessor
会扫描Mappconfig
中的@Bean
,加载对应的组件到容器中因为
AtBeanBeanFactoryPostProcessor
中,有两处关键位置使用到了对应的配置类
factory.getMetadataReader(MapperConfig.class.getName());
加载配置类的元数据信息,目的是为了得到其中标注了@Bean
的工厂方法(通过 class 找到对应的配置类)builder.setFactoryMethodOnBean(method.getMethodName(), "config");
设置创建@Bean
申明的组件时,其调用的对应的工厂方法(通过指定配置类注册时的名称:config
,找到对应的配置类,)针对以上两种情况,如果注册时的配置类与实际使用时的配置类不一致,则可能会出现当前得到的工厂方法名称,在另一个配置类中不存在等异常情况
自定义 @Mapper 工厂后处理器
首先是定义 Mapper 配置类,其中包含 Mapper 实例创建所必须的组件,如下
import com.alibaba.druid.pool.DruidDataSource;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
@MapperScan(basePackages = "com.example.spring.mapper")
@Configuration
public class MapperScanConfig {
@Bean(initMethod = "init")
public DruidDataSource dataSource() {
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setUrl("jdbc:mysql://localhost:3306/xlf_db");
druidDataSource.setUsername("root");
druidDataSource.setPassword("111111");
return druidDataSource;
}
@Bean
public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource);
return sqlSessionFactoryBean;
}
}
接下来便是实现自定义的 MapperBeanFactoryProcessor,如下代码
import com.example.spring.config.MapperScanConfig;
import org.apache.ibatis.annotations.Mapper;
import org.mybatis.spring.annotation.MapperScan;
import org.mybatis.spring.mapper.MapperFactoryBean;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.context.annotation.AnnotationBeanNameGenerator;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.type.ClassMetadata;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.util.Assert;
/**
* 自定义的 Mapper 工厂后处理器
*/
public class MapperBeanFactoryPostProcessor implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
try {
// 资源解析器
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
// 元数据读取器
CachingMetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory();
MapperScan mapperScan = AnnotationUtils.findAnnotation(MapperScanConfig.class, MapperScan.class);
AnnotationBeanNameGenerator beanNameGenerator = new AnnotationBeanNameGenerator();
Assert.notNull(mapperScan, "not null");
String[] basePackages = mapperScan.basePackages();
for (String basePackagePath : basePackages) {
String resourcePath = "classpath*:" + basePackagePath.replace(".", "/") + "/**/*.class";
// 加载类路径下的资源
Resource[] resources = resolver.getResources(resourcePath);
for (Resource resource : resources) {
MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(resource);
ClassMetadata classMetadata = metadataReader.getClassMetadata();
boolean hasMapper = metadataReader
.getAnnotationMetadata()
.hasAnnotation(Mapper.class.getName());
boolean isInterface = classMetadata.isInterface();
// 标注了 @Mapper 注解并且为接口类型,才可将其作为 Mapper 接口
if (hasMapper && isInterface) {
/*
在容器中为对应的 Mapper 接口,创建其对应的 MapperFactoryBean
*/
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder
.genericBeanDefinition(MapperFactoryBean.class)
// 设置当前 MapperFactoryBean 对应的 Mapper
.addConstructorArgValue(classMetadata.getClassName())
// 设置当前 MapperFactoryBean 对应的装配模式
// 因为 MapperFactoryBean 实例创建时,还需要其他
// 实例参数,这里通过自动装配的方式进行设置
.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE)
.getBeanDefinition();
/*
注意,这里如果直接使用当前的 beanDefinition 生成 beanName
会出现名称重复的情况(因为一直使用的是 MapperFactoryBean.class 来生成的 beanDefinition)
最终导致容器中只会注入一个 beanDefinition
spring 的解决办法为使用当前 Mapper 接口生成一个 beanDefinition
使用此 beanDefinition 专门来生成 beanName,
这样每个 Mapper 接口都有其对应的 MapperFactoryBean 了
*/
BeanDefinition mapperBd = BeanDefinitionBuilder
.genericBeanDefinition(classMetadata.getClassName())
.getBeanDefinition();
String beanName = beanNameGenerator.generateBeanName(mapperBd, registry);
// 注册 MapperFactoryBean 到容器
registry.registerBeanDefinition(beanName, beanDefinition);
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
}
}
Aware && InitializingBean
Aware 接口
org.springframework.beans.factory.Aware
接口主要用于注入一些与容器相关的信息,官方文档对其定义如下
一个标记性的超接口,表明 Bean 有资格被 Spring 容器通过回调式方法通知到某个特定的框架对象。实际的方法签名由各个子接口决定,但通常应该只由一个接受单一参数的无返回值的方法组成
例如Aware
接口的子接口org.springframework.beans.factory.BeanNameAware
,通过实现此接口,当前组件便可以通过其回调方法得到自身在 spring 容器中的名字,如下代码
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class MyBean implements BeanNameAware {
@Override
public void setBeanName(String name) {
log.info("---{}", name);
}
}
再比如org.springframework.beans.factory.BeanFactoryAware
接口,则可以将BeanFactory
容器注入到当前组件中来
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class MyBean implements BeanFactoryAware {
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
log.info("---{}", beanFactory.toString());
}
}
除此之外,还有如下一些常见的Aware
接口,包括
org.springframework.context.ApplicationContextAware
接口,用于注入ApplicationContext
容器
org.springframework.context.EmbeddedValueResolverAware
接口,用于注入${}
等表达式的解析器
InitializingBean 接口
关于org.springframework.beans.factory.InitializingBean
接口,官方文档对其定义如下
由需要在 BeanFactory 设置所有属性后做出反应的 Bean 实现的接口:例如,执行自定义的初始化,或者仅仅是检查所有必须的属性是否已经被设置。
关键在于,InitializingBean
接口中定义的方法,是在所有的属性设置完成后执行的,此时当前组件所有的属性已经准备完成了,利用这一条件,可以继续定义一些增强逻辑,也就是在正式使用组件之前,执行的一些初始化操作
例如凭借InitializingBean
接口,将配置文件中解析出的值赋给静态变量,示例代码如下
首先在配置文件中定义一个值
server.host=127.0.0.1
现在想要通过静态变量的方式,直接拿到配置文件中的值,可能会有如下写法
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class MyBean {
@Value("${server.host}")
public static String HOST;
}
但最终HOST
的值为null
,因为 spring 并不支持对静态字段使用@Autowired
注入,某些情况下,还可能打印类似错误信息Autowired annotation is not supported on static fields
此时便可以借助InitializingBean
接口,来实现静态变量从配置文件中取值的效果,如下代码
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class MyBean implements InitializingBean {
@Value("${server.host}")
private String hostValue;
public static String HOST;
@Override
public void afterPropertiesSet() throws Exception {
HOST = this.hostValue;
}
}
首先hostValue
是普通的成员变量,通过@Value
注解能够正常得到值,到目前为止,都可以看作 bean 生命周期的【依赖注入】阶段
而InitializingBean
接口则保证在其提供的afterPropertiesSet()
执行时(初始化阶段),所有的属性都已经设置完毕,这时就可以自然的将hostValue
的值赋给HOST
,当整个【初始化阶段】执行完成,组件才可以正常使用,不会存在赋值之前就调用导致拿到空值的情况
内置功能
实际上,不论是Aware
还是InitializingBean
接口,都有其对应的替代方式,例如Aware
接口的注入功能,使用@Autowired
便可以满足大部分的场景且功能更加丰富
InitializingBean
接口在初始阶段执行的逻辑,使用@PostConstruct
来标注相同的逻辑的方法,也能达到同样的效果
但@Autowired
和@PostConstruct
注解提供的功能,都属于 spring 的 扩展功能(需要各种后处理器来实现),Aware
和InitializingBean
则属于 spring 的 内置功能,最大的区别在于,内置功能是不会失效的,而扩展功能在某些情况下则会失效
@Autowired 失效分析
前面已经说过,spring 的内置功能在某些情况下可能会失效,下面则是@Autowired
失效情况的演示
定义配置类
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
@Configuration
@Slf4j
public class MyConfig {
@Autowired
public void setApplicationContext(ApplicationContext applicationContext) {
log.info("application bean-----{}", applicationContext);
}
@PostConstruct
public void init() {
log.info("myConfig init-------");
}
}
一个基本的 spring 容器启动流程
import com.example.spring.config.MyConfig;
import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor;
import org.springframework.context.annotation.CommonAnnotationBeanPostProcessor;
import org.springframework.context.annotation.ConfigurationClassPostProcessor;
import org.springframework.context.support.GenericApplicationContext;
import java.util.Arrays;
/**
* Autowired 失效测试
*/
public class AutoWiredInvalidTest {
public static void main(String[] args) {
test1();
}
public static void test1() {
GenericApplicationContext context = new GenericApplicationContext();
context.registerBean("myConfig", MyConfig.class);
// 对 @Autowired、@Value 注解提供支持
context.registerBean(AutowiredAnnotationBeanPostProcessor.class);
// 对 @PostConstruct 注解提供支持
context.registerBean(CommonAnnotationBeanPostProcessor.class);
// 对 @Configuration、@Bean 等注解提供支持
context.registerBean(ConfigurationClassPostProcessor.class);
context.refresh();
Arrays.stream(context.getBeanDefinitionNames()).forEach(System.out::println);
}
}
观察控制台的打印信息如下,不出意外,此时的@Autowired
和@PostConstruct
已经生效
application bean---org.springframework.context.support.GenericApplicationContext@768b970c
com.example.spring.config.MyConfig - myConfig init-------
myConfig
org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor
org.springframework.context.annotation.CommonAnnotationBeanPostProcessor
org.springframework.context.annotation.ConfigurationClassPostProcessor
修改配置类
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
@Configuration
@Slf4j
public class MyConfig {
@Autowired
public void setApplicationContext(ApplicationContext applicationContext) {
log.info("application bean-----{}", applicationContext);
}
@PostConstruct
public void init() {
log.info("myConfig init-------");
}
@Bean
public BeanFactoryPostProcessor processor1() {
return beanFactory -> {
log.info("processor1 exec----");
};
}
}
再次执行,发现新增的BeanFactoryPostProcessor
确实生效了,但是@Autowired
和@PostConstruct
注解相关的功能以及对应的后处理器提供的扩展功能却失效了,要想探究失效的具体原因,需要先对 spring 启动过程中的refresh()
流程有一个基本了解,这里分为两种情况,如下
配置类中不包含BeanFactoryPostProcessor
时,配置类的创建情况
配置类中包含BeanFactoryPostProcessor
时,配置类的创建情况,如下
...... 省略
@Bean
public BeanFactoryPostProcessor processor1() {
return beanFactory -> {
log.info("processor1 exec----");
};
}
...... 省略
此时的配置类中通过@Bean
向容器中注入了BeanFactoryPostProcessor
,而BeanFactoryPostProcessor
想要创建成功,就先要 提前 创建配置类,注意这里配置类的创建被 提前 到了refresh()
流程中的【执行 BeanFactoryPostProcessor】阶段,而此时还没有到@Autowired
和@PostConstruct
等对应的 扩展功能 执行的阶段
最终导致:当配置类中存在BeanFactoryPostProcessor
注入时,当前配置类中其余后执行的扩展逻辑失效
但就算是配置类被提前创建,假如配置类实现了Aware
或InitializingBean
接口,在配置类创建之前,其中对应的Aware
或InitializingBean
的回调方法还是会被正常执行(如上图)
这便是Aware
等内置扩展的优势,它总是会被执行,不用担心失效的问题,这也是 spring 框架的内部类常用的注入和初始化方式
初始化 && 销毁
初始化
spring 中提供了多种 bean 初始化的方式,如下代码
import com.example.spring.bean.init.Bean1;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MyConfig {
@Bean(initMethod = "init3") // 方式一:通过 @Bean 的 initMethod 指定初始化方法(方法由 Bean1 自身提供)
public Bean1 bean1() {
return new Bean1();
}
}
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import javax.annotation.PostConstruct;
@Slf4j
public class Bean1 implements InitializingBean {
// 方式二:被 @PostConstruct 标识的方法可以作为初始化方法
@PostConstruct
public void init1() {
log.info("PostConstruct-----");
}
// 方式三:实现 InitializingBean 的 afterPropertiesSet 方法,也会被作为初始化逻辑执行
@Override
public void afterPropertiesSet() throws Exception {
log.info("afterPropertiesSet-----");
}
public void init3() {
log.info("init3-----");
}
}
至于这三种初始化方式的执行顺序,通过如下打印信息可以得出
PostConstruct-----
afterPropertiesSet-----
init3-----
销毁
多种销毁方式的示例代码如下
import com.example.spring.bean.destroy.Bean1;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MyConfig {
@Bean(destroyMethod = "destroyMethod") // 方式一:通过 @Bean 注解的 destroyMethod 指定销毁方法(方法由 Bean1 自身提供)
public Bean1 bean1() {
return new Bean1();
}
}
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.DisposableBean;
import javax.annotation.PreDestroy;
@Slf4j
public class Bean1 implements DisposableBean {
// 方式二:被 @PreDestroy 标识的方法可以作为销毁方法
@PreDestroy
public void method1() {
log.info("PreDestroy-----");
}
// 方式三:实现 DisposableBean 的 destroy 方法,也会被作为销毁逻辑执行
@Override
public void destroy() throws Exception {
log.info("DisposableBean-----");
}
public void destroyMethod() {
log.info("destroyMethod-----");
}
}
三种销毁方式的执行顺序如下
PreDestroy-----
DisposableBean-----
destroyMethod-----
Scope
scope 简介
spring 中的 scope,指的是 bean 的作用域,《Spring 揭秘》对其的解释如下
scope 用来声明容器中的对象所应该处的限定场景或者说该对象的存活时间,即容器在对象进入其相应的 scope 之前生成并装配这些对象,在该对象不再处于这些 scope 的限定之后,容器通常会销毁这些对象
简单来说,scope 就是 bean 的有效范围,超出该范围,bean 将不再可用或被销毁
spinng 目前主要有五种 scope,如下
singleton
:单例模式,全局只有一个 bean,可通过ConfigurableBeanFactory.SCOPE_SINGLETON
得到此模式对应的常量值,singleton
也是 spring 中默认的 scope;singleton scope
下的 bean 会在 spring 容器关闭时被销毁prototype
:原型模式,每次获取 bean 时,都会返回一个新的实例,可通过ConfigurableBeanFactory.SCOPE_PROTOTYPE
得到此模式对应的常量值
以下三种,都针对 web 应用程序
request
:此模式下,每次 http 请求,都会产生一个新的 bean 且仅在当前 http request 内有效,请求结束后,旧的 bean 会被销毁;可通过WebApplicationContext.SCOPE_REQUEST
得到此模式的常量值session
:此模式下的 bean,在当前 session 会话内有效,每次新的会话都会产生新的 bean,如果 session 超时(可通过setMaxInactiveInterval()
设置 session 超时时间),则旧的 bean 会被销毁;可通过WebApplicationContext.SCOPE_SESSION
得到此模式的常量值application
:application 作用域,用于将 bean 的生命周期绑定到当前 web 应用程序(在当前 web 应用程序内有效,除非 web 应用重启);可以通过WebApplicationContext.SCOPE_SESSION
得到此模式的常量值,或者直接通过@ApplicationScope
设置当前的 bean 为 application scope
scope 失效
在 spring 中,一个 singleton 的 bean,想要注入其他 scope 的 bean,是会存在问题的,如下代码
一个 singleton scope 的 bean
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
@Component
public class SingletonBean {
// 在 singletion scope 的 bean 中注入其他 scope 的 bean
@Autowired
private PrototypeBean prototypeBean;
public PrototypeBean getPrototypeBean() {
return prototypeBean;
}
}
一个 prototype scope 的 bean
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Component
public class PrototypeBean {
}
启动 spring 容器,查看打印结果
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class MainTest {
public static void main(String[] args) {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext("com.example.spring.bean.scope.invalid");
for (int i = 0; i < 2; i++) {
SingletonBean singletonBean = context.getBean(SingletonBean.class);
System.out.println(String.valueOf(i + 1) + "----" + singletonBean.getPrototypeBean());
}
}
}
1----com.example.spring.bean.scope.invalid.PrototypeBean@176d53b2
2----com.example.spring.bean.scope.invalid.PrototypeBean@176d53b2
可以看到,多此获取一个prototype scope
的 bean,返回的却是相同的地址值,PrototypeBean
的 scope 失效了
原因在于SingletonBean
本身是单例的,它的依赖注入只会发生一次,其依赖的PrototypeBean
也只会被注入一次,后续没有其他地方再用到PrototypeBean
,PrototypeBean
也不会再重新生成,所以每次获取的都是相同的PrototypeBean
scope 失效解决
@Lazy
@Lazy
用于指示是否要延迟初始化 bean,使用@Lazy
注解标识一个成员变量或者该成员变量的set()
,后续每次使用到该成员变量时,都会通过代理创建新对象,达到prototype scope
的效果
proxyMode
在@Scope
注解中,指定proxyMode
,也可以配置当前 bean 使用代理,如下
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.stereotype.Component;
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE, proxyMode = ScopedProxyMode.TARGET_CLASS)
@Component
public class PrototypeBean {
}
ScopedProxyMode.TARGET_CLASS
,创建基于类的代理(使用CGLIB)
ObejctFactory
通过注入一个ObjectFactory
来获取目标 bean,每次获取,ObjectFactory
都会返回一个新的目标 bean,如下代码
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
@Component
public class SingletonBean {
// 通过泛型指定要通过 ObjectFactory 获取的目标 bean 类型
@Autowired
private ObjectFactory<PrototypeBean> factory;
public PrototypeBean getPrototypeBean() {
// 每次返回一个新的目标 bean
return factory.getObject();
}
}
ApplicationContext
直接通过 spring 容器获取目标 bean,因为每次都重新获取,不会导致 scope 失效,如下代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
@Component
public class SingletonBean {
@Autowired
private ApplicationContext context;
public PrototypeBean getPrototypeBean() {
return context.getBean(PrototypeBean.class);
}
}
Aop
aop 实现之 aspectJ 增强
AOP 是 Aspect Oriented Programming 的缩写,即面向切面编程;AOP 的作用在于将公共逻辑进行抽取,在目标方法执行的各个阶段(切入点),执行指定的逻辑,达到代码复用,提升一定的开发效率
目前最常见的 aop 实现方式为【代理】,但实际上还有其他几种方式来实现,首先是 aspectJ 代理增强,aspectJ 直接在编译阶段修改 class 文件织入增强逻辑,属于静态 AOP 框架,使用如下
aspectJ 静态织入的实现,需要对应的 aspectJ 编译器支持,可以通过 maven 依赖和插件进行引入
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
</dependency>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.14.0</version>
<configuration>
<complianceLevel>1.8</complianceLevel>
<source>8</source>
<target>8</target>
<showWeaveInfo>true</showWeaveInfo>
<verbose>true</verbose>
<Xlint>ignore</Xlint>
<encoding>UTF-8</encoding>
</configuration>
<executions>
<execution>
<goals>
<!-- use this goal to weave all your main classes -->
<goal>compile</goal>
<!-- use this goal to weave all your test classes -->
<goal>test-compile</goal>
</goals>
</execution>
</executions>
</plugin>
切面类
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class MyAspectJ {
@Before("execution(* com.example.aop.aspectj_demo.MyService.hello(..))")
public void before() {
System.out.println("before");
}
}
业务类
public class MyService {
public void hello() {
System.out.println("hello");
}
}
测试
public class MainTest {
public static void main(String[] args) {
MyService service = new MyService();
service.hello();
}
}
注意,如果使用 idea 运行,可能 aspectJ 插件不会生效,使用 maven compile 命令手动编译后再执行即可
观察编译后的MyService
的 class 文件,如下
public class MyService {
public MyService() {
}
public void hello() {
MyAspectJ.aspectOf().before();
System.out.println("hello");
}
}
aspectJ 直接将增强的逻辑织入进了 class 文件中,而不是通过代理对象的方式进行增强
同时 aspectJ 还支持对静态方法进行增强,如下代码
在业务类中新增静态方法
public class MyService {
public void hello() {
System.out.println("hello");
}
public static void hi(){
System.out.println("hi");
}
}
修改切入点表达式,匹配 MyService 中的所有方法
@Before("execution(* com.example.aop.aspectj_demo.MyService.*(..))")
测试
public class MainTest {
public static void main(String[] args) {
MyService service = new MyService();
service.hello();
service.hi();
}
}
注意静态方法需要通过对象来调用,否则无法进行增强且会抛异常
aop 实现之 agent 类加载
aspectJ 增强是在编译阶段通过修改 class 文件达到增强效果;而 agent 类加载则是在类加载阶段就开始进行 aop 增强,使用演示如下
首先需要去除 pom 文件中,acpectJ 的编辑器插件
准备切面类
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class MyAspectJ {
@Before("execution(* com.example.aop.aspectj_demo.MyService.*(..))")
public void before() {
System.out.println("before");
}
}
准备业务类
public class MyService {
public void hello() {
System.out.println("hello");
// 注意:这里调用了内部的 yes(), 如果是代理实现的 AOP, yes() 不会被增强
yes();
}
public void yes() {
System.out.println("yes");
}
public static void hi() {
System.out.println("hi");
}
}
测试类
public class MainTest {
public static void main(String[] args) {
MyService service = new MyService();
service.hello();
service.hi();
}
}
在 resource 目录或者是项目编译后的 classes 目录下添加META-INF
目录,在其中编写aop.xml
文件
<aspectj>
<aspects>
<aspect name="com.example.aop.aspectj_demo.MyAspectJ"/>
<weaver options="-verbose -showWeaveInfo">
<include within="com.example.aop.aspectj_demo.MyService"/>
<include within="com.example.aop.aspectj_demo.MyAspectJ"/>
</weaver>
</aspects>
</aspectj>
这是用于类加载时,读取相关的切面类以及切入点信息
在 idea 的 vm options 选项中,添加啊如下运行参数(又或者直接通过java -javaagent...
来运行)
# 路径需要根据当前运行环境进行替换
-javaagent:C:\Users\18781\software\MavenWork\maven_repository\org\aspectj\aspectjweaver\1.9.7\aspectjweaver-1.9.7.jar
查看打印结果
before
hello
before
yes
before
hi
通过 arthas 反编译 MyService 后的字节码
这也解释了为什么
yes()
内部调用时能被增强,因为yes()
内部被直接织入了增强逻辑,而非代理模式下,由代理对象来提供增强逻辑
可见,采用类加载的方式实现的 AOP,就算是内部方法的调用,也能织入增强逻辑,这是代理模式的 AOP 所不具备的
aop 代理增强-jdk
使用 jdk 自带的Proxy
类,能够针对接口实现代理增强,代码如下
MyService 接口
public interface MyService {
void hello();
}
MyServiceImpl 接口实现
public class MyServiceImpl implements MyService {
@Override
public void hello() {
System.out.println("Hello");
}
}
jdk 代理演示
import java.lang.reflect.Proxy;
/**
* jdk 的动态代理演示
*/
public class JdkProxyMainTest {
public static void main(String[] args) {
// 目标对象
MyService target = new MyServiceImpl();
// 拿到类加载器,用于加载运行期间动态生成的代理类字节码
// 因为代理类实际上没有源代码,通过直接生成字节码来执行增强逻辑,而字节码的加载,需要类加载器
ClassLoader loader = JdkProxyMainTest.class.getClassLoader();
MyService serviceProxy = (MyService) Proxy.newProxyInstance(loader, new Class[]{MyService.class}, (proxy, methods, params) -> {
// 执行代理对象的前置增强逻辑
System.out.println("before");
// 反射执行目标方法并且返回其执行结果
return methods.invoke(target,params);
});
serviceProxy.hello();
}
}
此时,目标对象
target
与代理对象serviceProxy
之间为平级关系,因为二者都实现了MySevice
接口jdk 的代理是通过接口实现的
aop 代理增强-cglib
cglib 代理演示如下
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
public class CglibProxyMainTest {
static class Target {
public void hello() {
System.out.println("hello");
}
}
public static void main(String[] args) {
Target target = new Target();
// 这里的 proxy 对象,实际上是 Target 的子类实例
// 因为 Target 没有实现或继承其他的接口或类,这里的 proxy 类型只能是 Target 的子类才可以强转成功
Target proxy = (Target) Enhancer.create(Target.class, (MethodInterceptor) (o, method, objects, methodProxy) -> {
System.out.println("before");
// 反射调用目标方法
return method.invoke(target,objects);
});
proxy.hello();
}
}
cglib 的代理对象
proxy
与目标对象target
之间为继承关系,这说明 cglib 通过继承目标类,实现代理增强这也限制了目标类不能为
final
的,因为final
类不能被继承,也就无法创建代理对象;同理,目标对象中被增强的方法如果是final
的,那么也不能被增强,因为final
修饰的方法无法被子类重写,代理对象的增强逻辑无法织入
cglib 还提供了通过methodProxy
调用目标方法的形式(spring 中也使用这种方式),可以避免反射调用目标方法,如下
// 不使用反射
methodProxy.invoke(target,objects);
jdk 代理实现原理
jdk 代理主要通过创建与目标对象实现同一接口的代理对象,使用代理对象执行目标方法,实现代理增强,下面是模拟 jdk 代理实现的示例代码
创建接口
public interface MyService {
void hello();
void hi(String msg);
}
因为代理对象中具体的增强逻辑以及需要调用的目标方法都是不确定的,所以需要定义一个接口将其抽象出来,在创建代理对象时,传入此接口的默认实现,后续代理对象便可直接调用执行接口实现中自定义的增强逻辑
import java.lang.reflect.Method;
/**
* 代理对象调用目标方法的处理程序抽象接口
*/
public interface InvocationHandler {
/**
* 通过此接口方法的具体实现,传入代理增强的逻辑以及调用的目标方法反射对象及其参数
* 因为接口中可能有多个目标方法,需要通过传入 Method 准确调用
*
* @param proxy 代理对象本身
* @param method 被增强的目标方法
* @param params 目标方法参数
* @return 返回目标方法调用后的返回值
*/
Object invoke(Object proxy, Method method, Object... params);
}
手动定义代理对象(实际上 jdk 的代理对象是在底层动态生成的字节码)
import java.lang.reflect.Method;
import java.lang.reflect.UndeclaredThrowableException;
/**
* 模拟实现 jdk 代理对象,与目标对象一样,都实现了 MyService 接口
*/
public class $Proxy0 implements MyService {
/**
* 目标方法调用处理程序
*/
private final InvocationHandler handler;
/**
* 方法反射对象,申明为 static,在类加载时初始化一次
* 后续每次调用代理方法时,无需再次创建
*/
private static final Method hiMethod;
private static final Method helloMethod;
static {
try {
hiMethod = MyService.class.getDeclaredMethod("hi", String.class);
helloMethod = MyService.class.getDeclaredMethod("hello");
} catch (NoSuchMethodException e) {
// 需要转化为运行时异常抛出
throw new RuntimeException(e);
}
}
/**
* 创建代理对象时,通过 handler 传入具体的代理增强逻辑
*
* @param handler 目标方法调用处理程序
*/
public $Proxy0(InvocationHandler handler) {
this.handler = handler;
}
@Override
public String hello() {
try {
// 拿到目标方法的反射对象
return (String) handler.invoke(this, helloMethod);
} catch (RuntimeException | Error e) {
throw new RuntimeException(e);
} catch (Throwable e) {
throw new UndeclaredThrowableException(e);
}
}
@Override
public String hi(String msg) {
try {
// 调用指定的目标方法并且传入参数
return (String) handler.invoke(this, hiMethod, msg);
} catch (RuntimeException | Error e) {
throw e;
} catch (Throwable e) {
throw new UndeclaredThrowableException(e);
}
}
}
演示
/**
* 模拟 jdk 动态代理的实现原理
*/
public class MainDemo {
/**
* 目标对象实现了 MyService 接口
*/
static class Target implements MyService {
@Override
public String hello() {
System.out.println("hello");
return "hello";
}
@Override
public String hi(String msg) {
System.out.println(msg);
return msg;
}
}
public static void main(String[] args) {
// 被代理的目标对象
Target targetObj = new Target();
// 创建代理对象并且自定义代理增强逻辑
$Proxy0 proxy = new $Proxy0((p, method, params) -> {
System.out.println("before");
try {
return method.invoke(targetObj, params);
} catch (Exception e) {
throw new RuntimeException(e);
}
});
// 通过代理对象调用目标方法
proxy.hi("hi!");
}
}
实际上,除了上述手动实现 handler 接口的细节方式外,自定义的代理对象,也可以直接继承java.lang.reflect.Proxy
,这是 jdk 提供的代理类且提供了对应的 handler 接口以及构造函数,如下
public class Proxy implements java.io.Serializable {
private static final long serialVersionUID = -2222568056686623797L;
/** parameter types of a proxy class constructor */
private static final Class<?>[] constructorParams =
{ InvocationHandler.class };
/**
* a cache of proxy constructors with
* {@link Constructor#setAccessible(boolean) accessible} flag already set
*/
private static final ClassLoaderValue<Constructor<?>> proxyCache =
new ClassLoaderValue<>();
/**
* the invocation handler for this proxy instance.
* @serial
*/
protected InvocationHandler h;
/**
* Prohibits instantiation.
*/
private Proxy() {
}
/**
* Constructs a new {@code Proxy} instance from a subclass
* (typically, a dynamic proxy class) with the specified value
* for its invocation handler.
*
* @param h the invocation handler for this proxy instance
*
* @throws NullPointerException if the given invocation handler, {@code h},
* is {@code null}.
*/
protected Proxy(InvocationHandler h) {
Objects.requireNonNull(h);
this.h = h;
}
// ......
这就无需我们手动去实现 handler 接口了,使用演示如下
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
// 继承 Proxy
public class $Proxy1 extends Proxy implements MyService {
// 对父类的 handler 接口赋值
public $Proxy1(InvocationHandler h) {
super(h);
}
private static final Method hiMethod;
private static final Method helloMethod;
// 初始化目标方法反射对象
static {
try {
hiMethod = MyService.class.getDeclaredMethod("hi", String.class);
helloMethod = MyService.class.getDeclaredMethod("hello");
} catch (NoSuchMethodException e) {
// 需要转化为运行时异常抛出
throw new RuntimeException(e);
}
}
@Override
public String hello() {
try {
// 通过父类中的 handler 接口实现调用目标方法
return (String) h.invoke(this, helloMethod, new Object[]{});
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
@Override
public String hi(String msg) {
try {
return (String) h.invoke(this, hiMethod, new Object[]{msg});
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) {
MainDemo.Target target = new MainDemo.Target();
$Proxy1 proxy1 = new $Proxy1((proxy, method, params) -> {
System.out.println("before");
return method.invoke(target, params);
});
proxy1.hi("hi");
}
}
jdk 代理源码分析
前面已经梳理了 jdk 代理的实现原理,但实际上 jdk 代理动态生成的代理类的具体源码,我们无法直接查看到,需要借助arthas
工具进行查看
arthas
需要对应的 java 程序保持运行,通过如下代码使 main 方法执行后,继续在控制台等待读取指令
try {
int b = System.in.read();
} catch (IOException e) {
throw new RuntimeException(e);
}
运行arthas-boot.jar
,输入指令jad com.sun.proxy.$Proxy0
,便可查看对应的代理类反编译的字节码文件,如下
/*
* Decompiled with CFR.
*
* Could not load the following classes:
* com.example.aop.proxy.MyService
*/
package com.sun.proxy;
import com.example.aop.proxy.MyService;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class $Proxy0
extends Proxy
implements MyService {
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m0;
public $Proxy0(InvocationHandler invocationHandler) {
super(invocationHandler);
}
// 初始化目标方法的反射对象,jdk 实现的代理中,额外还初始化了 equals hashCode 等方法
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m3 = Class.forName("com.example.aop.proxy.MyService").getMethod("hello", new Class[0]);
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
return;
}
catch (NoSuchMethodException noSuchMethodException) {
throw new NoSuchMethodError(noSuchMethodException.getMessage());
}
catch (ClassNotFoundException classNotFoundException) {
throw new NoClassDefFoundError(classNotFoundException.getMessage());
}
}
public final boolean equals(Object object) {
try {
return (Boolean)this.h.invoke(this, m1, new Object[]{object});
}
catch (Error | RuntimeException throwable) {
throw throwable;
}
catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
public final String toString() {
try {
return (String)this.h.invoke(this, m2, null);
}
catch (Error | RuntimeException throwable) {
throw throwable;
}
catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
public final int hashCode() {
try {
return (Integer)this.h.invoke(this, m0, null);
}
catch (Error | RuntimeException throwable) {
throw throwable;
}
catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
public final void hello() {
try {
// 通过 handler 的 invoke() 调用目标方法 m3
this.h.invoke(this, m3, null);
return;
}
catch (Error | RuntimeException throwable) {
throw throwable;
}
catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
}
cglib 代理实现原理
cglib 代理的实现与 jdk 代理实现大同小异,都是将代理逻辑抽象为接口,在代理对象中通过接口调用具体的代理逻辑,如下代码
定义业务类
public class MyService {
public void save() {
System.out.println("save()");
}
public void save(int i) {
System.out.println("saveInt-" + i);
}
public void save(long i) {
System.out.println("saveLong-" + i);
}
}
定义代理类
import org.springframework.cglib.proxy.MethodInterceptor;
import java.lang.reflect.Method;
// 代理对象通过继承的方式实现代理
public class Proxy0 extends MyService {
private final MethodInterceptor methodInterceptor;
private static final Method saveMethod0;
private static final Method saveMethod1;
private static final Method saveMethod2;
// 初始化目标代理对象中的反射方法对象
static {
try {
saveMethod0 = MyService.class.getDeclaredMethod("save");
saveMethod1 = MyService.class.getDeclaredMethod("save", int.class);
saveMethod2 = MyService.class.getDeclaredMethod("save", long.class);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
public Proxy0(MethodInterceptor methodInterceptor) {
this.methodInterceptor = methodInterceptor;
}
@Override
public void save() {
try {
// 通过 MethodInterceptor 的实现来调用代理方法
methodInterceptor.intercept(this, saveMethod0, new Object[]{}, null);
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
@Override
public void save(int i) {
try {
methodInterceptor.intercept(this, saveMethod1, new Object[]{i}, null);
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
@Override
public void save(long i) {
try {
methodInterceptor.intercept(this, saveMethod2, new Object[]{i}, null);
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
}
测试
public class MainTest {
public static void main(String[] args) {
MyService myService = new MyService();
Proxy0 proxy0 = new Proxy0((o, method, objects, methodProxy) -> {
System.out.println("before");
return method.invoke(myService, objects);
});
proxy0.save(5);
}
}
spring 代理选择规则
在 spring 中,不论是 jdk 代理还是 cglib 代理都有所使用,那么什么时候使用 jdk 代理,什么时候使用 cglib,在 spring 中都有一定的规则,如下代码
定义接口
public interface MyIService {
void hello();
}
定义实现
public class MyService implements MyIService{
@Override
public void hello() {
System.out.println("hello");
}
}
测试类
import org.aopalliance.intercept.MethodInterceptor;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.DefaultPointcutAdvisor;
public class MainDemo {
public static void main(String[] args) {
test1();
}
public static void test1() {
// 创建切点对象
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
// 设置切点表达式
pointcut.setExpression("execution(* hello())");
// 创建通知对象
MethodInterceptor advice = methodInvocation -> {
System.out.println("before");
Object result = methodInvocation.proceed(); // 调用目标
System.out.println("after");
return result;
};
// 创建切面,由一个切点加一个通知构成
DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut, advice);
// 创建代理
ProxyFactory factory = new ProxyFactory();
// 设置目标对象
factory.setTarget(new MyService());
// 添加切面
factory.addAdvice(advice);
// 拿到代理对象(内部决定使用 cglib 或则是 jdk 代理增强,通过查看代理对象 proxy 的类型可知)
MyIService proxy = (MyIService) factory.getProxy();
proxy.hello();
}
}
上述代码为 spring 中底层创建代理对象时采用的方式,具体使用 cglib 还是 jdk 代理,需要查看factory.getProxy()
方法的源码,如下
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
Class<?> targetClass = config.getTargetClass();
if (targetClass == null) {
throw new AopConfigException("TargetSource cannot determine target class: " +
"Either an interface or a target is required for proxy creation.");
}
// jdk 动态代理
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
// cglib 动态代理
return new ObjenesisCglibAopProxy(config);
}
else {
// jdk 动态代理
return new JdkDynamicAopProxy(config);
}
}
其中config.isProxyTargetClass()
和hasNoUserSuppliedProxyInterfaces
主要用于确定是否指定了目标类所实现的接口,这在创建代理工厂时可以设置,如下代码
factory.setInterfaces(MyService.class.getInterfaces());
如果指定了目标类实现的接口,此时proxyTargetClass
的默认值为false
,但hasNoUserSuppliedProxyInterfaces
返回值为true
,最终会使用 jdk 的动态代理
如果没有指定目标类实现的接口,在proxyTargetClass
默认值为false
的情况下,会使用cglib
的动态代理
如果在创建ProxyFactory
时,通过factory.setProxyTargetClass(true);
直接指定了proxyTargetClass
的值为true
,那么无论是否设置了目标类的接口,最终都会使用 cglib 动态代理
切点匹配
在 spring 中,通过定义通知对象,指定代理增强的具体逻辑;而通过创建切点对象,指定增强逻辑执行的具体位置;切点和通知相结合,构成了切面,这就好比用一把刀,找准了一个位置(切点),一刀切下去
想要在不同的类的方法中精确的插入增强逻辑,需要指定一个具体规则,这就是切点表达式,spring 提供了多种切入点匹配表达式,比较常用的有execution() 用于匹配方法
、@annotation() 用于匹配注解
,使用都较为简单;同时 spring 还支持自定义切点匹配逻辑,只需要实现org.springframework.aop.MethodMatcher
接口即可,如下代码演示了 spring 中匹配@Transactional
注解的大致原理
定义辅助类
static class T1 {
@Transactional
public void save() {
}
public void query() {
}
}
@Transactional
static class T2 {
public void save() {
}
public void select() {
}
}
自定义 @Transactional 匹配规则
try {
// 通过实现 MethodMatcher 接口,自定义注解匹配规则
StaticMethodMatcherPointcut pt = new StaticMethodMatcherPointcut() {
@Override
public boolean matches(Method method, Class<?> targetClass) {
// MergedAnnotations 能够在指定的范围上搜索注解
MergedAnnotations fromMethod = MergedAnnotations.from(method);
// 检查方法上是否存在 Transactional 注解
if (fromMethod.isPresent(Transactional.class)) {
return true;
}
// 检查类上是否标注了 Transactional 注解(同时直接搜索策略为:TYPE_HIERARCHY,这会同时检查本类,父类以及接口上是否标注了对应注解)
MergedAnnotations fromClass = MergedAnnotations.from(targetClass, MergedAnnotations.SearchStrategy.TYPE_HIERARCHY);
return fromClass.isPresent(Transactional.class);
}
};
// 匹配指定 Class 中的指定方法是否标注了 Transactional
boolean t1SaveIsTransactional = pt.matches(T1.class.getDeclaredMethod("save"), T1.class);
boolean t1QueryIsTransactional = pt.matches(T1.class.getDeclaredMethod("query"), T1.class);
System.out.println(t1QueryIsTransactional);
System.out.println(t1SaveIsTransactional);
boolean t2SaveIsTransactional = pt.matches(T2.class.getDeclaredMethod("save"), T2.class);
boolean t2SelectIsTransactional = pt.matches(T2.class.getDeclaredMethod("select"), T2.class);
System.out.println(t2SaveIsTransactional);
System.out.println(t2SelectIsTransactional);
} catch (Exception e) {
e.printStackTrace();
}
Aspect && Advisor 切面
aspect 切面因为使用方便,称之为高级切面,advisor 则为低级切面,这并非指 advisor 的功能更弱,只不过 advisor 更适合框架内部使用,实际上最终 spring 也会把 aspect 切面转化为 advisor 切面进行处理
以下代码演示了 spring 中如何确定对象是否需要创建代理的基本过程
首先同时定义高级切面和低级切面
@Aspect // 定义高级切面
public static class Aspect1 {
@Before("execution(* save())")
public void before() {
System.out.println("aspect before");
}
@After("execution(* save())")
public void after() {
System.out.println("aspect after");
}
}
@Configuration
static class Config {
// 定义通知
@Bean
public MethodInterceptor advice() {
return invocation -> {
System.out.println("before");
Object proceed = invocation.proceed();
System.out.println("after");
return proceed;
};
}
// 定义低级切面
@Bean
public Advisor advisor(MethodInterceptor advice) {
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression("execution(* query())");
return new DefaultPointcutAdvisor(pointcut, advice);
}
}
// 目标类
public static class Service1 {
public void save() {
System.out.println("save");
}
}
public static class Service2 {
public void query() {
System.out.println("query");
}
}
对于低级切面,一个通知对应一个低级切面;而对于高级切面中定义的多个通知,如上述代码中通过
@Before
和@After
定义的两个通知,spring 最终会将其转换为对应的两个低级切面
启动容器
package org.springframework.aop.framework.autoproxy;
import org.aopalliance.intercept.MethodInterceptor;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.aop.Advisor;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ConfigurationClassPostProcessor;
import org.springframework.context.support.GenericApplicationContext;
GenericApplicationContext context = new GenericApplicationContext();
// 注册配置类(包含低级切面的 bean 配置)
context.registerBean("config", Config.class);
// 注册高级切面 aspectj
context.registerBean("aspect1", Aspect1.class);
// 注册配置类解析的后处理器
context.registerBean(ConfigurationClassPostProcessor.class);
context.registerBean(AnnotationAwareAspectJAutoProxyCreator.class);
context.refresh();
AnnotationAwareAspectJAutoProxyCreator creator =
context.getBean(AnnotationAwareAspectJAutoProxyCreator.class);
// 根据传入的 class,查找所有符合要求的 advisor【低级切面】(前提是容器中已经存在对应的 advisor)
List<Advisor> service1Advisors =
creator.findEligibleAdvisors(Service1.class, "service1");
service1Advisors.forEach(System.out::println);
/*
wrapIfNecessary 会根据 findEligibleAdvisors 调用的情况
确定是否为传入的 bean 创建代理,在 findEligibleAdvisors 返回的
集合为空的情况下,不会创建代理
*/
Object wrap1 = creator.wrapIfNecessary(new Service1(), "service1", "service1");
System.out.println(wrap1.getClass()); // MainTest$Service1$$EnhancerBySpringCGLIB$$20cd8f4b
((Service1) wrap1).save();
注意这里的包路径,为了使用 AnnotationAwareAspectJAutoProxyCreator 中
protected
修饰的方法,当前的测试类被刻意放在了自建的org.springframework.aop.framework.autoproxy
包下
实际上,org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator
会在getBean()
调用流程中,自动完成如wrapIfNecessary()
、findEligibleAdvisors()
等相关调用,创建代理对象返回,因为AnnotationAwareAspectJAutoProxyCreator
的实际类型为一个BeanPostProcessor
,其是在 bean 依赖注入或初始化之后进行的功能增强,其父接口的描述如下
/**
BeanPost处理器的子接口,它添加了实例化之前的回调和实例化之后但在设置显式属性或发生自动连接之前的回调。通常用于抑制特定目标bean的默认实例化,例如创建具有特殊目标源(池化目标、延迟初始化目标等)的代理,或者实现额外的注入策略,如现场注入。注:此接口是一个专用接口,主要供框架内部使用。建议尽可能地实现简单的豆后处理器接口,或者从InstantiationAwareBeanPostProcessorAdapter派生,以避免对该接口的扩展。
*/
public interface InstantiationAwareBeanPostProcessor extends BeanPostProcessor {
因此上述代码又可以简化如下
GenericApplicationContext context = new GenericApplicationContext();
// 注册配置类(包含低级切面的 bean 配置)
context.registerBean("config", Config.class);
// 注册高级切面 aspectj
context.registerBean("aspect1", Aspect1.class);
// 注册配置类解析的后处理器
context.registerBean(ConfigurationClassPostProcessor.class);
context.registerBean(AnnotationAwareAspectJAutoProxyCreator.class);
// 注册目标对象
context.registerBean(Service1.class);
context.refresh();
// 获取目标对象(实际返回代理对象)
Service1 bean = context.getBean(Service1.class);
bean.save();
代理的创建时机
spring 中代理的创建时机主要有两种
其一
@Aspect
static class Aspect1{
@Before("execution(* save())")
public void before(){
}
}
@Component
static class Bean1 {
public void save(){
}
}
@Component
static class Bean2 {
@Autowired
Bean1 bean1;
}
上述代码中的 Bean1 被 Aspect1 织入了增强逻辑,理应为其创建代理对象,同时 Bean2 也依赖注入了 Bean1(实际需要注入 Bean1 的代理对象)
此时的 Bean1 和 Bean2 之间并没有相互依赖关系,因此 Bean1 会按照正常的流程,在其初始化之后,由AnnotationAwareAspectJAutoProxyCreator
创建出代理对象并依赖注入给 Bean2
其二
如果 Bean1 和 Bean2 之间存在依赖关系,如下
@Aspect
static class Aspect1{
@Before("execution(* save())")
public void before(){
}
}
@Component
static class Bean1 {
@Autowired
Bean2 bean2;
public void save(){
}
}
@Component
static class Bean2 {
@Autowired
Bean1 bean1;
}
因为循环依赖的存在,此时 Bean1 和 Bean2 都先进行了实例化;Bean2 如果想要继续实例化(由Bean1 依赖注入 Bean2触发),那么 Bean1 的代理对象就必须提前创建(二级缓存),而不是等到初始化之后再创建
当 Bean1 的代理对象创建完成后,Bean2 才能顺利完成依赖注入及其后续流程;先前 Bean1 依赖注入 Bean2 的动作也得以继续,这说明在循环依赖的情况下,代理对象的创建是在目标对象的依赖注入之前发生的(因为需要确保其他依赖了目标对象的 bean 能够依赖注入得到代理对象)
切面调用流程
通过@Aspect
注解声明的切面,实际上是通过以下流程转化为,才得到最终的调用逻辑
高级切面转低级切面
以下代码主要演示了 spring 将@Aspect
注解标识的高级切面转化为低级切面的基本流程
定义高级切面 & 目标类
@Aspect
public static class Aspect1 {
@Before("execution(* save())")
public void before() {
}
@After("execution(* save())")
public void after() {
}
}
static class Target {
public void save() {
System.out.println("save");
}
}
转为低级切面
import lombok.extern.log4j.Log4j2;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.aop.Advisor;
import org.springframework.aop.aspectj.*;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
List<Advisor> advisors = new ArrayList<>();
// 创建切面对象的工厂,传入当前的切面对象实例
AspectInstanceFactory factory = new SingletonAspectInstanceFactory(new Aspect1());
// 遍历高级切面类 Aspect1 的所有方法
for (Method method : Aspect1.class.getDeclaredMethods()) {
// 判断方法上是否有指定通知注解
if (method.isAnnotationPresent(Before.class)) {
// 创建切点对象
String beforeExecution = method.getAnnotation(Before.class).value();
AspectJExpressionPointcut beforePointcut = new AspectJExpressionPointcut();
beforePointcut.setExpression(beforeExecution);
// 创建 before 通知实例
AspectJMethodBeforeAdvice beforeAdvice = new AspectJMethodBeforeAdvice(method, beforePointcut, factory);
// 创建低级切面
DefaultPointcutAdvisor beforeAdvisor = new DefaultPointcutAdvisor(beforePointcut, beforeAdvice);
advisors.add(beforeAdvisor);
}
// after 通知的解析与 before 一致
if (method.isAnnotationPresent(After.class)) {
// 创建切点对象
String afterExecution = method.getAnnotation(After.class).value();
AspectJExpressionPointcut afterPointcut = new AspectJExpressionPointcut();
afterPointcut.setExpression(afterExecution);
// 创建 after 通知实例
AspectJAfterAdvice afterAdvice =
new AspectJAfterAdvice(method, afterPointcut, factory);
// 创建低级切面
DefaultPointcutAdvisor afterAdvisor = new DefaultPointcutAdvisor(afterPointcut, afterAdvice);
advisors.add(afterAdvisor);
}
}
advisors.forEach(System.out::println);
统一转换为环绕通知
对于一个目标对象,存在多个通知的情况很常见,如果只是单纯的将所有涉及到的通知收集起来简单调用,则不能达到前置或后置通知的效果,因此 spring 会将所有的通知都统一转换为环绕通知,由外及内,再由内到外的调用,如下图
而所谓环绕通知,实际上就是实现了MethodInterceptor
接口的通知类,本身就实现了此接口的通知类型,将不会被转换,例如AspectJAroundAdvice
、AspectJAfterAdvice
、AspectJAfterThrowingAdvice
这几个通知本身就是环绕通知
统一转换为环绕通知,可以借助ProxtFactory
实现,代码如下
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setTarget(new Target());
// 需要事先设置一个 ExposeInvocationInterceptor 通知(高优先级),这用于在所有环绕通知调用前将
// MethodInvocation 实例设置进当前线程(ThreadLocal),方便链式调用时获取
proxyFactory.addAdvice(ExposeInvocationInterceptor.INSTANCE);
// 设置通知集合
proxyFactory.addAdvisors(advisors);
// 统一转换为环绕通知,返回转换后的集合
List<Object> methodInterceptorList = proxyFactory.getInterceptorsAndDynamicInterceptionAdvice(
Target.class.getMethod("save"),
Target.class
);
通过打印集合信息可以得知,统一转换后的非环绕通知@AfterReturning
和@Before
被分别转换为了AfterReturningAdviceInterceptor
和MethodBeforeAdviceInterceptor
两种类型,实际上就是对应通知的包装类,例如@Before
的环绕通知包装类MethodBeforeAdviceInterceptor
的源码如下
// 依旧实现了 MethodInterceptor 接口
public class MethodBeforeAdviceInterceptor implements MethodInterceptor, BeforeAdvice, Serializable {
// 内部组合了一个 MethodBeforeAdvice 实例
private final MethodBeforeAdvice advice;
public MethodBeforeAdviceInterceptor(MethodBeforeAdvice advice) {
Assert.notNull(advice, "Advice must not be null");
this.advice = advice;
}
// 实际调用时,在目标方法之前调用前置通知的之前逻辑
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis());
// 链式调用(责任链模式)
return mi.proceed();
}
}
转换为环绕通知后,便可以利用环绕通知集合,创建MethodInterceptor
实例,实现链式调用
// 通过创建 MethodInvocation 实例,实现链式调用
MethodInvocation methodInvocation = new ReflectiveMethodInvocation(
null,
new Target(),
Target.class.getMethod("save"),
new Object[]{},
Target.class,
methodInterceptorList // 环绕通知列表
);
try {
methodInvocation.proceed();
} catch (Throwable e) {
throw new RuntimeException(e);
}
模拟通知调用链
为了更好的理解不同类型通知的调用逻辑,下面将模拟实现通知调用逻辑,代码如下
定义目标类
public class Target {
public String save() {
System.out.println("save");
// 模拟异常情况,用于观察异常通知逻辑
//int x = 10 / 0;
return "success";
}
}
实现自定义的 MethodInvocation
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Method;
import java.util.List;
public class CustomMethodInvocation implements MethodInvocation {
// 通知集合
private final List<MethodInterceptor> interceptors;
// 目标对象
private final Object target;
// 目标方法参数
private final Object[] args;
// 目标方法
private final Method targeMethod;
// 当前调用次数统计,也是 interceptors 的调用指针
private int count;
public CustomMethodInvocation(List<MethodInterceptor> interceptors,
Object target,
Method targeMethod,
Object[] args) {
this.interceptors = interceptors;
this.target = target;
this.targeMethod = targeMethod;
this.args = args;
}
@Override
public Method getMethod() {
return this.targeMethod;
}
@Override
public Object[] getArguments() {
return this.args;
}
@Override
public Object proceed() throws Throwable {
if (count > interceptors.size() - 1) {
// 目标方法调用
return targeMethod.invoke(target, args);
}
MethodInterceptor interceptor = interceptors.get(count);
count++;
// 调用下一个 interceptor,实际上是通过 this 间接的递归调用当前实例的 proceed()
return interceptor.invoke(this);
}
@Override
public Object getThis() {
return this.target;
}
@Override
public AccessibleObject getStaticPart() {
return this.targeMethod;
}
}
实现不同通知类型的自定义 MethodInterceptor
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
/**
* 自定义实现后置通知
*/
public class AfterMethodInterceptor implements MethodInterceptor {
InterceptorExec exec;
public AfterMethodInterceptor(InterceptorExec exec) {
this.exec = exec;
}
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
try {
return invocation.proceed();
} finally {
// 始终在目标方法调用完后执行通知逻辑(不管是否有异常)
exec.exec();
}
}
}
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
/**
* 自定义实现前置通知
*/
public class BeforeMethodInterceptor implements MethodInterceptor {
InterceptorExec exec;
public BeforeMethodInterceptor(InterceptorExec exec) {
this.exec = exec;
}
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
// 始终在目标方法调用之前执行通知逻辑
exec.exec();
return invocation.proceed();
}
}
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
/**
* 自定义实现返回通知(在目标方法返回前被调用)
*/
public class ReturningMethodInterceptor implements MethodInterceptor {
InterceptorExec exec;
public ReturningMethodInterceptor(InterceptorExec exec) {
this.exec = exec;
}
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
try {
// 只在没有异常且目标方法调用返回后,执行 returning 通知逻辑
Object result = invocation.proceed();
exec.exec();
return result;
} catch (Exception e) {
e.printStackTrace();
throw e;
}
}
}
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
/**
* 自定义实现异常通知(在目标方法异常时调用)
*/
public class ThrowMethodInterceptor implements MethodInterceptor {
InterceptorExec exec;
public ThrowMethodInterceptor(InterceptorExec exec) {
this.exec = exec;
}
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
try {
return invocation.proceed();
} catch (Exception e) {
// 只在目标方法调用异常时执行通知逻辑
exec.exec();
throw e;
}
}
}
定义函数式接口,方便通过构造传入具体执行的通知逻辑
/**
* 自定义的通知逻辑
*/
@FunctionalInterface
public interface InterceptorExec {
void exec();
}
测试类
public class MainTest {
public static void main(String[] args) {
AfterMethodInterceptor afterItr = new AfterMethodInterceptor(() -> System.out.println("after1"));
BeforeMethodInterceptor beforeItr = new BeforeMethodInterceptor(() -> System.out.println("before1"));
BeforeMethodInterceptor beforeItr2 = new BeforeMethodInterceptor(() -> System.out.println("before2"));
ReturningMethodInterceptor returnItr = new ReturningMethodInterceptor(() -> System.out.println("return1"));
ThrowMethodInterceptor throwItr = new ThrowMethodInterceptor(() -> System.out.println("throw1"));
// 同一类型的通知调用顺序,由添加到集合时的顺序决定(如 before2 在 before1 前被添加)
// 但不同通知类型的调用顺序不变(before 肯定会在 after 前调用) 这是由 invoke() 方法中,对 proceed() 方法不同时机的递归调用决定的
// 例如 before 通知,总是在 proceed() 前调用通知逻辑,那么它总是会在其余的 proceed() 还在递归调用时,就已经执行了
// 再比如 after 通知,总是在 proceed() 方法递归调用返回后执行,那么它也肯定在其他通知逻辑之后执行
List<MethodInterceptor> interceptors = List.of(afterItr, returnItr, throwItr, beforeItr2, beforeItr);
try {
CustomMethodInvocation invocation = new CustomMethodInvocation(
interceptors, new Target(), Target.class.getMethod("save"),
new Object[]{}
);
Object result = invocation.proceed();
System.out.println(result);
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
}
Mvc
DispatcherServlet 初始化
初始化时机
借助 spring mvc 中的相关类,可以使用内嵌 tomcat 的形式快速启动一个 web 服务,如下代码
配置文件
server.port=8888
spring.mvc.servlet.load-on-startup=1
编写配置
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletRegistrationBean;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.mvc.Controller;
import java.util.Objects;
@Configuration
@ComponentScan
@PropertySource("classpath:application.properties") // 加载类路径下的 properties 配置文件
public class WebConfig {
@Autowired
private Environment environment;
// 配置 web 容器
@Bean
public ServletWebServerFactory servletWebServerFactory() {
TomcatServletWebServerFactory tomcatServletWebServerFactory = new TomcatServletWebServerFactory();
int port = Integer.parseInt(Objects.requireNonNull(environment.getProperty("server.port")));
tomcatServletWebServerFactory.setPort(port);
return new TomcatServletWebServerFactory();
}
// 配置前控制器
@Bean
public DispatcherServlet dispatcherServlet() {
return new DispatcherServlet();
}
// 注册【前控制器】到 web 容器
@Bean
public DispatcherServletRegistrationBean registrationBean(DispatcherServlet dispatcherServlet) {
DispatcherServletRegistrationBean registrationBean = new DispatcherServletRegistrationBean(dispatcherServlet, "/");
// 设置 spring 容器启动时便初始化,参数的值越小,在多个 servlet 的情况下,初始化的优先级越高
int startUp = Integer.parseInt(Objects.requireNonNull(environment.getProperty("spring.mvc.servlet.load-on-startup")));
registrationBean.setLoadOnStartup(startUp);
// "/" 匹配所有请求
return registrationBean;
}
// 手动注册一个控制器,这相当于使用 @Controller 注解注册了一个控制器
@Bean("/hello") // 如果 bean 名称以 / 开头, 那么此控制器的访问路径就为完整的 bean 名称
public Controller controllerOne() {
return (request, response) -> {
response.getWriter().println("hello");
return null;
};
}
}
配置加载
import com.example.mvc.config.WebConfig;
import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;
import org.springframework.context.ApplicationContext;
public class WebTest1 {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);
}
}
注意,目前只是将 DispatcherServlet 的实例,通过 spring 初始化到容器中,再未指定初始化时机时,DispatcherServlet 真正的初始化,是在首次访问 web 服务时,由 tomcat 执行其初始化,初始化的打印信息如下
3月 11, 2023 11:59:49 上午 org.apache.catalina.core.ApplicationContext log
信息: Initializing Spring DispatcherServlet 'dispatcherServlet'
11:59:49.609 [http-nio-8080-exec-1] INFO org.springframework.web.servlet.DispatcherServlet - Initializing Servlet 'dispatcherServlet'
11:59:49.630 [http-nio-8080-exec-1] DEBUG org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping - Detected 1 mappings in 'org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping'
11:59:49.923 [http-nio-8080-exec-1] DEBUG org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter - ControllerAdvice beans: none
11:59:49.968 [http-nio-8080-exec-1] DEBUG org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver - ControllerAdvice beans: none
11:59:49.986 [http-nio-8080-exec-1] DEBUG org.springframework.web.servlet.DispatcherServlet - enableLoggingRequestDetails='false': request parameters and headers will be masked to prevent unsafe logging of potentially sensitive data
11:59:49.986 [http-nio-8080-exec-1] INFO org.springframework.web.servlet.DispatcherServlet - Completed initialization in 377 ms
11:59:49.999 [http-nio-8080-exec-1] DEBUG org.springframework.web.servlet.DispatcherServlet - GET "/hello", parameters={}
11:59:50.003 [http-nio-8080-exec-1] DEBUG org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping - Mapped to com.example.mvc.config.WebConfig$$Lambda$163/0x0000000800222040@71913952
11:59:50.008 [http-nio-8080-exec-1] DEBUG org.springframework.web.servlet.DispatcherServlet - Completed 200 OK
11:59:50.159 [http-nio-8080-exec-2] DEBUG org.springframework.web.servlet.DispatcherServlet - GET "/favicon.ico", parameters={}
11:59:50.167 [http-nio-8080-exec-2] WARN org.springframework.web.servlet.PageNotFound - No mapping for GET /favicon.ico
11:59:50.167 [http-nio-8080-exec-2] DEBUG org.springframework.web.servlet.DispatcherServlet - Completed 404 NOT_FOUND
初始化操作
DispatcherServlet 的初始化代码逻辑,在其内部的onRefresh()
方法中声明,执行的是一系列初始化策略,部分源码如下
@Override
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
/**
* Initialize the strategy objects that this servlet uses.
* <p>May be overridden in subclasses in order to initialize further strategy objects.
*/
protected void initStrategies(ApplicationContext context) {
// 初始化文件上传解析器
initMultipartResolver(context);
// 初始本地化解析器,语言,国际化信息等
initLocaleResolver(context);
initThemeResolver(context);
// 初始化请求和请求处理器之间的映射关系
initHandlerMappings(context);
// 初始化请求处理器的适配器(这里的 handler 可以理解为具体处理请求的逻辑,例如 controller 中的具体逻辑)
// 在 spring 中,处理请求的形式是多种多样的,通过定义一个 HandlerAdapter 接口,来定义统一的请求处理方式(屏蔽不同请求处理方式之间的差异)
// 具体的请求处理逻辑,交给接口实现(适配器模式)
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
DispatcherServlet 的初始化一共分为九个步骤,每个步骤都是去初始化某一类的组件
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步