1. 程式人生 > >Mybatis之攔截器原理(jdk動態代理優化版本)

Mybatis之攔截器原理(jdk動態代理優化版本)

在介紹Mybatis攔截器程式碼之前,我們先研究下jdk自帶的動態代理及優化

其實動態代理也是一種設計模式...優於靜態代理,同時動態代理我知道的有兩種,一種是面向介面的jdk的代理,第二種是基於第三方的非面向介面的cglib.

我們現在說的是jdk的動態代理,因為mybatis攔截器也是基於這個實現的。

簡單介紹就是建立一個目標類的代理類。在執行目標類的方法前先執行代理類的方法,目標類的方法是在代理類中執行的,所以目標類方法執行前後,可以再代理類中進行其他操作。

簡單版:

1 public interface Target {
2     void work();
3 }
1 public class TargetImpl implements Target {
2     @Override
3     public void work() {
4         System.out.println("我就只能做這麼多了");
5     }
6 }

下面建立一個代理類

 1 public class TargetProxy implements InvocationHandler {
 2 
 3     private Object target;
 4 
 5     public TargetProxy(Object target){
6 this.target = target; 7 } 8 9 /** 10 * 缺點 代理需要做的事情不是很靈活。直接在這裡面寫死了。 11 * @param proxy 12 * @param method 13 * @param args 14 * @return 15 * @throws Throwable 16 */ 17 @Override 18 public Object invoke(Object proxy, Method method, Object[] args) throws
Throwable { 19 System.out.println("代理在前面做事情了"); 20 try { 21 return method.invoke(target, args); 22 } catch (InvocationTargetException e) { 23 throw e.getCause(); 24 } 25 } 26 27 public static Object getProxyObject(Object target){ 28 return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),new TargetProxy(target)); 29 } 30 }
 1 public class Client {
 2 
 3     public static void main(String[] args) {
 4         Target target = new TargetImpl();
 5         target.work();
 6         System.out.println("-----------------------------");
 7         Target target1 = (Target) TargetProxy.getProxyObject(new TargetImpl());
 8         target1.work();
 9     }
10 }

結果:

我就只能做這麼多了-----------------------------代理在前面做事情了我就只能做這麼多了

———————————————————————————————————————————————————————————————————————————————

這樣是最常見的代理了,但是有個缺點,代理類要做的事情在代理類寫死了,要換就得多寫一個代理類。那下面我們就把代理的事項單獨拿出來。

增加攔截器介面和實現類

1 public interface Interceptor {
2     void doOtherThings();
3 }
1 public class InterceptorImpl implements  Interceptor {
2     @Override
3     public void doOtherThings() {
4         System.out.println("還可以靈活地做其他事情");
5     }
6 }

代理類變一下:

 1 public class TargetProxy implements InvocationHandler {
 2 
 3     private Object target;
 4 
 5     private Interceptor interceptor;
 6 
 7     public TargetProxy(Object target, Interceptor interceptor) {
 8         this.interceptor = interceptor;
 9         this.target = target;
10     }
11 
12     @Override
13     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
14         interceptor.doOtherThings();
15         return method.invoke(target, args);
16     }
17 
18     /**
19      * 獲取代理物件的時候順便把攔截邏輯物件也傳過來
20      *
21      * @param interceptor
22      * @return
23      * @paramarget
24      */
25     public static Object getProxyObject(Object target, Interceptor interceptor) {
26         return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new TargetProxy(target, interceptor));
27     }
28 
29 
30 }
 1 public class Client {
 2 
 3     public static void main(String[] args) {
 4         Target target = new TargetImpl();
 5         target.work();
 6         System.out.println("-----------------------------");
 7         Interceptor interceptor = new InterceptorImpl();
 8         Target target1 = (Target) TargetProxy.getProxyObject(new TargetImpl(),interceptor);
 9         target1.work();
10         System.out.println("-----------------------------");
11         Interceptor interceptor1 = new Interceptor() {
12             @Override
13             public void doOtherThings() {
14                 System.out.println("換個攔截方式?");
15             }
16         };
17         Target target2 = (Target) TargetProxy.getProxyObject(new TargetImpl(),interceptor1);
18         target2.work();
19     }
20 }

結果:

我就只能做這麼多了-----------------------------還可以靈活地做其他事情我就只能做這麼多了-----------------------------換個攔截方式?我就只能做這麼多了

———————————————————————————————————————————————————————————————————————————————

這樣寫是不是感覺挺好了。但是你是不是要攔截所有方法的呢?正常場景是不是隻需要攔截method中某個某幾個方法呢?那這樣就把攔截器加個引數,method好了。

1 public interface Interceptor {
2     void doOtherThings(Method method, Object[] args);
3 }
1 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  
2     interceptor.doOtherThings(method, args);
3     return method.invoke(target, args); 
4 }  

注意:java設計模式中有一個規則就迪米特法則,也叫最少知識法則,意思應該就是一個類知道的越少越好,對一個物件知道的越少越好。

那這樣看反正攔截器都需要method還不如讓代理類也不知道method好了,method執行需要代理類的target那就把target也傳過去。

傳了這麼多引數,我們可以不可以把這些引數封裝成一個物件傳過去,暫時就叫Invocation

然後method.invoke執行就需要這三個引數,那麼這三個操作就放在Invocation裡面好了,要不然你還得讓攔截器去獲取這些屬性

 1 public class Invocation {
 2 
 3     private Object target;
 4 
 5     private Method method;
 6 
 7     private Object[] args;
 8 
 9     public Invocation(Object target, Method method, Object[] args) {
10         this.target = target;
11         this.method = method;
12         this.args = args;
13     }
14 
15     public Object proceed() throws InvocationTargetException, IllegalAccessException {
16         return method.invoke(target,args);
17     }
18 
19     public Object getTarget() {
20         return target;
21     }
22 
23     public void setTarget(Object target) {
24         this.target = target;
25     }
26 
27     public Method getMethod() {
28         return method;
29     }
30 
31     public void setMethod(Method method) {
32         this.method = method;
33     }
34 
35     public Object[] getArgs() {
36         return args;
37     }
38 
39     public void setArgs(Object[] args) {
40         this.args = args;
41     }
42 }

下面看攔截器怎麼實現

1 public interface Interceptor {
2 
3     public Object intercept(Invocation invocation) throws InvocationTargetException, IllegalAccessException;
4 }
 1 public class InterceptorImpl implements Interceptor {
 2     @Override
 3     public Object intercept(Invocation invocation) throws InvocationTargetException, IllegalAccessException {
 4         if(invocation.getMethod().getName().equals("work")){
 5             System.out.println("真的假的");
 6             return invocation.proceed();
 7         }else{
 8             return null;
 9         }
10 
11     }
 1 public class TargetProxyTwo implements InvocationHandler {
 2 
 3     private Object target;
 4 
 5     private Interceptor interceptor;
 6 
 7     public TargetProxyTwo(Object target, Interceptor interceptor) {
 8         this.target = target;
 9         this.interceptor = interceptor;
10     }
11 
12     @Override
13     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
14         return interceptor.intercept(new Invocation(target,method,args));
15     }
16 
17     public static Object getProxyObj(Object target,Interceptor interceptor){
18         return Proxy.newProxyInstance(target.getClass().getClassLoader(),
19                 target.getClass().getInterfaces(),
20                 new TargetProxyTwo(target, interceptor));
21     }
22 }
1 public class Client {
2 
3     public static void main(String[] args) {
4         Target target = (Target) TargetProxyTwo.getProxyObj(new TargetImpl(),new InterceptorImpl());
5         target.work();
6     }
7 }

結果:

真的假的我就只能做這麼多了

———————————————————————————————————————————————————————————————————————————————

迪米特法則來看,客戶端現在需要知道 攔截器,和代理類。 那麼能不能把代理類的註冊放到攔截器裡面呢?可以的。來看下

1 public interface Interceptor {
2 
3     public Object register(Object target);
4 
5     public Object intercept(Invocation invocation) throws InvocationTargetException, IllegalAccessException;
6 }
 1 public class InterceptorImpl implements Interceptor {
 2 
 3 
 4     @Override
 5     public Object register(Object target) {
 6         return TargetProxyTwo.getProxyObj(target,this);
 7     }
 8 
 9     @Override
10     public Object intercept(Invocation invocation) throws InvocationTargetException, IllegalAccessException {
11         if(invocation.getMethod().getName().equals("work")){
12             System.out.println("真的假的");
13             return invocation.proceed();
14         }else{
15             return invocation.procedd();
16         }
17 
18     }
19 }
1 public class Client {
2 
3     public static void main(String[] args) {
4 
5         Target target = (Target) new InterceptorImpl().register(new TargetImpl());
6         target.work();
7     }
8 }

這樣是不是很完美?

這樣寫是有問題的

1         if(invocation.getMethod().getName().equals("work")){
2             System.out.println("真的假的");
3             return invocation.proceed();
4         }else{
5             return invocation.proceed();
6         }

把判斷方法的邏輯方法攔截方法裡面,那麼假如十個方法,十個攔截邏輯呢?你是不是要寫大一堆if else?這樣是美觀的。怎麼解決呢?註解啊!!!

在攔截器上新增要攔截的方法註解不就好了嘛。

來看程式碼:

1 @Retention(RetentionPolicy.RUNTIME)
2 @java.lang.annotation.Target(ElementType.TYPE)
3 public @interface MethodName {
4     public String value();
5 }
1 @MethodName("work")
2 public class InterceptorImpl implements Interceptor
 1 public class TargetProxy implements InvocationHandler {
 2 
 3     private Object target;
 4 
 5     private Interceptor interceptor;
 6 
 7     public TargetProxy(Object target, Interceptor interceptor) {
 8         this.interceptor = interceptor;
 9         this.target = target;
10     }
11 
12     @Override
13     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
14         MethodName methodName = this.interceptor.getClass().getAnnotation(MethodName.class);
15         if (methodName == null) {
16             throw new NullPointerException("攔截器註解方法名字為空");
17         }
18         String name = methodName.value();
19         if (name.equals(method.getName())) {
20             return interceptor.intercept(new Invocation(target, method, args));
21         }
22         return method.invoke(target, args);
23     }
24 
25     /**
26      * 獲取代理物件的時候順便把攔截邏輯物件也傳過來
27      *
28      * @param interceptor
29      * @return
30      * @paramarget
31      */
32     public static Object getProxyObject(Object target, Interceptor interceptor) {
33         return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new TargetProxy(target, interceptor));
34     }
35 }

怎麼樣,這樣是不是就很完美了。

先預告下: 上面的類對應Mybatis的類:

Invocation,Interceptor完全一樣。TargetProxy對應Plugin類。regist方法對應wrap方法

好的下面來看Mybatis的攔截器了

MyBatis 允許你在已對映語句執行過程中的某一點進行攔截呼叫。預設情況下,MyBatis 允許使用外掛來攔截的方法呼叫包括:

  1. Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
  2. ParameterHandler (getParameterObject, setParameters)
  3. ResultSetHandler (handleResultSets, handleOutputParameters)
  4. StatementHandler (prepare, parameterize, batch, update, query)

Mybatis的攔截器的使用方式是通過xml配置的

<plugins>
    <plugin interceptor="org.format.mybatis.cache.interceptor.ExamplePlugin"></plugin>
</plugins>

通過這樣的配置,就有了預設的攔截器:

那麼這四種攔截器分別在什麼時候新增的?

XMLConfigBuilder

 1   private void pluginElement(XNode parent) throws Exception {
 2     if (parent != null) {
 3       for (XNode child : parent.getChildren()) {
 4         String interceptor = child.getStringAttribute("interceptor");
 5         Properties properties = child.getChildrenAsProperties();
 6         Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
 7         interceptorInstance.setProperties(properties);
 8         configuration.addInterceptor(interceptorInstance);
 9       }
10     }
11   }

Mybatis中有一個攔截器鏈,典型的責任鏈模式

那麼這四種攔截器分別在什麼時候開始執行攔截呢?

先介紹下責任鏈獲取恰當的攔截器的方法,Configuration類中

 1   public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
 2     ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
 3     parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
 4     return parameterHandler;
 5   }
 6 
 7   public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
 8       ResultHandler resultHandler, BoundSql boundSql) {
 9     ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
10     resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
11     return resultSetHandler;
12   }
13 
14   public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
15     StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
16     statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
17     return statementHandler;
18   }
19 
20   public Executor newExecutor(Transaction transaction) {
21     return newExecutor(transaction, defaultExecutorType);
22   }
23 
24   public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
25     executorType = executorType == null ? defaultExecutorType : executorType;
26     executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
27     Executor executor;
28     if (ExecutorType.BATCH == executorType) {
29       executor = new BatchExecutor(this, transaction);
30     } else if (ExecutorType.REUSE == executorType) {
31       executor = new ReuseExecutor(this, transaction);
32     } else {
33       executor = new SimpleExecutor(this, transaction);
34     }
35     if (cacheEnabled) {
36       executor = new CachingExecutor(executor);
37     }
38     executor = (Executor) interceptorChain.pluginAll(executor);
39     return executor;
40   }
 1 public class InterceptorChain {
 2 
 3   private final List<Interceptor> interceptors = new ArrayList<Interceptor>();
 4 
 5   public Object pluginAll(Object target) {       //遍歷所有的攔截器看那個攔截器適用。plugin方法就是每個攔截器的適配方法
 6     for (Interceptor interceptor : interceptors) {
 7       target = interceptor.plugin(target);
 8     }
 9     return target;
10   }
11 
12   public void addInterceptor(Interceptor interceptor) {
13     interceptors.add(interceptor);
14   }
15   
16   public List<Interceptor> getInterceptors() {
17     return Collections.unmodifiableList(interceptors);
18   }
19 
20 }

下面開始看那塊開始攔截的

1.Executor介面的攔截器:

  獲取SqlSession的時候,就獲取了代理攔截器:

  類:DefaultSqlSessionFactory

 1   private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
 2     Transaction tx = null;
 3     try {
 4       final Environment environment = configuration.getEnvironment();
 5       final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
 6       tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
 7       final Executor executor = configuration.newExecutor(tx, execType);
 8       return new DefaultSqlSession(configuration, executor, autoCommit);
 9     } catch (Exception e) {
10       closeTransaction(tx); // may have fetched a connection so lets call close()
11       throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
12     } finally {
13       ErrorContext.instance().reset();
14     }
15   }

2 StatementHandler

處理SQL邏輯等方法

具體類:SimpleExecutor

 1   public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
 2     Statement stmt = null;
 3     try {
 4       Configuration configuration = ms.getConfiguration();
 5       StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
 6       stmt = prepareStatement(handler, ms.getStatementLog());
 7       return handler.<E>query(stmt, resultHandler);
 8     } finally {
 9       closeStatement(stmt);
10     }
11   }

3.parameterHandler;resultSetHandler

一個引數的一個結果集的。

都在statementHandler確定後在類:BaseStatementHandler構造器中初始化的

 1   protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
 2     this.configuration = mappedStatement.getConfiguration();
 3     this.executor = executor;
 4     this.mappedStatement = mappedStatement;
 5     this.rowBounds = rowBounds;
 6 
 7     this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
 8     this.objectFactory = configuration.getObjectFactory();
 9 
10     if (boundSql == null) { // issue #435, get the key before calculating the statement
11       generateKeys(parameterObject);
12       boundSql = mappedStatement.getBoundSql(parameterObject);
13     }
14 
15     this.boundSql = boundSql;
16 
17     this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
18     this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
19   }

這下總體應該瞭解了,這四類介面都是在獲取實現類之前,先通過代理獲取物件。如果存在攔截器,則執行攔截器的方法,否則直接返回物件本身。

Mybatis好的地方在於他把四類攔截器用一個攔截器鏈管理了起來。 用責任鏈模式解決了要單獨判斷哪類攔截邏輯用什麼攔截器的判斷邏輯。