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的配置文件中配置一下即可

<!-- Mapper映射文件的包掃描器 -->
<bean class="com.test.batis.MyMapperScannerConfigurer">
	<property name="basePackage" value="com.test.batis.mapper" />
</bean>

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<BeanDefinitionHolder> doScan(String... basePackages) {
		// 調用父類的掃描,得到若干個BeanDefinitionHolder,這個類定義了需要實例化的類
		Set<BeanDefinitionHolder> 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<BeanDefinitionHolder> 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<T> implements FactoryBean<T> {
	
	// 這個屬性就是類掃描器中設置的屬性,注意這裏是屬性是Class,而類掃描器中的屬性是String,我們並不需要手動將String轉爲Class。如果你想保持一致,這裏改爲String mapperInterface;這樣也未嘗不可
	private Class<T> 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<T> getMapperInterface() {
		return mapperInterface;
	}

	public void setMapperInterface(Class<T> 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詳解就會發現容易很多。
文章到此爲止,希望對小夥伴們能有幫助。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章