1: Scenario analysis

When we use SpringBoot+MyBatis, we usually import the dependencies first and then configure them

    mybatis:
      mapper-locations: classpath:mapper/*.xml
      type-aliases-package: com.coco.pojo
Copy the code

Of course, there is an annotation on the startup class

At this point, you can write an interface and then call this method to execute the corresponding SQL statement in the configuration file

So how does the underlying principle work?

All things are difficult before they are easy

The hardest part of analyzing the source code of a framework is not knowing where to start. I think that since we only need to write such an interface, then we can call the corresponding SQL statement, so there must be some special processing in this interface

When we add an annotation to the startup class, and the package path in the annotation is the path of our interface, we get an idea.

Go to @mapperscan (” com.coke.mapper “)

We see that in addition to the basic three annotations, there is another annotation@Import({MapperScannerRegistrar.class})Many of you may not know what this note is for, but let’s explain

3: The role of the @import annotation in SpringBoot

In SpringBoot, when we declare a Bean, we can add @Service, @Compont, etc., or @bean to the configuration class. Another method is @import

The @import annotation will indicate a class, and it will be processed when SpringBoot starts meaning it will instantiate the Bean, meaning it will do something to the Bean

4: MapperScannerRegistrar. The role of the class

Namely however know @ Import annotations, and now we see in into this class, the class implements the ImportBeanDefinitionRegistrar this interface

What is the use of this interface? MyBatis allows Spring to scan certain beans through this portal and these beans will be managed by Spring, which means they will be initialized by Spring.

So our custom Mapper interface will be scanned by Spring and loaded by Spring

ImportBeanDefinitionRegistrar this interface represents when the Bean is created corresponding BeanDefinition, will invoke the interface methods, we look at the interface defined in the method

What does this method do?

When Spring loads a Bean, it will first generate the Bean one by one, and then initialize the Bean one by one, that is, generate the Bean.

In short: Spring will scan our custom Mapper interface via @mapperscan (” com.coco-mapper “) annotation provided by MyBatis, and Spring will generate beanDefinitions for these mapper interfaces

5: Enter source code in debug mode

Then start the SpringBoot project in debug mode, of course, if the integration of MyBatis ha, this method I intercept, actually only need to focus on the following lines of code

@Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {// Get MapperScan AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName())); ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry); // Get basePackages from MapperScan for (String PKG: annoAttrs.getStringArray("basePackages")) { if (StringUtils.hasText(pkg)) { basePackages.add(pkg); }} / / really started to deal with the package under the path of the interface, which is our Mapper interface scanner. DoScan (StringUtils. ToStringArray (basePackages)); }Copy the code

Here you can get the full path of the package for our custom Mapper interface

6: Starts to process the Mapper interface

We entered to the upper scanner. DoScan (StringUtils. ToStringArray (basePackages)); This method

Set beanDefinitions = super.doScan(basePackages); Find a lot of code where the return value of this method is a BeanDefinitionHolder, which is the bean name and its BeanDefinition

This method scans our custom Mapper interface, generates a BeanDefinition for each interface, and returns it

protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
                Assert.notEmpty(basePackages, "At least one base package must be specified");
                Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
                for (String basePackage : basePackages) {
                        Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
                        for (BeanDefinition candidate : candidates) {
                                ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
                                candidate.setScope(scopeMetadata.getScopeName());
                                String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
                                if (candidate instanceof AbstractBeanDefinition) {
                                        postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
                                }
                                if (candidate instanceof AnnotatedBeanDefinition) {
                                        AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
                                }
                                if (checkCandidate(beanName, candidate)) {
                                        BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
                                        definitionHolder =
                                                        AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
                                        beanDefinitions.add(definitionHolder);
                                        registerBeanDefinition(definitionHolder, this.registry);
                                }
                        }
                }
                return beanDefinitions;
        }
Copy the code

If we debug to this step, we can see the return value, which also confirms what we said before

7: Treatment after getting BeanDefinition

Now let’s look at processBeanDefinitions; This method, because we’ve already got the BeanDefinition of the Mapper interface, we’re going to have to deal with it further

This method still has a lot of code, I won’t post it here, but I’ll tell you what this method does first.

Spring can change the BeanDefinition property value of the Bean before we initialize the Bean, and this method does just that. After this method is processed, the BeanDefiniton we got before will have some changes. I put up two pictures here for comparison

This is before:

This is after the method is processed:

You can see that the beanClass property of the Bean has changed and is no longer in the class of our custom Bean

What are the problems after the change?

When Spring initializes the Bean, it will get the Bean’s BenDefinition, and then initialize the Bean according to the beanClass attribute value. The value of the a.gete class should be com.xxx.a

But now changed, that is our custom Mapper interfaces after initialized by the Spring, and then execute al-qeada etClass becomes org. Mybatis. Spring. Mapper. MapperFactoryBean

8: Initialize the Bean

After the above steps, we have the BeanDefinition of the Mapper interface, and Spring is now ready to initialize the beans

Since this is where Spring’s source code comes in, I won’t go into detail here

General process:

1: When Spring initializes the bean, it initializes it according to the scope property of the bean. However, our customized Mapper interface has modified the beanClass property of BeanDefinition, so when initializing the bean, After a series of judgments, MapperProxy in MyBatis will eventually generate a proxy class. The underlying class is realized by JDK dynamic proxy

2: The Invoke method is then executed when we call the Mapper interface method, since it is a proxy class generated by the JDK dynamic proxy.

3: MyBatis can get the class where the method is located and the full path of the class. For example, we define a TestMapper interface under the com.cox. mapper package, and then there is a test() method in it. Com. Coco. Mapper. TestMapper. Test, which is the full path of the mapper interfaces + method name

4: When parsing XML configuration file, MyBatis has a namespace attribute, its value is the full path name of Mapper interface, and then add the id value, MyBatis will store all such paths in a Map. Then, when executing the interface method, it will match the value generated in step 3 and get the corresponding SQL statement

<? The XML version = "1.0" encoding = "utf-8"? > <! DOCTYPE mapper PUBLIC "- / / mybatis.org//DTD mapper / 3.0 / EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > < mapper namespace="com.coco.mapper.TestMapper"> <select id="test"> select * from test </select> </mapper>Copy the code