经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 数据库/运维 » MyBatis » 查看文章
mybatis plugin源码解析
来源:cnblogs  作者:睦月初雨  时间:2024/4/10 15:38:11  对本文有异议

概述

Plugin,意为插件,是mybatis为开发者提供的,对方法进行自定义编程的手段。其中用到了动态代理、反射方法,通过指定需要增强的对象与方法,进行程序编写。

核心类

主要涉及几个核心类:InterceptorPluginIntercepts

该增强功能的大致执行顺序为:

  1. 项目启动时,查询实现了Interceptor接口并且注册为Bean(或在xml文件中指定)的类,放入SqlSessionFactoryBean的Interceptor[]参数中,再由SqlSessionFactoryBean创建SqlSessionFactory的时候,将其放入Configuration参数中,留作后续调用

    1. // **注意interceptorsProvider,此为SpringBoot的configuration,会自动查询注册为Bean的Interceptor**
    2. public MybatisAutoConfiguration(MybatisProperties properties, ObjectProvider<Interceptor[]> interceptorsProvider,
    3. ObjectProvider<TypeHandler[]> typeHandlersProvider, ObjectProvider<LanguageDriver[]> languageDriversProvider,
    4. ResourceLoader resourceLoader, ObjectProvider<DatabaseIdProvider> databaseIdProvider,
    5. ObjectProvider<List<ConfigurationCustomizer>> configurationCustomizersProvider,
    6. ObjectProvider<List<SqlSessionFactoryBeanCustomizer>> sqlSessionFactoryBeanCustomizers) {
    7. this.properties = properties;
    8. this.interceptors = interceptorsProvider.getIfAvailable();
    9. this.typeHandlers = typeHandlersProvider.getIfAvailable();
    10. this.languageDrivers = languageDriversProvider.getIfAvailable();
    11. this.resourceLoader = resourceLoader;
    12. this.databaseIdProvider = databaseIdProvider.getIfAvailable();
    13. this.configurationCustomizers = configurationCustomizersProvider.getIfAvailable();
    14. this.sqlSessionFactoryBeanCustomizers = sqlSessionFactoryBeanCustomizers.getIfAvailable();
    15. }
    16. @Bean
    17. @ConditionalOnMissingBean
    18. public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
    19. SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
    20. factory.setDataSource(dataSource);
    21. .....
    22. // **注意此处,将interceptors放入factory中**
    23. if (!ObjectUtils.isEmpty(this.interceptors)) {
    24. factory.setPlugins(this.interceptors);
    25. }
    26. .....
    27. }
    28. public class SqlSessionFactoryBean
    29. protected SqlSessionFactory buildSqlSessionFactory() throws Exception {
    30. .......
    31. if (!isEmpty(this.typeAliases)) {
    32. Stream.of(this.typeAliases).forEach(typeAlias -> {
    33. targetConfiguration.getTypeAliasRegistry().registerAlias(typeAlias);
    34. LOGGER.debug(() -> "Registered type alias: '" + typeAlias + "'");
    35. });
    36. }
    37. if (!isEmpty(this.plugins)) {
    38. Stream.of(this.plugins).forEach(plugin -> {
    39. targetConfiguration.addInterceptor(plugin);
    40. LOGGER.debug(() -> "Registered plugin: '" + plugin + "'");
    41. });
    42. }
    43. .......
    44. return this.sqlSessionFactoryBuilder.build(targetConfiguration);
    45. }
    46. }
  2. Configuration类在初始化ParameterHandlerResultSetHandlerStatementHandlerExecutor四个类时,会对它们进行一次封装,封装内容即为用Interceptors注册插件功能,达到增强效果

    1. public class Configuration {
    2. public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
    3. ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
    4. parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
    5. return parameterHandler;
    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. public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    14. StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
    15. statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    16. return statementHandler;
    17. }
    18. public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    19. executorType = executorType == null ? defaultExecutorType : executorType;
    20. executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    21. Executor executor;
    22. if (ExecutorType.BATCH == executorType) {
    23. executor = new BatchExecutor(this, transaction);
    24. } else if (ExecutorType.REUSE == executorType) {
    25. executor = new ReuseExecutor(this, transaction);
    26. } else {
    27. executor = new SimpleExecutor(this, transaction);
    28. }
    29. if (cacheEnabled) {
    30. executor = new CachingExecutor(executor);
    31. }
    32. executor = (Executor) interceptorChain.pluginAll(executor);
    33. return executor;
    34. }
    35. }
  3. Configuration执行的pluginAll方法,内部是通过遍历Interceptor数组的plugin方法实现的。该方法入参和出参都是Object类型,所以可以认为它能为所有类型对象都进行增强封装。Interceptor内部调用了Plugin的wrap方法,对Object对象进行了封装。

    1. public class InterceptorChain {
    2. private final List<Interceptor> interceptors = new ArrayList<>();
    3. public Object pluginAll(Object target) {
    4. for (Interceptor interceptor : interceptors) {
    5. target = interceptor.plugin(target);
    6. }
    7. return target;
    8. }
    9. public void addInterceptor(Interceptor interceptor) {
    10. interceptors.add(interceptor);
    11. }
    12. public List<Interceptor> getInterceptors() {
    13. return Collections.unmodifiableList(interceptors);
    14. }
    15. }
    16. public interface Interceptor {
    17. Object intercept(Invocation invocation) throws Throwable;
    18. default Object plugin(Object target) {
    19. return Plugin.wrap(target, this);
    20. }
    21. default void setProperties(Properties properties) {
    22. // NOP
    23. }
    24. }
  4. Plugin方法实现了InvocationHandler动态代理类,并且wrap方法本身便是创建动态代理类。故Plugin类的职责有两项:

    1. 创建动态代理类,指定需要被代理(增强)的对象。此处为ExecutorHandler等。
    2. 指定被动态代理的对象,需要执行何种程序。重点关注invoke方法。
    1. public class Plugin implements InvocationHandler {
    2. private final Object target;
    3. private final Interceptor interceptor;
    4. private final Map<Class<?>, Set<Method>> signatureMap;
    5. private Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) {
    6. this.target = target;
    7. this.interceptor = interceptor;
    8. this.signatureMap = signatureMap;
    9. }
    10. // **创建动态代理对象**
    11. public static Object wrap(Object target, Interceptor interceptor) {
    12. Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
    13. Class<?> type = target.getClass();
    14. Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
    15. if (interfaces.length > 0) {
    16. return Proxy.newProxyInstance(
    17. type.getClassLoader(),
    18. interfaces,
    19. new Plugin(target, interceptor, signatureMap));
    20. }
    21. return target;
    22. }
    23. // **动态代理增强**
    24. @Override
    25. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    26. try {
    27. Set<Method> methods = signatureMap.get(method.getDeclaringClass());
    28. if (methods != null && methods.contains(method)) {
    29. return interceptor.intercept(new Invocation(target, method, args));
    30. }
    31. return method.invoke(target, args);
    32. } catch (Exception e) {
    33. throw ExceptionUtil.unwrapThrowable(e);
    34. }
    35. }
    36. // 省略getSignatureMap,getAllInterfaces方法
    37. ...
    38. }
  5. wrap方法执行时需要先通过interceptor获取signatureMap。SignatureIntercepts注解中的value值注解,由于此value的返回值是数组,所以Signature会多个存在,最后解析出的结果便为signatureMap。

    Signature注解的作用为标注被动态代理的对象,具体的类型(class),具体的方法,方法具体的参数。只有特定类型和方法才会执行Interceptor方法。

    1. public class Plugin implements InvocationHandler {
    2. private final Object target;
    3. private final Interceptor interceptor;
    4. private final Map<Class<?>, Set<Method>> signatureMap;
    5. private Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) {
    6. this.target = target;
    7. this.interceptor = interceptor;
    8. this.signatureMap = signatureMap;
    9. }
    10. private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
    11. Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
    12. **// 通过此代码可发现,实现Interceptor的类必须添加Intercepts注解**
    13. if (interceptsAnnotation == null) {
    14. throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());
    15. }
    16. Signature[] sigs = interceptsAnnotation.value();
    17. Map<Class<?>, Set<Method>> signatureMap = new HashMap<>();
    18. for (Signature sig : sigs) {
    19. Set<Method> methods = MapUtil.computeIfAbsent(signatureMap, sig.type(), k -> new HashSet<>());
    20. try {
    21. **// 通过Siganture的method与args,反射出Method对象,将其添加到map中
    22. // 作用是在执行动态代理invoke方法时,判断当前方法是否需要被interceptor执行**
    23. Method method = sig.type().getMethod(sig.method(), sig.args());
    24. methods.add(method);
    25. } catch (NoSuchMethodException e) {
    26. throw new PluginException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + e, e);
    27. }
    28. }
    29. return signatureMap;
    30. }
    31. }
  6. 通过阅读源码可知,开发者需要自己实现Interceptor,标记Intercepts注解,指定需要拦截的类、方法名,方法上的参数类型。并将Interceptor注册为Spring Bean。即可在interceptor方法中编写具体拦截代码。

实例

背景:在项目上为每一个需要插入至数据库中的实例对象,初始化id。

代码:

  1. @Component
  2. **// 拦截Executor类的update方法,该update方法会执行insert、update、delete操作**
  3. @Intercepts(@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}))
  4. public class MybatisUpdateInterceptor implements Interceptor {
  5. // 雪花算法id生成器
  6. @Autowired
  7. private IdGenerator idGenerator;
  8. @Override
  9. public Object intercept(Invocation invocation) throws Throwable {
  10. Method method = invocation.getMethod();
  11. MappedStatement ms = (MappedStatement) invocation.getArgs()[0];
  12. **// 判断是否为insert方法**
  13. if (ms.getSqlCommandType() != SqlCommandType.INSERT) {
  14. return invocation.proceed();
  15. }
  16. BaseEntity entity = (BaseEntity) invocation.getArgs()[1];
  17. if (entity.getId() == null) {
  18. entity.setId(idGenerator.generate());
  19. }
  20. return method.invoke(invocation.getTarget(), invocation.getArgs());
  21. }
  22. }

总结

项目开发者可灵活运用plugin,为数据库操作进行增强。日常开发中也可借鉴此流程,通过动态代理方式设计拦截/增强手段。

原文链接:https://www.cnblogs.com/sumray/p/18126151

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号