【Spring源码】@Import引入ImportBeanDefinitionRegistrar,实现自己的Mybatis

1,618 阅读3分钟

前面一篇文章写了@Import引入ImportSelector的原理以及在SpringBoot自动装配技术中的应用:

【spring源码】@Import在SpringBoot自动装配技术中的应用

本文讲@Import引入ImportBeanDefinitionRegistrar,以及在Mybatis中的引用,本文会写一个Mybatis的栗子,后面会把Spring源码的内容写完。

先看看ImportBeanDefinitionRegistrar这个接口:

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

这个接口中有一个方法,对比ImportSelector接口中的方法,并没有返回值,但是参数多了一个BeanDefinitionRegistry,看名称可以知道这是一个BeanDefinition注册器,那么就可以拿着这个注册器为所欲为了嘛!

下面就写一个简易版的mybatis框架,开整开整...

首先自定义一个ImportBeanDefinitionRegistrar,去实现它的registerBeanDefinitions

package importbeandefinitionregistry.registrar;

import importbeandefinitionregistry.factorybean.MyMapperFactoryBean;
import importbeandefinitionregistry.myannotation.MyMapperScan;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternUtils;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
 * 自定义的ImportBeanDefinitionRegistrar
 */
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar , ResourceLoaderAware {
   private static final String VALUE = "value";
   private ResourceLoader resourceLoader;

   /**
    * 提供了注解信息(importingClassMetadata)和一个注册器(registry)
    * @param importingClassMetadata annotation metadata of the importing class
    * @param registry current bean definition registry
    */
   @Override
   public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
      //取到相关的注解信息(包名),并进行扫描,通过代理构造对象,注册到bean容器中
      AnnotationAttributes annotationAttributes = AnnotationAttributes.fromMap(
            importingClassMetadata.getAnnotationAttributes(MyMapperScan.class.getName()));
      if(null!=annotationAttributes){
         for(String path : getScanPath(annotationAttributes)){
            //通过FactoryBean去动态生成接口的代理对象
            BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MyMapperFactoryBean.class);
            AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();
            //将接口的全路径 作为参数 传给FactoryBean 生成代理对象
            beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(path);
            //将beanDefinition注册到容器中
            registry.registerBeanDefinition(lowerFirst(path.substring(path.lastIndexOf(".")+1)),beanDefinition);
         }
      }
   }

   @Override
   public void setResourceLoader(ResourceLoader resourceLoader) {
      this.resourceLoader = resourceLoader;
   }

   /**
    * 得到注解中包下的接口全路径
    * @param annotationAttributes 注解
    * @return 注解中包下的接口全路径
    */
   private List<String> getScanPath(AnnotationAttributes annotationAttributes){
      List<String> scanPath = new ArrayList<>();
      for(String basePackage:annotationAttributes.getStringArray(VALUE)){
         ResourcePatternResolver resolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader);
         MetadataReaderFactory readerFactory = new CachingMetadataReaderFactory(resolver);
         try {
            Resource[] resources = resolver.getResources("classpath*:" + basePackage.replace('.', '/') + "/**/*.class");
            for(Resource resource:resources){
               MetadataReader metadataReader = readerFactory.getMetadataReader(resource);
               scanPath.add(metadataReader.getClassMetadata().getClassName());
            }
         } catch (IOException e) {
            e.printStackTrace();
         }
      }
      return scanPath;
   }

   /**
    * 首字母变小写
    * @param oldStr 需要转换的string
    * @return 转换后的string
    */
   private String lowerFirst(String oldStr){
      char[] charArray = oldStr.toCharArray();
      charArray[0] += 32;
      return String.valueOf(charArray);
   }
}

这个自定义的ImportBeanDefinitionRegistrar做了一些什么事情呢?主要是通过参数AnnotationMetadata拿到注解里的参数,即需要扫描的包,然后去扫描这些包。看看自定义的MapperScan

@Retention(RetentionPolicy.RUNTIME)
@Import(MyImportBeanDefinitionRegistrar.class)
public @interface MyMapperScan {
   String[] value() default {};
}

我们在使用mybatis的时候,都是通过去调用mapper的方法,然后写SQL去查询数据库,但是我们定义的mapper都是接口,而接口怎么能直接调用呢?其实我们调用的mapper不是’接口‘,而是一个代理对象。下面来看看如何对这些mapper生成代理对象的:

package importbeandefinitionregistry.factorybean;

import org.apache.ibatis.annotations.Select;
import org.springframework.beans.factory.FactoryBean;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * 自定义的 FactoryBean
 *
 * @author cy
 */
public class MyMapperFactoryBean  implements FactoryBean ,InvocationHandler{

   private Class interfaceClass;

   public MyMapperFactoryBean(Class interfaceClass){
      this.interfaceClass = interfaceClass;
   }

   /**
    *
    * @return 返回代理对象
    * @throws Exception 异常
    */
   @Override
   public Object getObject() throws Exception {
      return Proxy.newProxyInstance(getClass().getClassLoader(),new Class[]{interfaceClass},this);
   }

   /**
    *
    * @return 返回class
    */
   @Override
   public Class<?> getObjectType() {
      return interfaceClass;
   }

   /**
    * 打印出注解中的sql
    * @param proxy 代理
    * @param method 方法
    * @param args 参数
    * @return object
    * @throws Throwable 异常
    */
   @Override
   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      String sql = method.getDeclaredAnnotation(Select.class).value()[0];
      //打印sql
      System.out.println(sql);
      //JDBC去查询数据库,然后返回结果集
      return "哈哈哈哈";
   }
}

这里用到了FactoryBean这个接口,本文先不对这个接口做讲解,其实这里就是去接受一个class,然后对这个class生成代理对象,并返回。这样我们在自定义的ImportBeanDefinitionRegistrar中得到的对象就是一个代理对象,而不是接口,然后通过方法里提供的注册器参数,将这个代理对象注册到spring容器中(放到beanDefinitionMap中,在后续的过程中实例化bean,并注册到spring容器中)。

在invoke这个方法里,实际上就是去拿到注解@Select的参数信息,即SQL,这里只是将它简单的打印出来了。既然这里已经拿到了SQL,我们就可以通过jdbc的方式,去查询数据库,最后将得到的结果返回,最后调用mapper的方法得到的就是你查询到的结果集。

下面贴一下mapper,一个简单的查询语句:

public interface UserMapper {

   @Select("select * from user limit 1")
   String queryUser();

}

配置类,扫描mapper包:

@Configuration
@MyMapperScan("importbeandefinitionregistry.mapper")
public class MyConfiguration {
}

最后写一下测试类:

public class DataSourceTest {

   public static void main(String[] args) {
      AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfiguration.class);
		UserMapper userMapper = (UserMapper) context.getBean("userMapper");
		String s = userMapper.queryUser();
		System.out.println(s);
   }

}

执行结果:

image-20210519161203976.png

可以看到在invoke方法中打印的方法和最后输出的结果—>"哈哈哈哈"

整个目录:

image-20210519161425523.png

今天写了@Import注解引入ImportBeanDefinitionRegistrar去手写一个简版的mybatis框架,觉得不错的可以自己动手试一试噢。

写spring源码的文章到现在已经是第4篇了,因为会把spring中一些比较重要的点单独列出来嘛,后续的文章会写bean的创建过程,觉得不错的可以点赞收藏一下噢,欢迎关注后续的spring源码文章。