• spring bean管理2


    目录

    1.将xml配置文件改为配置类

    2.@Configuration的proxyBeanMethod属性

    3.bean管理方式

    4.使用@Import注解注入bean

     注:注解格式导入注解配置bean

    5. 编程形式注册bean

     6.导入实现了ImportSelector接口的类      

     7.导入实现了ImportBeanDefinitionRegistrar接口的类

     8.导入实现了BeanDefinitionRegistryPostProcessor接口的类

    总结

    bean的加载控制

    1.bean的加载控制(编程式)

    2. bean的加载控制(注解式)

    3. 指定类上根据条件选择性加载


    1.将xml配置文件改为配置类

            如果以前使用xml配置bean,现在想改用注解方式的配置类,如果将xml内的改写太麻烦,而且可能出错,想加载配置类的同时也加载xml配置文件,使用@ImpotResource注解实现。

    1. @ImportResouce("applicationContext.xml") //将applicationContext.xml注入进来,使用一个容器
    2. public class MyConfig {
    3. }

    2.@Configuration的proxyBeanMethod属性

            spring中有两种生成代理对象的方式:jdk动态代理和cglib动态代理

            在配置类上注解@Configuration中有proxyBeanMethod属性,默认值为true,即:

            @Configuration=@Configuration(proxyBeanMethod=true)

            这样配置类里@Bean生成的对象是使用cglib代理生成的代理对象,这样在其他地方不管调用几次代理对象都是同一个。

            将proxyBeanMethod属性改为false,就不是代理对象了,每次调用生成一个新的对象

            @Configuration(proxyBeanMethod=true)

    3.bean管理方式

           前三种在这。承接上文

    参考Spring 加载Bean的方式(8种) - 郝志锋 - 博客园

    4.使用@Import注解注入bean

            使用扫描的方式加载bean是企业级开发中常见的bean的加载方式,但是由于扫描的时候不仅可以加载到你要的东西,还有可能加载到各种各样的乱七八糟的东西,万一没有控制好得不偿失了。

            所以我们需要一种精准制导的加载方式,使用@Import注解就可以解决你的问题。它可以加载所有的一切,只需要在注解的参数中写上加载的类对应的.class即可。有人就会觉得,还要自己手写,多麻烦,不如扫描好用。对呀,但是他可以指定加载啊,好的命名规范配合@ComponentScan可以解决很多问题,但是@Import注解拥有其重要的应用场景。有没有想过假如你要加载的bean没有使用@Component修饰呢?这下就无解了,而@Import就无需考虑这个问题。

            被@Import进的bean的名字,是全路径类名。

    1. public class Dog {
    2. }
    1. @Import({Dog.class})
    2. // 被导入的为普通的Class就行,无需使用注解声明为bean
    3. public class SpringConfig4 {
    4. }

    测试:

    1. public class App4 {
    2. public static void main(String[] args) {
    3. ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig4.class);
    4. String[] names = ctx.getBeanDefinitionNames();
    5. for (String name : names) {
    6. System.out.println(name);
    7. }
    8. System.out.println("----------------------");
    9. }
    10. }

     

            这样在User类和Book类不需要使用@Component等注解就配置了对应bean,而且创建迅速,当导入的时候就容器中有了对应bean,可直接使用了,也体现了spring的无侵入式编程,降低与spring技术的耦合度。

            也可以导入一个配置类,且配置其中的所有bean。

     注:注解格式导入注解配置bean

            除了加载bean,还可以使用@Import注解加载配置类。其实本质上是一样的。  但是注意,这种@Import方式加载进spring的配置Bean的名字(全路径类名)和前面扫描加载进spring的配置bean的名字(类名小写)不同,具体可见方式二中和本方式的测试代码的运行效果。

    1. @Configuration
    2. // 实际山,当被使用@Import 方式导入时, 无论 @Configuration 注解是否有,都可以将 DbConfig和dataSource 加载到Spring容器中
    3. public class DbConfig {
    4. @Bean
    5. public DruidDataSource dataSource(){
    6. DruidDataSource ds = new DruidDataSource();
    7. return ds;
    8. }
    9. }
    1. @Import({Dog.class,DbConfig.class})
    2. public class SpringConfig4 {
    3. }

    测试:

    1. public class App3 {
    2. public static void main(String[] args) {
    3. ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig3.class);
    4. String[] names = ctx.getBeanDefinitionNames();
    5. for (String name : names) {
    6. System.out.println(name);
    7. }
    8. }
    9. }

     

     

    5. 编程形式注册bean

            前面介绍的加载bean的方式都是在容器启动阶段完成bean的加载,下面这种方式就比较特殊了,可以在容器初始化完成后手动加载bean。通过这种方式可以实现编程式控制bean的加载。这种方式平时应用开发中不常用,但是在框架开发中会使用。

    1. public class Cat {
    2. public Cat(){
    3. }
    4. int age;
    5. public Cat(int age){
    6. this.age = age;
    7. }
    8. @Override
    9. public String toString() {
    10. return "Cat{" +
    11. "age=" + age +
    12. '}';
    13. }
    14. }
    1. public class Mouse {
    2. }

    测试: 

    1. public class App5 {
    2. public static void main(String[] args) {
    3. // ApplicationContext对象做不了,只能用AnnotationConfigApplicationContext对象
    4. AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig4.class);
    5. //上下文容器对象已经初始化完毕后,手工加载bean
    6. ctx.registerBean("tom", Cat.class,0);
    7. ctx.registerBean("tom", Cat.class,1);
    8. ctx.registerBean("tom", Cat.class,2);
    9. ctx.register(Mouse.class);
    10. String[] names = ctx.getBeanDefinitionNames();
    11. for (String name : names) {
    12. System.out.println(name);
    13. }
    14. System.out.println("----------------------");
    15. System.out.println(ctx.getBean(Cat.class));
    16. }
    17. }

     6.导入实现了ImportSelector接口的类      

            bean的加载可以进行编程化的控制,添加if语句就可以实现bean的加载控制了。但是毕竟是在容器初始化后实现bean的加载控制,那是否可以在容器初始化过程中进行控制呢?答案是必须的。实现ImportSelector接口的类可以设置加载的bean的全路径类名,记得一点,只要能编程就能判定,能判定意味着可以控制程序的运行走向,进而控制一切。

            现在又多了一种控制bean加载的方式,或者说是选择bean的方式。

            在Spring源码中大量使用,通过导入实现了ImportSelector接口的类,实现对导入源的编程式处理

    1. public class MyImportSelector implements ImportSelector {
    2. @Override
    3. public String[] selectImports(AnnotationMetadata metadata) {
    4. // System.out.println("================");
    5. // System.out.println("提示:"+metadata.getClassName());
    6. // System.out.println(metadata.hasAnnotation("org.springframework.context.annotation.Configuration"));
    7. // Map attributes = metadata.getAnnotationAttributes("org.springframework.context.annotation.ComponentScan");
    8. // System.out.println(attributes);
    9. // System.out.println("================");
    10. //各种条件的判定,判定完毕后,决定是否装在指定的bean
    11. boolean flag = metadata.hasAnnotation("org.springframework.context.annotation.Configuration");
    12. if(flag){
    13. return new String[]{"com.hao.bean.Dog"};
    14. }
    15. return new String[]{"com.hao.bean.Cat"};
    16. }
    17. }
    1. @Configuration
    2. //@ComponentScan(basePackages = "com.hao")
    3. @Import(MyImportSelector.class)
    4. public class SpringConfig6 {
    5. }

    测试

    1. public class App6 {
    2. public static void main(String[] args) {
    3. ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig6.class);
    4. String[] names = ctx.getBeanDefinitionNames();
    5. for (String name : names) {
    6. System.out.println(name);
    7. }
    8. System.out.println("----------------------");
    9. }
    10. }

     7.导入实现了ImportBeanDefinitionRegistrar接口的类

            spring中定义了一个叫做BeanDefinition的东西,它才是控制bean初始化加载的核心。BeanDefinition接口中给出了若干种方法,可以控制bean的相关属性。说个最简单的,创建的对象是单例还是非单例,在BeanDefinition中定义了scope属性就可以控制这个。如果你感觉方式六没有给你开放出足够的对bean的控制操作,那么方式七你值得拥有。我们可以通过定义一个类,然后实现ImportBeanDefinitionRegistrar接口的方式定义bean,并且还可以让你对bean的初始化进行更加细粒度的控制。

            导入实现了ImportBeanDefinitionRegistrar接口的类,通过BeanDefinition的注册器注册实名bean,实现对 容器中bean的裁定,例如对现有bean的覆盖,进而达成不修改源代码的情况下更换实现的效果

    1. public class MyRegistrar implements ImportBeanDefinitionRegistrar {
    2. @Override
    3. public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    4. //1.使用元数据去做判定
    5. BeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(BookServiceImpl2.class).getBeanDefinition();
    6. registry.registerBeanDefinition("bookService",beanDefinition);
    7. }
    8. }
    1. @Import(MyRegistrar.class)
    2. public class SpringConfig7 {
    3. }

    测试:

    1. public class App7 {
    2. public static void main(String[] args) {
    3. ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig7.class);
    4. String[] names = ctx.getBeanDefinitionNames();
    5. for (String name : names) {
    6. System.out.println(name);
    7. }
    8. System.out.println("----------------------");
    9. }
    10. }

     8.导入实现了BeanDefinitionRegistryPostProcessor接口的类

            上述七种方式都是在容器初始化过程中进行bean的加载或者声明,但是这里有一个bug。这么多种方式,它们之间如果有冲突怎么办?谁能有最终裁定权?这是个好问题,当某种类型的bean被接二连三的使用各种方式加载后,在你对所有加载方式的加载顺序没有完全理解清晰之前,你还真不知道最后谁说了算。 

            导入实现了BeanDefinitionRegistryPostProcessor接口的类,通过BeanDefinition的注册器注册实名bean, 可以实现对容器中bean的最终裁定

    1. public interface BookSerivce {
    2. void check();
    3. }
    1. @Service("bookService")
    2. public class BookServiceImpl1 implements BookSerivce {
    3. @Override
    4. public void check() {
    5. System.out.println("book service 1..");
    6. }
    7. }
    1. public class BookServiceImpl2 implements BookSerivce {
    2. @Override
    3. public void check() {
    4. System.out.println("book service 2....");
    5. }
    6. }
    1. public class BookServiceImpl3 implements BookSerivce {
    2. @Override
    3. public void check() {
    4. System.out.println("book service 3......");
    5. }
    6. }
    1. public class MyRegistrar2 implements ImportBeanDefinitionRegistrar {
    2. @Override
    3. public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    4. //1.使用元数据去做判定
    5. BeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(BookServiceImpl3.class).getBeanDefinition();
    6. registry.registerBeanDefinition("bookService",beanDefinition);
    7. }
    8. }
    1. public class MyPostProcessor implements BeanDefinitionRegistryPostProcessor {
    2. @Override
    3. public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
    4. BeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(BookServiceImpl4.class).getBeanDefinition();
    5. registry.registerBeanDefinition("bookService",beanDefinition);
    6. }
    7. @Override
    8. public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
    9. }
    10. }
    1. @Import({BookServiceImpl1.class, MyPostProcessor.class, MyRegistrar2.class, MyRegistrar.class})
    2. public class SpringConfig8 {
    3. }

    测试:

    1. public class App8 {
    2. public static void main(String[] args) {
    3. ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig8.class);
    4. BookSerivce bookService = ctx.getBean("bookService", BookSerivce.class);
    5. bookService.check();
    6. }
    7. }

     

    总结

    1. bean的定义由前期xml配置逐步演化成注解配置,本质是一样的,都是通过反射机制加载类名后创建对象,对象就是spring管控的bean

    2. @Import注解可以指定加载某一个类作为spring管控的bean,如果被加载的类中还具有@Bean相关的定义,会被一同加载

    3. spring开放出了若干种可编程控制的bean的初始化方式,通过分支语句由固定的加载bean转成了可以选择bean是否加载或者选择加载哪一种bean

    bean的加载控制

    • AnnotationConfigApplicationContext调用register方法
    • @Import导入ImportSelector接口
    • @Import导入ImportBeanDefinitionRegistrar接口
    • @Import导入BeanDefinitionRegistryPostProcessor接口

    1.bean的加载控制(编程式)

    以ImportSelector为例

    1.根据任意条件确认是否加载bean

    2. bean的加载控制(注解式)

    需要导入springboot

     使用@Conditional注解的派生注解设置各种组合条件控制bean的加载

    格式:@ConditionalOn***

    1. //@Import(MyImportSelector.class)
    2. @Import(Mouse.class)
    3. public class SpringConfig {
    4. @Bean
    5. //@ConditionalOnClass(Mouse.class) 一般不使用这种方式
    6. //@ConditionalOnClass(name = "com.huangzx.bean.Mouse") //有Mouse时加载
    7. //@ConditionalOnMissingClass("com.huangzx.bean.Wolf") //没有Wolf时加载
    8. @ConditionalOnBean(name = "jerry") //有Mouse bean,且Mouse的名字是jerry时加载
    9. @ConditionalOnMissingClass("com.huangzx.bean.Dog") //有Mouse bean,且Mouse的名字是jerry时加载,且没有Dog时加载
    10. @ConditionalOnWebApplication //是web程序时加载
    11. public Cat tom(){
    12. return new Cat();
    13. }
    14. }

    3. 指定类上根据条件选择性加载

     

    后续可以单独配置bean,当有某种环境时,加载某些bean

    这里当有mysql数据库连接时,加载Druid数据源

    1. public class SpringConfig {
    2. @Bean
    3. @ConditionalOnClass(name = "com.mysql.cj.jdbc.Driver")
    4. public DruidDataSource dataSource(){
    5. DruidDataSource ds = new DruidDataSource();
    6. return ds;
    7. }
    8. }

     

  • 相关阅读:
    黑马JVM总结(十七)
    FullCalendar日历插件说明文档
    1022 Digital Library
    RabbitMQ-网页使用消息队列
    Profinet总线模拟输出模块
    React Native 的手势和触摸事件
    arm push/pop/b/bl汇编指令
    我完成了10000小时开发3D引擎
    zabbix配置钉钉报警
    flutter系列之:查询设备信息的利器:MediaQuery
  • 原文地址:https://blog.csdn.net/m0_62520968/article/details/126934565