Spring IOC 基于Cglib实现含构造函数的类实例化策略

作者:小凯
分享、让自己和他人都能有所收获!

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

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

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

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

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

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

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

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

报错如下:

java.lang.InstantiationException: cn.ittest.springframework.test.bean.UserServiceat java.lang.Class.newInstance(Class.java:427)at cn.ittest.springframework.test.ApiTest.test_newInstance(ApiTest.java:51)...

发生这一现象的主要原因就是因为 beanDefinition.getBeanClass().newInstance(); 实例化方式并没有考虑构造函数的入参,所以就这个坑就在这等着你了!那么我们的目标就很明显了,来把这个坑填平!

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

图 4-1

  • 参考 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.ittest.springframework.beans│           ├── factory│           │   ├── config│           │   │   ├── 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.ittest.springframework.test├── bean│   └── UserService.java└── ApiTest.java

Spring Bean 容器类关系,如图

在这里插入图片描述

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

2. 新增 getBean 接口

cn.ittest.springframework.beans.factory.BeanFactorypublic interface BeanFactory {Object getBean(String name) throws BeansException;Object getBean(String name, Object... args) throws BeansException;}
  • BeanFactory 中我们重载了一个含有入参信息 args 的 getBean 方法,这样就可以方便的传递入参给构造函数实例化了。

3. 定义实例化策略接口

cn.ittest.springframework.beans.factory.support.InstantiationStrategypublic 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.ittest.springframework.beans.factory.support.SimpleInstantiationStrategypublic class SimpleInstantiationStrategy implements InstantiationStrategy {@Overridepublic 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.ittest.springframework.beans.factory.support.CglibSubclassingInstantiationStrategypublic class CglibSubclassingInstantiationStrategy implements InstantiationStrategy {@Overridepublic Object instantiate(BeanDefinition beanDefinition, String beanName, Constructor ctor, Object[] args) throws BeansException {Enhancer enhancer = new Enhancer();enhancer.setSuperclass(beanDefinition.getBeanClass());enhancer.setCallback(new NoOp() {@Overridepublic int hashCode() {return super.hashCode();}});if (null == ctor) return enhancer.create();return enhancer.create(ctor.getParameterTypes(), args);}}
  • 其实 Cglib 创建有构造函数的 Bean 也非常方便,在这里我们更加简化的处理了,如果你阅读 Spring 源码还会看到 CallbackFilter 等实现,不过我们目前的方式并不会影响创建。

6. 创建策略调用

cn.ittest.springframework.beans.factory.support.AbstractAutowireCapableBeanFactorypublic abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory {private InstantiationStrategy instantiationStrategy = new CglibSubclassingInstantiationStrategy();@Overrideprotected 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.ittest.springframework.test.bean.UserServicepublic class UserService {private String name;public UserService(String name) {this.name = name;}public void queryUserInfo() {System.out.println("查询用户信息:" + name);}@Overridepublic String toString() {final StringBuilder sb = new StringBuilder("");sb.append("").append(name);return sb.toString();}
}
  • 这里唯一多在 UserService 中添加的就是一个有 name 入参的构造函数,方便我们验证这样的对象是否能被实例化。

2. 测试用例

cn.ittest.springframework.test.ApiTest@Test
public void test_BeanFactory() {// 1.初始化 BeanFactoryDefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();// 2. 注入beanBeanDefinition beanDefinition = new BeanDefinition(UserService.class);beanFactory.registerBeanDefinition("userService", beanDefinition);// 3.获取beanUserService userService = (UserService) beanFactory.getBean("userService", "小哥");userService.queryUserInfo();
}
  • 在此次的单元测试中,依然包括包括三个核心步骤;初始化 BeanFactory 工厂、注册 Bean、获取 Bean,此外与上一章节不同的是,在获取 Bean 对象时候,传递了一个参数名称为“小哥”的入参信息,这个信息的传递将会帮我们创建出含有 String 类型构造函数的 UserService 类,而不会再出现初始化报错的问题。

3. 测试结果

查询用户信息:小哥Process finished with exit code 0
  • 从测试结果来看,最大的变化就是可以满足带有构造函数的对象,可以被实例化了。
  • 你可以尝试分别使用两种不同的实例化策略,来进行实例化。SimpleInstantiationStrategy、CglibSubclassingInstantiationStrategy

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() {@Overridepublic 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 源码找到对应的代码。
  • 从我们不断的完善增加需求可以看到的,当你的代码结构设计的较为合理的时候,就可以非常容易且方便的进行扩展不同属性的类职责,而不会因为需求的增加导致类结构混乱。所以在我们自己业务需求实现的过程中,也要尽可能的去考虑一个良好的扩展性以及拆分好类的职责。
  • 动手是学习起来最快的方式,不要让眼睛是感觉看会了,但上手操作就废了。也希望有需要的读者可以亲手操作一下,把你的想法也融入到可落地实现的代码里,看看想的和做的是否一致。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/91370.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/91370.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

composer 常用命令

### 设置镜像源全局设置composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/当个项目设置composer config repo.packagist composer https://mirrors.aliyun.com/composer/恢复官方源composer config -g --unset repos.packagist### 常用源阿里云…

【python】Python爬虫入门教程:使用requests库

Python爬虫入门教程&#xff1a;使用requests库 爬虫是数据获取的重要手段&#xff0c;下面我将通过一个完整的示例&#xff0c;教你如何使用Python的requests库编写一个简单的爬虫。我们将以爬取豆瓣电影Top250为例。 【python】网络爬虫教程 - 教你用python爬取豆瓣电影 Top…

OpenCV图像缩放:resize

图像缩放是图像处理中的基础操作之一。无论是图像预处理、数据增强还是图像金字塔构建&#xff0c;cv::resize 都是我们最常用的函数之一。但你是否注意到&#xff0c;在 OpenCV 中同时还存在一个名为 cv::Mat::resize 的方法&#xff1f;这两个函数虽然名字类似&#xff0c;但…

汽车、航空航天、适用工业虚拟装配解决方案

一、现状在制造业数字化转型浪潮中&#xff0c;传统装配过程仍面临诸多挑战&#xff1a;物理样机试错成本高、装配周期冗长、工艺优化依赖经验、跨部门协作效率低下……如何打破“试错-返工”的恶性循环&#xff1f;目前总装工艺通过DELMIA、NX、Creo等工程软件进行工艺装配验证…

页面跳转和前端路由的区别

传统方式&#xff1a;通过改变浏览器地址栏的 URL 来实现window.location.href /new-page<a href"/new-page">跳转到新页面</a>会导致整个页面重新加载会触发浏览器向服务器发送新的请求页面状态不会保留&#xff0c;所有资源重新加载可以避免新上线的内…

C/C++核心知识点详解

C/C核心知识点详解 1. 变量的声明与定义&#xff1a;内存分配的本质区别 核心概念 在C/C中&#xff0c;变量的声明和定义是两个完全不同的概念&#xff1a; 声明&#xff08;Declaration&#xff09;&#xff1a;告诉编译器变量的名称和类型&#xff0c;但不分配内存空间定义&a…

物联网发展:从概念到应用的演变历程

物联网的发展历程是一部技术革新与社会需求共同驱动的进化史&#xff0c;其演变可划分为概念萌芽、技术积累、应用拓展和智能融合四个阶段&#xff0c;每个阶段均以关键技术突破或社会需求变革为标志&#xff0c;最终形成万物互联的智能生态。以下是具体演变历程&#xff1a;一…

一个人开发一个App(数据库)

后端要保存数据&#xff0c;我还是选择了关系型数据库Mysql, 因为其它的不熟悉。 flutter端这次我选择的是ObjectBox&#xff0c;以前都是直接用的sqlite3&#xff0c;看对比ObjectBox效率比sqlite3高许多&#xff0c;这次前端为了用户体验&#xff0c;我需要缓存数据&#xff…

天铭科技×蓝卓 | “1+2+N”打造AI驱动的汽车零部件行业智能工厂

7月24日&#xff0c;杭州天铭科技股份有限公司&#xff08;简称 “天铭科技”&#xff09;与蓝卓数字科技有限公司&#xff08;简称 “蓝卓”&#xff09;签订全面战略合作协议。天铭科技董事长张松、副总经理艾鸿冰&#xff0c;蓝卓副董事长谭彰等领导出席签约仪式&#xff0c…

技术复盘报告:Vue表格中多行文本字段数据保存丢失问题

1. 问题背景 在一个基于 Vue 2.0 和 ElementUI 的复杂数据维护页面中&#xff0c;用户报告了一个偶发但严重的问题&#xff1a;在表格中编辑一个多行文本&#xff08;textarea&#xff09;字段时&#xff0c;输入的内容有时会在点击“保存”后丢失。 具体表现&#xff1a; 前端…

#C语言——学习攻略:深挖指针路线(四)--字符指针变量,数组指针变量,二维数组传参的本质,函数指针变量,函数指针数组

&#x1f31f;菜鸟主页&#xff1a;晨非辰的主页 &#x1f440;学习专栏&#xff1a;《C语言学习》 &#x1f4aa;学习阶段&#xff1a;C语言方向初学者 ⏳名言欣赏&#xff1a;"暴力解法是上帝给的&#xff0c;优化解法是魔鬼教的。" 目录 1. 字符指针变量 1.1 使…

SpringBoot收尾+myBatis plus

一、数据传递返回值为:字符串package com.apesource.springboot_web_04.controller;import com.apesource.springboot_web_04.pojo.Emp; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping;/*** 返回值为:字符…

基于 Spring Boot 实现动态路由加载:从数据库到前端菜单的完整方案

在后台管理系统中&#xff0c;不同用户角色往往拥有不同的操作权限&#xff0c;对应的菜单展示也需动态调整。动态路由加载正是解决这一问题的核心方案 —— 根据登录用户的权限&#xff0c;从数据库查询其可访问的菜单&#xff0c;封装成前端所需的路由结构并返回。本文将详细…

VitePress学习-自定义主题

VitePress-自定义主题 代码仓库 基础了解 初始化项目的时候选择 custom theme 运行后会发现页面挺丑的。 如果想要用默认主题怎么办呢&#xff0c;修改Layout。 使用默认主题的Layout <script setup lang"ts"> import { useData } from vitepress; impo…

【GEO从入门到精通】生成式引擎与其他 AI 技术的关系

2.1.3 生成式引擎与其他 AI 技术的关系生成式引擎作为人工智能领域的创新力量&#xff0c;与其他 AI 技术紧密相连&#xff0c;相互促进&#xff0c;共同推动 生成式引擎优化&#xff08;GEO&#xff09; 的发展。这些技术使生成式引擎能够为消费者提供更加个性化和精准的内容。…

JAVAEE--4.多线程案例

设计模式1.单例模式1.1饿汉模式1.2懒汉模式(单线程版)1.3懒汉模式(多线程版本)1.4懒汉模式(多线程版本进阶版)2.阻塞队列3.定时器4.线程池1.单例模式设计模式是"软性约束",不是强制的,可以遵守也可以不遵守,按照设计模式写代码使代码不会太差框架是"硬性约束&qu…

量化感知训练(QAT)流程

WHAT&#xff1a;量化感知训练&#xff08;Quantization-Aware Training, QAT&#xff09; 是一种在模型训练阶段引入量化误差的技术。它的核心思想是&#xff1a;通过在前向传播时插入“伪量化节点”引入量化误差&#xff0c;将权重和激活模拟为低精度&#xff08;如 int8&…

docker 用于将镜像打包为 tar 文件

docker save 是 Docker 中用于将镜像打包为 tar 文件的命令&#xff0c;常用于镜像的备份、迁移或离线传输。以下是其核心用法和注意事项&#xff1a;一、基本语法bashdocker save [选项] IMAGE [IMAGE...] > 文件名.tar # 或 docker save -o 文件名.tar IMAGE [IMAGE...]IM…

设计模式(六)创建型:单例模式详解

设计模式&#xff08;六&#xff09;创建型&#xff1a;单例模式详解单例模式&#xff08;Singleton Pattern&#xff09;是 GoF 23 种设计模式中最简单却最常被误用的创建型模式。其核心价值在于确保一个类在整个应用程序生命周期中仅存在一个实例&#xff0c;并提供一个全局访…

PostgreSQL AND OR 操作符详解

PostgreSQL AND & OR 操作符详解 在数据库查询中,AND 和 OR 是两种常见的逻辑操作符,用于组合多个查询条件。PostgreSQL 作为一款功能强大的开源关系型数据库管理系统,同样支持这些操作符。本文将详细介绍 PostgreSQL 中的 AND 和 OR 操作符,并探讨它们在查询中的应用…