Mybatis Mapper/Dao cannot be instantiated, but @autowired can be injected.

For us in Java, interfaces cannot be instantiated. And all the methods of the interface are public.

But why Mybaits mapper interface can be directly @Autowired injection use?

Here’s how Mybatis does it.

Analysis based on @mapperscan annotation of SpringBoot.

With problem analysis code:

  1. Mybatis mapper interface, how is scanned?
  2. How is the Mapper interface instantiated and then injected using @AutoWired?

@MapperScan

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {

  // In MapperScan, scan the packet path.
  // Enter the package name of the mapper interface. Scan all files under this value
  String[] value() default {};
  String[] basePackages() default{}; Class<? >[] basePackageClasses()default {};
  
  
  Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
  Class<? extends Annotation> annotationClass() defaultAnnotation.class; Class<? > markerInterface()default Class.class;
  String sqlSessionTemplateRef(a) default "";
  String sqlSessionFactoryRef(a) default "";
  Class<? extends MapperFactoryBean> factoryBean() default MapperFactoryBean.class;

}

Copy the code
@SpringBootApplication
@MapperScan("cn.thisforyou.core.blog.mapper")
public class App
{
    public static void main( String[] args )
    {
        System.out.println( "Hello World!"); SpringApplication.run(App.class,args); }}Copy the code

To use Mybatis in SpringBoot, its entry is in @mapperscan. The @mapperscan annotation is in the boot class of SpringBoot.

There’s an @Import annotation in @MapperScan.

The @import annotation allows you to load a class and manage it in Spring’s IOC

In Spring, there are several ways to manage beans in an IOC container.

  • @import this method
  • @Configuration is used in conjunction with the @bean annotation
  • @Controller @Service @Repository @Component
  • @ ComponentScan scan.
  • Bean injection can also be implemented by overriding the postProcessBeanFactory() method of BeanFactoryPostProcessor

MapperScannerRegistrar

Through the @import annotation, MapperScannerRegistrar injected into the IOC container and MapperScannerRegistrar implemented the two interfaces ImportBeanDefinitionRegistrar, ResourceLoaderAware

ImportBeanDefinitionRegistrar need use with @ Impor in Spring loaded its implementation class, there is only one method, is mainly responsible for the dynamic injection of Bean.

public interface ImportBeanDefinitionRegistrar {
	public void registerBeanDefinitions( AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry);
}
Copy the code

This method takes Spring’s BeanDefinitionRegistry, which is also an interface that provides six or seven methods

public interface BeanDefinitionRegistry extends AliasRegistry
    {
        / / register BeanDefinition
        void registerBeanDefinition(String var1, BeanDefinition var2) throws BeanDefinitionStoreException;

        / / remove BeanDefinition
        void removeBeanDefinition(String var1) throws NoSuchBeanDefinitionException;

        // Get a BeanDefinition based on name
        BeanDefinition getBeanDefinition(String var1) throws NoSuchBeanDefinitionException;

        // Check whether it exists
        boolean containsBeanDefinition(String var1);

        // BeanDefinition
        String[] getBeanDefinitionNames();

        int getBeanDefinitionCount(a);

        // Whether to use
        boolean isBeanNameInUse(String var1);
    }
Copy the code

**BeanDefinition:** Spring defines the Bean into a BeanDefinition structure, which is the data wrapper of the class, the fully qualified name of the class, whether it is a singleton, whether it is lazily loaded, and how it is injected.

RegisterBeanDefinitions is a registration method

@Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, org.springframework.beans.factory.support.BeanDefinitionRegistry registry) {

        AnnotationAttributes = AnnotationAttributes = AnnotationAttributes = AnnotationAttributes = AnnotationAttributes = AnnotationAttributes = AnnotationAttributes = AnnotationAttributes
        AnnotationAttributes mapperScanAttrs = AnnotationAttributes
                .fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
        if(mapperScanAttrs ! =null) { registerBeanDefinitions(mapperScanAttrs, registry); }}/** * Performs a parse on the MapperScan attribute value *@param annoAttrs
     * @param registry
     */
    void registerBeanDefinitions(AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry) {

        🌟 / * * * * * this is a scanner * Mybatis and inherited Spring scanner ClassPathBeanDefinitionScanner * /
        ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);

        // this check is needed in Spring 3.1
        Optional.ofNullable(resourceLoader).ifPresent(scanner::setResourceLoader);

        / /...

        /** * Read the path under the package mapper and the set of mapper.class */
        List<String> basePackages = new ArrayList<>();
        basePackages.addAll(
                Arrays.stream(annoAttrs.getStringArray("value"))
                        .filter(StringUtils::hasText)
                        .collect(Collectors.toList()));

        basePackages.addAll(
                Arrays.stream(annoAttrs.getStringArray("basePackages"))
                        .filter(StringUtils::hasText)
                        .collect(Collectors.toList()));

        basePackages.addAll(
                Arrays.stream(annoAttrs.getClassArray("basePackageClasses"))
                        .map(ClassUtils::getPackageName)
                        .collect(Collectors.toList()));

        // 🌟 This is a custom scan rule, different from Spring's default mechanism
        scanner.registerFilters();

        // 🌟 calls the scanner to scan the package path
        scanner.doScan(StringUtils.toStringArray(basePackages));
    }
Copy the code

Scanner ClassPathMapperScanner

ClassPathMapperScanner is a class of mybatis, inherited ClassPathBeanDefinitionScanner, rewrite the doScan method; It then calls its scan method as well. For example, in a package, not only classes with mapper interface, our @mapperscan mainly deals with mapper interface, so it is excluded:

Exclude classes that are not interfaces

 @Override
  protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
    return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
  }
Copy the code

DoScan method of scanner ClassPathMapperScanner

Overrides the doScan method of the parent class, and calls its method. With the scan result of the parent class, all Mapper interfaces under this package are resolved into BeanDefinitionHolder and put into the set.

  @Override
  public Set<BeanDefinitionHolder> doScan(String... basePackages) {
    Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);

    if (beanDefinitions.isEmpty()) {
      LOGGER.warn(() -> "No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");
    } else {
      // Process the scan result, if not, the interface is treated as
      // If a normal Bean is injected into IOC, an error will occur when the call is imported.
      processBeanDefinitions(beanDefinitions);
    }
    return beanDefinitions;
  }
Copy the code

BeanDefinitionHolder: A BeanDefinition holder, containing the name Be Na, and the Bean alias, which also contains the BeanDefinition.

ProcessBeanDefinitions processes beanDefinitions’ BeanClass

private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
    GenericBeanDefinition definition;
    // Loop through, one by one
    for (BeanDefinitionHolder holder : beanDefinitions) {
      definition = (GenericBeanDefinition) holder.getBeanDefinition();
      String beanClassName = definition.getBeanClassName();
      
      definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); 
      
      // Key step: This changes GenericBeanDefinition's BeanClass to mapperFactoryBeanClass;
      // The beanClass is mapper.class
      definition.setBeanClass(this.mapperFactoryBeanClass);

      definition.getPropertyValues().add("addToConfig".this.addToConfig);

      / /...}}}Copy the code

We know that there are two kinds of beans in Spring. One is a plain Bean, and the other is a FactoryBean. If a FactoryBean is instantiated, it calls its getObject method to get an object.

FactoryBean is an interface: MapperFactoryBean of Mybatis implements it;

public interface FactoryBean<T> {

  // Get the object
	@Nullable
	T getObject(a) throws Exception;
	
  / / type
	@NullableClass<? > getObjectType();// The default is singleton beans
	default boolean isSingleton(a) {
		return true; }}Copy the code

MapperFactoryBean:

MapperFactoryBean has two properties, one of which

private Class<T> mapperInterface;
Copy the code

This is the class of the Mapper interface, and look at the getObject() method that it overrides;

  @Override
  public T getObject(a) throws Exception {
    return getSqlSession().getMapper(this.mapperInterface);
  }
Copy the code

It is its SqlSession concrete class that calls a getMapper method on a MapperRegister object in the global Configuration file. Then, according to the class, the MapperProxyFactory agent factory is taken by using Map -> knownMappers in MapperRegister. NewInstance method is used to generate the object.

public class MapperProxyFactory<T> {

  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<>();

  / /...
  
  // --------
  @SuppressWarnings("unchecked")
  protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

  public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    returnnewInstance(mapperProxy); }}Copy the code

This completes the processing of a Mapper interface.

During the startup process of myBatis, mapper information scanned will be encapsulated and all information will be displayed in the Configuration.

For example, a Mapper interface

package cn.thisforyou.blog.core.mapper;
public interface PayMapper{
  pay(String outNo)
}
Copy the code

Class: paymapper.class

The addMapper method of MapperRegistry is then called to wrap a proxy pair like MapperProxyFactory

Paymapper. class,vaue:new MapperProxyFactory(class);

At the time of injection, will getObect () method, the last call MapperProxyFactory. NewInstance generated proxy to like.

MapperRegistry is in the Configuration object;

And finally: Mapper’s @AutoWired injection is essentiallyMapperFactoryBeanThrough its getObject method, the agent generates interface objects.

Conclusion:

  1. Mybatis mapper interface, how is scanned?

    Mybatis through annotations @ @ Import under MapperScan annotations to load, MapperScannerRegistrar class, such inherited ImportBeanDefinitionRegistrar class, registration of bean processing, Before registering, you will get the @mapperscan parameter value, mapper package path, and then call the new ClassPathMapperScanner(Registry) class to scan. ClassPathMapperScanner extends ClassPathBeanDefinitionScanner rewrite doScan method, define the scanning rules, Change the BeanDefinition beanClass and replace it with MapperFactoryBeanClass;

  2. How is the Mapper interface instantiated and then injected using @AutoWired?

    The Mapper interface is not instantiated, but is injected into IOC as a FactoryBean. Proxy pairs like @AutoWired are generated by calling getObject.