文章目录

  • 前言
  • bean的8种加载方式
  • 第一种方式 xml配置文件+bean
  • 打印所有的bean
  • 第二种方式 xml:context+注解(@Component+4个@Bean)
  • 第三种方式 配置类+扫描+注解(@Component+4个@Bean)
  • @Bean定义FactoryBean接口
  • @ImportResource
  • @Configuration注解的proxyBeanMethods属性
  • 第四种方式 @Import导入类
  • 第五种方式 AnnotationConfigApplicationContext调用registrer方法
  • 第六种方式 @Import导入ImportSelector
  • 第七种方式 @Import导入实现ImportBeanDefinitionRegistrar
  • 第八种方式 @Import导入BeanDefinitionRegistryPostProcessor


前言

简述bean的几种加载方式

bean的8种加载方式

第一种方式 xml配置文件+bean

  1. 新建模块

spring boot Bean不能被加载 springboot加载bean原理_xml


2. 因为springboot是基于spring所开发的,所以这里引入spring的jar包

<dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.3.22</version>
        </dependency>
    </dependencies>
  1. 配置spring的applicationContext1.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">

    <bean name="dog" class="com.it2.bean.Dog" />
    <bean class="com.it2.bean.Cat" />
</beans>
  1. 编写主类
public class App1 {

    public static void main(String[] args) {
        ApplicationContext context=new ClassPathXmlApplicationContext("applicationContext1.xml");

        System.out.println(context.getBean("dog"));
        System.out.println(context.getBean(Cat.class));

    }
}
  1. 运行项目

打印所有的bean

public class App1 {

    public static void main(String[] args) {
        ApplicationContext context=new ClassPathXmlApplicationContext("applicationContext1.xml");
       String[] names= context.getBeanDefinitionNames();
        for (String name:names){
            System.out.println("name:"+name+"---------type:"+context.getBean(name).getClass());
        }
    }
}

spring boot Bean不能被加载 springboot加载bean原理_spring_02

第二种方式 xml:context+注解(@Component+4个@Bean)

通过注解+扫描包的方式,加载bean,注解可以使用@Component,@Service,@Configuration,@Controller等
例如

@Component("cat123")
public class Cat {
}

配置applicationContext2.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"
       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
        https://www.springframework.org/schema/context/spring-context.xsd">

    <!--指定组件的扫描位置-->
    <context:component-scan base-package="com.it2.bean"/>
</beans>

如何引入第三方的bean?
通过配置的方式,添加Bean,然后配置扫描即可。
添加bean

package com.it2.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

@Component
public class DbConfig {
    @Bean
    public DruidDataSource dataSource(){
        DruidDataSource ds=new DruidDataSource();
        return  ds;
    }
}

配置扫描增加com.it2.config

<!--指定组件的扫描位置-->
<context:component-scan base-package="com.it2.bean,com.it2.config"/>

运行后,可以看到项目所有被加载的bean

public static void main(String[] args) {
        ApplicationContext context=new ClassPathXmlApplicationContext("applicationContext2.xml");
       String[] names= context.getBeanDefinitionNames();
        for (String name:names){
            System.out.println(name);
        }
    }

spring boot Bean不能被加载 springboot加载bean原理_xml_03

第三种方式 配置类+扫描+注解(@Component+4个@Bean)

相较于第二种方式,第三种方式直接使用@CompoentScan替代了applicationContext.xml文件的配置
新增配置,在头部使用@ComponentScan 设定扫描范围

@ComponentScan({"com.it2"})
public class SpringConfig3 {
}

运行app3

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

查看结果,com.it2下面被注解的内容全部被加载了。

spring boot Bean不能被加载 springboot加载bean原理_spring_04

注:

@Configuration配置项如果不用于被扫描可以忽略

@Bean定义FactoryBean接口

@Component
public class DogConfig {
    @Bean
    public Dog dog(){
        return new Dog();
    }

    @Bean
    public DogFactoryBean dog2(){
        return new DogFactoryBean();
    }
}

上面的代码,两个Bean,dog2创建的是Dog对象吗?
DogFactoryBean通过实现FactoryBean来实现Bean的创建。如下例子。

public class DogFactoryBean implements FactoryBean<Dog> {
    public Dog getObject() throws Exception {
        return new Dog();
    }

    public Class<?> getObjectType() {
        return Dog.class;
    }

    /**
     * 是否单例
     * @return
     */
    public boolean isSingleton() {
        return false;
    }
}

运行代码,查看。可以发现DogFactoryBean创建的也是Dog对象。

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

        System.out.println("------------");
        System.out.println(context.getBean("dog"));
        System.out.println("------------");
        System.out.println(context.getBean("dog2"));
    }

spring boot Bean不能被加载 springboot加载bean原理_spring boot_05

通过FactoryBean的方式创建Bean,可以在Bean的初始化时做一些其它事情,比如设置参数,进行参数检查等。

@ImportResource

如何基于旧系统进行二次开发?
通过@ImportResource导入第三方的配置文件,获取到第三方的bean,进行系统集成。

@ImportResource({"applicationContext1.xml"})
public class SpringConfig32 {
}

运行

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

spring boot Bean不能被加载 springboot加载bean原理_System_06

@Configuration注解的proxyBeanMethods属性

proxyBeanMethods 为true和false有什么区别

@Configuration(proxyBeanMethods = true) //proxyBeanMethods默认是true
public class SpringConfig33 {

    @Bean
    public Cat cat(){
        return new Cat();
    }
}

运行时,可以观察到它们是同一对象

spring boot Bean不能被加载 springboot加载bean原理_xml_07

proxyBeanMethods 改为false,再次运行,可以观察到每次获取到的cat对象一样。

spring boot Bean不能被加载 springboot加载bean原理_java_08


这就是两者的不同。

只有proxyBeanMethods =true,并且里面的方法被@Bean标记,才能产生一个代理对象,否则每次都会得到不同的对象。

第四种方式 @Import导入类

使用@Import注解导入需要被注入的bean对应的字节码

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


}

被导入的bean对象不需要做任何声明

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

        System.out.println("--------");
        System.out.println(context.getBean(Dog.class));
    }

运行代码,可以发现容器里包含了被import的对象

spring boot Bean不能被加载 springboot加载bean原理_spring boot_09


此形式可以有效的降低源代码与spring技术的耦合度,在spring技术底层以及很多框架的整合种大量使用

使用@Import注解导入配置类,配置类会被加载,配置类里被@Bean声明的方法也会被加载到容器。

第五种方式 AnnotationConfigApplicationContext调用registrer方法

当系统上下文已经初始化完毕后,如何向容器中注入bean?
使用register注入bean

public static void main(String[] args) {
        AnnotationConfigApplicationContext context=new AnnotationConfigApplicationContext(SpringConfig5.class);
        //上下文容器对象已经初始化完毕后,手工加载bean
        context.registerBean("ddd", Dog.class);

        System.out.println(context.getBean("ddd"));
        context.registerBean("ddd", Dog.class);
        System.out.println(context.getBean("ddd"));

        String[] names= context.getBeanDefinitionNames();
        for (String name:names){
            System.out.println(name);
        }
    }

运行后,可以观察到ddd这个对象,使用了context.registerBean(“ddd”, Dog.class)注入bean后,获取可以get到这个bean,同时再次注入同名的bean,可以看到前面的bean会被覆盖

spring boot Bean不能被加载 springboot加载bean原理_java_10


当然也可以使用类名注册,不指定名称

context.register(Mouse.class);

使用register这种方式,只有AnnotationConfigApplicationContext可以做,其它的ApplicationContext 和ClassPathXmlApplicationContext都不能做。

第六种方式 @Import导入ImportSelector

public class MyImportSelector implements ImportSelector {
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        /**
         * annotationMetadata 注解的元数据,表使用使用@Import(MyImportSelector.class)的类
         * annotation 翻译 注解
         * Metadata 元数据
         */
        System.out.println("--"+annotationMetadata.getClassName());
        System.out.println("--"+annotationMetadata.getAnnotationTypes());
        boolean flag=annotationMetadata.hasAnnotation("org.springframework.context.annotation.Configuration");
        if (flag){
            return new String[]{"com.it2.bean.Cat"};
        }
        return new String[]{"com.it2.bean.Dog"};
    }
}
@Configuration //用来测试的注解
@Import(MyImportSelector.class)
public class SpringConfig6 {
}
public static void main(String[] args) {
        ApplicationContext context=new AnnotationConfigApplicationContext(SpringConfig6.class);
        String[] names= context.getBeanDefinitionNames();
        for (String name:names){
            System.out.println(name);
        }
    }

运行代码,可以看到加载了Cat,MyImportSelector 可以根据注解的导入情况,进行判断,加载不同的bean,这个在Springboot整合其它框架时很常见。

导入实现了ImportSelector接口的类,实现对导入元的编程式处理

spring boot Bean不能被加载 springboot加载bean原理_spring boot_11

第七种方式 @Import导入实现ImportBeanDefinitionRegistrar

public class MyRegistrar implements ImportBeanDefinitionRegistrar {

    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        /**
         * 1. 使用元数据判定
         */

        /**
         * 2. 注入一个BeanDefinition
         */
        BeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(Dog.class).getBeanDefinition();
        registry.registerBeanDefinition("hellodog", beanDefinition);
    }
}
@Import(MyRegistrar.class)
public class SpringConfig7 {
}
public class App7 {
    public static void main(String[] args) {
        ApplicationContext context=new AnnotationConfigApplicationContext(SpringConfig7.class);
        String[] names= context.getBeanDefinitionNames();
        for (String name:names){
            System.out.println(name);
        }
    }
}

打印输出bean,可以看到自己定义的bean被注入到容器

spring boot Bean不能被加载 springboot加载bean原理_java_12


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

同名bean中ImportBeanDefinitionRegistrar后覆盖前,所以多个ImportBeanDefinitionRegistrar 时,最后一个生效。

第八种方式 @Import导入BeanDefinitionRegistryPostProcessor

public class MyPostProcessor implements BeanDefinitionRegistryPostProcessor {

    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
        BeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(BookServiceImpl4.class).getBeanDefinition();
        beanDefinitionRegistry.registerBeanDefinition("bookService", beanDefinition);
    }

    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {

    }
}
public class MyRegistrar implements ImportBeanDefinitionRegistrar {

    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        /**
         * 1. 使用元数据判定
         */

        /**
         * 2. 注入一个BeanDefinition
         */
//        BeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(Dog.class).getBeanDefinition();
//        registry.registerBeanDefinition("hellodog", beanDefinition);
        BeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(BookServiceImpl3.class).getBeanDefinition();
        registry.registerBeanDefinition("bookService", beanDefinition);
    }
}
@Import({BookServiceImpl1.class, MyPostProcessor.class, MyRegistrar.class})
public class SpringConfig8 {
}
public static void main(String[] args) {
        ApplicationContext context=new AnnotationConfigApplicationContext(SpringConfig8.class);

        BookService bookService= context.getBean("bookService",BookService.class);
        bookService.query();
    }

运行代码,可以发现bookService 的实现类时BookServiceImpl4,

spring boot Bean不能被加载 springboot加载bean原理_xml_13


当我们将SpringConfig8修改,更改MyPostProcessor和MyRegistrar的位置,运行结果依然是BookServiceImpl4

spring boot Bean不能被加载 springboot加载bean原理_xml_13

因此使用BeanDefinitionRegistryPostProcessor来定义bean,可以保证一锤定音,可以保证bean不会被替换。