# 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 开头的衍生注解,可以根据注解名称判断是用于怎么样的判断条件的,自行了解一下。
