Mybatis Source Parsing-Interface Scanning

Keywords: Apache Mybatis Spring Session

In mybatis-spring, there's a comment called MapperScan, and we'll start with him today. When we add this comment, it represents the introduction of Bean s of the MapperScanner Registrar class.

public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {}

He's an example of ImportBean Definition Registrar type, so his life cycle approach, registerBean Definitions, is of particular concern to us. He can scan and register beans.

@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
  //Get the property values set in the annotations from MapperScan's annotations
  AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
  //Method-injected registers are injected into Scanner and registered when scanned into BeanDefinition Holder
  ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);

  // this check is needed in Spring 3.1
  if (resourceLoader != null) {
    scanner.setResourceLoader(resourceLoader);
  }

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

  Class<?> markerInterface = annoAttrs.getClass("markerInterface");
  if (!Class.class.equals(markerInterface)) {
    scanner.setMarkerInterface(markerInterface);
  }

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

  Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
  if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
    scanner.setMapperFactoryBean(BeanUtils.instantiateClass(mapperFactoryBeanClass));
  }

  scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef"));
  scanner.setSqlSessionFactoryBeanName(annoAttrs.getString("sqlSessionFactoryRef"));

  List<String> basePackages = new ArrayList<String>();
  for (String pkg : annoAttrs.getStringArray("value")) {
    if (StringUtils.hasText(pkg)) {
      basePackages.add(pkg);
    }
  }
  for (String pkg : annoAttrs.getStringArray("basePackages")) {
    if (StringUtils.hasText(pkg)) {
      basePackages.add(pkg);
    }
  }
  for (Class<?> clazz : annoAttrs.getClassArray("basePackageClasses")) {
    basePackages.add(ClassUtils.getPackageName(clazz));
  }
  scanner.registerFilters();
  scanner.doScan(StringUtils.toStringArray(basePackages));
}

The above code is nothing else but the attributes set by Scanner. As for the values of attributes, they are all obtained from MapperScanner. Finally, doScan. Maybe what we need to pay attention to is the package path of the scan, from which the scan is specified.

@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
  //Call the method of the parent class to scan the beans, and put the beans into registry, return references to these beans, and wait for subsequent processing
  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 {
    //Later processing
    processBeanDefinitions(beanDefinitions);
  }

  return beanDefinitions;
}

By our definition, sweep out the beans we need, and then customize them. Let's take a look at process Bean Definitions

private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
  GenericBeanDefinition definition;
  for (BeanDefinitionHolder holder : beanDefinitions) {
    definition = (GenericBeanDefinition) holder.getBeanDefinition();

    if (logger.isDebugEnabled()) {
      logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName() 
        + "' and '" + definition.getBeanClassName() + "' mapperInterface");
    }

    // the mapper interface is the original class of the bean
    // but, the actual class of the bean is MapperFactoryBean
    definition.getPropertyValues().add("mapperInterface", definition.getBeanClassName());
    definition.setBeanClass(this.mapperFactoryBean.getClass());  //Set the type of Bean class to mapperFactoryBean

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

    boolean explicitFactoryUsed = false;
    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)) {
      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) {
      if (logger.isDebugEnabled()) {
        logger.debug("Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
      }
      definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
    }
  }
}

For those interfaces scanned, registered beans are instances of mapperFactoryBean type. As for the attributes needed in such beans, they are also defined in the attributes, which are set when the beans are actually initialized. Now that it's FactoryBean, you need to look at his getObject method.

/**
 * {@inheritDoc}
 */
@Override
public T getObject() throws Exception {
  return getSqlSession().getMapper(this.mapperInterface);
}
 
### org.apache.ibatis.session.defaults.DefaultSqlSession#getMapper
@Override
public <T> T getMapper(Class<T> type) {
  return configuration.<T>getMapper(type, this);
}
 
### org.apache.ibatis.session.Configuration#getMapper
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
  return mapperRegistry.getMapper(type, sqlSession);
}
 
### org.apache.ibatis.binding.MapperRegistry#getMapper
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
  final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
  if (mapperProxyFactory == null) {
    throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
  }
  try {
    return mapperProxyFactory.newInstance(sqlSession);
  } catch (Exception e) {
    throw new BindingException("Error getting mapper instance. Cause: " + e, e);
  }
}

A MapperProxyFactory is created for each interface type, whose purpose is to generate a proxy object for an interface.

public class MapperProxyFactory<T> {

  private final Class<T> mapperInterface;   //Proxy interface
  private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();  //The method cache in the proxy interface is called by the execute method of MapperMethod.

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

  public Class<T> getMapperInterface() {
    return mapperInterface;
  }

  public Map<Method, MapperMethod> getMethodCache() {
    return methodCache;
  }

  @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<T>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

}

Let's take a look at MapperProxy as an example of an Invocation Handler, and then use jdk's dynamic proxy to generate the required object, which is the Bean we ultimately need. Let's take a look at MapperProxy that actually handles requests.

public class MapperProxy<T> implements InvocationHandler, Serializable {

  private static final long serialVersionUID = -6424540398559729838L;
  private final SqlSession sqlSession;
  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethod> methodCache;

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

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    if (Object.class.equals(method.getDeclaringClass())) {
      try {
        return method.invoke(this, args);
      } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
      }
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }

  private MapperMethod cachedMapperMethod(Method method) {
    MapperMethod mapperMethod = methodCache.get(method);
    if (mapperMethod == null) {
      mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
      methodCache.put(method, mapperMethod);
    }
    return mapperMethod;
  }

}

The call is mapperMethod.execute to execute the real data request.

Posted by FireDrake on Sat, 25 May 2019 13:57:21 -0700