Spring中给IoC容器注入bean的几种方法(一)

  目前来说,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的几种方法(二)》


后记
  个人总结,欢迎转载、评论、批评指正

你可能感兴趣的:(#,Spring应用,spring,java)