目前来说,java编程离不开spring,spring最基本的功能便是IoC容器功能,所以spring提供了众多的方法可以将自己写的bean对象注入到容器中,由容器来进行管理。本文总结了用注解将一个bean注入到容器中的常用的方法,用示例的方式来演示其具体的使用方式和效果。
@Bean注解需要写在由 @Configuration注解标注的配置类中的方法上,可以将该方法创建的类注入到IoC容器中,默认容器中的key是方法名称,也可以通过在@Bean中增加参数的方式改变注入到容器中的key,示例如下:
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;
}
}
@Configuration
public class BeanConfig {
@Bean
public Person person() {
return new Person("common");
}
}
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
在spring中,默认bean都是在spring容器启动的时候就已经加载好的,就像上面1.1中的示例中,在容器初始化完成之前就已经调用bean的构造方法将bean创建好放到IoC容器当中了,如果要实现在spring启动的时候不去初始化bean,而是在调用getBean()方法的时候再去创建bean的功能,就叫做bean的懒加载,可以在加载bean的类或者@Bean标注的方法上用@Lazy注解来实现该功能,代码如下:
@Bean("lazyPerson")
@Lazy
public Person lazy() {
return new Person("lazy");
}
@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对象在调用构造方法是在容器初始化之后才创建的
在Spring中,类默认都是单例的,如果想要将一个bean在Spring中不是单例的,就需要用到@Scope注解来完成。@Scope注解的可选参数和说明如下:
@Bean("prototype")
@Scope("prototype")
public Person prototype() {
return new Person("prototype");
}
@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类的构造方法来创建一个新的类,而且两次调用创建出来的类是不一样的。
@Conditional注解可以控制spring在注入该bean的时候先去按照我们写的要求去判断一下条件,如果是满足条件的,就注入该对象,如果是不满足条件的,就不去注入该对象,@Conditional注解是SpringBoot实现的基础的注解,在SpringBoot中就是根据这个注解来判断我们是否导入了某个功能的jar包,然后替我们将对应功能的配置类导入到容器中,实现自动化装配的功能。
@Conditional注解中的参数是一个实现了org.springframework.context.annotation.Condition接口的类,通过这个接口的matches方法,判断是否需要导入新的bean,我们用判断Windows和Linux系统的方式,来导入不同的对象来作为示例,代码如下:
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;
}
}
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;
}
}
@Bean
@Conditional(WindowsCondition.class)
public Person windowsPerson() {
return new Person("windows");
}
@Bean
@Conditional(LinuxCondition.class)
public Person linuxPerson() {
return new Person("Linux");
}
@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的异常。
@ComponentScan注解可以认为是批量的@Bean注解,spring可以根据***@ComponentScan***配置的路径或者规则自动装配扫描到的bean,这也是我们日常使用中最常见的一种方式,在这里我们该注解的使用方法做一个总结。
@ComponentScan注解最常用的是在参数中配置一个包路径,spring会为我们扫描这个包下面加了@Controller、@Service、@Repository、@Component这几个注解的类,然后添加到容器中。在容器中,默认bean的名称是首字母小写的类名当然,我们也可以用这些注解的value参数来指定新的类名。同时,@Lazy、@Scope和@Conditional等注解也可以加载对应的类上面,实现对应的功能。示例代码如下:
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);
}
}
}
@Controller
public class ControllerBean {
}
@Service
public class ServiceBean {
}
@Repository
public class RepositoryBean {
}
@Component
public class ComponentBean {
}
@Configuration
@ComponentScan(value = "entities")
public class ComponentScanConfig {
}
-----------------容器启动完成---------------------
componentBean
controllerBean
repositoryBean
serviceBean
从上面2.1的测试结果我们可以看出,@ComponentScan注解默认扫描了指定包下面加了@Controller、@Service、@Repository、@Component这几个注解的类。我们可以设置@ComponentScan注解中的useDefaultFilters = false,来关闭默认的扫描方式。示例代码和测试结果如下
@Configuration
@ComponentScan(value = "entities",useDefaultFilters = false)
public class ComponentScanConfig {
}
-----------------容器启动完成---------------------
Process finished with exit code 0
我们可以看到,将参数useDefaultFilters 设置为false之后,原本加了@Controller、@Service、@Repository、@Component注解的类也扫描不到容器中了,也就是说默认的扫描方式失效了
从上面的示例中我们可以看出,用将useDefaultFilters 设置为false的方式,我们可以关闭默认的扫描方式,那我们要是只需要扫描其中的一两个注解要怎么办呢?方法就是用includeFilters指定需要扫描的注解,代码如下:
@Configuration
@ComponentScan(value = "entities"
, includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, value = { Controller.class})}
, useDefaultFilters = false)
public class ComponentScanConfig {
}
-----------------容器启动完成---------------------
controllerBean
从结果可以看出,IoC中只加入了我们指定的@Controller注解标注的类
spring默认扫描了四个注解,我们可以用excludeFilters参数排除掉 其中的一些注解,使标注了排除注解的类不再被自动加载到容器中。代码如下:
@Configuration
@ComponentScan(value = "entities"
, excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, value = {Controller.class})})
public class ComponentScanConfig {
}
-----------------容器启动完成---------------------
componentBean
repositoryBean
serviceBean
从上面结果中可以看出,标注了@Controller注解的bean没有被加载到IoC容器中
@ComponentScan会默认扫描添加了@Controller、@Service、@Repository、@Component这几个注解的bean,并将它们注入到IoC容器中,如果一个bean没有添加这几个注解(比如引入一个二方jar包,我们没法添加注解)的bean,我们要如何将其加入到spring容器中呢?可以使用@ComponentScan的includeFilters属性将其添加进来,具体的做法是在includeFilters属性列表中加入一个@ComponentScan.Filter对象,其中type选择FilterType.ASSIGNABLE_TYPE,value就是需要添加的具体对象的class列表,示例如下:
public class ComponentFilterBean {
}
@Configuration
@ComponentScan(value = "entities"
, includeFilters = {@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = {ComponentFilterBean.class})})
public class ComponentScanConfig {
}
-----------------容器启动完成---------------------
componentBean
componentFilterBean
controllerBean
repositoryBean
serviceBean
从上面的结果中可以看出,除了默认的几个bean,没有添加任何注解的ComponentFilterBean也被添加到了容器中
@CompanentScan注解除了用includeFilters和excludeFilters两个属性来灵活选择需要注入的类外,还可以自定义扫描类的filter,来判断是否将扫描到的类加入到容器中。
具体的做法是,我们需要新建一个实现了org.springframework.core.type.filter.TypeFilter接口的类,并实现match方法,spring会将扫描到的类挨个传递给指定的filter,在这个filter中,我们可以根据自己的逻辑指定是否将这个类加入到spring容器中,需要加入的返回true,不需要加入的返回false;然后将这个自定义的filter类加入到includeFilters属性中,type指定为FilterType.CUSTOM即可,示例代码如下:
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;
}
}
@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的几种方法(二)》
后记
个人总结,欢迎转载、评论、批评指正