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注解实现。

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

}

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的名字,是全路径类名。

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

}

测试:

public class App4 {
    public static void main(String[] args) {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig4.class);
        String[] names = ctx.getBeanDefinitionNames();
        for (String name : names) {
            System.out.println(name);
        }
        System.out.println("----------------------");
    }
}

 

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

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

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

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

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

}
@Import({Dog.class,DbConfig.class})
public class SpringConfig4 {

}

测试:

public class App3 {
    public static void main(String[] args) {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig3.class);
        String[] names = ctx.getBeanDefinitionNames();
        for (String name : names) {
            System.out.println(name);
        }

    }
}

 

 

5. 编程形式注册bean

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

public class Cat {
    public Cat(){
    }

    int age;
    public Cat(int age){
        this.age = age;
    }

    @Override
    public String toString() {
        return "Cat{" +
                "age=" + age +
                '}';
    }
}
public class Mouse {
}

测试: 

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

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

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

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

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

public class MyImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata metadata) {
//        System.out.println("================");
//        System.out.println("提示:"+metadata.getClassName());
//        System.out.println(metadata.hasAnnotation("org.springframework.context.annotation.Configuration"));
//        Map<String, Object> attributes = metadata.getAnnotationAttributes("org.springframework.context.annotation.ComponentScan");
//        System.out.println(attributes);
//        System.out.println("================");

        //各种条件的判定,判定完毕后,决定是否装在指定的bean
        boolean flag = metadata.hasAnnotation("org.springframework.context.annotation.Configuration");
        if(flag){
            return new String[]{"com.hao.bean.Dog"};
        }
        return new String[]{"com.hao.bean.Cat"};
    }
}
@Configuration
//@ComponentScan(basePackages = "com.hao")
@Import(MyImportSelector.class)
public class SpringConfig6 {
}

测试

public class App6 {
    public static void main(String[] args) {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig6.class);
        String[] names = ctx.getBeanDefinitionNames();
        for (String name : names) {
            System.out.println(name);
        }
        System.out.println("----------------------");
    }
}

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

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

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

public class MyRegistrar implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        //1.使用元数据去做判定

        BeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(BookServiceImpl2.class).getBeanDefinition();
        registry.registerBeanDefinition("bookService",beanDefinition);
    }
}
@Import(MyRegistrar.class)
public class SpringConfig7 {
}

测试:

public class App7 {
    public static void main(String[] args) {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig7.class);
        String[] names = ctx.getBeanDefinitionNames();
        for (String name : names) {
            System.out.println(name);
        }
        System.out.println("----------------------");
    }
}

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

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

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

public interface BookSerivce {
    void check();
}
@Service("bookService")
public class BookServiceImpl1 implements BookSerivce {
    @Override
    public void check() {
        System.out.println("book service 1..");
    }
}
public class BookServiceImpl2 implements BookSerivce {
    @Override
    public void check() {
        System.out.println("book service 2....");
    }
}
public class BookServiceImpl3 implements BookSerivce {
    @Override
    public void check() {
        System.out.println("book service 3......");
    }
}
public class MyRegistrar2 implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        //1.使用元数据去做判定

        BeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(BookServiceImpl3.class).getBeanDefinition();
        registry.registerBeanDefinition("bookService",beanDefinition);
    }
}
public class MyPostProcessor implements BeanDefinitionRegistryPostProcessor {
    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        BeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(BookServiceImpl4.class).getBeanDefinition();
        registry.registerBeanDefinition("bookService",beanDefinition);
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {

    }
}
@Import({BookServiceImpl1.class, MyPostProcessor.class, MyRegistrar2.class, MyRegistrar.class})
public class SpringConfig8 {
}

测试:

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

 

总结

  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***

//@Import(MyImportSelector.class)
@Import(Mouse.class)
public class SpringConfig {
 
    @Bean
    //@ConditionalOnClass(Mouse.class)  一般不使用这种方式
    //@ConditionalOnClass(name = "com.huangzx.bean.Mouse") //有Mouse时加载
    //@ConditionalOnMissingClass("com.huangzx.bean.Wolf")  //没有Wolf时加载
 
    @ConditionalOnBean(name = "jerry")  //有Mouse  bean,且Mouse的名字是jerry时加载
    @ConditionalOnMissingClass("com.huangzx.bean.Dog") //有Mouse  bean,且Mouse的名字是jerry时加载,且没有Dog时加载
 
    @ConditionalOnWebApplication //是web程序时加载
    public Cat tom(){
        return new Cat();
    }
}

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

 

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

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

public class SpringConfig {
    @Bean
    @ConditionalOnClass(name = "com.mysql.cj.jdbc.Driver")
    public DruidDataSource dataSource(){
        DruidDataSource ds = new DruidDataSource();
        return ds;
    }
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值