Skip to content

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

可以发现容器中只有一个名为beanBeanDefinition,并且,在获取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接口中提供的,ConfigurableApplicationContextBeanFactory的子接口,GenericApplicationContext是实现类,所以我们使用GenericApplicationContext作为容器。

image-20250627191825768

完整代码如下:

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接口方法。

[问题]

我们只是向 容器中添加了MyBeanFactoryPostProcessorBeanDefinition,并没有调用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的作用,案例结构如下:

image-20250627195922950

代码如下:

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

此时容器中只有一个ConfigBeanDefinition

放开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)
preDestroy

6.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 容器只负责 prototype Bean 的创建和初始化。一旦 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@1786f9d5

8. 容器实现

我们之前一直在使用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);
    }
}

// 运行结果:
// bean1

beans.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都有:

image-20250628175428988

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

image-20250628175514859

说明我们自己搭建的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的子接口,我们以BeanNameAwareApplicationContextAware为例:

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="")这三者的顺序如下:

  1. @PostConstruct:由CommonAnnotationBeanPostProcessor提供支持;
  2. InitializingBean:Spring容器内置机制;
  3. @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 @Bean

10. Environment

在Spring中,Environment包含两方面的内容:profilesproperties

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): 操作系统级别的变量,例如 PATHJAVA_HOME
  • 配置文件(Configuration Files): 这是最常见的来源,比如 application.propertiesapplication.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 解决了在复杂项目中查找多个资源文件的痛点:

  1. 统一资源查找: 它提供了一个统一的 API 来查找不同位置(类路径、文件系统)的资源。
  2. 支持通配符: 它的核心优势在于支持 Ant 风格的路径匹配
    • *:匹配零个或多个字符(例如 *.xml 匹配所有 XML 文件)。
    • **:匹配零个或多个目录(例如 com/**/service/*.class 匹配 com 目录下任意深度的 service 目录中的 class 文件)。
    • ?:匹配一个字符。
  3. 跨 JAR 包查找: classpath*: 前缀是其最强大的特性,使得在微服务或多模块项目中,能够方便地聚合散落在不同 JAR 包中的同类型资源。
  4. 简化配置: 避免了手动列出所有资源文件路径的繁琐,特别是当资源数量众多或位置不确定时。

PathMatchingResourcePatternResolver 能够解析两种主要的资源路径模式:

  1. classpath*: 前缀
    • 当路径以 classpath*: 开头时,它会在 所有可用的类路径条目(包括 JAR 文件内部和外部的文件系统目录)中查找匹配的资源。
    • 这是它最强大的功能之一,因为它可以扫描多个 JAR 包中的资源。
    • 示例: classpath*:/META-INF/**/*.xml 会查找所有 JAR 包中 META-INF 目录下所有子目录中的所有 .xml 文件。
  2. 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 的核心功能:

  1. 注入字面量(Literal Values): 可以直接将一个固定不变的字符串、数字或布尔值注入到变量中。

    java
    @Value("hello world")
    private String greeting; // greeting 将会被赋值为 "hello world"
    
    @Value("100")
    private int maxConnections; // maxConnections 将会被赋值为 100
  2. 注入属性占位符(Property Placeholders): 这是 @Value 最常用的场景之一。它允许从配置文件(如 application.propertiesapplication.yml)、系统属性或环境变量中读取值。占位符使用 ${...} 语法。

    假设 application.properties 文件中有:

    properties
    app.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"
  3. 注入 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;

注意,如果要使用@Value注解,需要配合AutowiredAnnotationBeanPostProcessorContextAnnotationAutowireCandidateResolver

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 中,哪一个是符合要求的正确选择。

这个解析器负责处理用来控制自动装配的大多数基于注解的规则

  1. 确定自动装配候选者(isAutowireCandidate): 当 Spring 寻找一个 Bean 来注入某个依赖(例如一个被 @Autowired 标记的字段)时,这个解析器会评估每个已注册的 Bean 定义。它会检查各种因素,比如:

    • 这个 Bean 是否被定义为抽象的?(抽象 Bean 不能作为候选者)
    • 是否有明确的自动装配排除规则?
    • 简而言之,它确保只有“活跃的”、非抽象的、可注入的 Bean 才会被考虑。
  2. 处理 @Primary 注解 (isPrimary): 如果有多个相同类型的 Bean(例如,两个 PaymentGateway 实现),Spring 需要一种方式来选择一个默认的。ContextAnnotationAutowireCandidateResolver 会查找那些被 @Primary 标记的 Bean。如果找到了,那个 Bean 将会被优先选择。

  3. 解析 @Qualifier 注解 (getQualifierMatch): 当存在多个相同类型的 Bean 且没有指定 @Primary 时,可以使用 @Qualifier 来明确指定想要注入的 Bean 的名称。这个解析器负责:

    • 识别注入点(字段、构造函数或方法参数)上的 @Qualifier 注解。
    • 将限定符的值与候选 Bean 上定义的限定符进行比较。
    • 确保只有匹配的 Bean 才会被选作注入。这就是 Spring 如何在你指定 @Qualifier("mySqlUserDao") 时,知道要注入 mySqlUserDao 而不是 oracleUserDao 的原理。
  4. 处理 @Lazy 注解: 如果一个依赖或一个 Bean 被标记为 @Lazy,这个解析器会确保该 Bean 只有在它实际被使用时(即它的方法首次被调用时)才会被初始化,而不是在应用程序启动时。这可以加快启动速度,特别是对于拥有大量 Bean 的大型应用程序。

  5. 处理@Value注解:在DefaultListableBeanFactory.doResolveDependency()方法中,会使用AutowireCandidateResolver来解析@Value的值:

    txt
    org.springframework.beans.factory.support.DefaultListableBeanFactory#doResolveDependency()
      
      // Step 2: pre-defined value or expression, for example, from @Value
    	Object value = getAutowireCandidateResolver().getSuggestedValue(descriptor);

    getSuggestedValue()在接口默认实现中没有值:

    java
    default Object getSuggestedValue(DependencyDescriptor descriptor) {
      return null;
    }

    而在org.springframework.beans.factory.annotation.QualifierAnnotationAutowireCandidateResolver中有实现,QualifierAnnotationAutowireCandidateResolverContextAnnotationAutowireCandidateResolver是父类。

    之后,在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()));
        }
      }

image-20250628200552547

GenericApplicationContext中,默认是SimpleAutowireCandidateResolver

AnnotationConfigApplicationContext中,默认是ContextAnnotationAutowireCandidateResolver