前两个小节已经将了Spring创建bean的预处理工作
- 从缓存中获取bean
- 合并父bean的beanDefinition
- 加载depends-on依赖bean
今天继续讲Spring创建bean的预处理工作,解析方法注入和InstantiationAwareBeanPostProcessor。在14–Spring lookup-method注入和replace-method注入(二)简介了lookup-method注入和replace-method注入,但是这里有一个疑问,如果replace-method方法存在重载的话,Spring如何处理呢?Spring是何时对方法注入进行处理呢?带着这些疑问,我们来看今天的内容。
1.方法注入重载
在分析源码之前,我们还是通过一个简单的例子,来看一下Spring是如何处理replace-method方法重载的。
- 新建bean
package com.lyc.cn.day09.replaceMethod;
/**
* 原始bean
* @author: LiYanChao
* @create: 2018-09-06 00:01
*/
public class OriginalDog {
public void sayHello() {
System.out.println("大家好,我是一只黑色的狗...");
}
public void sayHello(String name) {
System.out.println("大家好,我是一只黑色的狗,我的名字叫" + name);
}
public void sayHello(String name, int age) {
System.out.println("大家好,我是一只黑色的狗,我的名字叫" + name + ",我今年" + age + "岁了!");
}
}
package com.lyc.cn.day09.replaceMethod;
import org.springframework.beans.factory.support.MethodReplacer;
import java.lang.reflect.Method;
import java.util.Arrays;
/**
* 方法替换bean
* @author: LiYanChao
* @create: 2018-09-06 00:02
*/
public class ReplaceDog implements MethodReplacer {
@Override
public Object reimplement(Object obj, Method method, Object[] args) throws Throwable {
System.out.println("大家好,我是一只白色的狗...");
Arrays.stream(args).forEach(str -> System.out.println("参数:" + str));
return obj;
}
}
- 新建day09.xml配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- ====================replace-method属性注入begin==================== -->
<bean id="replaceDog" class="com.lyc.cn.day09.replaceMethod.ReplaceDog"/>
<bean id="originalDog" class="com.lyc.cn.day09.replaceMethod.OriginalDog">
<replaced-method name="sayHello" replacer="replaceDog">
<!--替换方法参数配置-->
<arg-type match="java.lang.String"></arg-type>
<arg-type match="int"></arg-type>
</replaced-method>
</bean>
<!-- ====================replace-method属性注入end==================== -->
</beans>
- 新建MyTest测试类
package com.lyc.cn.day09;
import com.lyc.cn.day09.lookupMethod.Animal;
import com.lyc.cn.day09.lookupMethod.Dog;
import com.lyc.cn.day09.replaceMethod.OriginalDog;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;
/**
* @author: LiYanChao
* @create: 2018-09-08 01:01
*/
public class MyTest {
private XmlBeanFactory xmlBeanFactory;
@Before
public void initXmlBeanFactory() {
System.out.println("========测试方法开始=======\n");
xmlBeanFactory = new XmlBeanFactory(new ClassPathResource("day09.xml"));
}
@After
public void after() {
System.out.println("\n========测试方法结束=======");
}
/**
* 测试replace-method注入
*/
@Test
public void testReplace() {
OriginalDog originalDog = xmlBeanFactory.getBean("originalDog", OriginalDog.class);
originalDog.sayHello("壮壮", 5);
}
}
- 运行测试用例
========测试方法开始=======
大家好,我是一只白色的狗...
参数:壮壮
参数:5
========测试方法结束=======
可以看到在配置文件中通过配置arg-type
指定替换方法参数类型,就达到了区分方法替换时方法重载的问题。
2.Spring对方法注入的处理
打开AbstractBeanFactory的doGetBean方法,继续之前的分析
// 创建单例bean
if (mbd.isSingleton()) {
sharedInstance = getSingleton(beanName, () -> {
try {
// 创建bean
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
// Explicitly remove instance from singleton cache: It might have been put there
// eagerly by the creation process, to allow for circular reference resolution.
// Also remove any beans that received a temporary reference to the bean.
destroySingleton(beanName);
throw ex;
}
});
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
从上面的代码,可以看到,Spring对不同作用域的bean,有不同的创建分支,这里我们只看对单例bean的创建,该创建过程也是最复杂的,分析了单例bean的创建过程,相信大家可以自行分析其他作用域的bean的创建。
这里还用到了jdk8的lambda表达式,不熟悉的同学可以百度,先了解下lambda表达式。
打开getSingleton方法,定位到singletonObject = singletonFactory.getObject();
该方法会调用到AbstractAutowireCapableBeanFactory的createBean方法,打开该方法:
/**
* 这个类的中心方法:创建一个bean实例,填充bean实例、应用后处理器等。
*/
@Override
protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException {
RootBeanDefinition mbdToUse = mbd;
// 确保此时bean类已经被解析,并且在动态解析类不能存储在共享合并bean定义中时克隆bean定义。
Class<?> resolvedClass = resolveBeanClass(mbd, beanName);
if (resolvedClass != null && !mbd.hasBeanClass() && mbd.getBeanClassName() != null) {
mbdToUse = new RootBeanDefinition(mbd);
mbdToUse.setBeanClass(resolvedClass);
}
// ① 准备和验证配置的方法注入
// 注意:这里处理的是replace-method和lookup-method方法注入,而不是@Override注解
try {
mbdToUse.prepareMethodOverrides();
}
catch (BeanDefinitionValidationException ex) {
throw new BeanDefinitionStoreException(mbdToUse.getResourceDescription(),beanName, "Validation of method overrides failed", ex);
}
try {
// ② 让beanPostprocessor有机会返回一个代理而不是目标bean实例。
Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
if (bean != null) {
return bean;
}
}
catch (Throwable ex) {
throw new BeanCreationException(mbdToUse.getResourceDescription(), beanName, "beanPostProcessor before instantiation of bean failed", ex);
}
try {
// ③ 执行bean创建,并返回bean的实例
Object beanInstance = doCreateBean(beanName, mbdToUse, args);
return beanInstance;
}
catch (BeanCreationException | ImplicitlyAppearedSingletonException ex) {
// A previously detected exception with proper bean creation context already,
// or illegal singleton state to be communicated up to DefaultSingletonBeanRegistry.
throw ex;
}
catch (Throwable ex) {
throw new BeanCreationException(mbdToUse.getResourceDescription(), beanName, "Unexpected exception during bean creation", ex);
}
}
该方法的核心作用是创建一个bean实例,填充bean实例、应用后处理器等。此时大家似乎已经接触到了SpringIoC最为核心的东西,然而该方法中并没有任何具体的对bean的创建代码,这里也要给大家介绍一下Spring源码阅读的一个小技巧,Spring代码真正干活的一般都是doXXX,以do开头,所以我们离最想看的代码已经不远了。。。
从代码中标注的①②③等步骤,可以发现①和②是我们要分析的内容,我们打开①处的代码:
/**
* 验证并准备为该bean定义的方法覆盖。检查是否存在具有指定名称的方法。
* Validate and prepare the method overrides defined for this bean.
* Checks for existence of a method with the specified name.
* @throws BeanDefinitionValidationException in case of validation failure
*/
public void prepareMethodOverrides() throws BeanDefinitionValidationException {
// Check that lookup methods exists.
// 检测是否存在方法注入,并循环预处理方法注入
if (hasMethodOverrides()) {
Set<MethodOverride> overrides = getMethodOverrides().getOverrides();
synchronized (overrides) {
for (MethodOverride mo : overrides) {
prepareMethodOverride(mo);
}
}
}
}
代码读到这里,大家可能有疑问,从代码上看明明是处理的方法重载,但是为什么处理的是方法注入呢?而且如果我们在bean里设置几个方法重载的话,hasMethodOverrides()方法返回的是false。如果我们打开AbstractBeanDefinition类的hasMethodOverrides()方法,就能打消我们之前的疑问。
public boolean hasMethodOverrides() {
return (this.methodOverrides != null && !this.methodOverrides.isEmpty());
}
其中methodOverrides是做什么的呢?通过类名AbstractBeanDefinition我们可以发现,该类是BeanDefinition的一个子类,那么它保存的应该是我们解析到的beanDefinition,spring在解析配置文件的时候,如果发现配置了replace-method或者lookup-method那么,就会对应的标签解析,并存入到AbstractBeanDefinition的methodOverrides属性中,那么当bean实例化的时候,如果检测到了methodOverrides属性不为空,则动态的为当前bean生成代理并使用相应的拦截器对bean做处理(实现细节我们会在接下来的章节中讲解),这里大家只要把概念搞清楚即可。
了解了概念之后,我们再来看prepareMethodOverride方法:
/**
* 验证并准备给定的方法覆盖。检查指定名称的方法是否存在,如果没有找到方法,则将其标记为未重载。
*/
protected void prepareMethodOverride(MethodOverride mo) throws BeanDefinitionValidationException {
// 统计注入的方法个数
int count = ClassUtils.getMethodCountForName(getBeanClass(), mo.getMethodName());
// 如果为0,则抛出异常
if (count == 0) {
throw new BeanDefinitionValidationException(
"Invalid method override: no method with name '" + mo.getMethodName() +
"' on class [" + getBeanClassName() + "]");
}
// 如果为1,则将注入方法标记为未重载
// 注意:当有多个重载方法时,为了确定调用哪个具体的方法,Spring对重载方法的参数解析是很复杂的
// 所以,如果注入方法没有被重载这里就将其标记,省去了对方法参数的解析过程,直接调用即可
else if (count == 1) {
// Mark override as not overloaded, to avoid the overhead of arg type checking.
mo.setOverloaded(false);
}
}
spring对方法注入的解析,就讲到这里,大家可以debug代码跟踪调试,需要注意的是count == 1
时Spring的特殊处理,注释已经很详细,不再赘述。