• Spring Bean的加载方式


    目录

    Bean的加载方式

    1.配置文件 + 标签

    2.配置文件扫描 + 注解定义bean

    3.注解方式声明配置类

    导入XML格式配置的bean

    proxyBeanMethods属性

    4.使用@Impore注入bean

    5.编程形式注册bean

    6.导入实现了ImportSelector接口类

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

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

    Conditional - bean的加载控制

    bean的依赖属性配置管理

    自动配置原理(工作流程)


    Bean的加载方式

    1.配置文件 + 标签

    所以第一种方式就是给出bean的类名,至于内部嘛就是反射机制加载成class,然后,就没有然后了,拿到了class你就可以搞定一切了。

    1. "1.0" encoding="UTF-8"?>
    2. <beans xmlns="http://www.springframework.org/schema/beans"
    3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    4. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    5. <bean id="cat" class="Cat"/>
    6. <bean class="Dog"/>
    7. <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"/>
    8. <bean class="com.alibaba.druid.pool.DruidDataSource"/>
    9. <bean class="com.alibaba.druid.pool.DruidDataSource"/>
    10. beans>

    2.配置文件扫描 + 注解定义bean

    这里可以使用的注解有@Component以及三个衍生注解@Service、@Controller、@Repository。

    1. @Component("tom")
    2. public class Cat {
    3. }
    4. @Service
    5. public class Mouse {
    6. }
    7. @Component
    8. public class DbConfig {
    9. @Bean
    10. public DruidDataSource dataSource(){
    11. DruidDataSource ds = new DruidDataSource();
    12. return ds;
    13. }
    14. }
    1. "1.0" encoding="UTF-8"?>
    2. <beans xmlns="http://www.springframework.org/schema/beans"
    3. xmlns:context="http://www.springframework.org/schema/context"
    4. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    5. xsi:schemaLocation="
    6. http://www.springframework.org/schema/beans
    7. http://www.springframework.org/schema/beans/spring-beans.xsd
    8. http://www.springframework.org/schema/context
    9. http://www.springframework.org/schema/context/spring-context.xsd
    10. ">
    11. <context:component-scan base-package="com.itheima.bean,com.itheima.config"/>
    12. beans>

    3.注解方式声明配置类

    1. @ComponentScan({"com.itheima.bean","com.itheima.config"})
    2. public class SpringConfig3 {
    3. @Bean
    4. public DogFactoryBean dog(){
    5. return new DogFactoryBean();
    6. }
    7. }

    这里使用了FactoryBean接口

    补充一个小知识,spring提供了一个接口FactoryBean,也可以用于声明bean,只不过实现了FactoryBean接口的类造出来的对象不是当前类的对象,而是FactoryBean接口泛型指定类型的对象。如下列,造出来的bean并不是DogFactoryBean,而是Dog。

    1. public class DogFactoryBean implements FactoryBean {
    2. @Override
    3. public Dog getObject() throws Exception {
    4. Dog d = new Dog();
    5. //.........
    6. return d;
    7. }
    8. @Override
    9. public Class getObjectType() {
    10. return Dog.class;
    11. }
    12. @Override
    13. public boolean isSingleton() {
    14. return true;
    15. }
    16. }

    有人说,注释中的代码写入Dog的构造方法不就行了吗?干嘛这么费劲转一圈,还写个类,还要实现接口,多麻烦啊。还真不一样,你可以理解为Dog是一个抽象后剥离的特别干净的模型,但是实际使用的时候必须进行一系列的初始化动作。只不过根据情况不同,初始化动作不同而已。如果写入Dog,或许初始化动作A当前并不能满足你的需要,这个时候你就要做一个DogB的方案了。然后,就没有然后了,你就要做两个Dog类。当时使用FactoryBean接口就可以完美解决这个问题。

    导入XML格式配置的bean

    由于早起开发的系统大部分都是采用xml的形式配置bean,现在的企业级开发基本上不用这种模式了。 但是如果你特别幸运,需要基于之前的系统进行二次开发,这就尴尬了。新开发的用注解格式,之前开发的是xml格式。这个时候可不是让你选择用哪种模式的,而是两种要同时使用。

    spring提供了一个注解可以解决这个问题,@ImportResource,在配置类上直接写上要被融合的xml配置文件名即可,算的上一种兼容性解决方案

    1. @Configuration
    2. @ImportResource("applicationContext1.xml")
    3. public class SpringConfig32 {
    4. }

    proxyBeanMethods属性

    @Configuration这个注解,当我们使用AnnotationConfigApplicationContext加载配置类的时候,配置类可以不添加这个注解。但是这个注解有一个更加强大的功能,它可以保障配置类中使用方法创建的bean的唯一性。为@Configuration注解设置proxyBeanMethods属性值为true即可,由于此属性默认值为true

    1. @Configuration(proxyBeanMethods = true)
    2. public class SpringConfig33 {
    3. @Bean
    4. public Cat cat(){
    5. return new Cat();
    6. }
    7. }

    下面通过容器再调用上面的cat方法时,得到的就是同一个对象了。注意,必须使用spring容器对象调用此方法才有保持bean唯一性的特性。

    1. public class App33 {
    2. public static void main(String[] args) {
    3. ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig33.class);
    4. String[] names = ctx.getBeanDefinitionNames();
    5. for (String name : names) {
    6. System.out.println(name);
    7. }
    8. System.out.println("-------------------------");
    9. SpringConfig33 springConfig33 = ctx.getBean("springConfig33", SpringConfig33.class);
    10. System.out.println(springConfig33.cat());
    11. System.out.println(springConfig33.cat());
    12. System.out.println(springConfig33.cat());
    13. }
    14. }

    4.使用@Impore注入bean

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

    比如你扫描了com.itheima.service包,后来因为业务需要,又扫描了com.itheima.dao包,你发现com.itheima包下面只有service和dao这两个包,这就简单了,直接扫描com.itheima就行了。但是万万没想到,十天后你加入了一个外部依赖包,里面也有com.itheima包,这下就热闹了,该来的不该来的全来了。

    所以我们需要一种精准制导的加载方式,使用@Import注解就可以解决你的问题。它可以加载所有的一切,只需要在注解的参数中写上加载的类对应的.class即可。

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

    用@Import注解注入配置类

    除了加载bean,还可以使用@Import注解加载配置类。其实本质上是一样的,不解释太多了。

    1. @Import(DogFactoryBean.class)
    2. public class SpringConfig4 {
    3. }

    5.编程形式注册bean

    前面介绍的加载bean的方式都是在容器启动阶段完成bean的加载,下面这种方式就比较特殊了,可以在容器初始化完成后手动加载bean。通过这种方式可以实现编程式控制bean的加载。

    1. public class App5 {
    2. public static void main(String[] args) {
    3. AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
    4. //上下文容器对象已经初始化完毕后,手工加载bean
    5. ctx.register(Mouse.class);
    6. }
    7. }

    6.导入实现了ImportSelector接口类

    实现ImportSelector接口的类可以设置加载的bean的全路径类名,记得一点,只要能编程就能判定,能判定意味着可以控制程序的运行走向,进而控制一切。

    1. public class MyImportSelector implements ImportSelector {
    2. @Override
    3. public String[] selectImports(AnnotationMetadata metadata) {
    4. //各种条件的判定,判定完毕后,决定是否装载指定的bean
    5. boolean flag = metadata.hasAnnotation("org.springframework.context.annotation.Configuration");
    6. if(flag){
    7. return new String[]{"com.itheima.bean.Dog"};
    8. }
    9. return new String[]{"com.itheima.bean.Cat"};
    10. }
    11. }

    例子中的metadata是获取导入这个selector的配置类的源信息。可以从源信息中获取到配置类上的一些信息,可以通过这个信息进行自定义的逻辑判断来决定添加什么bean。

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

    方式六中提供了给定类全路径类名控制bean加载的形式, 其实bean的加载不是一个简简单单的对象,spring中定义了一个叫做BeanDefinition的东西,它才是控制bean初始化加载的核心。BeanDefinition接口中给出了若干种方法,可以控制bean的相关属性。说个最简单的,创建的对象是单例还是非单例,在BeanDefinition中定义了scope属性就可以控制这个。如果你感觉方式六没有给你开放出足够的对bean的控制操作,那么方式七你值得拥有。

    我们可以通过定义一个类,然后实现ImportBeanDefinitionRegistrar接口的方式定义bean

    1. public class MyRegistrar implements ImportBeanDefinitionRegistrar {
    2. @Override
    3. public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
    4. BeanDefinition beanDefinition =
    5. BeanDefinitionBuilder.rootBeanDefinition(BookServiceImpl2.class).getBeanDefinition();
    6. registry.registerBeanDefinition("bookService",beanDefinition);
    7. }
    8. }

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

    上述七种方式都是在容器初始化过程中进行bean的加载或者声明,但是这里有一个bug。这么多种方式,它们之间如果有冲突怎么办?谁能有最终裁定权?

    BeanDefinitionRegistryPostProcessor,看名字知道,BeanDefinition意思是bean定义,Registry注册的意思,Post后置,Processor处理器,全称bean定义后处理器,干啥的?在所有bean注册都折腾完后,它把最后一道关,说白了,它说了算,这下消停了,它是最后一个运行的。

    1. public class MyPostProcessor implements BeanDefinitionRegistryPostProcessor {
    2. @Override
    3. public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
    4. BeanDefinition beanDefinition =
    5. BeanDefinitionBuilder.rootBeanDefinition(BookServiceImpl4.class).getBeanDefinition();
    6. registry.registerBeanDefinition("bookService",beanDefinition);
    7. }
    8. }

    Conditional - bean的加载控制

    @ConditionalOnClass -- 注解实现了当虚拟机中加载了com.itheima.bean.Wolf类时加载对应的bean

    1. @Bean
    2. @ConditionalOnClass(name = "com.itheima.bean.Wolf")
    3. public Cat tom(){
    4. return new Cat();
    5. }

    @ConditionalOnMissingClass -- 注解控制虚拟机中没有加载指定的类才加载对应的bean。

    1. @Bean
    2. @ConditionalOnMissingClass("com.itheima.bean.Dog")
    3. public Cat tom(){
    4. return new Cat();
    5. }

    这种条件还可以做并且的逻辑关系,写2个就是2个条件都成立,写多个就是多个条件都成立。

    1. @Bean
    2. @ConditionalOnClass(name = "com.itheima.bean.Wolf")
    3. @ConditionalOnMissingClass("com.itheima.bean.Mouse")
    4. public Cat tom(){
    5. return new Cat();
    6. }

    @ConditionalOnWebApplication -- 判定当前容器环境是否是web环境。

    1. @Bean
    2. @ConditionalOnWebApplication
    3. public Cat tom(){
    4. return new Cat();
    5. }

    @ConditionalOnNotWebApplication -- 判定容器环境是否是非web环境。

    1. @Bean
    2. @ConditionalOnNotWebApplication
    3. public Cat tom(){
    4. return new Cat();
    5. }

    @ConditionalOnBean -- 判定是否加载了指定名称的bean

    1. @Bean
    2. @ConditionalOnBean(name="jerry")
    3. public Cat tom(){
    4. return new Cat();
    5. }

    当Spring容器中包含了id = jerry的bean,才加载

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

    判定当前是否加载了mysql的驱动类,如果加载了,我就给你搞一个Druid的数据源对象出来,完美!

    bean的依赖属性配置管理

    1. cartoon:
    2. cat:
    3. name: "图多盖洛"
    4. age: 5
    5. mouse:
    6. name: "泰菲"
    7. age: 1

    然后定义一个封装属性的专用类,加载配置属性,读取对应前缀相关的属性值。

    1. @ConfigurationProperties(prefix = "cartoon")
    2. @Data
    3. public class CartoonProperties {
    4. private Cat cat;
    5. private Mouse mouse;
    6. }

    最后在使用的位置注入对应的配置即可。

    1. @EnableConfigurationProperties(CartoonProperties.class)
    2. public class CartoonCatAndMouse{
    3. @Autowired
    4. private CartoonProperties cartoonProperties;
    5. }

    建议在业务类上使用@EnableConfigurationProperties声明bean,这样在不使用这个类的时候,也不会无故加载专用的属性配置类CartoonProperties,减少spring管控的资源数量。

    自动配置原理(工作流程)

    • 1.首先指定一个技术X,我们打算让技术X具备自动配置的功能,这个技术X可以是任意功能
    1. public class CartoonCatAndMouse{
    2. }
    • 2.然后找出技术X使用过程中的常用配置Y
    1. cartoon:
    2. cat:
    3. name: "图多盖洛"
    4. age: 5
    5. mouse:
    6. name: "泰菲"
    7. age: 1
    • 3.将常用配置Y设计出对应的yml配置书写格式,然后定义一个属性类封装对应的配置属性
    1. @ConfigurationProperties(prefix = "cartoon")
    2. @Data
    3. public class CartoonProperties {
    4. private Cat cat;
    5. private Mouse mouse;
    6. }
    • 4.最后做一个配置类,当这个类加载的时候就可以初始化对应的功能bean,并且可以加载到对应的配置
    1. @EnableConfigurationProperties(CartoonProperties.class)
    2. public class CartoonCatAndMouse{
    3. private CartoonProperties cartoonProperties;
    4. }
    • 5.当然,你也可以为当前自动配置类设置上激活条件,例如使用@CondtionOn* * * * 为其设置加载条件

    1. @ConditionalOnClass(name="org.springframework.data.redis.core.RedisOperations")
    2. @EnableConfigurationProperties(CartoonProperties.class)
    3. public class CartoonCatAndMouse {
    4. private CartoonProperties cartoonProperties;
    5. }

    这里只是随便举例,当虚拟机中存在RedisOperations类时,加载这个配置类

    • 6.创建spring.factories文件,让springboot启动时加载这个类

    springboot为我们开放了一个配置入口,在配置目录中创建META-INF目录,并创建spring.factories文件,在其中添加设置,说明哪些类要启动自动配置就可以了。

    1. # Auto Configure
    2. org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
    3. com.itheima.bean.CartoonCatAndMouse

    总结:

    1. springboot启动时先加载spring.factories文件中的org.springframework.boot.autoconfigure.EnableAutoConfiguration配置项,将其中配置的所有的类都加载成bean

    2. 在加载bean的时候,bean对应的类定义上都设置有加载条件,因此有可能加载成功,也可能条件检测失败不加载bean

    3. 对于可以正常加载成bean的类,通常会通过@EnableConfigurationProperties注解初始化对应的配置属性类并加载对应的配置

    4. 配置属性类上通常会通过@ConfigurationProperties加载指定前缀的配置,当然这些配置通常都有默认值。如果没有默认值,就强制你必须配置后使用了

  • 相关阅读:
    设计模式之装饰器模式
    ​python 的字符串转为字典​
    水果店圈子:做水果店有多赚钱,开个小型水果店一年收入能赚多少钱
    23、mysql数据库的安装
    夜出行动物
    HTTP协议简介
    基于Uniapp+SpringBoot+Vue的电影交流平台小程序设计与实现(源码+lw+部署文档+讲解等)
    064:vue+openlayers根据坐标来显示点、线段、圆形、多边形(代码示例)
    求一份网页设计结课大作业,要求用到html,css,javascript,的知识
    漏刻有时百度地图API实战开发(5)区域限制移动端鬼畜抖动的解决方案
  • 原文地址:https://blog.csdn.net/qq_33753147/article/details/127602018