MyBatis(四)MyBatis插件原理
MyBatis插件原理
MyBatis對開發者非常友好,它通過提供插件機制,讓我們可以根據自己的需要去增強MyBatis的功能。其底層是使用了代理模式+責任鏈模式
MyBatis官方https://mybatis.org/mybatis-3/zh/configuration.html#plugins可以看到MyBatis允許使用插件來攔截的方法調用
- Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed) //攔截執行器的方法
- ParameterHandler (getParameterObject, setParameters) //攔截參數的處理
- ResultSetHandler (handleResultSets, handleOutputParameters) //攔截結果集的處理
- StatementHandler (prepare, parameterize, batch, update, query) //攔截SQL語法構建的處理
我們可以先看一下官網的例子,要想使用插件,有三步:
1.實現 Interceptor 接口
2.指定想要攔截的方法簽名
3.在mybatis-config.xml的<plugins>標簽里進行配置
前面分析源碼的時候我們就看到了使用插件來代理Executor,最后調用了Interceptor接口的plugin方法來創建代理對象
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {executorType = executorType == null ? this.defaultExecutorType : executorType;executorType = executorType == null ? ExecutorType.SIMPLE : executorType;Object 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 (this.cacheEnabled) {executor = new CachingExecutor((Executor)executor);}Executor executor = (Executor)this.interceptorChain.pluginAll(executor);return executor;}public Object pluginAll(Object target) {Interceptor interceptor;for(Iterator var2 = this.interceptors.iterator(); var2.hasNext(); target = interceptor.plugin(target)) {interceptor = (Interceptor)var2.next();}return target;}至于其他幾個對象則是在具體的Executor執行doUpdate/doQuery方法的時候在創建對象之后創建了代理對象
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {Statement stmt = null;int var6;try {Configuration configuration = ms.getConfiguration();StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, (ResultHandler)null, (BoundSql)null);stmt = this.prepareStatement(handler, ms.getStatementLog());var6 = handler.update(stmt);} finally {this.closeStatement(stmt);}return var6;}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 = (StatementHandler)this.interceptorChain.pluginAll(statementHandler);return statementHandler;}//configuration.newStatementHandler最后調用了BaseStatementHandler的構造方法,這里創建了ParameterHandler和ResultSetHandler,并在創建后使用責任鏈模式創建代理對象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 = this.configuration.getTypeHandlerRegistry();this.objectFactory = this.configuration.getObjectFactory();if (boundSql == null) {this.generateKeys(parameterObject);boundSql = mappedStatement.getBoundSql(parameterObject);}this.boundSql = boundSql;this.parameterHandler = this.configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);this.resultSetHandler = this.configuration.newResultSetHandler(executor, mappedStatement, rowBounds, this.parameterHandler, resultHandler, boundSql);}public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);parameterHandler = (ParameterHandler)this.interceptorChain.pluginAll(parameterHandler);return parameterHandler;}分析下MyBatis關于插件的幾個核心類
Plugin
MyBatis里提供了一個Plugin 類,可以通過Plugin.wrap(target,inteceptor)方法來直接返回一個代理對象,mybatis的分頁插件PageInterceptor也是直接調用了Plugin的wrap方法通過jdk動態代理創建代理對象,其用來增強的InvocationHandler就是Plugin
所以代理對象在執行方法的時候會執行下面的invoke代碼,實際會調用Interceptor的intercept方法,因為這里沒有目標對象方法的調用,所以我們在實現Interceptor的intercept方法時,需要自己調用目標方法
Invocation
invocation里保存了目標對象和代理的方法,可以通過調用proceed()方法來調用目標對象的方法
public class Invocation {private final Object target;private final Method method;private final Object[] args;public Invocation(Object target, Method method, Object[] args) {this.target = target;this.method = method;this.args = args;}...public Object proceed() throws InvocationTargetException, IllegalAccessException {return this.method.invoke(this.target, this.args);} }總結一下:MyBatis插件相關的類
| 對象? | 作用? |
| Interceptor | 自定義插件需要實現的接口 |
| InterceptChain | 配置文件中配置的插件會解析后會保存在 Configuration 的 InterceptChain 中? |
| Plugin | 用來創建代理對象,包裝四大對象? |
| Invocation | 對被代理對象進行包裝,可以調用 proceed()調用到被攔截的方法 |
?
?
?
?
?
?
public interface Interceptor {//覆蓋被攔截對象的原有方法(需要在實現里調用目標對象的方法) Object intercept(Invocation var1) throws Throwable;//創建代理對象Object plugin(Object var1);//用于設置在mybatis-config.xml里配置的property屬性void setProperties(Properties var1); }PageHelper原理
用法
PageHelper.startPage(pageNumber, pageSize); //pageNumber, pageSize,第幾頁,每頁幾條List<?> list= service.getAll();PageInfo page = new PageInfo(list, 10);分頁插件的核心類PageInterceptor,從實現類的注解可以看到,攔截的方法是Executor的兩個重載的query方法
@Intercepts({@Signature(type = Executor.class,method = "query",args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class} ), @Signature(type = Executor.class,method = "query",args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class} )}) public Object intercept(Invocation invocation) throws Throwable {try {//獲取目標對象的參數,根據攔截的方法簽名獲取各個參數Object[] args = invocation.getArgs();//獲得MappedStatement參數,里面封裝了跟本次statement id相關的各種參數MappedStatement ms = (MappedStatement)args[0];Object parameter = args[1];RowBounds rowBounds = (RowBounds)args[2];ResultHandler resultHandler = (ResultHandler)args[3];//通過invocation的getTarget方法獲取被代理的Executor對象,后面直接調用而不是調用//invocation.proceed()來執行目標對象的方法Executor executor = (Executor)invocation.getTarget();CacheKey cacheKey;BoundSql boundSql;if (args.length == 4) {boundSql = ms.getBoundSql(parameter);cacheKey = executor.createCacheKey(ms, parameter, rowBounds, boundSql);} else {cacheKey = (CacheKey)args[4];boundSql = (BoundSql)args[5];}List resultList;if (this.dialect.skip(ms, parameter, rowBounds)) {//執行目標對象的方法resultList = executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);} else {Map<String, Object> additionalParameters = (Map)this.additionalParametersField.get(boundSql);if (this.dialect.beforeCount(ms, parameter, rowBounds)) {CacheKey countKey = executor.createCacheKey(ms, parameter, RowBounds.DEFAULT, boundSql);countKey.update("_Count");MappedStatement countMs = (MappedStatement)this.msCountMap.get(countKey);if (countMs == null) {countMs = MSUtils.newCountMappedStatement(ms);this.msCountMap.put(countKey, countMs);}String countSql = this.dialect.getCountSql(ms, boundSql, parameter, rowBounds, countKey);BoundSql countBoundSql = new BoundSql(ms.getConfiguration(), countSql, boundSql.getParameterMappings(), parameter);Iterator var16 = additionalParameters.keySet().iterator();while(var16.hasNext()) {String key = (String)var16.next();countBoundSql.setAdditionalParameter(key, additionalParameters.get(key));}Object countResultList = executor.query(countMs, parameter, RowBounds.DEFAULT, resultHandler, countKey, countBoundSql);Long count = (Long)((List)countResultList).get(0);if (!this.dialect.afterCount(count, parameter, rowBounds)) {Object var18 = this.dialect.afterPage(new ArrayList(), parameter, rowBounds);return var18;}}if (!this.dialect.beforePage(ms, parameter, rowBounds)) {resultList = executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, cacheKey, boundSql);} else {parameter = this.dialect.processParameterObject(ms, parameter, boundSql, cacheKey);//調用dialect的getPageSql方法獲取分頁SQLString pageSql = this.dialect.getPageSql(ms, boundSql, parameter, rowBounds, cacheKey);BoundSql pageBoundSql = new BoundSql(ms.getConfiguration(), pageSql, boundSql.getParameterMappings(), parameter);Iterator var25 = additionalParameters.keySet().iterator();while(true) {if (!var25.hasNext()) {resultList = executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, cacheKey, pageBoundSql);break;}String key = (String)var25.next();pageBoundSql.setAdditionalParameter(key, additionalParameters.get(key));}}}Object var22 = this.dialect.afterPage(resultList, parameter, rowBounds);return var22;} finally {this.dialect.afterAll();}}public Object plugin(Object target) {return Plugin.wrap(target, this);} //AbstractHelperDialect public String getPageSql(MappedStatement ms, BoundSql boundSql, Object parameterObject, RowBounds rowBounds, CacheKey pageKey) {String sql = boundSql.getSql();Page page = this.getLocalPage();return this.getPageSql(sql, page, pageKey);}最后會調用AbstractHelperDialect的getPageSql抽象方法,根據不同的數據庫選擇不同的實現類
public class MySqlDialect extends AbstractHelperDialect {public MySqlDialect() {}public String getPageSql(String sql, Page page, CacheKey pageKey) {StringBuilder sqlBuilder = new StringBuilder(sql.length() + 14);sqlBuilder.append(sql);if (page.getStartRow() == 0) {sqlBuilder.append(" LIMIT ");sqlBuilder.append(page.getPageSize());} else {//從Page對象獲取對應的分頁信息 起始下標和獲取個數拼接SQLsqlBuilder.append(" LIMIT ");sqlBuilder.append(page.getStartRow());sqlBuilder.append(",");sqlBuilder.append(page.getPageSize());pageKey.update(page.getStartRow());}pageKey.update(page.getPageSize());return sqlBuilder.toString();} }這里的Page是通過getLocalPage方法獲取的,實際是從PageMethod里的ThreadLocal里獲取的,所以我們在使用的時候直接設置page信息就可以了,因為ThreadLocal幫我們保證了線程安全
protected static final ThreadLocal<Page> LOCAL_PAGE = new ThreadLocal();public PageMethod() {}//PageHelper.startPage最終調用的方法protected static void setLocalPage(Page page) {LOCAL_PAGE.set(page);}public static <T> Page<T> getLocalPage() {return (Page)LOCAL_PAGE.get();}應用場景
1.分表
在接口上添加注解,然后使用Interceptor對 query,update 方法進行攔截 ,根據注解上配置的參數進行分表操作
2.數據加解密
可以攔截獲得入參和返回值,在update的時候加密;查詢的時候解密
3. 菜單權限控制
對 query 方法進行攔截,在方法上添加不同的權限注解,這樣就可以注解的權限信息,在 SQL 上加上權限過濾條件
?
總結
以上是生活随笔為你收集整理的MyBatis(四)MyBatis插件原理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: MyBatis(二)MyBatis基本流
- 下一篇: MyBatis(五)MyBatis整合S