MyBatis 与 Spring如何结合的——手撸简版MyBatis

做后台开发的同学肯定都用过mybatis。那么mybatis是如何与Spring结合起来的呢?如何扫描到我们定义的mapper的呢?如果你对此很感兴趣,但是又没有了解过,那么可以继续往下看了。
首先推荐一篇介绍的不错的mybatis讲解的文章
https://www.cnblogs.com/kevin-yuan/p/7229777.html?utm_source=itdadao&utm_medium=referral
如果看过一遍没明白,没关系,可以先看看下面的讲解,然后再回过头去研究一下,相信你就能明白Mybatis的原理了。

正题开始:

首先我们要知道,如果自己手撸一个MyBatis,都需要解决什么问题。

  • 如何与Spring一起启动,并且加载自己的配置文件?例如:使用过mybatis都知道,我们需要在Spring的配置文件中配置扫描mapper的包路径。
  • 如何让扫描到的mapper能被Autowire?或者说怎么自己实例化对象并且托管给Spring。
  • 如何实例化对象?我们的mapper基本都是接口,并没有具体的实现类。都是通过xml或者注解来写的SQL,那么如何实例化这些mapper呢?

本编文章就带领大家来解决这些疑惑,并且手撸一个假的Mybatis。

  1. 首先解决第一个问题,自己的Mybatis如何随Spring启动呢?
    参考mybatis的使用,我们知道如果使用mybatis,必须要在Spring的配置文件中配置一下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的完整结构如下
MyBatis 与 Spring如何结合的——手撸简版MyBatis_第1张图片
红框内是基本的类。

Demo如果能弄明白啊,在回过头来去看看大神们写的Mybatis详解就会发现容易很多。
文章到此为止,希望对小伙伴们能有帮助。

你可能感兴趣的:(Java)