Spring 无法代理 Mybatis 接口问题

本文介绍了一种在平台化进程中统一日志格式的方法,特别针对Mybatis的DAO层日志打印问题。通过自定义MapperFactoryBean和JdkDynamicDigestLogProxy,实现了对Mybatis接口调用的增强,从而能够打印细粒度的业务日志。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最近公司正在开始平台化,需要统一打印日志格式。由于领导对于我的认可,把这件事情交给我来做。所谓统一日志格式主要是做以下几件事:

  • more business log:细粒度的业务日志文件区分(Dao、Manage、Service、Web)
  • method digest log:方法级别的日志摘要(对应业务日志的摘要日志)
  • a call chain profile log:当前系统一次整个完整调用的层级概要图

这种不仅可以在排查问题更加方便,同时也可以通过监控方法摘要日志文件当某个方法调用时间达到设置的阈值时还可以进行报警。

摘要日志的打印原理是通过自定义注解,通过 Spring Aspect 切面对注解定义的方法进行增强。但是问题就卡在 dao 层的日志打印,Mybatis 调用数据库的接口在集成 Spring 生成的 Bean 对象无法代理。通过 debug 跟踪 Spring AOP 的源码发现这个接口对应的 Mapper 的 Spring bean Class 对象是 MapperFactoryBean 而不是对象的 BankInfoMapper。关于为什么是 MapperFactoryBean,不清的朋友可以看我之前的一篇文章 – MyBatis Spring 集成源码解析
在这里插入图片描述
Spring AOP 的原理是拿到 Spring bean 定义的所有的增强通知的列表。然后遍历这些通知,看哪个通知适用于当前需要创建的 Spring Bean。如果适用就添加到当前 bean 创建的动态代理对象的通知列表的属性中。当方法执行时就遍历代理对象的通知列表实现对对象功能的增强。

在这里插入图片描述
但是定义的切面表达式是 BankInfoMapper 所在的包,肯定与 MapperFactoryBean 所在的包不同。所以不能对 BankInfoMapper 这个对象进行代理。虽然可以把包定义到 MapperFactoryBean 的目录,但是与其它日志打印冲突。 所有只有别想他法:

如果上看到上面提到的文章就可以知道。在 Spring 集成 Mybatis 进行接口调用的时候,是通过 MapperFactoryBean 来对 Mybatis 接口进行创建对象的。那么能不能对 MapperFactoryBean 创建的对象进行代理呢。通过观察可以看到在 Mybatis 的 MapperScan 注解中,我们可以对 MapperFactoryBean 进行自定义。

在这里插入图片描述
那么问题就可以解决了,解题思路如下:我们可能通过继承 MapperFactoryBean ,然后对于重写它的 getObject 方法。MapperFactoryBean 创建的对象进行增强就可以了。

下面就是继承类,实现对 BankInfoMapper 进行增强。

DigestLogMapperFactoryBean.java

public class DigestLogMapperFactoryBean<T> extends MapperFactoryBean<T> {

	public DigestLogMapperFactoryBean(){}

	public DigestLogMapperFactoryBean(Class<T> mapperInterface){
		super(mapperInterface);
	}

	@Override
	public T getObject() throws Exception {
		Class<?> mapperInterfaceClazz = getObjectType();
		T mapperProxy = super.getObject();
		return (T) new JdkDynamicDigestLogProxy(mapperProxy, mapperInterfaceClazz).getProxy();
	}

}

下面就是增强类,对 Mybatis 的接口调用进行增强。

JdkDynamicDigestLogProxy.class

public class JdkDynamicDigestLogProxy implements InvocationHandler {

	private static final Logger logger = LoggerFactory.getLogger(JdkDynamicDigestLogProxy.class);

	/**
	 * 是否打印方法参数
	 */
	private boolean printArguments = false;
	/**
	 * 是否打印方法结果
	 */
	private boolean printResults = false;

	private Object object;

	private Class<?> interfaceClazz;

	public JdkDynamicDigestLogProxy(Object object, Class<?> interfaceClazz) {
		this.interfaceClazz = interfaceClazz;
		this.object = object;
	}

	public Object getProxy() {
		return getProxy(ClassUtils.getDefaultClassLoader());
	}

	public Object getProxy(ClassLoader classLoader) {
		return Proxy.newProxyInstance(classLoader, new Class[]{interfaceClazz}, this);
	}


	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		Object retValue = null;
		String result = "N";
		long beginTime = System.currentTimeMillis();
		try {
			retValue = method.invoke(object, args);
			result = "Y";
			return retValue;
		} finally {
			long endTime = System.currentTimeMillis();
			logger.info("[({}.{},{},{}ms)({})({})]", interfaceClazz.getSimpleName(), method.getName(), result,
					endTime - beginTime, getArgumentsString(args), getResultsString(retValue));
		}
	}

	private String getMessage(Method method) {
		return interfaceClazz.getSimpleName() + "." + method.getName();
	}

	/**
	 * 获取日志中使用的方法参数
	 */
	private String getArgumentsString(Object[] args) {
		String arguments = "-";
		if (printArguments) {
			arguments = StringUtils.join(args, ",");
		}
		return arguments;
	}

	/**
	 * 获取结果内容
	 */
	private Object getResultsString(Object retValue) {
		if (printResults) {
			return retValue == null ? "null" : retValue.toString();
		} else {
			return "-";
		}
	}

}

最后就是在 MapperScan 添加上这个继承类(DigestLogMapperFactoryBean)就行了 :
在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值