Mybatis 차단기 실행 과정 분석

17929 단어 mybatis
이전 글 은 Mybatis 차단기 의 데이터 암호 화 복호화 에 대해 Mybatis 차단기 의 간단 한 사용 을 소개 했다. 이 글 은 Mybatis 가 어떻게 차단 기 를 발견 하고 차단 기 를 호출 하 는 지 intercept 방법 을 투철 하 게 분석 할 것 이다.
동료 들 은 먼저 문장 내용 에 따라 전체 차단기 의 집행 과정 을 파악 하고 종이 에 각 점 을 그 려 서 의 소스 코드 를 읽 고 이런 점 을 선 으로 연결 시 켜 하나님 의 시각 에 서서 더욱 깊이 이해 했다.
차단기 발견
홈 페이지 설명 에 따 르 면 저 희 는 org.apache.ibatis.plugin.Interceptor 인터페이스 사용자 정의 차단 기 를 실현 함으로써 사용자 정의 차단 기 를 Mybatis configuration 에 추가 하 는 두 가지 방법 이 있 습 니 다.
설정 파일 방식mybatis-config.xml 에 plugin 추가


  
    
  
XMLConfigBuilder Mybatis 전역 프로필 을 분석 하 는 데 pluginElement 방법 이 있 습 니 다.
  private void pluginElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        String interceptor = child.getStringAttribute("interceptor");
        Properties properties = child.getChildrenAsProperties();
        Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
        interceptorInstance.setProperties(properties);
        configuration.addInterceptor(interceptorInstance);
      }
    }
  }

이 방법 에서 알 수 있 듯 이 XML XNode 을 옮 겨 다 니 며 이 node 에 interceptor 속성 이 있다 면 차단 기 를 설정 하고 configuration.addInterceptor(interceptorInstance); 를 통 해 차단 기 인 스 턴 스 를 Mybatis Configuration 에 추가 합 니 다.
주해 방식
글 Mybatis 차단기 의 데이터 암호 화 복호화 에서 제 가 사용자 정의 차단기 류 에 @Component 주 해 를 추가 한 것 을 보 았 습 니 다. 현재 마이크로 서비스 프레임 워 크 에는 Spring Boot 에 Mybatis Starter 의존 을 추가 하 는 형식 이 많이 존재 합 니 다. MybatisAutoConfiguration.java 의 구조 방법 을 보십시오.
public MybatisAutoConfiguration(MybatisProperties properties,
                              ObjectProvider interceptorsProvider,
                              ResourceLoader resourceLoader,
                              ObjectProvider databaseIdProvider,
                              ObjectProvider> configurationCustomizersProvider) {
    this.properties = properties;
    this.interceptors = interceptorsProvider.getIfAvailable();
    this.resourceLoader = resourceLoader;
    this.databaseIdProvider = databaseIdProvider.getIfAvailable();
    this.configurationCustomizers = configurationCustomizersProvider.getIfAvailable();
}

구조 방법 interceptorsProvider.getIfAvailable(); 에서 모든 주입 인 터 셉 터 를 가 져 오고 구축 SqlSessionFactory 할 때 차단 기 를 추가 합 니 다.
if (!ObjectUtils.isEmpty(this.interceptors)) {
  factory.setPlugins(this.interceptors);
}

호출 프로 세 스 분석
Configuration 류 는 Mybatis 의 모든 설정 정 보 를 포함 하고 있 습 니 다. 그 안에 4 가지 중요 한 방법 이 있 습 니 다. 또한 차단기 차단 방법 입 니 다.
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
    ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
    parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
    return parameterHandler;
  }

  public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
      ResultHandler resultHandler, BoundSql boundSql) {
    ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
    resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
    return resultSetHandler;
  }

  public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    return statementHandler;
  }

  public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    if (ExecutorType.BATCH == executorType) {
      executor = new BatchExecutor(this, transaction);
    } else if (ExecutorType.REUSE == executorType) {
      executor = new ReuseExecutor(this, transaction);
    } else {
      executor = new SimpleExecutor(this, transaction);
    }
    if (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

그들의 집행 순 서 는 newExecutor - > StatementHandler - > ParameterHandler - > ResultSetHandler - > StatementHandler 입 니 다. 왜 이 순서 인지 보 세 요. MyBatis 에서 SqlSession Factory 를 사용 하여 SqlSession 을 만 드 는 지 알 고 있 습 니 다.세 션 이 있 으 면 맵 문 구 를 실행 하고 제출 하거나 스크롤 백 연결 을 할 수 있 습 니 다. 마지막 으로 필요 하지 않 을 때 세 션 을 닫 습 니 다.참고 로 DefaultSqlSessionFactory.java 클래스 의 openSessionFromDataSource 방법 에서 Configuration 클래스 의 newExecutor 방법 을 호출 합 니 다.
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      final Environment environment = configuration.getEnvironment();
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      //   Configuration    newExecutor          
      final Executor executor = configuration.newExecutor(tx, execType);
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      closeTransaction(tx); // may have fetched a connection so lets call close()
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
}
SqlSessionTemplate.javaSqlSession 인 터 페 이 스 를 실현 했다. 그 안에 개인 내부 류 SqlSessionInterceptor 가 있 고 InvocationHandler 실현 되 었 다. 이것 은 자바 동적 에이전트 의 실현 방식 으로 재 작성 invoke 방법 에 관심 을 가진다. 여 기 는 방법 이 진정 으로 호출 된 곳 이다. 스 택 을 추적 한 결과 Configuration 류 에 최종 적 으로 호출 된 new Statement Handler 방법 을 발견 했다.
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      SqlSession sqlSession = getSqlSession(
          SqlSessionTemplate.this.sqlSessionFactory,
          SqlSessionTemplate.this.executorType,
          SqlSessionTemplate.this.exceptionTranslator);
      try {
        //        ,       ,     Configuration    newStatementHandler   
        Object result = method.invoke(sqlSession, args);
        ...
      } catch (Throwable t) {
        ...
      } finally {
        if (sqlSession != null) {
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
        }
      }
    }
  }

Configuration 류 의 new StatementHandler 방법 에서 new RoutingStatementHandler(...) 방법 으로 StatementHandler 를 구축 하고 이 방법 에서 statement Type 에 따라 어떤 StatementHandler 를 생 성 하 는 지 판단 합 니 다.
    public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {

    switch (ms.getStatementType()) {
      case STATEMENT:
        delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case PREPARED:
        delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case CALLABLE:
        delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      default:
        throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
    }

  }

세 가지 유형 Statement Handler 가 모두 계승 되 었 습 니 다 BaseStatementHandler.java. 아래 의 관계 도 를 보 세 요.
구체 적 인 StatementHandler 를 실례 화 할 때 부모 클래스 BaseStatementHandler 의 구조 기 를 먼저 호출 하고 부모 클래스 의 구조 기 에서 Configuration 클래스 의 newParameterHandlernewResultSetHandler 방법 을 각각 순서대로 호출 합 니 다.
this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);

그 러 니까 전체 호출 과정 은 다음 과 같다. newExecutor - > StatementHandler - > ParameterHandler - > ResultSetHandler - > StatementHandler이렇게 많은 말 을 했 지만 차단기 가 어떻게 실행 되 는 지 에 대해 서 는 말 하지 못 했 습 니 다. 서 두 르 지 마 세 요. 앞 에 있 는 것들 은 모두 깔 려 있 습 니 다. 세심 한 동료 들 이 Configuratin 류 의 네 가지 방법 중 똑 같은 코드 가 있다 는 것 을 발 견 했 을 수도 있 습 니 다.
interceptorChain.pluginAll(...)

맞습니다. 이름 을 통 해 우 리 는 이것 이 차단기 의 관건 이 라 고 추측 할 수 있 습 니 다. interceptorChain 은 Configuration 류 의 구성원 변수 입 니 다. InterceptorChain.java 류 를 보 세 요.
public class InterceptorChain {

  private final List interceptors = new ArrayList();

  public Object pluginAll(Object target) {
    for (Interceptor interceptor : interceptors) {
      target = interceptor.plugin(target);
    }
    return target;
  }

  public void addInterceptor(Interceptor interceptor) {
    interceptors.add(interceptor);
  }
  
  public List getInterceptors() {
    return Collections.unmodifiableList(interceptors);
  }

}

plugin All 방법 에서 모든 차단 기 를 옮 겨 다 니 는 plugin 방법 은 사용자 정의 차단기 에서 plugin 방법 을 다시 씁 니 다. 여 기 는 유 니 버 설 페이지 차단기 로 호출 차단기 과정 을 설명 합 니 다. 관건 적 인 코드 를 보십시오.
@Intercepts(
    {
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
    }
)
public class PageInterceptor implements Interceptor {

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }
}
Plugin.javaInvocationHandler 인 터 페 이 스 를 실 현 했 고 자바 동적 에이전트 로 정적 방법 wrap 을 호출 했다.
public static Object wrap(Object target, Interceptor interceptor) {
    Map, Set> signatureMap = getSignatureMap(interceptor);
    Class> type = target.getClass();
    Class>[] interfaces = getAllInterfaces(type, signatureMap);
    if (interfaces.length > 0) {
      return Proxy.newProxyInstance(
          type.getClassLoader(),
          interfaces,
          new Plugin(target, interceptor, signatureMap));
    }
    return target;
  }
interfaces.length > 0 도 target 에 프 록 시 대상 을 생 성 합 니 다. 즉, Configuration 류 네 가지 방법 으로 호출 된 executor / parameter Handler / resultSetHandler / statementHandler 에 프 록 시 대상 을 생 성 합 니 다. 여기 서 두 가지 중요 한 방법 getSignatureMap(interceptor)getAllInterfaces(type, signatureMap) 을 따로 분석 해 야 합 니 다.
private static Map, Set> getSignatureMap(Interceptor interceptor) {
    Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
    if (interceptsAnnotation == null) {
      throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());      
    }
    Signature[] sigs = interceptsAnnotation.value();
    Map, Set> signatureMap = new HashMap, Set>();
    for (Signature sig : sigs) {
      Set methods = signatureMap.get(sig.type());
      if (methods == null) {
        methods = new HashSet();
        signatureMap.put(sig.type(), methods);
      }
      try {
        Method method = sig.type().getMethod(sig.method(), sig.args());
        methods.add(method);
      } catch (NoSuchMethodException e) {
        throw new PluginException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + e, e);
      }
    }
    return signatureMap;
  }

이 방법 은 자바 반 사 를 통 해 차단기 류 의 주석 정 보 를 읽 고 Type 을 key 로 하고 Method 집합 을 Value 로 하 는 HashMap 을 되 돌려 줍 니 다. 위의 페이지 차단 기 를 예 로 들 면 key 는 getSignatureMap(interceptor) 이 고 Value 는 두 개의 org.apache.ibatis.executor.Executor query 방법 으로 다시 봅 니 다 .
private static Class>[] getAllInterfaces(Class> type, Map, Set> signatureMap) {
    Set> interfaces = new HashSet>();
    while (type != null) {
      for (Class> c : type.getInterfaces()) {
        if (signatureMap.containsKey(c)) {
          interfaces.add(c);
        }
      }
      type = type.getSuperclass();
    }
    return interfaces.toArray(new Class>[interfaces.size()]);
}

이 방법 은 대상 인 스 턴 스 target 과 부모 클래스 의 인터페이스 배열 에 따라 되 돌아 갑 니 다. getAllInterfaces(type, signatureMap) 방법 은 인터페이스 배열 의 길이 가 0 보다 크 면 target 에 프 록 시 대상 을 생 성 합 니 다. 마지막 으로 DefaultSqlSession 에서 구체 적 인 실행 을 수행 할 때 selectList 방법 과 같이 이때 executor 는 방금 생 성 된 프 록 시 대상 입 니 다.
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);

executor 호출 방법 은 plugin 재 작성 invoke 방법 을 실행 합 니 다.
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      Set methods = signatureMap.get(method.getDeclaringClass());
      if (methods != null && methods.contains(method)) {
        return interceptor.intercept(new Invocation(target, method, args));
      }
      return method.invoke(target, args);
    } catch (Exception e) {
      throw ExceptionUtil.unwrapThrowable(e);
    }
  }

최종 적 으로 사용자 정의 차단기 의 intercept 방법 을 실행 합 니 다. 차단 기 는 이렇게 실 행 됩 니 다. Mybatis 프레임 워 크 에서 자바 동적 대 리 를 대량으로 사 용 했 습 니 다. 예 를 들 어 Mapper 인터페이스 에서 방법 만 정의 하고 구체 적 인 실현 류 가 없 는 것 을 발 견 했 습 니 다. 이 모든 것 은 자바 동적 대 리 를 사용 해 야 하기 때문에 동적 대 리 를 이해 합 니 다.전체 집행 과정 을 더욱 잘 이해 할 수 있다.
차단기 설명
본 논문 에서 페이지 차단기 의 일부 핵심 코드 를 캡 처 했 는데 이 차단기 의 설명 내용 은 다음 과 같다.
@Intercepts(
    {
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
    }
)

Mybatis 차단기 의 데이터 암호 화 복호화 에서 매개 변수 차단 기 를 요청 하고 결과 집합 차단 기 를 되 돌려 주 는 내용 은 다음 과 같 습 니 다.
@Intercepts({
        @Signature(type = ParameterHandler.class, method = "setParameters", args = PreparedStatement.class),
})
@Intercepts({
        @Signature(type = ResultSetHandler.class, method = "handleResultSets", args={Statement.class})
})

모든 차단기 차단 방법 서명 (Signature) 이 다 릅 니 다. 어떻게 써 야 하나 요?사실 간단 합 니 다. 이것 은 모두 인터페이스 Executor / Parameter Handler / ResultSetHandler 의 방법 입 니 다. 해당 하 는 인터페이스 방법 에 따라 여기에 설정 하면 됩 니 다. 차단 기 를 반사 적 으로 해석 할 때 해당 하 는 방법 으로 서명 할 수 있 는 지 판단 합 니 다. 회의 보고 Plugin.wrap 이상 예 를 들 어 Executor 인 터 페 이 스 를 찾 지 못 하면 두 개의 과부하 query 방법 이 있 습 니 다.주해 중의 내용 을 다시 보 니 갑자기 밝 아 지지 않 습 니까?
 List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException;

 List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;

글 의 시작 부분 에 있 는 깔개 와 결합 하여 Configuration 류 의 네 가지 방법 을 차단 합 니 다. 맞습니다. 바로 Executor / Parameter Handler / ResultSetHandler / StatementHandler 입 니 다. Mybatis 차단기 의 데이터 암호 화 복호화 시작 NoSuchMethodException 내용 을 계속 보고 Executor / parameter Handler / ResultSetHandler / StatementHandler 의 역할 을 충분히 이해 합 니 다.우 리 는 차단 기 를 이용 해 우리 만 의 수작 을 부 릴 수 있다.
문제 달걀
우 리 는 호출 차단 기 를 보 았 을 때 interceptor Chain 을 통 해 호출 되 었 습 니 다. 직역 하면 입 니 다. 사실은 이것 은 디자인 모델 의 입 니 다.
  • 책임 체인 모델 을 아 세 요?
  • 어떤 프레임 워 크 나 장면 에서 책임 체인 모델 을 응 용 했 는 지 생각 할 수 있 습 니까?
  • 현실 업무 에서 책임 체인 모델 을 응용 하면 우리 코드 를 더욱 유연 하고 건장 하 게 할 수 있 습 니까?
  • 만약 에 우리 가 여러 개의 같은 유형의 차단기, 예 를 들 어 여러 개의 Executor 형식 차단기 등 을 정의 한다 면 여러 개의 차단기 의 순 서 는 어떻게 제어 해 야 합 니까?

  • 효율 향상 도구
    공중 번호 에 관심 을 가지 고 답장 을 통 해 우리 의 효율 적 인 작업 을 도 울 수 있 는 도 구 를 더 많이 얻 을 수 있 습 니 다.
    Free Mybatis Plugin
    Mybatis 를 사용 하고 손 으로 SQL 을 써 야 할 때 Mapper 인터페이스 에서 방법 을 정의 하 는 동시에 XML 에서 같은 이름 의 statementId 의 SQL 을 정의 해 야 합 니 다. 이 Intellij IDEA 플러그 인 은 빠 른 포 지 셔 닝 방법 과 XML 을 도와 주 고 자바 에서 SQL 로 전환 합 니 다.
    SQL 에서 자바 까지
    유인원 은 왜 원본 코드 를 봐 야 합 니까?
    대중 번호 에 관심 을 가지 고 coding 에 관 한 이 야 기 를 나 누 며 실력 을 향상 시 키 는 것 을 환영 합 니 다.

    좋은 웹페이지 즐겨찾기