作者:小傅哥

博客:https://bugstack.cn

沉淀、分享、成长,让自己和他人都能有所收获!

《Spring 手撸专栏》目录

一、前言

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

你觉得自己的技术什么时候得到了快速的提高,是CRUD写的多了以后吗?想都不要想,绝对不可能!CRUD写的再多也只是能满足你作为一个搬砖工具人,敲击少逻辑流水代码的速度而已,而编程能力这一块,除了最开始的从不熟练到熟练以外,就很少再有其他提升了。

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

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

二、目标

这一章节的目标主要是为了解决上一章节我们埋下的坑,那是什么坑呢?其实就是一个关于 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(); 实例化方式并没有考虑构造函数的入参,所以就这个坑就在这等着你了!那么我们的目标就很明显了,来把这个坑填平!

三、设计

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

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

四、实现

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

本章节“填坑”主要是在现有工程中添加 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

    源码中还需要比对入参类型,否则相同数量不同入参类型的情况,就会抛异常了。

五、测试

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 的扩展知识。

六、总结

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

七、系列推荐

Spring 实例化方式有几种?为什么会用到 Cglib?的更多相关文章

  1. spring实例化bean的三种方式

    公共使用的实体

  2. 吴裕雄--天生自然JAVA SPRING框架开发学习笔记:Spring实例化Bean的三种方法

    在面向对象的程序中,要想调用某个类的成员方法,就需要先实例化该类的对象.在 Spring 中,实例化 Bean 有三种方式,分别是构造器实例化.静态工厂方式实例化和实例工厂方式实例化. 构造器实例化 ...

  3. Spring实例化Bean的三种方式及Bean的类型

    1.使用类构造器实例化  [默认的类构造器] <bean id=“orderService" class="cn.itcast.OrderServiceBean"/ ...

  4. Spring 实例化bean的三种方式

    第一种方法:直接配置Bean <bena id="所需要实例化的一个实例名称" class="包名.类名"/> 例如: 配置文件中的bean.XML ...

  5. Spring实例化bean的几种方式

    一,通过constructor实例化bean Spring可以实例化各种类型的类,不要求必须是JavaBean类型的类.在XML中配置类如下: <bean id="exampleBea ...

  6. Spring 实例化Bean的3种方式

    要使用Spring中的Bean,需要先创建这个Bean的实例. 实例化Bean有3种方式: 构造器方式 静态工厂方式 实例工厂方式 构造器方式 构造器方式是最常用的.在Bean中写构造函数,然后在配置 ...

  7. Spring 实例化bean的三种方式:

    方法一:使用构造器实例化bean java代码: package com.model; public class User { private String username; public User ...

  8. Spring实例化bean的三种方法

    1.用构造器来实例化 <bean id="hello2" class="com.hsit.hello.impl.ENhello" /> 2.使用静态 ...

  9. spring读取bean有几种方式

    bean加载到spring的方式: 第一种:xml 第二种:注释「一定要配合包扫描」: <context:component-scan base-package="Cristin.Co ...

随机推荐

  1. 数据结构之LinkedList | 让我们一块来学习数据结构

    highlight: monokai theme: vue-pro 上一篇文章中使用列表(List)对数据排序,当时底层储存数据的数据结构是数组.本文将讨论另外一种列表:链表.我们会解释为什么有时链表 ...

  2. 手把手教你SonarQube入门安装与使用

    简介 Sonar (SonarQube)是一个开源平台,用于管理源代码的质量. Sonar 不只是一个质量数据报告工具,更是代码质量管理平台. 支持Java, C#, C/C++, PL/SQL, C ...

  3. 1.8.5- input按钮组

  4. SSDT表概念详解

    SSDT 的全称是 System Services Descriptor Table,系统服务描述符表. 这个表就是一个把 Ring3 的 Win32 API 和 Ring0 的内核 API 联系起来 ...

  5. 手机改 user模式为debug模式

    logcat 是Android中一个命令行工具,可用于监控手机应用程序的log信息.网上相关的教学很多,这里只想把自己折腾 2 部手机(一个是三星S4 I9500 港水,Android 5.01,一个 ...

  6. Msfvenonm生成一个后门木马

    在前一篇文章中我讲了什么是Meterpreter,并且讲解了Meterpreter的用法.传送门-->Metasploit之Meterpreter 今天我要讲的是我们用Msfvenom制作一个木 ...

  7. UVA10391复合词

    题意:      给定一个词典,然后问里面那些是复合词,复合词就是当前这个单词正好是有两个单词拼接而成. 思路:       用map来标记是否出现过,然后先按长短排序,把每个单体拆分成任意两个可能的 ...

  8. Access+Mssql+Oracle数据库注入

    Access数据库注入 前言:在常见的注入测试中,由于每个数据库内置架构和应用不同,导致数据库类型将决定注入的手法,Access数据库是小型数据库类型,普遍用于小型WEB应用,其中Access注入手法 ...

  9. thinkphp 5中的混合查询

    1.手册样例thinkphp 5.0Db::table('think_user') ->where('name',['like','thinkphp%'],['like','%thinkphp' ...

  10. Java中实现SAX解析xml文件到MySQL数据库

    大致步骤: 1.Java bean 2.DBHelper.java 3.重写DefaultHandler中的方法:MyHander.java 4.循环写数据库:SAXParserDemo.java ① ...