Appearance
Spring 容器与Bean
本文介绍Spring中的容器与Bean核心概念。
1. 核心概念与流程解析
什么是容器,什么是Bean?
容器是存储Bean的;Bean就是对象。
java
Map<String, Object> container = new HashMap<>();
container.put("obj1", new Object());
container.put("str1", new String("hello"));例如,上面的container可以认为是一个容器,new Object()和new String("hello")所产生的对象可以认为是Bean。
假设我们的程序有成千上万个Bean,都需要我们手动new出来放入容器吗?我们知道,Spring框架提供的核心功能就是控制反转:我们不用手动new创建Bean,而是由容器帮我们创建Bean。
所以,Spring提出了**BeanDefinition**的概念,即告诉Spring容器,通过BeanDefinition如何创建一个Bean。关于BeanDefinition具体内容,后面会详细讲解。
我们只需要向容器中添加BeanDefinition,之后,从容器中获取Bean时,容器会自动根据BeanDefinition创建出Bean返回,这称为懒加载。
所以,目前关于容器和Bean的流程,有如下关键步骤:
txt
0. 创建容器
1. 向容器中添加BeanDefinition
2. 从容器中获取Bean(此处涉及容器自动创建Bean)2. 流程实践
2.1 创建基本容器
在Spring框架中,关于容器最基本的接口是org.springframework.beans.factory.BeanFactory,它的一个实现类是org.springframework.beans.factory.support.DefaultListableBeanFactory。
我们,我们可以创建DefaultListableBeanFactory来获得一个最基本的容器:
java
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();2.2 添加BeanDefinition
我们可以通过beanFactory.registerBeanDefinition()方法(注意,该方法不是BeanFactory接口中定义的),向容器中添加BeanDefinition。
可以使用BeanDefinitionBuilder来快速构建BeanDefinition:
java
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder
.genericBeanDefinition(Bean.class) // 指定Bean的类型
.setScope("singleton")
.getBeanDefinition();
beanFactory.registerBeanDefinition("bean",beanDefinition);java
// 自定义的类,可以是任何名字
class Bean{
public Bean(){
System.out.println("bean constructor");
}
}2.3 获取Bean
最后,通过beanFactory.getBean()方法,可以从容器中获得Bean,如果Bean不存在,此过程容器会通过BeanDefinition创建出Bean。
java
Bean bean = beanFactory.getBean(Bean.class); // 通过类型获取2.4 总结
根据上面三步,整体代码如下:
java
@SpringBootTest
class IocApplicationTests {
@Test
void contextLoads() {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder
.genericBeanDefinition(Bean.class)
.setScope("singleton")
.getBeanDefinition();
beanFactory.registerBeanDefinition("bean",beanDefinition);
System.out.println("============1==============");
for (String name : beanFactory.getBeanDefinitionNames()) {
System.out.println(name);
}
System.out.println("============2==============");
Bean bean = beanFactory.getBean(Bean.class);
System.out.println(bean);
}
}
class Bean{
public Bean(){
System.out.println("bean constructor");
}
}运行上面的测试代码,结果如下:
txt
============1==============
bean
============2==============
bean constructor
com.lee.ioc.Bean@62b6c045可以发现容器中只有一个名为bean的BeanDefinition,并且,在获取Bean时才调用Bean的构造方法创建对象。
3. 流程扩展一:BeanFactoryPostProcessor
上面的流程只是一个简化的流程,在实际开发中,我们不可能手动创建BeanDefinition,这不仅影响开发效率,而且影响运行效率。为什么不手动直接把Bean创建好再加入容器呢?
因为在Spring中,当我们把BeanDefinition添加进容器后,Spring会执行refre()方法,在refresh()方法中,会执行BeanFactoryPostProcessor接口(工厂后处理器)方法:
txt
0. 创建容器
1. 向容器中添加BeanDefinition
------- refresh():可以理解为容器初始化方法,会在其中执行BeanFactoryPostProcessor的方法
2. 从容器中获取Bean(此处涉及容器自动创建Bean)BeanFactoryPostProcessor接口定义如下,只有一个方法,方法参数为ConfigurableListableBeanFactory,就是容器(子接口):
java
public interface BeanFactoryPostProcessor {
void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;
}有了这个接口,那么我们就可以做很多扩展功能了,通过beanFactory我们可以获得容器中的BeanDefinition,并对其进行修改与检查等操作。
因此,我们可以自定义BeanFactoryPostProcessor实现类,并将其加入到容器中,然后调用容器的refresh()方法。
自定义BeanFactoryPostProcessor实现类的逻辑很简单,就是把容器中BeanDefinition的关键信息输出:
java
class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor{
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
System.out.println("=====MyBeanFactoryPostProcessor.postProcessBeanFactory()======");
String[] beanDefinitionNames = beanFactory.getBeanDefinitionNames();
for (String name : beanDefinitionNames){
BeanDefinition beanDefinition = beanFactory.getBeanDefinition(name);
System.out.printf(" BeanDefinition 类名:%s\n",
beanDefinition.getBeanClassName());
}
}
}WARNING
在之前使用的容器DefaultListableBeanFactory,其中并没有refresh()方法,refresh()方法是在ConfigurableApplicationContext接口中提供的,ConfigurableApplicationContext是BeanFactory的子接口,GenericApplicationContext是实现类,所以我们使用GenericApplicationContext作为容器。

完整代码如下:
java
@SpringBootTest
public class BeanFactoryPostProcessorTest {
@Test
public void test1(){
GenericApplicationContext applicationContext = new GenericApplicationContext();
applicationContext.registerBean(MyBeanFactoryPostProcessor.class);
applicationContext.registerBean(Bean1.class);
System.out.println("-----------------------------");
applicationContext.refresh();
}
}
class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor{
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
System.out.println("=====MyBeanFactoryPostProcessor.postProcessBeanFactory()======");
String[] beanDefinitionNames = beanFactory.getBeanDefinitionNames();
for (String name : beanDefinitionNames){
BeanDefinition beanDefinition = beanFactory.getBeanDefinition(name);
System.out.printf(" BeanDefinition 类名:%s\n",
beanDefinition.getBeanClassName());
}
}
}
class Bean1{
}运行上述代码,结果如下:
txt
-----------------------------
=====MyBeanFactoryPostProcessor.postProcessBeanFactory()======
BeanDefinition 类名:com.lee.ioc.MyBeanFactoryPostProcessor
BeanDefinition 类名:com.lee.ioc.Bean1可以发现,调用refresh()方法,确实会执行BeanFactoryPostProcessor接口方法。
[问题]
我们只是向 容器中添加了MyBeanFactoryPostProcessor的BeanDefinition,并没有调用getBean(),照理来说,容器中是不存在MyBeanFactoryPostProcessor对象的,那又是如何执行工厂后处理器的postProcessBeanFactory()方法的呢?
跟踪refresh()方法,最终会到如下工具类(省略其他代码,只展示关键代码):
java
final class PostProcessorRegistrationDelegate {
// beanFactory: 容器
// beanFactoryPostProcessors: 工厂后处理器列表
public static void invokeBeanFactoryPostProcessors(
ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors) {
// BeanDefinitionRegistryPostProcessor:是BeanFactoryPostProcessor子接口,用于向容器中注册BeanFactoryPostProcessor
String[] postProcessorNames =
beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
for (String ppName : postProcessorNames) {
if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
processedBeans.add(ppName);
}
}
invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry, beanFactory.getApplicationStartup());
}
}可以看到,第13行代码调用了beanFactory.getBean(),这里就会创建出BeanFactoryPostProcessor实例,从而执行工厂后处理器方法。
工厂后处理器方法的作用之一,可以为容器中添加更多的BeanDefinition,接下来重点介绍一个工厂后处理器:
ConfigurationClassPostProcessor:用来处理@Configuration标注的配置类,包括@Bean、@ComponentScan、@Component、@Controller等注解的解析;下面做详细介绍。MapperScannerConfigurer:用来处理@MapperScan标注的类,这是mybatis提供的,不做详细介绍。
4. 工厂后处理器介绍:ConfigurationClassPostProcessor
4.1.1 效果演示
首先,我们先演示ConfigurationClassPostProcessor的作用,案例结构如下:

代码如下:
java
public class Main {
public static void main(String[] args) {
GenericApplicationContext applicationContext = new GenericApplicationContext();
applicationContext.registerBean(Config.class);
//applicationContext.registerBean(ConfigurationClassPostProcessor.class);
applicationContext.refresh();
for (String beanDefinitionName : applicationContext.getBeanDefinitionNames()) {
System.out.println(beanDefinitionName);
}
}
}java
@Configuration
@ComponentScan(basePackages = {"com.lee.ioc.t1.component"})
public class Config {
@Bean
public Bean1 bean1(){
return new Bean1();
}
@Bean
public Bean2 bean2(){
return new Bean2();
}
}java
public class Bean1 {
}
public class Bean2 {
}
@Component
public class Component1 {
}
public class Component2 {
}运行上述代码,结果如下:
txt
com.lee.ioc.t1.Config此时容器中只有一个Config的BeanDefinition。
放开Main中第6行的代码注释,再次运行程序,结果如下:
txt
com.lee.ioc.t1.Config
org.springframework.context.annotation.ConfigurationClassPostProcessor
component1
bean1
bean2可以看到,此时容器多了5个BeanDefinition,这就是ConfigurationClassPostProcessor的作用,解析@Configuration和@ComponentScan配置类,向容器中添加更多的BeanDefinition。
4.1.2 手动解析@ComponentScan
本小节的目标是自定义一个工厂后处理器,手动解析@ComponentScan,向容器中添加更多的BeanDefinition。
TIP
注意,这里实现的是BeanDefinitionRegistryPostProcessor接口,因为它提供了BeanDefinitionRegistry参数,可以更方便地注册组件,即添加BeanDefinition。
java
public class ComponentScanBeanFactoryPostProcessor implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanFactory) throws BeansException {
// 参考附录1
CachingMetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory();
// 参考附录2
PathMatchingResourcePatternResolver pathMatchingResourcePatternResolver = new PathMatchingResourcePatternResolver();
// 以下代码逻辑:根据目前容器中已有的BeanDefinition,判断类上是否有@ComponentScan注解
// 如果有,则解析其属性basePackages,并根据类路径判断指定包中的类是否有@Component注解
// 如果有,则将标注了@Component注解加入到容器中
String[] beanDefinitionNames = beanFactory.getBeanDefinitionNames();
for (String name : beanDefinitionNames) {
BeanDefinition beanDefinition = beanFactory.getBeanDefinition(name);
try {
MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(beanDefinition.getBeanClassName());
AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
boolean componentScan = annotationMetadata.hasAnnotation(ComponentScan.class.getName());
if(componentScan) {
// 有@ComponentScan注解
String[] basePackages = (String[]) annotationMetadata.getAnnotationAttributes(ComponentScan.class.getName()).get("basePackages");
if(basePackages == null || basePackages.length == 0){
continue;
}
for (String basePackage : basePackages) {
String path = "classpath*:" + basePackage.replaceAll("\\.", "/") + "/**/*.class";
Resource[] resources = pathMatchingResourcePatternResolver.getResources(path);
if (resources == null || resources.length == 0){
continue;
}
for (Resource resource : resources) {
MetadataReader componentMetadataReader = metadataReaderFactory.getMetadataReader(resource);
boolean componentAnnoted = componentMetadataReader.getAnnotationMetadata().isAnnotated(Component.class.getName());
if(componentAnnoted){
// 有@Component注解
String className = componentMetadataReader.getClassMetadata().getClassName();
AbstractBeanDefinition bd = BeanDefinitionBuilder.genericBeanDefinition(className)
.setScope("singleton")
.getBeanDefinition();
beanFactory.registerBeanDefinition(className, bd);
}
}
}
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
}
}将我们自定义的工厂后处理器加入到容器中:
java
public class Main {
public static void main(String[] args) {
GenericApplicationContext applicationContext = new GenericApplicationContext();
applicationContext.registerBean(Config.class);
applicationContext.registerBean(ComponentScanBeanFactoryPostProcessor.class);
applicationContext.refresh();
for (String beanDefinitionName : applicationContext.getBeanDefinitionNames()) {
System.out.println(beanDefinitionName);
}
}
}5. 流程扩展二:BeanPostProcessor
一个对象的生命过程,包含以下步骤:
- 创建:就是调用构造方法,创建对象;包括依赖注入;
- 初始化:调用初始化方法,例如
@Bean中指定的initMethod()方法; - 使用:调用对象的各个方法;
- 销毁:使用完毕,关闭资源;
在Spring的容器中,Bean(对象)的生命周期也包含以上步骤,因此,Spring留下了很多时间点,可以执行一些额外的操作,例如。可以在创建对象前、创建对象后、初始化对象前、初始化对象后、销毁对象前执行一些操作。这些操作封装在Bean后处理器(BeanPostProcessor)中。
因此,加上Bean后处理器,使用容器的流程如下:
txt
0. 创建容器
1. 向容器中添加BeanDefinition
------- refresh():可以理解为容器初始化方法,会在其中执行BeanFactoryPostProcessor的方法
2. 从容器中获取Bean(此处涉及容器自动创建Bean)
2.1 创建对象前钩子函数
2.2 new 创建对象
2.3 创建对象后钩子函数
2.4 准备依赖项
2.5 依赖项准备完成后钩子函数 postProcessProperties
2.6 -- 依赖注入 --
2.7 初始化对象前钩子函数
2.8 初始化对象 init()方法
2.9 初始化对象后钩子函数
3. 使用Bean
3.1 销毁对象前钩子函数
3.2 销毁对象BeanPostProcessor接口定义如下,定义了创建对象前后的钩子函数:
java
public interface BeanPostProcessor {
// 在初始化之前执行,bean就是创建的对象,beanName就是组件名
// 返回值将替代bean,如果返回值为null,那么后续的BeanPostProcessors将不会执行
@Nullable
default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
// 在初始化之后执行
@Nullable
default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}BeanPostProcessor还有子接口InstantiationAwareBeanPostProcessor,定义了初始化对象前后的钩子函数:
java
public interface InstantiationAwareBeanPostProcessor extends BeanPostProcessor {
// 在创建对象之前执行,beanClass就是要创建对象的类型,beanName是bean在容器中的名称
// 返回值为null,将按照默认的创建对象流程进行;如果返回值不为null,那么将替代创建的bean
@Nullable
default Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
return null;
}
// 在创建对象之后执行
// 如果返回值为false,将跳过初始化步骤
default boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
return true;
}
// 依赖注入前执行
// 在这一个方法中,可以通过PropertyValues获取到,该对象的哪个属性应该赋什么值
@Nullable
default PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName)
throws BeansException {
return pvs;
}
}另一个子接口DestructionAwareBeanPostProcessor定义了销毁对象前的钩子函数:
java
public interface DestructionAwareBeanPostProcessor extends BeanPostProcessor {
// 销毁对象前的钩子函数
void postProcessBeforeDestruction(Object bean, String beanName) throws BeansException;
// 是否需要调用 销毁对象前的钩子函数
default boolean requiresDestruction(Object bean) {
return true;
}
}接下来,我们就定义自己的Bean后处理器,看看这些方法的执行流程:
java
public class MyBeanPostProcessor implements InstantiationAwareBeanPostProcessor,
DestructionAwareBeanPostProcessor {
@Override
public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
System.out.println("postProcessBeforeInstantiation");
return InstantiationAwareBeanPostProcessor.super.postProcessBeforeInstantiation(beanClass, beanName);
}
@Override
public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
System.out.println("postProcessAfterInstantiation");
return InstantiationAwareBeanPostProcessor.super.postProcessAfterInstantiation(bean, beanName);
}
@Override
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) throws BeansException {
System.out.println("postProcessProperties");
return InstantiationAwareBeanPostProcessor.super.postProcessProperties(pvs, bean, beanName);
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("postProcessBeforeInitialization");
return InstantiationAwareBeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("postProcessAfterInitialization");
return InstantiationAwareBeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
}
@Override
public void postProcessBeforeDestruction(Object bean, String beanName) throws BeansException {
System.out.println("postProcessBeforeDestruction");
}
@Override
public boolean requiresDestruction(Object bean) {
return DestructionAwareBeanPostProcessor.super.requiresDestruction(bean);
}
}java
public class Main {
public static void main(String[] args) {
GenericApplicationContext applicationContext = new GenericApplicationContext();
applicationContext.registerBean(MyBeanPostProcessor.class);
AbstractBeanDefinition bd = BeanDefinitionBuilder
.genericBeanDefinition(Bean.class)
.setScope("singleton")
.getBeanDefinition();
applicationContext.registerBeanDefinition("bean", bd);
applicationContext.refresh();
//Bean bean = applicationContext.getBean(Bean.class);
applicationContext.close();
}
}代码执行结果如下:
txt
postProcessBeforeInstantiation
postProcessAfterInstantiation
postProcessProperties
postProcessBeforeInitialization
postProcessAfterInitialization
postProcessBeforeDestruction可以看到各个钩子函数按照顺序执行了。
问题一
我们只是将Bean后处理器的BeanDefinition加入到容器中,并没有实例化,为什么Bean后处理器也能发挥作用?
答:在容器的refresh()方法中,调用了registerBeanPostProcessors(beanFactory);方法,在这个方法中会实例化Bean后处理器。
问题二
在上面的代码中,我们并没有从容器中获取Bean对象,即注释了applicationContext.getBean(Bean.class);,为什么会触发Bean的创建过程?
答:我们注册的Bean是单例(singleton)的,在refresh()方法中,会实例化单例Bean,所以会触发Bean后处理器。
根据问题二,我们可以把组件改为多例(setScope("prototype")),会发现如果不手动从容器中获取Bean,不会触发Bean后处理器。
取消注释,改为手动从容器中获取Bean对象,触发对象创建过程,可以触发Bean后处理器,但是会发现注销前的钩子函数没有生效,,这是因为:Spring容器只负责创建和初始化这些prototype Bean,然后就将它们“交给”客户端代码。容器不再维护对这些prototype实例的引用,因此也不负责它们的销毁。
Bean后处理器的作用可以用来实现依赖注入(Dependency Inject)。
6. Bean后处理器介绍
6.1 AutowiredAnnotationBeanPostProcessor
6.1.1 @Autowired
AutowiredAnnotationBeanPostProcessor可以用来处理@Autowired和@Value注解,用于依赖注入。
查看下面这个例子,查看AutowiredAnnotationBeanPostProcessor对@Autowired注解的支持:
java
public class Main {
public static void main(String[] args) {
GenericApplicationContext applicationContext = new GenericApplicationContext();
applicationContext.registerBean(Bean2.class);
applicationContext.registerBean(Bean3.class);
applicationContext.registerBean(Bean4.class);
applicationContext.registerBean(Bean1.class);
//applicationContext.registerBean(AutowiredAnnotationBeanPostProcessor.class);
applicationContext.refresh();
Bean1 bean1 = applicationContext.getBean(Bean1.class);
System.out.println(bean1);
}
}
@Data
class Bean1{
@Autowired
private Bean2 bean2;
@Autowired
private Bean3 bean3;
private Bean4 bean4;
@Autowired
private void setBean4(Bean4 bean4){
System.out.println("setBean4");
this.bean4 = bean4;
}
}
class Bean2{
}
class Bean3{
}
class Bean4{
}结果如下:
txt
Bean1(bean2=null, bean3=null, bean4=null)如果取消第9行的注释,再次运行程序,结果如下:
txt
setBean4
Bean1(bean2=com.lee.ioc.t3.Bean2@52aa2946, bean3=com.lee.ioc.t3.Bean3@4de5031f, bean4=com.lee.ioc.t3.Bean4@67e2d983)可以看到Bean1对象中的属性成功注入了值,这就是AutowiredAnnotationBeanPostProcessor的作用。
6.1.2 @Value
请先查看附录3和附录4。
java
public class Main2 {
public static void main(String[] args) {
GenericApplicationContext applicationContext = new GenericApplicationContext();
applicationContext.registerBean(Bean5.class);
applicationContext.getDefaultListableBeanFactory()
.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());
applicationContext.registerBean(AutowiredAnnotationBeanPostProcessor.class);
applicationContext.refresh();
Bean5 bean5 = applicationContext.getBean(Bean5.class);
System.out.println(bean5);
}
}
@Data
class Bean5{
@Value("2")
private int num1;
@Value("hello")
private String str1;
@Value("${java.home}")
private String name;
@Value("#{1+1}")
private String num2;
}如果注释第10行,那么Bean5对象中的属性将没有值。
运行结果如下:
txt
Bean5(num1=2, str1=hello, name=/Users/xxx/Library/Java/JavaVirtualMachines/graalvm-jdk-21.0.5/Contents/Home, num2=2)6.2 CommonAnnotationBeanPostProcessor
CommonAnnotationBeanPostProcessor用于支持@Resource,@PostConstruct、@PreDestroy等通用注解(JavaEE)。
案例如下:
java
public class Main {
public static void main(String[] args) {
GenericApplicationContext applicationContext = new GenericApplicationContext();
applicationContext.registerBean(Bean2.class);
applicationContext.registerBean(Bean1.class);
//applicationContext.registerBean(CommonAnnotationBeanPostProcessor.class);
applicationContext.refresh();
Bean1 bean1 = applicationContext.getBean(Bean1.class);
System.out.println(bean1);
applicationContext.close();
}
}
@Data
class Bean1{
@Resource
private Bean2 bean2;
@PostConstruct
private void init(){
System.out.println("init");
}
@PreDestroy
private void preDestroy(){
System.out.println("preDestroy");
}
}
class Bean2{
}运行上面代码,结果如下:
txt
Bean1(bean2=null)放开第8行代码注释,再次运行程序,结果如下:
txt
init
Bean1(bean2=com.lee.ioc.t4.Bean2@21a947fe)
preDestroy6.3 ConfigurationPropertiesBindingPostProcessor
ConfigurationPropertiesBindingPostProcessor后处理器是专门为处理 @ConfigurationProperties 注解而设计的,用于将外部配置绑定到Bean属性上。
java
public class Main {
public static void main(String[] args) {
GenericApplicationContext applicationContext = new GenericApplicationContext();
applicationContext.registerBean(Bean.class);
//ConfigurationPropertiesBindingPostProcessor.register(applicationContext);
applicationContext.refresh();
Bean bean = applicationContext.getBean(Bean.class);
System.out.println(bean);
}
}
@Data
@ConfigurationProperties(prefix = "java")
class Bean{
private String home;
private String version;
}运行上面程序,结果如下:
txt
Bean(home=null, version=null)取消第6行注释,再次运行,结果如下:
java
Bean(home=/Users/xxx/Library/Java/JavaVirtualMachines/graalvm-jdk-21.0.5/Contents/Home, version=21.0.5)更多内容,请参考第10节:Environment。
7. BeanDefinition详解
BeanDefinition用于定义如何创建一个Bean,包括许多属性,本节介绍如下。
7.1 Class和Name
Class 表示Bean的类型,但是,这并不是必需的,因为Bean有可能是由工厂方法创建的。
Name表示Bean在容器中的名称,在容器中必须唯一,通过不同的注册方法,可以不指定Name,容器会自动获取Name。
java
public class Main1 {
public static void main(String[] args) {
GenericApplicationContext applicationContext = new GenericApplicationContext();
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder
.genericBeanDefinition(Bean01.class)
.setFactoryMethod("createBean01") // createBean01工厂方法必须为静态的
.getBeanDefinition();
applicationContext.registerBeanDefinition("bean01", beanDefinition);
AbstractBeanDefinition beanDefinition1 = BeanDefinitionBuilder
.genericBeanDefinition() // 没有指定类型
.setFactoryMethodOnBean("getBean02", "bean01")
.getBeanDefinition();
applicationContext.registerBeanDefinition("bean02", beanDefinition1);
applicationContext.refresh();
System.out.println("-------------------------");
Bean01 bean01 = applicationContext.getBean(Bean01.class);
System.out.println(bean01);
Bean02 bean02 = applicationContext.getBean(Bean02.class);
System.out.println(bean02);
}
}
class Bean01{
public Bean01(){
System.out.println("构造方法");
}
// 必须为static
private static Bean01 createBean01(){
System.out.println("工厂方法");
return new Bean01();
}
// private 也是可以的
private Bean02 getBean02(){
System.out.println("getBean02");
return new Bean02();
}
}
class Bean02{
}7.2 Scope
在 Spring 框架中,Bean Scope (Bean 作用域) 是一个非常核心的概念,它决定了 Spring 容器如何管理 Bean 的实例,即一个 BeanDefinition 会对应多少个实际的对象实例,以及这些实例的生命周期是怎样的。
Bean Scope的取值如下:
singleton: 这是 Spring Bean 的默认作用域。在整个 Spring IoC 容器中,一个 Bean 定义只对应一个唯一的实例。无论多少次请求该 Bean(通过
getBean()或自动装配),Spring 都会返回同一个实例。容器启动时创建(或首次请求时创建),容器关闭时销毁。Spring 容器会完全管理其生命周期。
prototype:每次从 Spring 容器请求(
getBean())一个prototype作用域的 Bean 时,Spring 都会创建一个全新的实例。Spring 容器只负责
prototypeBean 的创建和初始化。一旦 Bean 被创建并交给客户端,容器就不再管理它的生命周期。 销毁回调(如@PreDestroy方法或DisposableBean接口)不会被容器调用。request:针对 Web 应用。在单个 HTTP 请求的生命周期内,一个 BeanDefinition 只对应一个实例。 每次新的 HTTP 请求到来时,都会创建一个新的 Bean 实例。
随着 HTTP 请求的开始而创建,随着 HTTP 请求的结束而被销毁。
session:针对 Web 应用。在单个 HTTP Session 的生命周期内,一个 BeanDefinition 只对应一个实例。 只要用户的 Session 存在,该 Bean 实例就一直存在。
随着用户 HTTP Session 的开始而创建,随着 Session 的结束而被销毁。
application:针对 Web 应用。在整个 ServletContext 的生命周期内,一个 BeanDefinition 只对应一个实例。 它的生命周期与 Web 应用程序的生命周期绑定。
在 Web 应用程序启动时创建,在 Web 应用程序关闭时销毁。
websocket:针对 WebSocket 应用。在单个 WebSocket 会话的生命周期内,一个BeanDefinition 只对应一个实例。
随着 WebSocket 连接的建立而创建,随着连接的关闭而被销毁。
默认情况下,singleton作用域的BeanDefinition会在容器refresh()阶段创建Bean。
下面演示prototype作用域:
java
public class Main2 {
public static void main(String[] args) {
GenericApplicationContext applicationContext = new GenericApplicationContext();
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(Bean03.class)
.setScope("prototype")
.getBeanDefinition();
applicationContext.registerBeanDefinition("bean03", beanDefinition);
applicationContext.refresh();
System.out.println("-------------------");
Bean03 bean03_01 = applicationContext.getBean(Bean03.class);
System.out.println(bean03_01);
Bean03 bean03_02 = applicationContext.getBean(Bean03.class);
System.out.println(bean03_02);
}
}
class Bean03{
public Bean03(){
System.out.println("Bean03 构造方法");
}
}结果如下:
java
-------------------
Bean03 构造方法
com.lee.ioc.t6.Bean03@25bbf683
Bean03 构造方法
com.lee.ioc.t6.Bean03@6ec8211c可以发现:
- 在
refresh()阶段,并没有创建作用域为prototype的Bean; - 作用域为
prototype的Bean,每次从容器中获取,都是新创建的;
在作用域为singleton的Bean中注入作用域为prototype的Bean,由于singleton的Bean创建后就不会再改变,所以每次从singleton的Bean中获取作用域为prototype的Bean属性,都是同一个对象。为了每次获取的都是不同的对象,可以配合@Lazy注解使用,而为了解析@Lazy注解,需要添加ContextAnnotationAutowireCandidateResolver支持:
java
public class Main2 {
public static void main(String[] args) {
GenericApplicationContext applicationContext = new GenericApplicationContext();
applicationContext.registerBean(AutowiredAnnotationBeanPostProcessor.class);
applicationContext.registerBean(SingletonBean.class);
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(Bean03.class)
.setScope("prototype")
.getBeanDefinition();
applicationContext.registerBeanDefinition("bean03", beanDefinition);
applicationContext.getDefaultListableBeanFactory()
.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());
applicationContext.refresh();
System.out.println("-------------------");
SingletonBean bean = applicationContext.getBean(SingletonBean.class);
System.out.println(String.format("%s, %s", bean.hashCode(), bean.getBean03()));
SingletonBean bean1 = applicationContext.getBean(SingletonBean.class);
System.out.println(String.format("%s, %s", bean1.hashCode(), bean1.getBean03()));
}
}
@Data
class SingletonBean{
public SingletonBean(){
System.out.println("SingletonBean 构造方法");
}
@Autowired
@Lazy
private Bean03 bean03;
}
class Bean03{
public Bean03(){
System.out.println("Bean03 构造方法");
}
}结果如下:
java
SingletonBean 构造方法
-------------------
Bean03 构造方法
-666726578, com.lee.ioc.t6.Bean03@74f0ea28
Bean03 构造方法
-666726578, com.lee.ioc.t6.Bean03@3c19aaa5我们查看Bean3属性的类型:
java
System.out.println(bean1.getBean03().getClass());txt
class com.lee.ioc.t6.Bean03$$SpringCGLIB$$0是一个代理对象,因此,每次调用Bean03对象的方法(上面的例子是默认的toString()方法)时,代理对象都会请求容器,重新创建一个对象。
7.3 Autowiring mode
自动装配模式,是指Spring容器可以自动完成Bean的依赖赋值,取值如下:
- no:不进行自动装配,这也是默认的模式,取值为0。
- byName:通过属性的名称进行自动装配,要求具有
setXXX()方法,例如,假设Bean中有一个名为master的属性,并且提供setMaster()方法,那么Spring容器会根据master这个名称在容器中查找具有相同名称的Bean,如果找到,则调用setMaster()进行赋值。取值为1; - byType:通过属性的类型进行自动装配,如果在容器中找到多个具有相同属性的Bean,那么会报错;同样要求具有
setXXX()方法;取值为2; - constructor:通过构造方法进行自动装配,与
byType类似,不过是查找构造方法的参数类型,取值为2;
在AutowireCapableBeanFactory接口中定义了自动装配方式:
java
public interface AutowireCapableBeanFactory extends BeanFactory {
int AUTOWIRE_NO = 0;
int AUTOWIRE_BY_NAME = 1;
int AUTOWIRE_BY_TYPE = 2;
int AUTOWIRE_CONSTRUCTOR = 3;
}示例如下:
java
public class Main {
public static void main(String[] args) {
GenericApplicationContext applicationContext = new GenericApplicationContext();
applicationContext.registerBean("bean2",Bean2.class);
applicationContext.registerBean("bean3",Bean3.class);
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(Bean1.class)
.setAutowireMode(AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR) // 设置自动装配模式,此处是构造方法
.setScope("singleton")
.getBeanDefinition();
applicationContext.registerBeanDefinition("bean1", beanDefinition);
applicationContext.refresh();
Bean1 bean = applicationContext.getBean(Bean1.class);
System.out.println(bean);
}
}
@AllArgsConstructor
@NoArgsConstructor
@ToString
//@Setter
class Bean1{
private Bean2 bean2;
private Bean3 bean3;
}
class Bean2{
}
class Bean3{
}7.4 Lazy initialization mode
默认情况下,单例singleton的Bean都会在refresh()阶段进行创建,我们可以通过设置懒加载,将其创建过程延迟到第一次从容器中获取Bean时:
java
public class Main3 {
public static void main(String[] args) {
GenericApplicationContext applicationContext = new GenericApplicationContext();
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(Bean5.class)
.setScope("singleton")
.setLazyInit(true)
.getBeanDefinition();
applicationContext.registerBeanDefinition("bean5", beanDefinition);
applicationContext.refresh();
System.out.println("---------------");
Bean5 bean = applicationContext.getBean(Bean5.class);
System.out.println(bean);
}
}
class Bean5{
public Bean5(){
System.out.println("Bean5 构造方法");
}
}结果如下:
txt
---------------
Bean5 构造方法
com.lee.ioc.t6.Bean5@1786f9d58. 容器实现
我们之前一直在使用GenericApplicationContext,这是基本的容器,Spring还提供了其他容器实现,主要的如下:
8.1 ClassPathXmlApplicationContext
ClassPathXmlApplicationContext:读取类路径下的XML配置文件,注册Bean定义,使用如下:
java
public static void main(String[] args) {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
for (String beanDefinitionName : applicationContext.getBeanDefinitionNames()) {
System.out.println(beanDefinitionName);
}
}
// 运行结果:
// bean1beans.xml文件存在于resources目录下,内容如下:
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="org.lee.Bean1"></bean>
</beans>8.2 FileSystemXmlApplicationContext
FileSystemXmlApplicationContext:读取文件系统中的XML配置文件,注册Bean定义,使用如下:
java
public static void main(String[] args) {
// 获取当前项目的工作目录
System.out.println(System.getProperty("user.dir"));
// 基于工作目录的相对路径
String xmlPath = "/ioc/src/main/resources/beans.xml";
FileSystemXmlApplicationContext applicationContext = new FileSystemXmlApplicationContext(xmlPath);
for (String beanDefinitionName : applicationContext.getBeanDefinitionNames()) {
System.out.println(beanDefinitionName);
}
}8.3 AnnotationConfigApplicationContext
AnnotationConfigApplicationContext:根据@Configuration标注的配置类注册Bean定义,使用如下:
java
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(Config1.class);
for (String beanDefinitionName : applicationContext.getBeanDefinitionNames()) {
System.out.println(beanDefinitionName);
}
}
@Configuration
public class Config1 {
@Bean
public Bean1 bean1(){
return new Bean1();
}
}结果如下:
txt
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
config1
bean1可以发现,AnnotationConfigApplicationContext就是提前在容器中添加了一些工厂后处理器和Bean后处理器:
- internalConfigurationAnnotationProcessor:就是
ConfigurationClassPostProcessor工厂后处理器; - internalAutowiredAnnotationProcessor:就是
AutowiredAnnotationBeanPostProcessorBean后处理器; - internalCommonAnnotationProcessor:就是
CommonAnnotationBeanPostProcessorBean后处理器;
8.4 AnnotationConfigServletWebServerApplicationContext
这是用于Web环境的容器。如果要配置Web环境,需要三个Bean:
ServletWebServerFactory:即WEB应用运行的容器,可选Tomcat实现类;DispatcherServlet:请求分发器;DispatcherServletRegistrationBean:自动配置请求分发器,即DispatcherServlet处理哪些请求;
案例如下:
java
@Configuration
@ComponentScan(basePackages = {"org.lee.web"})
public class WebConfig {
@Bean
public ServletWebServerFactory servletWebServerFactory(){
return new TomcatServletWebServerFactory();
}
@Bean
public DispatcherServlet dispatcherServlet(){
return new DispatcherServlet();
}
@Bean
public DispatcherServletRegistrationBean dispatcherServletRegistrationBean(DispatcherServlet dispatcherServlet){
return new DispatcherServletRegistrationBean(dispatcherServlet, "/");
}
}java
@RestController
public class HelloController {
@Autowired
private Bean2 bean2;
@GetMapping("/hello")
public String hello(){
return "hello " + bean2;
}
}java
@Component
@ConfigurationProperties(prefix = "java")
@Data
public class Bean2 {
private String home;
private String version;
}java
public class Main2 {
public static void main(String[] args) {
AnnotationConfigServletWebServerApplicationContext applicationContext =
new AnnotationConfigServletWebServerApplicationContext();
// 支持@ConfigurationProperties注解
ConfigurationPropertiesBindingPostProcessor.register(applicationContext);
// 为了支持手动刷新,需要手动注册,不可以在构造方法里直接传入
applicationContext.register(WebConfig.class);
// 手动刷新,为了使ConfigurationPropertiesBindingPostProcessor生效
applicationContext.refresh();
for (String beanDefinitionName : applicationContext.getBeanDefinitionNames()) {
System.out.println(beanDefinitionName);
}
}
}运行上面的代码,可以看到容器中的Bean都有:

请求接口地址:localhost:8080/hello,正常返回结果

说明我们自己搭建的Web容器基本可以使用。
9. Aware和InitializingBean
9.1 Aware接口
Aware 接口提供了一种机制,允许 Bean 感知并访问它们所运行的 Spring 容器环境中的特定对象或资源。
简单来说,Aware 接口的作用就是让 Bean “意识到” 自己是 Spring 容器的一部分,并且能够从容器那里获取一些上下文信息或依赖,而无需通过传统的依赖注入(DI)方式。
通常,我们通过 @Autowired 或 XML 配置来注入 Bean 的依赖,例如注入另一个 Service、Repository 等。但是,有些情况下,Bean 需要访问的是 Spring 容器本身提供的基础设施对象,而不是容器中管理的业务 Bean。
例如,一个 Bean 可能需要:
- 访问它所在的
ApplicationContext,以便动态地查找其他 Bean。 - 获取 Spring 容器的
BeanName,用于日志记录或调试。 - 访问其运行的
BeanFactory,以进行一些低级别的 Bean 定义操作。 - 获取
Environment对象,以查询配置属性或激活的 Profile。
Aware 接口是一个标记接口(没有方法)。Spring 容器在初始化 Bean 的生命周期过程中,会检查当前正在创建的 Bean 是否实现了特定的 Aware 子接口。如果实现了,容器就会调用该接口中定义的方法,并将相应的容器对象或资源传递给 Bean。
java
public interface Aware {}如果关心容器中的基础设施对象,我们可以实现Aware的子接口,我们以BeanNameAware和ApplicationContextAware为例:
java
public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.registerBean(Bean.class);
// 在这里创建Bean
applicationContext.refresh();
}
}
class Bean implements BeanNameAware, ApplicationContextAware{
@Override
public void setBeanName(String name) {
System.out.println("bean name is " + name);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
System.out.println("application context is " + applicationContext);
}
}9.2 InitializingBean
InitializingBean是Spring容器内置的回调接口,当Bean的依赖注入后,会回调InitializingBean接口方法,其实可以看成和@PostConstruct一样的作用,还有一个与之对应的接口DisposableBean,用来定义销毁对象前的回调函数:
java
public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.registerBean(Bean.class);
// 在这里创建Bean
applicationContext.refresh();
applicationContext.close();
}
}
class Bean implements InitializingBean, DisposableBean {
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("InitializingBean.afterPropertiesSet");
}
@Override
public void destroy() throws Exception {
System.out.println("DisposableBean.destroy");
}
}有了InitializingBean接口,我们就有了三种方式定义初始化方法,另外两种为:@PostContruct和@Bean(initMethod="")这三者的顺序如下:
@PostConstruct:由CommonAnnotationBeanPostProcessor提供支持;InitializingBean:Spring容器内置机制;@Bean:由ConfigurationClassPostProcessor提供支持;
java
public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.registerBean(Config.class);
// 在这里创建Bean
applicationContext.refresh();
System.out.println("-----------------");
applicationContext.close();
}
}
@Configuration
class Config{
@Bean(initMethod = "initMethod", destroyMethod = "destroyMethod")
public Bean1 bean(){
return new Bean1();
}
}
class Bean1 implements InitializingBean, DisposableBean {
public void initMethod(){
System.out.println("initMethod from @Bean");
}
public void destroyMethod(){
System.out.println("destroyMethod from @Bean");
}
@PostConstruct
public void init(){
System.out.println("@PostConstruct");
}
@PreDestroy
public void preDestroy(){
System.out.println("@PreDestroy");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("InitializingBean.afterPropertiesSet");
}
@Override
public void destroy() throws Exception {
System.out.println("DisposableBean.destroy");
}
}结果如下:
txt
@PostConstruct
InitializingBean.afterPropertiesSet
initMethod from @Bean
-----------------
@PreDestroy
DisposableBean.destroy
destroyMethod from @Bean10. Environment
在Spring中,Environment包含两方面的内容:profiles和properties。
10.1 profiles
profiles可以理解为环境,生产环境,测试环境,开发环境等等。
假设我们的程序需要连接到数据库,那么生产环境、测试环境、开发环境的数据库地址肯定是不同的,所以程序在启动时,需要指定当前环境是什么,然后根据环境创建Bean。
@Profile注解可以指定当前Bean生效环境是什么,只有当前容器中激活的环境包含@Profile中指定的环境,Bean才会放进容器中。
例子如下:
java
public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext =
new AnnotationConfigApplicationContext();
ConfigurableEnvironment environment = applicationContext.getEnvironment();
environment.setActiveProfiles("test"); // 指定激活的环境,可以指定多个
applicationContext.register(Config.class);
applicationContext.refresh();
DataSource bean = applicationContext.getBean(DataSource.class);
System.out.println(bean);
}
}
@Configuration
class Config{
@Bean
@Profile("prod")
public DataSource bean1ForProd(){
return new DataSource("生产环境");
}
@Bean
@Profile("test")
public DataSource bean1ForTest(){
return new DataSource("测试环境");
}
}
@Data
@AllArgsConstructor
class DataSource {
private String name;
}结果为:
txt
Bean1(name=测试环境)其实,所谓环境,就是字符串Set,在AbstractEnvironment中定义如下:
java
private final Set<String> activeProfiles = new LinkedHashSet<>();
private final Set<String> defaultProfiles = new LinkedHashSet<>(getReservedDefaultProfiles());10.2 properties
properties可以理解为配置/属性,再简单地说,键值对集合。键值对在Spring中以PropertySource表示,键值对集合以PropertySources表示。
这些属性可以来自:
- 系统属性(System Properties): JVM 启动参数,比如
java -jar myapp.jar -Dserver.port=8081中的server.port。 - 操作系统环境变量(Environment Variables): 操作系统级别的变量,例如
PATH、JAVA_HOME。 - 配置文件(Configuration Files): 这是最常见的来源,比如
application.properties、application.yml或通过@PropertySource加载的自定义文件。Spring 会将这些文件的内容解析为键值对,并添加到Environment中。
Environment的实现StandardEnvirnment只包含两种属性:系统属性和操作系统环境变量属性。
java
public static void main(String[] args) {
StandardEnvironment standardEnvironment = new StandardEnvironment();
MutablePropertySources propertySources = standardEnvironment.getPropertySources();
propertySources.stream()
.forEach(x->{
System.out.println(x);
System.out.println(x.getSource());
});
}结果中的systemProperties就是系统属性,systemEnvironment就是操作系统环境变量。
我们也可以自己添加配置文件:
java
public static void main(String[] args) throws IOException {
StandardEnvironment standardEnvironment = new StandardEnvironment();
MutablePropertySources propertySources = standardEnvironment.getPropertySources();
// 加载properties配置文件
List<PropertySource<?>> propertySources1 = new PropertiesPropertySourceLoader().load("application", new ClassPathResource("application.properties"));
propertySources1.forEach(x-> propertySources.addLast(x));
// 也可以按照下面的方式加载properties配置文件
// Properties properties = PropertiesLoaderUtils.loadProperties(new ClassPathResource("application.properties"));
// propertySources.addLast(new PropertiesPropertySource("application", properties));
// 加载yaml配置文件
List<PropertySource<?>> propertySources2 = new YamlPropertySourceLoader().load("test_file_name", new ClassPathResource("test.yml"));
propertySources2.forEach(x-> propertySources.addLast(x));
propertySources.stream()
.forEach(x->{
System.out.println(x);
System.out.println(x.getSource());
});
}在结果中会输出如下内容(部分):
txt
OriginTrackedMapPropertySource {name='application'}
{spring.application.name=ioc}
OriginTrackedMapPropertySource {name='test_file_name'}
{test.environment=success}我们只有加载了配置源,才能获取到配置信息,例如,在@Value注解中才可以解析到值:
java
public class Main2 {
public static void main(String[] args) throws IOException {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
// 加载application.properties配置文件
MutablePropertySources propertySources = applicationContext.getEnvironment().getPropertySources();
List<PropertySource<?>> propertySources1 = new PropertiesPropertySourceLoader().load("application", new ClassPathResource("application.properties"));
propertySources1.forEach(x-> propertySources.addLast(x));
applicationContext.register(MyConfig.class);
applicationContext.refresh();
MyConfig bean = applicationContext.getBean(MyConfig.class);
System.out.println(bean);
}
}
@Data
@Configuration
class MyConfig{
@Value("${spring.application.name}")
private String name;
}结果如下:
txt
MyConfig(name=ioc)如果没有加载application.properties,结果如下:
txt
MyConfig(name=${spring.application.name})附录
1. MetadataReaderFactory
MetadataReaderFactory 是 Spring 框架中一个非常实用的接口,它主要用于读取类的元数据(metadata)信息,而无需加载类到 JVM 中。
在 Spring 框架的内部机制中,尤其是在处理类路径扫描、注解驱动的配置(如 @ComponentScan)以及其他高级特性时,频繁需要获取类的元数据。如果每次都加载类到 JVM 中,会带来显著的性能开销,甚至可能导致类加载顺序或兼容性问题。MetadataReaderFactory 就是为了解决这个问题而设计的。
MetadataReaderFactory 接口的核心作用就是提供了一个方法来创建 MetadataReader 实例:
java
public interface MetadataReaderFactory {
// 根据类名获取 MetadataReader
MetadataReader getMetadataReader(String className) throws IOException;
// 根据 Resource 对象获取 MetadataReader
MetadataReader getMetadataReader(Resource resource) throws IOException;
}MetadataReader: 这是 MetadataReaderFactory 的核心产出。MetadataReader 对象允许访问一个类的以下关键元数据,而无需实际加载该类:
- Annotation Metadata (注解元数据): 可以获取类、方法或字段上定义的注解信息,包括注解类型和注解属性值。
- Class Metadata (类元数据): 包括类名、父类名、接口名、是否是接口、是否是抽象类、访问修饰符等。
- Method Metadata (方法元数据): 包括方法名、参数类型、返回类型、访问修饰符等。
- Field Metadata (字段元数据): 包括字段名、类型、访问修饰符等。
MetadataReaderFactory 接口的实现类可以使用CachingMetadataReaderFactory:
java
CachingMetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory();2. PathMatchingResourcePatternResolver
PathMatchingResourcePatternResolver 是 Spring 框架中一个非常强大的工具类,它实现了 ResourcePatternResolver 接口,主要用于在 类路径、文件系统或其他 Spring 支持的资源位置中,通过 Ant 风格的路径模式(Ant-style path patterns)来查找和解析多个 Resource 对象。
简单来说,它的作用就是:根据提供的通配符路径,找到所有符合条件的资源文件(比如 .xml、.properties、.jar、.class 等),并以Resource对象返回。
PathMatchingResourcePatternResolver 解决了在复杂项目中查找多个资源文件的痛点:
- 统一资源查找: 它提供了一个统一的 API 来查找不同位置(类路径、文件系统)的资源。
- 支持通配符: 它的核心优势在于支持 Ant 风格的路径匹配:
*:匹配零个或多个字符(例如*.xml匹配所有 XML 文件)。**:匹配零个或多个目录(例如com/**/service/*.class匹配com目录下任意深度的service目录中的class文件)。?:匹配一个字符。
- 跨 JAR 包查找:
classpath*:前缀是其最强大的特性,使得在微服务或多模块项目中,能够方便地聚合散落在不同 JAR 包中的同类型资源。 - 简化配置: 避免了手动列出所有资源文件路径的繁琐,特别是当资源数量众多或位置不确定时。
PathMatchingResourcePatternResolver 能够解析两种主要的资源路径模式:
classpath*:前缀:- 当路径以
classpath*:开头时,它会在 所有可用的类路径条目(包括 JAR 文件内部和外部的文件系统目录)中查找匹配的资源。 - 这是它最强大的功能之一,因为它可以扫描多个 JAR 包中的资源。
- 示例:
classpath*:/META-INF/**/*.xml会查找所有 JAR 包中META-INF目录下所有子目录中的所有.xml文件。
- 当路径以
file:前缀或没有前缀的路径:- 当路径以
file:开头,或者只是一个相对/绝对路径时,它会在 文件系统 中查找匹配的资源。 - 示例:
file:/opt/config/*.properties会查找/opt/config目录下所有.properties文件。 my-config/*.yml(如果没有前缀,通常解析为文件系统相对路径,相对于当前工作目录)
- 当路径以
使用如下:
java
PathMatchingResourcePatternResolver pathMatchingResourcePatternResolver = new PathMatchingResourcePatternResolver();
String path = "classpath*:/META-INF/**/*.xml";
Resource[] resources = pathMatchingResourcePatternResolver.getResources(path);3. @Value用法
Spring 框架中的 @Value 注解是一个非常实用的工具,它允许将配置值、系统属性、环境变量,甚至是 Spring 表达式语言(SpEL)的结果直接注入到Spring 管理的 Bean 的字段、方法参数或构造函数参数中。
简单来说,它的主要作用就是从不同的来源获取数据,并把这些数据“注入”到代码中,实现所谓的“外部化配置”。
@Value 的核心功能:
注入字面量(Literal Values): 可以直接将一个固定不变的字符串、数字或布尔值注入到变量中。
java@Value("hello world") private String greeting; // greeting 将会被赋值为 "hello world" @Value("100") private int maxConnections; // maxConnections 将会被赋值为 100注入属性占位符(Property Placeholders): 这是
@Value最常用的场景之一。它允许从配置文件(如application.properties、application.yml)、系统属性或环境变量中读取值。占位符使用${...}语法。假设
application.properties文件中有:propertiesapp.name=My Spring App database.url=jdbc:mysql://localhost:3306/mydb可以在 Bean 中这样注入:
java@Value("${app.name}") private String applicationName; // applicationName 将会被赋值为 "My Spring App" @Value("${database.url}") private String dbUrl; // dbUrl 将会被赋值为 "jdbc:mysql://localhost:3306/mydb" // 你也可以为找不到的属性提供默认值,使用冒号分隔 @Value("${non.existent.property:default_value}") private String someProperty; // 如果 non.existent.property 不存在,someProperty 将是 "default_value"注入 Spring 表达式语言(SpEL)的结果:
@Value结合 SpEL(Spring Expression Language)功能非常强大,允许执行更复杂的逻辑,SpEL表达式使用#{...}语法。例如:- 访问 Bean 的属性:
#{myService.someValue} - 访问系统属性:
#{systemProperties['java.home']} - 访问环境变量:
#{systemEnvironment['PATH']} - 执行简单的计算:
#{1 + 1} - 调用方法:
#{'hello'.toUpperCase()} - 引用字面量:
#{"another literal"}(注意这里是#而非$)
java@Value("#{systemProperties['os.name']}") private String osName; // osName 将会被赋值为操作系统的名称 @Value("#{T(java.lang.Math).PI}") private double piValue; // piValue 将会被赋值为 Math.PI 的值 @Value("#{user.fullName}") // 假设有一个名为 user 的 Bean,且有 getFullName() 方法 private String userName;- 访问 Bean 的属性:
注意,如果要使用@Value注解,需要配合AutowiredAnnotationBeanPostProcessor和ContextAnnotationAutowireCandidateResolver:
java
GenericApplicationContext applicationContext = new GenericApplicationContext();
applicationContext.getDefaultListableBeanFactory()
.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());
applicationContext.registerBean(AutowiredAnnotationBeanPostProcessor.class);4. ContextAnnotationAutowireCandidateResolver
ContextAnnotationAutowireCandidateResolver 是 Spring 框架内部一个至关重要的组件,尤其在使用 注解驱动的配置(比如 @Autowired、@Qualifier、@Primary 等)时。它的主要作用是决定哪些 Bean 是某个依赖项的“合格自动装配候选者”。
可以把它想象成 Spring 进行依赖注入时的智能过滤器。当 Spring 需要将一个 UserDao 注入到 UserService 中时,这个解析器就会介入,来判断你的应用上下文中众多 UserDao Bean 中,哪一个是符合要求的正确选择。
这个解析器负责处理用来控制自动装配的大多数基于注解的规则:
确定自动装配候选者(
isAutowireCandidate): 当 Spring 寻找一个 Bean 来注入某个依赖(例如一个被@Autowired标记的字段)时,这个解析器会评估每个已注册的 Bean 定义。它会检查各种因素,比如:- 这个 Bean 是否被定义为抽象的?(抽象 Bean 不能作为候选者)
- 是否有明确的自动装配排除规则?
- 简而言之,它确保只有“活跃的”、非抽象的、可注入的 Bean 才会被考虑。
处理
@Primary注解 (isPrimary): 如果有多个相同类型的 Bean(例如,两个PaymentGateway实现),Spring 需要一种方式来选择一个默认的。ContextAnnotationAutowireCandidateResolver会查找那些被@Primary标记的 Bean。如果找到了,那个 Bean 将会被优先选择。解析
@Qualifier注解 (getQualifierMatch): 当存在多个相同类型的 Bean 且没有指定@Primary时,可以使用@Qualifier来明确指定想要注入的 Bean 的名称。这个解析器负责:- 识别注入点(字段、构造函数或方法参数)上的
@Qualifier注解。 - 将限定符的值与候选 Bean 上定义的限定符进行比较。
- 确保只有匹配的 Bean 才会被选作注入。这就是 Spring 如何在你指定
@Qualifier("mySqlUserDao")时,知道要注入mySqlUserDao而不是oracleUserDao的原理。
- 识别注入点(字段、构造函数或方法参数)上的
处理
@Lazy注解: 如果一个依赖或一个 Bean 被标记为@Lazy,这个解析器会确保该 Bean 只有在它实际被使用时(即它的方法首次被调用时)才会被初始化,而不是在应用程序启动时。这可以加快启动速度,特别是对于拥有大量 Bean 的大型应用程序。处理@Value注解:在
DefaultListableBeanFactory.doResolveDependency()方法中,会使用AutowireCandidateResolver来解析@Value的值:txtorg.springframework.beans.factory.support.DefaultListableBeanFactory#doResolveDependency() // Step 2: pre-defined value or expression, for example, from @Value Object value = getAutowireCandidateResolver().getSuggestedValue(descriptor);getSuggestedValue()在接口默认实现中没有值:javadefault Object getSuggestedValue(DependencyDescriptor descriptor) { return null; }而在
org.springframework.beans.factory.annotation.QualifierAnnotationAutowireCandidateResolver中有实现,QualifierAnnotationAutowireCandidateResolver是ContextAnnotationAutowireCandidateResolver是父类。之后,在
DefaultListableBeanFactory.doResolveDependency()中解析@Value的值,从而赋值给属性:java// Step 2: pre-defined value or expression, for example, from @Value Object value = getAutowireCandidateResolver().getSuggestedValue(descriptor); if (value != null) { if (value instanceof String strValue) { String resolvedValue = resolveEmbeddedValue(strValue); BeanDefinition bd = (beanName != null && containsBean(beanName) ? getMergedBeanDefinition(beanName) : null); value = evaluateBeanDefinitionString(resolvedValue, bd); } TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter()); try { return converter.convertIfNecessary(value, type, descriptor.getTypeDescriptor()); } catch (UnsupportedOperationException ex) { // A custom TypeConverter which does not support TypeDescriptor resolution... return (descriptor.getField() != null ? converter.convertIfNecessary(value, type, descriptor.getField()) : converter.convertIfNecessary(value, type, descriptor.getMethodParameter())); } }

在GenericApplicationContext中,默认是SimpleAutowireCandidateResolver;
在AnnotationConfigApplicationContext中,默认是ContextAnnotationAutowireCandidateResolver;