写点什么

面试官都爱问的 Spring 源码:Spring 与 Mybatis 高级整合

  • 2021 年 11 月 12 日
  • 本文字数:4105 字

    阅读完需:约 13 分钟

假设有一个 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 对象。


所以,现在我们要解决两个问题:


  1. Mybatis 的代理对象的类型是什么?因为我们要设置给 BeanDefinition

  2. 我们怎么把 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. 代理对象对应的接口


那么答案 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);


【一线大厂Java面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义】
浏览器打开:qq.cn.hn/FTf 免费领取
复制代码


} 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 {

评论

发布
暂无评论
面试官都爱问的Spring源码:Spring与Mybatis高级整合