MyBatis 传统模式
public static void main(String[] args) throws IOException {InputStream inputStream = Resources.getResourceAsStream("mybatis.xml");SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);SqlSession sqlSession = sqlSessionFactory.openSession();// 通过 JDK 动态代理,生成代理对象AdminMapper mapper = sqlSession.getMapper(AdminMapper.class);List<Admin> admins = mapper.selectAll();for (Admin admin : admins) {System.out.println(admin.getId() + ", " + admin.getName());}}
其中 AdminMapper mapper = sqlSession.getMapper(AdminMapper.class); 就是通过 JDK 动态代理生成的代理对象,我们可以往下跟源码发现
protected T newInstance(MapperProxy<T> mapperProxy) {return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(),new Class[] { mapperInterface }, mapperProxy);}
MyBatis 如何和 Spring 整合的(MyBatis-Spring 1.3.X 的实现)
一、演化一,FactoryBean
FactoryBean 的模式
@Componentpublic class MyFactoryBean implements FactoryBean<Object> {@Overridepublic UserMapper getObject() throws Exception {Object proxyInstance = Proxy.newProxyInstance(MyFactoryBean.class.getClassLoader(), new Class<?>[]{UserMapper.class}, new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {String select = method.getAnnotation(Select.class).value();System.out.println(method.getName() + " => " + select);return null;}});return (UserMapper) proxyInstance;}@Overridepublic Class<?> getObjectType() {return UserMapper.class;}}
我们可以通过 FactoryBean 的模式来给 UserMapper 生成一个代理对象,这样虽然可以成功,但是,如果在还有其他的 Mapper 怎么处理呢?难道在写一个 FactoryBean 么?肯定不是
二、演化二,ImportBeanDefinitionRegistrar
通过 ImportBeanDefinitionRegistrar 接口,我们可以想容器中注册 BeanDefinition,这也是 MyBatis 与 Spring 整合的方式
public class MyBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {@Overridepublic void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {// 这里可以用 扫描 来实现List<Class<?>> mapperClasses = new ArrayList<>();mapperClasses.add(UserMapper.class);mapperClasses.add(OrderMapper.class);for (Class<?> mapperClass : mapperClasses) {AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();beanDefinition.setBeanClass(MyFactoryBean.class);beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(mapperClass);// 注册到容器registry.registerBeanDefinition(mapperClass.getSimpleName(), beanDefinition);}}}public class MyFactoryBean implements FactoryBean<Object> {private Class<?> mapperInterface;public MyFactoryBean(Class<?> mapperInterface) {this.mapperInterface = mapperInterface;}@Overridepublic Object getObject() throws Exception {return Proxy.newProxyInstance(MyFactoryBean.class.getClassLoader(), new Class<?>[]{mapperInterface}, new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {String select = method.getAnnotation(Select.class).value();System.out.println(method.getName() + " => " + select);return null;}});}@Overridepublic Class<?> getObjectType() {return mapperInterface;}}@Configuration@ComponentScan("com.sourceflag.spring.mybatis")@Import(MyBeanDefinitionRegistrar.class)public static class Config {}
MyBatis 整合 Spring 源码
MapperScan 注解
我们可以看到 MapperScan 上面也有一个 @Import(MapperScannerRegistrar.class) 注解
@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.TYPE)@Documented@Import(MapperScannerRegistrar.class)public @interface MapperScan {}
MapperScannerRegistrar 的实现
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {private ResourceLoader resourceLoader;/*** {@inheritDoc}*/@Overridepublic void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {// 获取 MapperScan 注解AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);// this check is needed in Spring 3.1if (resourceLoader != null) {scanner.setResourceLoader(resourceLoader);}Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");if (!Annotation.class.equals(annotationClass)) {scanner.setAnnotationClass(annotationClass);}Class<?> markerInterface = annoAttrs.getClass("markerInterface");if (!Class.class.equals(markerInterface)) {scanner.setMarkerInterface(markerInterface);}Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");if (!BeanNameGenerator.class.equals(generatorClass)) {scanner.setBeanNameGenerator(BeanUtils.instantiateClass(generatorClass));}Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {scanner.setMapperFactoryBean(BeanUtils.instantiateClass(mapperFactoryBeanClass));}scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef"));scanner.setSqlSessionFactoryBeanName(annoAttrs.getString("sqlSessionFactoryRef"));List<String> basePackages = new ArrayList<String>();for (String pkg : annoAttrs.getStringArray("value")) {if (StringUtils.hasText(pkg)) {basePackages.add(pkg);}}for (String pkg : annoAttrs.getStringArray("basePackages")) {if (StringUtils.hasText(pkg)) {basePackages.add(pkg);}}for (Class<?> clazz : annoAttrs.getClassArray("basePackageClasses")) {basePackages.add(ClassUtils.getPackageName(clazz));}// 注册 filtersscanner.registerFilters();// 开始进行扫描scanner.doScan(StringUtils.toStringArray(basePackages));}/*** {@inheritDoc}*/@Overridepublic void setResourceLoader(ResourceLoader resourceLoader) {this.resourceLoader = resourceLoader;}}
看下 MyBatis 是如果定义扫描器的
public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {public ClassPathMapperScanner(BeanDefinitionRegistry registry) {// 我们可以看到传递的 useDefaultFilters 属性为 falsesuper(registry, false);}@Overrideprotected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {// 必须是接口return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();}}
我们可以看到传递的 useDefaultFilters 属性为 false,表示不使用 Spring 中的默认过滤器,因为 Spring 默认不会使用接口来注册 BeanDefinition
我们继续看 MyBatis 注册的过滤器
从这里我们可以看出 MyBatis 会把所有的类(包括接口)都扫描出来
addIncludeFilter(new TypeFilter() {@Overridepublic boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {return true;}});
doScan 的实现
经过 MyBatis 的过滤器和 isCandidateComponent 的判断,现在可以进行扫描了
public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {@Overridepublic Set<BeanDefinitionHolder> doScan(String... basePackages) {// 经过 MyBatis 的过滤器和 isCandidateComponent 的判断,这里扫描出来的都是接口了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;}private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {GenericBeanDefinition definition;for (BeanDefinitionHolder holder : beanDefinitions) {definition = (GenericBeanDefinition) holder.getBeanDefinition();if (logger.isDebugEnabled()) {logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName()+ "' and '" + definition.getBeanClassName() + "' mapperInterface");}// the mapper interface is the original class of the bean// but, the actual class of the bean is MapperFactoryBean// 将 beanClass 作为 MapperFactoryBean 的构造方法参数definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59// 同时修改 beanClass 为 MapperFactoryBeandefinition.setBeanClass(this.mapperFactoryBean.getClass());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) {if (logger.isDebugEnabled()) {logger.debug("Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");}definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);}}}}
MyBatis-Spring 2.0.X 的实现
在 MyBatis-Spring-2.0.X 版本中并没有直接通过 registerBeanDefinitions 进行扫描,而是直接向容器中注册了一个 MapperScannerConfigurer 类,我们先看下这个类
public class MapperScannerConfigurerimplements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {}
我们可以看到 MapperScannerConfigurer 这类实现了 BeanDefinitionRegistryPostProcessor 接口,那么 Spring 在启动后,会找到 MapperScannerConfigurer 类,然后执行 postProcessBeanDefinitionRegistry 方法来进行扫描和注册,其他并没有变动,只是多做了一层
- 老版本:MapperScannerRegistrar => registerBeanDefinitions => scanner => 注册 BeanDefinition
- 新版本:MapperScannerRegistrar => registerBeanDefinitions => 注册 MapperScannerConfigurer => Spring 调用 BeanDefinitionRegistryPostProcessor 的后置处理器 => scanner => 注册 BeanDefinition
乍一看新版本反而还多做了一步,反而麻烦了,为什么要多注册一个 MapperScannerConfigurer,然后通过后置处理器的方式来进行扫描呢?
其实这里 MyBatis 提供了一种扩展机制,也就是说,我们可以不需要写 @MapperScan 注解来进行扫描,而通过 @Bean 的方式注册 MapperScannerConfigurer 来替代 @MapperScan 注解
@Beanpublic MapperScannerConfigurer mapperScannerConfigurer(){MapperScannerConfigurer configurer = new MapperScannerConfigurer();configurer.setBasePackage("com.sourceflag.spring.mybatis");return configurer;}
