文章目录
- 目标
- 项目结构
- 一、代码实现
- 1、新增getBean接口
- 2、定义实例化策略接口
- 3、JDK 实例化
- 4、Cglib 实例化
- 5、创建策略调用
- 二、测试
- 1、准备
- 2、测试用例
- 3、测试结果
目标
上一篇文章,我们实例化对象,是通过无参的构造方式生成
所以今天是解决包含参数的构造方法创建对象
验证
public class UserService {
private String name;
public UserService(String name) {
this.name = name;
}
// ...
}
报错信息如下
Caused by: java.lang.InstantiationException: springframework.test.bean.UserService
at java.lang.Class.newInstance(Class.java:427)
at cn.ljc.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:17)
... 27 more
Caused by: java.lang.NoSuchMethodException: springframework.test.bean.UserService.<init>()
at java.lang.Class.getConstructor0(Class.java:3082)
at java.lang.Class.newInstance(Class.java:412)
... 28 more
项目结构
└─src
├─main
│ ├─java
│ │ └─cn
│ │ └─ljc
│ │ └─springframework
│ │ └─beans
│ │ │ BeansException.java
│ │ │
│ │ └─factory
│ │ │ BeanFactory.java
│ │ │
│ │ ├─config
│ │ │ BeanDefinition.java
│ │ │ SingletonBeanRegistry.java
│ │ │
│ │ └─support
│ │ AbstractAutowireCapableBeanFactory.java
│ │ AbstractBeanFactory.java
│ │ BeanDefinitionRegistry.java
│ │ CglibSubclassingInstantiationStrategy.java
│ │ DefaultListableBeanFactory.java
│ │ DefaultSingletonBeanRegistry.java
│ │ InstantiationStrategy.java
│ │ SimpleInstantiationStrategy.java
│ │
│ └─resources
└─test
└─java
└─springframework
└─test
│ ApiTest.java
│
└─bean
UserService.java
新增实例化策略接口、实现类
重载getBean方法,新增创建实例化方法
一、代码实现
1、新增getBean接口
/**
* @desc Bean工厂
* @Author: ljc
* @Date: 2022/11/28 10:36
*/
public interface BeanFactory {
Object getBean(String name) throws BeansException;
Object getBean(String name, Object... args) throws BeansException;
}
重载了一个含有入参信息 args 的 getBean 方法,这样就可以方便的传递入参给构造函数实例化了
2、定义实例化策略接口
/**
* 实例化策略接口
*/
public interface InstantiationStrategy {
/**
* 实例化bean
* @param beanDefinition bean定义
* @param beanName bean的名称
* @param ctor 构造方法类(包含了类的相关信息)
* @param args 具体的入参,实例化会用到
* @return
* @throws BeansException
*/
Object instantiate(BeanDefinition beanDefinition, String beanName, Constructor ctor, Object[] args) throws BeansException;
}
3、JDK 实例化
/**
* jdk实例化
*/
public class SimpleInstantiationStrategy implements InstantiationStrategy{
@Override
public Object instantiate(BeanDefinition beanDefinition, String beanName, Constructor ctor, Object[] args) throws BeansException {
Class beanClass = beanDefinition.getBeanClass();
try {
if (ctor != null) {
return beanClass.getDeclaredConstructor(ctor.getParameterTypes()).newInstance(args);
}else{
return beanClass.getDeclaredConstructor().newInstance();
}
} catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
throw new BeansException("Failed to instantiate [" + beanClass.getName() +"]", e);
}
}
}
1、判断ctor为空就使用无参构造函数实例化,否则就用有参构造函数实例化
2、重点关注有参构造函数的实例化,实例化方式为 clazz.getDeclaredConstructor(ctor.getParameterTypes()).newInstance(args);
把入参信息传递给 newInstance 进行实例化。
4、Cglib 实例化
/**
* Cglib实例化
*/
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 (ctor == null) {
return enhancer.create();
}
return enhancer.create(ctor.getParameterTypes(),args);
}
}
上面代码提到了一个新的类Enhancer,它是一个类的增强器,可以完成对类的代理,也被称之为代理类
另外cglib本身就提供了有参构造函数的创建
5、创建策略调用
/**
* @desc 实例化Bean类
* @Author: ljc
* @Date: 2022/12/7 13:06
*/
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 = createBeanInstance(beanName,beanDefinition,args);;
try {
bean = createBeanInstance(beanName,beanDefinition,args);
} catch (Exception e) {
throw new BeansException("Instantiation of bean failed", e);
}
addSingleton(beanName,bean);
return bean;
}
protected Object createBeanInstance(String beanName, BeanDefinition beanDefinition, Object[] args) {
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);
}
/**
* 获取实例化策略
* @return
*/
public InstantiationStrategy getInstantiationStrategy() {
return instantiationStrategy;
}
// 定义实例化策略
public void setInstantiationStrategy(InstantiationStrategy instantiationStrategy) {
this.instantiationStrategy = instantiationStrategy;
}
}
主要做了几件事情
1、定义了一个创建对象的实例化策略属性类 InstantiationStrategy instantiationStrategy,这里我们选择了 Cglib 的实现类。
2、新增createBeanInstance 方法,在这个方法中需要注意 Constructor 代表了你有多少个构造函数,通过beanClass.getDeclaredConstructors() 方式可以获取到你所有的构造函数,是一个集合。
3、接下来就需要循环比对出构造函数集合与入参信息 args 的匹配情况,这里我们对比的方式比较简单,只是一个数量对比,而实际 Spring 源码中还需要比对入参类型,否则相同数量不同入参类型的情况,就会抛异常了。
二、测试
1、准备
/**
* @desc 用户服务类
* @Author: ljc
* @Date: 2022/11/28 10:56
*/
public class UserService {
private String name;
public UserService(String name) {
this.name = name;
}
public void queryUserInfo(){
System.out.println("查询用户信息:" + name);
}
}
添加了一个带参的构造函数,方便验证
2、测试用例
/**
* @desc 测试
* @Author: ljc
* @Date: 2022/11/28 10:57
*/
public class 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","ljc");
userService.queryUserInfo();
}
}
getBean时,第二个参数传入参数,用于cglib去创建有参构造函数生成对象
3、测试结果
查询用户信息:ljc
Process finished with exit code 0