在介绍Spring整合Mybatis原理以前,咱们得先来稍微介绍Mybatis的工做原理。程序员
在Mybatis中,咱们可使用一个接口去定义要执行sql,简化代码以下:
定义一个接口,@Select表示要执行查询sql语句。web
public interface UserMapper { @Select("select * from user where id = #{id}") User selectById(Integer id); }
如下为执行sql代码:spring
InputStream inputStream = Resources.getResourceAsStream("mybatis.xml"); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); SqlSession sqlSession = sqlSessionFactory.openSession(); // 如下使咱们须要关注的重点 UserMapper mapper = sqlSession.getMapper(UserMapper.class); Integer id = 1; User user = mapper.selectById(id);
Mybatis的目的是:使得程序员可以以调用方法的方式执行某个指定的sql,将执行sql的底层逻辑进行了封装。sql
这里重点思考如下mapper这个对象,当调用SqlSession的getMapper方法时,会对传入的接口生成一个代理对象,而程序要真正用到的就是这个代理对象,在调用代理对象的方法时,Mybatis会取出该方法所对应的sql语句,而后利用JDBC去执行sql语句,最终获得结果。apache
Spring和Mybatis时,咱们重点要关注的就是这个代理对象。由于整合的目的就是:把某个Mapper的代理对象做为一个bean放入Spring容器中,使得可以像使用一个普通bean同样去使用这个代理对象,好比能被@Autowire自动注入。mybatis
好比当Spring和Mybatis整合以后,咱们就可使用以下的代码来使用Mybatis中的代理对象了:app
@Component public class UserService { @Autowired private UserMapper userMapper; public User getUserById(Integer id) { return userMapper.selectById(id); } }
UserService中的userMapper属性就会被自动注入为Mybatis中的代理对象。若是你基于一个已经完成整合的项目去调试便可发现,userMapper的类型为:org.apache.ibatis.binding.MapperProxy@41a0aa7d。证实确实是Mybatis中的代理对象。ide
好,那么如今咱们要解决的问题的就是:如何可以把Mybatis的代理对象做为一个bean放入Spring容器中?svg
要解决这个,咱们须要对Spring的bean生成过程有一个了解post
Spring启动过程当中,大体会通过以下步骤去生成bean
假设有一个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对象。
因此,如今咱们要解决两个问题:
注意:上文中咱们使用的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来帮咱们作了,得由咱们本身来作。
有,那就是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中提供的方法区生成一个代理对象。这里就不花时间去介绍了。
到这里,咱们还有一个事情没有作,就是怎么真正的定义一个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时就会新增一个BeanDefinition,该BeanDefinition会生成一个LubanFactoryBean对象,而且在生成LubanFactoryBean对象时会传入UserMapper.class对象,经过LubanFactoryBean内部的逻辑,至关于会自动生产一个UserMapper接口的代理对象做为一个bean。
总结一下,经过咱们的分析,咱们要整合Spring和Mybatis,须要咱们作的事情以下:
这样就能够基本完成整合的需求了,固然还有两个点是能够优化的
第一,单独再定义一个@LubanScan的注解,以下:
@Retention(RetentionPolicy.RUNTIME) @Import(LubanImportBeanDefinitionRegistrar.class) public @interface LubanScan { }
这样在AppConfig上直接使用@LubanScan便可
第二,在LubanImportBeanDefinitionRegistrar中,咱们能够去扫描Mapper,在LubanImportBeanDefinitionRegistrar咱们能够经过AnnotationMetadata获取到对应的@LubanScan注解,因此咱们能够在@LubanScan上设置一个value,用来指定待扫描的包路径。而后在LubanImportBeanDefinitionRegistrar中获取所设置的包路径,而后扫描该路径下的全部Mapper,生成BeanDefinition,放入Spring容器中。
因此,到此为止,Spring整合Mybatis的核心原理就结束了,再次总结一下:
以上这个三个要素分别对象org.mybatis.spring中的:
4. MapperFactoryBean
5. MapperScannerRegistrar
6. @MapperScan
视频完整讲解地址:spring整合Mybatis视频
若是对于spring整合这块儿还有疑问的话,也能够在评论区留言,博主看到会第一时间为你们解答