上一节简单叙述了容器原始接口的相关的基本知识,这一节继续来探讨,容器的那些具体的实现都有哪些,都有什么功能。
DefaultListableBeanFactory
这个实现前面简单的提过,他是BeanFactory最重要的实现,那就从这里继续开始吧。先来看段代码。
public class DemoBeanFactory {
public static void main(String[] args) {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
AbstractBeanDefinition beanDefinition =
BeanDefinitionBuilder.genericBeanDefinition(Config.class).setScope("singleton").getBeanDefinition();
beanFactory.registerBeanDefinition("config", beanDefinition);
String[] beanDefinitionNames = beanFactory.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
System.out.println(beanDefinitionName);
}
}
@Configuration
static class Config {
@Bean
public Bean1 bean1() {return new Bean1();}
@Bean
public Bean2 bean2() {return new Bean2();}
}
static class Bean1 {
public Bean1() {
System.out.println("Bean1构造函数执行");
}
@Autowired
private Bean2 bean2;
public Bean2 getBean2() {
return bean2;
}
}
static class Bean2 {
public Bean2() {
System.out.println("Bean2构造函数执行");
}
}
}
输出结果:
config
分析一下这段代码的含义,每个步骤的作用如下:
- 先创建DefaultListableBeanFactory容器的对象,后面所有的bean实例都是由它来进行创建的;
- 但是创建之前,该容器并不知道谁是bean,需要生成什么样子的bean,这里出现一个新名词,BeanDefinition,翻译一下的意思就是bean定义的意思。正如其名,它内部的各种参数方法来实现对bean的定义。比如,bean是什么类型、scope属性是什么、初始化和销毁方法是什么等等。
- 创建了一个测试bean的类型Config,里面随意写了两个通过@Bean注解的bean对象;
- 通过BeanDefinitionBuilder的genericBeanDefinition方法声明bean类型为Config,并且设置singleton参数表明为单例模式;
- 将该BeanDefinition加入容器中,并指定bean的名字为config,这样启动的时候就会生成该定义下的bean实例;
- 获取容器的所有bean实例,打印验证,结束;
AnnotationConfigUtils
我们看到打印结果为config,好像没什么太大问题,把我们定义好的Config类型bean生成了出来。但是注意,Config里面有两个被注解的bean,好像并没有被创建出来,这好像和我们之前理解的不太一样。原因就是,我们使用的DefaultListableBeanFactory并没有给我们提供解析这些注解的功能,我们使用SpringBoot框架通过SpringApplication的run方法启动的容器已经配置好了该功能,所以我们使用@Bean也好,还是说@Controller、@Service等都可以加入容器中。
原因找到了,模拟其他具备该功能的容器加入该功能即可,我们在获取容器所有bean实例之前,加入如下代码段。这段代码的意思就是在创建好的beanFactory中注册一部分后处理器,通过方法名也可以看到,注册的就是注解配置相关的后处理器。
AnnotationConfigUtils.registerAnnotationConfigProcessors(beanFactory);
加入后,我们再次打印观看结果。
看到除了config之外,多了五个后置处理器bean实例,通过名字不难看出,第一个就是处理@Configuration注解后处理器,第二个是处理@Autowired后处理器,第三个是@Resource相关注解的后处理器,以及后面两个事件监听相关的注解后处理器。
那么问题又来了,这处理注解的处理器都加上了,两个bean怎么还是没有,这点其实有点绕,我们上面那一行代码,只是把处理器加入容器中,但是并没有运行,我们再在刚才那一行代码中加入以下代码片段。这段含义就是,先从容器中根据类型找到BeanFactoryPostProcessor类型的所有bean,我们刚才加入的注解配置相关处理器就是属于BeanFactoryPostProcessor,所以继续便利这些bean,挨个执行每一个后处理器。
BeanFactoryPostProcessor
//获取并循环执行BeanFactoryPostProcessor
beanFactory.getBeansOfType(BeanFactoryPostProcessor.class).values()
.forEach(beanFactoryPostProcessor -> {
beanFactoryPostProcessor.postProcessBeanFactory(beanFactory);
});
继续看执行后的结果
可以看到两个bean被加载到了容器中,我们再次回顾上面的代码,好像没有遗漏什么了。我在Bean1类里面使用@Autowired注解注入了Bean2类型的bean,根据我们加入并执行的后置处理器中可以看到,有用来处理该注解的处理器,那么不出意外是注入没问题的。我们使用下面一段代码打印一下该属性。
Bean2 bean2 = beanFactory.getBean(Bean1.class).getBean2();
System.out.println(bean2);
en。。。又出问题了,bean1是被成功创建了,也执行了构造方法。但是无法从容器中的bean1中获取到成员变量bean2的值,那就是并没有注入成功。
BeanPostProcessor
再看AnnotationConfigUtils.registerAnnotationConfigProcessors(beanFactory);这一行代码。这是我们给beanFactory加入注解相关的后置处理器,为了解析那些加了注解的bean的加载。但是Bean1中的@Autowired并不是类本身的注解,这是它内部一个成员变量的一个注解。所以它需要自身的后置处理器,也就是BeanPostProcessors,即BeanFactoryPostProcessors只管理bean本体。所以它不会留意到Bean1类中的@Autowired。
问题找到了,那就给它加上相关的BeanPostProcessors就好了,其实,这一部分,就在上面那五个之中,第二个第三个分别是解析@Autowired和@Resource的BeanPostProcessors。ok,那我们就照着再写一个获取并循环执行的代码。
//获取并循环添加BeanPostProcessor
Collection<BeanPostProcessor> beanPostProcessors = beanFactory.getBeansOfType(BeanPostProcessor.class).values();
beanFactory.addBeanPostProcessors(beanPostProcessors);
加入后置处理器的方式和BeanFactoryPostProcessors不太一样,这里直接调用容器的添加方法就可以。
这里扩展一个知识点,我们看到运行结果可以看到,只有每次getBean的时候才会调用构造方法进行初始化创建对象,我们加入beanFactory.preInstantiateSingletons();,使单例对象直接创建出来。
后置处理器执行顺序
我们在刚才的类里面接着加入Bean3和Bean4,并通过@Bean注解也加入到容器中,
interface TestInterface{}
static class Bean3 implements TestInterface{}
static class Bean4 implements TestInterface{}
@Autowired
private TestInterface testInterface;//这里注入的是bean3还是bean4
public TestInterface getTestInterface() {
return testInterface;
}
如图中所问,我们注入接口类,因为有两个实现的缘故,那么注入的到底是谁,我们先说结果,程序因分不清要注入谁,会报找到多个的错误。我们之前学过,这时候只要指定名字,或把testInterface改名为bean3或者bean4即可,这里我们先改成bean3,结果就会正确了。
这里我们做个反常态的事情,再加一个@Resource(name = "bean4"),这时候我们调用我们刚加入的getTestInterface()方法,可以获取到实例是bean3。@Resource注解并没有起到什么作用,这就引入了后置处理器加载顺序的一个问题,在底层源码中,@Autowired的解析器在前,所先生效,而且生效后不再走后面的处理器。
我们也可以在上面加入BeanPostProcessor后置处理器那一个环节,自定义顺序来进行控制。这里说一下,beanFactory提供的有一个排序规则getDependencyComparator,是按照每个后置处理器内的一个order参数来控制的,数越小优先级越靠前。这里@Resource的后处理器是比@Autowired的优先级更加靠前。
ApplicationContext
容器基本的功能其实在上面BeanFactory方面已经叙述差不多了,ApplicationContext大题上是一种扩展,主要的实现分为以下几种。
ClassPathXmlApplicationContext和FileSystemXmlApplicationContext
ClassPathXmlApplicationContext在早期使用是很频繁的,直接读取类路径下的xml文件,在xml文件里面配置bean的一系列信息。FileSystemXmlApplicationContext作用和它其实一样,只是读取的目录不同罢了,这个是读取系统文件的目录。
但是他们内部,还是用的BeanFactory的一系列实现,这个在上一篇也提到过,那么它们是怎么运用的呢,如下代码所述,一样的声明一个DefaultListableBeanFactory,然后不同点来了,上面bean的定义是有一个AbstractBeanDefinition来处理,而这里改成了读取类路径下的xml文件。
public static void main(String[] args) {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
reader.loadBeanDefinitions("src/main/resources/bean.xml");
}
AnnotationConfigApplicationContext
由名字可看出,和注解有关系,它就是以注解来进行一系列配置的容器,和上面的串一下,其实它就是底层做了添加各种后置处理器的逻辑。在上面读取xml的两个容器中也有相关的配置,就是我们之前写过的<context:annotation-config/>和<context:component-scan/>标签,这两个标签后者在扫描的过程中也包含了前者的后置处理器的加入。
AnnotationConfigServletWebServerApplicationContext
还是从名字分析,也是一种注解配置的容器,但是多了ServletWebServer,是一种web环境下的容器。
public static void main(String[] args) {
AnnotationConfigServletWebServerApplicationContext context =
new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);
}
@Configuration
static class WebConfig{
//提供能够提供ServletWebServer的组件,这里选用Tomcat的内置组件。
@Bean
public ServletWebServerFactory servletWebServerFactory(){
return new TomcatServletWebServerFactory();
}
//请求入口点,前控制器配置
@Bean
public DispatcherServlet dispatcherServlet(){
return new DispatcherServlet();
}
//将DispatcherServlet注册到Tomcat服务器
@Bean
public DispatcherServletRegistrationBean registrationBean(DispatcherServlet dispatcherServlet){
return new DispatcherServletRegistrationBean(dispatcherServlet,"/");
}
//定义控制器作为测试
@Bean("/hi")
public Controller controller(){
return (request, response) -> {
response.getWriter().println("hi");
return null;
};
}
}
当以上代码运行后,访问localhost:8080/hi就可以获取到一个显示“hi”的界面,整体流程为先确定ServletWebServer提供者,再定义请求入口“/”,即DispatcherServletRegistrationBean传入的参数,当有符合请求时传入到DispatcherServlet做路径分发,访问自定义的控制器。这里是一个web应用的简化,整体流程是这样,但是SpringBoot内部其实在该方面也做了很多的功能上的补充。