如何在MyBatis拦截器中获取请求

时间:2018-07-13 07:06:26

标签: spring-boot orm mybatis ibatis performance-measuring

我想测量将由MyBatis(Spring Boot项目)运行的sql执行时间,并将其与其他请求参数绑定,因此我可以获得有关特定请求的性能问题的完整信息。对于这种情况,我已通过以下方式使用MyBatis拦截器:

@Intercepts({
    @Signature(
            type = Executor.class,
            method = "query",
            args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
    @Signature(
            type = Executor.class,
            method = "query",
            args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
public class QueryMetricsMybatisPlugin implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Stopwatch stopwatch = Stopwatch.createStarted();
        Object result = invocation.proceed();
        stopwatch.stop();
        logExectionTime(stopwatch, (MappedStatement) invocation.getArgs()[0]);
        return result;
    }
}

现在要绑定请求时,我想将这些指标存储在请求中作为属性。我已经尝试过使用这种简单的方法来获取请求,但是由于请求始终为空,因此该方法不起作用(我已经知道该解决方案不适用于异步方法,但是使用MyBatis Interceptor及其方法,认为不是这种情况):

@Autowired
private HttpServletRequest request;

所以,问题是如何在MyBatis拦截器中正确获取请求?

1 个答案:

答案 0 :(得分:0)

在回答您的问题之前,请注意一个重要说明:在DAO层中访问UI层是一种不好的做法。这会导致错误方向的依赖性。应用程序的外层可以访问内层,但是在这种情况下,这是另一回事。取而代之的是,您需要创建一个不属于任何层并且将(或至少可能)被应用程序的所有层使用的类。可以像MetricsHolder这样命名。拦截器可以向其中存储值,也可以在计划获取度量的其他地方读取它(可以直接使用它,也可以将它们存储在请求中(如果它在UI层中并且请求在那里可用)。

但是现在回到您的问题。即使创建MetricsHolder之类的内容,您仍然会遇到无法将其注入mybatis拦截器的问题。

您不能仅将带有Autowired批注的字段添加到拦截器并期望对其进行设置。这是因为拦截器是由mybatis实例化的,而不是由Spring实例化的。因此spring没有机会将依赖项注入拦截器中。

一种解决方法是将拦截处理委托给Spring bean,该Spring bean将是spring上下文的一部分,并且可以在那里访问其他bean。这里的问题是如何使该bean在拦截器中可用。

这可以通过将对此类bean的引用存储在线程局部变量中来完成。这是如何执行此操作的示例。首先创建一个用于存储Spring bean的注册表。

public class QueryInterceptorRegistry {

    private static ThreadLocal<QueryInterceptor> queryInterceptor = new ThreadLocal<>();

    public static QueryInterceptor getQueryInterceptor() {
        return queryInterceptor.get();
    }

    public static void setQueryInterceptor(QueryInterceptor queryInterceptor) {
        QueryInterceptorRegistry.queryInterceptor.set(queryInterceptor);
    }

    public static void clear() {
        queryInterceptor.remove();
    }

}

查询拦截器如下:

public interface QueryInterceptor {
    Object interceptQuery(Invocation invocation) throws InvocationTargetException, IllegalAccessException;
}

然后,您可以创建一个拦截器,将处理委托给spring bean:

@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 class QueryInterceptorPlugin implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        QueryInterceptor interceptor = QueryInterceptorRegistry.getQueryInterceptor();
        if (interceptor == null) {
            return invocation.proceed();
        } else {
            return interceptor.interceptQuery(invocation);
        }
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
    }

}

您需要创建一个QueryInterceptor的实现,该实现执行您所需的工作并将其变为Spring bean(在这里您可以访问其他spring bean,包括我上面写的不可以的请求):

@Component
public class MyInterceptorDelegate implements QueryInterceptor {

    @Autowired
    private SomeSpringManagedBean someBean;

    @Override
    public Object interceptQuery(Invocation invocation) throws InvocationTargetException, IllegalAccessException {
      // do whatever you did in the mybatis interceptor here
      // but with access to spring beans
   }
}

现在唯一的问题是在注册表中设置和清理委托。

我是通过应用于服务层方法的方面来做到这一点的(但是您可以手动执行,也可以在spring mvc拦截器中执行)。我的表情如下:

@Aspect
public class SqlSessionCacheCleanerAspect {

    @Autowired MyInterceptorDelegate myInterceptorDelegate;

    @Around("some pointcut that describes service methods")
    public Object applyInterceptorDelegate(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        QueryInterceptorRegistry.setQueryInterceptor(myInterceptorDelegate);
        try {
            return proceedingJoinPoint.proceed();
        } finally {
            QueryInterceptorRegistry.clear();
        }
    }

}