Basic principle of Mybatis and integration of Spring

Basic principle of Mybatis and integration of Spring

MyBatis is an excellent persistence layer framework, which supports custom SQL, stored procedures and advanced mapping. MyBatis eliminates almost all JDBC code and the work of setting parameters and obtaining result sets. MyBatis can configure and map primitive types, interfaces and Java POJO s (Plain Old Java Objects) to records in the database through simple XML or annotations.

MyBatis and its integration with Spring need to solve the following problems

  • How do sql statements in xml files map to interfaces
  • How to instantiate the mapper interface
  • How to hand over mapper objects to spring ioc for management

In this article, we first solve the latter two problems. Of course, we should first understand how the first problem is solved, and the solution of the problem is closely related to the second problem. In fact, it is not difficult to solve:

  • Firstly, the configuration xml file is read in through a read in stream, and then sent to a class to parse into a related configuration. Finally, the parsing results are stored in an object (there are several important attributes: data source related configuration and mapper related location)
    • During the parsing process, mapper parsing is very important. mybatis will store each mapper interface into the configuration
    • At the same time, the mapper interface and the corresponding mapersqlxml file are in the same path. Therefore, we only need to coexist the sql parsing of the configured sql statement. Because each sql statement in the sql configuration has an id attribute corresponding to the name of the interface method, when the method in the interface is executed, the corresponding sql statement is taken out and handed over to a specific executor for processing
  • Create a factory class and create an operation class according to the in the configuration. This class is called SqlSession in Mybatis
  1. How to instantiate the mapper interface?

Through dynamic agent

  1. How to hand over mapper objects to spring ioc for management

Register the proxy object generated by dynamic proxy into ioc, but our common registration method is usually a ready-made class, and the proxy object is a dynamically generated class without a certain class. How to solve this? We have to mention a special bean in spring - > factorybean, which uses a generic type. Each factorybean corresponds to an object. We can return the dynamically generated object by implementing the factorybean interface

1, Important interface

1.1. BeanDefinitionRegistryPostProcessor

public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor {

	/*
	1. BeanFactoryPostProcessor: Provide the developer with an interface. When calling this interface, all beans in the project have been loaded but not instantiated. In this interface, we can obtain the specified beanDefinition, modify it or add attribute values
	2. BeanDefinitionRegistryPostProcessor Inheriting beanfactoryprocessor, it provides a registration interface through which we can load and register our own beans
	*/
	void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException;
}

1.2. ImportBeanDefinitionRegistrar

ImportBeanDefinitionRegistrar and BeanDefinitionRegistryPostProcessor are functionally similar

ImportBeanDefinitionRegistrar can also register new beans, but it is used with * * @ Import registration. In addition to providing BeanDefinitionRegistry object, it also provides AnnotationMetadata object

AnnotationMetadata: inherits classmetadata and annotatedtypemetadata, so it stores the meta information of the class and the annotation information on the class

ImportBeanDefinitionRegistrar focuses on registering related beans according to the information of the class modified by @ Import and other annotations on the class

public interface ImportBeanDefinitionRegistrar {
	default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry,
			BeanNameGenerator importBeanNameGenerator) {
		registerBeanDefinitions(importingClassMetadata, registry);
	}

	default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
	}
}

1.3. FactoryBean

//factoryBean. The object returned by GetObject () has actually been instantiated and initialized. I just want to take advantage of other functions such as automatic injection of ioc
public interface FactoryBean<T> {

	String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType";

    //Inherit the FactoryBean to implement the getObject method, return the object we created, and put it into ioc for management
	@Nullable
	T getObject() throws Exception;
	
	@Nullable
	Class<?> getObjectType();

	default boolean isSingleton() {
		return true;
	}
}

2, Mapper proxy class generation logic

  • Because the mapper interface and related sql statements were resolved before, a class is needed to maintain these mapper interfaces. This class is MapperRegistry

  • There is a mapper generation factory to create the specified production object. This factory is MapperProxyFactory. Adopt factory method mode

  • To implement dynamic proxy based on interface, jdk dynamic proxy is adopted. A class is required to implement InvocationHandler interface, which is MapperProxy

2.1. MapperRegistry

Literally, it is a mapper registry, which mainly stores the relationship between the mapper interface and the agent factory

mapperRegistry is saved in the Configuration class, which is the Configuration we parsed from the xml file. In addition, it also provides some method tool classes according to business needs

public class MapperRegistry {

  private final Configuration config;
  private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();

  public MapperRegistry(Configuration config) {
    this.config = config;
  }

  @SuppressWarnings("unchecked")
  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);//Get the specified proxy factory from the registered mapper
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {//Create a proxy instance of the mapper interface through the proxy factory
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }
	//***Methods such as addMapper are omitted
}

2.2. MapperProxyFactory

//Factory method pattern, which distributes the task of generating specific products to specific product factories
//This is mainly about creating objects based on jdk dynamic proxy
public class MapperProxyFactory<T> {

  private final Class<T> mapperInterface;//mapper class (i.e. interface) to be generated
  private final Map<Method, MapperMethodInvoker> methodCache = new ConcurrentHashMap<>();

  public MapperProxyFactory(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
  }

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

  public T newInstance(SqlSession sqlSession) {
    //Create a proxy class MapperProxy that implements the InvocationHandler interface
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }
  //Omit getter setter....
}

2.3. MapperProxy

public class MapperProxy<T> implements InvocationHandler, Serializable {

  //Omit.....
  private final SqlSession sqlSession;
  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethodInvoker> methodCache;

  public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethodInvoker> methodCache) {
    this.sqlSession = sqlSession;
    this.mapperInterface = mapperInterface;
    this.methodCache = methodCache;
  }

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    //Specifically, bind and execute with sql
  }
  //Omit.....
}

3, spring integrates mybatis

  • Give the dynamically generated proxy class to spring management - > mapperfactorybean (implements the FactoryBean interface)
  • During spring startup, scan the specified mapper according to the configuration and create and generate maperfactorybean

3.1. MapperFactoryBean

//Put the corresponding MapperFactoryBean into spring, and spring will call the getObject method to put the returned instance into spirng ioc for management
//Inherited from SqlSessionDaoSupport, which adds support for sqlSession
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
  private Class<T> mapperInterface;

  private boolean addToConfig = true;//Register the scanned mapperInterface in the configuration

  public MapperFactoryBean() {}

  public MapperFactoryBean(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
  }
  
  //The getmapper method in sqlSession was called
  //Call chain: sqlsession getMapper()->configuration. getmapper()->mapperRegistry. getmapper->mapperProxyFactory. Newinstance() [JDK dynamic proxy generation]
  @Override
  public T getObject() throws Exception {
    return getSqlSession().getMapper(this.mapperInterface);
  }

   //This class inherits from SqlSessionDaoSupport, and SqlSessionDaoSupport inherits from DaoSupport
    /**
    DaoSupport It is an abstract class that implements the InitializingBean interface. We may not be familiar with daosupplport, but we are familiar with JDBC DaoSupport. JDBC DaoSupport is also an abstract class that inherits from DaoSupport. In non Mybatis projects, we may inherit JDBC DaoSupport in dao and inject datasource into this class in the form of xml configuration, This allows you to use the JDBC template in JDBC DaoSupport
    
    **/
  @Override
  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) {
              logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", e);
              throw new IllegalArgumentException(e);
          } finally {
              ErrorContext.instance().reset();
          }
      }
  }
  /**
   * Omit....
   */
}

3.2. MapperScannerConfigurer

Two questions:

  1. With MapperFactoryBean, do we want to inject one by one like this
<bean id="xxxxMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
     <property name="mapperInterface" value="xxxxxMapper"/>
</bean>

When we have many mapper s, how can we automatically assemble them

A: by implementing the methods in the BeanDefinitionRegistryPostProcessor interface, scan the mapper interface under the specified path, create the corresponding beanDefinition and register it in the ioc

In this way, we can only inject the class that implements the BeanDefinitionRegistryPostProcessor interface. This class is mappercannerconfigurer. We can configure it as follows:

<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
     <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
     <property name="basePackage" value="com.lmt.mapper"/>
</bean>

**Implementation: * * mappercannerconfigurer will use the bean scanner ClassPathBeanDefinitionScanner in spring to scan out relevant mapper interfaces according to relevant configurations, such as basePackage, and create relevant FactoryBean instances

//With the implementation of BeanDefinitionRegistryPostProcessor, spring will call the relevant methods of BeanDefinitionRegistryPostProcessor interface in ioc after all beans have been registered but not instantiated
public class MapperScannerConfigurer
    implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {

  private String basePackage;

  private boolean addToConfig = true;

  private String lazyInitialization;

  private SqlSessionFactory sqlSessionFactory;

  private SqlSessionTemplate sqlSessionTemplate;

  private String sqlSessionFactoryBeanName;

  private String sqlSessionTemplateBeanName;

  private Class<? extends Annotation> annotationClass;

  private Class<?> markerInterface;

  private Class<? extends MapperFactoryBean> mapperFactoryBeanClass;

  private ApplicationContext applicationContext;

  private String beanName;

  private boolean processPropertyPlaceHolders;

  private BeanNameGenerator nameGenerator;


  @Override
  public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    if (this.processPropertyPlaceHolders) { //Whether to process the properties with placeholders in this class, such as Value(${})
      processPropertyPlaceHolders();
    }
	//Create a mapper scanner under the classpath. Classpathmappercanner inherits from ClassPathBeanDefinitionScanner
    ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
    //In fact, the following information is null during execution because there is no configuration [it may be that some have not been initialized in spring and cannot be injected], which will make the BeanDefinition created by ClassPathMapperScanner have no corresponding class attribute value, directly resulting in the MapperFactoryBean created without sqlSession and other attribute values, affecting the operation of the database. What should I do,
    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));
    }
    scanner.registerFilters();//Set scan policy for scanner
    scanner.scan(
        StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
  }
	
  //This method obtains all the propertyresourceconfigurators in the container, because the methods defined by the beanfactoryprocessor interface in all the propertyresourceconfigurator classes in the container have not been executed when the postProcessBeanDefinitionRegistry of this method is executed, Therefore, it is impossible to set the relevant property values of beans in all beandefinitions. The proposed method simulates a Spring factory environment, registers this class therein, and finally executes the postProcessBeanFactory method of propertyresourceconfigurator to obtain the values of relevant properties
  private void processPropertyPlaceHolders() {
      //Gets the propertyresourceconfigurator object in spring
      Map<String, PropertyResourceConfigurer> prcs = applicationContext.getBeansOfType(PropertyResourceConfigurer.class,false, false);

      
  	 if (!prcs.isEmpty() && applicationContext instanceof ConfigurableApplicationContext){
         //Get the BeanDefinition corresponding to this class
      BeanDefinition mapperScannerBean = ((ConfigurableApplicationContext) applicationContext).getBeanFactory()
          .getBeanDefinition(beanName);
	 //Simulate a new factory
      DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
      factory.registerBeanDefinition(beanName, mapperScannerBean);//Register the beandefinition of this class
		//Execute the postProcessBeanFactory interface method of propertyresourceconfigurator
      for (PropertyResourceConfigurer prc : prcs.values()) {
        prc.postProcessBeanFactory(factory);//Propertyresourceconfigurator will get the value in the environment and put the relevant properties into the beandefinition of this class
      }
	 //Get property value
      PropertyValues values = mapperScannerBean.getPropertyValues();
	 //Update attribute information in turn
      this.basePackage = updatePropertyValue("basePackage", values);
      this.sqlSessionFactoryBeanName = updatePropertyValue("sqlSessionFactoryBeanName", values);
      this.sqlSessionTemplateBeanName = updatePropertyValue("sqlSessionTemplateBeanName", values);
      this.lazyInitialization = updatePropertyValue("lazyInitialization", values);
    }
    this.basePackage = Optional.ofNullable(this.basePackage).map(getEnvironment()::resolvePlaceholders).orElse(null);
    this.sqlSessionFactoryBeanName = Optional.ofNullable(this.sqlSessionFactoryBeanName)
        .map(getEnvironment()::resolvePlaceholders).orElse(null);
    this.sqlSessionTemplateBeanName = Optional.ofNullable(this.sqlSessionTemplateBeanName)
        .map(getEnvironment()::resolvePlaceholders).orElse(null);
    this.lazyInitialization = Optional.ofNullable(this.lazyInitialization).map(getEnvironment()::resolvePlaceholders)
        .orElse(null);
  }
  //Omit.....
}

The classpathmappercanner class is relatively large, so we only focus on the processing part after scanning

//Classpathmappercanner inherits from ClassPathBeanDefinitionScanner. ClassPathBeanDefinitionScanner will scan out classes according to the configuration and encapsulate them into BeanDefinition. Finally, it will register them in spring. Finally, it will hand over all BeanDefinition to subclasses, that is, classpathmappercanner for processing. The following is the processing method
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
    GenericBeanDefinition definition;
    //ergodic
    for (BeanDefinitionHolder holder : beanDefinitions) {
      definition = (GenericBeanDefinition) holder.getBeanDefinition();
      String beanClassName = definition.getBeanClassName();
      //Set the bean class of definition as MapperFactoryBean, that is, FactoryBean in spring to dynamically generate proxy classes
      definition.setBeanClass(this.mapperFactoryBeanClass);
//--------------------------------------------------------------------------
	  /*Set the initial values of FactoryBean properties addToConfig, sqlSessionFactory and sqlSessionTemplate. There are two situations:
       1. These attribute information may not exist in this class (classpathmappercanner) because these classes have not been instantiated in spring at this time, so the initial value of MapperFactoryBean cannot be set
       2.These objects have been initialized in spring. Mappercannerconfigurer may have been injected with these attribute information during initialization. Just give it to this class, that is, classpathmappercanner. Here, you can initialize the value of mappercfactorybean.
       
       In the first case, the MapperFactoryBean created by Spring does not have a SqlSession object, which will directly affect the operation of the database. What should we do? See below: set the injection method to type injection
       */
      definition.getPropertyValues().add("addToConfig", this.addToConfig);
      boolean explicitFactoryUsed = false;//Flag whether the has been initialized
      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)) {
        definition.getPropertyValues().add("sqlSessionTemplate",
            new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
        explicitFactoryUsed = true;
      } else if (this.sqlSessionTemplate != null) {
        definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
        explicitFactoryUsed = true;
      }
 //----------------------------------------------------------------------------------
	
      if (!explicitFactoryUsed) {
         //If the value of the required property of FactoryBean cannot be obtained, the BeanDefinition can be set to inject according to the property type, which means that the FactoryBean will be injected according to the type when instantiated
        definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
      }
      definition.setLazyInit(lazyInitialization);
    }
  }

4, spring integrates Mybatis support for annotation @ MapperScan

We already have the mappercannerconfigurer class to help us scan and register related classes, but we still need to configure mappercannerconfigurer as a spring bean in the configuration file. Is there any other way to simplify this step? Of course, the answer is: @ MapperScan

4.1. @MapperScan

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)//a key
@Repeatable(MapperScans.class)
public @interface MapperScan {
  String[] value() default {};
  String[] basePackages() default {};
  /**
   * ellipsis
   */
}

When spring scans the @ MapperScan annotation marked on the configuration class during startup (actually the scanning performed by the ConfigurationClassPostProcessor class), if it finds that there is an @ import annotation, it will process the class imported with the import annotation.

4.2. MapperScannerRegistrar

Mappercannerregister inherits from ImportBeanDefinitionRegistrar

public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {

  @Override
  public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
      //Gets the attribute value of the annotation on the class marked by the MapperScan annotation
    AnnotationAttributes mapperScanAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
    if (mapperScanAttrs != null) {
        //According to the attribute value beanDefinition
      registerBeanDefinitions(importingClassMetadata, mapperScanAttrs, registry,
          generateBaseBeanName(importingClassMetadata, 0));
    }
  }

  void registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs,
      BeanDefinitionRegistry registry, String beanName) {
	//The builder mode creates a builder, and the target definition is mappercannerconfigurer
    BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
    //Get the attribute value from the annotation and add it to the attribute corresponding to beandefinitiotn--------------------------
    builder.addPropertyValue("processPropertyPlaceHolders", true);

    Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
    if (!Annotation.class.equals(annotationClass)) {
      builder.addPropertyValue("annotationClass", annotationClass);
    }

    Class<?> markerInterface = annoAttrs.getClass("markerInterface");
    if (!Class.class.equals(markerInterface)) {
      builder.addPropertyValue("markerInterface", markerInterface);
    }

    Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
    if (!BeanNameGenerator.class.equals(generatorClass)) {
      builder.addPropertyValue("nameGenerator", BeanUtils.instantiateClass(generatorClass));
    }

    Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
    if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
      builder.addPropertyValue("mapperFactoryBeanClass", mapperFactoryBeanClass);
    }

    String sqlSessionTemplateRef = annoAttrs.getString("sqlSessionTemplateRef");
    if (StringUtils.hasText(sqlSessionTemplateRef)) {
      builder.addPropertyValue("sqlSessionTemplateBeanName", annoAttrs.getString("sqlSessionTemplateRef"));
    }

    String sqlSessionFactoryRef = annoAttrs.getString("sqlSessionFactoryRef");
    if (StringUtils.hasText(sqlSessionFactoryRef)) {
      builder.addPropertyValue("sqlSessionFactoryBeanName", annoAttrs.getString("sqlSessionFactoryRef"));
    }
     //basePackage information
    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()));
    if (basePackages.isEmpty()) {
      basePackages.add(getDefaultBasePackage(annoMeta));
    }

    String lazyInitialization = annoAttrs.getString("lazyInitialization");
    if (StringUtils.hasText(lazyInitialization)) {
      builder.addPropertyValue("lazyInitialization", lazyInitialization);
    }
    builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(basePackages));
 //-------------------------------------------------------------------
	//Registering this beandefinition spring will eventually instantiate the registered beandefinition
    registry.registerBeanDefinition(beanName, builder.getBeanDefinition());

  }
  /**
   * ellipsis
   */
}

This is a common annotation pattern

5, Imitate

Customize a FactoryBean based component that automatically generates proxy objects based on interfaces

5.1. LMTProxy

/**
 * @author Li Mengtong
 * @time 2021/8/8 10:52
 * @Description  Proxy object
 */
public class LMTProxy<T> implements InvocationHandler {
    private Class<T> lmtInterface;

    public LMTProxy(Class<T> lmtInterface) {
        this.lmtInterface = lmtInterface;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (Object.class.equals(method.getDeclaringClass())) {
            return method.invoke(this, args);
        } else {
            System.out.println("\n-------------------------------------------------");
            System.out.println("call" + lmtInterface.getName() + " : " + method.getName() + "method");
            System.out.println("The parameters are:");
            if (args != null)
                Arrays.stream(args).forEach(System.out::println);

            Class<?> returnType = method.getReturnType();
            System.out.println("Return type:" + returnType.getName());
            System.out.println("-------------------------------------------------\n");
            if (method.getName().equals("getName")) {
                return "Hello";
            }
            return null;
        }
    }
}

5.2. LMTFactory

Agent construction plant

public class LMTFactory<T> {
    private Class<T> lmtInterface;

    public LMTFactory(Class<T> lmtInterface) {
        this.lmtInterface = lmtInterface;
    }

    public T newInstance() {
        final LMTProxy<T> lmtProxy = new LMTProxy<>(lmtInterface);
        return (T) Proxy.newProxyInstance(lmtInterface.getClassLoader(), new Class[]{lmtInterface}, lmtProxy);
    }

    public Class<T> getLmtInterface() {
        return lmtInterface;
    }

    public void setLmtInterface(Class<T> lmtInterface) {
        this.lmtInterface = lmtInterface;
    }
}

5.3. LMTFactoryBean

Based on FactoryBean, implement the method of registering objects to ioc in factory method mode

public class LMTFactoryBean<T> implements FactoryBean<T> {

    private Class<T> lmtInterface;

    private LMTSqlExecutor sqlExecutor;//Just to test the type injection function, there is no actual function

    @Override
    public T getObject() throws Exception {
        LMTFactory<T> lmtFactory = new LMTFactory<>(lmtInterface);
        return lmtFactory.newInstance();
    }

    @Override
    public Class<?> getObjectType() {
        return lmtInterface;
    }

    public Class<T> getLmtInterface() {
        return lmtInterface;
    }

    public void setLmtInterface(Class<T> lmtInterface) {
        this.lmtInterface = lmtInterface;
    }

    public LMTSqlExecutor getSqlExecutor() {
        return sqlExecutor;
    }

    public void setSqlExecutor(LMTSqlExecutor sqlExecutor) {
        System.out.println("LMTSqlExector is being injected into the LMTFactoryBean");
        this.sqlExecutor = sqlExecutor;
    }
}

5.4. FtBeanImportConfigurer

Used to create the specified FactoryBean class

public class FtBeanImportConfigurer implements BeanDefinitionRegistryPostProcessor {

    public static final String IMPORTINTERFACES_ATTRIBUTE_NAME = "ImportInterfaces";

    private Class<?>[] ImportInterfaces;

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        if (ImportInterfaces != null) {
            for (Class inf : ImportInterfaces) {
                if (inf.isInterface()) {
                    GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
                    beanDefinition.setBeanClass(LMTFactoryBean.class);
                    beanDefinition.getPropertyValues().addPropertyValue("lmtInterface", inf);
                    beanDefinition.setLazyInit(false);
                    beanDefinition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);//Important: set to autowire_ BY_ In type mode, Spring can analyze the property types of FactoryBean and inject injectable properties
                    registry.registerBeanDefinition(inf.getName(), beanDefinition);
                } else {
                    System.out.println("-------- " + inf.getName() + "is not a interface -------------");
                }
            }
        }
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {

    }

    public Class<?>[] getImportInterfaces() {
        return ImportInterfaces;
    }

    public void setImportInterfaces(Class<?>[] importInterfaces) {
        ImportInterfaces = importInterfaces;
    }
}

5.5. Annotation

With the above points, we have established our own small demo of generating proxy objects based on jdk dynamic proxy by imitating mybatis. We can continue to imitate @ MapperScan for annotation configuration

5.5.1. FtBeanImportRegister
public class FtBeanImportRegister implements ImportBeanDefinitionRegistrar {//Implement ImportBeanDefinitionRegistrar to register the specified class
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        //Get annotation information
        Map<String, Object> annotationAttributes = importingClassMetadata.getAnnotationAttributes(EnableLmt.class.getName());
        Class<?>[] interfaces = null;
        for (String key : annotationAttributes.keySet()) {
            if (key.equals("value")) {
                Object o = annotationAttributes.get(key);
                if (o instanceof Class[]) {
                    interfaces = (Class<?>[]) o;
                }
            }
        }
        if (interfaces == null) {
            return;
        }
        // Register the definition of ftbeanimportconfigurator
        GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
        beanDefinition.setBeanClass(FtBeanImportConfigurer.class);
        beanDefinition.getPropertyValues().add(FtBeanImportConfigurer.IMPORTINTERFACES_ATTRIBUTE_NAME, interfaces);
        registry.registerBeanDefinition(beanDefinition.getBeanClassName(), beanDefinition);
    }
}
5.5.2. EnableLmt
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FtBeanImportRegister.class)//Import FtBeanImportRegister
public @interface EnableLmt {
    Class<?>[] value();//Interface to import
}

Keywords: Java

Added by BandonRandon on Wed, 29 Dec 2021 09:51:22 +0200