# SpringBoot3教程 - 6 注册组件
在 Spring IOC 容器中注册组件可以使用 xml 和 注解,在 SpringBoot 中全部使用注解来注册。
如果是我们自己编写的类,那么可以使用如下注解注入到 Spring 容器中:
@Component
: 用于标记通用组件类。
@Controller
: 用于标记控制器类。
@Service
: 用于标记服务层类。
@Repository
: 用于标记数据访问层类。
但是如果是别人的类,我们是没有办法修改代码添加注解的,在 SpringBoot中可以使用 Java 配置类的方式,通过@Configuration
和@Bean
注解来定义Bean。
# 6.1 @Bean注解
在 Spring 中可以通过在 xml 配置文件中注册第三方的组件,SpringBoot 中没有 xml 配置文件了,所以都是通过类来完成的。
首先创建一个配置类,添加 @Configuration
注解,然后在其中编写方法,方法使用@Bean
注解,方法返回的对象会被添加到 Spring IOC 容器中。
举个例子:
package com.doubibiji.hellospringboot.config;
import com.doubibiji.hellospringboot.bean.UserInfo;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MyConfig {
@Bean // 将对象添加到Spring容器中
public UserInfo userInfo() {
UserInfo userInfo = new UserInfo(); // UserInfo是随意创建用来测试的类
userInfo.setUsername("逗比笔记");
userInfo.setAge(3);
return userInfo;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
上面的配置类就相当于以前 Spring 的xml 配置文件,@Bean
注解相当于 <bean/>
标签创建的对象,对象在 Spring IOC容器中的名称就是上面方法的名称,要改名字直接使用@Bean("名称")
即可。
一般情况下,使用@Component
代替 @Configuration
也能正常工作,但是推荐配置类使用 @Configuration
注解。
编写一个单元测试方法,查看能否获取容器中的对象:
package com.doubibiji.hellospringboot;
import com.doubibiji.hellospringboot.bean.UserInfo;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class HelloSpringbootApplicationTests {
@Autowired
private UserInfo userInfo;
@Test
void contextLoads() {
System.out.println(userInfo);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
执行结果,可以获取到值:
UserInfo(username=逗比笔记, age=3)
如果在使用 @Bean
创建对象的时候,依赖于另一个 Spring 容器中的对象,只需要将另一个对象作为方法的参数即可。
举个栗子:
下面的 Dog 和 UserInfo 都是我随意创建的两个类。
UserInfo 类对象需要 Dog 类对象,只需要在方法上添加 Dog 类的参数,Spring 会自动注入。
package com.doubibiji.hellospringboot.config;
import com.doubibiji.hellospringboot.bean.Dog;
import com.doubibiji.hellospringboot.bean.UserInfo;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MyConfig {
@Bean
public Dog dog() {
Dog dog = new Dog();
dog.setName("大黄");
dog.setAge(2);
return dog;
}
@Bean // 将对象添加到Spring容器中
public UserInfo userInfo(Dog dog) {
UserInfo userInfo = new UserInfo();
userInfo.setUsername("逗比笔记");
userInfo.setAge(3);
userInfo.setDog(dog);
return userInfo;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 6.2 @Import注解
除了使用 @Bean
注解注册组件到 Spring IOC 容器中,还可以使用 @Import 注解。
举个栗子:
package com.doubibiji.hellospringboot.config;
import com.doubibiji.hellospringboot.bean.UserInfo;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@Configuration
@Import(UserInfo.class)
public class MyConfig {
}
2
3
4
5
6
7
8
9
10
11
上面通过 @Import
注解,将类作为 bean 注册到 Spring IOC 容器中。通过 @Import
注解的方式注册,默认情况下,对象在 Spring IOC 容器中的名字是类的全类名。
也可以一次注入多个:
package com.doubibiji.hellospringboot.config;
import com.doubibiji.hellospringboot.bean.Dog;
import com.doubibiji.hellospringboot.bean.UserInfo;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@Configuration
@Import({UserInfo.class, Dog.class})
public class MyConfig {
}
2
3
4
5
6
7
8
9
10
11
12
但是如果要注册的类很多,这样写就很不优雅,而且麻烦。
那么可以使用下面 ImportSelector
接口的实现方式。
# 6.3 ImportSelector接口
我们可以创建一个类,实现 ImportSelector
接口,并通过实现 selectImports
方法动态决定要导入的类。
举个栗子:
package com.doubibiji.hellospringboot.config;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[] {
"com.doubibiji.hellospringboot.bean.UserInfo",
"com.doubibiji.hellospringboot.bean.Dog"
};
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
selectImports
方法返回的是类的全类名的数组。
然后使用 @Import
注解将 MyImportSelector
注册到 Spring IOC 容器中。这样会将selectImports
方法返回的类注册到 Spring IOC 容器中。
package com.doubibiji.hellospringboot.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@Configuration
@Import(MyImportSelector.class)
public class MyConfig {
}
2
3
4
5
6
7
8
9
10
在SpringBoot中很多组件,都是使用了上面这种方式,但是是将类名写在文件中,在代码中读取文件中的类名进行导入。
举个栗子,将要导入的类写到一个文件中,在 resources
目录下创建一个my.imports
文件:
com.doubibiji.hellospringboot.bean.UserInfo
com.doubibiji.hellospringboot.bean.Dog
2
然后在 MyImportSelector
中按行读取文件中的数据,然后返回:
package com.doubibiji.hellospringboot.config;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
List<String> imports = new ArrayList<>();
//读取配置文件的内容
InputStream is = MyImportSelector.class.getClassLoader().getResourceAsStream("my.imports");
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String line;
try {
// 按行读取
while ((line = br.readLine()) != null){
imports.add(line);
}
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (null != br) {
try {
br.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
return imports.toArray(new String[0]);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
SpringBoot 中的组件都是使用类似的方式实现自动配置的。我们自己开发的项目,在项目中一般使用 @Bean
注解的方式。
# 6.4 注册条件
在前面使用 @Bean 注解,将类的对象注册到 Spring IOC容器中。
package com.doubibiji.hellospringboot.config;
import com.doubibiji.hellospringboot.bean.UserInfo;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MyConfig {
@Bean
public UserInfo userInfo() {
UserInfo userInfo = new UserInfo();
userInfo.setUsername("逗比笔记");
userInfo.setAge(3);
return userInfo;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
但是上面的属性值是写死在代码中的,这种方式耦合性高,不易于维护,我们可以将属性值定义在配置文件中。
# 1 配置文件注入
例如在 application.yaml中定义属性如下:
userInfo:
username: 逗比笔记
age: 3
2
3
然后在配置类中,将配置注入到 @Bean
所在方法的参数:
package com.doubibiji.hellospringboot.config;
import com.doubibiji.hellospringboot.bean.UserInfo;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MyConfig {
@Bean
public UserInfo userInfo(@Value("${userInfo.username}") String username,
@Value("${userInfo.age}") int age) {
UserInfo userInfo = new UserInfo();
userInfo.setUsername(username);
userInfo.setAge(age);
return userInfo;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
可以看到 @Value
除了用在属性上,还可以用到方法的参数上。
用单元测试类测试一下:
package com.doubibiji.hellospringboot;
import com.doubibiji.hellospringboot.bean.UserInfo;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class HelloSpringbootApplicationTests {
@Autowired // 使用容器中的userInfo注入
private UserInfo userInfo;
@Test
void contextLoads() {
System.out.println(userInfo);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
执行结果:
UserInfo(username=逗比笔记, age=3)
# 2 按条件注册
但是现在有一个问题,当配置文件中没有定义 userInfo.username
和 userInfo.age
的时候,代码运行会报错,提示找不到对应的配置。
那么问题就来了,能否根据配置文件中是否有指定的配置来注册呢,如果有指定的配置,就注册Bean,如果没有,就不注册。
当然是可以的,SpringBoot 提供 @Conditional
注解,但是这个注解使用起来比较麻烦,所以还提供了很多的衍生注解,最常用的介绍三个:
@ConditionalOnProperty
: 当配置文件中存在对应的属性,才注册该Bean;通过这个注解,可以让配置了指定的配置才注册组件。@ConditionalOnMissingBean
:当Spring容器中不存在指定类型的Bean时,才注册该Bean;通过这个注解,通常可以提供默认的实现,如果用户有自己的实现了,那么默认的实现就不注册了。@ConditionalOnClass
:当存在指定的类时(不是存在Spring容器中),才注册该Bean;如果一个组件依赖于另一个组件,那么只有另一个组件存在的时候,才注册这个组件。@ConditionalOnBean
:当容器中存在指定的 Bean 时,才注册该Bean。
下面介绍一下使用。
@ConditionalOnProperty
注解演示:
package com.doubibiji.hellospringboot.config;
import com.doubibiji.hellospringboot.bean.UserInfo;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MyConfig {
/**
* 当存在userInfo.username和userInfo.age的时候才注册
*/
@ConditionalOnProperty(prefix = "userInfo", name = {"username", "age"})
@Bean
public UserInfo userInfo(@Value("${userInfo.username}") String username,
@Value("${userInfo.age}") int age) {
UserInfo userInfo = new UserInfo();
userInfo.setUsername(username);
userInfo.setAge(age);
return userInfo;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
上面的代码,当存在userInfo.username和userInfo.age配置的时候才注册UserInfo的Bean。
@ConditionalOnMissingBean
注解演示:
package com.doubibiji.hellospringboot.config;
import com.doubibiji.hellospringboot.bean.Dog;
import com.doubibiji.hellospringboot.bean.UserInfo;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MyConfig {
@Bean
public Dog dog() {
Dog dog = new Dog();
return dog;
}
/**
* 如果容器中存在Dog类对象,就不注册,如果不存在,才注册
* @return
*/
@Bean
@ConditionalOnMissingBean(Dog.class)
public UserInfo userInfo() {
UserInfo userInfo = new UserInfo();
return userInfo;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
上面的代码,使用 @ConditionalOnMissingBean(Dog.class)
表示当不存在 Dog
类的 bean 时才注册UserInfo
,所以上面是不会注册UserInfo 的 Bean的。**需要注意,上面注册 dog()
方法必须放在 userInfo()
方法前面,如果放在后面,注解检查到没有注册 Dog 的bean,则会注册UserInfo。 **
@ConditionalOnClass
注解演示:
package com.doubibiji.hellospringboot.config;
import com.doubibiji.hellospringboot.bean.UserInfo;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MyConfig {
/**
* 如果容器中存在Dog类对象,就不注册,如果不存在,才注册
* @return
*/
@Bean
@ConditionalOnClass(name = "com.doubibiji.hellospringboot.bean.Dog")
public UserInfo userInfo() {
UserInfo userInfo = new UserInfo();
return userInfo;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
在相面的代码中,如果环境中存在 com.doubibiji.hellospringboot.bean.Dog
就注册 UserInfo,注意不是 Spring 容器中存在。
如果想判断容器中存在指定的 Bean 才注册当前的 Bean,可以时候用 @ConditionalOnBean
注解。
@ConditionalOnBean
注解演示:
package com.doubibiji.hellospringboot.config;
import com.doubibiji.hellospringboot.bean.Dog;
import com.doubibiji.hellospringboot.bean.UserInfo;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MyConfig {
@Bean
public Dog dog() {
Dog dog = new Dog();
return dog;
}
/**
* 如果容器中存在Dog类对象,就不注册,如果不存在,才注册
* @return
*/
@Bean
@ConditionalOnBean(Dog.class)
public UserInfo userInfo() {
UserInfo userInfo = new UserInfo();
return userInfo;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
在上面的代码中,@ConditionalOnBean(Dog.class)
判断的是 Spring 容器中存在 Dog 类的 Bean,才注册 UserInfo。
还有一些 @Conditional
开头的衍生注解,可以根据注解名称判断是用于怎么样的判断条件的,自行了解一下。