The use and design principle of MyBatis interceptor __mybatis

Source: Internet
Author: User
Tags prepare throwable

using interceptors



In web development we often encounter paging operations, a project may have more than one use to paging, then if the Java background using MyBatis as a persistence layer, we can use the MyBatis interceptor function to complete the entire project in multiple paging operations, reduce the code redundancy.



Interceptor Code


// Intercept prepare method with parameter type Connection in StatementHandler
@Intercepts ({@ Signature (type = StatementHandler.class, method = "prepare", args = {Connection.class})})
public class PageInterceptor implements Interceptor {

    private String test; // Get the attributes configured in xml

    @Override
    public Object intercept (Invocation invocation) throws Throwable {
        StatementHandler statementHandler = (StatementHandler) invocation.getTarget ();
        // Elegantly access the properties of the object through MetaObject, here is the property of accessing statementHandler
        MetaObject metaObject = MetaObject.forObject (statementHandler, SystemMetaObject.DEFAULT_OBJECT_FACTORY, SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY, new DefaultReflectorFactory ());
        // First intercept to RoutingStatementHandler, which has a delegate variable of type StatementHandler, whose implementation class is BaseStatementHandler, and then to the member variable mappedStatement of BaseStatementHandler
        MappedStatement mappedStatement = (MappedStatement) metaObject.getValue ("delegate.mappedStatement");
        // The ID of the SQL statement in the configuration file
        String id = mappedStatement.getId ();
        if (id.matches (". + ByPage $")) {// ID to be intercepted (regular matching)
            BoundSql boundSql = statementHandler.getBoundSql ();
            // Original SQL statement
            String sql = boundSql.getSql ();
            // Query the total number of SQL statements
            String countSql = "select count (*) from (" + sql + ") a";
            // Execute the query of the total number of SQL statements
            Connection connection = (Connection) invocation.getArgs () [0];
            PreparedStatement countStatement = connection.prepareStatement (countSql);
            //// Get the parameter information, which is the condition information of the where statement. Note that the parameters in the SQL obtained above are still replaced with?
            ParameterHandler parameterHandler = (ParameterHandler) metaObject.getValue ("delegate.parameterHandler");
            parameterHandler.setParameters (countStatement);
            ResultSet rs = countStatement.executeQuery ();

            Map <?,?> Parameter = (Map <?,?>) BoundSql.getParameterObject ();
            Page page = (Page) parameter.get ("page");
            if (rs.next ()) {
                page.setTotalNumber (rs.getInt (1));
            }
            // SQL statement with paging query after transformation
            String pageSql = sql + "limit" + page.getDbIndex () + "," + page.getDbNumber ();
            metaObject.setValue ("delegate.boundSql.sql", pageSql);
        }
        return invocation.proceed ();
    }

    @Override
    public Object plugin (Object target) {
        System.out.println (this.test);
        return Plugin.wrap (target, this);
    }

    @Override
    public void setProperties (Properties properties) {
        this.test = properties.getProperty ("test");
        // TODO Auto-generated method stub
    }

}


MetaObject is an object that MyBatis provides for easy, elegant access to object properties, which simplifies the code, does not need to try/catch various reflect exceptions, and supports JavaBean, Collection, The operation of the map three types of objects. Getting the MetaObject object requires the use of a static method Metaobject.forobject, and you need to specify Objectfactory, Objectwrapperfactory, Reflectorfactory ( 3.3.0 not needed before).



Configuring interceptors




<plugins>
    <plugin interceptor= "Com.chm.inteceptor.PageInterceptor" >
        <property name= "test" value= "abc"/>
    </plugin>
</plugins>


This configures the value of test to ABC, and the test in the interceptor above is assigned ABC.



Query sql




<select id= "Querymessagelistbypage" parametertype= "Java.util.Map" resultmap= "Messageresult" >
 Select < Include refid= "columns"/> from message
  <where>
    <if test= "Message.command!= null and!&quot; &quot;. Equals (Message.command.trim ()) "> and
    command=#{message.command}
   </if>
   <if test=" Message.description!= null and!&quot;&quot;. Equals (Message.description.trim ()) "> and
    description like '% ' #{message.description} '% '
   </if>
  </where>
ORDER by ID </select>

<sql id= "Columns" >id,command,description, Content</sql>


Call Example




/**
 * Query message list based on query criteria *
/public list<message> querymessagelistbypage (String command,string Description,page Page) {
    map<string,object> parameter = new hashmap<string, object> ();
    Organization Message object Messages
    = new ();
    Message.setcommand (command);
    Message.setdescription (description);
    Parameter.put ("message", message);
    Parameter.put ("page", page);
    Messagedao Messagedao = new Messagedao ();
    Paging the query and returns the result return
    messagedao.querymessagelistbypage (parameter);


Complete code: http://download.csdn.net/download/zxc123e/9930719 



Interceptor Design principle


The implementation of the interceptor is based on the design pattern of the proxy, simply to create a proxy class for the target class, execute the target class method in the proxy class, and execute the Interceptor code before the method. First of all, regardless of MyBatis's source code is how to design, first assume that you want to do a interceptor should do. Let's use the JDK's dynamic proxy to design a simple interceptor.



Target interface to be blocked:




Public interface Target {public  
    void execute ();  


One implementation class for the target interface:




public class Targetimpl implements Target {public  
    void execute () {  
        System.out.println ("execute");  
    }  
  


Implement interceptors using JDK's dynamic proxies:




public class Targetproxy implements Invocationhandler {  
    private Object target;  
    Private Targetproxy (Object target) {  
        this.target = target;  
    }  

    Generate a proxy object for a target object public  
    static object bind (object target) {return  
        proxy.newproxyinstance (target.getclass) . getClassLoader (),   
                Target.getclass (). Getinterfaces (),  
                       new Targetproxy (target);  

    Precede the target object method with its own blocking logic public  
    object Invoke (Object proxy, methods method,  
                             object[] args) throws Throwable {  
        System.out.println ("Begin");  
        Return Method.invoke (target, args);  
    }  
  


Client invocation:




The public class Client {public  
static void Main (string[] args) {  

    //is not intercepted before  
    target target = new Targetimpl ();target.execute (); Execute  

    //intercept after  
    target = (target) targetproxy.bind (target);  
    Target.execute ();   
    Begin  
    //execute  
}


There are several very obvious deficiencies in the design above. First, the interception logic is written to death in the proxy object:




public object invoke (object proxy, Method method,  
                   object[] args) throws Throwable {  
   //intercept logic is written to die in the proxy object, Causes the client to be unable to flexibly set its intercept logic  
   System.out.println ("Begin");  
   Return Method.invoke (target, args);  
  


We can encapsulate the interception logic into a class that the client passes together as a parameter when calling the Targetproxy bind () method:
Defines an interface interceptor for intercepting logical encapsulation, which is the true interceptor interface.




Public interface Interceptor {public  
    void intercept ();  
}  


Then our proxy class can be changed to:




public class Targetproxy implements Invocationhandler {  

private Object target;  
Private Interceptor Interceptor;  

Private Targetproxy (Object Target, Interceptor Interceptor) {  
    this.target = target;  
    This.interceptor = Interceptor;  
}  

The interception logic is encapsulated into the interceptor, and the client generates the proxy class of the target class together, so that the client can set different interception logic. Public  
static object bind (Object target, Interceptor Interceptor) {return  
    proxy.newproxyinstance ( Target.getclass (). getClassLoader (),   
                       Target.getclass (). Getinterfaces (),  
                       new Targetproxy (Target, Interceptor));  
}  

public object invoke (object proxy, Method method,   
                      object[] args) throws Throwable {  
    //EXECUTE client-defined blocking logic  
    Interceptor.intercept ();  
    Return Method.invoke (target, args);  
  


Client Invoke Code:




/clients can define various blocking logic  
interceptor Interceptor = new Interceptor () {public  
    void intercept () {  
        System.out.println ( "Go"!!!);  
    }  
;  
target = (target) targetproxy.bind (target, Interceptor);  
Target.execute ();  


Of course, many times our interceptor needs to determine whether the current method needs to be intercepted, or to obtain the method parameters that are currently being intercepted. We can pass the intercepted target method object, the parameter information to the Interceptor.
The Interceptor interface is changed to:




Public interface Interceptor {public  
    void intercept (method method, object[] args);  
  


The current method and parameter can be passed to the intercept when the proxy class executes, that is, the Targetproxy invoke method is changed to:




public object Invoke (Object Proxy, Method method, object[] args) throws Throwable {  
    interceptor.intercept (method, AR GS);
    Return Method.invoke (target, args); 
  


One of the Java design principles is called the Dimitri rule, presumably meaning that the less a class knows about other classes the better. In fact, reduce the coupling between class and class strength. This is to think from the perspective of the class members. What is called the less the better, what is the least. At least I just don't know. So is it possible for us to understand that the fewer classes a class should know, the better.



For the above example, the Interceptor interface needs to be understood by using the Intercept method to pass through the methods class. So since interceptor needs to use method, it's better to put the execution of method into the interceptor and not let the Targetproxy class know about it. The target object is required for the execution of method, so you also need to give the target object to interceptor. Encapsulate Method,target and args into an object invocation and pass invocation to interceptor. Invocation.java




public class Invocation {
    private Object target;
    Private method method;
    Private object[] args;

    Public invocation (Object target, method method, object[] args) {
        this.target = target;
        This.method = method;
        This.args = args;
    }

    The operation of its member variables as far as possible within themselves, do not need to interceptor to obtain their own member variables to operate them,
    ///unless such operations require interceptor other support. However here does not need. Public
    Object Proceed () throws InvocationTargetException, illegalaccessexception {return
        Method.invoke ( Target, args);

    Public Object Gettarget () {return
        target;
    }

    public void Settarget (Object target) {
        this.target = target;
    }

    Public method GetMethod () {return method
        ;
    }

    public void Setmethod (method method) {
        This.method = method;
    }

    Public object[] Getargs () {return
        args;
    }

    public void Setargs (object[] args) {
        This.args = args;
    }
}
Targetproxy.java


public class Targetproxy implements Invocationhandler {

    private Object target;
    Private Interceptor Interceptor;

    Private Targetproxy (Object Target, Interceptor Interceptor) {
        this.target = target;
        This.interceptor = Interceptor;
    }

    The interception logic is encapsulated into the interceptor, and the client generates the proxy class of the target class together, so that the client can set different interception logic. Public
    static object bind (Object target, Interceptor Interceptor) {return
        proxy.newproxyinstance ( Target.getclass (). getClassLoader (),
                Target.getclass (). Getinterfaces (),
                new Targetproxy (Target, Interceptor));
    }

    @Override Public
    object Invoke (Object proxy, Method method, object[] args) throws Throwable {return
        Intercepto R.intercept (new invocation (target, method, args));
    }

Test class


public class Client {public
    static void Main (string[] args) {
        Interceptor Interceptor = invocation-> {
            Sy Stem.out.println ("Go to go!!!");
            return Invocation.proceed ();
        };

        Target target = (target) targetproxy.bind (new Targetimpl (), interceptor);
        Target.execute ();

    }


According to the Dimitri Law, the client does not need to understand the Targetproxy class at all. The binding logic is placed inside the interceptor, and the client only needs to deal with the interceptor.



The Interceptor interface becomes:




Public interface Inteceptor {
    Object intercept (invocation invocation) throws Throwable;
    Object Register (object target);


Interceptor Implementation Class




public class Inteceptorimpl implements Inteceptor {
    @Override public
    Object intercept (invocation invocation) Throws Throwable {
        System.out.println ("Go to go!!!");
        return Invocation.proceed ();
    }

    @Override Public
    Object Register (object target) {return
        Targetproxy.bind (target, this);
    }
}


Test class




public class Client {public
    static void Main (string[] args) {
        Inteceptor interceptor = new Inteceptorimpl ();target Target = (target) interceptor.register (new Targetimpl ());
        Target.execute ();
    }


Well, with a series of tweaks, the design is fine, but there's a big problem with the interceptor, which is that the interceptor intercepts all the methods of the target object, but this is often unnecessary, and we often need interceptors to intercept the target object's specified methods. We use the annotation on the interceptor to solve.



Suppose the target object interface has multiple methods:




Public interface Target {public
    void execute1 ();
    public void Execute2 ();
}


First, simply define an annotation:




@Retention (retentionpolicy.runtime)
@Target (elementtype.type) public
@interface methodname {
    public String value ();
}


Add the annotation to the Interceptor's implementation class:




@MethodName ("execute1") Public
class Inteceptorimpl implements Inteceptor {
    ...
}


Judge Interceptor's annotations in Targetproxy to see if they are being intercepted:




@Override Public
    Object Invoke (Object Proxy, Method method, object[] args) throws Throwable {
        methodname Methodna me = This.interceptor.getClass (). Getannotation (Methodname.class);
        if (methodname = null)
            throw new NullPointerException ("xxxx");

        Block
        String name = Methodname.value () If the method name on the annotation is the same as the method name;
        if (Name.equals (Method.getname ())) return
            interceptor.intercept (new invocation (target, Method    , args));

        Return Method.invoke (This.target, args);
    }


OK, the above series of procedures are actually mybatis interceptor code structure, the above targetproxy is actually MyBatis plugin class. Interceptor and invocation are almost the same. Only the annotations supported by MyBatis's interceptor are more complex.



MyBatis of the plugin class, see inside the two methods, now is not so unfamiliar.




public class Plugin implements InvocationHandler {

  private Object target;
  private Interceptor interceptor;
  private Map<Class<?>, Set<Method>> signatureMap;
  ...

  public static Object wrap(Object target, Interceptor interceptor) {
    Map<Class<?>, Set<Method>> 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;
  }

  @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)) {
        return interceptor.intercept(new Invocation(target, method, args));
      }
      return method.invoke(target, args);
    } catch (Exception e) {
      throw ExceptionUtil.unwrapThrowable(e);
    }
  }

  ...
}


MyBatis ultimately by configuring the custom interceptor into an XML file:




<!--the Interceptor-->  
 <plugins> <plugin interceptor= "that handles the map return result  
     " Com.chm.inteceptor.PageInterceptor ">
 </plugins>  


Reference article: Http://zhangbo-peipei-163-com.iteye.com/blog/2033832?utm_source=tuicool&utm_medium=referral


Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.