18--Spring创建Bean的准备工作(三),解析方法注入(方法重载)

版权声明:如有转载,请标明出处,谢谢合作! https://blog.csdn.net/lyc_liyanchao/article/details/82591822

前两个小节已经将了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的特殊处理,注释已经很详细,不再赘述。

猜你喜欢

转载自blog.csdn.net/lyc_liyanchao/article/details/82591822
今日推荐