这一章节的目标主要是为了解决上一章节我们埋下的坑
,那是什么坑呢?其实就是一个关于 Bean 对象在含有构造函数进行实例化的坑。
1.具体实现
a.拥有含参构造的class对象
arduino
public class UserService{
private String name;
public UserService(String name){//这里因为含参,所以要重写getBean
this.name = name;
}
}
此时会产生报错
css
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();
实例化方式并没有考虑构造函数的入参,所以就这个坑就在这等着你了!那么我们的目标就很明显了,来把这个坑填平
b.getBean优化-增加含参情况的getBean
typescript
public interface BeanFactory{
Object getBean(String name) throws BeansException;
Object getBean(String name,Object...args) throws BeansException;//这就是优化所在,我们为含参数的构造函数提供了新的实例化方案.
}
这里我们对...
这个语法现象进行分析:
在 Java 中,
...
被称为可变参数(Varargs)语法。它允许方法接收不定数量的参数。这种语法使得调用方法时可以传入零个、一个或多个参数,而不需要事先定义参数的数量。具体分析
可变参数的定义:
- 在方法的参数列表中,使用
T...
的形式来定义可变参数,其中T
是参数的类型。- 在这个例子中,
Object... args
表示该方法可以接受任意数量的Object
类型的参数。使用方式:
调用方法时,可以传入任意数量的参数,例如:
scssjavaCopy CodegetBean("beanName"); // 0个参数 getBean("beanName", arg1); // 1个参数 getBean("beanName", arg1, arg2); // 2个参数
传入的参数都将被视为一个数组,方法内部可以通过
args
参数访问这些参数。方法内部处理:
在方法体内,
args
实际上是一个
cssObject[]
数组,可以通过索引访问各个参数。例如:
arduinojavaCopy Codefor (Object arg : args) { // 处理每个参数 }
总结
...args
允许getBean
方法接收可变数量的Object
类型参数,使得该方法更加灵活和易于使用。使用可变参数的好处是简化了方法的调用方式,避免了重载大量不同参数数量的方法。
c.定义实例化策略接口(策略模式)
typescript
public interface InstantiationStrategy {
Object instantiate(BeanDefinition beanDefinition, String beanName, Constructor ctor, Object[] args) throws BeansException;
}
d.策略模式之使用JDK实例化(实现策略接口)
java
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);
//这里就是关键所在,我们使用JDK提供的反射,从传入的构造函数中,取出参数的类型,并且使用Objects[] args中的参数去newInstance();
//从而实现了含参构造的创建
} else {
return clazz.getDeclaredConstructor().newInstance();
//在上一个版本,仅仅有此部分,也就是仅有无参构造的新对象创建
}
} catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
throw new BeansException("Failed to instantiate [" + clazz.getName() + "]", e);
}
}
}
e.策略模式之使用CgLib完成实例化
java
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());//设置Cglib要代理的类
enhancer.setCallback(new NoOp() {//设置方法拦截器
@Override
public int hashCode() {
return super.hashCode();
}
});
if (null == ctor) return enhancer.create();//此部分为默认的构造无参对象
return enhancer.create(ctor.getParameterTypes(), args); //这部分是通过ctor拿到参数类型,再用参数数组创建调用了含参构造的对象
}
}
看到上面的内容有点懵?兴许是你不了解CgLib,我们做一个简单介绍
CGLIB 是一个强大的字节码生成库,主要用于在运行时创建类的子类。它广泛用于动态代理和 AOP(面向切面编程)。下面是一个简单的 CGLIB 使用示例,帮助你了解如何使用它。
1. 添加依赖
如果你使用 Maven,可以在
pom.xml
中添加以下依赖:
xmlxmlCopy Code<dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.3.0</version> <!-- 请检查最新版本 --> </dependency>
- 创建被代理的类
假设我们有一个简单的类
UserService
:
typescriptjavaCopy Codepublic class UserService { public void addUser(String user) { System.out.println("User " + user + " added."); } public void deleteUser(String user) { System.out.println("User " + user + " deleted."); } }
- 创建 CGLIB 代理类
接下来,我们使用 CGLIB 创建
UserService
的代理类:
typescriptjavaCopy Codeimport net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; public class CglibExample { public static void main(String[] args) { // 创建 Enhancer 对象 Enhancer enhancer = new Enhancer(); // 设置要代理的类 enhancer.setSuperclass(UserService.class); // 设置方法拦截器 enhancer.setCallback(new MethodInterceptor() { @Override public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args, MethodProxy proxy) throws Throwable { // 在方法调用前执行的逻辑 System.out.println("Before method: " + method.getName()); // 调用原始方法 Object result = proxy.invokeSuper(obj, args); // 在方法调用后执行的逻辑 System.out.println("After method: " + method.getName()); return result; } }); // 创建代理对象 UserService proxy = (UserService) enhancer.create(); // 使用代理对象 proxy.addUser("Alice"); proxy.deleteUser("Bob"); } }
- 运行示例
当你运行上面的
CglibExample
类时,你会看到输出如下:
sqlCopy CodeBefore method: addUser User Alice added. After method: addUser Before method: deleteUser User Bob deleted. After method: deleteUser
总结
上面的代码展示了如何使用 CGLIB 创建一个简单的代理:
添加依赖:确保项目中包含 CGLIB 的依赖。
创建目标类:定义需要被代理的类。
创建代理
:
- 使用
Enhancer
设置代理的超类。- 使用
MethodInterceptor
实现对方法调用的拦截。调用代理方法:通过代理对象调用方法,观察拦截器的行为。
这样,你就成功地使用 CGLIB 创建了一个动态代理!
f.使用Cglib做为策略的模版BeanFactory
ini
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);
}
}
1.
createBean
和createBeanInstance
的区别
createBean
:
- 这是一个公共方法,负责创建一个新的 bean 实例。
- 它处理异常,确保在实例化 bean 失败时抛出相应的
BeansException
。- 在成功创建 bean 后,会调用
addSingleton(beanName, bean)
将其添加到单例池(单例缓存)中。
createBeanInstance
:
- 这是一个受保护的方法,负责具体的 bean 实例化逻辑。
- 它会根据传入的参数查找合适的构造函数并利用
InstantiationStrategy
创建实例。- 该方法不处理异常或管理单例,而是专注于实际的实例化过程。
2.
addSingleton
的来源
addSingleton(beanName, bean)
方法没有在代码中实现,这表明它是在AbstractBeanFactory
类或其父类中定义的。通常,这个方法的作用是将创建的 bean 存储在一个单例集合中,以便后续请求可以直接返回同一个实例。这种设计可以提高效率,避免重复创建相同的 bean 实例。
- 设计模式
在这个代码片段中,可以看到以下几种设计模式的体现:
工厂模式 (Factory Pattern) :
AbstractAutowireCapableBeanFactory
是一个工厂类,负责创建 bean 实例。通过封装实例化的细节,使得客户端代码不需要关心如何创建对象。策略模式 (Strategy Pattern) :
InstantiationStrategy
是一个策略接口,不同的实现(如CglibSubclassingInstantiationStrategy
)可以用来选择不同的实例化方式。这种模式允许在运行时选择算法的实现。模板方法模式 (Template Method Pattern) :
createBean
方法定义了创建 bean 的步骤,而createBeanInstance
是一个细化步骤的实现。这种模式通过在基类中定义模板方法,允许子类实现具体的步骤,从而控制流程。总结
createBean
负责整个 bean 创建流程,包括异常处理和单例管理,而createBeanInstance
则专注于实例化的具体实现。addSingleton
方法可能来自父类,负责将实例存储以支持单例特性。- 该设计展示了工厂模式、策略模式和模板方法模式的应用,体现了良好的软件设计原则。
g.测试用例
typescript
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();
}
}
java
@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();
}
vbnet
查询用户信息:测试内容
Process finished with exit code 0