手写Spring:第4章-基于Cglib实现含构造函数的类实例化策略

文章目录

  • 一、目标:含构造函数的类实例化
  • 二、设计:含构造函数的类实例化
  • 三、实现:含构造函数的类实例化
    • 3.1 工程结构
    • 3.2 含构造函数的类实例化类图
    • 3.3 类实例化策略
      • 3.3.1 定义实例化策略接口
      • 3.3.2 JDK实例化
      • 3.3.3 Cglib实例化
    • 3.4 抽象类定义模板方法
      • 3.4.1 Bean工厂接口,新增getBean接口
      • 3.4.2 抽象Bean工厂基类,定义模板方法
      • 3.4.3 创建策略调用
  • 四、测试:含构造函数的类实例化
    • 4.1 用户Bean对象
    • 4.2 单元测试
      • 4.2.1 单元测试
      • 4.2.2 无构造函数测试
      • 4.2.3 有构造函数实例化测试
      • 4.2.4 获取构造函数信息
      • 4.2.5 Cglib实例化
  • 五、总结:含构造函数的类实例化

一、目标:含构造函数的类实例化

关于 Bean 对象在含有构造函数进行实例化?

  • 之前扩充了 Bean 容器的功能,把实例化对象交给容器来统一处理,但在我们实例化对象里并没有考虑对象类是否含构造函数。
    • 也就是说如果我们去实例化一个含有构造函数的对象就要抛异常。

UserService

public class UserService {

    private String name;

    public UserService() {
    }

    public UserService(String name) {
        this.name = name;
    }
}

报错如下:

Caused by: java.lang.InstantiationException: com.lino.springframework.test.bean.UserService
    at java.lang.Class.newInstance(Class.java:427)
    at com.lino.springframework.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:17)
    ... 26 more
  • 发现这一现象的主要原因是 beanDefinition.getBeanClass().newInstance()。实例化方式并没有考虑构造函数的入参。

二、设计:含构造函数的类实例化

技术设计需要考虑两部分?

  • 一个是串联流程从哪合理的把构造函数的入参信息传递给实例化操作里。
  • 另一个是怎么去实例化含有构造函数的对象。

手写Spring:第4章-基于Cglib实现含构造函数的类实例化策略_第1张图片

  • 参考 Spring Bean 容器源码的实现方式,在 BeanFactory 中添加 Object getBean(String name, Object... args) 接口。
    • 这样就可以在获取 Bean 时把构造函数的入参信息传递进去。
  • 核心:使用什么方式来创建含有构造函数的 Bean 对象呢?
    • 一个是基于 Java 本身自带的方法 DeclaredConstructor
    • 另一个是使用 Cglib 来动态创建 Bean 对象。Cglib 是基于字节码框架 ASM 实现,所以可以直接通过 ASM 操作指令码来创建对象

三、实现:含构造函数的类实例化

3.1 工程结构

spring-step-03
|-src
    |-main
    |   |-java
    |       |-com.lino.springframework
    |           |-factory
    |           |   |-config
    |           |   |   |-BeanDefinition.java
    |           |   |   |-SingletonBeanRegistry.java
    |           |   |-support
    |           |   |   |-AbstractAutowireCapableBeanFactory.java
    |           |   |   |-AbstractBeabFactory.java
    |           |   |   |-BeanDefinitionRegistry.java
    |           |   |   |-CglibSubclassingInstantiationStrategy.java
    |           |   |   |-DefaultListableBeanFactory.java
    |           |   |   |-DefaultSingletonBeanRegistry.java
    |           |   |   |-InstantiationStrategy.java
    |           |   |   |-SimpleInstantiationStrategy.java
    |           |   |-BeanFactory.java
    |           |-BeansException.java
    |-test
        |-java
            |-com.lino.springframework.test
            |-bean
            |   |-UserService.java
            |-ApiTest.java

3.2 含构造函数的类实例化类图

手写Spring:第4章-基于Cglib实现含构造函数的类实例化策略_第2张图片

  • 主要添加 InstantiationStrategy 实例化策略接口,以及补充相应的 getBean 入参信息,让外部调用时可以传递构造函数的入参并顺利实例化。

3.3 类实例化策略

3.3.1 定义实例化策略接口

InstantiationStrategy.java

package com.lino.springframework.factory.support;

import com.lino.springframework.BeansException;
import com.lino.springframework.factory.config.BeanDefinition;
import java.lang.reflect.Constructor;

/**
 * @description: Bean 实例化策略接口
 */
public interface InstantiationStrategy {

    /**
     * 实例化
     *
     * @param beanDefinition Bean 对象
     * @param beanName       要检索的bean名称
     * @param ctor           类信息
     * @param args           构造函数入参
     * @return 实例化后的对象
     * @throws BeansException 不能获取 Bean 对象,抛出异常
     */
    Object instantiate(BeanDefinition beanDefinition, String beanName, Constructor ctor, Object[] args) throws BeansException;
}
  • 在实例化接口 InstantiationStrategy 方法中添加必要的入参信息,包括:beanDefinition、beanName、ctor、args
  • 其中 Constructor,它是 java.lang.reflect 包下的 Constructor 类,里面包含了一些必要的类信息。
    • 有这个参数的目的就是为了拿到符合入参信息相对应的构造函数。
  • args 就是一个具体的入参信息,最终实例化时会用到。

3.3.2 JDK实例化

SimpleInstantiationStrategy.java

package com.lino.springframework.factory.support;

import com.lino.springframework.BeansException;
import com.lino.springframework.factory.config.BeanDefinition;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

/**
 * @description: JDK实例化策略
 */
public class SimpleInstantiationStrategy implements InstantiationStrategy {

    @Override
    public Object instantiate(BeanDefinition beanDefinition, String beanName, Constructor ctor, Object[] args) throws BeansException {
        Class clazz = beanDefinition.getBeanClass();
        try {
            if (null != ctor) {
                return clazz.getDeclaredConstructor(ctor.getParameterTypes()).newInstance(args);
            } else {
                return clazz.getDeclaredConstructor().newInstance();
            }
        } catch (NoSuchMethodException | IllegalAccessException | InstantiationException | InvocationTargetException e) {
            throw new BeansException("Failed to instantiate [" + clazz.getName() + "]", e);
        }
    }
}
  • 首先通过 beanDefinition 获取 Class 信息,这个 Class 信息是在 Bean 定义的时候传递进去的。
  • 接下来判断 ctor 是否为空,如果为空则是无构造函数实例化,否则就是需要有构造函数的实例化。
  • 这里我们重点关注有构造函数的实例化,实例化方式为 clazz.getDeclaredConstructor(ctor.getParameterTypes()).newInstance(args),把入参信息传递给 newInstance 进行实例化。

3.3.3 Cglib实例化

CglibSubclassingInstantiationStrategy.java

package com.lino.springframework.factory.support;

import com.lino.springframework.BeansException;
import com.lino.springframework.factory.config.BeanDefinition;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.NoOp;
import java.lang.reflect.Constructor;

/**
 * @description: Cglib 实例化策略
 */
public class CglibSubclassingInstantiationStrategy implements InstantiationStrategy {

    @Override
    public Object instantiate(BeanDefinition beanDefinition, String beanName, Constructor ctor, Object[] args) throws BeansException {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(beanDefinition.getBeanClass());
        enhancer.setCallback(new NoOp() {
            @Override
            public int hashCode() {
                return super.hashCode();
            }
        });
        if (null == ctor) {
            return enhancer.create();
        }
        return enhancer.create(ctor.getParameterTypes(), args);
    }
}
  • 其实 Cglib 创建有构造函数的 Bean 也非常方便。

3.4 抽象类定义模板方法

3.4.1 Bean工厂接口,新增getBean接口

BeanFactory.java

package com.lino.springframework.factory;

import com.lino.springframework.BeansException;

/**
 * @description: 定义 Bean 工厂接口
 */
public interface BeanFactory {

    /**
     * 返回 Bean 的实例对象
     *
     * @param name 要检索的bean的名称
     * @return 实例化的 Bean 对象
     * @throws BeansException 不能获取 Bean 对象,抛出异常
     */
    Object getBean(String name) throws BeansException;

    /**
     * 返回含构造函数的 Bean 实例对象
     *
     * @param name 要检索的bean的名称
     * @param args 构造函数入参
     * @return 实例化的 Bean 对象
     * @throws BeansException 不能获取 Bean 对象,抛出异常
     */
    Object getBean(String name, Object... args) throws BeansException;
}
  • BeanFactory 中重载了一个含有入参信息 argsgetBean 方法,这样就可以传递入参给构造函数实例化了。

3.4.2 抽象Bean工厂基类,定义模板方法

AbstractBeanFactory.java

package com.lino.springframework.factory.support;

import com.lino.springframework.BeansException;
import com.lino.springframework.factory.BeanFactory;
import com.lino.springframework.factory.config.BeanDefinition;

/**
 * @description: 抽象的 Bean 工厂基类,定义模板方法
 * @author: lingjian
 * @createDate: 2022/11/22 14:34
 */
public abstract class AbstractBeanFactory extends DefaultSingletonBeanRegistry implements BeanFactory {

    @Override
    public Object getBean(String name) throws BeansException {
        return doGetBean(name, null);
    }

    @Override
    public Object getBean(String name, Object... args) throws BeansException {
        return doGetBean(name, args);
    }

    protected <T> T doGetBean(final String name, final Object[] args) {
        Object bean = getSingleton(name);
        if (bean != null) {
            return (T) bean;
        }
        BeanDefinition beanDefinition = getBeanDefinition(name);
        return (T) createBean(name, beanDefinition, args);
    }

    /**
     * 获取 Bean 对象
     *
     * @param beanName 要检索的bean的名称
     * @return Bean 对象
     */
    protected abstract BeanDefinition getBeanDefinition(String beanName);

    /**
     * 创建Bean对象
     *
     * @param beanName       要检索的bean的名称
     * @param beanDefinition Bean对象
     * @param args           构造函数入参
     * @return 实例化的Bean对象
     */
    protected abstract Object createBean(String beanName, BeanDefinition beanDefinition, Object[] args);
}
  • 添加含构造参数的 getBean 方法。

3.4.3 创建策略调用

AbstractAutowireCapableBeanFactory.java

package com.lino.springframework.factory.support;

import com.lino.springframework.BeansException;
import com.lino.springframework.factory.config.BeanDefinition;
import java.lang.reflect.Constructor;

/**
 * @description: 实现默认bean创建的抽象bean工厂超类
 */
public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory {

    private InstantiationStrategy instantiationStrategy = new CglibSubclassingInstantiationStrategy();

    @Override
    protected Object createBean(String beanName, BeanDefinition beanDefinition, Object[] args) {
        Object bean = null;
        try {
            bean = createBeanInstance(beanDefinition, beanName, args);
        } catch (Exception e) {
            throw new BeansException("Instantiation of bean failed", e);
        }
        registerSingletonBean(beanName, bean);
        return bean;
    }

    protected Object createBeanInstance(BeanDefinition beanDefinition, String beanName, Object[] args) {
        Constructor constructorToUse = null;
        Class<?> beanClass = beanDefinition.getBeanClass();
        Constructor<?>[] declaredConstructors = beanClass.getDeclaredConstructors();
        for (Constructor ctor : declaredConstructors) {
            if (null != args && ctor.getParameterTypes().length == args.length) {
                constructorToUse = ctor;
                break;
            }
        }
        return getInstantiationStrategy().instantiate(beanDefinition, beanName, constructorToUse, args);
    }

    public InstantiationStrategy getInstantiationStrategy() {
        return instantiationStrategy;
    }

    public void setInstantiationStrategy(InstantiationStrategy instantiationStrategy) {
        this.instantiationStrategy = instantiationStrategy;
    }
}
  • 首先在 AbstractAutowireCapableBeanFactory 抽象类中定义了一个创建对象的实例化测量属性类 InstantiationStrategy instantiationStrategy。这里选择 Cglib 的实现类。
  • 接下来抽取 createBeanInstance 方法,在这个方法中需要注意 Constructor 代表了你有多少个构造函数,通过 beanClass.getDeclaredConstructors() 方式可以获取到你所有的构造函数,是一个集合。
  • 最后就需要循环比对出构造函数集合与入参信息 args 的匹配信息。
    • 这里比对方式只是一个数量对比,而 Spring 源码中还需要比对入参类,否则相同数量不同入参类型的情况,就会抛异常。

四、测试:含构造函数的类实例化

4.1 用户Bean对象

UserService.java

package com.lino.springframework.test.bean;

/**
 * @description: 模拟用户 Bean 对象
 */
public class UserService {

    private String name;

    public UserService() {
    }

    public UserService(String name) {
        this.name = name;
    }

    /**
     * 查询用户信息
     */
    public void queryUserInfo() {
        System.out.println("查询用户信息: " + name);
    }

    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder();
        sb.append(name);
        return sb.toString();
    }
}
  • UserService 中添加一个无参构造函数和有一个 name 入参的构造函数。

4.2 单元测试

4.2.1 单元测试

ApiTest.java

@Test
public void test_BeanFactory() {
    // 1.初始化 BeanFactory
    DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();

    // 2.注册bean
    BeanDefinition beanDefinition = new BeanDefinition(UserService.class);
    beanFactory.registerBeanDefinition("userService", beanDefinition);

    // 3.获取bean
    UserService userService = (UserService) beanFactory.getBean("userService", "小零");
    userService.queryUserInfo();
}
  • 次单元测试中,包含三个核心步骤:初始化 BeanFactory 工厂,注册 bean,获取获取 bean
    • 在获取 Bean 对象时候,传递了一个参数名称为 小零 的入参信息,这个信息的传递将会帮我们创建出含有 String 类型构造函数的 UserService 类,而不会再出现初始化报错的问题。

测试结果

查询用户信息: 小零
  • 从测试结果来看,最大的变化就是可以满足带有构造函数的对象,可以被实例化。

4.2.2 无构造函数测试

ApiTest.java

@Test
public void test_newInstance() throws InstantiationException, IllegalAccessException {
    UserService userService = UserService.class.newInstance();
    System.out.println(userService);
}
  • 这个方式的实例化在实现 Spring Bean 容器时直接使用的方式

测试结果

null

4.2.3 有构造函数实例化测试

ApiTest.java

@Test
public void test_constructor() throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
    Class<UserService> userServiceClass = UserService.class;
    Constructor<UserService> declaredConstructor = userServiceClass.getDeclaredConstructor(String.class);
    UserService userService = declaredConstructor.newInstance("小零");
    System.out.println(userService);
}
  • 从最简单的操作来看,如果有构造函数的类需要实例化时,则需要使用 getDeclaredConstructor 获取构造函数,之后通过传递参数进行实例化。

测试结果

小零

4.2.4 获取构造函数信息

ApiTest.java

@Test
public void test_parameterTypes() throws Exception {
    Class<UserService> beanClass = UserService.class;
    Constructor<?>[] declaredConstructors = beanClass.getDeclaredConstructors();
    Constructor<?> constructor = declaredConstructors[1];
    Constructor<UserService> declaredConstructor = beanClass.getDeclaredConstructor(constructor.getParameterTypes());
    UserService userService = declaredConstructor.newInstance("小零");
    System.out.println(userService);
}
  • 这个测试最核心的点在于获取一个类中所有的构造函数,其实也就是这个方法的使用 beanClass.getDeclaredConstructors()

测试结果

小零

4.2.5 Cglib实例化

ApiTest.java

@Test
public void test_cglib() {
    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(UserService.class);
    enhancer.setCallback(new NoOp() {
        @Override
        public int hashCode() {
            return super.hashCode();
        }
    });
    Object obj = enhancer.create(new Class[]{String.class}, new Object[]{"小零"});
    System.out.println(obj);
}

测试结果

小零

五、总结:含构造函数的类实例化

  • 完善实例化操作,增加 InstantiationStrategy 实例化策略接口,并新增了两个实例化类。

你可能感兴趣的:(手写spring,spring,java)