每天进步一点点。不积跬步,无以至千里。
上一讲,我们提到,Spring容器有低级容器和高级容器之分。低级容器不够智能,需要你给它配置很多东西它才能工作,比如你需要告诉它去哪里寻找BeanDefinitions,给它添加一些列的BeanFactoryPostProcessor或者BeanPostProcessor等,它才能具备处理创建bean的相关能力,甚至连@Autowired注解都是通过org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor这个BeanPostProcessor处理后才能注入相应的依赖的。
使用低级容器这么复杂,有那么多模版式的工作需要做,所以Spring提供了高级容器,XXApplicationContext,通过它的refresh方法你就可以看到它为了让低级容器智能起来做了哪些工作了。
我们这一讲就打算自己操纵低级容器,像refresh方法那样配置一下低级容器,让它可用。希望读者尽可能在IDEA中自己实践,不要只看个热闹。
创建一个空的maven工程,在pom.xml中增加如下依赖:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starterartifactId>
<version>2.3.1.RELEASEversion>
dependency>
因为spring-boot-starter依赖会帮我们引入spring-core、spring-beans、spring-context、spring-aop等依赖,这样可以省去我们自己手动添加一个个依赖。
创建一个接口DemoService:
package org.example.service;
public interface DemoService {
String getName();
}
创建一个接口实现类DemoServiceImpl:
package org.example.service.impl;
import org.example.service.DemoService;
public class DemoServiceImpl implements DemoService {
@Override
public String getName() {
return "炒饭";
}
}
创建一个包含main方法的入口类MainClass:
package org.example;
import org.example.service.DemoService;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
public class MainClass {
public static void main(String[] args) {
// Spring低级容器的实现类
DefaultListableBeanFactory defaultListableBeanFactory = new DefaultListableBeanFactory();
// 从容器中获取一个bean
DemoService demoService = defaultListableBeanFactory.getBean(DemoService.class);
// 调用bean的方法
System.out.println(demoService.getName());
}
}
工程的目录结构:

上面准备好了代码,当我们运行MainClass的main方法的时候会报错:
Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.example.service.DemoService' available
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:352)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:343)
at org.example.MainClass.main(MainClass.java:17)
原因是,我们在创建DefaultListableBeanFactory实例defaultListableBeanFactory和从defaultListableBeanFactory里面getBean这两个步骤之间还缺少一些工作没做。
我们想从Spring容器中取出bean的前提是容器中要有这个bean的定义,即这个bean的BeanDefinition,这样Spring容器才能根据这个BeanDefinition创建出你要的bean,让我们增加将bean定义放到Spring容器中的代码:
package org.example;
import org.example.service.DemoService;
import org.example.service.impl.DemoServiceImpl;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition;
public class MainClass {
public static void main(String[] args) {
// Spring低级容器的实现类
DefaultListableBeanFactory defaultListableBeanFactory = new DefaultListableBeanFactory();
// 将BeanDefinition放到容器中
BeanDefinition demoBeanDefinition = new RootBeanDefinition(DemoServiceImpl.class);
defaultListableBeanFactory.registerBeanDefinition("demoService", demoBeanDefinition);
DemoService demoService = defaultListableBeanFactory.getBean(DemoService.class);
System.out.println(demoService.getName());
}
}
再次运行一下main方法,控制台会输出:
[main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'demoService'
炒饭
Process finished with exit code 0
漂亮地从Spring容器中取出了bean,并且成功地调用了bean的方法。
如果我们再增加一些bean,难道要一个一个地为它们创建BeanDefinition,然后一个一个地注册到容器中么?这样效率太低了,Spring已经为我们考虑到了,它提供了一些工具类,可以将bean定义批量自动注册到容器中。让我们以org.springframework.context.annotation.ClassPathBeanDefinitionScanner为例,体验一把,修改后的main方法内部如下:
// Spring低级容器的实现类
DefaultListableBeanFactory defaultListableBeanFactory = new DefaultListableBeanFactory();
// 将BeanDefinition放到容器中,注意这儿的扫描器的构造器将Spring容器传递进去了,这样它才能知道将bean定义注册到哪里
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(defaultListableBeanFactory);
// 扫描指定包下面的bean定义
scanner.scan("org.example");
DemoService demoService = defaultListableBeanFactory.getBean(DemoService.class);
System.out.println(demoService.getName());
再次运行main方法,又报错了:
NoSuchBeanDefinitionException: No qualifying bean of type 'org.example.service.DemoService' available
为什么?因为我们没告诉它,哪些类需要作为bean定义注册到Spring容器中(前面我们是通过代码new RootBeanDefinition(DemoServiceImpl.class)硬编码的方式,指明了那个类要封装成BeanDefinition,所以可以工作),难道你打算把一个包下面的所有类都放到容器中么?难道你定义的XXUtil类、XXDto、XXConstant、XXEnum都是要作为bean放到Spring容器中么?所以,你得标记一下哪些类是bean的定义,注解就是最好的标记,所以我们需要将包下面需要作为bean定义的类打上诸如@Component、@Service、@Configuration标记,这样扫描器才会识别出来,它是bean定义,而不是阿猫阿狗都往容器里扔。
我们将DemoServiceImpl类上面加上@Service(或者@Componenet也行,只是语义上有区别而已)注解再试试,这下运行main方法又能成功完成取出bean,调用bean的方法了。
这里有个重要的知识点,和Spring关系不大,但是却和Java的类加载有关,虽然可以不提,但我还是想提一下。你有没有想过,scanner是如何判断一个类是否要作为bean定义的呢?你可能会说,这还不简单,并给出类似如下代码:
Class<?> clazz = Class.forName("org.example.service.impl.DemoServiceImpl");
Annotation[] declaredAnnotations = clazz.getAnnotations();
for (Annotation annotation : declaredAnnotations) {
if (annotation.getClass() == Service.class || annotation.getClass() == Component.class || ...){
// 说明clazz就是Spring bean定义
}
}
但我想说的是,ClassPathBeanDefinitionScanner并不是这么干的,因为这样有一个问题,Class.forName方法会将一个类装载到Java虚拟机中,如果你发现它不是一个有着@Componenet、@Service等注解的类,你能从Java虚拟机中将它卸载掉么?对不起,JDK中没有提供卸载类的方法,那么相当于加载了一个可能没有用的类到Java虚拟机中了。比如有人新建工程,从别的工程中拷贝了几百上千个类过来,真正用到的就一小部分,难道那些从来没有被引用的类都会被加载到Java虚拟机中么?并不是的,只有被虚拟机中的类用到的类才会被加载,或者你手动用Class.forName加载。
那不能用Class.forName的方式,要怎么办呢?
其实Java类编译后的.clsss文件,你也可以把它当作普通文件看待。我们只需要把它当作文件读取,看看文件的内容中是否有那些注解即可,文件读完发现不符合条件跳过即可,也不会对Java虚拟机造成垃圾。
可以通过org.springframework.context.annotation.ClassPathBeanDefinitionScanner#doScan方法内调用的org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider#findCandidateComponents调用的org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider#scanCandidateComponents调用的org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider#isCandidateComponent方法,看它是如何判断类是否符合条件的,其实就是我上面说的那样。
注意:我们这儿是直接给scanner传入了一个路径,真实项目中都是在配置类上加上@ComponenetScan注解,在注解中写上路径的,如果不写路径,就会采用当前配置类的路径作为包扫描路径,SpringBoot就是这么干的,约定大于配置思想的运用。
好了,让我们更进一步完善我们的功能,假设我们的DemoServiceImpl内部依赖了一个DemoRepository。
DemoRepository类定义如下:
package org.example.repository;
import org.springframework.stereotype.Component;
@Component
public class DemoRepository {
public String getName(){
return "皮拉夫大王";
}
}
DemoServiceImpl修改一下:
package org.example.service.impl;
import org.example.repository.DemoRepository;
import org.example.service.DemoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class DemoServiceImpl implements DemoService {
@Autowired
private DemoRepository demoRepository;
@Override
public String getName() {
return demoRepository.getName();
}
}
让我们再运行一下main方法,又报错了:
15:12:53.080 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'demoServiceImpl'
Exception in thread "main" java.lang.NullPointerException
at org.example.service.impl.DemoServiceImpl.getName(DemoServiceImpl.java:21)
at org.example.MainClass.main(MainClass.java:22)

错误原因是DemoServiceImpl中的demoRepository是null,即没有注入成功。为什么没有注入成功?我们不是加了@Autowired注解了么?低级容器DefaultListableBeanFactory你怎么不听话,为什么不给我注入一个DemoRepository bean!
哈哈,这就是它叫低级容器的原因,很多东西都需要我们去配置,让我们给它再赋能一下,给它一个抓手AutowiredAnnotationBeanPostProcessor,MainClass修改如下:
package org.example;
import org.example.service.DemoService;
import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
/**
* @author pilaf
* @description
* @date 2022-08-24 08:17
**/
public class MainClass {
public static void main(String[] args) {
// Spring低级容器的实现类
DefaultListableBeanFactory defaultListableBeanFactory = new DefaultListableBeanFactory();
// 将BeanDefinition放到容器中
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(defaultListableBeanFactory);
scanner.scan("org.example");
// 给低级容器一个处理@Autowired注解的抓手
AutowiredAnnotationBeanPostProcessor zhuashou = new AutowiredAnnotationBeanPostProcessor();
zhuashou.setBeanFactory(defaultListableBeanFactory);
defaultListableBeanFactory.addBeanPostProcessor(zhuashou);
DemoService demoService = defaultListableBeanFactory.getBean(DemoService.class);
System.out.println(demoService.getName());
}
}
再次运行main方法,控制台完美输出:
[main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'demoServiceImpl'
[main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'demoRepository'
皮拉夫大王
Process finished with exit code 0
Spring高级容器是通过org.springframework.context.annotation.AnnotationConfigUtils#registerAnnotationConfigProcessors方法将AutowiredAnnotationBeanPostProcessor注册到Spring低级容器的。(方法内部有这么一行RootBeanDefinition def = new RootBeanDefinition(AutowiredAnnotationBeanPostProcessor.class);)
bean后置处理器能够在bean实例创建后,初始化方法执行前或初始化方法执行后对bean进行处理。比如处理@Autowired注解的AutowiredAnnotationBeanPostProcessor,它就是bean后置处理器。让我们看看这个熟悉的陌生人都有哪些功能。
先看它的构造器:
public AutowiredAnnotationBeanPostProcessor() {
this.autowiredAnnotationTypes.add(Autowired.class);
this.autowiredAnnotationTypes.add(Value.class);
try {
this.autowiredAnnotationTypes.add((Class<? extends Annotation>)
ClassUtils.forName("javax.inject.Inject", AutowiredAnnotationBeanPostProcessor.class.getClassLoader()));
logger.trace("JSR-330 'javax.inject.Inject' annotation found and supported for autowiring");
}
catch (ClassNotFoundException ex) {
// JSR-330 API not available - simply skip.
}
}
它指明了自己支持的几个注解:@Autowired、@Value还有@javax.inject.Inject,就是说bean字段上的这三个注解都是它处理的。
不知道你看到这里有没有一个冲动,要自己实现一个属于你的Autowired注解和注解处理器,那么来吧。
定义一个自己的@MyAutowired注解:
package org.example.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAutowired {
}
定一个自己的注解处理器:
package org.example.bpp;
import org.example.annotation.MyAutowired;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.config.BeanPostProcessor;
import java.lang.reflect.Field;
public class MyAutowiredBeanPostProcessor implements BeanPostProcessor {
private BeanFactory beanFactory;
public MyAutowiredBeanPostProcessor(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
// 正常情况下,每个bean创建的时候都会走到这儿 ,除非你的bean比这个BeanPostProcessor创建还早
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
// 获取bean的字段
Field[] declaredFields = bean.getClass().getDeclaredFields();
for (Field declaredField : declaredFields) {
// 看看字段是否有@MyAutowired注解
MyAutowired[] annotationsByType = declaredField.getAnnotationsByType(MyAutowired.class);
if (annotationsByType.length > 0) {
// 防止字段是private不允许修改,设置accessible为true就可以了
declaredField.setAccessible(true);
try {
// 反射赋值,从Spring容器中取出你需要的字段类型的bean塞给目标bean
declaredField.set(bean, beanFactory.getBean(declaredField.getType()));
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}
return bean;
}
}
然后将DemoServiceImpl中的@Autowired换成我们的@MyAutowired注解:
package org.example.service.impl;
import org.example.annotation.MyAutowired;
import org.example.repository.DemoRepository;
import org.example.service.DemoService;
import org.springframework.stereotype.Service;
@Service
public class DemoServiceImpl implements DemoService {
// 注意这儿的注解被换成我们自定义的了
@MyAutowired
private DemoRepository demoRepository;
@Override
public String getName() {
return demoRepository.getName();
}
}
再给低级容器一个得力的抓手:
package org.example;
import org.example.bpp.MyAutowiredBeanPostProcessor;
import org.example.service.DemoService;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
public class MainClass {
public static void main(String[] args) {
// Spring低级容器的实现类
DefaultListableBeanFactory defaultListableBeanFactory = new DefaultListableBeanFactory();
// 将BeanDefinition放到容器中
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(defaultListableBeanFactory);
scanner.scan("org.example");
// 给低级容器一个处理@MyAutowired注解的抓手
MyAutowiredBeanPostProcessor zhuashou = new MyAutowiredBeanPostProcessor(defaultListableBeanFactory);
defaultListableBeanFactory.addBeanPostProcessor(zhuashou);
DemoService demoService = defaultListableBeanFactory.getBean(DemoService.class);
System.out.println(demoService.getName());
}
}
好了,运行一下main方法,成功了!控制台输出:
[main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'demoServiceImpl'
[main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'demoRepository'
皮拉夫大王
Disconnected from the target VM, address: '127.0.0.1:51437', transport: 'socket'
Process finished with exit code 0
怎么样,经过这一次DIY的尝试,是不是对Spring容器没那么陌生了,并且有了想进一步了解它的欲望了?没错,我就是希望大家能通过一些自己的尝试,去感受Spring容器,遇到问题就debug,问题解决了,理解就深了,再也不用在面试前,去往上搜“有没有人告诉我Spring bean的生命周期的详细的八股文,面试前需要背”。你多debug几次org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean方法,就可以将一般的面试官按在地上摩擦了。
我们前面说过,生产环境都是通过在配置类上的@ComponentScan注解中使用包路径,指出要扫描的范围。我们下面来体验一下。
增加一个配置bean:
package org.example.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@ComponentScan("org.example")
@Configuration
public class MyConfiguration {
}
修改MainClass:
package org.example;
import org.example.bpp.MyAutowiredBeanPostProcessor;
import org.example.config.MyConfiguration;
import org.example.service.DemoService;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.annotation.ConfigurationClassPostProcessor;
public class MainClass {
public static void main(String[] args) {
// Spring低级容器的实现类
DefaultListableBeanFactory defaultListableBeanFactory = new DefaultListableBeanFactory();
// 必须得先将配置bean的BeanDefinition放到容器中,否则Spring容器去哪儿找@ComponenetScan去。需要从它的配置bean上找
RootBeanDefinition configBeanDef = new RootBeanDefinition(MyConfiguration.class);
defaultListableBeanFactory.registerBeanDefinition("myConfiguration", configBeanDef);
// 配置bean上的@ComponenentScan注解需要ConfigurationClassPostProcessor这个BeanFactory后置处理器去处理,
// 它会将@ComponenetScan注解后面的包路径下的bean定义都扫描注册到Spring容器中
ConfigurationClassPostProcessor configurationClassPostProcessor = new ConfigurationClassPostProcessor();
configurationClassPostProcessor.postProcessBeanDefinitionRegistry(defaultListableBeanFactory);
// 给低级容器一个处理@MyAutowired注解的抓手
MyAutowiredBeanPostProcessor zhuashou = new MyAutowiredBeanPostProcessor(defaultListableBeanFactory);
defaultListableBeanFactory.addBeanPostProcessor(zhuashou);
DemoService demoService = defaultListableBeanFactory.getBean(DemoService.class);
System.out.println(demoService.getName());
}
}
运行main方法,控制台完美输出:
[main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'demoServiceImpl'
[main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'demoRepository'
皮拉夫大王
Process finished with exit code 0
本文通过手动配置低级容器,来实现获取bean、注入bean、将配置bean上面的包中的bean都注册到Spring容器的过程,让我们对Spring容器的原理不再感到神秘,不就是一堆辅助对象来帮忙把我们定义的类抽象成bean定义放到Spring容器中,并在获取bean的时候,按需干预一下嘛。这篇文章用到了Spring两大扩展点,分别是BeanFactory后置处理ConfigurationClassPostProcessor和Bean后置处理器AutowiredAnnotationBeanPostProcessor,前者用于后置处理Spring容器,后者用于后置处理Spring中的Bean。
我们做的这些工作正是各种Spring高级容器中都会做的,比如大名鼎鼎的AbstractApplicationContext#refresh方法中所列出的步骤。SpringBoot应用在Spring高级容器之上又包了一层,让高级容器更强大了,简化了很多配置,开箱即用,但是要想用好,还需要去熟悉它的源码才行。本文试图消除Spring初学者对源码的恐惧,让初学者有信心自己去探索Spring源码。