springboot关于 bean 如何创建的 8 个方法

156 阅读7分钟

自动配置

bean 的加载方式

  • spring 的 xml 配置文件模板

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans 
           http://www.springframework.org/schema/beans/spring-beans.xsd">
        
        ...
    
    </beans>
    

1. xml + <bean/>

  • 使用方法

    方法一:我们可以在 xml 配置文件中通过<bean id="beanId" class="classPath" /> 来将 classPath路径的类放入 spring 容器当中。

    方法二:我们也可以通过 <bean class="classPath"/>将类放入 spring 容器当中。这种做法会将小驼峰写法的类名作为 id。

    方法三:我们可以通过方法一将第三方类放入 spring 容器当中,例如 druid、tomcat。

<!--xml方式创建 bean 方式一 -->
<bean id="beanID" class="classPath"/>

<!--自动生成id-->
<bean class="classPath"/>


<!--xml方式声明第三方的类-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"/>

2. xml:context + 注解

  • 使用方法

    添加对应的约束xmlns:context="http://www.springframework.org/schema/context"和修改约束xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd",然后通过 <context:component-scan base-package="packageName"/>指定开启包路径下的注解。对应路径下的类就可以通过@Component注解注入 spring 容器当中。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd">
    
    <!--使用组件扫描指定加载 bean 所在的包路径-->
    <context:component-scan base-package="packageName"/>

</beans>
@Component("beanID")
public class Test {
}

3. 配置类 + 扫描 + 注解

在我们使用 @Configuration的时候,会把当前的配置类放入 spring 容器当中。当我们注册配置类的时候,默认将配置类放入 spring 容器当中。

  1. 通过 @Configuration将指定的类放入 spring 容器当中。

    通过 @Configuration将当前类设置为配置类并放入 spring 容器里;使用 @Bean创建要放入 spring 容器中的类的方法,返回类型是要放入的类。

    这个方法可以代替 xml 配置文件。也可以用来放入第三方的 bean。

    @Configuration
    public class ConfigurationClass {
        @Bean
        public DruidDataSource dataSource() {
            DruidDataSource ds = new DruidDataSource();
            return ds;
        }
    }
    
  2. 通过 @ImportResource将其他配置类或 xml 配置文件导入到当前配置类统一管理

    当我们接手的项目是别人的 jar 包或原代码无法修改的情况下,我们如何通过配置类将原代码的某些类放入 spring 容器当中去,通过 @ImportResource 将其他的配置类或配置文件导入到当前配置类里面去。这样子我们就可以通过当前的配置类去管理其他配置类,从而达到整合的目的了。

    @ImportResource({"OtherConfigurationClass.class","applicationContext.xml"})
    public class ConfigurationClass {
        /*
        * 当我们接手的项目是别人的 jar 包或原代码无法修改的情况下,我们如何通过配置类将原代码的某些类放入 spring 容器当中去
        * 我们通过 @ImportResource 将其他的配置类或配置文件导入到当前配置类里面去。
        * 我们就可以通过当前的配置类去管理其他配置类,从而达到整合的目的了。
        * */
    }
    
  3. 通过 @Configuration(proxyBeanMethods = false) 将配置类设置为非代理模式(不使用单列模式)

    proxyBeanMethods = false的时候 ,通过注册配置类获取其中的 spring 容器的 bean 每一个都是新建的。

    @Configuration(proxyBeanMethods = false)
    public class ConfigurationClass {
        @Bean
        public Cat cat() {
    
            return new Cat();
        }
    }
    

4. @Import 导入 bean 的类

当我们在面对一些不需要过多的配置,仅仅只是为了能够通过自动转配获取的类的时候,可以通过 @Import将对应的类放入 spring 容器中。

@Configuration
@Import(OterClass.class)
public class ConfigurationClass {
}

5. AnnotationCofigApplicationContext 调用 registerBean方法

当上下文容器已经初始化完毕,我们可以通过手动将 bean 注入 spring 容器中,指定 id、class 和使用有参构造方法来完成。但是我们必须是使用AnnotationConfigApplicationContext来完成。注意的是,如果要在 bean 里添加参数请使用有参构造的方法。

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ConfigurationClass.class);
// 上下文容器已经初始化完毕,手工加载 bean,指定 id,class 和使用有参构造方法来完成。
context.registerBean("beanID", bean.class, Object... constructorArgs);

6. @Import 导入 ImportSelector 接口

我们通过实现 ImportSelector 接口来创建 bean。这个实现类是一个选择器,通过对配置文件的条件决定装载的bean

image-20220512221631478

package com.hyz.bean.pojo;

import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;

import java.util.Map;

/**
 * @author workplace
 * @date 2022/5/12 15:10
 */
public class MyImportSelector implements ImportSelector {
    /*
     * 实现了 ImportSelector 接口的实现类,可以通过各种条件的判定,判定完毕后,决定是否装载指定的bean
     * 可以实现动态加载 bean
     * */

    @Override
    public String[] selectImports(AnnotationMetadata metadata) {

        // 下面的数组填入要加载到 spring 容器中的类的去路径
        System.out.println("---------------------------");
        // 查看当前作用类的类名
        System.out.println("当前选择器作用的类名:" + metadata.getClassName());
        // 查看当前作用类是否有使用 annotationName 的注解
        System.out.println("当前作用类是否用使用 @Configuration 注解:" + metadata.hasAnnotation("org.springframework.context.annotation.Configuration"));
        // 获取注解的属性
        Map<String, Object> annotationAttributes = metadata.getAnnotationAttributes("org.springframework.context.annotation.ComponentScan");
        System.out.println(annotationAttributes);
        System.out.println("---------------------------");

        // 如果被装载的 bean 有 @Configuration 注解
        boolean b = metadata.hasAnnotation("org.springframework.context.annotation.Configuration");
        if (b) {
            return new String[]{"com.hyz.bean.pojo.Dog"};
        }
        return new String[]{"com.hyz.bean.pojo.Cat"};

//        return new String[]{"com.hyz.bean.pojo.Dog"};
    }
}

当我们完成了实现类之后,可以使用 @Import 来将实现类放入 spring 容器中

@Configuration
@Import({MyImportSelector.class})
public class SpringConfig6 {

}

这样就将实现类里的 bean 放入 spring 容器当中。

这个实现类的最大的作用就是能够通过条件判断选择创建什么 bean。

7. @Import 导入 ImportBeanDefinitionRegistrar 接口

我们通过实现 ImportBeanDefinitionRegistrar 接口来现有的 bean 覆盖,进而达成不修改源代码的情况下更换实现的效果。

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

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        // 可以使用使用元数据去做判定,和 MyImportSelector 一样
        // 使用 bean定义 来选择什么类放入 bean 中。
        BeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(Dog.class).getBeanDefinition();
        // 设置 beanID 为 “狗”。
        registry.registerBeanDefinition("狗", beanDefinition);
    }
}

和 ImportSelect 的步骤一样,在实现类完成之后,通过在配置类上使用@Imorp来将其注入 spring 容器当中。

@Configuration
@Import({MyRegistrar.class})
public class SpringConfig7 {
}

这个实现类的最大作用就是重写,而且也能够实现选择对应的 bean 注入到 spring 容器中的效果。可以说覆盖了一点 ImportSelect 的功能。

8. @Import 导入 BeanDefinitionRegistryPostProcessor 接口

public class MyPostProcessor implements BeanDefinitionRegistryPostProcessor {

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
        // 通过 rootBeanDefinition 来选择最终要注入的 bean 的类
        BeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(BookServiceImpl5.class).getBeanDefinition();
        // 通过 registerBeanDefinition 来选择注入 bean 的 beanID 是什么
        beanDefinitionRegistry.registerBeanDefinition("bookService", beanDefinition);
    }

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

    }
}

在实现类完成之后,通过在配置类上使用@Imorp来将其注入 spring 容器当中。当 @Import 中的 spring 容器里有的 bean 有冲突,全部按照实现类的 bean 来注入。

@Import({BookServiceImpl1.class, MyRegistrar2.class, MyRegistrar3.class, MyPostProcessor.class})
public class SpringConfig8 {
    /*
     * 通过 Import 将类放入 spring 容器中
     * 相同级别的按照顺序覆盖:前被后覆盖。
     *
     * */
}

这个实现方法最大的作用就是对容器中的 bean 的最终裁定。无论如何都是根据 eanDefinitionRegistryPostProcessor 的实现结果来完成。就是说,他也包含了一部分的 ImportBeanDefinitionRegistrar 的功能。

总结

  1. xml 里使用 <bean id = "beanID" calss = "classPath"来将对应的类放入 spring 容器当中去。
  2. xml 里添加约束,开启对应包路径下的注解功能<context:component-scan base-package="packageName"/>,在对应的类上添加@Component("beanId")``@Service("beanId")``@Controller("beanId")``@Repository("beanId") 将其添加到 spring 容器当中。
  3. 通过配置类将类注入到 spring 容器当中。在配置类上添加 @Comfiguration,创建一个返回值为对应类的方法并添加 @Bean注解。
  4. 通过在配置类上添加 @Import({"className.class","className.class"})将多个类变作 bean 注入到 spring 容器当中。
  5. 当容器已经初始话完毕之后,通过 AnnotationConfigApplicationContextregisterBean("beanID", bean.class, Object... constructorArgs)方法可以将 bean 注入到 spring 容器当中。
  6. 通过 ImportSelector 实现类来选择创建哪一个类的 bean。配置类通过 @Import导入 ImportSelector 接口实现类的 bean 放入 spring 容器当中。
  7. 通过 ImportBeanDefinitionRegistrar 实现类的方法将已存在的 bean 覆盖,不存在的 bean 创建,然后通过配置类将 bean 放入 spring 容器当中。
  8. 通过 BeanDefinitionRegistryPostProcessor 实现类的方法对 bean 进行裁定,它是目前位置我知道的最高权限的!!然后通过配置类将实现方法修改过/创建的 bean 放入 spring 容器当中。
  9. 当我们通过 new AnnotationConfigApplicationContext(configuration.class);来将配置类获取上下文的时候,默认将配置类放入 spring 容器当中。所以当我们所有的操作都集中在一个配置类上的时候,那个配置类可以不添加任何的注解也能正常使用。