需求:我们在使用mybatis的拦截器做一些工作时,有时候需要打印出调用者的信息以便能够快速处理。
1. 数据准备
插件详情:
@Slf4j
@Intercepts({@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class,
RowBounds.class, ResultHandler.class})})
public class MybatisLogInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
log.info("========{},{}", MoreReflection.getCallerPlace().getClassName(),
MoreReflection.getCallerPlace().getMethodName());
//直接过去
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
}
}
2. 代码实现
想输出如图所示的调用者:
![](https://img.haomeiwen.com/i16013479/98b489c6c5d59714.png)
原理:利用异常的堆栈信息+过滤路径的前缀方式,最终输出符合我们要求的第一个类信息。
@Slf4j
public class MoreReflection {
private static final StackTraceProvider STACK_TRACE_PROVIDER=new StackTraceProviderJdk8();
/**
* 获取当前方法的调用者,通过获取当前的调用栈向上查找获得
*
* @return StackTraceElement
*/
@Nullable
public static StackTraceElement getCallerPlace() {
return getCallerPlace(MoreReflection.class);
}
/**
* 获取当前方法的调用者,从指定的类位置向上查找
*
* @param locationAwareClass 指定类的类型
* @return StackTraceElement
*/
@Nullable
public static StackTraceElement getCallerPlace(Class<?> locationAwareClass) {
String name = locationAwareClass.getName();
return STACK_TRACE_PROVIDER.getCallerPlace(name::equals, Predicates.alwaysFalse());
}
}
接口类:
public interface StackTraceProvider {
@Nullable
default StackTraceElement getCallerPlace(Class<?> locationAwareClassChecker) {
String name = locationAwareClassChecker.getName();
return getCallerPlace(name::equals, Predicates.alwaysFalse());
}
/**
* 返回 {@param locationAwareClassChecker} 最接近调用方方向第一个元素
*/
@Nullable
StackTraceElement getCallerPlace(Predicate<String> locationAwareClassChecker, Predicate<String> ignore);
}
实现类:
public class StackTraceProviderJdk8 implements StackTraceProvider {
//反射相关的类
static final String[] REFLECTION_PREFIXES = {"sun.reflect.", "java.lang.reflect.", "jdk.internal.reflect."};
//mybatis相关的类
static final String[] ibatis_PREFIXES = {"org.apache.ibatis.", "org.mybatis.", "com.tellme.Intercept."};
//代理相关的类
static final String[] proxy_PREFIXES = {"com.sun.proxy."};
@Nullable
@Override
public StackTraceElement getCallerPlace(Predicate<String> locationAwareClassChecker, Predicate<String> ignore) {
boolean afterSelf = false;
boolean afterDeprecated = false;
String deprecatedClass = null;
// 之所以用异常获取而不是Thread.currentThread().getStackTrace(),是因为它内部实现其实也是判断当前线程了
StackTraceElement[] stackTrace = (new Exception()).getStackTrace();
for (StackTraceElement stack : stackTrace) {
String stackClassName = stack.getClassName();
if (isReflection(stackClassName)) {
continue;
}
if (isIbatis(stackClassName)) {
continue;
}
if (isProxy(stackClassName)) {
continue;
}
if (locationAwareClassChecker.test(stackClassName)) {
afterSelf = true;
continue;
}
if (afterSelf) {
if (deprecatedClass == null) {
deprecatedClass = stackClassName;
}
}
if (stackClassName.equals(deprecatedClass)) {
afterDeprecated = true;
}
//此处跳出循环
if (afterDeprecated) {
if (ignore.test(stackClassName)) {
continue;
}
//打印出堆栈信息
return stack;
}
}
return null;
}
private boolean isReflection(String stackClassName) {
return StringUtils.startsWithAny(stackClassName, REFLECTION_PREFIXES);
}
private boolean isIbatis(String stackClassName) {
return StringUtils.startsWithAny(stackClassName, ibatis_PREFIXES);
}
private boolean isProxy(String stackClassName) {
return StringUtils.startsWithAny(stackClassName, proxy_PREFIXES);
}
}
输出结果:
========com.tellme.service.impl.UserServiceImpl,findById
网友评论