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!" ". Equals (Message.command.trim ()) "> and
command=#{message.command}
</if>
<if test=" Message.description!= null and!"". 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