:崭露头角,基于Cglib实现含构造函数的类实例化策略

Posted 泡^泡

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了:崭露头角,基于Cglib实现含构造函数的类实例化策略相关的知识,希望对你有一定的参考价值。

目标

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

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

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

ublic 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(); 实例化方式并没有考虑构造函数的入参,所以就这个坑就在这等着你了!那么我们的目标就很明显了,来把这个坑填平!

设计

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

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

实现

工程结构


Spring Bean 容器类

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

新增 getBean 接口

package com;

/**
 * Bean工厂
 */
public interface BeanFactory 

    Object getBean(String name) throws Exception;

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

  • BeanFactory 中我们重载了一个含有入参信息 args 的 getBean 方法,这样就可以方便的传递入参给构造函数实例化了。
package com;

public abstract class AbstractBeanFactory extends DefaultSingletonBeanRegistry implements BeanFactory 

    @Override
    public Object getBean(String name) throws Exception 
       return doGetBean(name,null);
    

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

    public <T> T doGetBean(String name, Object... args) throws Exception 
        Object bean = getSingleton(name);
        if(bean != null)
            return (T)bean;
        
        BeanDefinition beanDefinition = getBeanDefinition(name);
        return (T)createBean(name,beanDefinition,args);
    

    protected abstract Object createBean(String beanName, BeanDefinition beanDefinition, Object[] args) throws Exception;

    protected abstract BeanDefinition getBeanDefinition(String beanName) throws Exception;

定义实例化策略接口

package com;

import java.lang.reflect.Constructor;

public interface InstantiationStrategy 
    Object instantiate(BeanDefinition beanDefinition, String beanName, Constructor ctor,Object[] args) throws Exception;

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

JDK 实例化

package com;

import java.lang.reflect.Constructor;

public class SimpleInstantiationStrategy implements InstantiationStrategy 
    @Override
    public Object instantiate(BeanDefinition beanDefinition, String beanName, Constructor ctor, Object[] args) throws Exception 
        Class clazz = beanDefinition.getBeanClass();
        try 
            if(ctor != null)
                return clazz.getDeclaredConstructor(ctor.getParameterTypes()).newInstance(args);
            else
                return clazz.getDeclaredConstructor().newInstance();
            
        catch (Exception e)
            throw new RuntimeException("Failed to instantiate [" + clazz.getName() + "]", e);
        
    

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

Cglib 实例化

package com;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.NoOp;

import java.lang.reflect.Constructor;

public class CglibSubclassingInstantiationStrategy implements InstantiationStrategy 
    @Override
    public Object instantiate(BeanDefinition beanDefinition, String beanName, Constructor ctor, Object[] args) throws Exception 
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(beanDefinition.getBeanClass());
        enhancer.setCallback(new NoOp() 
            @Override
            public int hashCode() 
                return super.hashCode();
            
        );
        if(ctor == null)
            return enhancer.create();
        
        return enhancer.create(ctor.getParameterTypes(),args);
    

  • 其实 Cglib 创建有构造函数的 Bean 也非常方便,在这里我们更加简化的处理了,如果你阅读 Spring 源码还会看到 CallbackFilter 等实现,不过我们目前的方式并不会影响创建。

创建策略调用

package com;

import java.lang.reflect.Constructor;

public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory 

    private InstantiationStrategy instantiationStrategy = new CglibSubclassingInstantiationStrategy();

    @Override
    protected Object createBean(String name, BeanDefinition beanDefinition,Object[] args) throws Exception 
        Object bean = null;
        try 
           bean = createBeanInstance(beanDefinition,name,args);
        catch (Exception e)
           throw new RuntimeException("Instantiation of bean failed");
        
        addSingleton(name,bean);
        return bean;
    

    /**
     * 查找对应构造函数
     * @param beanDefinition
     * @param beanName
     * @param args
     * @return
     * @throws Exception
     */
    protected Object createBeanInstance(BeanDefinition beanDefinition,String beanName,Object[] args) throws Exception
        Constructor constructorToUse = null;
        Class<?> beanClass = beanDefinition.getBeanClass();
        Constructor<?>[] declaredConstructors = beanClass.getDeclaredConstructors();
        for(Constructor ctor : declaredConstructors)
           if(args != null && ctor.getParameterTypes().length == args.length)
               constructorToUse = ctor;
               break;
           
        
        return getInstantiationStrategy().instantiate(beanDefinition,beanName,constructorToUse,args);
    

    public InstantiationStrategy getInstantiationStrategy() 
        return instantiationStrategy;
    

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

测试

package com;

public class UserService 

    private String name;

    public UserService() 
    

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

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

package com;

import org.junit.Test;

public class UTest

    @Test
    public void test_BeanFactory() throws Exception 
         //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();
    

以上是关于:崭露头角,基于Cglib实现含构造函数的类实例化策略的主要内容,如果未能解决你的问题,请参考以下文章

Js实现继承的方法

JavaScript的类

线程安全的单实例模式

枚举器而不是类实例化的构造函数?

第3项:用私有构造器或者枚举类型强化Singleton属性

cglib动态代理