spring源码学习 四
前言
这一篇文章我们来了解一下常见的Bean后处理器,还记得它是干嘛的吗?前面的文章很高频地提到它,它是给Bean地各个生命周期前后提供扩展功能的组件,一起来学习它是怎么工作的吧。
一、常见的Bean后处理器
我们先准备好一个容器,这个容器我们之前没有见过,叫GenericApplicationContext
它是一个干净的ApplicationContext容器,什么叫干净?
它里面没什么东西,也不会帮我们添加Bean后处理器,这样的话,我们就可以手动去添加我们想要的Bean后处理器,可以直观的看到添加的Bean后处理器有什么效果。像我们之前学的AnnotationConfigApplicationContext
它是会自动帮我们装配Bean后处理器的,我们用它的话就看不到效果啦。
那可不可以用BeanFactory呢,它也是干净的,不会帮我们添加Bean后处理器。这是可以的,但是GenericApplicationContext
有refresh方法,它会帮我们调用Bean工厂后处理器,且会自动执行我们手动加的Bean后处理器,显然BeanFactory做不到,不仅要手动添加,还要手动调用,没那么方便。
首先我们来看看main方法:
public class BeanProcessorLearn {
public static void main(String[] args) {
//新建的“干净的容器”
GenericApplicationContext context = new GenericApplicationContext();
//注册三个Bean
context.registerBean("bean1", Bean1.class);
context.registerBean("bean2", Bean2.class);
context.registerBean("bean3", Bean3.class);
//初始化容器
context.refresh();
//销毁
context.close();
}
}
主要做的是往干净的容器里添加三个Bean,我们来看看这三个Bean做了什么事:
public class Bean2 {
}
public class Bean3 {
}
Bean2和Bean3什么事也没做,只是一个刚定义好的类。
public class Bean1 {
private Bean2 bean2;
@Autowired
public void setBean2(Bean2 bean2) {
System.out.println("@Autowire 生效:"+bean2);
this.bean2 = bean2;
}
private Bean3 bean3;
@Resource
public void setBean3(Bean3 bean3) {
System.out.println("@Resource 生效:"+bean3);
this.bean3 = bean3;
}
private String home;
@Autowired
public void setHome(@Value("${JAVA_HOME}") String home){
System.out.println("@Value 生效:"+home);
this.home=home;
}
@PostConstruct
public void init(){
System.out.println("@PostConstruct 生效");
}
@PreDestroy
public void destroy(){
System.out.println("@PreDestroy 生效");
}
@Override
public String toString() {
return "Bean1{" +
"bean2=" + bean2 +
", bean3=" + bean3 +
", home='" + home + '\'' +
'}';
}
}
Bean1做的事就多了,但是不要害怕,我们稍微看一下就可以理解,首先是@Autowired和@Resource注解来注入Bean2和Bean3,待会我们要测试哪个Bean后处理器解析这两个注解的。
然后是通过@Autowired注入@Value(“${JAVA_HOME}”) 的String 环境变量,这样做是为了可以打印一些信息,看看@Value是谁来处理的,如果直接属性注入就做不到打印这个动作,接下来是@PostConstruct初始化方法和@PreDestroy销毁方法,同样要看看是谁处理这两个注解的,最后重写了toString方法。
现在我们执行一下main方法,会发现什么也没有。
这说明那些注解都没有被解析。
现在我们在调用refresh方法前,往容器里添加一个Bean后处理器:
context.registerBean(AutowiredAnnotationBeanPostProcessor.class);//解析@Autowire @Value
AutowiredAnnotationBeanPostProcessor
我们看看它是不是解析@Autowire和@Value,但是在这之前我们要再多一个动作:
context.getDefaultListableBeanFactory().setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());//@Value注解属性的解析
这一步可以先不管,它是为了可以解析@Value里属性"${JAVA_HOME}"的值,才能注入。我们运行一下:
可以看到,这两个注解确实生效了。
现在我们再加点料:
context.registerBean(CommonAnnotationBeanPostProcessor.class);//解析@Resource @PostConstruct @PreDestroy
它是来解析解析@Resource @PostConstruct @PreDestroy这三的,运行一下:
没有问题。
完整代码:
public static void main(String[] args) {
//新建的“干净的容器”
GenericApplicationContext context = new GenericApplicationContext();
//注册三个Bean
context.registerBean("bean1", Bean1.class);
context.registerBean("bean2", Bean2.class);
context.registerBean("bean3", Bean3.class);
context.getDefaultListableBeanFactory().setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());//@Value注解属性的解析
context.registerBean(AutowiredAnnotationBeanPostProcessor.class);//解析@Autowire @Value
context.registerBean(CommonAnnotationBeanPostProcessor.class);//解析@Resource @PostConstruct @PreDestroy
//初始化容器
context.refresh();
//销毁
context.close();
}
感兴趣的同学可以去搜一搜@ConfigurationProperties
这个注解是哪个Bean处理器解析的,它是SpringBoot的注解,用来注入配置文件中值到类属性。
二、@Autowire解析过程
现在我们来看看@Autowire具体是怎么被解析的。
我们只能拿BeanFactory来学习了,因为这样才能看到Bean后处理器工作的流程,刚刚也说到ApplicationContext就算不帮我们加入Bean后处理器,也会在我们加入后自动帮我们执行。
这回,我们直接用Bean后处理的方法,来模拟它的工作过程。
public static void main(String[] args) {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
beanFactory.registerSingleton("bean2",new Bean2());//通过这种方法放入到容器的已经是成品Bean,不会再走创建、依赖注入、初始化
beanFactory.registerSingleton("bean3",new Bean3());
beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());// @Value
AutowiredAnnotationBeanPostProcessor processor = new AutowiredAnnotationBeanPostProcessor();//bean后处理器
processor.setBeanFactory(beanFactory);//设置BeanFactory
Bean1 bean1 = new Bean1();
System.out.println("调用前Bean:"+bean1);
System.out.println("开始调用");
processor.postProcessProperties(null,bean1,"bean1");//手动进行依赖注入
System.out.println("调用后Bean:"+bean1);
}
我们看看main里写了什么,首先创建了一个beanFactory,然后把成品Bean2和Bean3直接放入容器里,因为Bean2和Bean3都是一个最原始的类,没有任何属性和方法,忘记了的同学可以往上翻阅。
然后我们new了一个AutowiredAnnotationBeanPostProcessor
,它就是用来解析@Autowire的Bean后处理器,这里我们需要给它设置一个beanFactory,目的是让它在依赖出入时,能找到需要的Bean,然后我new了一个Bean1,这样手动new出来的Bean是没有走依赖注入和初始化步骤的,我们让处理器去postProcessProperties
,也就是依赖注入,它的第一个参数有兴趣的同学可以去了解一下,第二个参数是需要依赖注入的Bean,第三个参数是Bean的名字。我们来看看执行结果:
可以看到调用前Bean1的属性都是null,注入后Bean2和home有了值,但是bean3没有,这是因为Bean3是标注了@Resource注解,它的Bean后处理器还没出场呢。
@Resource
public void setBean3(Bean3 bean3) {
System.out.println("@Resource 生效:"+bean3);
this.bean3 = bean3;
}
现在我们进入postProcessProperties
的源码看看它是怎么做的:
代码很少,总共有两个步骤:
-
第一步:找到标记了@Autowire注解的方法和属性
-
第二步:执行inject,通过反射来赋值
我们看看这个InjectionMetadata
里有什么内容。
因为findAutowiringMetadata
方法是私有的,所以我们通过反射来调用它看看。
public static void main(String[] args) throws Throwable {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
beanFactory.registerSingleton("bean2",new Bean2());//通过这种方法放入到容器的已经是成品Bean,不会再走创建、依赖注入、初始化
beanFactory.registerSingleton("bean3",new Bean3());
beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());// @Value
AutowiredAnnotationBeanPostProcessor processor = new AutowiredAnnotationBeanPostProcessor();//bean后处理器
processor.setBeanFactory(beanFactory);//设置BeanFactory
Bean1 bean1 = new Bean1();
// System.out.println("调用前Bean:"+bean1);
// System.out.println("开始调用");
// processor.postProcessProperties(null,bean1,"bean1");//手动进行依赖注入
// System.out.println("调用后Bean:"+bean1);
Method findAutowiringMetadata = AutowiredAnnotationBeanPostProcessor.class.getDeclaredMethod("findAutowiringMetadata", String.class, Class.class, PropertyValues.class);
findAutowiringMetadata.setAccessible(true);
InjectionMetadata metadata = (InjectionMetadata)findAutowiringMetadata.invoke(processor, "bean1", Bean1.class, null);
System.out.println(metadata);
}
因为InjectionMetadata是没有重写toString的,所以我们在打印语句加个断点来看看里面有什么东西。
可以看到里面记录了两个AutowiredMethodElement
一个是setBean2方法,另一个是setHome方法。
我们往Bean1里加个@Autowire标注的test1属性,看看里边会不会多出什么东西。
可以看到多出来一个AutowiredFieldElement
。
现在我们调用metadata的inject方法,给bean1注入值,注意要删掉test1属性。
metadata.inject(bean1,"bean1",null);
执行方法后就可以看到bean1成功注入了@Autowire属性。
那inject里做了什么?这里我们做一下模拟,就不读源码了,跨度很大。
首先我们通过反射,可以表示一个类中的属性,我们就拿Bean1里的属性Bean3来举例。
Field bean3 = Bean1.class.getDeclaredField("bean3");
上文我们可以得知,被@Autowire标记的成员变量会被找到,此时会被封装成DependencyDescriptor
,从名字上看,他叫依赖描述器,他可以传入一个Field参数和一个boolean值,Field表示某个属性,boolean表示是否必须。
DependencyDescriptor descriptor=new DependencyDescriptor(bean3,false);
接下来我们通过beanFactory的doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName,
@Nullable Set autowiredBeanNames, @Nullable TypeConverter typeConverter) 方法,把DependencyDescriptor传进去,就可以在Bean容器里找到类型相同的Bean。
Field bean3 = Bean1.class.getDeclaredField("bean3");
DependencyDescriptor descriptor=new DependencyDescriptor(bean3,false);
Object o = beanFactory.doResolveDependency(descriptor, null, null, null);
System.out.println(o);
看看输出结果:
果然找到了Bean3。
找到需要的Bean就很简单了,通过反射就可完成值的注入。
而set注入呢?做法是很类似的:
Method setBean2 = Bean1.class.getDeclaredMethod("setBean2", Bean2.class);
DependencyDescriptor descriptor2 = new DependencyDescriptor(new MethodParameter(setBean2, 0), false);
Object o1 = beanFactory.doResolveDependency(descriptor2, null, null, null);
System.out.println(o1);
和属性值注入不同的点是,DependencyDescriptor里传的参数不一样,要传一个
MethodParameter
,它是为了定位方法参数的,第一个参数是Method,第二个参数是Method参数的下标,所以有多少个参数就要创建多少个MethodParameter,有多少个MethodParameter就有多少个DependencyDescriptor。
上述两种情况中,如果boolean值传true,就表示必须注入值,如果在Bean容器里找不到对应的类型值,就会抛出异常。
后续章节我们会聊聊doResolveDependency做的事。
三、总结
- Bean后处理器处理对应的注解
-
AutowiredAnnotationBeanPostProcessor:解析@Autowire @Value
-
CommonAnnotationBeanPostProcessor:解析@Resource @PostConstruct @PreDestroy
- @Autowire解析流程
-
查找标记了@Autowire的属性和方法,将其反射信息封装成InjectionMetadata
-
在Bean容器里通过类型找到所需的注入值,再通过反射将值注入