概述
Plugin,意為外掛,是mybatis為開發者提供的,對方法進行自定義程式設計的手段。其中用到了動態代理、反射方法,透過指定需要增強的物件與方法,進行程式編寫。
核心類
主要涉及幾個核心類:Interceptor
、Plugin
、Intercepts
該增強功能的大致執行順序為:
-
專案啟動時,查詢實現了
Interceptor
介面並且註冊為Bean(或在xml檔案中指定)的類,放入SqlSessionFactoryBean
的Interceptor[]引數中,再由SqlSessionFactoryBean
建立SqlSessionFactory
的時候,將其放入Configuration
引數中,留作後續呼叫// **注意interceptorsProvider,此為SpringBoot的configuration,會自動查詢註冊為Bean的Interceptor** public MybatisAutoConfiguration(MybatisProperties properties, ObjectProvider<Interceptor[]> interceptorsProvider, ObjectProvider<TypeHandler[]> typeHandlersProvider, ObjectProvider<LanguageDriver[]> languageDriversProvider, ResourceLoader resourceLoader, ObjectProvider<DatabaseIdProvider> databaseIdProvider, ObjectProvider<List<ConfigurationCustomizer>> configurationCustomizersProvider, ObjectProvider<List<SqlSessionFactoryBeanCustomizer>> sqlSessionFactoryBeanCustomizers) { this.properties = properties; this.interceptors = interceptorsProvider.getIfAvailable(); this.typeHandlers = typeHandlersProvider.getIfAvailable(); this.languageDrivers = languageDriversProvider.getIfAvailable(); this.resourceLoader = resourceLoader; this.databaseIdProvider = databaseIdProvider.getIfAvailable(); this.configurationCustomizers = configurationCustomizersProvider.getIfAvailable(); this.sqlSessionFactoryBeanCustomizers = sqlSessionFactoryBeanCustomizers.getIfAvailable(); } @Bean @ConditionalOnMissingBean public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception { SqlSessionFactoryBean factory = new SqlSessionFactoryBean(); factory.setDataSource(dataSource); ..... // **注意此處,將interceptors放入factory中** if (!ObjectUtils.isEmpty(this.interceptors)) { factory.setPlugins(this.interceptors); } ..... } public class SqlSessionFactoryBean protected SqlSessionFactory buildSqlSessionFactory() throws Exception { ....... if (!isEmpty(this.typeAliases)) { Stream.of(this.typeAliases).forEach(typeAlias -> { targetConfiguration.getTypeAliasRegistry().registerAlias(typeAlias); LOGGER.debug(() -> "Registered type alias: '" + typeAlias + "'"); }); } if (!isEmpty(this.plugins)) { Stream.of(this.plugins).forEach(plugin -> { targetConfiguration.addInterceptor(plugin); LOGGER.debug(() -> "Registered plugin: '" + plugin + "'"); }); } ....... return this.sqlSessionFactoryBuilder.build(targetConfiguration); } }
-
Configuration類在初始化
ParameterHandler
、ResultSetHandler
、StatementHandler
、Executor
四個類時,會對它們進行一次封裝,封裝內容即為用Interceptors註冊外掛功能,達到增強效果public class Configuration { public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) { ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql); parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler); return parameterHandler; } 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; } 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, 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; } }
-
Configuration
執行的pluginAll方法,內部是透過遍歷Interceptor陣列的plugin方法實現的。該方法入參和出參都是Object型別,所以可以認為它能為所有型別物件都進行增強封裝。Interceptor
內部呼叫了Plugin
的wrap方法,對Object物件進行了封裝。public class InterceptorChain { private final List<Interceptor> 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<Interceptor> getInterceptors() { return Collections.unmodifiableList(interceptors); } } public interface Interceptor { Object intercept(Invocation invocation) throws Throwable; default Object plugin(Object target) { return Plugin.wrap(target, this); } default void setProperties(Properties properties) { // NOP } }
-
Plugin
方法實現了InvocationHandler
動態代理類,並且wrap方法本身便是建立動態代理類。故Plugin類的職責有兩項:- 建立動態代理類,指定需要被代理(增強)的物件。此處為
Executor
、Handler
等。 - 指定被動態代理的物件,需要執行何種程式。重點關注
invoke
方法。
public class Plugin implements InvocationHandler { private final Object target; private final Interceptor interceptor; private final Map<Class<?>, Set<Method>> signatureMap; private Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) { this.target = target; this.interceptor = interceptor; this.signatureMap = 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); } } // 省略getSignatureMap,getAllInterfaces方法 ... }
- 建立動態代理類,指定需要被代理(增強)的物件。此處為
-
wrap方法執行時需要先透過interceptor獲取signatureMap。
Signature
是Intercepts
註解中的value值註解,由於此value的返回值是陣列,所以Signature會多個存在,最後解析出的結果便為signatureMap。Signature
註解的作用為標註被動態代理的物件,具體的型別(class),具體的方法,方法具體的引數。只有特定型別和方法才會執行Interceptor方法。public class Plugin implements InvocationHandler { private final Object target; private final Interceptor interceptor; private final Map<Class<?>, Set<Method>> signatureMap; private Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) { this.target = target; this.interceptor = interceptor; this.signatureMap = signatureMap; } private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) { Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class); **// 透過此程式碼可發現,實現Interceptor的類必須新增Intercepts註解** if (interceptsAnnotation == null) { throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName()); } Signature[] sigs = interceptsAnnotation.value(); Map<Class<?>, Set<Method>> signatureMap = new HashMap<>(); for (Signature sig : sigs) { Set<Method> methods = MapUtil.computeIfAbsent(signatureMap, sig.type(), k -> new HashSet<>()); try { **// 透過Siganture的method與args,反射出Method物件,將其新增到map中 // 作用是在執行動態代理invoke方法時,判斷當前方法是否需要被interceptor執行** 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; } }
-
透過閱讀原始碼可知,開發者需要自己實現
Interceptor
,標記Intercepts
註解,指定需要攔截的類、方法名,方法上的引數型別。並將Interceptor註冊為Spring Bean。即可在interceptor方法中編寫具體攔截程式碼。
例項
背景:在專案上為每一個需要插入至資料庫中的例項物件,初始化id。
程式碼:
@Component
**// 攔截Executor類的update方法,該update方法會執行insert、update、delete操作**
@Intercepts(@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}))
public class MybatisUpdateInterceptor implements Interceptor {
// 雪花演算法id生成器
@Autowired
private IdGenerator idGenerator;
@Override
public Object intercept(Invocation invocation) throws Throwable {
Method method = invocation.getMethod();
MappedStatement ms = (MappedStatement) invocation.getArgs()[0];
**// 判斷是否為insert方法**
if (ms.getSqlCommandType() != SqlCommandType.INSERT) {
return invocation.proceed();
}
BaseEntity entity = (BaseEntity) invocation.getArgs()[1];
if (entity.getId() == null) {
entity.setId(idGenerator.generate());
}
return method.invoke(invocation.getTarget(), invocation.getArgs());
}
}
總結
專案開發者可靈活運用plugin,為資料庫操作進行增強。日常開發中也可借鑑此流程,透過動態代理方式設計攔截/增強手段。