什么是容器
容器,容纳万物之器具,盛水的容器叫杯子,那么Spring的容器是什么,用来容纳什么?Spring容器保存与该框架相关的数据的一个容器就叫做Spring容器,也就是平常理解的bean的存放之处。
为什么要用容器
对Spring有一定学习使用的都知道,Spring里有大量的对象是交予Spring进行管理,也就是常说的控制反转,这些对象交予Spring进行创建后,必定要有一个地方对这些对象进行存放,也就是Spring容器的工作职能所在。
Spring都有哪些容器
以上可能是对Spring的一个简单介绍,但是很多初学者,以及对Spring了解不是很深入的人来说,并不了解容器到底帮我们做了什么,也并不知道容器是如何对各种Bean进行管理以及使用。对于容器的疑问,先确定Spring都有哪些容器,各自都有什么功能,有什么区别,下面来详细分析一下。容器的两大家族,BeanFactory和ApplicationContext,相信不少人都见过,但是其中的用法与原理可能没有太去做深入的了解,我们就从两个维度才剖析Spring容器的奥秘。
BeanFactory和ApplicationContext
介绍BeanFactory之前我们先看一下ApplicationContext,我们先来回顾一下,我们之前哪里用到了它,来看这段代码。
@SpringBootApplication
public class Test {
public static void main(String[] args) {
SpringApplication.run(Test.class, args);
}
}
相信大家都不陌生,这不就是SpringBoot的启动类中的main方法么,但是有没有使用过这一行代码SpringApplication.run(Test.class, args);的返回值做一些特定的操作。它的返回值就是一种容器的实现接口ConfigurableApplicationContext,称作可配置的ApplicationContext容器,看一下类图关系可以发现它是ApplicationContext的一个子接口。由此可以这么猜想,SpringBoot的容器,无非就是在ApplicationContext上附加了自己的功能罢了。
图中可以发现,ApplicationContext又继承了MessageSource、ResourcePatternResolver、ApplicationEventPublisher、EnvironmentCapable四个接口,这些接口就是ApplicationContext容器最关键的四个功能,这四个功能我们后面详细介绍。
再往上看继承关系,会发现最顶层居然是BeanFactory,是的,ApplicationContext也是在上面附加的一种实现,换句话说,BeanFactory才是Spring的核心容器。那它俩有什么区别呢,除了功能上BeanFactory少了一些之外,它加载bean的方式是延迟加载,只有使用bean的时候才会被加载,ApplicationContext则是在容器启动的时候占用一定的内存空间加载好所有的单例Bean(多例Bean还是在使用的时候才会加载),BeanFactory相较于ApplicationContext功能上少了很多,是我们使用的比较原始的容器接口。当我们不用使用ApplicationContext的附加功能,并且系统资源比较少的时候,可以使用更加简洁的BeanFactory作为实现。
BeanFactory功能
查看BeanFactory的方法列表可以看到有这么多的功能,其实把类似的归类一下,就只有简单的几个功能罢了。
- containsBean:验证容器中是否包含参数bean;
- getAliases:返回给定 bean 名称的别名(如果有);
- getBean():根据类、bean名字等参数的组合从容器获取一个bean;
- getType():根据bean的名称返回bean的类型;
还有单例、多例判断等方法,不是太常用可以自行了解。其中使用最频繁的其实只有getBean(),从具体容器中取出需要的bean,做后续的操作。但是,这并代表BeanFactory只有以上图中的功能,我们使用的时候并不会直接使用该接口,而是使用它的各种强大的实现类,我们常说的控制反转、依赖注入、还有bean生命周期相关的操作,都是由它的各种实现类来实现。
DefaultSingletonBeanRegistry
这里举例一个它的实现类DefaultSingletonBeanRegistry,类图中可以看到BeanFactory只是它其中的一个实现,还有很多的功能。先看一下DefaultSingletonBeanRegistry的内容,其中有一个map集合比较关键,所有的单例bean就是在该集合进行存储。
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
由于是私有的参数,我们试着用反射拿一下看看里面的内容。
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
ConfigurableApplicationContext context = SpringApplication.run(Test.class, args);
Field singletonObjects =
DefaultSingletonBeanRegistry.class.getDeclaredField("singletonObjects");
singletonObjects.setAccessible(true);
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
Map<String, Object> map = (Map<String, Object>) singletonObjects.get(beanFactory);
map.forEach((k, v) -> {
System.out.println(k + "=" + v);
});
}
截取一小部门返回结果,因为SpringBoot自带的bean确实是非常多,不然也不会有如此丰富的功能,如果自己新建一个Component注解扫描的bean,在启动后,也可以在控制台中找到。
ApplicationContext
回顾一下上面查看ConfigurableApplicationContext类图的时候,提到的四个关键功能。
- MessageSource:国际化支持;
- ResourcePatterResolver:通配符匹配资源能力,比如根据类路径找到配置文件的位置等;
- ApplicationEventPublisher:事件发布、监听;
- EnvironmentCapable:读取环境信息能力,系统环境变量、yml配置等;
MessageSource
先建立两个配置文件,分别为“messages_en.properties”和“messages_zh.properties”内容分别为“hi=Hello”,“hi=你好”,说到这里,应该大致了解它的使用目的了,接着往下看。
System.out.println(context.getMessage("hi",null, Locale.CHINA));
System.out.println(context.getMessage("hi",null, Locale.ENGLISH));
使用容器的getMessage()方法,根据不同国家的语言参数,获取“hi”对应的语言,可以看到打印结果将上面两个文件中的对应值打印了出来,这就是国际化支持的一个基本使用。在web应用程序中,具体语言的参数会通过请求头的方式发送过来,然后进行逐一的支持。
ResourcePatterResolver
Resource[] resources = context.getResources("classpath:message_zh.properties");
for (Resource resource : resources) {
System.out.println(resource);
}
上面所说的通配符,就是指classpath这样,这个代表从类路径下找到具体文件,上面测试国际化支持的时候创建过message_zh.properties文件,这里直接用来测试获取,具体返回结果如图,找到了该文件路径信息。如果有多个同名文件,我们看到返回值是数组形式,相应的也会返回多个文件信息。
EnvironmentCapable
System.out.println(context.getEnvironment().getProperty("java_home"));
System.out.println(context.getEnvironment().getProperty("server.port"));
这两行获取的分别是jdk的环境变量信息,配置文件中server.prot配置信息。
ApplicationEventPublisher、
这个接口支持了事件的发送与监听,由于事件的概念比较多,这里不再多做赘述,不太了解的伙伴可以先了解一下Spring的事件相关知识,这里先做一个简单测试。
创建事件类,继承ApplicationEvent,构造方法中传入事件的发起者。
public class MyRegisteredEvent extends ApplicationEvent {
public MyRegisteredEvent(Object source){
super(source);
}
}
监听事件,Spring为我们提供了EventListener注解,标注该方法为监听方法,监听的事件就是上面创建的MyRegisteredEvent事件,这里做一个简单的打印。
@EventListener
public void event(MyRegisteredEvent event) {
System.out.println("event:" + event);
}
发送事件,并传入发件源。
context.publishEvent(new MyRegisteredEvent(context));
输出结果