目录
- IoC容器
- 1. Bean概述
- 1.1. Bean命名
- 1.2. 实例化Bean
- 2. 依赖关系
- 2.1. 依赖注入
- 2.2. 自动装配
- 3. Bean的范围
- 4. 定制Bean的特性
- 4.1. 生命周期回调
- 4.2. Lifecycle、SmartLifecycle 以及 LifecycleProcessor
- 4.3. Aware接口
- 5. 容器拓展
- 6. 容器启动过程
- 7. 基于注解的容器配置
- 7.1. @Autowired
- 7.2. @Primary
- 7.3. @Qualifier
- 7.4. 使用泛型作为自动装配限定符
- 7.5. @Resource
- 7.6. @Value
- 7.7. @PostConstruct和@PreDestroy
- 8. 类路径扫描和管理级组件
- 8.1. 使用元注解和组合注解
- 8.2. @ComponentScan
- 8.3. 在组件中定义Bean元数据
- 8.4. 命名自动检测到的组件
- 8.5. @Scope
- 9. 使用JSR 330标准注解
- 9.1. @Inject和@Named
- 9.2. @Named和@ManagedBean
- 9.3. 对比Spring注解和JSR-330标准注解
- 10. 基于Java的容器配置
- 10.1. @Bean和@Configuration
- 10.2. 使用AnnotationConfigApplicationContext实例化Spring容器
- 10.3. 使用AnnotationConfigWebApplicationContext支持Web应用程序
- 11. @Bean
注:spring版本为v6.1.5
官方文档链接
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:
Property | Explained |
---|---|
Class | 实例化的Bean |
Name | Bean的名称 |
Scope | Bean的范围 |
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
- 使用构造函数实例化
- 使用静态工厂方法实例化
- 使用实例工厂方法实例化:从容器中调用现有 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的范围
Scope | Description |
---|---|
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的InitializingBean
和DisposableBean
接口。容器要求 afterPropertiesSet()和destroy()让 Bean 在初始化和销毁 Bean 时执行某些操作。
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;public class ExampleBean implements InitializingBean, DisposableBean {@Overridepublic void afterPropertiesSet() throws Exception {System.out.println("do something after init...");}@Overridepublic void destroy() throws Exception {System.out.println("do something before destroy...");}
}
现在通常可以使用@PostConstruct
和@PreDestroy
代替。
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;public class ExampleBean{@PostConstructpublic void init(){System.out.println("ExampleBean init...");}@PreDestroypublic 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可以由
@Autowiredprivate 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容器的启动过程涉及多个关键步骤,确保应用程序能够正确运行。以下是该过程的关键步骤:
- 加载配置文件:Spring容器会读取XML配置文件、注解配置或Java配置等,将配置信息加载到内存中。
- 创建BeanDefinition:解析配置文件,将每个Bean的信息封装成BeanDefinition对象,包括类名、作用域、依赖关系等信息。
- 实例化Bean:根据BeanDefinition中的信息,通过反射机制实例化Bean,并将其放入Bean容器中。
- 设置Bean属性(依赖注入):自动装配Bean的属性,将Bean所依赖的其他Bean注入到当前Bean中。
- 调用Bean的初始化方法:如果Bean实现了InitializingBean接口或在配置文件中定义了init-method方法,Spring容器会在实例化Bean后调用其初始化方法。
- 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对象的初始化过程中进行一些自定义的处理。这个接口包含两个方法:postProcessBeforeInitialization
和postProcessAfterInitialization
,分别在Bean的init方法前后被调用。通过实现这个接口,可以对Bean进行一些额外的处理,如代理包装或者属性值的修改等。
7. 基于注解的容器配置
基于注解的配置提供了XML设置的另一种选择,它依赖于字节码元数据而不是XML声明来连接组件。开发人员不使用XML来描述bean连接,而是通过在相关的类、方法或字段声明上使用注解,将配置移动到组件类本身。
7.1. @Autowired
该注解可以实现自动装配的功能
将@Autowired注释应用于构造函数
public class MovieRecommender {private final CustomerPreferenceDao customerPreferenceDao;@Autowiredpublic MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {this.customerPreferenceDao = customerPreferenceDao;}// ...
}
将@Autowired注释应用于传统的setter 方法
public class SimpleMovieLister {private MovieFinder movieFinder;@Autowiredpublic void setMovieFinder(MovieFinder movieFinder) {this.movieFinder = movieFinder;}// ...
}
@Autowired也可以应用于字段,甚至可以将其与构造函数混合使用
public class MovieRecommender {private final CustomerPreferenceDao customerPreferenceDao;@Autowiredprivate MovieCatalog movieCatalog;@Autowiredpublic MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {this.customerPreferenceDao = customerPreferenceDao;}// ...
}
@Autowired也可以应用于数组或集合
数组
public class MovieRecommender {@Autowiredprivate MovieCatalog[] movieCatalogs;// ...
}
Set
public class MovieRecommender {private Set<MovieCatalog> movieCatalogs;@Autowiredpublic void setMovieCatalogs(Set<MovieCatalog> movieCatalogs) {this.movieCatalogs = movieCatalogs;}// ...
}
Map
public class MovieRecommender {private Map<String, MovieCatalog> movieCatalogs;@Autowiredpublic 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 {@Autowiredpublic void setMovieFinder(@Nullable MovieFinder movieFinder) {...}
}
// 3
public class SimpleMovieLister {@Autowiredpublic 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@Primarypublic MovieCatalog firstMovieCatalog() { ... }@Beanpublic MovieCatalog secondMovieCatalog() { ... }// ...
}
public class MovieRecommender {@Autowiredprivate 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 {@Beanpublic MovieCatalog firstMovieCatalog() { ... }@Beanpublic 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 {@Beanpublic StringStore stringStore() {return new StringStore();}@Beanpublic 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注解。它会尝试按以下顺序进行查找:
- 先按类型查找
- 按名称查找:如果找不到对于普通Bean此时会抛异常
- 对于BeanFactory、 ApplicationContext、ResourceLoader、ApplicationEventPublisher和MessageSource 这些特殊的bean,Spring保证一定能正确找到默认的实现类。
public class Example{// 1@Resourceprivate Animal dog;// 2@Resource(name="cat") private Animal catttt;// 3@Resourceprivate Animal dogggg;// 4@Resourceprivate 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})作为值注入。如果要对不存在的值保持严格控制,则应该声明一个
PropertySourcesPlaceholderConfigurer
Bean,如以下示例所示:
@Configuration
public class AppConfig {@Beanpublic 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 {@Beanpublic 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 {@PostConstructpublic void populateMovieCache() {// populates the movie cache upon initialization...}@PreDestroypublic 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_type | org.example.SomeClass | 指定的类 |
aspectj | org.example…*Service+ | 目标组件要匹配的AspectJ类型表达式 |
regex | org.example.Default.* | 与目标组件的类名匹配的正则表达式 |
custom | org.example.MyTypeFilter | org.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 {@Beanpublic Service service() {return new DefaultService();}@Beanpublic Client client(Service service) { // 这里通过方法参数注入了service Beanreturn 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 {@Beanpublic 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;@Injectpublic 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;@Injectpublic void setMovieFinder(MovieFinder movieFinder) {this.movieFinder = movieFinder;}// ...
}
当使用@Named或@ManagedBean时,也可以以使用@ComponentScan去扫描组件。
与@Component相反,@Named和@ManagedBean注解都是不可组合的,不能用他们来构造自定义的注解。
9.3. 对比Spring注解和JSR-330标准注解
spring | jakarta.inject.* | 对比 |
---|---|---|
@Autowired | @Inject | @Inject没有’required’属性。 |
@Component | @Named / @ManagedBean | JSR-330不可组合注解 |
@Scope(“singleton”) | @Singleton | JSR-330的默认作用域类似于Spring的原型。但是,为了使其与Spring的一般默认值保持一致,在Spring容器中声明的JSR-330 bean默认情况下是单例的。为了使用单例以外的作用域,你应该使用Spring的@Scope注释。jakarta.inject还提供了一个jakarta.inject.Scope注释:但是,这个注释仅用于创建自定义注释。 |
@Qualifier | @Qualifier / @Named | jakarta.inject.Qualifier只是一个用于构建自定义限定符的元注释。具体的字符串限定符(比如Spring带值的@Qualifier)可以通过jakarta.inject.Named来关联。 |
@Value | - | - |
@Lazy | - | - |
ObjectFactory | Provider | provider是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 {@Beanpublic 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实例化方式
- 在实例化一个AnnotationConfigApplicationContext时,可以使用@Configuration类作为输入。
public static void main(String[] args) {ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);MyService myService = ctx.getBean(MyService.class);myService.doStuff();
}
- 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();
}
- 使用一个无参数的构造函数实例化一个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();
}
- 使用一个无参数的构造函数,并使用组件扫描的方式。(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应用程序
AnnotationConfigWebApplicationContext
是AnnotationConfigApplicationContext
的扩展,专为Web应用设计,适用于Servlet环境。它除了包含AnnotationConfigApplicationContext
的所有功能外,还增加了对Web应用特有的特性的支持,比如处理Servlet监听器、过滤器、Spring MVC的DispatcherServlet以及其他与Web容器交互的功能。
11. @Bean
@Bean
是一个方法级注释,是XML 元素<bean/>
的直接类比。
声明一个 Bean:
@Configuration
public class AppConfig {@Beanpublic TransferServiceImpl transferService() {return new TransferServiceImpl();}
}
前面的配置与以下 Spring XML 完全相同:
<beans><bean id="transferService" class="com.acme.TransferServiceImpl"/>
</beans>
@Bean还可以声明接口(或基类)的类型来作为方法的返回
@Configuration
public class AppConfig {@Beanpublic TransferService transferService() {return new TransferServiceImpl();}
}
推荐返回更具体的类型