Spring官方文档学习之IoC容器

注:spring版本为v6.1.5,且文章基本以基于注解的方式去使用Spring
官方文档链接

IoC容器

简介: IoC 也称为依赖注入 (DI)。对象仅通过构造函数参数、工厂方法的参数或在构造对象实例或从工厂方法返回后在对象实例上设置的属性来定义其依赖项。然后,容器在创建 bean 时注入这些依赖项。这个过程从根本上来说是 bean 本身的逆过程(因此得名“控制反转”)。

理解:控制反转(Ioc)是思想,依赖注入(DI)是实现。

BeanFactory接口提供了能够管理任何类型对象的高级配置机制。

ApplicationContext是 BeanFactory的子接口。BeanFactory提供了配置框架和基本功能,而ApplicationContext添加了更多企业特定的功能。

ApplicationContext是一个高级工厂接口,能够维护不同 bean 及其依赖项的注册表。

1. Bean概述

Spring IoC 容器管理一个或多个 bean。

在容器本身内,这些 bean 定义表示为BeanDefinition 对象,其中包含以下元数据(以及其他信息):

  • 包限定的类名:通常是所定义的 bean 的实际实现类
  • Bean 行为配置元素,说明 Bean 在容器中的行为方式(范围、生命周期回调等)。
  • 对 Bean 完成其工作所需的其他 Bean 的引用。这些引用也称为协作者或依赖项
  • 在新创建的对象中设置的其他配置设置 — 例如,池的大小限制或管理连接池的 bean 中使用的连接数。

ApplicationContext还允许注册在容器外部(由用户)创建的现有对象。

bean definition:

PropertyExplained
Class实例化的Bean
NameBean的名称
ScopeBean的范围
Constructor arguments构造函数参数
Properties属性值
Autowiring mode自动装配模式
Lazy initialization mode懒加载模式
Initialization method初始化回调
Destruction method销毁回调

1.1. Bean命名

每个 bean 都有一个或多个标识符。这些标识符在托管 bean 的容器中必须是唯一的。一个 bean 通常只有一个标识符,但是可以拥有多个别名。

Bean 命名约定:bean 名称以小写字母开头,并采用驼峰式。例如:accountManager、 accountService、userDao、loginController等。
ps:Spring对于未命名的Bean会默认采用类名并将首字母转为小写。但是,在特殊情况下,当有多个字符并且第一个和第二个字符均为大写时,原始大小写将被保留。

1.2. 实例化Bean

  1. 使用构造函数实例化
  2. 使用静态工厂方法实例化
  3. 使用实例工厂方法实例化:从容器中调用现有 bean 的非静态方法来创建新 bean(将工厂bean也注入Ioc容器中管理 )

2. 依赖关系

典型的企业应用程序不包含单个对象(或 Spring 术语中的 bean)。即使是最简单的应用程序也有一些对象一起工作来呈现最终用户所认为的连贯的应用程序。

2.1. 依赖注入

概述:依赖注入 (DI) 是一个过程,对象仅通过构造函数参数、工厂方法的参数或对象实例构造后设置的属性来定义其依赖项。容器在创建 bean 时注入这些依赖项。

依赖注入的两种方式

  • 基于构造函数的依赖注入:基于构造函数的 DI 是通过容器调用带有多个参数的构造函数来完成的,每个参数代表一个依赖项。
  • 基于 Setter 的依赖注入:基于 Setter 的 DI 是通过容器在调用无参构造函数或无参static工厂方法来实例化 bean 后调用 bean 上的 setter 方法来完成的。

最好的经验法则是使用构造函数来实现强制依赖项,并使用 setter 方法或配置方法来实现可选依赖项。

依赖解析过程

  • ApplicationContext带着描述bean的配置元素据创建和初始化。配置元数据可以通过 XML、Java 代码或注解来指定。
  • 对于每个 bean,其依赖项以属性、构造函数参数或静态工厂方法的参数(如果您使用它而不是普通构造函数)的形式表示。这些依赖关系是在实际创建 bean 时提供给 bean 的。
  • 每个属性或构造函数参数都是要设置的值的实际定义,或对容器中另一个 bean 的引用。
  • 作为值的每个属性或构造函数参数都会从其指定格式转换为该属性或构造函数参数的实际类型。默认情况下,Spring 可以将以字符串格式提供的值转换为所有内置类型,例如int、 long、String、boolean等。

延迟初始化Bean
默认情况下,ApplicationContext 会在初始化过程中创建和配置所有的单例Bean。如果不需要,可以通过Bean定义标记为延迟初始化来防止预实例化单例Bean。延迟初始化的 bean 告诉 IoC 容器在第一次请求时而不是在启动时创建一个 bean 实例。

2.2. 自动装配

Spring容器可以自动装配bean协作者之间的关系。
区别于依赖注入,自动装配是以一种特殊的依赖注入方式。依赖注入只有构造器注入和setter注入两种方式,而自动装配则是通过spring自行注入,不需要繁琐的去定义构造函数和setter方法。
自动装配的优点

  • 自动装配可以显着减少指定属性或构造函数参数的需要。
  • 自动装配可以随着对象的发展而更新配置。例如,如果您需要向类添加依赖项,则可以自动满足该依赖项,而无需修改配置。

自动装配的局限性和缺点

  • 显式依赖项(构造函数设置、直接设置值等)始终会覆盖自动装配。自动装配无法装配简单属性,例如基本数据元素、Strings、Classes等。
  • 自动装配不如显式装配精确。因为自动装配可能会出现同名的,要尽量避免让Spring去猜测,以防出现歧义
  • 从Spring容器生成文档的工具可能无法获得装配信息。
  • 容器内可能会有多个Bean与自动装配的方法或构造函数匹配。对于Arrays、Collections或Map来说,这不是问题。但是对于单个值的依赖项,这种歧义是不能解决的。如果没有可用的唯一 bean 定义,则会引发异常。

3. Bean的范围

ScopeDescription
singleton(默认)将单个 bean 定义范围限定为每个 Spring IoC 容器的单个对象实例。
prototype将单个 bean 定义的范围限定为任意数量的对象实例。
request将单个 bean 定义的范围限定为单个 HTTP 请求的生命周期。也就是说,每个 HTTP 请求都有自己的 Bean 实例,该实例是根据单个 Bean 定义创建的。仅在支持 Web 的 Spring 上下文中有效ApplicationContext。
session将单个 bean 定义的范围限定为 HTTP 的生命周期Session。仅在支持 Web 的 Spring 上下文中有效ApplicationContext。
application将单个 bean 定义的范围限定为ServletContext.仅在支持 Web 的 Spring 上下文中有效ApplicationContext。
websocket将单个 bean 定义的范围限定为WebSocket.仅在支持 Web 的 Spring 上下文中有效ApplicationContext。

4. 定制Bean的特性

Spring 框架提供了许多可用于自定义 bean 性质的接口。

4.1. 生命周期回调

要与容器对 bean 生命周期的管理进行交互,可以通过实现 Spring的InitializingBeanDisposableBean接口。容器要求 afterPropertiesSet()和destroy()让 Bean 在初始化和销毁​​ Bean 时执行某些操作。

import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;

public class ExampleBean implements InitializingBean, DisposableBean {
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("do something after init...");
    }
    
    @Override
    public void destroy() throws Exception {
        System.out.println("do something before destroy...");
    }
}

现在通常可以使用@PostConstruct@PreDestroy代替。

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

public class ExampleBean{

    @PostConstruct
    public void init(){
        System.out.println("ExampleBean init...");
    }

    @PreDestroy
    public void destroy(){
        System.out.println("ExampleBean destroy...");
    }
}

4.2. Lifecycle、SmartLifecycle 以及 LifecycleProcessor

  • Lifecycle接口:Lifecycle接口定义了基本的启动start()和停止stop()方法,允许Spring容器管理那些具有生命周期的对象,比如数据库连接池、监听器、定时任务等。当Spring应用上下文(ApplicationContext)启动和关闭时,它会遍历所有实现了Lifecycle接口的bean并调用对应的方法。通常需要显示的去调用
  • SmartLifecycle接口:martLifecycle 是 Lifecycle 接口的扩展,提供了更高级别的生命周期管理功能。除了start()和stop()之外,还引入了以下特性:
    • int getPhase():返回一个整数表示启动和停止时的执行顺序。数字越小,优先级越高,在启动时会先被启动,在停止时会后被停止。
    • boolean isAutoStartup():标识该组件是否在Spring容器启动时自动启动。
    • void stop(Runnable callback):提供了一个可选的回调函数,在停止时可以异步执行其他操作。
  • LifecycleProcessor接口:SmartLifecycleProcessor 是Spring内部用来管理和协调所有实现了SmartLifecycle接口的bean的处理器。它负责根据getPhase()方法返回的阶段值,有序地启动和停止这些bean。默认的实现是DefaultLifecycleProcessor。

4.3. Aware接口

Spring提供了广泛的Aware回调接口,让 bean 向容器表明它们需要某种基础设施依赖。

很多时候我们可以使用Spring的依赖注入(DI)机制,如@Autowired注解,直接注入需要的资源,如ApplicationContext、BeanFactory等。这样做可以使代码更加清晰和简洁,同时也降低了组件与Spring容器的耦合度。然而,在早期版本的Spring框架中,尤其是注解驱动编程还未广泛采用之前,实现Aware接口是一种常用的方式去获取Spring容器的服务。这是因为:

  • 在Spring较早的版本中,注解驱动编程尚未成熟,实现Aware接口是当时主流的做法。尽管现在推荐使用注解,但为了保持向后兼容和满足老项目的需求,Spring仍然保留了这些接口。
  • 在某些特殊场景下,可能无法直接通过@Autowired注入,例如在非Spring管理的类中需要获取Spring容器的一些上下文信息,这时实现Aware接口就成为了一种可行的选择。
  • 虽然注解注入方便且直观,但在某些复杂的应用场景中,实现Aware接口可以提供更多的灵活性,允许开发者在Bean初始化阶段做更多的定制化处理。

总之,目前推荐尽可能使用注解而非实现Aware接口来注入依赖,但如果遇到特殊情况或者有特殊需求时,实现Aware接口仍不失为一种有效的手段。同时,随着Spring框架的发展,许多Aware接口的功能已经被其他的编程模型所取代,比如@Autowired配合ApplicationContextAware可以由

 @Autowired
 private ApplicationContext context;

代替。
其他Aware接口

5. 容器拓展

通常,应用程序开发人员不需要创建ApplicationContext实现类的子类。Spring IoC 容器可以通过插入特殊集成接口的实现来扩展。

1、BeanPostProcessor(针对Bean)
实现该接口,可以在Spring容器完成实例化、配置和初始化Bean前后(如 InitializingBean 的afterPropertiesSet或自定义 init 方法)实现一些自定义逻辑。
BeanPostProcessor实例的范围是每个容器的,不同容器的BeanPostProcessor实例不会互相影响。

2、BeanFactoryPostProcessor(针对Bean的元数据)
BeanFactoryPostProcessor对 bean 配置元数据进行操作。也就是说,Spring IoC容器允许BeanFactoryPostProcessor读取配置元数据,并可能在容器实例化除BeanFactoryPostProcessor实例之外的任何bean之前更改它。BeanFactoryPostProcessor主要是用于修改BeanDefinition。

ps:对于BeanFactoryPostProcessor和BeanPostProcessor,无论是否启用全局懒加载模式,都会在Spring容器初始化早期阶段被积极地实例化和执行,这是由其在整个IoC容器生命周期中的作用决定的。因为它们的作用是在Spring容器初始化过程中对其他Bean的定义或实例进行处理的关键组件。

3、自定义FactoryBean实例化逻辑
可以为本身就是工厂的对象实现org.springframework.beans.factory.FactoryBean接口。
FactoryBean接口提供了三种方法:

  • T getObject():返回该工厂创建的对象的实例。该实例可能会被共享,具体取决于该工厂是否返回单例或原型。
  • boolean isSingleton():如果FactoryBean返回单例则返回true,否则返回false。该方法的默认实现返回true。
  • Class<?> getObjectType():返回对象的getObject()方法返回的类型或者null(不知道类型)。

当需要向容器请求实际的FactoryBean实例本身而不是它生成的bean时,请在调用ApplicationContext的getBean()方法时,在bean的id前面加上&符号。
在容器上调用getBean(“myBean”)将返回FactoryBean生产的对象,而调用getBean(“&myBean”)将返回FactoryBean实例本身。

ApplicationContext的GetBean()方法总结

  • 对于普通的Bean,getBean(beanName) 返回的是普通Bean的实例。
  • 对于实现了 FactoryBean 的Bean,getBean(factoryBeanName) 通常返回的是由该工厂Bean生成的产品Bean实例。
  • 若要获得 FactoryBean 类型的Bean自身的实例,应该使用 getBean("&factoryBeanName")

6. 容器启动过程

Spring容器的启动过程涉及多个关键步骤,确保应用程序能够正确运行。以下是该过程的关键步骤

  1. 加载配置文件:Spring容器会读取XML配置文件、注解配置或Java配置等,将配置信息加载到内存中。
  2. 创建BeanDefinition:解析配置文件,将每个Bean的信息封装成BeanDefinition对象,包括类名、作用域、依赖关系等信息。
  3. 实例化Bean:根据BeanDefinition中的信息,通过反射机制实例化Bean,并将其放入Bean容器中。
  4. 设置Bean属性(依赖注入):自动装配Bean的属性,将Bean所依赖的其他Bean注入到当前Bean中。
  5. 调用Bean的初始化方法:如果Bean实现了InitializingBean接口或在配置文件中定义了init-method方法,Spring容器会在实例化Bean后调用其初始化方法。
  6. Bean的使用:将所有初始化完成的Bean放入Bean容器中,供其他Bean或程序使用。

注意区分Bean的实例化和初始化:

  • Bean实例化:是指创建Bean对象的过程。这包括选择合适的构造函数来创建对象实例,以及处理Bean中的占位符和值注入。
  • Bean初始化:是一个更为复杂的过程,它涉及到对Bean进行定制化的设置,如设置属性的值、执行某些方法等。在Spring中,可以通过实现InitializingBean接口、使用@PostConstruct注解或在XML配置中定义init-method来指定初始化方法。

BeanFactoryPostProcessor在Spring容器实例化Bean之前执行,而BeanPostProcessor在Bean初始化阶段,即init方法前后执行。

Spring框架中的BeanFactoryPostProcessor和BeanPostProcessor是两个关键的接口,它们在Bean的生命周期中扮演着不同的角色。具体来说:

  • BeanFactoryPostProcessor:它主要用于在Spring容器实例化Bean之前对Bean的定义进行修改。这个接口允许用户自定义如何修改Bean的定义信息,包括添加、更新或删除Bean定义。这有助于在Bean实例化之前对配置信息进行调整。
  • BeanPostProcessor:它用于在Bean对象的初始化过程中进行一些自定义的处理。这个接口包含两个方法:postProcessBeforeInitializationpostProcessAfterInitialization,分别在Bean的init方法前后被调用。通过实现这个接口,可以对Bean进行一些额外的处理,如代理包装或者属性值的修改等。

7. 基于注解的容器配置

基于注解的配置提供了XML设置的另一种选择,它依赖于字节码元数据而不是XML声明来连接组件。开发人员不使用XML来描述bean连接,而是通过在相关的类、方法或字段声明上使用注解,将配置移动到组件类本身。

7.1. @Autowired

该注解可以实现自动装配的功能

将@Autowired注释应用于构造函数

public class MovieRecommender {

	private final CustomerPreferenceDao customerPreferenceDao;

	@Autowired
	public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
		this.customerPreferenceDao = customerPreferenceDao;
	}

	// ...
}

将@Autowired注释应用于传统的setter 方法

public class SimpleMovieLister {

	private MovieFinder movieFinder;

	@Autowired
	public void setMovieFinder(MovieFinder movieFinder) {
		this.movieFinder = movieFinder;
	}

	// ...
}

@Autowired也可以应用于字段,甚至可以将其与构造函数混合使用

public class MovieRecommender {

	private final CustomerPreferenceDao customerPreferenceDao;

	@Autowired
	private MovieCatalog movieCatalog;

	@Autowired
	public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
		this.customerPreferenceDao = customerPreferenceDao;
	}

	// ...
}

@Autowired也可以应用于数组或集合
数组

public class MovieRecommender {

	@Autowired
	private MovieCatalog[] movieCatalogs;

	// ...
}

Set

public class MovieRecommender {

	private Set<MovieCatalog> movieCatalogs;

	@Autowired
	public void setMovieCatalogs(Set<MovieCatalog> movieCatalogs) {
		this.movieCatalogs = movieCatalogs;
	}

	// ...
}

Map

public class MovieRecommender {

	private Map<String, MovieCatalog> movieCatalogs;

	@Autowired
	public void setMovieCatalogs(Map<String, MovieCatalog> movieCatalogs) {
		this.movieCatalogs = movieCatalogs;
	}

	// ...
}

如果希望数组或集合按某一个顺序去注入bean,则目标bean可以实现org.springframework.core.Ordered接口,或者使用@Order或标准的@Priority注解。否则,它们的顺序遵循容器中相应目标bean定义的注册顺序。
你可以在目标类级别和@Bean方法上声明@Order注解,这适用于单个bean定义(在多个定义使用同一个bean类的情况下)。
@Order注解主要用于影响Bean在某些场景下的注入顺序,而不直接影响Bean的初始化启动顺序。而Bean的初始化顺序更多地是由Bean间自然的依赖关系及@DependsOn注解显式指定的关系所决定的。

如果IoC容器找不到对应的Bean去注入,则会报错。若注入的Bean是非必须的可以声明@Autowired(required = false),就不会报错了。也可以使用@Nullable注解或者Java8的Optional类

// 1
public class SimpleMovieLister {

	@Autowired(required = false)
	public void setMovieFinder(MovieFinder movieFinder) {
		...
	}
}
// 2
public class SimpleMovieLister {

	@Autowired
	public void setMovieFinder(@Nullable MovieFinder movieFinder) {
		...
	}
}
// 3
public class SimpleMovieLister {

	@Autowired
	public void setMovieFinder(Optional<MovieFinder> movieFinder) {
		...
	}
}

你也可以对那些众所周知的可解析依赖的接口使用@Autowired。例如:BeanFactory、ApplicationContext、Environment、ResourceLoader、ApplicationEventPublisher和MessageSource,以及这些接口和它们的扩展接口,如ConfigurableApplicationContext或ResourcePatternResolver。他们是自动解析的,不需要特殊的设置。

:@Autowired、@Inject、@Value和@Resource注解是由Spring的BeanPostProcessor实现处理的。这意味着您不能在自己的BeanPostProcessor或BeanFactoryPostProcessor类型(如果有的话)中应用这些注释。这些类型必须通过使用XML或Spring @Bean方法显式地“连接”起来。

7.2. @Primary

如果按类型自动装配,可能会出现很多候选者实例,使用Spring的@Primary,可以指定优先使用哪一个实例进行注入。

@Configuration
public class MovieConfiguration {

	@Bean
	@Primary
	public MovieCatalog firstMovieCatalog() { ... }

	@Bean
	public MovieCatalog secondMovieCatalog() { ... }

	// ...
}
public class MovieRecommender {

	@Autowired
	private MovieCatalog movieCatalog;

	// ...
}

这里会使用firstMovieCatalog作为Bean注入

7.3. @Qualifier

@Primary当可以确定一个主要候选者时,是在多个实例中使用按类型自动装配的有效方法。当需要对选择过程进行更多控制时,可以使用 Spring 的@Qualifier。一般与@Autowired一起使用。@Qualifier主要作用是给bean绑定限定符值,让bean具有某种特征值。特征值可以不唯一

public class MovieRecommender {

	@Autowired
	@Qualifier("secondMovieCatalog")
	private MovieCatalog movieCatalog;

	// ...
}
@Configuration
public class MovieConfiguration {

	@Bean
	public MovieCatalog firstMovieCatalog() { ... }

	@Bean
	public MovieCatalog secondMovieCatalog() { ... }

	// ...
}

这里会使用secondMovieCatalog作为Bean注入

可以通过@Qualifier对bean进行一个分组注入

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig {

    @Bean(name = "man")
    @Qualifier("human")
    public Man man(){
        return new Man();
    }

    @Bean(name = "women")
    @Qualifier("human")
    public Woman women(){
        return new Woman();
    }

    @Bean(name = "dog")
    @Qualifier("animal")
    public Dog dog(){
        return new Dog();
    }

    @Bean(name = "cat")
    @Qualifier("animal")
    public Cat cat(){
        return new Cat();
    }

}
@Service
public class TestServiceImpl{

    @Autowired
    @Qualifier("animal")
    private List<Animal> animals;

    @Autowired
    @Qualifier("human")
    private List<Animal> humans;
	
	...
}

cat和dog会被注入animals集合,man和women会被注入humans集合

可以创建自己的自定义限定符注解

@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface CustomQualifier{
}

7.4. 使用泛型作为自动装配限定符

假设前面的 beans 实现了通用接口(即Store和 Store)

@Configuration
public class MyConfiguration {

	@Bean
	public StringStore stringStore() {
		return new StringStore();
	}

	@Bean
	public IntegerStore integerStore() {
		return new IntegerStore();
	}
}
@Autowired
private Store<String> s1; // <String> qualifier, injects the stringStore bean

@Autowired
private Store<Integer> s2; // <Integer> qualifier, injects the integerStore bean

我个人理解泛型匹配实际上是一种特殊的按类型匹配

7.5. @Resource

当指定了name属性时,默认按名称查找并注入Bean。

当未显示的指定注解的name属性时,则类似于@Autowired注解。它会尝试按以下顺序进行查找:

  1. 先按类型查找
  2. 按名称查找:如果找不到对于普通Bean此时会抛异常
  3. 对于BeanFactory、 ApplicationContext、ResourceLoader、ApplicationEventPublisher和MessageSource 这些特殊的bean,Spring保证一定能正确找到默认的实现类。
public class Example{
	// 1
	@Resource
	private Animal dog;

	// 2
	@Resource(name="cat") 
	private Animal catttt;
	
	// 3
	@Resource
	private Animal dogggg;

	// 4
	@Resource
	private ApplicationContext context; 

	// ...
}

假设有Dog 和 Cat两个类继承自Animal类,且有两个bean:dog和cat
1、2、4都能成功注入,3这种会报错

7.6. @Value

@Value通常用于注入外部化属性,比如application.properties。
形如@Value("${animal.name:dog}"),dog是默认值,用:分隔

@Component
public class MovieRecommender {

    private final String catalog;

    public MovieRecommender(@Value("${catalog.name}") String catalog) {
        this.catalog = catalog;
    }
}
@Configuration
@PropertySource("classpath:application.properties")
public class AppConfig { }

application.properties文件配置:

catalog.name=MovieCatalog

在这种情况下,catalog参数和字段将等于该MovieCatalog值。

Spring 提供了默认的宽松嵌入值解析器。它将尝试解析属性值,如果无法解析,则将属性名称(例如 ${catalog.name})作为值注入。如果要对不存在的值保持严格控制,则应该声明一个PropertySourcesPlaceholderConfigurerBean,如以下示例所示:

@Configuration
public class AppConfig {

	@Bean
	public static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() {
		return new PropertySourcesPlaceholderConfigurer();
	}
}

使用 JavaConfig配置时PropertySourcesPlaceholderConfigurer, @Bean方法必须是static。 这里是因为包含@Configuration的类将在很早之前实例化,并且负责解析诸如@Value,@Autowired等注解的BeanPostProcessors无法对其执行操作,而PropertySourcesPlaceholderConfigurer则实现了BeanPostProcessors。

${} 如果无法解析任何占位符,使用上述配置可确保 Spring 初始化失败。也可以使用setPlaceholderPrefix、setPlaceholderSuffix或setValueSeparator等方法来定制占位符。

Spring Boot默认配置了PropertySourcesPlaceholderConfigurer获取属性的Beanapplication.properties和application.yml文件。

Spring BeanPostProcessor在后台使用ConversionService来处理将@Value中的String值转换为目标类型的过程。如果你想为你自己的自定义类型提供转换支持,你可以提供你自己的converonservice bean实例,如下面的例子所示:

@Configuration
public class AppConfig {

    @Bean
    public ConversionService conversionService() {
        DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();
        conversionService.addConverter(new MyCustomConverter());
        return conversionService;
    }
}

@Value注解同时也支持使用SpEL表达式

@Component
public class MovieRecommender {

    private final String catalog;

    public MovieRecommender(@Value("#{systemProperties['user.catalog'] + 'Catalog' }") String catalog) {
        this.catalog = catalog;
    }
}

7.7. @PostConstruct和@PreDestroy

@PostConstruct初始化前回调
@PreDestroy销毁前回调

public class CachingMovieLister {

	@PostConstruct
	public void populateMovieCache() {
		// populates the movie cache upon initialization...
	}

	@PreDestroy
	public void clearMovieCache() {
		// clears the movie cache upon destruction...
	}
}

8. 类路径扫描和管理级组件

@Component是Spring中一个管理级的组件,Spring可以自动检测被其标识的类并注册相应的bean。Spring中除了@Component外还有许多其他管理级的注解,例如:@Service、@Controller、@Repository。可以使用这些注解来管理不同特性的Bean,做不同的处理。查看@Configuration、@Bean、@Import和@DependsOn注解,了解如何使用这些注解管理Bean(这里不做过多解释)。

8.1. 使用元注解和组合注解

Spring提供的许多注解都可以在自己的代码中用作元注解。元注解是一种可以应用于其他注释的注解。例如,前面提到的@Service注释是用@Component做元注释的。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component 
public @interface Service {

	// ...
}

组合注解可以重新声明元注解中的属性,允许自定义。例如:Spring的@SessionScope注释将作用域名称硬编码到session,但仍然允许自定义proxyMode。

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Scope(WebApplicationContext.SCOPE_SESSION)
public @interface SessionScope {

	/**
	 * Alias for {@link Scope#proxyMode}.
	 * <p>Defaults to {@link ScopedProxyMode#TARGET_CLASS}.
	 */
	@AliasFor(annotation = Scope.class)
	ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;

}

在使用@SessionScope注解时任可以自定义proxyMode

@Service
@SessionScope(proxyMode = ScopedProxyMode.INTERFACES)
public class SessionScopedUserService implements UserService {
	// ...
}

8.2. @ComponentScan

Spring可以使用@ComponentScan自动检测原型类,并向ApplicationContext注册相应的BeanDefinition实例。
以下是两个支持自动检测的类:

@Service
public class SimpleMovieLister {

	private MovieFinder movieFinder;

	public SimpleMovieLister(MovieFinder movieFinder) {
		this.movieFinder = movieFinder;
	}
}
@Repository
public class JpaMovieFinder implements MovieFinder {
	// implementation elided for clarity
}

为了自动检测这些类并注册相应的bean,可以将@ComponentScan添加到@Configuration类中,其中的basePackages属性是这两个类的公共父包。

@Configuration
@ComponentScan(basePackages = "org.example")
public class AppConfig  {
	// ...
}

使用@ComponentScan的过滤器属性自定义扫描
默认情况下,带有@Component、@Repository、@Service、@Controller、@Configuration注解的类,或者带有@Component注解的自定义注解是Spring可以自动检测到的组件。但是,我们也可以通过应用自定义过滤器来修改和扩展此行为。将被标识的类添加为@ComponentScan注释的includeFilters或excludeFilters属性

FilterType类型:

类型例子描述
annotation (default)org.example.SomeAnnotation在目标组件的类上存在或元存在的注解。
assignable_typeorg.example.SomeClass指定的类
aspectjorg.example…*Service+目标组件要匹配的AspectJ类型表达式
regexorg.example.Default.*与目标组件的类名匹配的正则表达式
customorg.example.MyTypeFilterorg.springframework.core.type.TypeFilter接口的自定义实现
@Configuration
@ComponentScan(basePackages = "org.example",
		includeFilters = @Filter(type = FilterType.REGEX, pattern = ".*Stub.*Repository"),
		excludeFilters = @Filter(Repository.class))
public class AppConfig {
	// ...
}

8.3. 在组件中定义Bean元数据

Spring组件还可以向容器提供bean定义元数据。
例如通过@Qualifier注解标识限定符值:

@Component
public class FactoryMethodComponent {

	@Bean
	@Qualifier("public")
	public TestBean publicInstance() {
		return new TestBean("publicInstance");
	}

	public void doWork() {
		// Component method implementation omitted
	}
}

其他可以指定的方法级注解是@Scope、@Lazy和自定义限定符注解。

@Bean方法在普通@Component类与@Configuration类中的处理方式存在差异

Spring不会对普通的@Component类使用CGLIB代理进行增强。CGLIB是一个强大的代码生成库,它可以动态生成字节码来实现代理功能。
在@Configuration类中,通过CGLIB代理,当调用@Bean方法内的方法或字段时,Spring会创建与其协作对象相关的Bean元数据引用。这意味着这些方法并不是按照常规Java语义被直接调用,而是通过Spring容器进行调用,从而提供Spring Bean的常规生命周期管理以及代理功能。即使在方法内部通过编程方式调用其他的@Bean方法来引用其他Bean,也会受到容器的管理和代理
相反,在一个普通的@Component类中的@Bean方法内调用方法或访问字段时,其行为遵循标准的Java语义,不会有CGLIB处理或其他特别约束。这意味着,当在一个普通组件类的@Bean方法内部调用其他方法或字段时,它们的调用是直接的,不会经过Spring容器进行额外的生命周期管理或代理处理。

@Configuration
public class AppConfig {

    @Bean
    public Service service() {
        return new DefaultService();
    }

    @Bean
    public Client client(Service service) { // 这里通过方法参数注入了service Bean
        return new Client(service);
    }
}

在上述例子中,AppConfig是一个@Configuration类,其中定义了两个@Bean方法——service()和client()。当client()方法被调用时,并不是简单地创建一个新的Client实例并将new DefaultService()作为参数传递给它。相反,Spring IoC容器会拦截这个调用,确保service()方法也被正确调用并返回一个代理后的Service实例,然后将这个代理实例注入到Client中。这样做的好处是可以确保Service实例也符合Spring的生命周期管理,例如如果Service有初始化方法@PostConstruct或者依赖其他bean,这些都会得到正确的处理。

@Component
public class ComponentConfig {

    @Bean
    public Service service() {
        return new DefaultService();
    }

    public Client getClient() { 
        Service service = this.service(); // 直接调用了service()方法
        return new Client(service);
    }
}

在这个例子中,ComponentConfig是一个普通的@Component类,同样包含了一个@Bean方法service()和一个非@Bean方法getClient()。当getClient()方法被执行时,它直接调用了service()方法来获取Service实例。此时,service()方法的调用遵循标准的Java语义,没有CGLIB代理介入,也就是说,DefaultService实例会被立即创建并注入到Client中,但这个Service实例并没有享受到Spring容器的生命周期管理,比如如果有@PostConstruct注解的方法,则可能不会被自动调用。同时,后续对该Service实例的任何修改不会反映到由Spring容器管理的其他地方共享的Bean上。并且每次调用getClient方法都会创建一个新的service。

使用 @Bean方法 注意事项:

  • 在定义后处理器bean(例如,类型为BeanFactoryPostProcessor或BeanPostProcessor)时,@Bean方法声明为静态方法。因为此类bean在容器生命周期的早期被初始化,并且应该避免在那时触发配置的其他部分。
  • 对静态@Bean方法的调用永远不会被容器截获,即静态@Bean方法获取的实例不会被IoC容器管理,甚至在@Configuration类中也不会被截获,因为CGLIB子类化只能覆盖非静态方法。
  • @Configuration类中的常规@Bean方法需要是可重写的——也就是说,它们不能被声明为私有或final。(也可以不被重写,一般是可重写)
  • @Bean方法不仅可以直接在配置类(@Configuration类)中定义,还可以在基类或者接口中定义,并且能够被子类或实现类继承和使用,从而提供更加灵活的配置方式。
  • 单个类可以为同一个bean保存多个@Bean方法。即有多个实现类的情况,在构造时选择具有最多可满足依赖项的实例。

8.4. 命名自动检测到的组件

如果没有在注解中显示命名Bean名称,则默认 Bean 名称生成器将返回非大写的非限定类名称。例如:
以下两个Bean名称分别为myMovieLister和movieFinderImpl

@Service("myMovieLister")
public class SimpleMovieLister {
	// ...
}
@Repository
public class MovieFinderImpl implements MovieFinder {
	// ...
}

如果不想依赖默认的 bean 命名策略,可以提供自定义的 bean 命名策略。首先,实现 BeanNameGenerator 接口,并确保包含默认的无参数构造函数。然后,在配置@ComponentScan扫描器时提供完全限定的类名

@Configuration
@ComponentScan(basePackages = "org.example", nameGenerator = MyNameGenerator.class)
public class AppConfig {
	// ...
}

8.5. @Scope

与spring管理的组件一样,自动检测组件的默认和最常见的作用域是singleton(单例)的。然而,有时您需要一个可以通过@Scope注释指定的不同作用域。你可以在注释中提供作用域的名称,如下例所示:

@Scope("prototype")
@Repository
public class MovieFinderImpl implements MovieFinder {
	// ...
}

要提供范围解析的自定义策略而不是依赖基于注解的方法,可以实现该 ScopeMetadataResolver 接口。然后,可以在配置@ComponentScan扫描器时提供完全限定的类名

@Configuration
@ComponentScan(basePackages = "org.example", scopeResolver = MyScopeResolver.class)
public class AppConfig {
	// ...
}

9. 使用JSR 330标准注解

Spring提供了对JSR-330标准注解(依赖注入)的支持。以与Spring注释相同的方式扫描这些注释。要使用它们,需要引入依赖

<dependency>
	<groupId>jakarta.inject</groupId>
	<artifactId>jakarta.inject-api</artifactId>
	<version>2.0.0</version>
</dependency>

9.1. @Inject和@Named

你可以用@Inject代替@Autowired,使用@Named对注入的依赖项使用名称

public class SimpleMovieLister {

	private MovieFinder movieFinder;

	@Inject
	public void setMovieFinder(@Named("main") MovieFinder movieFinder) {
		this.movieFinder = movieFinder;
	}

	// ...
}

9.2. @Named和@ManagedBean

使用Named@ManagedBean等价于使用@Component

@Named("movieListener")  // @ManagedBean("movieListener") could be used as well
public class SimpleMovieLister {

	private MovieFinder movieFinder;

	@Inject
	public void setMovieFinder(MovieFinder movieFinder) {
		this.movieFinder = movieFinder;
	}

	// ...
}

当使用@Named或@ManagedBean时,也可以以使用@ComponentScan去扫描组件。
与@Component相反,@Named和@ManagedBean注解都是不可组合的,不能用他们来构造自定义的注解。

9.3. 对比Spring注解和JSR-330标准注解

springjakarta.inject.*对比
@Autowired@Inject@Inject没有’required’属性。
@Component@Named / @ManagedBeanJSR-330不可组合注解
@Scope(“singleton”)@SingletonJSR-330的默认作用域类似于Spring的原型。但是,为了使其与Spring的一般默认值保持一致,在Spring容器中声明的JSR-330 bean默认情况下是单例的。为了使用单例以外的作用域,你应该使用Spring的@Scope注释。jakarta.inject还提供了一个jakarta.inject.Scope注释:但是,这个注释仅用于创建自定义注释。
@Qualifier@Qualifier / @Namedjakarta.inject.Qualifier只是一个用于构建自定义限定符的元注释。具体的字符串限定符(比如Spring带值的@Qualifier)可以通过jakarta.inject.Named来关联。
@Value--
@Lazy--
ObjectFactoryProviderprovider是Spring的ObjectFactory的直接替代品,只是get()方法名更短。它还可以与Spring的@Autowired或无注释的构造函数和setter方法结合使用。

10. 基于Java的容器配置

这里主要介绍如何在 Java 代码中使用注解来配置 Spring 容器。

10.1. @Bean和@Configuration

Spring的Java配置支持中的核心是带@Configuration注释的类和带@Bean注释的方法。@Bean注释用于指示方法实例化、配置和初始化要由Spring IoC容器管理的新对象。被@Configuration注解标记的类表明它的主要目的是作为bean定义的来源。
此外,@Configuration标记的类允许通过调用同一类中的其他@Bean方法来定义bean间依赖关系。

@Configuration
public class AppConfig {

	@Bean
	public MyServiceImpl myService() {
		return new MyServiceImpl();
	}
}

Full @Configuration vs “lite” @Bean mode

当@Bean方法在没有使用@Configuration注释的类中声明时,它们被称为以“精简”模式处理。这里的“精简模式”意味着这类Bean方法所在的类不被视为专门用来定义Bean配置的类,而是该类有自己的主要功能,比如它可能是某个服务组件的实现类。
在这种情况下,@Bean方法就像是该类的一个附加功能,允许类通过这些方法向Spring IoC容器公开额外的Bean实例。换句话说,这些类不仅可以提供其原有的业务逻辑,还可以充当Bean工厂,通过@Bean注解的方法创建和管理其他Bean。这对于服务组件或者其他实体类而言,是一种扩展其功能的方式,使它们能够参与到Spring容器的依赖注入和管理中,从而实现更灵活的组件组装和协同工作。

10.2. 使用AnnotationConfigApplicationContext实例化Spring容器

@Configuration

  • @Configuration类主要用于定义Spring应用的配置和Bean的创建逻辑。
  • 这些类通常包含一系列@Bean方法,每个方法对应一个Bean的定义,Spring容器会调用这些方法来实例化和初始化Bean。
  • @Configuration类可以互相引用,通过@Import注解导入其他配置类,或者通过@Bean方法间的相互调用来建立Bean间的依赖关系。
  • @Configuration类本身也会被注册为一个Bean,可以拥有自己的依赖注入。

@Component(及其衍生注解如@Service、@Repository、@Controller)和JSR-330注解类

  • 这些注解用于标识一个类为Spring容器管理的组件,通常这些类代表了应用中的业务逻辑、数据访问层或控制器等。
  • Spring容器会自动扫描含有这些注解的类,并将其注册为Bean定义。
  • 依赖注入通常通过注解如@Autowired、@Inject等来完成,Spring容器会根据类型、名称或者构造器参数自动注入Bean的依赖。
  • 这些类一般不直接定义如何创建其他Bean,而是专注于自身的业务逻辑。

总结起来,@Configuration类更偏向于系统配置和Bean工厂的角色,而@Component和JSR-330注解类则主要扮演着业务逻辑组件的角色。前者更侧重于整体框架和Bean的组装,后者则专注于业务的具体实现。两者共同构成了Spring IoC容器中的Bean生态系统。

AnnotationConfigApplicationContext实例化方式

  1. 在实例化一个AnnotationConfigApplicationContext时,可以使用@Configuration类作为输入。
public static void main(String[] args) {
	ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
	MyService myService = ctx.getBean(MyService.class);
	myService.doStuff();
}
  1. AnnotationConfigApplicationContext并不局限于只使用@Configuration类。任何@Component或JSR-330注释类都可以作为构造函数的输入
public static void main(String[] args) {
	ApplicationContext ctx = new AnnotationConfigApplicationContext(MyServiceImpl.class, Dependency1.class, Dependency2.class);
	MyService myService = ctx.getBean(MyService.class);
	myService.doStuff();
}
  1. 使用一个无参数的构造函数实例化一个AnnotationConfigApplicationContext,然后使用register()方法配置它。
public static void main(String[] args) {
	AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
	ctx.register(AppConfig.class, OtherConfig.class);
	ctx.register(AdditionalConfig.class);
	ctx.refresh();
	MyService myService = ctx.getBean(MyService.class);
	myService.doStuff();
}
  1. 使用一个无参数的构造函数,并使用组件扫描的方式。(AnnotationConfigApplicationContext 的 scan方法)
public static void main(String[] args) {
	AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
	ctx.scan("com.acme");
	ctx.refresh();
	MyService myService = ctx.getBean(MyService.class);
}

10.3. 使用AnnotationConfigWebApplicationContext支持Web应用程序

AnnotationConfigWebApplicationContextAnnotationConfigApplicationContext的扩展,专为Web应用设计,适用于Servlet环境。它除了包含AnnotationConfigApplicationContext的所有功能外,还增加了对Web应用特有的特性的支持,比如处理Servlet监听器、过滤器、Spring MVC的DispatcherServlet以及其他与Web容器交互的功能。

11. @Bean

@Bean是一个方法级注释,是XML 元素<bean/>的直接类比。
声明一个 Bean

@Configuration
public class AppConfig {

	@Bean
	public TransferServiceImpl transferService() {
		return new TransferServiceImpl();
	}
}

前面的配置与以下 Spring XML 完全相同:

<beans>
	<bean id="transferService" class="com.acme.TransferServiceImpl"/>
</beans>

@Bean还可以声明接口(或基类)的类型来作为方法的返回

@Configuration
public class AppConfig {

	@Bean
	public TransferService transferService() {
		return new TransferServiceImpl();
	}
}

推荐返回更具体的类型

Bean 依赖关系

带@Bean注解的方法可以具有任意数量的参数。如果TransferService 需要一个AccountRepository,我们可以使用方法参数来实现该依赖关系。

@Configuration
public class AppConfig {

	@Bean
	public TransferService transferService(AccountRepository accountRepository) {
		return new TransferServiceImpl(accountRepository);
	}
}

生命周期回调
使用@Bean注解种的属性initMethoddestroyMethod

public class BeanOne {

	public void init() {
		// initialization logic
	}
}

public class BeanTwo {

	public void cleanup() {
		// destruction logic
	}
}

@Configuration
public class AppConfig {

	@Bean(initMethod = "init")
	public BeanOne beanOne() {
		return new BeanOne();
	}

	@Bean(destroyMethod = "cleanup")
	public BeanTwo beanTwo() {
		return new BeanTwo();
	}
}

如果 bean 实现InitializingBean、DisposableBean或Lifecycle,则它们各自的方法将由容器调用。还完全支持标准Aware接口集(例如BeanFactoryAware、 BeanNameAware、 MessageSourceAware、 ApplicationContextAware等)

指定Bean范围
使用@Scope注解

@Configuration
public class MyConfiguration {

	@Bean
	@Scope("prototype")
	public Encryptor encryptor() {
		// ...
	}
}

自定义 Bean 命名
默认情况下,配置类使用@Bean方法的名称作为结果 bean 的名称。但是,可以使用name属性覆盖此功能

@Configuration
public class AppConfig {

	@Bean("myThing")
	public Thing thing() {
		return new Thing();
	}
}

Bean 别名
有时需要为单个 bean 提供多个名称,也称为 bean 别名。为此,注解name的属性接受一个字符串数组。

@Configuration
public class AppConfig {

	@Bean({"dataSource", "subsystemA-dataSource", "subsystemB-dataSource"})
	public DataSource dataSource() {
		// instantiate, configure and return DataSource bean...
	}
}

Bean描述
有时,提供 Bean 的更详细的文本描述会很有帮助。
要向@Bean中添加描述,可以使用 @Description 注解

@Configuration
public class AppConfig {

	@Bean
	@Description("Provides a basic example of a bean")
	public Thing thing() {
		return new Thing();
	}
}

12. @Configuration

@Configuration是类级别的注释,指示对象是bean定义的源。@Configuration类通过@ bean注释的方法声明bean。对@Configuration类上的@Bean方法的调用也可用于定义bean间依赖关系。

@Configuration
public class AppConfig {

	@Bean
	public BeanOne beanOne() {
		return new BeanOne(beanTwo());
	}

	@Bean
	public BeanTwo beanTwo() {
		return new BeanTwo();
	}
}

@Configuration的proxyBeanMethods属性

@Configuration
public class AppConfig {

	@Bean
	public ClientService clientService1() {
		ClientServiceImpl clientService = new ClientServiceImpl();
		clientService.setClientDao(clientDao());
		return clientService;
	}

	@Bean
	public ClientService clientService2() {
		ClientServiceImpl clientService = new ClientServiceImpl();
		clientService.setClientDao(clientDao());
		return clientService;
	}

	@Bean
	public ClientDao clientDao() {
		return new ClientDaoImpl();
	}
}

clientDao()在clientService1()和clientService2()中分别被调用一次。由于该方法创建了一个ClientDaoImpl的新实例并返回它,因此通常期望有两个实例(每个ClientService 一个),但在上述例子中两次调用返回的是同一个对象。这肯定会有问题:在Spring中,实例化的bean默认具有单例作用域。这就是神奇之处:所有的@Configuration类都在启动时用CGLIB子类化。在子类中,子方法在调用父方法并创建新实例之前,首先检查容器中是否存在任何缓存的(限定作用域的)bean。
如果希望避免任何CGLIB强加的限制,请考虑在non-@Configuration类上声明@Bean方法(例如,在普通的@Component类上),或者用@Configuration(proxyBeanMethods = false)注释配置类。

13. 环境抽象

接口Environment是集成在容器中的抽象,它对应用程序环境的两个关键方面进行建模:配置文件 和属性。

13.1. @Profile

该注解允许在不同环境中注册不同的 Bean

@Configuration
@Profile("development")
public class StandaloneDataConfig {

	@Bean
	public DataSource dataSource() {
		return new EmbeddedDatabaseBuilder()
			.setType(EmbeddedDatabaseType.HSQL)
			.addScript("classpath:com/bank/config/sql/schema.sql")
			.addScript("classpath:com/bank/config/sql/test-data.sql")
			.build();
	}
}
@Configuration
@Profile("production")
public class JndiDataConfig {

	@Bean(destroyMethod = "") 
	public DataSource dataSource() throws Exception {
		Context ctx = new InitialContext();
		return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
	}
}

上述例子在development和production环境下注册不同的bean

支持使用逻辑符

  • !:非
  • &:与
  • |: 或

激活Profile

方法1:通过ApplicationContext的Environment

AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().setActiveProfiles("dev", "test");
ctx.refresh();

方法2:通过JVM启动参数配置spring.profiles.active

-Dspring.profiles.active ="dev,test"

方法3:通过yml配置spring.profiles.active

spring:
	profiles:
		active: dev,test

在单元测试中可以使用@ActiveProfiles注解去激活环境

默认配置
默认配置文件表示在没有激活配置文件时启用的配置文件

@Configuration
@Profile("default")
public class DefaultDataConfig {

	@Bean
	public DataSource dataSource() {
		return new EmbeddedDatabaseBuilder()
			.setType(EmbeddedDatabaseType.HSQL)
			.addScript("classpath:com/bank/config/sql/schema.sql")
			.build();
	}
}

13.2. @PropertySource

PropertySource抽象
Spring的环境抽象在属性源的可配置层次结构上提供搜索操作。

ApplicationContext ctx = new GenericApplicationContext();
Environment env = ctx.getEnvironment();
boolean containsMyProperty = env.containsProperty("my-property");
System.out.println("Does my environment contain the 'my-property' property? " + containsMyProperty);

当你查询Environment对象是否包含某个特定属性时,Spring会遍历关联的一系列PropertySource对象,检查这些来源中是否包含了你查询的属性。StandardEnvironment作为默认环境配置,已经预先配置好了从系统属性和环境变量两个来源查找属性的能力。这意味着,你可以方便地在Spring应用中通过Environment对象检索系统(System.getProperties())或环境层面(System.getenv())的配置信息。
注意:

执行的搜索是分层的。默认情况下,系统属性优先于环境变量。因此,如果在调用envy . getproperty(“my-property”)期间碰巧在两个地方都设置了my-property属性,则系统属性值“胜出”并被返回。注意,属性值不是合并的,而是被前面的条目完全覆盖。
对于一个通用的standardservletenenvironment,完整的层次结构如下所示,最高优先级的条目位于顶部:

  1. ServletConfig参数(如果适用——例如,DispatcherServlet上下文)
  2. ervletContext参数(web.xml上下文参数条目)
  3. JNDI环境变量(java:comp/env/ entries)
  4. JVM系统属性(-D命令行参数)
  5. JVM系统环境(操作系统环境变量)

你也可以自定义PropertySources

ConfigurableApplicationContext ctx = new GenericApplicationContext();
MutablePropertySources sources = ctx.getEnvironment().getPropertySources();
sources.addFirst(new MyPropertySource());

上述代码中,MyPropertySource在搜索中以最高优先级被添加。如果它包含my-property属性,则检测并返回该属性。

使用@PropertySource

@PropertySource注解向Spring环境添加PropertySource提供了一种方便的声明性机制。

给定一个名为app.properties的文件,其中包含键值对testbean.name=myTestBean,下面的@Configuration类使用@PropertySource的方式是,调用testBean.getName()返回myTestBean:

@Configuration
@PropertySource("classpath:/com/myco/app.properties")
public class AppConfig {

 @Autowired
 Environment env;

 @Bean
 public TestBean testBean() {
  TestBean testBean = new TestBean();
  testBean.setName(env.getProperty("testbean.name"));
  return testBean;
 }
}

@PropertySource资源位置中出现的任何${…}占位符都会根据已经在环境中注册的属性源集进行解析

@Configuration
@PropertySource("classpath:/com/${my.placeholder:default/path}/app.properties")
public class AppConfig {

 @Autowired
 Environment env;

 @Bean
 public TestBean testBean() {
  TestBean testBean = new TestBean();
  testBean.setName(env.getProperty("testbean.name"));
  return testBean;
 }
}

假设my.placeholder存在于已注册的属性源,则占位符将解析为相应的值。如果没有,则使用默认值default/path。如果未指定默认值并且无法解析属性, 则会抛出异常IllegalArgumentException。

14. LoadTimeWeaver

LoadTimeWeaver是Spring框架中用于在类加载时动态织入(weave)代码的技术组件。它的核心作用是在类加载的过程中修改字节码,以支持运行时的AOP(面向切面编程)功能,特别是对于那些无法在编译时织入切面的第三方类库或运行时才可用的类。

具体来说,LoadTimeWeaver的工作机制是利用Java的类加载机制,在类被加载到JVM之前或加载后即时修改类的字节码,插入横切关注点(例如,事务管理、日志、缓存等)。这对于那些无法事先预知或直接修改源代码的场景非常有用,因为它可以在运行时对类的行为进行增强。

要启用 load-time weaving,可以将@EnableLoadTimeWeaving添加到@Configuration类中:

@Configuration
@EnableLoadTimeWeaving
public class AppConfig {
}

一旦为ApplicationContext配置好,该ApplicationContext中的任何bean都可以实现LoadTimeWeaverAware,从而接收到对加载时编织实例的引用。
通过LoadTimeWeaver,Spring能够实现在运行时灵活地织入切面,极大地增强了应用的可扩展性和模块化能力。

15. ApplicationContext的其他功能

  • 通过MessageSource接口以i18n样式访问消息。
  • 通过ResourceLoader接口访问资源,如url和文件。
  • 事件发布,即通过使用ApplicationEventPublisher接口实现ApplicationListener接口的bean。
  • 通过HierarchicalBeanFactory接口加载多个(分层)上下文,让每个上下文都专注于一个特定的层,例如应用程序的web层。

15.1. 国际化功能:MessageSource

ApplicationContext接口扩展了一个名为MessageSource的接口,因此提供了国际化(“i18n”)功能。Spring还提供了HierarchicalMessageSource接口,它可以分层地解析消息。这些接口一起为Spring实现消息解析提供了基础。在这些接口上定义的方法包括:

  • String getMessage(String code, Object[] args, String default, Locale loc):用于从MessageSource检索消息的基本方法。如果没有找到指定语言环境的消息,则使用默认消息。通过使用标准库提供的MessageFormat功能,传入的任何参数都成为替换值。
  • String getMessage(String code, Object[] args, Locale loc):本质上与前一个方法相同,但有一个区别:不能指定默认消息。如果找不到消息,则抛出NoSuchMessageException。
  • String getMessage(MessageSourceResolvable resolvable, Locale locale):上述方法中使用的所有属性也都包装在一个名为MessageSourceResolvable类中,可以与此方法一起使用该类。

当加载ApplicationContext时,它会自动搜索上下文中定义的MessageSource bean。bean的名称必须为messageSource。如果找到了这样的bean,那么对上述方法的所有调用都将委托给消息源。如果没有找到消息源,ApplicationContext将尝试查找包含同名bean的父节点。如果是,它将使用该bean作为MessageSource。如果ApplicationContext找不到任何消息源,则实例化一个空的DelegatingMessageSource,以便能够接受对上面定义的方法的调用。

Spring提供了三个MessageSource实现,ResourceBundleMessageSource, ReloadableResourceBundleMessageSource和StaticMessageSource。它们都实现了HierarchicalMessageSource,以便进行嵌套消息传递。StaticMessageSource很少使用,但它提供了将消息添加到源的编程方法。下面的例子展示了ResourceBundleMessageSource:

使用举例:
该示例假设在类路径中定义了两个资源包,分别是format、exceptions

<beans>
	<bean id="messageSource"
			class="org.springframework.context.support.ResourceBundleMessageSource">
		<property name="basenames">
			<list>
				<value>format</value>
				<value>exceptions</value>
				<value>windows</value>
			</list>
		</property>
	</bean>
</beans>

format文件

# in format.properties
message=Alligators rock!

exceptions文件

# in exceptions.properties
argument.required=The {0} argument is required.

使用:

public static void main(String[] args) {
	MessageSource resources = new ClassPathXmlApplicationContext("beans.xml");
	String message = resources.getMessage("message", null, "Default", Locale.ENGLISH);
	System.out.println(message);
}

输出结果:

Alligators rock!

15.2. Spring事件机制

可以使用@EventListener注解在托管bean的任何方法上注册事件侦听器

public class BlockedListNotifier {

	private String notificationAddress;

	public void setNotificationAddress(String notificationAddress) {
		this.notificationAddress = notificationAddress;
	}

	@EventListener
	public void processBlockedListEvent(BlockedListEvent event) {
		// notify appropriate parties via notificationAddress...
	}
}

如果希望能异步处理监听可以使用@Async注解

@Async
@EventListener
public void processBlockedListEvent(BlockedListEvent event) {
	// BlockedListEvent is processed in a separate thread
}

使用异步事件时请注意以下限制:

  • 如果异步事件侦听器抛出Exception,它不会传播到调用者。
  • 异步事件侦听器方法无法通过返回值来发布后续事件。如果您需要发布另一个事件作为处理结果,请注入ApplicationEventPublisher来手动发布该事件。
  • 默认情况下,事件处理不会传播ThreadLocals和日志上下文。

如果需要一个监听器在另一个监听器之前被调用,可以在方法声明中添加@Order

@EventListener
@Order(42)
public void processBlockedListEvent(BlockedListEvent event) {
	// notify appropriate parties via notificationAddress...
}

构造通用事件
可以使用泛型来进一步定义事件的结构。考虑使用EntityCreatedEvent<T>,其中T是被创建的实际实体的类型。

public class EntityCreatedEvent<T> {
    private final T entity;

    public EntityCreatedEvent(T entity) {
        this.entity = entity;
    }

    public T getEntity() {
        return entity;
    }
    // 其他相关方法...
}
@EventListener
public void onPersonCreated(EntityCreatedEvent<Person> event) {
	// ...
}

通过这种方式,onPersonCreated方法只会响应EntityCreatedEvent中封装的实体类型为Person的事件,增强了事件处理的类型安全性及可读性。

Spring默认使用ApplicationEventMulticaster的子类SimpleApplicationEventMulticaster将事件广播给所有注册的监听器,我们可以自定义ApplicationEventMulticaster代替默认的类。

@Bean
ApplicationEventMulticaster applicationEventMulticaster() {
	SimpleApplicationEventMulticaster multicaster = new SimpleApplicationEventMulticaster();
	multicaster.setTaskExecutor(...);
	multicaster.setErrorHandler(...);
	return multicaster;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值