分享我最近整理的Mybatis知識點,簡單介紹了Mybatis插件功能的使用和實現方式~sql
自定義插件實現org.apache.ibatis.plugin.Interceptor接口apache
經過 @Intercepts註解代表插件加強的類型,方法markdown
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})public class SQLStatsInterceptor implements Interceptor { private final Logger logger = LoggerFactory.getLogger(this.getClass()); @Override public Object intercept(Invocation invocation) throws Throwable { StatementHandler statementHandler = (StatementHandler) invocation.getTarget(); BoundSql boundSql = statementHandler.getBoundSql(); String sql = boundSql.getSql(); System.out.println("mybatis intercept sql:" + sql); return invocation.proceed(); } @Override public Object plugin(Object target) { return Plugin.wrap(target, this); } @Override public void setProperties(Properties properties) { String dialect = properties.getProperty("dialect"); System.out.println("mybatis intercept dialect:"+dialect); }}mybatis
config.xml中配置插件app
只是由於在Configuration中定義了以下幾個方法:ide
/** *plugin在此對ParameterHandler進行加強 */ public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) { ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql); parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler); return parameterHandler; } /** *plugin在此對ResultSetHandler進行加強 */ 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; } /** *plugin在此對StatementHandler進行加強 */ 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) { return newExecutor(transaction, defaultExecutorType); } /** *plugin在此對Executor進行加強 */ 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; }
複製代碼
加強實現的源碼 在解析config.xml文件的時候會將中配置的插件會添加到Configuration中到interceptorChain中源碼分析
private void pluginElement(XNode parent) throws Exception { //若是存在plugings節點 if (parent != null) { //遍歷全部子節點 for (XNode child : parent.getChildren()) { //獲取interceptor屬性 String interceptor = child.getStringAttribute("interceptor"); Properties properties = child.getChildrenAsProperties(); //建立Interceptor實例 Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance(); //設置屬性 interceptorInstance.setProperties(properties); //向攔截器鏈注入攔截器 configuration.addInterceptor(interceptorInstance); } } }
public void addInterceptor(Interceptor interceptor) { interceptorChain.addInterceptor(interceptor); }
複製代碼
SimpleExecutor中執行doUpdate方法是會經過configuration去建立一個StatementHandlerthis
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException { Statement stmt = null; try { log.trace(" SimpleExecutor ms.getConfiguration()"); Configuration configuration = ms.getConfiguration(); log.trace("SimpleExecutor configuration.newStatementHandler"); StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null); stmt = prepareStatement(handler, ms.getStatementLog()); return handler.update(stmt); } finally { closeStatement(stmt);
複製代碼
上文已經列出來了,在newStatementHandler中會對StatementHandler進行加強spa
/** *plugin在此對StatementHandler進行加強 */ 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; }
/** * 攔截器鏈 * @author Clinton Begin */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); }}
@Override public Object plugin(Object target) { return Plugin.wrap(target, this); }
複製代碼
經過上文中發現彷佛全部攔截器都進行了加強處理,事實上不是這樣都,具體使用哪些攔截器對接口進行加強是以下方法中處理的。同時能夠發現 Plugin實現了InvocationHandler接口,也就是調用處理也是在該類中完成的,下面主要就是Plugin中的實現源碼插件
//對目標對象使用過interceptor進行加強 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; }
private static Map, Set> getSignatureMap(Interceptor interceptor) { //獲取類上@Intercepts的註解 Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class); // 若是插件上沒有@Intercepts註解,拋出異常 if (interceptsAnnotation == null) { throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName()); } //@Intercepts註解中全部簽名 Signature[] sigs = interceptsAnnotation.value(); Map, Set> signatureMap = new HashMap, Set>(); //遍歷全部簽名 for (Signature sig : sigs) { //根據類型從簽名映射表中獲取方法集合 Set methods = signatureMap.get(sig.type()); if (methods == null) { //若是方法集合爲null,則建立一個空集合並放入到映射表中 methods = new HashSet(); signatureMap.put(sig.type(), methods); } try { //根據方法名稱,參數類型列表從指定的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; }
複製代碼
此次能夠發現經過JDK的動態代理進行了加強,在進行方法調用對時候會執行一下方法,在該方法中判斷當前方法是否須要加強,若是須要就會調用interceptor 進行加強處理
@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); } }
複製代碼
有任何問題歡迎留言交流~
看到這裏的小夥伴,若是你喜歡這篇文章的話,別忘了轉發、收藏、留言互動!
若是對文章有任何問題,歡迎在留言區和我交流~
最近我新整理了一些Java資料,包含面經分享、模擬試題、和視頻乾貨,若是你須要的話,歡迎留言or私信我!