一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第4天,点击查看活动详情。
小付哥手撸Spring学习笔记
作者:小米粥
专栏地址:手撸Spring专栏
一、前言
说书唱戏劝人方,三条大陆走中央。善恶到头终有报,人间正道是沧桑。
您的假期余额已不足,清明小假期,sou~的一下就没有了。
持之以恒的去做一件事,真的是很困难的,昨天我就没有进行更新,偷了一次懒。确实是自己的原因,再想想那些持续输出爆表的博主们,真的觉得他们很不容易。我从小到现在,就是恒心不够,做事总是三分钟热度。但是这次我想挑战一下我的软肋,继续坚持更文。首次撰写技术性文章,有不足之处,请大家多多谅解。
二、正文
书接上回,上一章节我们埋下了一个坑,是一个关于Bean对象在含有构造参数进行实例化的坑。
我们将实例化对象交给容器进行统一处理,但是在实例化的时候并没有处理对象类有入参的情况。我们根据上次的测试方法,把UserService添加一个含入参信息的构造函数,进行测试:
public class UserService {
private String name;
public UserService(String name) {
this.name = name;
}
public void queryUserInfo() {
System.out.println("查询用户信息...");
}
}
报错如下:
Caused by: java.lang.InstantiationException: cn.xiaomizhou.springframework.test.bean.UserService
at java.lang.Class.newInstance(Class.java:427)
at cn.xiaomizhou.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:28)
... 24 more
发生这一现象的主要原因是通过BeanDefinition的class对象去进行实例化的时候,没有考虑构造参数入参的情况。
-
我们先在BeanFactory中添加获取Bean时将构造参数入参信息传递的接口。
public interface BeanFactory { /** * 根据name获取bean对象的接口 * @param name * @return * @throws BeansException */ Object getBean(String name) throws BeansException; Object getBean(String name, Object... args) throws BeansException; } -
Spring一共提供了两种创建含有构造函数Bean对象的方法,实际上使用的就是动态代理,一个jdk默认的动态代理,一个是cglib的动态代理。我们先定义一个实例化的策略接口。
public interface InstantiationStrategy { /** * 实例化策略接口 * * @param beanDefinition bean的定义 * @param beanName bean的名称 * @param ctor 里面包含了一些必要类信息,为了拿到符合入参信息相对应的构造函数 * @param args 入参列表 * @return * @throws BeansException */ Object instantiate(BeanDefinition beanDefinition, String beanName, Constructor ctor, Object[] args) throws BeansException; } -
然后我们使用JDK默认的动态代理,进行实例化策略的具体实现。通过入参信息Constructor判断是否传递参数,然后进行不同的实例化。
public class SimpleInstantiationStrategy implements InstantiationStrategy { /** * JDK实例化 * * @param beanDefinition bean的定义 * @param beanName bean的名称 * @param ctor 里面包含了一些必要类信息,为了拿到符合入参信息相对应的构造函数 * @param args 入参列表 * @return * @throws BeansException */ @Override public Object instantiate(BeanDefinition beanDefinition, String beanName, Constructor ctor, Object[] args) throws BeansException { Class clazz = beanDefinition.getBeanClass(); try { if (null != ctor) { //Constructor不为空,代表是有构造参数实例化 return clazz.getDeclaredConstructor(ctor.getParameterTypes()).newInstance(args); }else { //Constructor为空,无构造参数实例化 return clazz.getDeclaredConstructor().newInstance(); } } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { throw new BeansException("Failed to instantiate [" + clazz.getName() + "]", e); } } } -
我们此次只实现JDK默认的方式,下面我们对实例化Bean的流程进行修改,通过获取Bean的Class对象,然后在获取Bean的构造函数传参,判断Bean的构造函数有没有传参,然后进行实例化。
public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory { private InstantiationStrategy instantiationStrategy = new SimpleInstantiationStrategy(); /** * 根据Bean Name 和 Bean 定义类创建Bean对象 * <p> * 1.通过 BeanDefinition 获取 BeanClass 对象去创建 Bean 对象 * 2.加入到 Bean 对象单例容器中 * 3.返回 Bean 对象 * </p> * @param beanName * @param beanDefinition * @return * @throws BeansException */ @Override protected Object createBean(String beanName, BeanDefinition beanDefinition, Object[] args) throws BeansException { Object bean = null; try { bean = createBeanInstance(beanDefinition, beanName, args); } catch (Exception e) { throw new BeansException("Instantiation of bean failed", e); } addSingleton(beanName, bean); return bean; } /** * 通过实例化进行创建Bean * * @param beanDefinition * @param beanName * @param args * @return */ protected Object createBeanInstance(BeanDefinition beanDefinition, String beanName, Object[] args) { Constructor constructorToUse = null; /** * <P> * 接下来就需要循环比对出构造函数集合与入参信息 args 的匹配情况,这里我们 * 对比的方式比较简单,只是一个数量对比,而实际 Spring 源码中还需要比对入参 * 类型,否则相同数量不同入参类型的情况,就会抛异常了 * </P> */ Class beanClass = beanDefinition.getBeanClass(); Constructor[] declaredConstructors = beanClass.getDeclaredConstructors(); for (Constructor constructor : declaredConstructors) { if (null != args && constructor.getParameterTypes().length == args.length) { constructorToUse = constructor; break; } } return getInstantiationStrategy().instantiate(beanDefinition, beanName, constructorToUse, args); } public InstantiationStrategy getInstantiationStrategy() { return instantiationStrategy; } } -
最后,我们通过开头的那个UserService进行测试,加入了一个name的入参。测试方法如下:
public class ApiTest { @Test public void test_factory() { DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); BeanDefinition beanDefinition = new BeanDefinition(UserService.class); factory.registryBeanDefinition("userService", beanDefinition); UserService userService = (UserService) factory.getBean("userService", "小米粥"); userService.queryUserInfo(); } }测试结果:
查询用户信息:小米粥 Process finished with exit code 0
这就是第三天更文挑战的内容了,预知后事如何,且听下回分解。