Spring+Mybatis源码解析
1、Spring集成Mybatis项目搭建
1.1、pom
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.learn.mybatis_info</groupId>
<artifactId>mybatis_info</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>mybatis_info</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
1.2、配置类
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import javax.sql.DataSource;
@Configuration
@MapperScan("com.learn.mybatis_info.mybatis_info.mappers")//扫描mapper
public class AppConfig {
/**
* 数据源
* @return
*/
@Bean
public DataSource getDataSource(){
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8&useSSL=false&serverTimezone=UTC");
dataSource.setUsername("root");
dataSource.setPassword("123456");
return dataSource;
}
/**
* sqlSessionFactoryBean
* @param dataSource
* @return
*/
@Bean
public SqlSessionFactoryBean getSqlSessionFactoryBean(DataSource dataSource){
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource);
return sqlSessionFactoryBean;
}
}
1.3、Mapper接口
import org.apache.ibatis.annotations.Select;
import java.util.List;
import java.util.Map;
public interface UserMapper {
@Select("select * from user")
List<Map<String,String>> findAll();
}
1.4、测试类
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Client {
public static void main(String[] args) {
//拿到上下文对象
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
//数据库查询
UserMapper userMapper = context.getBean(UserMapper.class);
System.out.println(userMapper.findAll());
}
}
2、原理解析
2.1、简单剖析
// Client.main
//数据库查询
UserMapper userMapper = context.getBean(UserMapper.class);
我们在这行代码可以发现一个问题,UserMapper是一个接口,但是我们从Spring容器中拿出来的时候却实例了一个实例对象,其实Spring会使用代理模式帮我们生成一个代理对象来操作数据库,其实使用代理模式也很简单,我们先来实现以下
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class UserMapperInvocationHandler implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//模拟查询数据库
System.out.println("查询数据库");
return null;
}
}
import com.learn.mybatis_info.mybatis_info.config.AppConfig;
import com.learn.mybatis_info.mybatis_info.handler.UserMapperInvocationHandler;
import com.learn.mybatis_info.mybatis_info.mappers.UserMapper;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import java.lang.reflect.Proxy;
public class Client {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
//动态代理为UserMapper生成代理对象
Class[] clazz = new Class[]{UserMapper.class};
UserMapper userMapper = (UserMapper) Proxy.newProxyInstance(Client.class.getClassLoader(), clazz, new UserMapperInvocationHandler());
userMapper.findAll();
}
}
上面我们就使用了代理对象去操作数据了,但是我们的DataSource,SqlSessionFactory等都是交给Spring来管理的,所以我们操作数据库肯定需要在代理对象里面注入SqlSessionFactory等,所以我们还是需要把代理对象交给Spring来管理,其实我们生成一个代理对象很简单,但是我们如何把代理对象放进Spring容器就比较困难了。通常我们把对象注册到Bean的方式无非以下几种:
- 包扫描`ComponentScan("xxx")
- @Bean
- xml配置
我们简单想一下就知道上面的传统的几种方式基本上是不可能实现的,所以我们要研究以下Spring如何加载bean把bean放进容器中的
2.2、Bean如何被Spring产生的
这里简单说一下,
我们都知道一个类被实例化出来,我们会生成一个描述这个类的class文件,Jdk会提供一个类Class会存放这个类的基本信息,比如说className、packageName、SimpleName等,然后Spring是通过包扫描或者其他方式找到这个class,然后把这个class变成BeanDefinition(如果class是用来描述类的基本信息,那么这个BeanDefinition就可以理解为Spring自己用来描述一个类的对象),然后把beanDefinition放进一个Map中
BeanDefinition:
- isAbstract:是否抽象
- isLazy:是否懒加载
- autowriteModel:是否自动注入
- scope:作用域
- beanName:bean名称
- ...等
然后Spring会根据BeanDefinition来实例化所有的Bean。但是在实例化所有的bean之前Spring会判断你有没有提供BeanFactoryPostProcessor,
BeanFactoryPostProcessor:
可以拿到BeanDefinition,然后可以修改BeanDefinition里面的属性,假设这个时候我们修改了BeanName,AMapper改成BMapper,那么我们实例化出来的就是BMapper,我们来做一个测试
@Configuration
@MapperScan("com.learn.mybatis_info.mybatis_info.mappers")
@ComponentScan("com.learn.mybatis_info.mybatis_info.mappers")//加一个包扫描 把@Component交给Spring管理
public class AppConfig {
...
}
//加包扫描
@Component
public class A {
}
//没有加包扫描
public class B {
}
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.stereotype.Component;
@Component
public class MyBeanFactoryPostPorcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
//拿到beanName是"a"的beanDefinition
GenericBeanDefinition aMapper = (GenericBeanDefinition) beanFactory.getBeanDefinition("a");
//把这个class修改为B
aMapper.setBeanClass(B.class);
}
}
import com.learn.mybatis_info.mybatis_info.config.AppConfig;
import com.learn.mybatis_info.mybatis_info.mappers.B;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Client {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
System.out.println(context.getBean("a").getClass().getSimpleName());// B
System.out.println(context.getBean(B.class).getClass().getSimpleName());//B
}
}
2.3、思路分析
上面我们知道其实Spring为我们产生Bean在最根本上跟我的扫描包没有关系,主要还是跟BeanDeFinition有关,也就是说就算我们没有让Spring扫描到我们的类,但是只要我们我们能把他放到BeanDeFinition里面,Spring就会帮我们实例化出来
那么我们很自然就会想把上面的代码MyBeanFactoryPostPorcessor
做一个这样一个思路修改
@Component
public class MyBeanFactoryPostPorcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
GenericBeanDefinition aMapper = (GenericBeanDefinition) beanFactory.getBeanDefinition("a");
aMapper.setBeanClass(B.class);
//动态代理为UserMapper生成代理对象(前面写的代码拷贝到这里)
Class[] clazz = new Class[]{UserMapper.class};
UserMapper userMapper = (UserMapper)Proxy.newProxyInstance(this.getClass().getClassLoader(), clazz, new UserMapperInvocationHandler());
/**
*如果我们在这里把UserMapper的代理对象,然后我们拿到他的BeanDeFinition,然后我们把他的 *BeanDefinition放进BeanDefinition的Map中那么我们就可以让Spring根据Map中的BeanDefinition *往beanFactory中生成bean,实现让Spring来帮我们管理了。
*但是可惜的是beanFactory只能帮我们修改beanDefinition却不能帮我们添加一个beanDifinition,
*但是却提供了api可以直接把bean放到beanFactory
*/
beanFactory.registerSingleton("test",userMapper);
}
}
public class Client {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
System.out.println(context.getBean("test").getClass().getSimpleName());// $Proxy14
}
}
这个时候我们就把代理对象放大beanFactory里面了,但是这样做还有一个最大的问题,因为UserMapperInvocationHandler
这里我们要执行数据库查询,所以我们需要拿到datasource、sqlSession、但是这些我们都交给了Spring去管理了,但是UserMapperInvocationHandler
我们这个是new 出来的 没有交给Spring管理,所以没有办法在UserMapperInvocationHandler
里面注入dataSource等,所以这种方式不能解决问题,我们抛弃。现在我们看看Mybatis是如何实现的。
2.4、Mybatis的实现方式
2.4.1、FactoryBean
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import javax.sql.DataSource;
@Configuration
//@MapperScan("com.learn.mybatis_info.mybatis_info.mappers") 把这个去掉,我们准备自己实现Mybatis
@ComponentScan("com.learn.mybatis_info.mybatis_info.mappers")
public class AppConfig {
...
}
//没有加包扫描
public class B {
}
把MyBeanFactoryPostPorcessor
这个类删除了,我们之前已经排除了这个方式了,我们使用BeanFactory
import org.springframework.beans.factory.FactoryBean;
@Component
public class MyMapperFactoryBean implements FactoryBean {
@Override
public Object getObject() throws Exception {
return new B();
}
@Override
public Class<?> getObjectType() {
return B.class;
}
}
- 它是一个普通Bean
如果我们把MyMapperFactoryBean交给Spring去管理,我们&myMapperFactoryBean
就可以吧它取出来
public class Client {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
System.out.println(context.getBean("&myMapperFactoryBean").getClass().getSimpleName());// MyMapperFactoryBean
}
}
-
他是一个特殊Bean
如果我们不使用
&
用myMapperFactoryBean
取出来的就是getObject()方法返回的类public class Client { public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); System.out.println(context.getBean("myMapperFactoryBean").getClass().getSimpleName());// B } }
2.4.2、ImportBeanDefinitionRegistrar
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.stereotype.Component;
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
/**
* 注册BeanDefinition
*/
@Override
public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
/**
* 这里我们可以通过beanDefinitionRegistry 来注册一个BeanDefinition,但是这里我们不能先通过
* Class[] clazz = new Class[]{UserMapper.class};
* UserMapper userMapper = (UserMapper) Proxy.newProxyInstance(Client.class.getClassLoader(), clazz, new UserMapperInvocationHandler());
* 拿到代理对象,因为这样代理对象已经生成了,所以我们想到了用FactoryBean,我们直接注册MyMapperFactoryBean
*/
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MyMapperFactoryBean.class);
GenericBeanDefinition beanDefinition = (GenericBeanDefinition) builder.getBeanDefinition();
beanDefinitionRegistry.registerBeanDefinition("userMapper",beanDefinition);
}
}
然后我们在FactoryBean的getObject()中返回一个代理对象
import com.learn.mybatis_info.mybatis_info.handler.UserMapperInvocationHandler;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.stereotype.Component;
import java.lang.reflect.Proxy;
//这里我们使用MyImportBeanDefinitionRegistrar来注册这个bean的BeanDefinition,所以不需要包扫描了
//@Component
public class MyMapperFactoryBean implements FactoryBean {
@Override
public Object getObject() throws Exception {
//这里我们生成代理对象
Class[] clazz = new Class[]{UserMapper.class};
UserMapper userMapper = (UserMapper) Proxy.newProxyInstance(this.getClass().getClassLoader(), clazz, new UserMapperInvocationHandler());
return userMapper;
}
@Override
public Class<?> getObjectType() {
return UserMapper.class;
}
}
也就是说,我们想注册useMapper代理对象的BeanDefinition,但是代理对象生成完了就拿不到BeanDefinition了,所以我们注册BeanFactory的BeanDefinition,又因为BeanFactory是一个特殊的bean,所以只要我们在getObject()方法中返回一个代理对象就可以,这样我们在从Spring容器中是要获取myMapperFactoryBean
就可以拿到getObject的返回的对象了
public class Client {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
System.out.println(context.getBean("userMapper").getClass().getSimpleName());// $Proxy14
}
}
到此我们已经解决了一个大问题:如何把生成的代理对象放进Spring让Spring帮我们来管理
2.4.3、动态生成代理对象
上面代码的主要问题是代码写死了,只能返回一个userMapper,我们想要的效果可以动态的根据Mapper生成我们想要的代理对象
import com.learn.mybatis_info.mybatis_info.handler.UserMapperInvocationHandler;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.stereotype.Component;
import java.lang.reflect.Proxy;
public class MyMapperFactoryBean implements FactoryBean {
private Class mapperInterface;
public MyMapperFactoryBean(){
super();
}
/**
* 构造方法接受一个对象
* @param mapperInterface
*/
public MyMapperFactoryBean(Class mapperInterface){
this.mapperInterface = mapperInterface;
}
@Override
public Object getObject() throws Exception {
//这里我们生成代理对象
Class[] clazz = new Class[]{mapperInterface};
UserMapper userMapper = (UserMapper) Proxy.newProxyInstance(this.getClass().getClassLoader(), clazz, new UserMapperInvocationHandler());
return userMapper;
}
@Override
public Class<?> getObjectType() {
return mapperInterface;
}
}
现在我们只要做到能把mapperInterface
注入进去就可以了
注:官网上面针对一个单个mapper的配置,其实原理就是用setter注入 把mapperInterface的值为org.mybatis.spring.sample.mapper.UserMapper
<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
<property name="mapperInterface" value="org.mybatis.spring.sample.mapper.UserMapper" />
<property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>
我们现在用的是在生成MyMapperFactoryBean
的BeanDefinition的时候动态修改他的构造器,为构造器传入指定参数
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
/**
* 注册BeanDefinition
*/
@Override
public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
/**
* 这里我们可以通过beanDefinitionRegistry 来注册一个BeanDefinition,但是这里我们不能先通过
* Class[] clazz = new Class[]{UserMapper.class};
* UserMapper userMapper = (UserMapper) Proxy.newProxyInstance(Client.class.getClassLoader(), clazz, new UserMapperInvocationHandler());
* 拿到代理对象,因为这样代理对象已经生成了,所以我们想到了用FactoryBean,我们直接注册MyMapperFactoryBean
*/
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MyMapperFactoryBean.class);
GenericBeanDefinition beanDefinition = (GenericBeanDefinition) builder.getBeanDefinition();
//为构造器传入指定参数
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(UserMapper.class);
//这里我们可以通过Mybatis的包扫描@MapperScan拿到所有的mapper然后拿到他的类名替代这个userMapper就行了
beanDefinitionRegistry.registerBeanDefinition("userMapper",beanDefinition);
}
}
最后我们需要让Spring知道我们MyImportBeanDefinitionRegistrar
这个类
package com.learn.mybatis_info.mybatis_info.config;
import com.learn.mybatis_info.mybatis_info.mappers.MyImportBeanDefinitionRegistrar;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import javax.sql.DataSource;
@Configuration
//@MapperScan("com.learn.mybatis_info.mybatis_info.mappers") 把这个去掉
@ComponentScan("com.learn.mybatis_info.mybatis_info.mappers")//加一个包扫描 把@Component交给Spring管理
@Import(MyImportBeanDefinitionRegistrar.class)//导入MyImportBeanDefinitionRegistrar
public class AppConfig {
...
}
下面就是见证奇迹的时候了
public class Client {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
System.out.println(context.getBean("userMapper").getClass().getSimpleName());// $Proxy15
}
}
搞定,接下来就是怎么把sql语句拿到
package com.learn.mybatis_info.mybatis_info.handler;
import org.apache.ibatis.annotations.Select;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class UserMapperInvocationHandler implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("查询数据库");
System.out.println(method.getAnnotation(Select.class).value()[0]);
return null;
}
}
让我们来执行一把
public class Client {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
UserMapper userMapper = (UserMapper)context.getBean("userMapper");
userMapper.findAll(); //select * from user
}
}
现在最后一个问题就是自己写一个@MapperScan注解
import com.learn.mybatis_info.mybatis_info.mappers.MyImportBeanDefinitionRegistrar;
import org.springframework.context.annotation.Import;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
@Import(MyImportBeanDefinitionRegistrar.class)
public @interface MyScan {
}
@Configuration
//@MapperScan("com.learn.mybatis_info.mybatis_info.mappers") 把这个去掉
@ComponentScan("com.learn.mybatis_info.mybatis_info.mappers")//加一个包扫描 把@Component交给Spring管理
//@Import(MyImportBeanDefinitionRegistrar.class)//导入MyImportBeanDefinitionRegistrar
@MyScan
public class AppConfig {
...
}
结束!!!