目录
mybatis中mapper代理的生成过程
与Spring集成时mapper代理的生成过程
与SpringBoot集成时mapper代理的生成过程
mybatis中mapper代理的生成过程 构建代理类工厂 从入口点开始一步一步看,首先SqlSessionFactoryBuilder
类中build()
方法加载配置文件
1 2 3 4 5 6 7 8 9 10 public SqlSessionFactory build (InputStream inputStream, String environment, Properties properties) { try { XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties); return build(parser.parse()); } catch (Exception e) { throw ExceptionFactory.wrapException("Error building SqlSession." , e); } finally { } }
将配置文件读取为XMLConfigBuilder
对象,并调用parse()
方法来解析文件,进到parse()
中
1 2 3 4 5 6 public Configuration parse() { // ...省略 parsed = true; parseConfiguration(parser.evalNode("/configuration")); return configuration; }
可以看到具体的解析过程是在parseConfiguration
方法中进行的。
1 2 3 4 5 6 7 8 9 private void parseConfiguration (XNode root) { try { mapperElement(root.evalNode("mappers" )); } catch (Exception e) { throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e); } }
这里重点看一下最后解析mapper的方法mapperElement(root.evalNode("mappers"))
,进到方法里,
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 28 29 30 31 32 33 34 35 private void mapperElement (XNode parent) throws Exception { if (parent != null ) { for (XNode child : parent.getChildren()) { if ("package" .equals(child.getName())) { String mapperPackage = child.getStringAttribute("name" ); configuration.addMappers(mapperPackage); } else { String resource = child.getStringAttribute("resource" ); String url = child.getStringAttribute("url" ); String mapperClass = child.getStringAttribute("class" ); if (resource != null && url == null && mapperClass == null ) { ErrorContext.instance().resource(resource); InputStream inputStream = Resources.getResourceAsStream(resource); XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments()); mapperParser.parse(); } else if (resource == null && url != null && mapperClass == null ) { ErrorContext.instance().resource(url); InputStream inputStream = Resources.getUrlAsStream(url); XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments()); mapperParser.parse(); } else if (resource == null && url == null && mapperClass != null ) { Class<?> mapperInterface = Resources.classForName(mapperClass); configuration.addMapper(mapperInterface); } else { throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one." ); } } } } }
整个mapperElement()
方法就是加载mapper的过程了,可以看到加载mapper 有两种形式:通过class文件和通过xml文件。 构建mapper代理的过程也就是从这开始的,那就一步一步分析。 看一下通过XML文件加载的过程,mybatis将mapper相关的配置读取为一个XMLMapperBuilder
对象,并通过parse()
方法进行解析,进到这个方法中
1 2 3 4 5 6 7 8 9 10 11 public void parse () { if (!configuration.isResourceLoaded(resource)) { configurationElement(parser.evalNode("/mapper" )); configuration.addLoadedResource(resource); bindMapperForNamespace(); } }
parse()
方法做了主要做了两件事,加载xml文件和加载class文件。 看一下加载xml的过程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 private void configurationElement (XNode context) { try { String namespace = context.getStringAttribute("namespace" ); if (namespace == null || namespace.equals("" )) { throw new BuilderException("Mapper's namespace cannot be empty" ); } builderAssistant.setCurrentNamespace(namespace); } catch (Exception e) { throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e); } }
本文是分析mapper代理的生成过程,所以加载xml的具体细节就不详细分析了,这里注意的是读取xml文件中namespace
标签的值,并将值设置到builderAssistant
对象中 现在回过头来看一下加载class文件的过程。进到bindMapperForNamespace()
方法中去
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 private void bindMapperForNamespace () { String namespace = builderAssistant.getCurrentNamespace(); if (namespace != null ) { Class<?> boundType = null ; try { boundType = Resources.classForName(namespace); } catch (ClassNotFoundException e) { } if (boundType != null ) { if (!configuration.hasMapper(boundType)) { configuration.addLoadedResource("namespace:" + namespace); configuration.addMapper(boundType); } } } }
bindMapperForNamespace()
通过xml文件中设置的namespace值加载对应的mapper接口,最后通过configuration.addMapper()
添加到configuration
中。
还记不记得刚才提到的加载mapper 有两种形式:通过class文件和通过xml文件。通过class文件的方式直接调用configuration.addMapper()
将mapper接口加载到了configuration
中了。
Configuration
是mybatis的全局配置类,所有的mybatis相关的信息都保存在Configuration
中。 继续进到Configuration
的addMapper
方法中
1 2 3 public <T> void addMapper (Class<T> type) { mapperRegistry.addMapper(type); }
Configuration
把对应的mapper接口添加到mapperRegistry
中,再进到mapperRegistry.addMapper()
方法中
1 2 3 4 5 6 7 8 9 10 11 12 13 public <T> void addMapper (Class<T> type) { if (type.isInterface()) { try { knownMappers.put(type, new MapperProxyFactory<T>(type)); } finally { if (!loadCompleted) { knownMappers.remove(type); } } } }
该方法首先判断是否是接口,如果是接口则将mapper接口添加到knownMappers
中。 看一下knownMappers
的定义
1 private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();
knownMappers
是一个HashMap
,它保存的是所有的mapper接口和对应的mapper代理工厂。
到现在为止,mapper已经加载完了,但是并没有生成mapper的代理对象,只是生成了对应的代理工厂。
生成并使用代理对象 mybatis并没有在加载mapper接口的时候生成代理对象,而是在调用的时候生成的。 首先从入口开始
1 sqlSession.getMapper(XXX.class)
sqlSession
默认是DefaultSqlSession
。进到DefaultSqlSession
的getMapper()
方法中
1 2 3 4 @Override public <T> T getMapper (Class<T> type) { return configuration.<T>getMapper(type, this ); }
继续到Configuration
的getMapper
中
1 2 3 public <T> T getMapper (Class<T> type, SqlSession sqlSession) { return mapperRegistry.getMapper(type, sqlSession); }
继续到 mapperRegistry.getMapper()
中
1 2 3 4 5 6 7 8 9 10 public <T> T getMapper (Class<T> type, SqlSession sqlSession) { final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type); } try { return mapperProxyFactory.newInstance(sqlSession); } catch (Exception e) { throw new BindingException("Error getting mapper instance. Cause: " + e, e); } }
从knownMappers
中获取到对应mapper接口的代理工厂类MapperProxyFactory
,然后通过MapperProxyFactory
获取真正的代理对象。 进到MapperProxyFactory
的newInstance()
方法中
1 2 3 4 5 6 7 8 public T newInstance (SqlSession sqlSession) { final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache); return newInstance(mapperProxy); } protected T newInstance (MapperProxy<T> mapperProxy) { return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy); }
首先生成了MapperProxy
类,再通过Proxy
生成真正的代理类。 看一下MapperProxy
类
1 2 3 public class MapperProxy <T > implements InvocationHandler , Serializable { }
MapperProxy
实现了InvocationHandler
接口,mapper接口的具体处理逻辑也就是在这类中处理。
到此为止,代理对象才真正的生成。
与Spring集成时mapper代理的生成过程 mybatis与Spring集成时需要用到mybatis-spring
的jar。
Spring注册mapper代理类 既然是与Spring集成,那么就要配置一下,将mybatis交给Spring管理。 spring的xml文件配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <bean id ="dataSource" class ="org.springframework.jdbc.datasource.DriverManagerDataSource" > <property name ="driverClassName" value ="driverClassName" /> <property name ="url" value ="url" /> <property name ="username" value ="username" /> <property name ="password" value ="password" /> </bean > <bean id ="sqlSessionFactory" class ="org.mybatis.spring.SqlSessionFactoryBean" > <property name ="dataSource" ref ="dataSource" /> <property name ="configLocation" value ="classpath:mybatis-config.xml" /> <property name ="mapperLocations" value ="classpath:cn/ycl/mapper/*.xml" /> </bean > <bean class ="org.mybatis.spring.mapper.MapperScannerConfigurer" > <property name ="basePackage" value ="cn/ycl/mapper" /> <property name ="sqlSessionFactoryBeanName" value ="sqlSessionFactory" /> </bean >
将mybatis交给Spring只需要配置3个bean就可以了 1、 数据库相关的dataSource
2、 mybatis的sqlSessionFactory
3、 将mapper委托给Spring的工具类MapperScannerConfigurer
生成mapper代理的过程主要在MapperScannerConfigurer
里,看一下MapperScannerConfigurer
的定义
1 2 3 4 public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor , InitializingBean , ApplicationContextAware , BeanNameAware { }
关键点在MapperScannerConfigurer
实现了BeanDefinitionRegistryPostProcessor
,BeanDefinitionRegistryPostProcessor
是Spring留的扩展点,可以往Spring中注册自定义的bean。
MapperScannerConfigurer
中实现了BeanDefinitionRegistryPostProcessor
的postProcessBeanDefinitionRegistry()
方法,mapper的注册就是在该方法中注册的
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 public void postProcessBeanDefinitionRegistry (BeanDefinitionRegistry registry) { ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry); scanner.setAddToConfig(this .addToConfig); scanner.setAnnotationClass(this .annotationClass); scanner.setMarkerInterface(this .markerInterface); scanner.setSqlSessionFactory(this .sqlSessionFactory); scanner.setSqlSessionTemplate(this .sqlSessionTemplate); scanner.setSqlSessionFactoryBeanName(this .sqlSessionFactoryBeanName); scanner.setSqlSessionTemplateBeanName(this .sqlSessionTemplateBeanName); scanner.setResourceLoader(this .applicationContext); scanner.setBeanNameGenerator(this .nameGenerator); scanner.setMapperFactoryBeanClass(this .mapperFactoryBeanClass); if (StringUtils.hasText(lazyInitialization)) { scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization)); } if (StringUtils.hasText(defaultScope)) { scanner.setDefaultScope(defaultScope); } scanner.registerFilters(); scanner.scan( StringUtils.tokenizeToStringArray(this .basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS)); }
postProcessBeanDefinitionRegistry()
的主要逻辑是定义一个ClassPathMapperScanner
对象,然后调用registerFilters()
注册扫描规则,最后调用scan()
方法。
在xml中定义MapperScannerConfigurer
bean时可以设置一个annotationClass
属性,值是一个注解类,调用registerFilters()
时,registerFilters()
会添加一个只扫描设置有annotationClass
注解的类,这里没有设置,会扫描所有的接口。SpringBoot集成mybatis时会用到这个字段
看一下ClassPathMapperScanner
类的定义
1 2 3 public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner { }
ClassPathMapperScanner
继承了ClassPathBeanDefinitionScanner
,ClassPathBeanDefinitionScanner
是Spring中定义的,是一个从指定包内扫描所有bean定义的Spring工具。
看一下ClassPathMapperScanner
的scan()
方法
1 2 3 4 5 6 7 8 9 10 11 public Set<BeanDefinitionHolder> doScan (String... basePackages) { Set<BeanDefinitionHolder> beanDefinitions = super .doScan(basePackages); if (beanDefinitions.isEmpty()) { } else { processBeanDefinitions(beanDefinitions); } return beanDefinitions; }
通过super.doScan(basePackages)
已经扫描到了所有的mapper,继续processBeanDefinitions()
方法
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 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 private void processBeanDefinitions (Set<BeanDefinitionHolder> beanDefinitions) { AbstractBeanDefinition definition; BeanDefinitionRegistry registry = getRegistry(); for (BeanDefinitionHolder holder : beanDefinitions) { definition = (AbstractBeanDefinition) holder.getBeanDefinition(); boolean scopedProxy = false ; if (ScopedProxyFactoryBean.class.getName().equals(definition.getBeanClassName())) { definition = (AbstractBeanDefinition) Optional .ofNullable(((RootBeanDefinition) definition).getDecoratedDefinition()) .map(BeanDefinitionHolder::getBeanDefinition).orElseThrow(() -> new IllegalStateException( "The target bean definition of scoped proxy bean not found. Root bean definition[" + holder + "]" )); scopedProxy = true ; } String beanClassName = definition.getBeanClassName(); LOGGER.debug(() -> "Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + beanClassName + "' mapperInterface" ); definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); definition.setBeanClass(this .mapperFactoryBeanClass); definition.getPropertyValues().add("addToConfig" , this .addToConfig); definition.setAttribute(FACTORY_BEAN_OBJECT_TYPE, beanClassName); 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) { LOGGER.debug(() -> "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'." ); definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE); } definition.setLazyInit(lazyInitialization); if (scopedProxy) { continue ; } if (ConfigurableBeanFactory.SCOPE_SINGLETON.equals(definition.getScope()) && defaultScope != null ) { definition.setScope(defaultScope); } if (!definition.isSingleton()) { BeanDefinitionHolder proxyHolder = ScopedProxyUtils.createScopedProxy(holder, registry, true ); if (registry.containsBeanDefinition(proxyHolder.getBeanName())) { registry.removeBeanDefinition(proxyHolder.getBeanName()); } registry.registerBeanDefinition(proxyHolder.getBeanName(), proxyHolder.getBeanDefinition()); } } }
这个方法比较长,但是并不复杂,主要逻辑为将扫描的bean的类型修改成MapperFactoryBean
类型,并增加一个将接口类型作为入参的构造函数,也就是说Spring获取mapper时都是通过FactoryBean生成的。最后通过调用egistry.registerBeanDefinition()
方法注册到Spring中。
看一下mybatis提供的MapperFactoryBean
的定义
1 2 public class MapperFactoryBean <T > extends SqlSessionDaoSupport implements FactoryBean <T > {}
MapperFactoryBean
实现了FactoryBean
,FactoryBean
是一个Spring提供的一个能生产对象的工厂Bean
MapperFactoryBean
同时继承了SqlSessionDaoSupport
,SqlSessionDaoSupport
继承了DaoSupport
,DaoSupport
实现了InitializingBean
。InitializingBean
的作用是在Spring初始化bean对象时会首先调用InitializingBean
的afterPropertiesSet()
方法。
DaoSupport
的afterPropertiesSet()
中调用了checkDaoConfig()
方法。
1 2 3 4 5 6 7 8 9 public final void afterPropertiesSet () throws IllegalArgumentException, BeanInitializationException { this .checkDaoConfig(); try { this .initDao(); } catch (Exception var2) { throw new BeanInitializationException("Initialization of DAO failed" , var2); } }
具体checkDaoConfig()
方法的实现逻辑在MapperFactoryBean
中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 protected void checkDaoConfig () { super .checkDaoConfig(); notNull(this .mapperInterface, "Property 'mapperInterface' is required" ); Configuration configuration = getSqlSession().getConfiguration(); if (this .addToConfig && !configuration.hasMapper(this .mapperInterface)) { try { configuration.addMapper(this .mapperInterface); } catch (Exception e) { } finally { ErrorContext.instance().reset(); } } }
OK,到这又回到mybatis了。在前面中说了configuration.addMapper()
方法只是生成了对应的代理工厂。
以上整个过程,即把mapper注册为Spring的bean,又将mapper设置到mybatis中的configuration
中,所以,在使用时既可以使用Spring自动注入那一套,又可以使用mybatis中通过sqlSession
来获取mapper的代理对象
Spring生成代理对象 Spring中所有的mapper对应的bean是mapper对应的MapperFactoryBean
,那么在获取mapper bean时是通过MapperFactoryBean
的getObject()
方法生成的
1 2 3 public T getObject () throws Exception { return getSqlSession().getMapper(this .mapperInterface); }
MapperFactoryBean
先获取到sqlsession
,再通过getMapper()
获取到的代理对象。到这里就回到了mybatis生成代理对象的过程了。
与SpringBoot集成时mapper代理的生成过程 mybatis与Spring集成时需要用到mybatis-spring-boot-starter
的jar,mybatis-spring-boot-starter
依赖mybatis-spring-boot-autoconfigure
这个jar,而mybatis-spring-boot-autoconfigure
这个jar又依赖mybatis-spring
这个jar,所以最终其实还是mybatis集成Spring那一套
根据SpringBoot自动加载的原理直接看mybatis-spring-boot-autoconfigure
jar下META-INF/spring.factories
文件
1 2 3 org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration,\ org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
SpringBoot会自动加载MybatisAutoConfiguration
这个类,直接看这个类,MybatisAutoConfiguration
定义了mybtis所需的各个bean。
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 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 @Bean @ConditionalOnMissingBean public SqlSessionFactory sqlSessionFactory (DataSource dataSource) throws Exception { } @Bean @ConditionalOnMissingBean public SqlSessionTemplate sqlSessionTemplate (SqlSessionFactory sqlSessionFactory) { } @Configuration @Import({MybatisAutoConfiguration.AutoConfiguredMapperScannerRegistrar.class}) @ConditionalOnMissingBean({MapperFactoryBean.class, MapperScannerConfigurer.class}) public static class MapperScannerRegistrarNotFoundConfiguration implements InitializingBean { public MapperScannerRegistrarNotFoundConfiguration () { } public void afterPropertiesSet () { MybatisAutoConfiguration.logger.debug("Not found configuration for registering mapper bean using @MapperScan, MapperFactoryBean and MapperScannerConfigurer." ); } } public static class AutoConfiguredMapperScannerRegistrar implements BeanFactoryAware , ImportBeanDefinitionRegistrar { private BeanFactory beanFactory; public AutoConfiguredMapperScannerRegistrar () { } public void registerBeanDefinitions (AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { if (!AutoConfigurationPackages.has(this .beanFactory)) { MybatisAutoConfiguration.logger.debug("Could not determine auto-configuration package, automatic mapper scanning disabled." ); } else { MybatisAutoConfiguration.logger.debug("Searching for mappers annotated with @Mapper" ); List<String> packages = AutoConfigurationPackages.get(this .beanFactory); if (MybatisAutoConfiguration.logger.isDebugEnabled()) { packages.forEach((pkg) -> { MybatisAutoConfiguration.logger.debug("Using auto-configuration base package '{}'" , pkg); }); } BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class); builder.addPropertyValue("processPropertyPlaceHolders" , true ); builder.addPropertyValue("annotationClass" , Mapper.class); builder.addPropertyValue("basePackage" , StringUtils.collectionToCommaDelimitedString(packages)); BeanWrapper beanWrapper = new BeanWrapperImpl(MapperScannerConfigurer.class); Stream.of(beanWrapper.getPropertyDescriptors()).filter((x) -> { return x.getName().equals("lazyInitialization" ); }).findAny().ifPresent((x) -> { builder.addPropertyValue("lazyInitialization" , "${mybatis.lazy-initialization:false}" ); }); registry.registerBeanDefinition(MapperScannerConfigurer.class.getName(), builder.getBeanDefinition()); } } public void setBeanFactory (BeanFactory beanFactory) { this .beanFactory = beanFactory; } }