spring学习-第一讲-beanFactory与ApplicationContext

BeanFactory与ApplicationContext区别

创建一个springboot项目,对这个函数的main函数来进行执行。

 ConfigurableApplicationContext context = SpringApplication.run(BeanFactoryApplicationContextStudyApplication.class, args);

很明显,ConfigurableApplicationContext对象就是一个ApplicationContext派生的对象。观察一下这个类所对应的类图。

从这个类图中,我们可以很明确的看出来,BeanFactory与ApplicationContext存在派生关系。

那么,在以前面试的时候,我们都扯皮所说的容器,依赖注入,控制反转之类的。其实,beanFactory便实现了最基本的容器功能,依赖注入,控制反转都是后期扩展的功能。而ApplicationContext是内部持有了beanFactory来完成这个操作的(策略模式),下面看一下源码。

BeanFactory实际上是一个接口,看一下内部的方法。

可以发现,其实核心方法就是getBean来获取到容器内部存放的对象,也就是证明了BeanFactory这个接口的目的确实是作为容器存放对象,以及取出对象的功能。

至于ConfigurableApplicationContext这个类,我们进入源码看一下,发现它本身也是一个interface接口,也就是说,这里其实是多态展示其名下的某一个子类。

在run方法里不断进行跳入,发现最终底层的一个容器对象为AnnotationConfigServletWebServerApplicationContext。看看所包含的类图,目前为止,我这面似乎注意到AbstractApplicationContext这个类。

 

点进来看了下,内部持有了一个ApplicationContext的变量parent对象,意思是存在容器也是存在上下层级的吗?那么ApplicationContext采用策略模式所持有的BeanFactory具体是哪个呢?(接着往后学)。

这里卡住了,回归老师所讲的正题。

BeanFactory功能

照老师所讲,这里底层默认持有的BeanFactory类为DefaultListableBeanFactory,去看一下这个类的类图结构。

 

 结合着这趟的类图结构,我好像理解了BeanFactory与ApplicationContext中的关系了。它这面的结构,可以按照装饰者的类图结构来理解。ApplicationContext与BeanFactory的具体实现类中,最上层都是BeanFactory接口,在下方有两类,一个是具体的BeanFactory实现类。另一个则是创建个ApplicationContext接口来对BeanFactory来进行扩展,在ApplicationContext接口下具体实现ApplicationContext实体类,并且持有BeanFactory实体类来对功能进行扩展。个人类图如下:

目前先按照装饰者的思路来理解,应该是对的。

 

那么DefaultListableBeanFactory上方这么多父层面存在,具体实现了容器功能的是哪个呢?是DefaultSingletonBeanRegistry,看看源码。

如果之前背过面试题循环依赖怎么解决,依赖三级缓存之类的,会对这东西有一定的印象。这俩singleonObjects以及earlySingleonObjects,可不就是一级缓存和二级缓存的存放地吗,这就是容器里要存放的对象的位置。

 我们可以试着拿反射来尝试获取一下内部的元素。看着下面这段代码,我上面画的那个类图就完全理解了。

//在 DefaultSingletonBeanRegistry类中获取到singletonObjects对象
Field singletonObjects = DefaultSingletonBeanRegistry.class.getDeclaredField("singletonObjects");
        singletonObjects.setAccessible(true);

//获取到ApplicationContext中的beanFactory对象,从这里可以看出策略模式持有
 ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();

//在beanFactory中得到容器内部的所有元素
Map<String,Object> map = (Map<String, Object>) singletonObjects.get(beanFactory);

//打印最终结果
 map.entrySet().stream().filter(stringObjectEntry -> stringObjectEntry.getKey().equals("messageSource"))
                .forEach(stringObjectEntry -> System.out.println(stringObjectEntry.getKey()+"++++"+stringObjectEntry.getValue()));

注释里写的很明白了,ApplicationContext(最下级类是GenericApplicationContext)中包含一个getBeanFactory方法,我们可以得到其内部持有的beanFactory对象。beanFacotory中的父层面是存在defaultSingleonBeanRegister的,所以它内部的singleObjects也从父层面继承下来,最后获取到了。就是这么个流程。

ApplicationContext功能

从上面我们已经知道了,applicationContext类实质上是在内部依赖了beanFactory对象来完成操作的。那么,除此之外,它还实现了什么额外功能?

回到类图上,我们可以看到,ApplicationContext除了实现BeanFactory系列的接口,还实现了额外的一些接口。分别为MessageSource,ResourcePatternResolver,ApplicationEventPublisher,EnvironmentCapable这四个接口,下面一一介绍一下。

 MessageSource接口

这个接口的功能作用,其实就是我们日常所说的国际化功能。通过在不同配置文件中,配置不同国家的语言,然后在使用过程中来对不同文件中不同语言来进行展示。这块代码整了,但没运行起来,也不算是日常常用的功能,先这么过了。

ResourcePatternResolver接口

ResourcePatternResolver,资源格式解析器,这块其实就是来解析本地资源文件的。如果配置过项目,你是否对一些classpath*:路径,classpath:路径,file:路径这些方式感到眼熟。就像mybatis配置底层的xml访问路径,就是classpath*:路径这种。

classpath:路径1,扫描当前项目下路径1下的所有文件信息。

classpath*:路径2,扫描当前项目下以及底层jar包下的路径2下的所有文件信息

file:路径3,扫描本地磁盘下路径3下的文件信息。

前一段时间看了下网络编程这块吧,书上把这种路径方式称呼为模式:模式特定组成部分。我不确定,网络上的资源路径是否可以通过这种方式来获取呢?后续可以试试,先记一下。

调用方式如下:

 //得到项目路径下的application配置文件(getResource得到的感觉不对,结果感觉还是不对,算了,后续再说)
        Resource[] resource = context.getResources("classpath:application.properties");
        for (Resource resource1 : resource) {
            System.out.println(resource1);
        }

//获取到底层所有starter对应的spring.factories配置文件内信息
        Resource[] resources = context.getResources("classpath*:META-INF/spring.factories");
        for (Resource resource1 : resources) {
            System.out.println(resource1);
        }

EnvironmentCapable接口

EnvironmentCapable,环境能力。这个功能的作用其实就是从配置文件中,或者环境变量中得到相关的配置信息。调用方式如下:

      //通过EnvironmentCapable来获取到相关的环境变量,这玩意是个接口,我暂时没有找到引用到这个接口的内容
        String java_home = context.getEnvironment().getProperty("java_home");
        String port = context.getEnvironment().getProperty("server.port");
        System.out.println("java_home:"+java_home);
        System.out.println("port:"+port);

ApplicationEventPublisher接口

ApplicationEventPublisher,应用事件发布器,类似发布订阅流程,异步处理的方式。我有一个想法,这个东西,和tomcat内部的listener监听器有没有关系,功能如此像。虽然不了解底层实现,但确实应该是一个东西,有个@EventListener注解。就是个异步处理流程,但假设数量过大的情况下,底层是否用的线程池,怎么实现的,需要好好考虑一下。

写个类试试。

package com.bo.beanfactoryapplicationcontextstudy.one;

import org.springframework.context.ApplicationEvent;
import org.springframework.context.annotation.Configuration;

import java.time.Clock;

/**
 * @Auther: zeroB
 * @Date: 2023/1/17 16:39
 * @Description: 事件发布器所使用的应用事件对象
 */
public class UserEvent extends ApplicationEvent {

    public UserEvent(Object source) {
        super(source);
    }

    public UserEvent(Object source, Clock clock) {
        super(source, clock);
    }
}

 

package com.bo.beanfactoryapplicationcontextstudy.one;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;

/**
 * @Auther: zeroB
 * @Date: 2023/1/17 17:00
 * @Description: 事件发布方
 */
@Component
public class Compment1 {

    @Autowired
    public ApplicationEventPublisher applicationEventPublisher;

    public String name = "落";

    public void register(){
        System.out.println("正在注册");
        //将当前对象发送出去
        applicationEventPublisher.publishEvent(new UserEvent(this));
    }

}
package com.bo.beanfactoryapplicationcontextstudy.one;

import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;

/**
 * @Auther: zeroB
 * @Date: 2023/1/17 17:00
 * @Description: 事件消费方
 */
@Component
public class Compment2 {

    @EventListener
    public void consume(UserEvent event){
        //接收到信息,并获取到结果
        System.out.println("发布事件已经被消费");
        Compment1 compment1 = (Compment1)event.getSource();
        System.out.println("接收过来的名称信息"+ compment1.name);
    }

}

在启动类中获取的context对象执行如下语句,确实接收到结果了。

   context.getBean(Compment1.class).register();

依赖着容器对象,如果我后续想用这个功能的话,必须获取到容器对象才能做后续的操作。

 

总结

因为初步学习,所以有很多东西其实并不是很了解,需要自身在后面学习的差不多了,然后再继续巩固。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值