cool hit counter Mybatis uses dynamic proxies to implement interceptor functionality_Intefrankly

Mybatis uses dynamic proxies to implement interceptor functionality


1. Background

   Interceptor as the name suggests is a weapon to intercept a function, there are "interceptors" in many frameworks. What is the use of this Plugin? Or what's the point of an interceptor? Think about how the interceptor is implemented. Plugin uses a very important feature in Java - dynamic proxy. So the Plugin can be interpreted as a way to "intercept" a method when it is called to do something that I want it to do (both before and after the method). The following methods can be intercepted in Mybatis.

these are the four objects of mybatis fame.

2, source code process tracing, understanding the interceptor interception process and the principle

 1 //ParameterHandler  processsql parameter object of the
 2 public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
 3     ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
 4     //Package Parameter Insert
 5     parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
 6     return parameterHandler;
 7 }
 8 
 9 //ResultSetHandler  processsql of the returned result set
10 public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
11                                             ResultHandler resultHandler, BoundSql boundSql) {
12     ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
13     // Package Return Results Plugin
14     resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
15     return resultSetHandler;
16 }
17 
18 //StatementHandler database process targets
19 public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
20     StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
21     // Packaged database executionsql plug-in (software component)
22     statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
23     return statementHandler;
24 }
25 
26 public Executor newExecutor(Transaction transaction) {
27     // establishMybatis Actuators:Executor
28     return newExecutor(transaction, defaultExecutorType);
29 }
30 
31 public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
32     executorType = executorType == null ? defaultExecutorType : executorType;
33     executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
34     Executor executor;
35     //mybatis Three types of actuators supported:batch、reuse、simple, where the default support issimple
36     if (ExecutorType.BATCH == executorType) {
37         executor = new BatchExecutor(this, transaction);
38     } else if (ExecutorType.REUSE == executorType) {
39         executor = new ReuseExecutor(this, transaction);
40     } else {
       // default isSimpleExecutor
41         executor = new SimpleExecutor(this, transaction);
42     }
      // If L2 cache is turned on   standardexecutor Perform cache packing
43     if (cacheEnabled) {
44         executor = new CachingExecutor(executor);
45     }
46     // Package actuator plug-in
47     executor = (Executor) interceptorChain.pluginAll(executor);
48     return executor;
49 }

   We can see that all four of these objects were created by calling(Executor) interceptorChain.pluginAll(Object target) code, What does that code look like? Exactly what we call an interceptor chain, Pass the four major objects into the interceptor chain for process Then return to the packed The four main targets If we intercept in the interceptor chain process In contrast, interception techniques are implemented;

Here we look at what is in the connector chain.

public class InterceptorChain {

  private final List<Interceptor> interceptors = new ArrayList<Interceptor>();

  // (located) atpluginAll method  Traversing the interceptor set  Convert the incomingtarget  That is, four major objects for incoming  (located) atinterceptor hit the targetPlugin method process
  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<Interceptor> getInterceptors() {
    return Collections.unmodifiableList(interceptors);
  }

}

Here we look at the source code of the interceptor object.

public interface Interceptor {

  Object intercept(Invocation invocation) throws Throwable;

  Object plugin(Object target);

  void setProperties(Properties properties);

}

The source code shows that interceptor is an interface. There are three methods in the interface: intercept plugin and setProperties; the methods are described below.

We write our own plug-ins or intercept the four major objects to add the appropriate functionality in order to implement the interface, and then implement the three methods of the interface.

Example implementation of the interface.

Caution.Remember that you must use annotations to implement the declaration of which class object the interceptor intercepts for reasons to be analyzed later in the source code

@Intercepts({ @Signature(type = StatementHandler.class, method = "prepare", args = { Connection.class }) })
public class ExamplePlugin implements Interceptor {

    /*  This method is used to implement the interception logic
     * @see org.apache.ibatis.plugin.Interceptor#intercept(org.apache.ibatis.plugin.Invocation)
     */
    @Override
   public Object intercept(Invocation invocation) throws Throwable
   {

Let's look at the plugin method, which creates a proxy object via Plugin.wrap. Let's look at the source code: we see that the Plugin class implements theInvocationHandler This is the invocationHandel class of the dynamic proxy we talked about in the last article

The previous section explains why when implementing the interceptor class yourself Why you must use the annotation approach Because in obtaining a digital signatureMap in the methodology of the Methods exist for using reflection to obtain annotation information

// get Interceptor annotation, type(class to intercept),method(method of interceptor class) and args(interceptor used in these classes) in @Signature

Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);

So if no annotation method is added then it will throwthrow new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName())

Then we look at thewrap approach The method is to sign the data via the Then see if the digital signature contains the object and method to be intercepted If it contains follow establish Agent objects Return the proxy object

public class Plugin implements InvocationHandler {
 
   private Object target;  // Target audience
   private Interceptor interceptor;//interceptor object
  private Map<Class<?>, Set<Method>> signatureMap;// Target object method signature
 
  private Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) {
     this.target = target;  // The target here is the object being passed in as the proxy 
    this.interceptor = interceptor;
    this.signatureMap = signatureMap;
  }
 
  public static Object wrap(Object target, Interceptor interceptor) {
     // Get the intercepted class name and method information from the interceptor's annotations
    Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
    Class<?> type = target.getClass();
     //Parsing all interfaces of the intercepted object
    Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
    if (interfaces.length > 0) {
       // Generate a proxy object, and the Plugin object is the InvocationHandler for that proxy object
      return Proxy.newProxyInstance(type.getClassLoader(), interfaces, new Plugin(target, interceptor, signatureMap));
    }
    return target;
  }
 
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      Set<Method> methods = signatureMap.get(method.getDeclaringClass());
      if (methods != null && methods.contains(method)) {
         // call the intercept method of the interceptor to which the proxy class belongs
        return interceptor.intercept(new Invocation(target, method, args));
      }
      return method.invoke(target, args);
    } catch (Exception e) {
      throw ExceptionUtil.unwrapThrowable(e);
    }
  }
 
  private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
     // get Interceptor annotation, type(class to intercept),method(method of interceptor class) and args(interceptor used in these classes) in @Signature
    Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
    // issue #251
    if (interceptsAnnotation == null) {
      throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());      
    }
     //Get the annotation type,method and args to generate a signature array
    Signature[] sigs = interceptsAnnotation.value();
    Map<Class<?>, Set<Method>> signatureMap = new HashMap<Class<?>, Set<Method>>();
    for (Signature sig : sigs) {
      Set<Method> methods = signatureMap.get(sig.type());
      if (methods == null) {
        methods = new HashSet<Method>();
        signatureMap.put(sig.type(), methods);
      }
      try {
         // Get the methods of the class
        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;
  }
   // Get all interfaces
  private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) {
    Set<Class<?>> interfaces = new HashSet<Class<?>>();
    while (type != null) {
       // Get the interface
      for (Class<?> c : type.getInterfaces()) {
        if (signatureMap.containsKey(c)) {
          interfaces.add(c);
        }
      }
       //Get the parent class
      type = type.getSuperclass();
    }
     // return an array of interfaces
    return interfaces.toArray(new Class<?>[interfaces.size()]);
  }
 
}

Go here. Proxy objects and intermediate objectsinvocationHandler The object has been establish accomplish Now let's see. agent-object The proxied object is the statementHandler Subclasses of this interface The ones in this category are

SimpleStatementHandler 

From this you can see that this class implements the statementHandler interface of the Methods.

By looking at the source code here you can also see that the underlying mysql is actually using the underlying jdbc to implement it!

public abstract class BaseStatementHandler implements StatementHandler {
 
  protected final Configuration configuration;
  protected final ObjectFactory objectFactory;
  protected final TypeHandlerRegistry typeHandlerRegistry;
  protected final ResultSetHandler resultSetHandler;
  protected final ParameterHandler parameterHandler;
 
  protected final Executor executor;
  protected final MappedStatement mappedStatement;
  protected final RowBounds rowBounds;
 
  protected BoundSql boundSql;
 
  protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    this.configuration = mappedStatement.getConfiguration();
    this.executor = executor;
    this.mappedStatement = mappedStatement;
    this.rowBounds = rowBounds;
 
    this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
    this.objectFactory = configuration.getObjectFactory();
 
    if (boundSql == null) { // issue #435, get the key before calculating the statement
      generateKeys(parameterObject);
      boundSql = mappedStatement.getBoundSql(parameterObject);
    }
 
    this.boundSql = boundSql;
 
    this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
    this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
  }
  // gainBoundSql
  public BoundSql getBoundSql() {
    return boundSql;
  }
  // gainParameterHandler
  public ParameterHandler getParameterHandler() {
    return parameterHandler;
  }
 
  // Prepare statement
  public Statement prepare(Connection connection) throws SQLException {
    ErrorContext.instance().sql(boundSql.getSql());
    Statement statement = null;
    try {
      // instantiatedStatement
      statement = instantiateStatement(connection);
      // Set timeout
      setStatementTimeout(statement);
      // Set the number of reads
      setFetchSize(statement);
      return statement;
    } catch (SQLException e) {
      closeStatement(statement);
      throw e;
    } catch (Exception e) {
      closeStatement(statement);
      throw new ExecutorException("Error preparing statement.  Cause: " + e, e);
    }
  }
  // How to instantiateStatement, Leave it to the subclasses
  protected abstract Statement instantiateStatement(Connection connection) throws SQLException;
 
  // Set timeout, The actual call toStatement.setQueryTimeout
  protected void setStatementTimeout(Statement stmt) throws SQLException {
    Integer timeout = mappedStatement.getTimeout();
    Integer defaultTimeout = configuration.getDefaultStatementTimeout();
    if (timeout != null) {
      stmt.setQueryTimeout(timeout);
    } else if (defaultTimeout != null) {
      stmt.setQueryTimeout(defaultTimeout);
    }
  }
  // Set the number of reads, It's actually a call toStatement.setFetchSize
  protected void setFetchSize(Statement stmt) throws SQLException {
    Integer fetchSize = mappedStatement.getFetchSize();
    if (fetchSize != null) {
      stmt.setFetchSize(fetchSize);
    }
  }
  // closeStatement
  protected void closeStatement(Statement statement) {
    try {
      if (statement != null) {
        statement.close();
      }
    } catch (SQLException e) {
      //ignore
    }
  }
    
  protected void generateKeys(Object parameter) {
    KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
    ErrorContext.instance().store();
    keyGenerator.processBefore(executor, mappedStatement, null, parameter);
    ErrorContext.instance().recall();
  }
 
}

public class SimpleStatementHandler extends BaseStatementHandler {  
  
  public SimpleStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {  
    super(executor, mappedStatement, parameter, rowBounds, resultHandler, boundSql);  
  }  
  
  @Override  
  public int update(Statement statement) throws SQLException {  
    String sql = boundSql.getSql();  
    Object parameterObject = boundSql.getParameterObject();  
    KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();  
    int rows;  
    if (keyGenerator instanceof Jdbc3KeyGenerator) {  
      statement.execute(sql, Statement.RETURN_GENERATED_KEYS);  
      rows = statement.getUpdateCount();  
      keyGenerator.processAfter(executor, mappedStatement, statement, parameterObject);  
    } else if (keyGenerator instanceof SelectKeyGenerator) {  
      statement.execute(sql);  
      rows = statement.getUpdateCount();  
      keyGenerator.processAfter(executor, mappedStatement, statement, parameterObject);  
    } else {  
      statement.execute(sql);  
      rows = statement.getUpdateCount();  
    }  
    return rows;  
  }  
  
  @Override  
  public void batch(Statement statement) throws SQLException {  
    String sql = boundSql.getSql();  
    statement.addBatch(sql);  
  }  
  
  @Override  
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {  
    String sql = boundSql.getSql();  
    statement.execute(sql);  
    return resultSetHandler.<E>handleResultSets(statement);  
  }  
  
  @Override  
  protected Statement instantiateStatement(Connection connection) throws SQLException {  
    if (mappedStatement.getResultSetType() != null) {  
      return connection.createStatement(mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);  
    } else {  
      return connection.createStatement();  
    }  
  }  
  
  @Override  
  public void parameterize(Statement statement) throws SQLException {  
    // N/A  
  }  
  
}  

Recommended>>
1、New Upstream Hall Winning Tips New upstream hall cheat auxiliary seethrough software
2、Why learn Photoshop first when learning graphic design
3、Ma Huateng Do digital currencies consume a lot of arithmetic counting with an equivalent backing
4、National Big Data Weekly Report 80 Dogfish Head Week 5 Welcome to the arena
5、If you like the ocean youll want to get your eyes on it Full of dry goods waiting for you to get

    已推荐到看一看 和朋友分享想法
    最多200字,当前共 发送

    已发送

    朋友将在看一看看到

    确定
    分享你的想法...
    取消

    分享想法到看一看

    确定
    最多200字,当前共

    发送中

    网络异常,请稍后重试

    微信扫一扫
    关注该公众号