Mybatis source analysis – Mapper creation and Spring management
When we analyze their first guess to achieve the way to compare mybatis source code implementation
Mapper created
- Since MyBatis can be used without Spring itself, mapper bean creation is done by MyBatis
- The method of creation, depending on the Mapper, is the method that corresponds to the name of the annotation or configuration file, so we assume that we are using spring’s dynamic proxy creation method
We implemented mapper ourselves to create factory agent classes:
public class MySessionFactoryProxy { public static Object getMapper(Class c){ Class[] classes = new Class[]{c}; / / dynamic Proxy for mapper Object o = Proxy. NewProxyInstance (MySessionFactoryProxy. Class. GetClassLoader (), classes, new InvocationHandler() { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// Parse SQL // execute SQL Select annotation = method.getannotation (self.class); String sql = annotation.value()[0]; System.out.println(" SQL :"+ SQL); return null; }}); return o; }}Copy the code
A factoryBean is used to call the method. Each time you call it, you create a factoryBean and pass in a Mapper class to create the mapper.
- MyMapperFactoryBean
Public class MyMapperFactoryBean<T> implements FactoryBean<T> { mapperInterface) { this.mapperInterface = mapperInterface; } private Class<T> mapperInterface; private Class<T> mapperInterface; public T getObject() throws Exception { System.out.println("get mapper"); return (T) MySessionFactoryProxy.getMapper(mapperInterface); } public Class<? > getObjectType() { return this.mapperInterface; }}Copy the code
Look at the implementation of Mybatis
- Mapper The registration class obtains the mapper
public class MapperRegistry { public <T> T getMapper(Class<T> type, SqlSession SqlSession) {/ / main call return mapperProxyFactory. NewInstance (SqlSession); }Copy the code
- Mybatis implements the agent factory class:
public class MapperProxyFactory<T> { private final Class<T> mapperInterface; Public T newInstance(SqlSession SqlSession) {MapperProxy<T> MapperProxy = new MapperProxy(SqlSession, this.mapperInterface, this.methodCache); return this.newInstance(mapperProxy); }}Copy the code
- Call the MapperFactoryBean of the getMapper method
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> { private Class<T> mapperInterface; public MapperFactoryBean(Class<T> mapperInterface) { this.mapperInterface = mapperInterface; } public T getObject() throws Exception { return this.getSqlSession().getMapper(this.mapperInterface); } public Class<T> getObjectType() { return this.mapperInterface; } public void setMapperInterface(Class<T> mapperInterface) { this.mapperInterface = mapperInterface; } public Class<T> getMapperInterface() { return this.mapperInterface; }}Copy the code
It’s basically the same as what we created
After creating mapper, we need to submit mapper to IOC management
Create a Mapper and put it in Spring IOC
Please add annotations or configuration differently, submit the class to Spring management, by Spring to create objects for us, Mapper is created by Mybatis through dynamic proxy
You create your own objects and give them to Spring to manage
- Idea 1: Created by a configuration class
@Configuration @ComponentScan("top.dzou.mybatis") public class Appconfig { @Bean public UserMapper userMapper(){ return (UserMapper) MySessionFactoryProxy.getMapper(UserMapper.class); }}Copy the code
Creating an @bean annotation for each one is impractical. How does Spring implement this?
- We remembered that beans can be configured through spring configuration files, and we thought of configuring Mapper beans in XML format
Something like this:
Create the Mapper using our own Mapper factory agent class
<bean id="roleMapper" class="top.dzou.mybatis.mapper_to_spring.my_mybatis.MyMapperFactoryBean">
<property name="mapperInterface" value="top.dzou.mybatis.mapper_to_spring.RoleMapper"/>
</bean>
<bean id="userMapper" class="top.dzou.mybatis.mapper_to_spring.my_mybatis.MyMapperFactoryBean">
<property name="mapperInterface" value="top.dzou.mybatis.mapper_to_spring.UserMapper"/>
</bean>
</beans>Copy the code
This code is not familiar, but there are still a lot of beans to configure, and we are reminded of the Mapperscan annotation used in SpringBoot
- Idea 3: Implement a note yourself
MyMapperScan
Include a package, import the Mapper import class on the MapperScan annotation, create everything below the package and put it in Spring
When Spring creates a bean, it loads the class and creates a BeanDefinition. When Spring creates a bean from a BeanDefinition, it creates a bean from mapper
We implement ourselves:
- MyMapperScan
Import mapper according to basePackages
/ / Import ImportBeanDefinitionRegister @ Import (MyBeanDefinitionRegister. Class) @ Retention (RetentionPolicy. RUNTIME) to the public @interface MyMapperScan { String[] basePackages() default {}; }Copy the code
- MyBeanDefinitionRegister
Using Spring registration of bean ImportBeanDefinitionRegistrar registered mapper
public class MyBeanDefinitionRegister implements ImportBeanDefinitionRegistrar { //class->beanDefinition->map->bean public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry BeanDefinitionRegistry) {// Get package information and add all classes registered in the package dynamically to beanDefinition {// pseudo-code basePackages = Get the mapper class used for the package name, Save to collection basePackages baseName = Get beanName} BeanDefinitionBuilder BeanDefinitionBuilder = from mapper class BeanDefinitionBuilder.genericBeanDefinition(MyMapperFactoryBean.class); BeanDefinition beanDefinition = beanDefinitionBuilder.getBeanDefinition(); beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(basePackages); beanDefinitionRegistry.registerBeanDefinition(beanName,beanDefinition); }}Copy the code
- Configuration class instead of main startup class (for testing)
@Configuration @ComponentScan("top.dzou.mybatis.mapper_to_spring") @MyMapperScan(basePackages = "Top.dzou. Mapper_to_spring.mapper ")// Custom mapper scan annotation public class Appconfig {}Copy the code
How to implement mybatis
- MapperScan
It imports a MapperScannerRegistrar to scan the classes to register Mapper
@Import({MapperScannerRegistrar.class})
public @interface MapperScan {Copy the code
- MapperScannerRegistrar
We can see that it implements ImportBeanDefinitionRegistrar bean definitions of import registration class, implement the specific registerBeanDefinitions registered bean definition method, all the package under the mapper added to a collection, Then register this collection in IOC, which is basically consistent with our idea
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware { void registerBeanDefinitions(AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry, String beanName) { BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class); List<String> basePackages = new ArrayList(); basePackages.addAll((Collection)Arrays.stream(annoAttrs.getStringArray("value")).filter(StringUtils::hasText).collect(Co llectors.toList())); basePackages.addAll((Collection)Arrays.stream(annoAttrs.getStringArray("basePackages")).filter(StringUtils::hasText).col lect(Collectors.toList())); basePackages.addAll((Collection)Arrays.stream(annoAttrs.getClassArray("basePackageClasses")).map(ClassUtils::getPackageN ame).collect(Collectors.toList())); builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(basePackages)); registry.registerBeanDefinition(beanName, builder.getBeanDefinition()); }Copy the code
It holds all mapper classes in a collection and registers them in a beanDefinition
conclusion
The whole process is mainly divided into
- Mybatis registers the entire Mybatis factory bean to IOC through MapperScan
- Spring is responsible for mapper injection IOC created by Mybatis
Key points:
- MapperScan ImportBeanDefinitionRegistrar registerBeanDefinitions beanMapperFactoryBean encapsulated into the plant in the beanDefinition and add mapper under the package The constructor parameters are passed in and registered with Spring
- After registration, Spring will call Mybatis to create mapper based on constructor parameters and factory beans and register mapper with Spring-managed beans