mybatis 是一个设计十分优秀的框架, 其使用 java 原生的动态代理为使用者带来了 “仅定义接口, 无需具体实现, 完成 ORM 映射操纵数据库” 的巨大便利, 成为 java 应用中最受欢迎的轻量级 ORM 框架; 本文尝试探究一下 mybatis 动态代理的实现原理, 以及其在 spring 中的引导逻辑 (但不涉及具体的 ORM 映射逻辑);
自动扫描 & 自动替换 mybatis 实现自动扫描 Mapper 的核心类是 MapperScannerConfigurer, 这个类实现了 spring BeanDefinitionRegistryPostProcessor 接口, 从而获得了在 spring bean 实例化之前操纵修改 beanDefinition 的机会, 并进而利用代理机制实现了对用户自定义 Mapper 接口的 “偷梁换柱”;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 MapperScannerConfigurer: -> postProcessBeanDefinitionRegistry(registry: BeanDefinitionRegistry) -> scanner = new ClassPathMapperScanner(registry) -> scanner.scan(basePackage) ClassPathBeanDefinitionScanner: -> beanDefinitions: Set<BeanDefinition> = findCandidateComponents(basePackage) ClassPathMapperScanner: -> beanDefinitions = super .doScan(basePackages) -> processBeanDefinitions(beanDefinitions) -> beanDefinitions.foreach { definition.getPropertyValues().add("mapperInterface" , Resources.classForName(beanClassName)) definition.getPropertyValues().add("sqlSessionFactory" , this .sqlSessionFactory) definition.getPropertyValues().add("sqlSessionTemplate" , this .sqlSessionTemplate) definition.setBeanClass(MapperFactoryBean.class) }
springboot 引导层 对于非 springboot 应用, 用户需要自己主动将 MapperScannerConfigurer 配置为 bean, spring 才能感知并加载它, 比如:
1 2 3 <bean name ="mapperScannerConfigurer" class ="org.mybatis.spring.mapper.MapperScannerConfigurer" > <property name ="basePackage" value ="com.xxx.mapper" /> </bean >
而对于一个 springboot 应用, 如果在启动类已经标记了 @EnableAutoConfiguration
, 则只需在 maven pom 中引用 mybatis springboot starter, 即可实现自动注册:
1 2 3 4 5 <dependency > <groupId > org.mybatis.spring.boot</groupId > <artifactId > mybatis-spring-boot-starter</artifactId > <version > ${mybatis-starter.version}</version > </dependency >
因为 mybatis springboot starter 定义了 MybatisAutoConfiguration, 将其注册为自动配置类:
1 2 3 4 org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration,\ org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
并在 MybatisAutoConfiguration 中引导 spring 加载 MapperScannerConfigurer:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 @Configuration @ConditionalOnClass ({ SqlSessionFactory.class, SqlSessionFactoryBean.class })@EnableConfigurationProperties (MybatisProperties.class)public class MybatisAutoConfiguration implements InitializingBean { public static class AutoConfiguredMapperScannerRegistrar implements BeanFactoryAware , ImportBeanDefinitionRegistrar { private BeanFactory beanFactory; @Override public void registerBeanDefinitions (AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { ...... BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class); builder.addPropertyValue("processPropertyPlaceHolders" , true ); builder.addPropertyValue("annotationClass" , Mapper.class); builder.addPropertyValue("basePackage" , StringUtils.collectionToCommaDelimitedString(packages)); ...... registry.registerBeanDefinition(MapperScannerConfigurer.class.getName(), builder.getBeanDefinition()); } ...... } @Configuration @Import (AutoConfiguredMapperScannerRegistrar.class) @ConditionalOnMissingBean ({ MapperFactoryBean.class, MapperScannerConfigurer.class }) public static class MapperScannerRegistrarNotFoundConfiguration implements InitializingBean {......} }
通过配置型 bean MapperScannerRegistrarNotFoundConfiguration 标记了注解 @Import(AutoConfiguredMapperScannerRegistrar.class), spring 最终调用 AutoConfiguredMapperScannerRegistrar#registerBeanDefinitions 方法, 将 MapperScannerConfigurer 的 beanDefinition 注册到了 beanDefinitionRegistry 中; spring 会在实例化 bean 之前, 优先加载 MapperScannerConfigurer 并执行其 postProcessBeanDefinitionRegistry 方法实现 Mapper beanDefinition 的自动替换;
自动代理 在 ApplicationContext 启动并 refresh() 的过程中, MapperScannerConfigurer 已经于 spring bean 实例化之前完成了对用户自定义 Mapper 对应的 beanDefinition 的逻辑替换, 此时用户 Mapper BeanDefinition 里记录的 beanClass 是 MapperFactoryBean !!!
当 BeanFactory 开始加载各个 bean, MapperFactoryBean 将启动它的自动代理引导:
1 2 3 4 5 6 public class MapperFactoryBean <T > extends SqlSessionDaoSupport implements FactoryBean <T > { @Override public T getObject () throws Exception { return getSqlSession().getMapper(this .mapperInterface); } }
getSqlSession() 方法返回的是 MapperScannerConfigurer 注入的 sqlSessionTemplate:
1 2 3 4 5 6 7 8 9 10 11 public class SqlSessionTemplate implements SqlSession , DisposableBean { @Override public <T> T getMapper (Class<T> type) { return getConfiguration().getMapper(type, this ); } @Override public Configuration getConfiguration () { return this .sqlSessionFactory.getConfiguration(); } }
最终是从 MapperRegistry 中拿到的代理实例:
1 2 3 4 5 6 7 8 9 10 11 12 public class DefaultSqlSessionFactory implements SqlSessionFactory { @Override public Configuration getConfiguration () { return configuration; } } public class Configuration { public <T> T getMapper (Class<T> type, SqlSession sqlSession) { return mapperRegistry.getMapper(type, sqlSession); } }
MapperRegistry 注册了所有的 MapperProxyFactory, MapperRegistry#getMapper 方法将会代理给 MapperProxyFactory 真正实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 public class MapperRegistry { public <T> T getMapper (Class<T> type, SqlSession sqlSession) { final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type); if (mapperProxyFactory == null ) { throw new BindingException("Type " + type + " is not known to the MapperRegistry." ); } try { return mapperProxyFactory.newInstance(sqlSession); } catch (Exception e) { throw new BindingException("Error getting mapper instance. Cause: " + e, e); } } }
代理框架 最终 mybatis 使用了 jdk 原生的 InvocationHandler / Proxy 动态代理解决方案:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class MapperProxy <T > implements InvocationHandler , Serializable { @Override public Object invoke (Object proxy, Method method, Object[] args) throws Throwable { try { if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this , args); } else { return cachedInvoker(method).invoke(proxy, method, args, sqlSession); } } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } } }
jdk 原生的动态代理最大的局限是只能代理 Interface, 不能代理 Class, 不过在 mybatis 的场景中已经足够用了, mybatis 给用户的使用方式就是 用纯接口定义行为规范即可; 而一旦不受该约束所困, jdk 原生动态代理的高性能优势就会凸显出来; MapperProxyFactory 类作为引擎, 使用 Proxy.newProxyInstance 基于 mapperProxy 创建出真正的代理实例:
1 2 3 4 5 6 7 8 9 10 11 public class MapperProxyFactory <T > { public T newInstance (SqlSession sqlSession) { final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache); return newInstance(mapperProxy); } protected T newInstance (MapperProxy<T> mapperProxy) { return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy); } }
代理实现 mybatis 在 MapperProxy 的 invoke 方法中实现了真正的 ORM 映射逻辑, 不过这就是另一个宏大的话题了, 本文不再讨论相关细节;