directory
- Mapper proxy generation process in Mybatis
- Mapper proxy generation process when integrated with Spring
- Mapper agent generation process when integrated with SpringBoot
Mapper proxy generation process in Mybatis
Build the proxy class factory
Step by step from the entry point, the build() method in the SqlSessionFactoryBuilder class loads the configuration file
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 {
/ /... omit}}Copy the code
Read the configuration file as an XMLConfigBuilder object and call the parse() method to parse the file into parse()
public Configuration parse() { // ... Omit parsed = true; parseConfiguration(parser.evalNode("/configuration")); return configuration; }Copy the code
You can see that parsing is done in the parseConfiguration method.
private void parseConfiguration(XNode root) {
try {
/ /... omit
/ / parsing mapper
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: "+ e, e); }}Copy the code
The last method to parse mapper, mapperElement(root.evalNode(“mappers”)),
private void mapperElement(XNode parent) throws Exception {
if(parent ! =null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
// Load all class files under package
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) {
// Load through mapper.xml
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) {
// Load through mapper.xml
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) {
// Load through a single class fileClass<? > 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.");
}
}
}
}
}
Copy the code
The entire mapperElement() method is the process of loading a Mapper. You can see that a Mapper can be loaded in two forms: through a class file and through an XML file. This is where the process of building a Mapper proxy starts, so step by step. Taking a look at the loading process through an XML file, Mybatis reads the mapper-related configuration as an XMLMapperBuilder object and parses it into this method using the parse() method
public void parse(a) {
if(! configuration.isResourceLoaded(resource)) {// Load the XML file
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
// Load the mapper class file
bindMapperForNamespace();
}
/ /... omit
}
Copy the code
The parse() method does two main things, loading an XML file and loading a class file. Take a look at the loading of XML
private void configurationElement(XNode context) {
try {
// Get the namespace of the XML file
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
// Save the namespace that gets the XML file
builderAssistant.setCurrentNamespace(namespace);
/ /... omit
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: "+ e, e); }}Copy the code
This article analyzes the generation process of the Mapper agent, so the details of loading XML will not be analyzed in detail. Instead, we pay attention to the value of the namespace tag in the XML file. And set the value into the builderAssistant object. Now let’s go back to loading the class file. Go to the bindMapperForNamespace() method
private void bindMapperForNamespace(a) {
// Get the namespace value set in the XML file
String namespace = builderAssistant.getCurrentNamespace();
if(namespace ! =null) { Class<? > boundType =null;
try {
/ / load the classes
boundType = Resources.classForName(namespace);
} catch (ClassNotFoundException e) {
//ignore, bound type is not required
}
if(boundType ! =null) {
if(! configuration.hasMapper(boundType)) {// Spring may not know the real resource name so we set a flag
// to prevent loading again this resource from the mapper interface
// look at MapperAnnotationBuilder#loadXmlResource
configuration.addLoadedResource("namespace:" + namespace);
// Add it to configurationconfiguration.addMapper(boundType); }}}}Copy the code
BindMapperForNamespace () loads the corresponding Mapper interface through the namespace value set in the XML file, and is added to the Configuration through configuration.addMapper().
Remember that there are two ways to load a Mapper: through a class file and through an XML file. The Mapper interface is loaded into the Configuration directly by calling configuration.addmapper () as a class file.
Configuration is the global Configuration class of MyBatis. All myBatis related information is saved in Configuration. Proceed to the addMapper method on Configuration
public <T> void addMapper(Class<T> type) {
mapperRegistry.addMapper(type);
}
Copy the code
Configuration adds the corresponding Mapper interface to mapperRegistry and then to the mapperregistry.addmapper () method
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
/ /... omit
try {
knownMappers.put(type, new MapperProxyFactory<T>(type));
/ /... omit
} finally {
if(! loadCompleted) { knownMappers.remove(type); }}}}Copy the code
This method first determines if it is an interface, and if it is, adds the Mapper interface to knownMappers. Take a look at the definition of knownMappers
private finalMap<Class<? >, MapperProxyFactory<? >> knownMappers =newHashMap<Class<? >, MapperProxyFactory<? > > ();Copy the code
KnownMappers is a HashMap that holds all mapper interfaces and their corresponding Mapper agent factories.
At this point, mapper has been loaded, but no proxy objects for Mapper have been generated, just the corresponding proxy factories.
Generate and use proxy objects
Mybatis does not generate the proxy object when the Mapper interface is loaded, but when it is called. Start with the entrance
sqlSession.getMapper(XXX.class)
Copy the code
SqlSession Default is DefaultSqlSession. Go to the getMapper() method of DefaultSqlSession
@Override
public <T> T getMapper(Class<T> type) {
return configuration.<T>getMapper(type, this);
}
Copy the code
Continue to the getMapper in Configuration
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
Copy the code
Continue to mapperregistry.getmapper ()
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
/ /... omit
}
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: "+ e, e); }}Copy the code
The proxy factory class MapperProxyFactory corresponding to the Mapper interface is retrieved from knownMappers, and then the real proxy object is retrieved via MapperProxyFactory. Enter newInstance() to the MapperProxyFactory
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);
}
Copy the code
The MapperProxy class is generated first, and then the real Proxy class is generated through Proxy. Take a look at the MapperProxy class
public class MapperProxy<T> implements InvocationHandler.Serializable {
/ /... omit
}
Copy the code
MapperProxy implements the InvocationHandler interface. The specific processing logic of the Mapper interface is also processed in this class.
At this point, the proxy object is actually generated.
Mapper proxy generation process when integrated with Spring
The JAR of Mybatis – Spring is needed when mybatis is integrated with Spring.
Spring registers the Mapper proxy class
Since it is integrated with Spring, it is necessary to configure myBatis to be managed by Spring. Spring XML file configuration
<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>
<! --sqlSessionFactory-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<! -- Bind mybatis profile -->
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<! Mapper.xm -->
<property name="mapperLocations" value="classpath:cn/ycl/mapper/*.xml"/>
</bean>
<! -- Register all mapper-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<! The basePackage property is the package path to the mapper interface file. -->
<! -- You can set more than one package path using semicolons or commas -->
<property name="basePackage" value="cn/ycl/mapper"/>
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean>
Copy the code
Mybatis dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource MapperScannerConfigurer The process of generating the Mapper agent is mainly implemented in MapperScannerConfigurer
public class MapperScannerConfigurer
implements BeanDefinitionRegistryPostProcessor.InitializingBean.ApplicationContextAware.BeanNameAware {
/ /... omit
}
Copy the code
Key points in MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor BeanDefinitionRegistryPostProcessor are Spring to keep the extension points, You can register custom beans into Spring.
In MapperScannerConfigurer realized BeanDefinitionRegistryPostProcessor postProcessBeanDefinitionRegistry () method, Mapper registration is registered in this method
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
/ /... omit
// Instantiate ClassPathMapperScanner and configure the scanner-related properties
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);
}
// Register scan rules
scanner.registerFilters();
// Scan and register all mapper
scanner.scan(
StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
Copy the code
PostProcessBeanDefinitionRegistry () the main logic is to define a ClassPathMapperScanner object, then calls the registerFilters scan () registration rules, finally call the scan () method.
Defined in XML MapperScannerConfigurerbean can set a annotationClass attribute, the value is an annotation class, call registerFilters (), RegisterFilters () will add a class that scans only classes with annotationClass annotations set, which are not set here, scanning all interfaces. This field will be used when SpringBoot integrates mybatis
Take a look at the definition of the ClassPathMapperScanner class
public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {
/ /... omit
}
Copy the code
ClassPathMapperScanner inherited ClassPathBeanDefinitionScanner ClassPathBeanDefinitionScanner is defined in the Spring, Is a Spring utility that scans all bean definitions from a specified package.
Take a look at the Scan () method of ClassPathMapperScanner
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
if (beanDefinitions.isEmpty()) {
/ /... omit
} else {
processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
}
Copy the code
All mappers have been scanned by super.doscan (basePackages), continue with the processBeanDefinitions() method
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
AbstractBeanDefinition definition;
BeanDefinitionRegistry registry = getRegistry();
// Iterate over all the scanned beans
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");
// Add a constructor that takes the interface type as an input
definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);
// Convert the bean type to mapperFactoryBean
definition.setBeanClass(this.mapperFactoryBeanClass);
// Add the addToConfig attribute
definition.getPropertyValues().add("addToConfig".this.addToConfig);
definition.setAttribute(FACTORY_BEAN_OBJECT_TYPE, beanClassName);
boolean explicitFactoryUsed = false;
// Add sqlSessionFactory
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;
}
// Add the sqlSessionTemplate attribute
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()); }}}Copy the code
This method is long, but not complicated. The main logic is to change the scanned bean type to MapperFactoryBean type and add a constructor that takes the interface type as an input parameter. In other words, Spring obtains mapper from FactoryBean. Finally by calling egistry. RegisterBeanDefinition () method to register in the Spring.
Take a look at the MapperFactoryBean definition provided by Mybatis
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {}Copy the code
MapperFactoryBean implements FactoryBean, which is a FactoryBean provided by Spring that produces objects
MapperFactoryBean also inherits SqlSessionDaoSupport, which inherits DaoSupport and implements InitializingBean. The purpose of the InitializingBean is to call the afterPropertiesSet() method of the InitializingBean first when Spring initializes the bean object.
The checkDaoConfig() method is called in afterPropertiesSet() of DaoSupport.
public final void afterPropertiesSet(a) throws IllegalArgumentException, BeanInitializationException {
this.checkDaoConfig();
try {
this.initDao();
} catch (Exception var2) {
throw new BeanInitializationException("Initialization of DAO failed", var2); }}Copy the code
The implementation logic for the specific checkDaoConfig() method is in the MapperFactoryBean
protected void checkDaoConfig(a) {
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) {
/ /.. omit
} finally{ ErrorContext.instance().reset(); }}}Copy the code
OK, this is back to Mybatis. As mentioned earlier, the configuration.addmapper () method simply generates the corresponding agent factory.
The whole process above is to register mapper as the bean of Spring, and set mapper to the configuration in Mybatis. Therefore, Spring can use automatic injection of that set when using. Mybatis can also use sqlSession to obtain the proxy object of Mapper
Spring generates proxy objects
All beans corresponding to mapper in Spring are mapperFactoryBeans corresponding to MapperFactoryBean, so the mapper bean is generated by the getObject() method of MapperFactoryBean
public T getObject(a) throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
Copy the code
The MapperFactoryBean gets the proxy object from getMapper() after getting sqlSession. This is where myBatis returns to the process of generating proxy objects.
Mapper agent generation process when integrated with SpringBoot
The jar of Mybatis -spring-boot-starter is used when mybatis is integrated with Spring. Mybatis -spring-boot-starter relies on the jar mybatis-spring-boot-autoconfigure, Mybatis = mybatis = mybatis = mybatis = mybatis = mybatis = mybatis = mybatis = mybatis = mybatis = mybatis = mybatis = mybatis
Check the meta-INF /spring.factories file under mybatis- spring-boot-AutoConfigurejar according to the SpringBoot automatic loading mechanism
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration,\
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
Copy the code
SpringBoot automatically loads the MybatisAutoConfiguration class, which defines the beans required by MyBtis.
/ / generated SqlSessionFactory
@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
/ /... omit
}
/ / generated SqlSessionTemplate
@Bean
@ConditionalOnMissingBean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
/ /... omit
}
/ / scanning mapper
@Configuration
@Import({MybatisAutoConfiguration.AutoConfiguredMapperScannerRegistrar.class})
@ConditionalOnMissingBean({MapperFactoryBean.class, MapperScannerConfigurer.class})
public static class MapperScannerRegistrarNotFoundConfiguration implements InitializingBean {
public MapperScannerRegistrarNotFoundConfiguration(a) {}public void afterPropertiesSet(a) {
MybatisAutoConfiguration.logger.debug("Not found configuration for registering mapper bean using @MapperScan, MapperFactoryBean and MapperScannerConfigurer."); }}/ / scanning mapper
public static class AutoConfiguredMapperScannerRegistrar implements BeanFactoryAware.ImportBeanDefinitionRegistrar {
private BeanFactory beanFactory;
public AutoConfiguredMapperScannerRegistrar(a) {}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);
});
}
/ / generated MapperScannerConfigurer
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
builder.addPropertyValue("processPropertyPlaceHolders".true);
// Register scan rules
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; }}Copy the code