面试官都爱问的 Spring 源码:Spring 与 Mybatis 高级整合
假设有一个 A 类,假设有如下代码:
一个 A 类:
@Component
public class A {
}
一个 B 类,不存在 @Component 注解
public class B {
}
执行如下代码:
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
System.out.println(context.getBean("a"));
输出结果为:com.luban.util.A@6acdbdf5
A 类对应的 bean 对象类型仍然为 A 类。但是这个结论是不确定的,我们可以利用 BeanFactory 后置处理器来修改 BeanDefinition,我们添加一个 BeanFactory 后置处理器:
@Component
public class LubanBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
BeanDefinition beanDefinition = beanFactory.getBeanDefinition("a");
beanDefinition.setBeanClassName(B.class.getName());
}
}
这样就会导致,原本的 A 类对应的 BeanDefiniton 被修改了,被修改成了 B 类,那么后续正常生成的 bean 对象的类型就是 B 类。此时,调用如下代码会报错:
context.getBean(A.class);
但是调用如下代码不会报错,尽管 B 类上没有 @Component 注解:
context.getBean(B.class);
并且,下面代码返回的结果是:com.luban.util.B@4b1c1ea0
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
System.out.println(context.getBean("a"));
之所以讲这个问题,是想说明一个问题:在 Spring 中,bean 对象跟 class 没有直接关系,跟 BeanDefinition 才有直接关系。
那么回到我们要解决的问题:如何能够把 Mybatis 的代理对象作为一个 bean 放入 Spring 容器中?
在 Spring 中,如果你想生成一个 bean,那么得先生成一个 BeanDefinition,就像你想 new 一个对象实例,得先有一个 class。
解决问题
继续回到我们的问题,我们现在想自己生成一个 bean,那么得先生成一个 BeanDefinition,只要有了 BeanDefinition,通过在 BeanDefinition 中设置 bean 对象的类型,然后把 BeanDefinition 添加给 Spring,Spring 就会根据 BeanDefinition 自动帮我们生成一个类型对应的 bean 对象。
所以,现在我们要解决两个问题:
Mybatis 的代理对象的类型是什么?因为我们要设置给 BeanDefinition
我们怎么把 BeanDefinition 添加给 Spring 容器?
注意:上文中我们使用的 BeanFactory 后置处理器,他只能修改 BeanDefinition,并不能新增一个 BeanDefinition。我们应该使用 Import 技术来添加一个 BeanDefinition。后文再详细介绍如果使用 Import 技术来添加一个 BeanDefinition,可以先看一下伪代码实现思路。
假设:我们有一个 UserMapper 接口,他的代理对象的类型为 UserMapperProxy。那么我们的思路就是这样的,伪代码如下:
BeanDefinitoin bd = new BeanDefinitoin();
bd.setBeanClassName(UserMapperProxy.class.getName());
SpringContainer.addBd(bd);
但是,这里有一个严重的问题,就是上文中的 UserMapperProxy 是我们假设的,他表示一个代理类的类型,然而 Mybatis 中的代理对象是利用的 JDK 的动态代理技术实现的,也就是代理对象的代理类是动态生成的,我们根本无法确定代理对象的代理类到底是什么。
所以回到我们的问题:Mybatis 的代理对象的类型是什么?
本来可以有两个答案:
代理对象对应的代理类
代理对象对应的接口
那么答案 1 就相当于没有了,因为是代理类是动态生成的,那么我们来看答案 2:代理对象对应的接口如果我们采用答案 2,那么我们的思路就是:
BeanDefinition bd = new BeanDefinitoin();
// 注意这里,设置的是 UserMapper
bd.setBeanClassName(UserMapper.class.getName());
SpringContainer.addBd(bd);
但是,实际上给 BeanDefinition 对应的类型设置为一个接口是行不通的,因为 Spring 没有办法根据这个 BeanDefinition 去 new 出对应类型的实例,接口是没法直接 new 出实例的。
那么现在问题来了,我要解决的问题:Mybatis 的代理对象的类型是什么?
两个答案都被我们否定了,所以这个问题是无解的,所以我们不能再沿着这个思路去思考了,只能回到最开始的问题:如何能够把 Mybatis 的代理对象作为一个 bean 放入 Spring 容器中?
总结上面的推理:我们想通过设置 BeanDefinition 的 class 类型,然后由 Spring 自动的帮助我们去生成对应的 bean,但是这条路是行不通的。
终极解决方案
那么我们还有没有其他办法,可以去生成 bean 呢?并且生成 bean 的逻辑不能由 Spring 来帮我们做了,得由我们自己来做。
FactoryBean
有,那就是 Spring 中的 FactoryBean。我们可以利用 FactoryBean 去自定义我们要生成的 bean 对象,比如:
@Component
public class LubanFactoryBean implements FactoryBean {
@Override
public Object getObject() throws Exception {
Object proxyInstance = Proxy.newProxyInstance(LubanFactoryBean.class.getClassLoader(), new Class[]{UserMapper.class}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else {
// 执行代理逻辑
return null;
}
}
});
return proxyInstance;
}
@Override
public Class<?> getObjectType() {
return UserMapper.class;
}
}
我们定义了一个 LubanFactoryBean,它实现了 FactoryBean,getObject 方法就是用来自定义生成 bean 对象逻辑的。
执行如下代码:
public class Test {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
System.out.println("lubanFactoryBean: " + context.getBean("lubanFactoryBean"));
System.out.println("&lubanFactoryBean: " + context.getBean("&lubanFactoryBean"));
System.out.println("lubanFactoryBean-class: " + context.getBean("lubanFactoryBean").getClass());
}
}
将打印:
lubanFactoryBean: com.luban.util.LubanFactoryBean$1@4d41cee
&lubanFactoryBean: com.luban.util.LubanFactoryBean@3712b94
lubanFactoryBean-class: class com.sun.proxy.$Proxy20
从结果我们可以看到,从 Spring 容器中拿名字为"lubanFactoryBean"的 bean 对象,就是我们所自定义的 jdk 动态代理所生成的代理对象。
所以,我们可以通过 FactoryBean 来向 Spring 容器中添加一个自定义的 bean 对象。上文中所定义的 LubanFactoryBean 对应的就是 UserMapper,表示我们定义了一个 LubanFactoryBean,相当于把 UserMapper 对应的代理对象作为一个 bean 放入到了容器中。
但是作为程序员,我们不可能每定义了一个 Mapper,还得去定义一个 LubanFactoryBean,这是很麻烦的事情,我们改造一下 LubanFactoryBean,让他变得更通用,比如:
@Component
public class LubanFactoryBean implements FactoryBean {
// 注意这里
private Class mapperInterface;
public LubanFactoryBean(Class mapperInterface) {
this.mapperInterface = mapperInterface;
}
@Override
public Object getObject() throws Exception {
Object proxyInstance = Proxy.newProxyInstance(LubanFactoryBean.class.getClassLoader(), new Class[]{mapperInterface}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else {
// 执行代理逻辑
return null;
}
}
});
return proxyInstance;
}
@Override
public Class<?> getObjectType() {
return mapperInterface;
}
}
改造 LubanFactoryBean 之后,LubanFactoryBean 变得灵活了,可以在构造 LubanFactoryBean 时,通过构造传入不同的 Mapper 接口。
实际上 LubanFactoryBean 也是一个 Bean,我们也可以通过生成一个 BeanDefinition 来生成一个 LubanFactoryBean,并给构造方法的参数设置不同的值,比如伪代码如下:
BeanDefinition bd = new BeanDefinitoin();
// 注意一:设置的是 LubanFactoryBean
bd.setBeanClassName(LubanFactoryBean.class.getName());
// 注意二:表示当前 BeanDefinition 在生成 bean 对象时,会通过调用 LubanFactoryBean 的构造方法来生成,并传入 UserMapper
bd.getConstructorArgumentValues().addGenericArgumentValue(UserMapper.class.getName())
SpringContainer.addBd(bd);
特别说一下注意二,表示表示当前 BeanDefinition 在生成 bean 对象时,会通过调用 LubanFactoryBean 的构造方法来生成,并传入 UserMapper 的 Class 对象。那么在生成 LubanFactoryBean 时就会生成一个 UserMapper 接口对应的代理对象作为 bean 了。
到此为止,其实就完成了我们要解决的问题:把 Mybatis 中的代理对象作为一个 bean 放入 Spring 容器中。只是我们这里是用简单的 JDK 代理对象模拟的 Mybatis 中的代理对象,如果有时间,我们完全可以调用 Mybatis 中提供的方法区生成一个代理对象。这里就不花时间去介绍了。
Import
到这里,我们还有一个事情没有做,就是怎么真正的定义一个 BeanDefinition,并把它添加到 Spring 中,上文说到我们要利用 Import 技术,比如可以这么实现:
定义如下类:
public class LubanImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();
AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();
beanDefinition.setBeanClass(LubanFactoryBean.class);
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(UserMapper.class);
// 添加 beanDefinition
registry.registerBeanDefinition("luban"+UserMapper.class.getSimpleName(), beanDefinition);
}
}
并且在 AppConfig 上添加 @Import 注解:
@Import(LubanImportBeanDefinitionRegistrar.class)
public class AppConfig {
评论