首页 > 其他分享 >1_关于BeanFactory与ApplicationContext的联系和区别

1_关于BeanFactory与ApplicationContext的联系和区别

时间:2024-06-08 17:14:07浏览次数:16  
标签:ApplicationContext beanFactory 区别 public bean BeanFactory context class

BeanFactory与ApplicationContext

1. 容器和接口

1.1 BeanFactory与ApplicationContext的联系与区别:

ConfigurableApplicationContext 是 ApplicationContext的子接口,而ApplicationContext接口又是 BeanFactory 的子接口。因此ConfigurableApplicationContext 接口简介继承了 BeanFactory 的接口。注意,BeanFactory 才是Spring 容器的核心实现!而ApplicationContext只是组合了BeanFactory的功能。例如 ApplicationContext 中的 getBean() 方法的就是首先获取到 BeanFactory,然后再调用中的 BeanFactory.getBean()方法

@Override
public Object getBean(String name) throws BeansException {
    this.assertBeanFactoryActive();
    return this.getBeanFactory().getBean(name);
}

编写一段代码:

@SpringBootApplication
public class A01Application {
    private static final Logger log = LoggerFactory.getLogger(A01Application.class);

    public static void main(String[] args){
        ConfigurableApplicationContext context = SpringApplication.run(A01Application.class, args);
        System.out.println(context);	// 在此处打断点调试
    }
}

image-20240608120353186

就会发现,ApplicationContext对象其实包含了一个BeanFactory对象,自然也就包含了BeanFactory中定义的核心方法。

1.2 BeanFactory能做什么事情?

查看BeanFactory对外提供了哪些方法?(ctrl+f12)

image-20240608120749434

在 BeanFactory 接口中定义了许多获取bean的方法,表面上只有 getBean(), 实际上控制反转,依赖注入,bean的生命周期管理以及功能都是由 BeanFactory 的实现类提供。BeanFactory 的真正实现类就是 DefaultListableBeanFactory。其中 DefaultListableBeanFactory 可以管理管理单例对象,而对单例对象的管理是在它的父类 DefaultSingletonBeanRegistry 中实现的。

该类不仅仅实现了对bean的单例管理,还有循环依赖的解决,以及三级缓存的处理。在后面依次分析。

手动编写两个组件 Component1和Component2:

@Component
public class Component1 { }

package com.cherry.a01;

//..............

@Component
public class Component2 { }

然后通过反射的机制拿到DefaultSingletonBeanRegistry的存放单例的属性,判断这两个组件是否存在:

@SpringBootApplication
public class A01Application {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        ConfigurableApplicationContext context = SpringApplication.run(A01Application.class, args);
        System.out.println(context);

        // 首先获取DefaultSingletonBeanRegistry类中的私有属性singletonObjects,该属性记录了Spring容器中运行时的单例
        Field singletonObjects = DefaultSingletonBeanRegistry.class.getDeclaredField("singletonObjects");
        // 由于是似有属性,要设置该属性可访问
        singletonObjects.setAccessible(true);
        // 获取bean对象
        ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
        Map<String, Object> map = (Map<String, Object>) singletonObjects.get(beanFactory);
        map.entrySet().stream().filter(e -> e.getKey().startsWith("component")).forEach(e -> {
            System.out.println(e.getKey() + "=" + e.getValue());
        });
    }
}
component1=com.cherry.a01.Component1@784abd3e
component2=com.cherry.a01.Component2@36c2b646

1.3 ApplicationContext有哪些扩展功能

首先查看 ApplicationContext 接口的定义:

public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory, MessageSource, ApplicationEventPublisher, ResourcePatternResolver { }

ApplicationContext 继承了如下四个接口:

  1. MessageSource:该类具备处理国际化资源的能力
  2. ResourcePatternResolver:该类具备资源匹配的能力
  3. ApplicationEventPublisher:该类具备发布事件的能力
  4. EnvironmentCapable:该类具备获取系统环境的能力

1.3.1 处理国际资源

首先编写通用语言翻译结果的配置文件 messages.properties

其次依次编写不同的语言翻译配置文件,以及写入如下信息:

  • messahes_en.proeprties ---> hi=hello
  • messages_zh.properties ---> hi=你好
  • messages_ja.properties ---> hi=こんにちは

编写代码使用 ApplicationContext 对象进行翻译:

@SpringBootApplication
public class A01Application {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        ConfigurableApplicationContext context = SpringApplication.run(A01Application.class, args);
        System.out.println(context.getMessage("hi", null, Locale.CHINA));
        System.out.println(context.getMessage("hi", null, Locale.ENGLISH));
        System.out.println(context.getMessage("hi", null, Locale.JAPAN));
    }
}

1.3.2 处理资源

该类主要用于获取各种资源,例如下面的获取resources下的application的配置文件:

@SpringBootApplication
public class A01Application {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException {
        ConfigurableApplicationContext context = SpringApplication.run(A01Application.class, args);
        Resource[] resources = context.getResources("classpath*:/application.properties");
        for(Resource res:resources){
            System.out.println(res);
        }
    }
}
file [/home/yihaoshen/Spring-Project/show/target/classes/application.properties]

1.3.3 获取当前系统资源(配置信息)

编写如下代码获取当前JDK版本以及当前服务器端口号:

@SpringBootApplication
public class A01Application {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException {
        ConfigurableApplicationContext context = SpringApplication.run(A01Application.class, args);
        System.out.println(context.getEnvironment().getProperty("JAVA_HOME"));
        System.out.println(context.getEnvironment().getProperty("server.port"));
    }
}
/opt/development_tools/jdk21/jdk-21.0.3
9000

1.3.4 发布事件(最大作用就是实现组件或功能的解偶!)

首先编写一个事件:

public class UserRegisterEvent extends ApplicationEvent {
    // 参数:事件源(谁发布的事件)
    public UserRegisterEvent(Object source) {
        super(source);
    }
}

其次再编写一个事件监听器(用于处理事件)

@Component
public class Component2 {
    // 编写一个监听器,用于接收事件,其参数为发布事件的类型
    @EventListener	//该注解表明该方法专门用于监听事件
    public void receive(UserRegisterEvent event){
        System.out.println(event.toString());
    }
}

在ApplicationContext中使用发布事件的功能:

@SpringBootApplication
public class A01Application {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException {
        ConfigurableApplicationContext context = SpringApplication.run(A01Application.class, args);
        context.publishEvent(new UserRegisterEvent(context));
    }
}
com.cherry.a01.UserRegisterEvent[source=org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@7ef82753, started on Sat Jun 08 13:07:15 CST 2024]

2. 容器实现

2.1 BeanFactory 实现的特点

由前面可知,BeanFactory 的默认实现是DefaultListableBeanFactory,我们可以在BeanFactory中添加一些bean的定义,然后BeanFactory根据Bean的定义创建Bean对象。

关于Bean的定义,包含如下部分:

  1. Bean的类型(class)
  2. 该Bean的作用域(scope),单例还是多例
  3. 该Bean有无初始化方法,有无销毁方法等

首先编写Bean1, Bean2:

public class Bean1 {
    public Bean1(){
        System.out.println("构造 Bean1()");
    }

    @Autowired
    private Bean2 bean2;

    public Bean2 getBean2(){
        System.out.println("Get Bean2对象");
        return bean2;
    }
}

// ===============

public class Bean2 {
    public Bean2(){
        System.out.println("构造 Bean2()");
    }
}

编写配置类:

@Configuration
public class Config {

    @Bean
    public Bean1 bean1(){
        return new Bean1;
    }

    @Bean
    public Bean2 bean2(){
        return new Bean2;
    }
}

首先创建一个关于Config类的BeanDefinition对象, 并设置为单例模式:

AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(Config.class.getName()).setScope("singleton").getBeanDefinition();

其次将创建好的而BeanDefinition对象加入到beanFactory中,并为该beanDefineition命名:

beanFactory.registerBeanDefinition("config", beanDefinition);

完整代码如下:

@SpringBootApplication
public class TestBeanFactory {
    public static void main(String[] args) {
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        // 为beanFactory添加一些bean的定义,然后beanFactory根据定义创建bean对象
        // 创建BeanDefinition并设置为单例
        AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(Config.class.getName()).setScope("singleton").getBeanDefinition();
        //将beanDefinition注册到beanFactory中
        beanFactory.registerBeanDefinition("config", beanDefinition);
        //此时beanFactory中已经有了config bean
        //查看当前beanFactory中有哪些beanDefinition
        String[] beanNames = beanFactory.getBeanDefinitionNames();
        for(String beanName:beanNames){
            System.out.println(beanName);
        }
    }
}

输出结果为:

config

此时我们发现,命名我们已经在Config类里面已经为bean1和bean2两个属性加上了@Bean注解,但是并没有将该两者的对象加入到容器中,换句话说,容器中并没有包含对@Bean注解标记的 BeanDefinition,由此看出 BeanFactory缺少了注解解析的能力。

此时就需要我们为beanFactory加入一些后处理器来实现支持对其它注解的解析的能力:

AnnotationConfigUtils.registerAnnotationConfigProcessors(beanFactory);

此时再来查看当前容器中包含哪些beanDefine:

config
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor //解析2Autowired注解
org.springframework.context.annotation.internalCommonAnnotationProcessor	//解析@Resource注解
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory

此时我们还没找到Bean1和Bean2这两个bean, 原因是我们虽然加入了上面这些处理更多功能的后处理器,但没有执行后处理器,只有执行后处理器才能呢过真正的获取到bean对象,完整代码如下:

@SpringBootApplication
public class TestBeanFactory {
    public static void main(String[] args) {
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        // 为beanFactory添加一些bean的定义,然后beanFactory根据定义创建bean对象
        // 创建BeanDefinition并设置为单例
        AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(Config.class.getName()).setScope("singleton").getBeanDefinition();
        //将beanDefinition注册到beanFactory中
        beanFactory.registerBeanDefinition("config", beanDefinition);
        //此时beanFactory中已经有了config bean
        //查看当前beanFactory中有哪些beanDefinition

        // 该方法为beanFactory添加一个后处理器,后处理器可以扩展beanFactory的功能,
        // 该处理器可以帮助beanFactory处理更多的注解信息(当然包括@Bean)
        AnnotationConfigUtils.registerAnnotationConfigProcessors(beanFactory);

        // 执行beanFactory后处理器
        beanFactory.getBeansOfType(BeanFactoryPostProcessor.class).values().stream().forEach(beanFactoryPostProcessor -> {
            beanFactoryPostProcessor.postProcessBeanFactory(beanFactory);
        });

        String[] beanNames = beanFactory.getBeanDefinitionNames();
        for(String beanName:beanNames){
            System.out.println(beanName);
        }
    }
}

运行结果如下:

config
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
bean1
bean2

此时判断bean1对象能否正常使用:

Bean1 bean1 = beanFactory.getBean(Bean1.class);
System.out.println(bean1.getBean2());
bean1
bean2
构造 Bean1()
Get Bean2对象
null

此时发现bean1.getBean2()获取bean2对象竟然为空,这是为什么?

@Autowired
private Bean2 bean2;

原因在于@Autowired注解并未没解析处理,因为2Autowired注解解析是由bean的后处理器完成的,并不是beanFactory的后处理器完成的。因此我们需要添加对@Autowired注解解析的bean后处理器,完整代码如下:

@SpringBootApplication
public class TestBeanFactory {
    public static void main(String[] args) {
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        // 为beanFactory添加一些bean的定义,然后beanFactory根据定义创建bean对象
        // 创建BeanDefinition并设置为单例
        AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(Config.class.getName()).setScope("singleton").getBeanDefinition();
        //将beanDefinition注册到beanFactory中
        beanFactory.registerBeanDefinition("config", beanDefinition);
        //此时beanFactory中已经有了config bean
        //查看当前beanFactory中有哪些beanDefinition

        // 该方法为beanFactory添加一个后处理器,后处理器可以扩展beanFactory的功能,
        // 该处理器可以帮助beanFactory处理更多的注解信息(当然包括@Bean)
        AnnotationConfigUtils.registerAnnotationConfigProcessors(beanFactory);

        // beanFactory 后处理器,补充一些对bean的定义
        beanFactory.getBeansOfType(BeanFactoryPostProcessor.class).values().forEach(beanFactoryPostProcessor -> {
            beanFactoryPostProcessor.postProcessBeanFactory(beanFactory);
        });

       // bean 后处理器:针对bean的生命周期各个阶段来扩展bean的功能,例如@Autowired解析,@Resource的解析等
        beanFactory.getBeansOfType(BeanPostProcessor.class).values().forEach(beanFactory::addBeanPostProcessor);

        String[] beanNames = beanFactory.getBeanDefinitionNames();
        for(String beanName:beanNames){
            System.out.println(beanName);
        }

        Bean1 bean1 = beanFactory.getBean(Bean1.class);
        System.out.println(bean1.getBean2());
    }
}

此时发现bean1对象可以正常使用了!

config
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
bean1
bean2
构造 Bean1()
构造 Bean2()
Get Bean2对象
com.cherry.a02.Bean2@45752059

此外我们还发现了,容器中只包含了对bean的定义,只有需要使用某一个bean的时候没,才会根据bean定义创建bean对象供给调用者使用!当然如果我们希望在使用bean对象之前就创建好bean对象,我么可以调用如下这个方法:

beanFactory.preInstantiateSingletons();	// 提前准备好单例

关于BeanFactory的总结:

  1. 不会主动调用BeanFactory的后处理器
  2. 不会主动添加Bean的后处理器
  3. 不会主动初始化单例对象,只有需要用到的时候才会实例化所需要的Bean对象

关于注解排序问题:

如果我们在一个属性或方法上添加了多个注解,那么注解解析是怎么样的?

此时的解析顺序就和后处理器的添加顺序有关,谁先加入了后处理器,哪个后处理器的优先级就高,谁就先做注解的解析。

2.2 ApplicationContext 的常见实现和用法

关于ApplicationContext接口,有如下4个经典的实现:

  1. ClassPathXmlApplicationContext:基于classpath下的xml格式的Spring配置文件
  2. FileSystemXmlApplicationContext:基于磁盘路径下xml格式的Spring配置文件
  3. AnnotationConfigApplicationContext:基于java的配置类来创建
  4. AnnotationConfigServletWebServerApplicationContext:基于java的配置类来创建,用于WEB环境

标签:ApplicationContext,beanFactory,区别,public,bean,BeanFactory,context,class
From: https://www.cnblogs.com/lilyflower/p/18238758

相关文章

  • mysql阶段03 mysql多实例, 数据库主从, mysql5.6和5.7区别, 用户管理, 权限管理
    一、mysql的多实例nginx多实例,就是配置多个配置文件mysql多实例:1.有多个配置文件2.多端口3.多个socket文件4.多个日志文件5.多个server_id1.创建多实例存放目录之前数据库已安装在/usr/local/mysql下[root@db03~]#mkdir/usr/local/{3307,3308,3309}-p2.配置......
  • computed(计算属性)和watch(侦听属性)的区别
    1.computed计算属性调用才会执行,有返回值watch侦听属性不需要调用,只要侦听的数据发生改变就会执行2.computed计算属性能够完成的操作watch侦听属性都可以完成,但是watch侦听属性能够完成的操作computed不能全部完成,比如异步操作,computed内部不能执行异步操作,watch内部可以执......
  • 在计算机论文中suppose suggest assume 用法上的区别
    ChatGPT3.5的答案:在计算机论文中,"suppose,""suggest,"和"assume"有不同的用法和含义。它们在表达假设、建议和假定时具有不同的语气和语境。以下是它们的区别和示例:Suppose定义:假设某种情况或前提,通常用于讨论或推理。用法:假设情景:"Supposeweuseamoreefficie......
  • GET 和 POST 的区别
    根据技术规格文档,GET和POST最大的区别是语义。区别一:幂等性(重复操作不改变结果)由于GET是读,POST是写,所以GET是幂等的,POST不是幂等的。由于GET是读,POST是写,所以用浏览器打开网页会发送GET请求,想要POST打开网页要用form标签。由于GET是读,POST是写,所以GE......
  • Arduino UNO和Nano的区别
    ArduinoUNO和ArduinoNANO的区别ATmega328pUNO对于用过Arduino开发板的小伙伴来说应该是比较熟悉的了,主要说说NANO,UNO和NANO控制芯片同样用的ATmega328p,不同的是芯片封装不同,UNO是DIP28封装NANO是TQFP32封装(NANO比UNO多了4个引脚出来)图Arduino Uno图Arduino N......
  • 在我的 ngrx 效果中,switchMap 和其他运算符的区别
    我有以下效果publicSetProperTab$=createEffect(publicSetProperTab$=createEffect(()=>{返回this.actions$.pipe(ofType(actions.SetProperTab)、switchMap((action)=>;this.store$.select(selectors.GetHasLogicalPr......
  • 机械硬盘和SSD有什么区别?
    机械硬盘(HDD)和固态硬盘(SSD)在多个方面存在显著差异: 1.**工作原理**:机械硬盘采用传统的磁性存储技术,通过旋转磁盘和读写头之间的物理接触来读写数据。而固态硬盘则采用半导体存储技术,通过电子信号的传输来读写数据。2.**读写速度**:由于工作原理的不同,固态硬盘的读写速度通......
  • Redis(事务、持久化、高可用 、高可扩、过期删除、内存淘汰)说明、分析、区别
    Redis高级理解Redis事务机制掌握Redis持久化机制理解Redis高可用—主从复制、哨兵模式理解Redis高可扩—RedisCluster数据分片掌握Redis过期删除策略掌握Redis内存淘汰策略1事务机制1.1场景分析以关注为例,在B站上程序员关注了A,同时A也关注了程序员,那么......
  • 在Linux中,BASH 和 DOS之间的区别是什么?
    BASH(BourneAgainSHell)和DOS(DiskOperatingSystem)之间存在显著的区别,这些差异不仅体现在它们的设计哲学、功能特性上,也反映在它们所服务的操作系统环境及其用途上。以下是一些主要的区别:性质和定位:BASH:是一种命令行解释器(shell),它是用户与Linux或其他类UNIX操作系统交互......
  • EFCore和EF6的使用和区别
    1、Sqlserver是微软的亲儿子,很少会报错,EF报错问题最多出现在Mysql。以下使用mysql为例子C#一般很形象,带Core的都是.NetCore平台的。所以在.NetFramework平台我们使用EF6,也就是EntityFramework6在.NetCore平台使用EFCore2、直接上代码(.Net6)internalclassProgram{......