共计 8971 个字符,预计需要花费 23 分钟才能阅读完成。
作者:小傅哥
博客:https://bugstack.cn
积淀、分享、成长,让本人和别人都能有所播种!😄
《Spring 手撸专栏》目录
- [x] 第 1 章:开篇介绍,我要带你撸 Spring 啦!
- [x] 第 2 章:小试牛刀,实现一个简略的 Bean 容器
- [x] 第 3 章:初显身手,使用设计模式,实现 Bean 的定义、注册、获取
- [x] 第 4 章:锋芒毕露,基于 Cglib 实现含构造函数的类实例化策略
- [] 第 5 章:给 Bean 对象填充属性信息
- [] 第 6 章:待归档 …
一、前言
技术成长,是对场景设计细节一直的雕刻!
你感觉本人的技术什么时候失去了疾速的进步,是 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
- 从测试后果来看,最大的变动就是能够满足带有构造函数的对象,能够被实例化了。
- 你能够尝试别离应用两种不同的实例化策略,来进行实例化。
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() {
@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 源码找到对应的代码。
- 从咱们一直的欠缺减少需要能够看到的,当你的代码结构设计的较为正当的时候,就能够非常容易且不便的进行扩大不同属性的类职责,而不会因为需要的减少导致类构造凌乱。所以在咱们本人业务需要实现的过程中,也要尽可能的去思考一个良好的扩展性以及拆分好类的职责。
- 入手是学习起来最快的形式,不要让眼睛是感觉看会了,但上手操作就废了。也心愿有须要的读者能够亲手操作一下,把你的想法也融入到可落地实现的代码里,看看想的和做的是否统一。
七、系列举荐
- 面经手册 · 第 1 篇《认知本人的技术栈盲区》)
- 刚火了的中台转头就拆,一大波公司放不下又拿不起来!
- 工作两三年了,整不明确架构图都画啥?
- 初识畛域驱动设计 DDD 落地
- 面试现场:小伙伴美团一面的分享和剖析(含解答)