目前来说,java编程离不开spring,spring最基本的功能便是IoC容器功能,所以spring提供了众多的方法可以将自己写的bean对象注入到容器中,由容器来进行管理。本文总结了用注解将一个bean注入到容器中的常用的方法,用示例的方式来演示其具体的使用方式和效果。
1、@Bean注解直接导入单个类
1.1 @Bean注解的默认使用方式
@Bean注解需要写在由 @Configuration注解标注的配置类中的方法上,可以将该方法创建的类注入到IoC容器中,默认容器中的key是方法名称,也可以通过在@Bean中增加参数的方式改变注入到容器中的key,示例如下:
- 我们新建一个Person类来演示需要注入的Bean,其中有一个name属性
public class Person {
private String name;
public Person(String name) {
System.out.println("创建person对象,name=" + name);
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
- 创建一个BeanConfig类,用来作为@Bean注解注入spring的配置类,并在其中用@Bean注解注入person对象
@Configuration
public class BeanConfig {
@Bean
public Person person() {
return new Person("common");
}
}
- 编写一个测试类BeanConfigTest ,用来测试@Bean注解的使用
public class BeanConfigTest {
@Test
public void testBean(){
ApplicationContext context = new AnnotationConfigApplicationContext(BeanConfig.class);
System.out.println("-------------------容器初始化完成------------------");
Person person = (Person) context.getBean("person");
System.out.println(person.getName());
}
}
测试结果如下:
创建person对象,name=common
-------------------容器初始化完成------------------
common
1.2 @Bean配合@Lazy注解实现bean的懒加载
在spring中,默认bean都是在spring容器启动的时候就已经加载好的,就像上面1.1中的示例中,在容器初始化完成之前就已经调用bean的构造方法将bean创建好放到IoC容器当中了,如果要实现在spring启动的时候不去初始化bean,而是在调用getBean()方法的时候再去创建bean的功能,就叫做bean的懒加载,可以在加载bean的类或者@Bean标注的方法上用@Lazy注解来实现该功能,代码如下:
- 在BeanConfig中增加一个方法,注入一个新的Person对象
@Bean("lazyPerson")
@Lazy
public Person lazy() {
return new Person("lazy");
}
- 在BeanConfigTest中编写一个测试方法
@Test
public void testLazyBean(){
ApplicationContext context = new AnnotationConfigApplicationContext(BeanConfig.class);
System.out.println("-------------------容器初始化完成------------------");
Person person = (Person) context.getBean("lazyPerson");
System.out.println(person.getName());
}
测试结果如下:
-------------------容器初始化完成------------------
创建person对象,name=lazy
lazy
和1.1中的测试结果对比我们可以看到,加了@Lazy注解注入的person对象在调用构造方法是在容器初始化之后才创建的
1.3 @Bean配合@Scope注解实现类的作用范围控制
在Spring中,类默认都是单例的,如果想要将一个bean在Spring中不是单例的,就需要用到@Scope注解来完成。@Scope注解的可选参数和说明如下:
- prototype :原型bean,每次使用都会创建一个新的bean
- singleton :单例bean,spring的默认,在整个容器中只会创建一次
- request :主要应用于web模块,同一次请求只创建一个实例
- session 主要应用于web模块,同一个session只创建一个实例
写一个原型的代码示例: - 在BeanConfig类中新增一个带有@Scope注解大注入方法
@Bean("prototype")
@Scope("prototype")
public Person prototype() {
return new Person("prototype");
}
- 在BeanConfigTest中新增一个测试方法
@Test
public void testPrototypeBean(){
ApplicationContext context = new AnnotationConfigApplicationContext(BeanConfig.class);
System.out.println("-------------------容器初始化完成------------------");
Person person1 = (Person) context.getBean("prototype");
Person person2 = (Person) context.getBean("prototype");
System.out.println(person1 == person2);
}
测试结果:
-------------------容器初始化完成------------------
创建person对象,name=prototype
创建person对象,name=prototype
false
从上面的测试结果中我们可以看出,每次调用getBean方法的时候,spring都会去调用Person类的构造方法来创建一个新的类,而且两次调用创建出来的类是不一样的。
1.4 @Bean配合@Conditional实现按照条件来注入bean
@Conditional注解可以控制spring在注入该bean的时候先去按照我们写的要求去判断一下条件,如果是满足条件的,就注入该对象,如果是不满足条件的,就不去注入该对象,@Conditional注解是SpringBoot实现的基础的注解,在SpringBoot中就是根据这个注解来判断我们是否导入了某个功能的jar包,然后替我们将对应功能的配置类导入到容器中,实现自动化装配的功能。
@Conditional注解中的参数是一个实现了org.springframework.context.annotation.Condition接口的类,通过这个接口的matches方法,判断是否需要导入新的bean,我们用判断Windows和Linux系统的方式,来导入不同的对象来作为示例,代码如下:
- 实现WindowsCondition类,用来判断在windows系统时注入对象
public class WindowsCondition implements Condition {
@Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
Environment environment = conditionContext.getEnvironment();
if (environment.getProperty("os.name").contains("Windows")){
return true;
}
return false;
}
}
- 实现LinuxCondition类,用来判断在linux系统时注入对象
public class LinuxCondition implements Condition {
@Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
Environment environment = conditionContext.getEnvironment();
if (environment.getProperty("os.name").contains("Linux")) {
return true;
}
return false;
}
}
- 在BeanConfig中用@Bean注解分别注入不同条件下的bean
@Bean
@Conditional(WindowsCondition.class)
public Person windowsPerson() {
return new Person("windows");
}
@Bean
@Conditional(LinuxCondition.class)
public Person linuxPerson() {
return new Person("Linux");
}
- 在BeanConfigTest中编写对应的测试方法
@Test
public void testConditionBean(){
ApplicationContext context = new AnnotationConfigApplicationContext(BeanConfig.class);
System.out.println("-------------------容器初始化完成------------------");
Person windowsPerson = (Person) context.getBean("windowsPerson");
System.out.println(windowsPerson.getName());
Person linuxPerson = (Person) context.getBean("linuxPerson");
System.out.println(linuxPerson.getName());
}
测试结果如下:
创建person对象,name=windows
-------------------容器初始化完成------------------
windows
org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'linuxPerson' available
从上面的结果中我们看到,因为我的系统时windows,spring根据@Conditional注解只注入了windows的person对象,并没有注入linux的对象,因此我们在获取linux的对象的时候,抛出了没有bean的异常。
2、@ComponentScan注解扫描并自动注入包中的bean
@ComponentScan注解可以认为是批量的@Bean注解,spring可以根据***@ComponentScan***配置的路径或者规则自动装配扫描到的bean,这也是我们日常使用中最常见的一种方式,在这里我们该注解的使用方法做一个总结。
2.1 @ComponentScan注解的默认使用方法
@ComponentScan注解最常用的是在参数中配置一个包路径,spring会为我们扫描这个包下面加了@Controller、@Service、@Repository、@Component这几个注解的类,然后添加到容器中。在容器中,默认bean的名称是首字母小写的类名当然,我们也可以用这些注解的value参数来指定新的类名。同时,@Lazy、@Scope和@Conditional等注解也可以加载对应的类上面,实现对应的功能。示例代码如下:
- 我们首先编写一个ComponentScanTest的测试类,用来打印出注入到spring的IoC容器中的bean,为了方便演示,我们在打印的时候过滤掉了IoC容器中spring的bean和配置类
public class ComponentScanTest {
@Test
public void showIocComponents() {
ApplicationContext context = new AnnotationConfigApplicationContext(ComponentScanConfig.class);
System.out.println("-----------------容器启动完成---------------------");
String[] beanDefinitionNames = context.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
//跳过spring的 bean和配置bean本身,只关注我们自己定义的对象
if (beanDefinitionName.contains("org.springframework.context")
|| beanDefinitionName.endsWith("Config")) {
continue;
}
System.out.println(beanDefinitionName);
}
}
}
- 我们在entities包下创建几个类,分别加了@Controller、@Service、@Repository、@Component这几个注解
@Controller
public class ControllerBean {
}
@Service
public class ServiceBean {
}
@Repository
public class RepositoryBean {
}
@Component
public class ComponentBean {
}
- 编写ComponentScanConfig配置类,用@ComponentScan的默认方式扫描bean
@Configuration
@ComponentScan(value = "entities")
public class ComponentScanConfig {
}
- 执行测试方法,测试结果如下:
-----------------容器启动完成---------------------
componentBean
controllerBean
repositoryBean
serviceBean
2.2 关闭@ComponentScan的默认扫描
从上面2.1的测试结果我们可以看出,@ComponentScan注解默认扫描了指定包下面加了@Controller、@Service、@Repository、@Component这几个注解的类。我们可以设置@ComponentScan注解中的useDefaultFilters = false,来关闭默认的扫描方式。示例代码和测试结果如下
- 关闭默认扫描的ComponentScanConfig 类
@Configuration
@ComponentScan(value = "entities",useDefaultFilters = false)
public class ComponentScanConfig {
}
- 测试结果
-----------------容器启动完成---------------------
Process finished with exit code 0
我们可以看到,将参数useDefaultFilters 设置为false之后,原本加了@Controller、@Service、@Repository、@Component注解的类也扫描不到容器中了,也就是说默认的扫描方式失效了
2.3 用includeFilters指定扫描的注解
从上面的示例中我们可以看出,用将useDefaultFilters 设置为false的方式,我们可以关闭默认的扫描方式,那我们要是只需要扫描其中的一两个注解要怎么办呢?方法就是用includeFilters指定需要扫描的注解,代码如下:
- 我们在ComponentScanConfig中设置useDefaultFilters = false,并用includeFilters指定只扫描@Controller注解
@Configuration
@ComponentScan(value = "entities"
, includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, value = { Controller.class})}
, useDefaultFilters = false)
public class ComponentScanConfig {
}
- 测试结果如下:
-----------------容器启动完成---------------------
controllerBean
从结果可以看出,IoC中只加入了我们指定的@Controller注解标注的类
2.4 用excludeFilters参数排除部分的扫描类
spring默认扫描了四个注解,我们可以用excludeFilters参数排除掉 其中的一些注解,使标注了排除注解的类不再被自动加载到容器中。代码如下:
- 在ComponentScanConfig中排除添加了Controller注解的类
@Configuration
@ComponentScan(value = "entities"
, excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, value = {Controller.class})})
public class ComponentScanConfig {
}
- 测试结果如下:
-----------------容器启动完成---------------------
componentBean
repositoryBean
serviceBean
从上面结果中可以看出,标注了@Controller注解的bean没有被加载到IoC容器中
2.5 用includeFilters添加特定的Bean到容器中
@ComponentScan会默认扫描添加了@Controller、@Service、@Repository、@Component这几个注解的bean,并将它们注入到IoC容器中,如果一个bean没有添加这几个注解(比如引入一个二方jar包,我们没法添加注解)的bean,我们要如何将其加入到spring容器中呢?可以使用@ComponentScan的includeFilters属性将其添加进来,具体的做法是在includeFilters属性列表中加入一个@ComponentScan.Filter对象,其中type选择FilterType.ASSIGNABLE_TYPE,value就是需要添加的具体对象的class列表,示例如下:
- 新增一个没有添加任何注解的类ComponentFilterBean
public class ComponentFilterBean {
}
- 在ComponentScanConfig中添加includeFilters属性
@Configuration
@ComponentScan(value = "entities"
, includeFilters = {@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = {ComponentFilterBean.class})})
public class ComponentScanConfig {
}
- 测试结果如下:
-----------------容器启动完成---------------------
componentBean
componentFilterBean
controllerBean
repositoryBean
serviceBean
从上面的结果中可以看出,除了默认的几个bean,没有添加任何注解的ComponentFilterBean也被添加到了容器中
2.6 自定义@CompanentScan的扫描过滤器
@CompanentScan注解除了用includeFilters和excludeFilters两个属性来灵活选择需要注入的类外,还可以自定义扫描类的filter,来判断是否将扫描到的类加入到容器中。
具体的做法是,我们需要新建一个实现了org.springframework.core.type.filter.TypeFilter接口的类,并实现match方法,spring会将扫描到的类挨个传递给指定的filter,在这个filter中,我们可以根据自己的逻辑指定是否将这个类加入到spring容器中,需要加入的返回true,不需要加入的返回false;然后将这个自定义的filter类加入到includeFilters属性中,type指定为FilterType.CUSTOM即可,示例代码如下:
- 新建一个MyComponentScanFilter类,实现TypeFilter接口,其中只将componentFilterBean加入容器中,其他的都不加入
public class MyComponentScanFilter implements TypeFilter {
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
//获取当前扫描到的类的注解信息
AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
//获取当前扫描到的类信息
ClassMetadata classMetadata = metadataReader.getClassMetadata();
//获取当前扫描到的类的资源
Resource resource = metadataReader.getResource();
String className = classMetadata.getClassName();
System.out.println("------" + className + "------");
if(className.contains("ComponentFilterBean")){
return true;
}
return false;
}
}
- 修改ComponentScanConfig的@ComponentScan,将自定义的filter类加入到includeFilters属性中
@Configuration
@ComponentScan(value = "entities"
, includeFilters = {@ComponentScan.Filter(type = FilterType.CUSTOM, value = {MyComponentScanFilter.class})}
, useDefaultFilters = false)
public class ComponentScanConfig {
}
- 测试结果如下:
------entities.ComponentBean------
------entities.ComponentFilterBean------
------entities.ControllerBean------
------entities.RepositoryBean------
------entities.ServiceBean------
-----------------容器启动完成---------------------
componentFilterBean
从上面的测试结果可以看出,spring将扫描到的所有的类都传入到我们自定义的filter中,但是只根据我们写的filter的逻辑,将componentFilterBean加入到了容器中。
由于文章比较长,一次发不出去,分开来发,后面两种方法请查看《Spring中给IoC容器注入bean的几种方法(二)》
后记
个人总结,欢迎转载、评论、批评指正