关注「Java视界」公众号,获取更多技术干货

向spring容器中注入bean有哪些方式?大总结!

怎么把想要的bean(而要注入的bean包括自己项目中的、第三方的)注册到spring容器,让spring替我们管理这些bean呢?

总结起来有四大方式:

  • 一、通过包扫描特定注解的方式
  • 二、@Import 注解
  • 三、@Bean 注解
  • 四、FactoryBean 接口

一、通过包扫描特定注解的方式

特定注解包括:@Controller、@Service、@Component、@Repository。

结合@ComponentScan注解配置相应的路径即可将需要的bean注入容器。

这个之前写过一篇详细的文章,详情见:@ComponentScan配置老扫描不到Bean,这下彻底搞懂!

二、@Import注解

@Import注解注入bean有三种方式:

  1. @Import(要注入的bean的class对象),生成的bean的id默认是这个类的全类名
  2. @Import(ImportSelector实现类的class对象),导入的组件全类名数组
  3. @Import(ImportBeanDefinitionRegistrar实现类的class对象),手动注册需要的bean到容器

比如,我们要导入A、B、C、D、E几个类:

public class A {
    
}

public class B {
    
}

public class C {
    
}

public class D {
    
}

public class E {
    
}

2.1  @Import(要注入的bean的class对象)

@Configuration
@Import({A.class, B.class})
public class ImportTestConfig01 {

}
@SpringBootTest
public class ImportAnnotationTest {

    @Test
    public void test() {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(ImportTestConfig01.class);
        String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
        Stream.of(beanDefinitionNames).forEach(System.out::println);
    }
}
importTestConfig01
fengge.config.importAnnotation.A
fengge.config.importAnnotation.B

可以看到A、B两个类已经注入容器,且id为全类名。

2.2 @Import(ImportSelector实现类的class对象)

public class MyImportSelector implements ImportSelector {

    /**
     * 方法返回值就是要导入的组件全类名数组
     * AnnotationMetadata :@import注解所在类的所有注解信息
     */
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        Console.log("@import注解所在类的所有注解类型信息:" + importingClassMetadata.getAnnotationTypes());
        return new String[]{"fengge.config.importAnnotation.C", "fengge.config.importAnnotation.D"};
    }
}
@Configuration
@Import({A.class, B.class, MyImportSelector.class})
public class ImportTestConfig02 {

}
importTestConfig02
fengge.config.importAnnotation.A
fengge.config.importAnnotation.B
fengge.config.importAnnotation.C
fengge.config.importAnnotation.D

可以看到C、D两个类是通过 ImportSelector 接口的实现类 MyImportSelector 注入的。

AnnotationMetadata :@import注解所在类的所有注解信息。这里我们也打印了一下,正好就是 ImportTestConfig02 上面的两个注解:

@import注解所在类的所有注解类型信息:[org.springframework.context.annotation.Configuration, org.springframework.context.annotation.Import]

2.3 @Import(ImportBeanDefinitionRegistrar实现类的class对象)

public class MyRegistry implements ImportBeanDefinitionRegistrar {

    /**
     * 手动将需要的bean定义注册
     * AnnotationMetadata :@import注解所在类的所有注解信息
     */
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(E.class);
        registry.registerBeanDefinition("E", rootBeanDefinition);
    }
}
@Configuration
@Import({A.class, B.class, MyImportSelector.class, MyRegistry.class})
public class ImportTestConfig03 {

}
importTestConfig03
fengge.config.importAnnotation.A
fengge.config.importAnnotation.B
fengge.config.importAnnotation.C
fengge.config.importAnnotation.D
E

可以看到,E这个类型的bean是通过 ImportBeanDefinitionRegistrar 接口的实现类 MyRegistry 注入的。且id为类名,非全类名。

三、@Bean 注解

如果要注入的bean来自第三方,类没有使用@Controller、@Service、@Component、@Repository等注解去修饰,那就意味着spring扫描时就不会识别这是个要注入的bean,那就可以使用@bean注解去实现注入:

@Configuration // 相当于原来的xml文件,告诉spring这是个配置类
public class TestConfig {

    @Bean("car") // 给spring注入一个bean,类型是返回值类型,id是默认是方法名
    public CarDTO carDTO(){
        return new CarDTO(1,"BMW");
    }
}

假设这个CarDTO来自第三方,那就可以这样输入。

四、FactoryBean 接口

public class MyFactoryBean implements FactoryBean<CarDTO> {

    /**
     * 这里的CartDTO类型的bean组件,会注入spring容器
     */
    @Override
    public CarDTO getObject() throws Exception {
        Console.log("MyFactoryBean中的getObject()方法生成carDTO这个bean");
        return new CarDTO(666, "BMW");
    }

    /**
     * 这里返回要注入的bean的Class对象
     */
    @Override
    public Class<?> getObjectType() {
        return CarDTO.class;
    }

    /**
     *  设置是否是单例
     *  1、如果是true容器中只保留一个bean对象
     *  2、若是false每次获取则产生新的bean对象
     */
    @Override
    public boolean isSingleton() {
        return true;
    }
}
@Configuration
public class BeanFactoryConfig {

    @Bean
    public MyFactoryBean myFactoryBean(){
        Console.log("进入BeanFactoryConfig方法");
        return new MyFactoryBean();
    }
}
    @Test
    public void test1() {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(BeanFactoryConfig.class);
        Console.log("容器已创建完成!");
        Object myFactoryBean = applicationContext.getBean("myFactoryBean");
        Console.log("实际生成的bean:" + myFactoryBean);
        Object myFactoryBean01 = applicationContext.getBean("myFactoryBean");
        Console.log("实际生成的bean:" + myFactoryBean01);
        Console.log(myFactoryBean == myFactoryBean01);
    }
进入BeanFactoryConfig方法
容器已创建完成!
MyFactoryBean中的getObject()方法生成carDTO这个bean
实际生成的bean:CarDTO(id=666, brand=BMW)
实际生成的bean:CarDTO(id=666, brand=BMW)
true

可以看到:

  • 1、这里getBean("myFactoryBean"),是小写的,@Bean注解没有指定value时,默认方法名为id
  • 2、@Bean注解是加在返回类型为MyFactoryBean类型的方法上的,但是getBean("myFactoryBean")时返回的bean并不是MyFactoryBean类型的组件,实际返回的是它里面的getObject()方法产生的CarDTO类型的bean
  • 3、因为MyFactoryBean中的isSingleton()方法返回值为true,即单例模式,容器中只有一份实例,多次获取的是同一实例

若把MyFactoryBean中的 isSingleton()方法返回值设为false:

进入BeanFactoryConfig方法
容器已创建完成!
MyFactoryBean中的getObject()方法生成carDTO这个bean
实际生成的bean:CarDTO(id=666, brand=BMW)
MyFactoryBean中的getObject()方法生成carDTO这个bean
实际生成的bean:CarDTO(id=666, brand=BMW)
false

因为MyFactoryBean中的isSingleton()方法返回值为false,即多例模式,每次获取的是不同实例。

好了有了上面的说明,还怕你的bean不能注入吗!

代码参见: https://github.com/ImOk520/myspringcloud

posted @ 2022-06-25 14:02  沙滩de流沙  阅读(1137)  评论(0编辑  收藏  举报

关注「Java视界」公众号,获取更多技术干货