Spring 实例化方式有几种?为何会用到 Cglib?

做者:小傅哥
博客:https://bugstack.cnhtml

沉淀、分享、成长,让本身和他人都能有所收获!😄

《Spring 手撸专栏》目录

1、前言

技术成长,是对场景设计细节不断的雕刻!java

你以为本身的技术何时获得了快速的提升,是CRUD写的多了之后吗?想都不要想,绝对不可能!CRUD写的再多也只是能知足你做为一个搬砖工具人,敲击少逻辑流水代码的速度而已,而编程能力这一块,除了最开始的从不熟练到熟练之外,就不多再有其余提高了。面试

那你可能会想什么才是编程能力提高?其实更多的编程能力的提高是你对复杂场景的架构把控以及对每个技术实现细节点的不断用具备规模体量的流量冲击验证时,是否能保证系统稳定运行从而决定你见识了多少、学到了多少、提高了多少!算法

最终当你在接一个产品需求时,开始思考程序数据结构的设计核心功能的算法逻辑实现总体服务的设计模式使用系统架构的搭建方式应用集群的部署结构,那么也就是的编程能力真正提高的时候!spring

2、目标

这一章节的目标主要是为了解决上一章节咱们埋下的坑,那是什么坑呢?其实就是一个关于 Bean 对象在含有构造函数进行实例化的坑。编程

在上一章节咱们扩充了 Bean 容器的功能,把实例化对象交给容器来统一处理,但在咱们实例化对象的代码里并无考虑对象类是否含构造函数,也就是说若是咱们去实例化一个含有构造函数的对象那么就要抛异常了。设计模式

怎么验证?其实就是把 UserService 添加一个含入参信息的构造函数就能够,以下:缓存

public class UserService {

    private String name;

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

    // ...
}

报错以下:数据结构

java.lang.InstantiationException: cn.bugstack.springframework.test.bean.UserService

    at java.lang.Class.newInstance(Class.java:427)
    at cn.bugstack.springframework.test.ApiTest.test_newInstance(ApiTest.java:51)
    ...

发生这一现象的主要缘由就是由于 beanDefinition.getBeanClass().newInstance(); 实例化方式并无考虑构造函数的入参,因此就这个坑就在这等着你了!那么咱们的目标就很明显了,来把这个坑填平!架构

3、设计

填平这个坑的技术设计主要考虑两部分,一个是串流程从哪合理的把构造函数的入参信息传递到实例化操做里,另一个是怎么去实例化含有构造函数的对象。

图 4-1

  • 参考 Spring Bean 容器源码的实现方式,在 BeanFactory 中添加 Object getBean(String name, Object... args) 接口,这样就能够在获取 Bean 时把构造函数的入参信息传递进去了。
  • 另一个核心的内容是使用什么方式来建立含有构造函数的 Bean 对象呢?这里有两种方式能够选择,一个是基于 Java 自己自带的方法 DeclaredConstructor,另一个是使用 Cglib 来动态建立 Bean 对象。Cglib 是基于字节码框架 ASM 实现,因此你也能够直接经过 ASM 操做指令码来建立对象

4、实现

1. 工程结构

small-spring-step-03
└── src
    ├── main
    │   └── java
    │       └── cn.bugstack.springframework.beans
    │           ├── factory
    │           │   ├── factory
    │           │   │   ├── BeanDefinition.java
    │           │   │   └── SingletonBeanRegistry.java
    │           │   ├── support
    │           │   │   ├── AbstractAutowireCapableBeanFactory.java
    │           │   │   ├── AbstractBeanFactory.java
    │           │   │   ├── BeanDefinitionRegistry.java
    │           │   │   ├── CglibSubclassingInstantiationStrategy.java
    │           │   │   ├── DefaultListableBeanFactory.java
    │           │   │   ├── DefaultSingletonBeanRegistry.java
    │           │   │   ├── InstantiationStrategy.java
    │           │   │   └── SimpleInstantiationStrategy.java
    │           │   └── BeanFactory.java
    │           └── BeansException.java
    └── test
        └── java
            └── cn.bugstack.springframework.test
                ├── bean
                │   └── UserService.java
                └── ApiTest.java

工程源码公众号「bugstack虫洞栈」,回复:Spring 专栏,获取完整源码

Spring Bean 容器类关系,如图 4-2

图 4-2

本章节“填坑”主要是在现有工程中添加 InstantiationStrategy 实例化策略接口,以及补充相应的 getBean 入参信息,让外部调用时能够传递构造函数的入参并顺利实例化。

2. 新增 getBean 接口

cn.bugstack.springframework.beans.factory.BeanFactory

public interface BeanFactory {

    Object getBean(String name) throws BeansException;

    Object getBean(String name, Object... args) throws BeansException;

}
  • BeanFactory 中咱们重载了一个含有入参信息 args 的 getBean 方法,这样就能够方便的传递入参给构造函数实例化了。

3. 定义实例化策略接口

cn.bugstack.springframework.beans.factory.support.InstantiationStrategy

public interface InstantiationStrategy {

    Object instantiate(BeanDefinition beanDefinition, String beanName, Constructor ctor, Object[] args) throws BeansException;

}
  • 在实例化接口 instantiate 方法中添加必要的入参信息,包括:beanDefinition、 beanName、ctor、args
  • 其中 Constructor 你可能会有一点陌生,它是 java.lang.reflect 包下的 Constructor 类,里面包含了一些必要的类信息,有这个参数的目的就是为了拿到符合入参信息相对应的构造函数。
  • 而 args 就是一个具体的入参信息了,最终实例化时候会用到。

4. JDK 实例化

cn.bugstack.springframework.beans.factory.support.SimpleInstantiationStrategy

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 | InstantiationException | IllegalAccessException | InvocationTargetException e) {
            throw new BeansException("Failed to instantiate [" + clazz.getName() + "]", e);
        }
    }

}
  • 首先经过 beanDefinition 获取 Class 信息,这个 Class 信息是在 Bean 定义的时候传递进去的。
  • 接下来判断 ctor 是否为空,若是为空则是无构造函数实例化,不然就是须要有构造函数的实例化。
  • 这里咱们重点关注有构造函数的实例化,实例化方式为 clazz.getDeclaredConstructor(ctor.getParameterTypes()).newInstance(args);,把入参信息传递给 newInstance 进行实例化。

5. Cglib 实例化

cn.bugstack.springframework.beans.factory.support.CglibSubclassingInstantiationStrategy

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 也很是方便,在这里咱们更加简化的处理了,若是你阅读 Spring 源码还会看到 CallbackFilter 等实现,不过咱们目前的方式并不会影响建立。

6. 建立策略调用

cn.bugstack.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory

public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory {

    private InstantiationStrategy instantiationStrategy = new CglibSubclassingInstantiationStrategy();

    @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;
    }

    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);
    }

}
  • 首先在 AbstractAutowireCapableBeanFactory 抽象类中定义了一个建立对象的实例化策略属性类 InstantiationStrategy instantiationStrategy,这里咱们选择了 Cglib 的实现类。
  • 接下来抽取 createBeanInstance 方法,在这个方法中须要注意 Constructor 表明了你有多少个构造函数,经过 beanClass.getDeclaredConstructors() 方式能够获取到你全部的构造函数,是一个集合。
  • 接下来就须要循环比对出构造函数集合与入参信息 args 的匹配状况,这里咱们对比的方式比较简单,只是一个数量对比,而实际 Spring
    源码中还须要比对入参类型,不然相同数量不一样入参类型的状况,就会抛异常了。

5、测试

1. 事先准备

cn.bugstack.springframework.test.bean.UserService

public class UserService {

    private String name;

    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("").append(name);
        return sb.toString();
    }
}
  • 这里惟一多在 UserService 中添加的就是一个有 name 入参的构造函数,方便咱们验证这样的对象是否能被实例化。

2. 测试用例

cn.bugstack.springframework.test.ApiTest

@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();
}
  • 在这次的单元测试中除了包括;Bean 工厂、注册 Bean、获取 Bean,三个步骤,还额外增长了一次对象的获取和调用。这里主要测试验证单例对象的是否正确的存放到了缓存中。
  • 此外与上一章节测试过程当中不一样的是,咱们把 UserService.class 传递给了 BeanDefinition 而不是像上一章节那样直接 new UserService() 操做。

3. 测试结果

查询用户信息:小傅哥

Process finished with exit code 0
  • 从测试结果来看,最大的变化就是能够知足带有构造函数的对象,能够被实例化了。
  • 你能够尝试分别使用两种不一样的实例化策略,来进行实例化。SimpleInstantiationStrategyCglibSubclassingInstantiationStrategy

4. 操做案例

这里咱们再把几种不一样方式的实例化操做,放到单元测试中,方便你们比对学习。

4.1 无构造函数

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

4.2 验证有构造函数实例化

@Test
public void test_constructor() throws Exception {
    Class<UserService> userServiceClass = UserService.class;
    Constructor<UserService> declaredConstructor = userServiceClass.getDeclaredConstructor(String.class);
    UserService userService = declaredConstructor.newInstance("小傅哥");
    System.out.println(userService);
}
  • 从最简单的操做来看,若是有构造函数的类须要实例化时,则须要使用 getDeclaredConstructor 获取构造函数,以后在经过传递参数进行实例化。

4.3 获取构造函数信息

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

4.4 Cglib 实例化

@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);
}
  • 此案例演示使用很是简单,但关于 Cglib 在 Spring 容器中的使用很是多,也能够深刻的学习一下 Cglib 的扩展知识。

6、总结

  • 本章节的主要以完善实例化操做,增长 InstantiationStrategy 实例化策略接口,并新增了两个实例化类。这部分类的名称与实现方式基本是 Spring 框架的一个缩小版,你们在学习过程当中也能够从 Spring 源码找到对应的代码。
  • 从咱们不断的完善增长需求能够看到的,当你的代码结构设计的较为合理的时候,就能够很是容易且方便的进行扩展不一样属性的类职责,而不会由于需求的增长致使类结构混乱。因此在咱们本身业务需求实现的过程当中,也要尽量的去考虑一个良好的扩展性以及拆分好类的职责。
  • 动手是学习起来最快的方式,不要让眼睛是感受看会了,但上手操做就废了。也但愿有须要的读者能够亲手操做一下,把你的想法也融入到可落地实现的代码里,看看想的和作的是否一致。

7、系列推荐

相关文章
相关标签/搜索