1. 程式人生 > >MyBatis和Spring整合,流程淺析 - 20181208

MyBatis和Spring整合,流程淺析 - 20181208

  • 【面試題】我們在開發mybaits時候,有幾種寫sql語句的方式?
    一種是註解、一種是xml檔案、還有一種是@Provider
    註解形式:簡單但是不是很靈活,對於動態條件查詢是無法實現的,這時,我們可以使用xml的方式。
    xml形式:有xml檔案,且約束嚴格。
    @Provider:結合上兩種優點,注意:方法不能過載

  • 【面試題】我們都知道mybatis預設是有一級快取的,但mybatis和spring整合後,一級快取失效。

  • 如果想了解mybatis的原始碼,有兩種方向,一個old(原生的),一個new(結合spring的),兩者是完全不同的工作機制。這次我們從spring方面去接觸原始碼,那麼 spring和mybatis整合的時候,有哪幾個關聯點?主要有兩點。
    @MapperScan
    @Bean ——>SqlSessionFactoryBean

  1. 先看@MapperScan
    通過原始碼得知MapperScan主要利用spring的Import技術和spring的擴充套件點ImportBeanDefinitionRegistear
    @Mapperscan裡@Import(MapperScannerRegistrar.class),而MapperScannerRegistrar實現了關鍵類ImportBeanDefinitionRegistrar(是spring提供的一個擴充套件類,作用:[spring會先處理import,解析這個類,呼叫registerBeanDefinitions方法,產生springBean的註冊器,就是bean的前身])

  2. @Bean

所以spring整合mybatis執行流程:

首先解析@Mapperscan,發現Import匯入的類,執行類中的方法,這個類擴充套件了spring的掃描器,doScan方法,只掃描了所有介面。
之後將介面變成了物件:
1、首先拿到接口裡的beanDefinitions物件(用來描述bean的,記錄了bean的資訊)
​ ps.spring建立物件跟介面無關,和bd有關
2、之後,介面無法直接產生物件,所以spring是產生MapperFactoryBean(FactoryBean物件),裡邊的getObject方法可以返回代理物件(動態代理),實現了介面
3、getObject方法中是:
​ getSqlSession().getMapper(this.mapperInterface) //生成mapper物件
​ ps.底層MapperProxy實現了jdk中動態代理的InvocationHandler,所以mapper.方法實際上就是呼叫了動態代理中的invoke方法(就是介面產生代理物件,然後呼叫的是invoke方法)
4、呼叫這個代理物件時,裡邊的invoke方法執行mapperMethod.execute方法,可以分別執行對應的CRUD操作。
5、那麼為什麼getMapper(this.mapperInterface)需要傳介面呢?因為返回代理物件,所以是介面。
​ ​ 那麼所有介面都變成了MapperFactoryBean物件後,又是如何區分的呢? definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName());
​ 在生成MapperFactoryBean物件的時候,立即在一參構造中設定了一個值,這樣就有名稱了
​ ps.掃描遍歷過程中,defintion可以拿到每次傳進來的資訊,和beanDefinitions相關
6、之後迴圈拿到所有構造方法,篩出有參,根據有參來建立對應名稱的MapperFactoryBean物件

那麼我們實戰一下看看,手寫

  1. 依賴
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.0.9.RELEASE</version>
    </dependency>
  1. 包結構
    圖1
  2. 類:UserMapper、App、MyScan
public interface UserMapper {
    public void query();
}
@Configuration
@ComponentScan("com.play")
@MyScan     //相當於Mybatis的@MapperScan
public class App {
}
/**
 * 模擬mybatis的@MapperScan
 * Created by Turing on 2018/12/8 16:12
 */
@Retention(RetentionPolicy.RUNTIME)
@Import(MyScanRegistar.class)   //類似MapperScanRegistar
public @interface MyScan {
}
  1. MyScanRegistar
public class MyScanRegistar implements ImportBeanDefinitionRegistrar {
    /**
     * 方法作用:掃描包
     * @param importingClassMetadata
     * @param registry
     */
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        //System.err.println("scan scan scan ...");
        //假設掃描出來!
        //1、把掃描出來的bd的beanClass改成FactoryBean
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(UserMapper.class);//暫不模擬掃描了,正常應該是List,直接拿到類
        //ps.spring不是根據你的類來建立物件的,而是通過build來建立物件的

        //2、得到beanDefinition
        GenericBeanDefinition genericBeanDefinition = (GenericBeanDefinition) builder.getBeanDefinition();

        //4、裡邊的getObject方法可以返回代理物件(動態代理),實現了介面
        genericBeanDefinition.setBeanClass(MyFactoryBean.class);

        //3、利用registy,讓spring幫助例項化,但是介面無法例項化【Failed to instantiate】,這時候需要第4步了
        registry.registerBeanDefinition("userMapper",genericBeanDefinition);

    }
}
  1. MyFactoryBean
/**
 * 當你get這個bean時,返回getObject中return的物件
 *
 * Created by Turing on 2018/12/8 16:26
 */
public class MyFactoryBean implements FactoryBean {
    /**
     * 當然,實際情況更復雜
     */
    @Override
    public Object getObject() throws Exception {
        return Proxy.newProxyInstance(this.getClass().getClassLoader(),new Class[]{
                UserMapper.class
        },new MyInvocationHandler());
    }

    @Override
    public Class<?> getObjectType() {
        return null;
    }
}
  1. MyInvocationHandler
public class MyInvocationHandler implements InvocationHandler {

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("---------MyInvocationHandler----------");
        return null;
    }
}
  1. 測試
public class MyTest {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext annotationConfigApplicationContext
                =new AnnotationConfigApplicationContext(App.class);

        annotationConfigApplicationContext.start();

        //代理物件
        UserMapper userMapper = (UserMapper) annotationConfigApplicationContext.getBean("userMapper");

        userMapper.query(); //會執行InvocationHandler中的程式碼
        //結果為:---------MyInvocationHandler----------
    }
}
  • spring AOP的編碼原理,亦是如此!!!Spring另一個擴充套件點是BeanFactoryPostProcessor