Spring 注解
封面画师:T5-茨舞(微博) 封面ID:67342794
本文以 SpringBoot 3.1.x 为例
1. 对象创建相关注解
1.1 @Component
作用:用于把当前 类对象 存入(注册到) Spring 容器中。
主要属性:
属性名 | 作用 |
---|---|
value | 指定 bean 的 id 。 若未指定,则默认为当前类名首字母小写。 |
如果 Spring 配置文件中使用 <bean>
标签配置的 id、class 与 @Component
注解指定的 id、class 一样时,配置文件的配置将覆盖注解的配置。
@Component
有以下几个衍生注解,它们与 @Component
的作用是一样的,但在实际开发中它们所处的位置不一样:
1、@Controller
:用于表现层
2、@Service
:用于业务层
3、@Repository
:用于持久层
Spring 注解使用注意
常规 SSM 项目中,使用 Spring 注解开发时,记得在 Spring 配置文件中告知 Spring 在创建容器时需要扫描的包。如:
1 | <context:component-scan base-package="xxx.xxx"/> |
1.2 @Scope
作用:用于指定 bean 的作用范围(控制简单对象的创建次数)。未使用时,默认 bean 单例。
主要属性:
属性名 | 作用 |
---|---|
value | 指定范围的取值。 常用的 value 属性值有:singleton 、prototype 。 |
1.3 @Lazy
作用:延时创建单例 bean。未使用时,Spring 工厂创建时就会创建 单例 bean;使用后,在使用 bean 时才创建 bean。
1.4 @PostConstruct
此注解并不是由 Spring 提供的,而是 JSR-250 规范中指定的。
此注解只能在 非静态无返回值 方法上使用,被此注解标记了的方法,作为 Spring 中 bean 的 初始化 方法。
在整个 bean 的初始化过程中,最先执行构造方法,再进行依赖注入,然后再执行被此注解标记的方法。
注意: 此注解在 JDK9 中已被废弃,在 JDK11 中已被移除。要想在高版本 JDK 中使用此注解,需要添加依赖 jakarta.annotation-api
。
1.5 @PreDestroy
此注解并不是由 Spring 提供的,而是 JSR-250 规范中指定的。
此注解只能在 非静态无返回值 方法上使用,被此注解标记了的方法,作为 Spring 中 bean 的 销毁 方法。
注意: 此注解在 JDK9 中已被废弃,在 JDK11 中已被移除。要想在高版本 JDK 中使用此注解,需要添加依赖 jakarta.annotation-api
。
1.6 @Lookup
Lookup,意为“查找、查阅”,在 Spring 中也有一个 @Lookup
注解,该注解只能作用在 方法 上。
当使用 ApplicationContext#getBean()
方法按类型获取 Spring 容器中的 Bean 时,如果传入的类型是一个抽象类,那么会尝试在容器中查找该抽象类的实现类类型的 Bean,如果不存在这样的 Bean,则会抛出 NoSuchBeanDefinitionException
异常。
生成抽象类的代理类类型的 Bean
可以在抽象类中任意定义一个方法并使用 @Lookup
标记:
1 |
|
然后再尝试获取该类型的 Bean 时,将得到由 CGLib 生成的代理对象:
1 |
|
在单例对象中注入多例对象
向单例 Bean 中注入多例 Bean 时,尽管注入的是多例 Bean,但每次获取注入的 Bean 时都将是同一个。此时可以声明一个 无参、返回值类型是想要注入的多例 Bean 对应的类型的方法(这个方法甚至可以是抽象方法):
1 |
|
1 |
|
2. 注入相关注解
2.1 @Autowried
作用:自动按照 类型 注入。只要容器中有唯一的一个 bean 对象类型(子类)和要注解的对象类型匹配时,就可以注入成功。
位置:set 方法、构造方法、成员变量。作用在成员变量上时,Spring 使用反射对其进行注入,并且 set 方法变得不再必须。
注入方式
先按照类型注入:
1、如果有唯一的 bean 对象类型在 IoC 容器中,则注入成功。
2、如果 IoC 容器中一个匹配的 bean 对象类型都没有,则报错。
3、如果 IoC 容器中有多个匹配的 bean 对象类型,则再按照变量名进行注入,在圈出的多个匹配的 bean 对象类型中寻找匹配的变量名:
- 如果存在,则注入成功;反之,则注入失败。有多个匹配的 bean 对象时,常与
@Qualifier
搭配使用。
高端玩法
如果一个泛型为自定义类型的集合对象被 @Autowried
标记,那么 Spring 会自动把相同的类型(包括子类)的自定义对象收集到集合中。比如:
1 |
|
如果 @Autowried
作用在数组或集合上,数组或集合中的 bean 的顺序是根据 Spring 创建的顺序。如果想指定里面排序的优先级,可以使用 @Order
或者 @Priority
指定优先级,值越小,优先级就越高。
如果作用在 map 集合上,其 key 类型只能为 String,表示 bean 的 id。
@Order
注解(或者实现 Ordered
接口、又或者实现 PriorityOrdered
接口),不会影响 Bean 的实例化顺序和执行顺序,更不会影响 Spring 的 Bean 的扫描顺序,它影响着 Spring 将扫描的多个 Bean 放入数组、集合(Map)时的排序。
2.2 @Qualifier
作用:在按照类注入的基础上再按照名称注入。这个注解在给成员变量注入时,不能单独使用(与 @Autowried
一起搭配使用),但是在给方法参数注入时可以单独使用。
主要属性:
属性名 | 作用 |
---|---|
value | 用于指定注入 bean 的 id。 |
注意: 当我们使用 @Bean
给一个配置类中的方法进行注解时,会判断这个方法是否有参数,Spring 框架会去容器中查找有无可用的 bean 对象,查找方式和 @Autowried
注解一样。如果没找到,就会报错。如果我们在 IoC 容器中添加了两个相同的对象,恰逢这个对象又出现在使用了 @Bean
注解的方法的参数中,这个时候可以使用 @Qualifier
指定具体是哪个对象(给方法参数注入,单独使用)。
2.3 @Primary
作用:当一个接口有多个实现类时,指定 某一个 实现类作为依赖注入的默认优先选择。
注意: 默认优先选择的实现类 只能有 一个。
2.4 @Resource
作用:默认按照 bean 的 id 进行注入,可以单独使用。
主要属性:
属性名 | 作用 |
---|---|
name | 指定 bean 的 id。 |
type | 指定 bean 的类型。 |
属性值使用规则
1、如果同时指定了 name 和 type ,则从 Spring 上下文中找到唯一匹配的 bean 进行装配,找不到则抛出异常(异常信息:找不到 “bean” 的声明);
2、如果只指定了 name ,则从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常;
3、如果只指定了 type ,则从上下文中找到类型匹配的唯一 bean 进行装配,找不到或者找到多个,都会抛出异常;
4、如果既没有指定 name ,又没有指定 type ,则自动按照 byName 方式进行装配(见2)。如果没有匹配,则回退为一个原始类型进行匹配,如果匹配则自动装配,否则抛出异常。
使用建议
推荐使用 @Resource
注解,这个注解是属于 JDK 的,可以减少与 Spring 的耦合(但人们依旧习惯使用 @Autowried
)。
@Autowried
、 @Qualifier
和 @Resource
都只能注入其他 bean 类型的数据,而基本数据类型和 String 类型是无法使用这三个注解的。另外,集合类型的注入只能通过 XML 来实现。
2.5 @Value
作用:常用于注入基本类型和 String 类型的数据,可以作用在字段、方法、参数和注解上。
主要属性:
属性名 | 作用 |
---|---|
value | 用于指定数据的值(可以使用 Spring 中的 EL 表达式,即 SPEL )。 |
SPEL 的写法:${表达式}
,为了避免无法匹配到值,可以设置默认值,比如 @Value("${name:张三}")
。
@Value 与 EL 表达式
如果要 @Value
注解与 EL 表达式结合使用,需要先编写一个 xxx.properties 文件:
1 | name = "mofan" |
然后在 Spring 配置文件中指定这个 properties 文件的位置(location
属性值支持使用 classpath
):
1 | <context:property-placeholder location="classpath:/xxx.properties"/> |
<context:property-placeholder>
标签的命名空间为:
1 | <beans xmlns:context="http://www.springframework.org/schema/context"></beans> |
然后在成员变量上使用 @Value
注解即可:
1 |
|
使用细节
-
@Value
注解不能应用在 静态变量 上,否则会注入失败,但也有变通之道,可以将@Value
标记在静态变量的Setter
上或构造方法的参数上,在方法内部设置静态变量的值,比如:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class MyProperties {
private static String str1;
private static String str2;
public void setStr1(String str1) {
str1 = str1;
}
MyProperties( String str2){
str2 = str2;
}
} -
@Value
注解不能应用在被final
修饰的变量上,final
变量 必须 在构造方法中进行初始化,并且被赋值后不能再次更改,而@Value
注解是在 Bean 实例化后才进行注入的,无法在构造方法中初始化final
变量; -
@Value
注解不能注入集合类型的数据; -
@Value
注解只能作用在被 Spring 管理的类中,也就是说该注解必须在 Spring Bean 的生命周期中才能生效。
3. 注解扫描详解
在扫描注解时,需要在 Spring 配置文件中指定扫描路径,即:
1 | <context:component-scan base-package="xxx.xxx"/> |
但这种方式并不能满足绝大多数场景,比如排除无需扫描的类。这时可以增加一个子标签 <context:exclude-filter>
。
<context:exclude-filter>
标签中 type
属性表示排除策略,expression
属性用于指定排除策略对应的排除方式。比如下述配置就是排除对 User
类的扫描:
1 | <context:component-scan base-package="indi.mofan"> |
type
属性值有以下五种,其名称与含义如下:
名称 | 含义 |
---|---|
assignable | 排除特定的类型 |
annotation | 排除特定的注解 |
aspectj | 使用包切入点或类切入点表达式进行排除 |
regex | 使用正则表达式进行排除 |
custom | 自定义排除策略 |
排除策略是可以叠加使用的。
<context:component-scan>
标签中还有名为 <context:include-filter>
的子标签,这个标签用于设置注解扫描包含方式,其使用方式与 <context:exclude-filter>
标签是一样的。
使用 <context:include-filter>
标签时需要将 <context:component-scan>
标签的 use-default-filters
属性值设置为 false
,表示不使用 Spring 默认扫描规则。
4. 配置相关注解
小知识
在 Spring 中,解析 Spring 的配置文件(以 applicationContext.xml 为例),使用:
1 | ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml"); |
解析配置类使用(以MyConfiguration.java为例),使用:
1 | ApplicationContext ac1 = new AnnotationConfigApplicationContext(MyConfiguration.class); |
分析一下 AnnotationConfigApplicationContext()
方法,我们可以点击进入查看源码:
1 | public AnnotationConfigApplicationContext(Class<?>... annotatedClasses) { |
这个构造方法的参数是可变长度的参数,可以传入配置类(被 @Configuration
标记的类)的 Class 对象,也可以传入配置类所在路径。
4.1 @Configuration
作用:指定当前类是配置类。其本质也是 @Component
注解的衍生注解。
它相当于 xml 文件的 <beans>
标签,这个类里面需要注册 bean ,相当于在 <beans>
中注册 bean 。
细节: 当配置类作为 AnnotationConfigApplicationContext
对象创建的参数时,可以 不用 写 @Configuration
注解在配置类上。
bean 的注册方法
bean 有两种方式:
1、使用 @Bean
标记一个返回 JavaBean 的方法,返回的 JavaBean 作为 bean 注册到 beans 中。
2、使用@Component
+ @ComponentScan
的方式:@Component
标记在要注册 bean 的类上,@ComponentScan
标记在配置类上用于扫描组件。
最后,将配置类注册到上下文中来初始化容器。
父配置类加载子配置类的问题
如果有多个配置类,且这些配置类存在父子关系、 AnnotationConfigApplicationContext
对象创建的参数中只有父配置类,要想在父配置类中加载子配置类,需要在父配置类上使用 @ComponentScan
扫描子配置类,而且这个子配置类必须是配置类,即:子配置类上必须有 @Configuration
。
当然你也可以在父配置类上使用 @Import
注解导入子配置类,这个时候子配置类可以不用有 @Configuration
。
4.2 @ComponentScan
作用:通过注解指定 Spring 在创建容器时需要扫描的包。
主要属性:
属性名 | 作用 |
---|---|
value、basePackage | 这俩的作用是一样的,都是用于指定创建容器时要扫描的包。 |
basePackageClasses | 定义扫描的类。 |
在没有定义@ComponentScan
属性的情况下,它只会扫描当前包和其子包的路径。
细节: 在父配置类上使用该注解扫描需要被装配的类时,这个类必须被 @Configuration
或 @Component
所注解。
使用方式
1 |
|
以上代码相当于:
1 | <beans> |
扫描排除
与 <context:component-scan>
一样,@ComponentScan
注解也可以配置扫描排除策略。如果在 Spring 配置文件中有这样一段配置:
1 | <context:component-scan base-package="indi.mofan"> |
那么等价于:
1 |
其他排除策略使用方式与 <context:exclude-filter>
是一样的,这些排除策略也是可以叠加的。
@ComponentScan
也支持扫描包含,举一反三即可。
4.3 @Bean
作用:把当前 public
方法的返回值作为 bean 对象存入到 Spring 的 IoC 容器中。常与 @Configuration
配合使用。
主要属性:
属性名 | 作用 |
---|---|
name | 用于指定 bean 的 id 。不写时,默认值为当前方法名称。 |
用 @Bean
注解了的方法的名字,就相当于 <bean>
标签中的 id 属性;这个方法的返回值,就相当于 <bean>
标签中的 class 属性。
@Bean
注解默认作用域为单例 singleton 作用域,可通过 @Scope("prototype")
设置为原型作用域。
细节分析
当我们使用注解配置方法时,如果方法有参数,Spring 框架会去容器中查找有无可用的 bean 对象,查找方式和 @Autowried
注解一样。如果找到了就会进行注入,否则会报错。
在被 @Configuration
标记的配置类中,一个 @Bean
标记的方法可以调用当前类中另一个被 @Bean
注解标记的方法,并将后者作为依赖注入到前者创建的 bean 中。虽然 @Configuration
是 @Component
的衍生注解,但是使用 @Component
是无法完成上述场景的。
4.4 @Import
作用:用于导入其他的配置类或 bean。
主要属性:
属性名 | 作用 |
---|---|
value | 用于指定其他配置类的 Class 对象(可以指定多个) |
当我们使用 @Import
注解后,有 @Import
注解的类就是父配置类,而导入的都是子配置类。
细节: 使用 @Import
注解导入的子配置类上可以不用写 @Configuration
注解。
配置优先级
Spring 配置优先级如下:
Spring 配置文件 <bean>
标签的优先级 大于 @Bean
注解 大于 @Component
及其衍生注解
优先级高的配置覆盖优先级低的配置,但在进行配置覆盖值,bean 的 id 值需要保持一致。
4.5 @ConfigurationProperties
SpringBoot 提供了多种配置文件方便用户对配置的管理,如果需要读取配置文件中的信息,可以使用 @Value
注解。
在 SpringBoot 的配置文件中,针对同一种配置往往有相同的前缀,为简化 @Value
注解的使用,SpringBoot 提供了 @ConfigurationProperties
注解。
使用这个注解可以指定相同的前缀,SpringBoot 将根据指定的前缀和属性名称在配置文件中寻找对应的信息并完成注入。
假设有这样的配置信息:
1 | properties: |
使用
@Value
读取
1 | /** |
测试一下:
1 |
|
使用 @Value
注解进行读取时,无需 Setter 方法。
使用
@ConfigurationProperties
读取
1 |
|
使用 @ConfigurationProperties
注解进行读取时,必须要有 Setter 方法, 否则会报错。
Spring Boot Configuration Annotation Processor not configured
在 IDEA 中使用@ConfigurationProperties
注解时,顶部可能会出现以下警告:
Spring Boot Configuration Annotation Processor not configured
警告的意思是:没有配置 SpringBoot 配置注解执行器。该警告不影响代码的正常执行,如果需要消除警告,添加以下依赖即可:
1 | <dependency> |
添加 SpringBoot 配置注解执行器后,IDEA 能够给出更准确的提示信息。
@ConfigurationProperties
与@Value
的混合使用
有时一个 Bean 可能会牵扯到很多信息,单用@ConfigurationProperties
无法完成所有配置的注入,可以使用@Value
注入剩下的配置信息:
1 | properties: |
1 |
|
@ConfigurationProperties
的其他配置
ignoreUnknownFields
属性值默认为 true
,如果修改为 false
,SpringBoot 在启动时会查找所有配置文件中的内容进行匹配,当存在没有匹配的信息时就会报错。
比如:
1 | properties: |
1 |
|
配置文件中的 height
未在 MyProperties
中绑定,启动时出现以下错误:
org.springframework.boot.context.properties.bind.UnboundConfigurationPropertiesException: The elements [properties.height] were left unbound.
ignoreInvalidFields
默认为 false
,如果修改为 true
,SpringBoot 启动时会忽略转换错误的值,但这可能会对后续维护带来不必要的麻烦,因此需要慎用。比如:
1 | properties: |
1 |
|
期望 bool
的类型是 Boolean
,但是配置文件中的类型是 String
,如果配置了 ignoreInvalidFields = true
,那么会忽略这种转换错误的值,否则 启动时出现以下错误:
org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] to type [java.lang.Boolean] for value 'invalid'; nested exception is java.lang.IllegalArgumentException: Invalid boolean value 'invalid'
java.lang.IllegalArgumentException: Invalid boolean value 'invalid'
校验注入的值
类加上 @Validated
注解,字段上加上 @NotEmpty
等注解,在注入值的时候会自动校验,校验不通过将报错。比如:
1 |
|
@Validated
是 Spring 中自带的,使用 @NotEmpty
等校验注解则需要导入以下依赖:
1 | <dependency> |
加载多层嵌套配置
某些复杂的配置往往不是简单的一层,可能会嵌套多层,@ConfigurationProperties
注解也支持加载多层拥有相同前缀的配置。
修改原有的配置信息:
1 | properties: |
properties
下的 name
变成了 my-name
,同时增加一层名为 innerProperties
的配置,此时需要对 MyProperties
配置类进行修改:
1 |
|
1 |
|
重新测试下:
1 |
|
Java 中类里的字段通常采用小驼峰命名,对使用 @ConfigurationProperties
注解加载的配置信息,配置类中采用小驼峰命名的字段可以对应配置文件中相同的配置项,也可以对应配置文件中中划线命名的配置项。
4.6 @EnableConfigurationProperties
如果一个类仅仅配置 @ConfigurationProperties
注解,也没有被添加到 Spring 容器中,那么是不能完成配置文件与 Bean 的数据绑定。
假设 A 类上使用了 @ConfigurationProperties
注解,且未被添加到 Spring 容器中。此时可以使用 @EnableConfigurationProperties({A.class})
指定 A 与配置文件进行绑定,同时将 A 添加到 Spring 容器中。
1 | properties: |
1 | /** |
在配置类上使用 @EnableConfigurationProperties
指定 MyProperties.class
,使其与配置文件绑定并交由 Spring 管理:
1 | /** |
注意: 使用 @EnableConfigurationProperties
注解时,所在类必须被添加到 Spring 容器中,否则无效。
4.7 @PropertySource
作用:与 Spring 配置文件的 <context:property-placeholder>
标签作用一样。用于指定 properties
文件的位置。
使用示例:
1 |
|
如果需要定义多个配置文件,可以使用 @PropertySources
注解,在其内部声明多个 @PropertySource
注解即可。
如果想要定义配置文件的编码,可以使用 @PropertySource
提供的 encoding
属性。
绑定 yaml 配置文件
@PropertySource
只能用于指定 properties
文件,如果想要解析 yaml
文件,需要自定义配置工厂,然后使用 factory
属性指定自定义的配置工厂,比如:
1 | public class YamlPropertySourceFactory implements PropertySourceFactory { |
使用时:
1 |
4.8 @EnbleAspectJAutoProxy
使用该注解后,表示开启 Spring AOP 动态代理的自动配置。
@EnbleAspectJAutoProxy
内有两个属性:
proxyTargetClass
:使用 CGLib 动态代理还是使用 JDK 动态代理。设置为true
时表示使用 CGLib 动态代理,设置为false
时表示 尽可能 使用 JDK 动态代理。如果被代理的类未实现接口,就算是false
也会使用 CGLib 实现动态代理。exposeProxy
:是否暴露代理对象,设置为true
时,可以使用AopContext.currentProxy()
获取到代理对象。
使用示例
导入依赖:
1 | <dependency> |
SpringBoot 的启动类上添加 @EnableAspectJAutoProxy(exposeProxy = true)
注解,表示通过 AOP 框架暴露代理对象,可以使用 AopContext.currentProxy()
获取到代理对象:
1 |
|
使用:
1 |
|
4.9 @ConstructorBinding
在使用 @ConfigurationProperties
注解时,需要提供 Setter 方法,但如果要求对象不能被修改,就不会再提供 Setter 方法,而是只提供构造方法,此时应该怎么实现配置文件信息绑定呢?
可以使用 @ConstructorBinding
注解,该注解可以作用在构造方法、注解上:
1 |
|
该注解也需要与 @ConfigurationProperties
搭配使用,通过构造方法将配置文件中的信息绑定到属性上。
使用示例
配置文件信息:
1 | another-properties: |
使用 @ConstructorBinding
将配置文件中的信息绑定到属性上:
1 | /** |
简单测试下:
1 |
|
注意: 使用 @ConstructorBinding
注解时,不能与 @Configuration
、@Component
注解及其衍生注解搭配使用。Spring 使用构造方法来创建 Bean 时,要求 Spring 容器中存在构造方法的参数类型的 Bean,而常见的 String 类型显然是不存在的,应用启动时就会抛出 NoSuchBeanDefinitionException
异常。在 IDEA 中出现搭配使用的场景时,会提示以下信息:
Annotated with @ConstructorBinding but defined as Spring component
4.10 @DurationUnit
Spring 支持直接从配置文件中读取 Duration
类型的配置信息。
在配置文件中未显式指定单位时,默认单位为毫秒,支持配置带单位(比如:ns
、us
、ms
、s
、m
、h
、d
等)的文本,也可以使用 @DurationUnit
注解来显式指定单位,因此 @DurationUnit
常与 @ConfigurationProperties
搭配使用。
使用示例
配置文件信息:
1 | properties: |
配置信息绑定类:
1 |
|
简单测试下:
1 |
|
相似注解
除 @DurationUnit
外,还有两个类似的注解:
@PeriodUnit
:用于指定Period
类型配置信息的单位,未显式指定单位时,默认单位为天@DataSizeUnit
:用于指定DataSize
类型配置信息的单位,DataSize
是 Spring 提供的用于描述文件大小的类,未显式指定单位时,默认单位是bytes
4.11 @ConfigurationPropertiesBinding
Spring 内置了一些将配置文件中的信息转换成常见 Java 对象的转换器,如果要转换成自定义对象,则需要自定义转换器。
比如有配置信息如下:
1 | properties: |
且存在自定义类 Weight
:
1 |
|
现在需要读取配置文件中的 weight
信息,将其绑定到 Weight
对象上:
1 |
|
自定义转换器 WeightConvertor
:
1 | public class WeightConvertor implements Converter<String, Weight> { |
使用 @Bean
注解将自定义转换器添加到 Spring 容器中,并使用 @ConfigurationPropertiesBinding
指定该 Bean 用于读取配置文件信息的数据转换:
1 |
|
简单测试下:
1 |
|