我想测量将由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拦截器中正确获取请求?
答案 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();
}
}
}