首页 > 技术文章 > MapperScan实现原理分析

tianboblog 2020-04-02 10:38 原文

mybatis.spring中一个关键注解MapperScan,通过它可以扫描指定包下面的所有mapper(mybatis自己实现了一个扫描器

public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {}

最终调用父类的doScan()方法,把bean定义交给了spring初始化管理),然后我们就可以在service中注入使用了:

UserMapper:

@Mapper
public interface UserMapper {
   @Select("SELECT * FROM user WHERE id = #{userId}")
   User getUser(@Param("userId") String userId);


   @Select("SELECT * FROM user")
   List<User> getAll();
}

UserService

@Service
public class UserService {
   @Autowired
   UserMapper userMapper;


   public User getUser(String userId) {
      return userMapper.getUser(userId);
   }


   public List<User> getAll() {
      return userMapper.getAll();
   }

有两个关键的点:

  1. 这些mapper是怎么交给spring容器管理的呢?
  2. mapper都是接口类型都是怎么实例化的呢?

很好奇,其实答案就在MapperScan注解当中,通过查看其源码:

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

很关键的一个:@Import(MapperScannerRegistrar.class),其中:

public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {}

看到这就会明白了吧,实现了ImportBeanDefinitionRegistrar接口的方法,这样就可以通过BeanDefinitionRegistry registry注册了。

registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry)

 

另外一个问题就是它是接口类型的到底是怎么实例化调用的呢?毫无疑问肯定是采用了代理模式,最终通过代理对象实现调用的。但这又会引出另外一个疑问:既然是实现了代理,那到底是怎么动态注册到容器之中的呢?定义BeanDefinition,是需要指定BeanClass的,既然是代理对象,怎么动态拿到它的BeanClass的呢?

其实它是采用了FactoryBean,如下可以简单模拟MapperScan的实现:

自定义一个MyScan注解:

@Import(MyImportBeanDefinitionRegistrar.class)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyScan {


}

MyImportBeanDefinitionRegistrar实现:

public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {


   @Override
   public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
      //得到bd


      //根据包名扫描所有的class,这里就直接写死了
      BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(UserMapper.class);
      GenericBeanDefinition beanDefinition = (GenericBeanDefinition) builder.getBeanDefinition();
      //System.out.println(beanDefinition.getBeanClassName());
      //通过构造函数注入,spring内部调用下面指定的 MyFactoryBean的构造
      beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(beanDefinition.getBeanClassName());
      //指定bd类型,因为它是一个代理对象,我们只能通过FactoryBean去动态获取其类型
      beanDefinition.setBeanClass(MyFactoryBean.class);
      registry.registerBeanDefinition("userMapper", beanDefinition);
   }

MyFactoryBean实现:

public class MyFactoryBean implements FactoryBean, InvocationHandler {


   Class clazz;
   //通过构造函数注入
   public MyFactoryBean(Class clazz) {
      this.clazz = clazz;
   }


   @Override
   public Object getObject() throws Exception {
      Class[] clazzs = new Class[]{clazz};
      Object instance = Proxy.newProxyInstance(this.getClass().getClassLoader(), clazzs, this);
      return instance;
   }


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


   @Override
   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      System.out.println("proxy");
      //可以动态拿到每个方法注解的sql语句
      Method method1 = proxy.getClass().getInterfaces()[0].getMethod(method.getName(), String.class);
      Select annotations = method1.getDeclaredAnnotation(Select.class);
      System.out.println(annotations.value()[0]);
      return null;
   

推荐阅读