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 {
...
}
結束!!!