Spring-Spring 整合 MyBatis 原理分析
![Spring-Spring整合MyBatis原理分析](https://static001.geekbang.org/infoq/b3/b37718ec429970d879340705688c980b.png)
对于 CRUD boy 天天用 SSM 框架,可是问起 Spring 如何整合 MyBatis 的原理却支支吾吾,不能很好的说出底层的原理
Mybatis 也随着 spring 的版本变化在新版本也做出了一些调整,能说说都有哪些调整吗?
你如何能手写一个 MyBatis 框架吗?
模拟 Spring 整合 Mybatis
@Component
public class UserService {
@Autowired
OrderMapper orderMapper;
@Autowired
UserMapper userMapper;
public void test(){
orderMapper.selectById();
userMapper.insert1();
}
}
public interface UserMapper {
@Insert("insert into t values(1,'1','1',1,'1')")
void insert1();
@Insert("insert into t values(2,'1','1',1,'1')")
void insert2();
}
public interface OrderMapper {
@Select("select 'test'")
String selectById();
}
@Component
public class MyBatisFactoryBean implements FactoryBean {
private Class mapperInterface;
public MyBatisFactoryBean() {
}
public MyBatisFactoryBean(Class mapperInterface) {
this.mapperInterface = mapperInterface;
}
@Override
public Object getObject() throws Exception {
Object o = Proxy.newProxyInstance(MyBatisFactoryBean.class.getClassLoader(), new Class[] {mapperInterface},
new InvocationHandler() {
@Override
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
System.out.println(method.getName());
return null;
}
});
return o;
}
@Override
public Class<?> getObjectType() {
return mapperInterface;
}
}
public class MyBatisBeanDefinitionRegistar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
List<Class> mapperList = new ArrayList<>();
mapperList.add(OrderMapper.class);
mapperList.add(UserMapper.class);
for(Class s:mapperList){
AbstractBeanDefinition bd = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
bd.setBeanClass(MyBatisFactoryBean.class);
bd.getConstructorArgumentValues().addGenericArgumentValue(s);
registry.registerBeanDefinition(s.getName(), bd);
}
}
}
@ComponentScan({"com.spring.test.ch05"})
@Import(value = {MyBatisBeanDefinitionRegistar.class})
public class Ch05Config {
}
public class Ch05Test {
public static void main(String[] args) {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext();
ac.register(Ch05Config.class);
ac.refresh();
UserService userService = (UserService)ac.getBean("userService");
userService.test();
}
}
运行结果:
![](https://static001.geekbang.org/infoq/0d/0d503089b1b6ebf3d164e9b0b001cefa.png)
实现思路:
实现 FactoryBean,在 getObject 方法中用返回接口的代理对象,getObject 方法中需要 Mybatis 来生成代理对象
把指定路径下的 Mapper 需要扫描出来,然后变成对应的 BeanDefinition 存入 BeanFactory 中;
myBatis-spring 2.0.5
使用姿势是需要在配置类引入注解,标识需要扫描那些 mapper
@ComponentScan({"com.spring.test.ch05"})
@MapperScan({"com.spring.test.ch05"})
public class Ch05Config {
@Bean
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setUrl("jdbc:mysql://127.0.0.1:3306/test?useSSL=false");
dataSource.setUsername("root");
dataSource.setPassword("123456");
return dataSource;
}
//
@Bean
public SqlSessionFactory sqlSessionFactory() {
SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean();
sessionFactoryBean.setDataSource(dataSource());
try {
return sessionFactoryBean.getObject();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
点击 @MapperScan 注解,可以看到 @Import(MapperScannerRegistrar.class),这个就很关键了;MapperScannerRegistrar 类实现了 ImportBeanDefinitionRegistrar 类,该类从名字就能猜到可以进行 BeanDefinition 的注册。
/**
* {@inheritDoc}
*/
@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) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
builder.addPropertyValue("processPropertyPlaceHolders", true);
Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
if (!Annotation.class.equals(annotationClass)) {
builder.addPropertyValue("annotationClass", annotationClass);
}
Class<?> markerInterface = annoAttrs.getClass("markerInterface");
if (!Class.class.equals(markerInterface)) {
builder.addPropertyValue("markerInterface", markerInterface);
}
Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
if (!BeanNameGenerator.class.equals(generatorClass)) {
builder.addPropertyValue("nameGenerator", BeanUtils.instantiateClass(generatorClass));
}
Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
builder.addPropertyValue("mapperFactoryBeanClass", mapperFactoryBeanClass);
}
String sqlSessionTemplateRef = annoAttrs.getString("sqlSessionTemplateRef");
if (StringUtils.hasText(sqlSessionTemplateRef)) {
builder.addPropertyValue("sqlSessionTemplateBeanName", annoAttrs.getString("sqlSessionTemplateRef"));
}
String sqlSessionFactoryRef = annoAttrs.getString("sqlSessionFactoryRef");
if (StringUtils.hasText(sqlSessionFactoryRef)) {
builder.addPropertyValue("sqlSessionFactoryBeanName", annoAttrs.getString("sqlSessionFactoryRef"));
}
List<String> basePackages = new ArrayList<>();
basePackages.addAll(
Arrays.stream(annoAttrs.getStringArray("value")).filter(StringUtils::hasText).collect(Collectors.toList()));
basePackages.addAll(Arrays.stream(annoAttrs.getStringArray("basePackages")).filter(StringUtils::hasText)
.collect(Collectors.toList()));
basePackages.addAll(Arrays.stream(annoAttrs.getClassArray("basePackageClasses")).map(ClassUtils::getPackageName)
.collect(Collectors.toList()));
if (basePackages.isEmpty()) {
basePackages.add(getDefaultBasePackage(annoMeta));
}
String lazyInitialization = annoAttrs.getString("lazyInitialization");
if (StringUtils.hasText(lazyInitialization)) {
builder.addPropertyValue("lazyInitialization", lazyInitialization);
}
builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(basePackages));
registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
}
以上代码中生成了 MapperScannerConfigurer 的 BeanDefinition:
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
打开 MapperScannerConfigurer 的类,根据类的实现可以知道该类的执行时机;
public class MapperScannerConfigurer
implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
以下是该类的关键代码:
/**
* {@inheritDoc}
*
* @since 1.0.2
*/
@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));
}
ClassPathMapperScanner 是继承自 Spring 的 ClassPathBeanDefinitionScanner,也就是自己实现了 ClassPathBeanDefinitionScanner 的部分功能。
public ClassPathMapperScanner(BeanDefinitionRegistry registry) {
super(registry, false);
}
注意初始化方法第二个参数 useDefaultFilters 传递值是 false,即跳过父类的一段处理逻辑,此处理逻辑是用于扫描 @Component 注解标注的类;
自己是实现过滤操作
public void registerFilters() {
boolean acceptAllInterfaces = true;
// if specified, use the given annotation and / or marker interface
if (this.annotationClass != null) {
addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));
acceptAllInterfaces = false;
}
// override AssignableTypeFilter to ignore matches on the actual marker interface
if (this.markerInterface != null) {
addIncludeFilter(new AssignableTypeFilter(this.markerInterface) {
@Override
protected boolean matchClassName(String className) {
return false;
}
});
acceptAllInterfaces = false;
}
if (acceptAllInterfaces) {
// default include filter that accepts all classes
addIncludeFilter((metadataReader, metadataReaderFactory) -> true);
}
// exclude package-info.java
addExcludeFilter((metadataReader, metadataReaderFactory) -> {
String className = metadataReader.getClassMetadata().getClassName();
return className.endsWith("package-info");
});
}
扫描的操作依旧是采用父类
@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;
}
isCandidateComponent 方法也进行了重写,支持接口;
/**
* {@inheritDoc}
*/
@Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
}
以下这个方法就很熟悉了,和之前模拟的逻辑基本类似:
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
GenericBeanDefinition definition;
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (GenericBeanDefinition) holder.getBeanDefinition();
String beanClassName = definition.getBeanClassName();
LOGGER.debug(() -> "Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + beanClassName
+ "' mapperInterface");
// the mapper interface is the original class of the bean
// but, the actual class of the bean is MapperFactoryBean
// 关键代码
definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
definition.setBeanClass(this.mapperFactoryBeanClass);
definition.getPropertyValues().add("addToConfig", this.addToConfig);
boolean explicitFactoryUsed = false;
if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
definition.getPropertyValues().add("sqlSessionFactory",
new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionFactory != null) {
definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
explicitFactoryUsed = true;
}
if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
if (explicitFactoryUsed) {
LOGGER.warn(
() -> "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
}
definition.getPropertyValues().add("sqlSessionTemplate",
new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionTemplate != null) {
if (explicitFactoryUsed) {
LOGGER.warn(
() -> "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
}
definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
explicitFactoryUsed = true;
}
if (!explicitFactoryUsed) {
LOGGER.debug(() -> "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
// 关键代码
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
definition.setLazyInit(lazyInitialization);
}
}
以下的代码很重要,修改了注入模式为 ByType,主要是为了给 SqlSessionTemplate 赋值,之前在配置类定义了 SqlSessionFactory 的 bean,而在 SqlSessionDaoSupport 类属性注入的时候会根据类型寻找对应的 bean,关于属性注入的查找是先找 set 方法等这个后面会详细介绍;
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
if (this.sqlSessionTemplate == null || sqlSessionFactory != this.sqlSessionTemplate.getSqlSessionFactory()) {
this.sqlSessionTemplate = createSqlSessionTemplate(sqlSessionFactory);
}
}
beanClass 为 MapperFactoryBeanClass,而 MapperFactoryBean 实现了 FactoryBean
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;
}
/**
* {@inheritDoc}
*/
@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();
}
}
}
/**
* {@inheritDoc}
*/
@Override
public T getObject() throws Exception {
//执行的是Mybatis的jar中逻辑
return getSqlSession().getMapper(this.mapperInterface);
}
/**
* {@inheritDoc}
*/
@Override
public Class<T> getObjectType() {
return this.mapperInterface;
}
/**
* {@inheritDoc}
*/
@Override
public boolean isSingleton() {
return true;
}
// ------------- mutators --------------
/**
* Sets the mapper interface of the MyBatis mapper
*
* @param mapperInterface
* class of the interface
*/
public void setMapperInterface(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
/**
* Return the mapper interface of the MyBatis mapper
*
* @return class of the interface
*/
public Class<T> getMapperInterface() {
return mapperInterface;
}
/**
* If addToConfig is false the mapper will not be added to MyBatis. This means it must have been included in
* mybatis-config.xml.
* <p>
* If it is true, the mapper will be added to MyBatis in the case it is not already registered.
* <p>
* By default addToConfig is true.
*
* @param addToConfig
* a flag that whether add mapper to MyBatis or not
*/
public void setAddToConfig(boolean addToConfig) {
this.addToConfig = addToConfig;
}
/**
* Return the flag for addition into MyBatis config.
*
* @return true if the mapper will be added to MyBatis in the case it is not already registered.
*/
public boolean isAddToConfig() {
return addToConfig;
}
}
新版与旧版区别
2.0.5 版本
Import-MapperScannerRegistrar->注册了一个 MapperScannerConfigurer->扫描->BeanDefinition
1.0.3 版本
Import-MapperScannerRegistrar->扫描->BeanDefinition
增加这一层的好处,就是可以不使用 MapperScan 注解,直接定义一个 MapperScannerConfigurer 类型的 bean 来扫描,使用更加灵活了;
注意:
@Mapper 注解目前没有使用场景,除非在 spring-boot 中,@MapperScan 注解和 @Mapper 配合使用,意思是接口只有加了 @Mapper 的注解才能被解析,否则不行。
![用户头像](http://static001.geekbang.org/account/avatar/00/0f/f3/a0/a693e561.jpg?x-oss-process=image/resize,w_200,h_200)
魔曦
我思故我在! 2018.01.15 加入
凡事有交代,件件有着落,事事有回音。
评论