使用过mybatis的都知道,要在配置中定义扫描mybatis对应的接口,也就是 MapperScan 注解。
那么MapperScan做了什么?
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package org.mybatis.spring.annotation;
import java.lang.annotation.Annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.mybatis.spring.mapper.MapperFactoryBean;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.context.annotation.Import;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({MapperScannerRegistrar.class})
@Repeatable(MapperScans.class)
public @interface MapperScan {
String[] value() default {};
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
Class<? extends Annotation> annotationClass() default Annotation.class;
Class<?> markerInterface() default Class.class;
String sqlSessionTemplateRef() default "";
String sqlSessionFactoryRef() default "";
Class<? extends MapperFactoryBean> factoryBean() default MapperFactoryBean.class;
}
这是MapperScan注解的源码,从中我们可以看到 @Import({MapperScannerRegistrar.class})
@import注解,就是导入一个对象到容器中,他这里指定的类就是要导入到容器中的类,那么我们也可以模仿这个做法,自己创建一个注解。
package lxq.mybatis.springboot.annotation;
import lxq.mybatis.springboot.handler.ServiceBeanDefinitionRegistry;
import org.springframework.context.annotation.Import;
import java.lang.annotation.*;
/**
* @description: 自定义扫描注解
* @author: Lxq
* @date: 2020/7/13 9:03
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({ServiceBeanDefinitionRegistry.class})
public @interface LxqMapperScan {
String value() default "";
}
可以看到我这里指定的是 ServiceBeanDefinitionRegistry.class ,没错这个就是我们自定义的类
ServiceBeanDefinitionRegistry.class
package lxq.mybatis.springboot.handler;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.env.Environment;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternUtils;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.util.ClassUtils;
import java.io.IOException;
import java.util.LinkedHashSet;
import java.util.Set;
/**
* @description: 用于Spring动态注入自定义接口
* @author: Lxq
* @date: 2020/7/10 14:57
*/
public class ServiceBeanDefinitionRegistry implements BeanDefinitionRegistryPostProcessor, ResourceLoaderAware, ApplicationContextAware {
private ResourcePatternResolver resourcePatternResolver;
private CachingMetadataReaderFactory metadataReaderFactory;
private ApplicationContext applicationContext;
private static final String DEFAULT_RESOURCE_PATTERN = "**/*.class";
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
// 这里通过反射获取需要代理的接口的clazz列表
// 获取所有的接口类
Set<Class<?>> beanClazzs = scannerPackages("lxq.mybatis.springboot.test");
for (Class beanClazz : beanClazzs) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(beanClazz);
GenericBeanDefinition definition = (GenericBeanDefinition) builder.getRawBeanDefinition();
// 如果采用definition.getConstructorArgumentValues(),也就是构造方法注入
// 则FactoryBean中需要提供包含该属性的构造方法,否则会注入失败
definition.getConstructorArgumentValues().addGenericArgumentValue(beanClazz);
//注意,这里的BeanClass是生成Bean实例的工厂,不是Bean本身。
// FactoryBean是一种特殊的Bean,其返回的对象不是指定类的一个实例,
// 其返回的是该工厂Bean的getObject方法所返回的对象。
definition.setBeanClass(ServiceFactory.class);
//这里采用的是byType方式注入,类似的还有byName等
definition.setAutowireMode(GenericBeanDefinition.AUTOWIRE_BY_TYPE);
registry.registerBeanDefinition(beanClazz.getSimpleName(), definition);
}
}
/**
* 获取包路径以及子包下面的所有类
*
* @param basePackage
* @return
*/
private Set<Class<?>> scannerPackages(String basePackage) {
Set<Class<?>> set = new LinkedHashSet<>();
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
resolveBasePackage(basePackage) + '/' + DEFAULT_RESOURCE_PATTERN;
try {
Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath);
for (Resource resource : resources) {
if (resource.isReadable()) {
MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource);
String className = metadataReader.getClassMetadata().getClassName();
Class<?> clazz;
try {
clazz = Class.forName(className);
set.add(clazz);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
return set;
}
protected String resolveBasePackage(String basePackage) {
return ClassUtils.convertClassNameToResourcePath(this.getEnvironment().resolveRequiredPlaceholders(basePackage));
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
}
/**
* 获取资源加载器,可以获得外部资源文件
*/
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourcePatternResolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader);
this.metadataReaderFactory = new CachingMetadataReaderFactory(resourceLoader);
}
/**
* 当前的application context从而调用容器的服务
*
* @param applicationContext
* @throws BeansException
*/
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
private Environment getEnvironment() {
return applicationContext.getEnvironment();
}
}
这里做的就是先扫包,将所有的接口扫描出来,然后构建 BeanDefinition ,也就是bean的定义,然后通过bean工厂生产。
仔细看的时候会发现,definition.setBeanClass(ServiceFactory.class) 在这里的时候我们设置了一个bean class 解释我上面已经说明。
ServiceFactory.class
package lxq.mybatis.springboot.handler;
import org.springframework.beans.factory.FactoryBean;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
/**
* @description:接口实例工厂,这里主要是用于提供接口的实例对象
* @author: Lxq
* @date: 2020/7/10 14:49
*/
public class ServiceFactory<T> implements FactoryBean {
private Class<T> interfaceType;
public ServiceFactory(Class<T> interfaceType) {
this.interfaceType = interfaceType;
}
@Override
public Object getObject() throws Exception {
//这里主要是创建接口对应的实例,便于注入到spring容器中
InvocationHandler handler = new ServiceProxy<>(interfaceType);
return Proxy.newProxyInstance(interfaceType.getClassLoader(), new Class[]{interfaceType}, handler);
}
@Override
public Class<?> getObjectType() {
return interfaceType;
}
}
这里利用的就是JDK动态代理,还有我模仿mybtais 将 InvocationHandler 也抽出来了,你也直接用匿名方法创建。
ServiceProxy.class
package lxq.mybatis.springboot.handler;
import com.alibaba.fastjson.JSON;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
* @description: 动态代理,需要注意的是,这里用到的是JDK自带的动态代理,
* 代理对象只能是接口,不能是类
* @author: Lxq
* @date: 2020/7/10 14:38
*/
public class ServiceProxy<T> implements InvocationHandler {
private Class<T> interfaceType;
public ServiceProxy(Class<T> intefaceType) {
this.interfaceType = interfaceType;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this,args);
}
System.out.println("调用前,参数:{}" + args);
//这里可以得到参数数组和方法等,可以通过反射,注解等,进行结果集的处理
//mybatis就是在这里获取参数和相关注解,然后根据返回值类型,进行结果集的转换
Object result = JSON.toJSONString(args);
System.out.println("调用后,结果:{}" + result);
return result;
}
}
当我们将接口注入,调用的时候,就会执行invoke方法,接下来创建两个接口并测试。
package lxq.mybatis.springboot.test;
/**
* @description:
* @author: Lxq
* @date: 2020/7/10 14:48
*/
public interface TestService {
String getList(String code, String name);
}
package lxq.mybatis.springboot.test;
/**
* @description:
* @author: Lxq
* @date: 2020/7/10 14:47
*/
public interface CalculateService {
String getResult(String name);
}
TestController
package lxq.mybatis.springboot.web;
import lxq.mybatis.springboot.test.CalculateService;
import lxq.mybatis.springboot.test.TestService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @description:
* @author: Lxq
* @date: 2020/7/10 15:17
*/
@RestController
public class TestController {
@Autowired
private TestService testService;
@Autowired
private CalculateService calculateService;
@RequestMapping("/test")
public String getHello() {
String testList = testService.getList("code","name");
String calculateResult = calculateService.getResult("测试");
return (testList + "," +calculateResult);
}
}
成功注入到spring容器中,并实现调用,那么接下来我们来看spring如何整合mybatis,原理是一样的。
从 MapperScan 入手我们可以看到用的是
MapperScannerRegistrar.class
this.registerBeanDefinitions(mapperScanAttrs, registry);
我就挑重点看,其他的直接忽略。
doScan(String... basePackages)
processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions)
这里做法和我一样,通过扫描包,将接口转换成beanDefinitions定义
重点看 getObject,
return this.getSqlSession().getMapper(this.mapperInterface);
这里我们看sqlsession 默认实现类 DefaultSqlSession
这里是用 configuration 获取,因为启动项目的时候,mybatis会将接口放入其中
继续进去看。
从集合中获取 MapperProxyFactory,这里不就是我们的ServiceFactory
MapperProxy 就是我们的 ServiceProxy
当我们调用接口就执行这里。