目录
3.8MyImportBeanDefinitionRegistrar
1.代码结构图
框红的类不需要,可以基于spring源码添加mybatis的依赖包调试,也可以建个springBoot项目,添加基本的依赖包
2.依赖
2.1基于spring源码调试
dependencies {
//log 输出
implementation group: 'org.slf4j', name: 'slf4j-api', version: '1.7.21'
implementation group: 'ch.qos.logback', name: 'logback-core', version: '1.1.7'
implementation group: 'ch.qos.logback', name: 'logback-classic', version: '1.1.7'
implementation group: 'org.projectlombok', name: 'lombok', version: '1.18.12'
//添加spring的依赖
compile(project(":spring-context"))
//数据源
compile(project(":spring-jdbc"))
//支持aop
// compile(project(":spring-aspects"))
// implementation group: 'org-aspectj', name: 'aspectjweaver', version: '1.9.6'
implementation group: 'org.springframework', name: 'spring-aspects', version: '5.3.24'
//连接池
implementation group: 'mysql', name: 'mysql-connector-java', version: '8.0.23'
//mybatis
implementation group: 'org.mybatis', name: 'mybatis', version: '3.5.5'
//mybatis 和 spring 的 插件包
implementation group: 'org.mybatis', name: 'mybatis-spring', version: '2.0.5'
testCompile group: 'junit', name: 'junit', version: '4.12'
}
2.2基于springboot调试
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.6.4</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.3</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.18</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.20</version>
</dependency>
3.代码
3.1BatisConfig
package com.spring.mybatis.config;
import com.spring.mybatis.util.MyMapperScan;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
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;
/**
* @Description:
* @Author: tangHC
* @Date: 2023/6/21 0:32
*/
@Configuration
//@MapperScan("com.spring.mybatis.dao")
@ComponentScan("com.spring.mybatis")
@MyMapperScan("com.spring.mybatis.dao")
public class BatisConfig {
@Bean
public DataSource dataSource(){
DriverManagerDataSource source = new DriverManagerDataSource();
source.setDriverClassName("com.mysql.cj.jdbc.Driver");
source.setPassword("root");
source.setUsername("root");
source.setUrl("jdbc:mysql://localhost:3306/stu?serverTimezone=Asia/Shanghai&useAffectedRows=true&useSSL=false&allowPublicKeyRetrieval=true");
return source;
}
@Bean
@Autowired
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(dataSource);
//如果有配置xml就引入
// factoryBean.setConfigLocation(new ClassPathResource("mysql-config.xml"));
return factoryBean.getObject();
}
@Bean
public DataSourceTransactionManager dataSourceTransactionManager(DataSource dataSource){
return new DataSourceTransactionManager(dataSource);
}
}
3.2Student
@Data
public class Student implements Serializable {
private Integer uid;
private String name;
}
3.3UserDao
@Repository
public interface UserDao {
@Select("select * FROM student where uid = 1")
Student select();
}
3.4UserService
public interface UserService {
void select();
}
3.5UserServiceImpl
package com.spring.mybatis.service.impl;
import com.spring.mybatis.dao.UserDao;
import com.spring.mybatis.entity.Student;
import com.spring.mybatis.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
@Slf4j(topic = "e")
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;
@Override
public void select() {
Student student = userDao.select();
log.info("结果: " + student.toString());
}
}
3.6MySqlSession
package com.spring.mybatis.util;
import com.spring.mybatis.entity.Student;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.annotations.Select;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* @Description:
* @Author: tangHC
* @Date: 2023/6/21 1:15
*/
@Slf4j(topic = "e")
public class MySqlSession {
public Object getMapper(Class<?> type) {
//动态代理会重写toString 默认空实现
return Proxy.newProxyInstance(MySqlSession.class.getClassLoader(),
new Class[]{type},
new MyMapperProxy());
}
class MyMapperProxy implements InvocationHandler{
/**
* 完成所有代理功能 得到sql 执行sql
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.isAnnotationPresent(Select.class)){
Select annotation = method.getAnnotation(Select.class);
String sql = annotation.value()[0];
log.info("sql: {}",sql);
log.info("jdbc conn ...");
log.info("execute query ...");
}
if (method.getName().equals("toString")){
return proxy.getClass().getName();
}
Student student = new Student();
student.setUid(1);
student.setName("代理模拟返回查询结果");
return student;
}
}
}
3.7MyFactoryBean
package com.spring.mybatis.util;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.FactoryBean;
/**
* @Description:
* @Author: tangHC
* @Date: 2023/6/21 14:17
*/
/**
* FactoryBean 本身是一个bean,即生效需要把自己 MyFactoryBean 交给spring
* 可以返回一个bean
*/
@Slf4j(topic = "e")
public class MyFactoryBean implements FactoryBean {
Class<?> mapperInterface;
public MyFactoryBean(){
log.info("MyFactoryBean 无参构造");
}
/**
* 使用构造函数注入
* beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(aClass);
*/
public MyFactoryBean(Class<?> mapperInterface){
log.info("MyFactoryBean 有参构造");
this.mapperInterface = mapperInterface;
}
/**
* 使用set方法注入
* beanDefinition.getPropertyValues().add("mapperInterface", aClass);这种方式添加参数
* 初始化时PropertyValues的参数赋值,需要set方法
*/
public void setMapperInterface(Class<?> mapperInterface){
this.mapperInterface = mapperInterface;
}
@Override
public Object getObject() throws Exception {
MySqlSession sqlSession = new MySqlSession();
//FactoryBean 的 getObject() 方法 return 的对象,会交给spring容器管理
return sqlSession.getMapper(mapperInterface);
}
@Override
public Class<?> getObjectType() {
return mapperInterface;
}
}
3.8MyImportBeanDefinitionRegistrar
package com.spring.mybatis.util;
import com.spring.mybatis.dao.UserDao;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.annotation.MergedAnnotation;
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.core.type.AnnotationMetadata;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* @Description:
* @Author: tangHC
* @Date: 2023/6/21 16:13
*/
/**
* ImportBeanDefinitionRegistrar
* 此时BeanDefinition已经完成了扫描,用来注册一个beanDefinition到beanDefinitionMap
*/
@Slf4j(topic = "e")
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
//假设完成了扫描
getScanPackage(importingClassMetadata);
/**
* 测试用,直接是Class,而且 beanDefinition 直接设置为 FactoryBean 类型的
* mybatis的 doScan,把包下的dao都扫描完成,形成 beanDefinition,然后把每个bd设置为mapperFactBean,
* 所以 list 里是一个个 dao 的 BeanDefinition,把 FactoryBean代理的对象 aClass 设置到构造函数参数里,
* 然后 通过 beanDefinition.setBeanClass(MyFactoryBean.class) 把当前 dao 的BeanClass类型,设置为
* FactoryBean 类型。
*/
List<Class<?>> list = new ArrayList<>();
list.add(UserDao.class);
for (Class<?> aClass : list) {
//把 MyFactoryBean 加工成一个 beanDefinition 放到 beanDefinitionMap
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MyFactoryBean.class);
AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();
/**
* mybatis 真实使用的方式
* beanDefinition.setBeanClass(MyFactoryBean.class);
*/
/**
* PropertyValues在beanDefinition中存这个类 MyFactoryBean 的属性和值,
* 添加属性值需要有提供的set方法即MyFactoryBean.setMapperInterface方法
*/
// beanDefinition.getPropertyValues().add("mapperInterface", aClass);
/**
* 设置构造函数的参数,MyFactoryBean需要提供对应的有参构造函数
* ConstructorArgumentValues里面没值,走无参构造
*/
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(aClass);
String name = aClass.getSimpleName();
String beanName = toLowerCaseFirstChar(name);
registry.registerBeanDefinition(beanName, beanDefinition);
}
}
public static void getScanPackage(AnnotationMetadata importingClassMetadata){
MergedAnnotations annotations = importingClassMetadata.getAnnotations();
MergedAnnotation<MyMapperScan> annotation = annotations.get(MyMapperScan.class);
if (annotation.isPresent()) {
Map<String, Object> attributes = annotation.asAnnotationAttributes();
String[] value = (String[])attributes.get("value");
if (value != null && value.length > 0) {
String basePackage = value[0];
//要扫描的包名
log.info(basePackage);
}
}
}
public static String toLowerCaseFirstChar(String str) {
if (str == null || str.isEmpty()) {
return str;
}
char firstChar = Character.toLowerCase(str.charAt(0));
if (str.length() == 1) {
return String.valueOf(firstChar);
} else {
return firstChar + str.substring(1);
}
}
}
3.9MyMapperScan
package com.spring.mybatis.util;
import org.springframework.context.annotation.Import;
import java.lang.annotation.*;
/**
* @Description:
* @Author: tangHC
* @Date: 2023/6/21 16:55
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({MyImportBeanDefinitionRegistrar.class})
public @interface MyMapperScan {
String[] value() default {};
}
4.测试
4.1测试类
package com.spring.mybatis.main;
import com.spring.mybatis.config.BatisConfig;
import com.spring.mybatis.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
/**
* @Description: 测试 mybatis 整合到 spring
* @Author: tangHC
* @Date: 2023/6/21 0:40
*/
@Slf4j(topic = "e")
public class MybatisMain {
@Test
public void MybatisMain03(){
//执行mybatis操作 注释掉:BatisConfig 上的 @MapperScan 关闭了mybatis的代理
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(BatisConfig.class);
context.refresh();
UserService bean = context.getBean(UserService.class);
bean.select();
}
}
4.2结果
"C:\Program Files\Java\jdk1.8.0_131\bin\java.exe" -ea -Didea.test.cyclic.buffer.size=1048576 "-javaagent:D:\software\idea2019\IntelliJ IDEA 2019.2.4\lib\idea_rt.jar=61135:D:\software\idea2019\IntelliJ IDEA 2019.2.4\bin" -Dfile.encoding=UTF-8 -classpath "D:\software\idea2019\IntelliJ IDEA 2019.2.4\lib\idea_rt.jar;D:\software\idea2019\IntelliJ IDEA 2019.2.4\plugins\junit\lib\junit-rt.jar;D:\software\idea2019\IntelliJ IDEA 2019.2.4\plugins\junit\lib\junit5-rt.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\rt.jar;D:\java_code\spring-framework-5.2.x\spring-aaMy\out\production\classes;D:\java_code\spring-framework-5.2.x\spring-aaMy\out\production\resources;D:\java_code\spring-framework-5.2.x\spring-context\out\production\classes;D:\java_code\spring-framework-5.2.x\spring-context\out\production\resources;D:\java_code\spring-framework-5.2.x\spring-jdbc\out\production\classes;D:\java_code\spring-framework-5.2.x\spring-jdbc\out\production\resources;D:\gradle\caches\modules-2\files-2.1\ch.qos.logback\logback-classic\1.1.7\9865cf6994f9ff13fce0bf93f2054ef6c65bb462\logback-classic-1.1.7.jar;D:\gradle\caches\modules-2\files-2.1\org.slf4j\slf4j-api\1.7.21\139535a69a4239db087de9bab0bee568bf8e0b70\slf4j-api-1.7.21.jar;D:\gradle\caches\modules-2\files-2.1\ch.qos.logback\logback-core\1.1.7\7873092d39ef741575ca91378a6a21c388363ac8\logback-core-1.1.7.jar;D:\gradle\caches\modules-2\files-2.1\org.projectlombok\lombok\1.18.12\48e4e5d60309ebd833bc528dcf77668eab3cd72c\lombok-1.18.12.jar;D:\gradle\caches\modules-2\files-2.1\org.springframework\spring-aspects\5.3.24\e97d36c3e516d3dd0579437428b6e26902da0c88\spring-aspects-5.3.24.jar;D:\gradle\caches\modules-2\files-2.1\mysql\mysql-connector-java\8.0.23\14d64ac509adccaff444513c12646d93e4ed8be8\mysql-connector-java-8.0.23.jar;D:\gradle\caches\modules-2\files-2.1\org.mybatis\mybatis\3.5.5\1c8974cdec88c9259b6fa21147ea4538216e447\mybatis-3.5.5.jar;D:\gradle\caches\modules-2\files-2.1\org.mybatis\mybatis-spring\2.0.5\3bfeffacf579b7f607486c1cd32224643102c316\mybatis-spring-2.0.5.jar;D:\java_code\spring-framework-5.2.x\spring-aop\out\production\classes;D:\java_code\spring-framework-5.2.x\spring-aop\out\production\resources;D:\java_code\spring-framework-5.2.x\spring-tx\out\production\classes;D:\java_code\spring-framework-5.2.x\spring-tx\out\production\resources;D:\java_code\spring-framework-5.2.x\spring-beans\out\production\classes;D:\java_code\spring-framework-5.2.x\spring-beans\out\production\resources;D:\java_code\spring-framework-5.2.x\spring-expression\out\production\classes;D:\java_code\spring-framework-5.2.x\spring-expression\out\production\resources;D:\java_code\spring-framework-5.2.x\spring-core\out\production\classes;D:\java_code\spring-framework-5.2.x\spring-core\out\production\resources;D:\java_code\spring-framework-5.2.x\spring-core\build\libs\spring-cglib-repack-3.3.0.jar;D:\java_code\spring-framework-5.2.x\spring-core\build\libs\spring-objenesis-repack-3.1.jar;D:\gradle\caches\modules-2\files-2.1\org.aspectj\aspectjweaver\1.9.6\ee3b73aa16df35179255f17354d9dfd8e7822835\aspectjweaver-1.9.6.jar;D:\gradle\caches\modules-2\files-2.1\com.google.code.findbugs\jsr305\3.0.2\25ea2e8b0c338a877313bd4672d3fe056ea78f0d\jsr305-3.0.2.jar;D:\gradle\caches\modules-2\files-2.1\com.google.protobuf\protobuf-java\3.11.4\7ec0925cc3aef0335bbc7d57edfd42b0f86f8267\protobuf-java-3.11.4.jar;D:\java_code\spring-framework-5.2.x\spring-jcl\out\production\classes;D:\java_code\spring-framework-5.2.x\spring-jcl\out\production\resources;D:\java_code\spring-framework-5.2.x\lib\junit-4.12.jar;D:\java_code\spring-framework-5.2.x\lib\hamcrest-core-1.3.jar" com.intellij.rt.execution.junit.JUnitStarter -ideVersion5 -junit4 com.spring.mybatis.main.MybatisMain,MybatisMain03
2023-11-30 11:16:38 DEBUG 219 --- [ main] s.c.a.AnnotationConfigApplicationContext : Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@1810399e
2023-11-30 11:16:38 DEBUG 234 --- [ main] o.s.b.f.s.DefaultListableBeanFactory : Creating shared instance of singleton bean 'org.springframework.context.annotation.internalConfigurationAnnotationProcessor'
2023-11-30 11:16:38 DEBUG 297 --- [ main] o.s.c.a.ClassPathBeanDefinitionScanner : Ignored because not a concrete top-level class: file [D:\java_code\spring-framework-5.2.x\spring-aaMy\out\production\classes\com\spring\mybatis\dao\UserDao.class]
2023-11-30 11:16:38 DEBUG 297 --- [ main] o.s.c.a.ClassPathBeanDefinitionScanner : Identified candidate component class: file [D:\java_code\spring-framework-5.2.x\spring-aaMy\out\production\classes\com\spring\mybatis\service\impl\UserServiceImpl.class]
MyImportBeanDefinitionRegistrar.java l=77 11:16:38.803 [main] INFO e - com.spring.mybatis.dao
2023-11-30 11:16:38 DEBUG 391 --- [ main] o.s.b.f.s.DefaultListableBeanFactory : Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerProcessor'
2023-11-30 11:16:38 DEBUG 391 --- [ main] o.s.b.f.s.DefaultListableBeanFactory : Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerFactory'
2023-11-30 11:16:38 DEBUG 391 --- [ main] o.s.b.f.s.DefaultListableBeanFactory : Creating shared instance of singleton bean 'org.springframework.context.annotation.internalAutowiredAnnotationProcessor'
2023-11-30 11:16:38 DEBUG 391 --- [ main] o.s.b.f.s.DefaultListableBeanFactory : Creating shared instance of singleton bean 'org.springframework.context.annotation.internalCommonAnnotationProcessor'
2023-11-30 11:16:38 DEBUG 406 --- [ main] o.s.b.f.s.DefaultListableBeanFactory : Creating shared instance of singleton bean 'batisConfig'
2023-11-30 11:16:38 DEBUG 422 --- [ main] o.s.b.f.s.DefaultListableBeanFactory : Creating shared instance of singleton bean 'userServiceImpl'
MyFactoryBean.java l=31 11:16:38.944 [main] INFO e - MyFactoryBean 有参构造
2023-11-30 11:16:38 DEBUG 453 --- [ main] o.s.b.f.s.DefaultListableBeanFactory : Creating shared instance of singleton bean 'userDao'
2023-11-30 11:16:38 DEBUG 453 --- [ main] o.s.b.f.s.DefaultListableBeanFactory : Creating shared instance of singleton bean 'dataSource'
2023-11-30 11:16:38 DEBUG 469 --- [ main] o.s.j.d.DriverManagerDataSource : Loaded JDBC driver: com.mysql.cj.jdbc.Driver
2023-11-30 11:16:38 DEBUG 469 --- [ main] o.s.b.f.s.DefaultListableBeanFactory : Creating shared instance of singleton bean 'sqlSessionFactoryBean'
2023-11-30 11:16:38 DEBUG 469 --- [ main] o.s.b.f.s.DefaultListableBeanFactory : Autowiring by type from bean name 'sqlSessionFactoryBean' via factory method to bean named 'dataSource'
2023-11-30 11:16:38 DEBUG 484 --- [ main] org.apache.ibatis.logging.LogFactory : Logging initialized using 'class org.apache.ibatis.logging.slf4j.Slf4jImpl' adapter.
2023-11-30 11:16:38 DEBUG 484 --- [ main] o.mybatis.spring.SqlSessionFactoryBean : Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration
2023-11-30 11:16:39 DEBUG 532 --- [ main] o.mybatis.spring.SqlSessionFactoryBean : Property 'mapperLocations' was not specified.
MySqlSession.java l=37 11:16:39.038 [main] INFO e - sql: select * FROM student where uid = 1
MySqlSession.java l=38 11:16:39.038 [main] INFO e - jdbc conn ...
MySqlSession.java l=39 11:16:39.038 [main] INFO e - execute query ...
UserServiceImpl.java l=22 11:16:39.038 [main] INFO e - 结果: Student(uid=1, name=代理模拟返回查询结果)
Process finished with exit code 0
5.源码分析
对于mybatis整合spring,主要问题是解决以下几点:
1.在扫描时,即@MyMapperScan("com.spring.mybatis.dao")扫描包,spring本身不会扫接口,所以需要扩展扫描
2.扫描后的dao层的接口是不能用的,需要代理成对象,而spring只提供接口,实现就得mybatis自己来
3.代理对象只能由mybatis完成,但是注入的对象是spring提供的,所以需要把代理对象交给spring管理
5.0简介
1.后文 beanDefinitionMap 简称bdMap;beanDefinition简称bd,beanDefinition是类信息的抽象,存储是否单例、是否懒加载、是否抽象、类属性值等等,就是用来描述这个类用于后面实例化和初始化。
2.ImportBeanDefinitionRegistrar接口,spring提供的回调方法,可以用户自己提供的BeanDefinition到bdMap,此时还处于整体开始实例化bean之前。
3.FactoryBean(工厂bean)本身是一个bean,同样他的getObject()方法,可以返回一个bean
4.BeanDefinitionRegistryPostProcessor是bean工厂后置处理器BeanFactoryPostProcessor的子类接口,执行时,先执行实现了子类的回调,在执行实现了父类的回调。
5.还是4.1的测试类,我们把BatisConfig类的上的MapperScan注释放开,把我们自己扩展的MyMapperScan注释掉,开始后面的调试。
5.1mybatis扩展的spring的扫描
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {
1.这是我们常用的@MapperScan("com.xxx"),其中@Import注解,在spring解析配置类时,会拿到Import的这个类
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
@Override
@Deprecated
public void setResourceLoader(ResourceLoader resourceLoader) {
// NOP
}
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
AnnotationAttributes mapperScanAttrs = AnnotationAttributes
.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
if (mapperScanAttrs != null) {
registerBeanDefinitions(importingClassMetadata, mapperScanAttrs, registry,
generateBaseBeanName(importingClassMetadata, 0));
}
}
void registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs,
BeanDefinitionRegistry registry, String beanName) {
//这里直接封装bd,类是MapperScannerConfigurer
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
builder.addPropertyValue("processPropertyPlaceHolders", true);
//省略部分代码
builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(basePackages));
registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
}
2.ImportBeanDefinitionRegistrar接口的回调registerBeanDefinitions方法执行时,又包装了MapperScannerConfigurer.class为beanDefinition,放到bdMap里
public class MapperScannerConfigurer
implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
3. MapperScannerConfigurer类继承了BeanDefinitionRegistryPostProcessor类,这是bean工厂后置处理器BeanFactoryPostProcessor的子类,所以在spring执行的invokeBeanFactoryPostProcessors(beanFactory)方法(bean工厂后置处理器执行方法)时,MapperScannerConfigurer实现BeanDefinitionRegistryPostProcessor类的回调方法将被执行
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
if (this.processPropertyPlaceHolders) {
processPropertyPlaceHolders();
}
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
scanner.setAddToConfig(this.addToConfig);
scanner.setAnnotationClass(this.annotationClass);
scanner.setMarkerInterface(this.markerInterface);
scanner.setSqlSessionFactory(this.sqlSessionFactory);
scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
scanner.setResourceLoader(this.applicationContext);
scanner.setBeanNameGenerator(this.nameGenerator);
scanner.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);
if (StringUtils.hasText(lazyInitialization)) {
scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));
}
scanner.registerFilters();
scanner.scan(
StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
4.自己实例化扫描器类ClassPathMapperScanner,继承了spring的扫描器ClassPathBeanDefinitionScanner,随后 scanner.registerFilters();
public void registerFilters() {
//通过所有接口
boolean acceptAllInterfaces = true;
// if specified, use the given annotation and / or marker interface
if (this.annotationClass != null) {
//如果配了annotationClass = Mapper.class,则只有dao层加了Mapper的才被扫描
addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));
acceptAllInterfaces = false;
}
//省略部分代码
if (acceptAllInterfaces) {
//扫描规则默认返回true
addIncludeFilter((metadataReader, metadataReaderFactory) -> true);
}
}
5.添加过滤规则,如果@MapperScan(value = "com.spring.mybatis.dao", annotationClass = Mapper.class)则进入第一个if条件,如果没加 annotationClass = Mapper.class 则进入第二个if条件,全返回true,即可以扫描到接口
@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
if (beanDefinitions.isEmpty()) {
LOGGER.warn(() -> "No MyBatis mapper was found in '" + Arrays.toString(basePackages)
+ "' package. Please check your configuration.");
} else {
processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
}
6.调用super.doScan(basePackages);意味着使用spring的扫描方法来扫描,那么这些接口将被包装为一个个beanDefinition,放入bdMap,至此扫描结束。
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
GenericBeanDefinition definition;
for (BeanDefinitionHolder holder : beanDefinitions) {
//1.此时mybatis扫描完毕,@MapperScan注解里提供的包名就是扫描的包路径
definition = (GenericBeanDefinition) holder.getBeanDefinition();
//2.循环这些返回的bd,得到类名,也就是接口名称
String beanClassName = definition.getBeanClassName();
//3.给构造函数的参数设置值
definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);
//4.设置beanClass即当前bd描述的类的类型被改为mapperFactoryBeanClass->MapperFactoryBean
definition.setBeanClass(this.mapperFactoryBeanClass);
definition.setLazyInit(lazyInitialization);
}
}
7.上面这段代码,是不是类似我们的模拟代码 3.8MyImportBeanDefinitionRegistrar,通过这4步,在实例化时,将采用有参构造,参数是我们的接口名(这里是字符串,但是有全类名,对spring来说,全类名的字符串和类class一样),此时类类型是FactoryBean,除了名字是userDao,内里已经基本全部替换。
5.2mybatis把自己的代理对象交给spring
@Test
public void MybatisMain03(){
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(BatisConfig.class);
context.refresh();
UserService bean = context.getBean(UserService.class);
bean.select();
}
1.还是4.1的测试类,在context.refresh();之后,开始了整个容器的刷新过程。
/**
* 此时校验是从最新的 mergedBeanDefinitions 中拿的,冻结也不好使
* 在次判断是否单例
*/
if (mbd.isSingleton()) {
/**
* getSingleton方法
* 0.在singletonsCurrentlyInCreation添加beanName,表示当前bean已经开始创建
* 1.创建bean 2.放入单例池
*/
sharedInstance = getSingleton(beanName, () -> {
try {
// 创建bean
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
destroySingleton(beanName);
throw ex;
}
});
//如果是工厂bean,返回自定义的,不是则直接返回
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
2.这段代码是我们的createBean方法,在返回创建完的sharedInstance对象后,bean的生命周期基本已经完成,但是最后一行代码就是我们把代理类交给spring返回的入口。
3.记住此时的sharedInstance对象,他的类型是MapperFactoryBean,这个类继承了FactoryBean,还记的我们的 3.7章节的 MyFactoryBean吗?程序接着往下走
private Object doGetObjectFromFactoryBean(FactoryBean<?> factory, String beanName) throws BeanCreationException {
Object object;
try {
if (System.getSecurityManager() != null) {
AccessControlContext acc = getAccessControlContext();
try {
object = AccessController.doPrivileged((PrivilegedExceptionAction<Object>) factory::getObject, acc);
}
catch (PrivilegedActionException pae) {
throw pae.getException();
}
}
else {
//调用factoryBean的getObject方法,返回bean
object = factory.getObject();
}
}
4.终于看到了熟悉的factory.getObject();FactoryBean(工厂bean)可以返回一个bean交给spring管理。getObject方法在往下走一步
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
private Class<T> mapperInterface;
private boolean addToConfig = true;
public MapperFactoryBean() {
// intentionally empty
}
public MapperFactoryBean(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
@Override
protected void checkDaoConfig() {
super.checkDaoConfig();
notNull(this.mapperInterface, "Property 'mapperInterface' is required");
Configuration configuration = getSqlSession().getConfiguration();
if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
try {
configuration.addMapper(this.mapperInterface);
} catch (Exception e) {
logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", e);
throw new IllegalArgumentException(e);
} finally {
ErrorContext.instance().reset();
}
}
}
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
@Override
public Class<T> getObjectType() {
return this.mapperInterface;
}
@Override
public boolean isSingleton() {
return true;
}
public void setMapperInterface(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
public Class<T> getMapperInterface() {
return mapperInterface;
}
public void setAddToConfig(boolean addToConfig) {
this.addToConfig = addToConfig;
}
public boolean isAddToConfig() {
return addToConfig;
}
}
5.对比我们 3.7MyFactoryBean,是不是极为相似,调用到MapperFactoryBean的getObject()方法,通过sqlSession.getMapper()返回代理对象,bean工厂的功能,返回一个bean给spring容器,至此容器中的userDao已经是一个正经的类了。
5.3代理类是怎么生成及功能
public class MapperProxyFactory<T> {
private final Class<T> mapperInterface;
private final Map<Method, MapperMethodInvoker> methodCache = new ConcurrentHashMap<>();
public MapperProxyFactory(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
public Class<T> getMapperInterface() {
return mapperInterface;
}
public Map<Method, MapperMethodInvoker> getMethodCache() {
return methodCache;
}
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
}
1.顺着return getSqlSession().getMapper(this.mapperInterface);方法接着往下走,最终调用newInstance方法完成动态代理,此时代理类返回。
需要注意的是参数mapperProxy是继承了InvocationHandler的,所以,代理的逻辑就在mapperProxy的invoke方法里。在3.5UserServiceImpl类中,执行userDao.select();方法时,执行的就是代理方法。
public class MapperProxy<T> implements InvocationHandler, Serializable {
private static final long serialVersionUID = -4724728412955527868L;
private static final int ALLOWED_MODES = MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED
| MethodHandles.Lookup.PACKAGE | MethodHandles.Lookup.PUBLIC;
private static final Constructor<Lookup> lookupConstructor;
private static final Method privateLookupInMethod;
private final SqlSession sqlSession;
private final Class<T> mapperInterface;
private final Map<Method, MapperMethodInvoker> methodCache;
public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethodInvoker> methodCache) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else {
return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
2. 此时的SqlSession是SqlSessionTemplate,目前只是SqlSessionFactory和数据源DataSource已经创建,真正的openSession还没调用,所以目前SqlSession只是个引用对象。
public class SqlSessionTemplate implements SqlSession, DisposableBean {
private final SqlSessionFactory sqlSessionFactory;
private final ExecutorType executorType;
private final SqlSession sqlSessionProxy;
private final PersistenceExceptionTranslator exceptionTranslator;
private class SqlSessionInterceptor implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//getSqlSession将调用mybatis的
//session = sessionFactory.openSession(executorType);方法,
SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
try {
//method.invoke方法和纯mybatis的方法还是有点不一样的,这一步就是userDao.select();的代理
Object result = method.invoke(sqlSession, args);
if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
sqlSession.commit(true);
}
return result;
} catch (Throwable t) {
} finally {
if (sqlSession != null) {
//这里关闭了sqlSession,放入连接池,即一级缓存不生效,因为每次查询都是一个新的session
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
}
}
3.上面的getSqlSession方法,后续调用的openSession方法这里就不展开了,还有method.invoke后面具体调用的mybatis相关的代码也是一样。本篇主要讲mybatis整合spring,下面通过两个示例代码的注释大家对比下纯mybatis和mybatis整合spring后的区别。
代码片段1,spring和mybatis
@Service
@Slf4j(topic = "e")
public class UserServiceImpl implements UserService {
/**
* 在初始化UserServiceImpl时,需要填充属性userDao
*/
@Autowired
private UserDao userDao;
@Override
public void select() {
/**
* 1.获取工厂对象创建sqlSession
* 2.调用插件代理Executor的方法
* 3.建立被spring管理的事务对象和连接
* 4.执行真实的sql语句
* 5.提交commit,spring整合mybatis,默认事务手动提交
* 6.sqlSession.close关闭sqlSession,所以一级缓存不生效,每次查询都是一个全新的sqlSession
*/
List<Student> student = userDao.select();
log.info("结果: " + student.toString());
}
}
代码片段2,纯mybatis
@Slf4j
public class MybatisApplication {
public static void main(String[] args) throws Exception {
/**
* 第一步,通过classLoader读取 mybatis-config.xml 配置文件
*/
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
/**
* 第二步
* 1.读取 .dtd文件(mybatis文件的标签规范),把流文件解析为document,再下一步解析成xNode对象
* 2.开始进行Configuration对象的初始化,从xNode里把标签值拿出来封装要用的对象,比如数据源、事务工厂
* 3.构建 sqlSessionFactory 框架初始化 底层等于 new DefaultSqlSessionFactory(config)
* 相当于创建了一个 DefaultSqlSessionFactory对象,通过构造持有 Configuration(包含整个xml解析后的标签信息 + 事务工厂 + 数据源对象)的引用
*/
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
/**
* 第三步,打开 sqlSession,返回了 DefaultSqlSession对象
* 此对象持有Configuration、
* 和执行器,包含 事务对象 + datasource数据源,和操作sql的 Executor执行器接口的实现类
*/
SqlSession sqlSession = sqlSessionFactory.openSession();
/**
* 第四步,获取第二步解析xml得到的接口,通过动态代理生成代理对象
* 代理的业务逻辑即增删改查等在 MapperProxy对象的invoke里
*/
UserDao mapper = sqlSession.getMapper(UserDao.class);
/**
* 第五步,调用接口执行操作数据库的方法,调用代理类的invoke方法
* 1.找到要执行的sql类型(select、update等),以及确定是否有返回值及返回一个还是多个,通过 MapperMethod来执行方法
* 2.预编译sql语句,其中首先加载驱动、获取连接、代理增强(连接池)、代理增强(日志打印)、在通过conn获取statement
* (封装了下面jdbc的1、2、3、4步)
* 3.执行sql,封装结果集返回,根据映射关系 resultType 或 resultMap拿到实体属性和数据库字段的对应关系
* 对返回结果进行逐行逐循环处理,通过反射创建要返回的对象,然后反射调用set方法给对象中的字段赋值
*/
Student select = mapper.select();
log.info(select.toString());
/**
* 第六步,关闭,close方法被代理,连接放入空闲连接池
*/
sqlSession.close();
}
4,通过上面两个代码可以看出,和spring整合后,spring容器在初始化后,sqlSessionFactory和dataSource和userDao对象已经在容器中,即第二个代码的第一和第二步和第四步已经完成。而在第一个代码执行select方法时,内部完成了第二个代码的第三步和第五步和第六步。
6.总结
1.我们在dao层写的使用mybatis查询数据库的方法,都是一个个接口,在使用查询方法时,通过@Autowired 注入比如userDao,然后使用userDao的方法,此时的userDao已经不是一个接口,而是一个bean,所以mybatis实现对spring的整合,需要生成接口的代理对象,并且把代理对象交给spring容器管理。
2.mybatis生成代理对象是在sqlSession.getMapper(UserDao.class)这一步,
通过动态代理为传入的接口生成代理对象,其中完成了从xml中解析sql语句,通过SqlSessionFactory产生sqlSession,并最终执行sql语句的方法。
3.代理对象生成后,需要做的就是把代理对象交给spring容器管理,此时需要用到spring中的FactoryBean接口和ImportBeanDefinitionRegistrar接口,mybatis实现FactoryBean的类叫做mapperFactoryBean,可以通过getObject方法返回一个对象,会被交给spring管理,mybatis实现ImportBeanDefinitionRegistrar的类叫做MapperScannerRegistrar,顾名思义,可以注册BeanDefinition到BeanDefinitionMap中,mybatis通过doScan方法解析出MapperScan注解中要扫描的包,并且把包下相关的接口全部生成对应的BeanDefinition,然后设置BeanDefinition的BeanClass为mapperFactoryBean类型,生成代理对象需要传入代理的接口,把接口也设置进BeanDefinition中,在执行实现了mapperFactoryBean的方法时,在BeanDefinition里拿到要代理的接口,在getObject方法里完成动态代理。
4.最后一步,就是把MapperScannerRegistrar类通过@Import注解导入,作为入口,让spring开始解析。