做后台开发的同学肯定都用过mybatis。那么mybatis是如何与Spring结合起来的呢?如何扫描到我们定义的mapper的呢?如果你对此很感兴趣,但是又没有了解过,那么可以继续往下看了。
首先推荐一篇介绍的不错的mybatis讲解的文章
https://www.cnblogs.com/kevin-yuan/p/7229777.html?utm_source=itdadao&utm_medium=referral
如果看过一遍没明白,没关系,可以先看看下面的讲解,然后再回过头去研究一下,相信你就能明白Mybatis的原理了。
正题开始:
首先我们要知道,如果自己手撸一个MyBatis,都需要解决什么问题。
本编文章就带领大家来解决这些疑惑,并且手撸一个假的Mybatis。
MapperScannerConfigurer
,指定basePackage。其中MapperScannerConfigurer
就是入口。我们自己实现的话,就需要自己实现一个这样的类。package com.test.batis;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
// 这个类实现的接口是参考mybatis的MapperScannerConfigurer写的,我们的简版mybatis不需要使用这么多
// BeanDefinitionRegistryPostProcessor的作用是为了可以向Spring中托管对象
public class MyMapperScannerConfigurer
implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
private String beanName;
private String basePackage;
private ApplicationContext applicationContext;
// BeanNameAware接口的方法
@Override
public void setBeanName(String name) {
this.beanName = name;
}
// ApplicationContextAware接口的方法
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
// InitializingBean接口的方法
@Override
public void afterPropertiesSet() throws Exception {
System.err.println("afterPropertiesSet");
}
// BeanDefinitionRegistryPostProcessor接口的方法
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
}
// BeanDefinitionRegistryPostProcessor接口的方法,这里很重要
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
// 这里自定义了一个类扫描器
MyClassPathMapperScanner scanner = new MyClassPathMapperScanner(registry);
// 扫描指定的包路径
scanner.scan(basePackage);
}
public void setBasePackage(String basePackage) {
System.err.println("setBasePackage:" + basePackage);
this.basePackage = basePackage;
}
}
上面是入口类,那如何使用呢?只需要在Spring的配置文件中配置一下即可
MyMapperScannerConfigurer
类中使用了一个自定义的类扫描器,代码如下:
package com.test.batis;
import java.io.IOException;
import java.util.Set;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.TypeFilter;
public class MyClassPathMapperScanner extends ClassPathBeanDefinitionScanner {
public MyClassPathMapperScanner(BeanDefinitionRegistry registry) {
// 第二个参数意思是 是否使用默认filter,我们不使用。后面手动添加filter
super(registry, false);
// 手动实例一个包含的filter,这里是包含所有扫面到的类
addIncludeFilter(new TypeFilter() {
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
throws IOException {
return true;
}
});
// 手动实例需要排除的类 package-info.java
addExcludeFilter(new TypeFilter() {
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
throws IOException {
String className = metadataReader.getClassMetadata().getClassName();
return className.endsWith("package-info");
}
});
}
@Override
protected Set doScan(String... basePackages) {
// 调用父类的扫描,得到若干个BeanDefinitionHolder,这个类定义了需要实例化的类
Set ret = super.doScan(basePackages);
// 自己实现的方法
processBeanDefinitions(ret);
return ret;
}
// 这个方法参考了Mybatis的写法
@Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
}
/**
* 定义自己的操作
* @param ret
*/
private void processBeanDefinitions(Set ret) {
GenericBeanDefinition definition;
for (BeanDefinitionHolder holder : ret) {
definition = (GenericBeanDefinition) holder.getBeanDefinition();
System.err.println("Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '"
+ definition.getBeanClassName() + "' mapperInterface");
// 1.这个操作的意思是给mapperInterface属性赋值,值是扫描到的类的全路径名。
definition.getPropertyValues().add("mapperInterface", definition.getBeanClassName()); // 给mapperInterface属性赋值
// 2.这个是注册这个类的具体实现。
definition.setBeanClass(MyMapperFactoryBean.class); // 设置这个类的实际对象
definition.setLazyInit(false);
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE); // 按照类型注入,Autowire
// 上面的过程按步骤说来就是
// 1. 例如扫描出来了一个com.test.TestMapper,mapperInterface属性就是com.test.TestMapper
// 2. 具体的实现类就是MyMapperFactoryBean这个类,也就是我们按照com.test.TestMapper去Autowire的时候,实际上得到的是MyMapperFactoryBean的一个对象,MyMapperFactoryBean实际上是一个代理类
}
}
}
经过上面的类扫描分析可以知道,我们实际上的Mapper并不会实例化真正的对象,因为接口是无法实例化对象的,实际实例化的是一个个的代理对象。那么代理对象是怎么写的呢?继续往下看:
package com.test.batis;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import org.springframework.beans.factory.FactoryBean;
// 这个类实现了FactoryBean,实现了这个接口的类,被Spring实例化之后获取bean是getObject方法返回的对象
public class MyMapperFactoryBean implements FactoryBean {
// 这个属性就是类扫描器中设置的属性,注意这里是属性是Class,而类扫描器中的属性是String,我们并不需要手动将String转为Class。如果你想保持一致,这里改为String mapperInterface;这样也未尝不可
private Class mapperInterface;
public MyMapperFactoryBean() {
System.err.println("MyMapperFactoryBean构造函数");
}
// 实现了FactoryBean,这个方法返回实际的对象
@SuppressWarnings("unchecked")
@Override
public T getObject() throws Exception {
System.err.println("MyMapperFactoryBean getObject");
// 这里返回的是我们的mapper的代理对象,执行mapper中的方法之后,实际上在这里执行crud,这里是一个代理对象
return (T)Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class [] {mapperInterface}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("这里是一个mapper的代理");
System.out.println("执行了方法:" + method.getName());
MyInsert annotation = method.getAnnotation(MyInsert.class); // 自定义的注解,模拟Mybatis的SQL
System.out.println("模拟执行SQL:" + annotation.value());
return "模拟返回";
}
});
}
// 这个对象代表的类型
@Override
public Class> getObjectType() {
return mapperInterface;
}
// 是否是单例
@Override
public boolean isSingleton() {
return true;
}
public Class getMapperInterface() {
return mapperInterface;
}
public void setMapperInterface(Class mapperInterface) {
this.mapperInterface = mapperInterface;
}
}
到上面位置,基本需要的类基本都全了,下面是注解类和Mapper
package com.test.batis;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyInsert {
String value();
}
package com.test.batis.mapper;
import com.test.batis.MyInsert;
public interface TestMapper {
@MyInsert("insert into .....")
Object testinsert();
}
测试controller
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import com.test.batis.mapper.TestMapper;
@Controller
public class TestDspController {
@Autowired
private TestMapper testmapper;
@Autowired
private ApplicationContext applicationContext;
@ResponseBody
@RequestMapping(value = "/win", method = RequestMethod.GET)
public String win(){
System.out.println(testmapper.testinsert());
return "win";
}
}
调用controller后打印的日志
这里是一个mapper的代理
执行了方法:testinsert
模拟执行SQL:insert into .....
模拟返回
一个简版的基本的mybatis执行流程完成了。Demo的完整结构如下
红框内是基本的类。
Demo如果能弄明白啊,在回过头来去看看大神们写的Mybatis详解就会发现容易很多。
文章到此为止,希望对小伙伴们能有帮助。