1. 程式人生 > 程式設計 >Mybatis原始碼分析—Mapper建立和Spring的管理

Mybatis原始碼分析—Mapper建立和Spring的管理

Mybatis原始碼分析—Mapper建立和Spring的管理

我們分析的時候先自己猜測實現方式再對比mybatis的原始碼實現方式

mapper 建立

  • 因為mybatis可以脫離spring自己使用,所以mapper的bean建立是由mybatis完成的
  • 建立方式,根據不同的mapper,方法都是對應與註解或者配置檔案對應名稱的方法,所以我們猜測使用的是spring的動態代理建立方式

我們自己實現mapper建立工廠代理類:

public class MySessionFactoryProxy {
    public static Object getMapper(Class c){
        Class[] classes = new Class[]{c};
        //動態代理獲取mapper
        Object o = Proxy.newProxyInstance(MySessionFactoryProxy.class.getClassLoader(),classes,new InvocationHandler() {
            public Object invoke(Object proxy,Method method,Object[] args) throws Throwable {
                //解析sql
                //執行sql
                Select annotation = method.getAnnotation(Select.class);
                String sql = annotation.value()[0];//一個註解可能有多個sql語句
                System.out.println("sql:"+sql);
                return null;
            }
        });
        return o;
    }
}複製程式碼

那麼由誰來呼叫這個getMapper方法呢,毫無疑問是mybatis,這個時候需要一個工廠bean,用來呼叫該方法,每次呼叫,建立一個factoryBean,傳入一個mapper類,來建立該mapper(這樣就可以解決程式碼寫死的情況)

  • MyMapperFactoryBean
public class MyMapperFactoryBean<T> implements FactoryBean<T> {

    //例項化的時候傳入
    public MyMapperFactoryBean(Class<T> mapperInterface) {
        this.mapperInterface = mapperInterface;
    }

    //使用全域性變數儲存不同的mapper類
    private Class<T> mapperInterface;

    public T getObject() throws Exception {
        System.out.println("get mapper");
        return (T) MySessionFactoryProxy.getMapper(mapperInterface);
    }

    public Class<?> getObjectType() {
        return this.mapperInterface;
    }
}複製程式碼

再看mybatis的實現

  • mapper註冊類獲取mapper
public class MapperRegistry {
    public <T> T getMapper(Class<T> type,SqlSession sqlSession) {
        //主要呼叫
                return mapperProxyFactory.newInstance(sqlSession);
    }複製程式碼

  • 再看mybatis實現的代理工廠類:
public class MapperProxyFactory<T> {
    private final Class<T> mapperInterface;
    //主要方法如下
    public T newInstance(SqlSession sqlSession) {
        MapperProxy<T> mapperProxy = new MapperProxy(sqlSession,this.mapperInterface,this.methodCache);
        return this.newInstance(mapperProxy);
    }
}複製程式碼

  • 呼叫getMapper方法的MapperFactoryBean
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
    private Class<T> mapperInterface;

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

    public T getObject() throws Exception {
        return this.getSqlSession().getMapper(this.mapperInterface);
    }

    public Class<T> getObjectType() {
        return this.mapperInterface;
    }
    public void setMapperInterface(Class<T> mapperInterface) {
        this.mapperInterface = mapperInterface;
    }

    public Class<T> getMapperInterface() {
        return this.mapperInterface;
    }
}
複製程式碼

與我們建立的大體一致

建立完成mapper後,我們需要把mapper交給ioc管理

建立mapper放到Spring IOC

請區別新增註解或者配置,將類交個Spring管理,由Spring為我們建立物件,mapper是由mybatis通過動態代理建立的

自己建立物件交給Spring管理

  • 想法1:由配置類建立
@Configuration
@ComponentScan("top.dzou.mybatis")
public class Appconfig {
    @Bean
    public UserMapper userMapper(){
        return (UserMapper) MySessionFactoryProxy.getMapper(UserMapper.class);
    }
}複製程式碼

每一個都要建立一個@bean註解,不切實際,想想Spring怎麼實現的?

  • 我們想起來bean可以通過spring配置檔案配置,我們想到使用xml格式配置mapper bean

類似下面寫法:

使用我們自己的mapper工廠代理類建立mapper

    <bean id="roleMapper" class="top.dzou.mybatis.mapper_to_spring.my_mybatis.MyMapperFactoryBean">
        <property name="mapperInterface" value="top.dzou.mybatis.mapper_to_spring.RoleMapper"/>
    </bean>
    <bean id="userMapper" class="top.dzou.mybatis.mapper_to_spring.my_mybatis.MyMapperFactoryBean">
        <property name="mapperInterface" value="top.dzou.mybatis.mapper_to_spring.UserMapper"/>
    </bean>
</beans>複製程式碼

這樣的程式碼是不是很熟悉,但是這樣任然要配置很多的bean,我們想到了springboot中使用的mapperscan註解

  • 想法3:自己實現一個註解MyMapperScan,包括一個包,在MapperScan註解上匯入mapper匯入類,把包下面的全部建立並放到spring中

這裡我們需要知道的是Spring建立bean的時候是先載入類並建立BeanDefinition,在通過BeanDefinition建立相應的bean,我們因為mapper

我們自己實現:

  • MyMapperScan

根據basePackages匯入mapper

//匯入ImportBeanDefinitionRegister
@Import(MyBeanDefinitionRegister.class)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyMapperScan {
    String[] basePackages() default {};
}
複製程式碼

  • MyBeanDefinitionRegister

使用Spring註冊bean時使用的ImportBeanDefinitionRegistrar註冊mapper

public class MyBeanDefinitionRegister implements ImportBeanDefinitionRegistrar {
    //class->beanDefinition->map->bean
    public void registerBeanDefinitions(AnnotationMetadata annotationMetadata,BeanDefinitionRegistry beanDefinitionRegistry) {
        //獲取包資訊並把包中類全部註冊動態新增到beanDefinition引數中
        {
            //虛擬碼
            basePackages = 獲取包名下的所用mapper類,儲存到集合basePackages
            baseName = 從mapper類獲取beanName
        }
        BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(MyMapperFactoryBean.class);
        BeanDefinition beanDefinition = beanDefinitionBuilder.getBeanDefinition();
        beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(basePackages);
        beanDefinitionRegistry.registerBeanDefinition(beanName,beanDefinition);
    }
    
}複製程式碼

  • 配置類代替主啟動類(用於測試)
@Configuration
@ComponentScan("top.dzou.mybatis.mapper_to_spring")
@MyMapperScan(basePackages = "top.dzou.mapper_to_spring.mapper")//自定義mapper掃描註解
public class Appconfig {}複製程式碼

我們看一下mybatis怎麼實現的

  • MapperScan

它匯入了一個MapperScannerRegistrar掃描註冊mapper的類

@Import({MapperScannerRegistrar.class})
public @interface MapperScan {複製程式碼

  • MapperScannerRegistrar

我們可以看到它實現了ImportBeanDefinitionRegistrar的bean定義匯入註冊類,實現了具體的registerBeanDefinitions註冊bean定義的方法,把包下的mapper全部新增到一個集合中,然後把這個集合進行註冊到ioc中,和我們的想法基本一致

public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar,ResourceLoaderAware {

    void registerBeanDefinitions(AnnotationAttributes annoAttrs,BeanDefinitionRegistry registry,String beanName) {
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
        List<String> basePackages = new ArrayList();
        basePackages.addAll((Collection)Arrays.stream(annoAttrs.getStringArray("value")).filter(StringUtils::hasText).collect(Collectors.toList()));
        basePackages.addAll((Collection)Arrays.stream(annoAttrs.getStringArray("basePackages")).filter(StringUtils::hasText).collect(Collectors.toList()));
        basePackages.addAll((Collection)Arrays.stream(annoAttrs.getClassArray("basePackageClasses")).map(ClassUtils::getPackageName).collect(Collectors.toList()));

        builder.addPropertyValue("basePackage",StringUtils.collectionToCommaDelimitedString(basePackages));
        registry.registerBeanDefinition(beanName,builder.getBeanDefinition());
    }複製程式碼

它使用一個集合儲存了所有的mapper類,並把他們放在一個beanDefinition中進行註冊

總結

整個過程主要分為

  1. mybatis通過MapperScan把整個mybatis工廠bean註冊到ioc
  2. spring負責mybatis建立的mapper注入ioc

重點:

  • MapperScan中的ImportBeanDefinitionRegistrar的registerBeanDefinitions將工廠beanMapperFactoryBean封裝成beanDefinition並且把包下的mapper新增成建構函式引數傳入並註冊到spring中
  • 註冊完成後再啟動時spring將根據構造器引數和工廠bean呼叫mybatis建立mapper,並且把mapper註冊到spring管理的bean中