前言:
本章主要基于spring提供的一些扩展接口进行相关的服务调用链路设计。对于spring框架相信大家都比较熟悉,该框架成为我们Java开发必修的一门技术。本章博客主要基于BeanPostProcessor该接口进行一个关于调用链路的一个小小设计,方便大家更好的理解spring各个接口在各个场景的一个扩展。
基于BeanPostProcessor接口主要用于在spring的bean对象创建途中进行相关的代理从而在执行目标方法途中进行链路调用的记录。在阅读该文章前希望大家对spring相关扩展接口相关的认识以及对jdk代理和cglib代理有相关的认识。以下是关键类的核心代理的一个过程,下面将围绕该类进行一个相关的讲解。
package com.log.link.monitor;
import com.log.link.monitor.annotation.MapperMonitor;
import com.log.link.monitor.annotation.ServiceMonitor;
import com.log.link.monitor.listener.DefaultStackListener;
import com.log.link.monitor.listener.StackCallListener;
import com.log.link.monitor.monitor.MapperMonitorProxyFactory;
import com.log.link.monitor.monitor.ServiceMonitorProxyFactory;
import com.log.link.monitor.parameter.DefaultParseParamHandel;
import com.log.link.monitor.parameter.ParseParamHandel;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import org.mybatis.spring.mapper.MapperFactoryBean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* @author 唐砖
* @Classname ReferenceBeanPostProcessor
* @Date 2022/01/02 16:07
* 拦截spring初始化bean对象的时候进行相关代理过程
* 功能介绍:
* 代理dao层面的mapper对象,进行相关的入参打印和执行时间监控
* 代理service层面进行相关的调用链路监控
*/
@Component
public class ReferenceBeanPostProcessor implements BeanPostProcessor, InitializingBean {
private static final Logger LOG = LoggerFactory.getLogger(ReferenceBeanPostProcessor.class);
/**
* 需要被拦截代理的方法
*/
private static final String GET_OBJECT_METHOD = "getObject";
private StackCallListener stackCallListener;
private ParseParamHandel parseParamHandel;
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
try {
Class<?> beanClass = bean.getClass();
ServiceMonitor annotation = beanClass.getAnnotation(ServiceMonitor.class);
if (annotation != null) {
LOG.info("准备构建代理ioc服务bean对象......");
return crateProxyObject(beanClass, new ServiceMonitorProxyFactory(bean, stackCallListener, parseParamHandel));
}
return bean;
} catch (Exception err) {
LOG.error("初始化代理ioc的bean对象失败,错误信息为:{}", err.getMessage());
return bean;
}
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (checkAnnotation(bean)) {
LOG.info("create spring ioc bean success , bean object name is {}", beanName);
return bean;
}
LOG.info("准备构建代理mapper服务对象......");
return EnhancedMapperBeanProxy.createProxy(bean, parseParamHandel, stackCallListener);
}
public boolean checkAnnotation(Object bean) {
if (bean instanceof MapperFactoryBean) {
MapperFactoryBean mapperFactoryBean = (MapperFactoryBean) bean;
Class<?> mapperObject = mapperFactoryBean.getMapperInterface();
return mapperObject.getAnnotation(MapperMonitor.class) == null;
}
return true;
}
static Object crateProxyObject(Class<?> type, Callback callback) {
Enhancer enhancer = new Enhancer();
enhancer.setCallback(callback);
enhancer.setSuperclass(type);
return enhancer.create();
}
@Override
public void afterPropertiesSet() throws Exception {
this.stackCallListener = new DefaultStackListener();
this.parseParamHandel = new DefaultParseParamHandel();
}
}
在初始化之前调用postProcessBeforeInitialization,会将当前正在初始化的bean对象和bean名称传入,我们基于改对象可以进行一个相关代理。为了比较灵活的一个适配我们采用了注解的一个方式。存在该注解的情况我们对该类进行一个相关的cglib代理。
这只是在我们代理service的过程中所进行的一个代理过程。其难点主要基于一个mapper层面的一个代理过程。我们都知道,大多数情况下我们使用mybatis都是会集成spring,把我们构建的mapper对象交由我们springioc容器进行一个管理。在这种情况下我们如果监控到我们的mapper层面。当然也是依靠于代理技术实现,如何代理怎么代理何时代理这讲围绕我们的spring于mybatis集成的源码当中分析。
在mybatis和spring集成之后我们的mapper接口都会被封装成MapperFactoryBean对象。
在搞清楚我们的mapper对象会被封装成MapperFactoryBean对象之后,我们只需要在spring初始化完成对象之后判断是否为该对象并进行一个相关的代理过程。
如果当前mapper类需要进行代理,当前我采用的是静态内部类进行一个代理。
private static class EnhancedMapperBeanProxy implements MethodInterceptor {
/**
* 当前目标对象为:MapperFactoryBean.class
* 作用:为什么要代理该对象,从spring集成mybatis源码看出,我们需要把mybatis为我们构建的对象交给spring进行管理。
* 可以看出MapperFactoryBean.class实现于我们的FactoryBean.class改变bean的创建行为。其实也是基于session构建的mapper对象
* 我们基于代理MapperFactoryBean.class对象,拦截getObject对象进行相关拦截,装饰mapper代理对象
*/
private final Object target;
private final ParseParamHandel paramHandel;
private final StackCallListener listener;
/**
* 曾倩mapper代理
* @param target 目标代理对象
* @param paramHandel 参数解析处理器
*/
public EnhancedMapperBeanProxy(Object target, ParseParamHandel paramHandel, StackCallListener listener) {
this.target = target;
this.paramHandel = paramHandel;
this.listener = listener;
}
public static Object createProxy(Object target, ParseParamHandel paramHandel, StackCallListener listener) {
try {
final Class<?> type = target.getClass();
EnhancedMapperBeanProxy callback = new EnhancedMapperBeanProxy(target, paramHandel, listener);
return crateProxyObject(type, callback);
} catch (Exception err) {
LOG.error(target.getClass().getName() + "object create proxy error");
return target;
}
}
/**
* 作用:主要用于代理MapperFactoryBean.class拦截getObject方法,因为核心构建代理对象在这个方法
* 基于mybatis为我们构建的代理对象进行相关装饰,可以起到一个拦截作用
* @param o 代理对象
* @param method 目标方法
* @param objects 参数
* @param methodProxy 代理方法
* @return 返回代理对象
* @throws Throwable
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
try {
if (GET_OBJECT_METHOD.equals(method.getName())) {
MapperFactoryBean mapper = (MapperFactoryBean) target;
Class mapperInterface = mapper.getMapperInterface();
// 获取mybatis为我们创建的代理对象。调用:MapperFactoryBean.getObject()
Object proxyObject = method.invoke(target, objects);
return Proxy.newProxyInstance(
mapperInterface.getClassLoader(),
new Class[]{mapperInterface},
new MapperMonitorProxyFactory(proxyObject, listener, paramHandel, mapperInterface));
}
return method.invoke(target, objects);
} catch (Exception err) {
LOG.error("create proxy object error , info is : {}", err.getMessage());
return method.invoke(target, objects);
}
}
}
这里我们要注意的是,上面也有说过我们的mapper对象会被封装成MapperFactoryBean对象的,所以我们对该对象进行了一层再次代理,很多人会有疑问,明明知道mapper对象被封装在MapperFactoryBean对象内部,为什么不直接针对内部被封装的mapper对象进行相关代理这不是多此一举。在这里我们不得不思考这样一个问题,我们现在要做的是将我们的mapper对象交给我们的spring进行管理,并且管理的还是mybatis为我们构建的代理对象,这样才能不改变对象的原有行为。这样一想我们能代理内部的mapper对象吗。
上面我也介绍过MapperFactoryBean对象的一个继承体系。它实现于我们的FactoryBean会改变spring构建bean的默认行为。
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
因为我们的MapperFactoryBean是存在于我们的ioc容器内部的,并且是在初始化成功后进行一个代理的,我上面也说过如何代理怎么代理何时代理这里我选择了代理MapperFactoryBean对象拦截内部的gitObject拿到mybatis为我们构建的代理对象。这还远远不够,如果我们就这样把该对象交给spring进行管理我们也起不到真正的拦截作用。我们还需要在这层的基础上再进行一层代理。
其实这里mybatis是基于动态代理为我们代理的对象,这个时候我们不能再次代理了,但是我们又需要对该对象进行一个多重代理,但是在代理这一层面是行不通的。我们采用一种设计模式装饰者模式,对mybatis为我们构建的代理对象进行一层装饰。从而在不改变真正执行逻辑进行相关的增强。从MapperFactoryBean类内部我们有看到维护了我们最原始的mapper对象类型。我们可以居于它进行一个动态代理,把mybatis为我们构建的代理对象作为装饰传入。从而代理了mapper类,但其内部维护了mybatis为我们代理的对象。由上图可看出。
接下来将演示:
当前采用了模板方法模式,针对jdk动态代理和cglib动态代理进行了公用的封装,具体的调用实现进行了抽象子类实现。
当前就是调用的核心逻辑
具体思想才是真正本文章想表达传递的。显示效果如图:
当前服务的一个调用追踪都会被记录下来,方便排查线上错误。当前功能也并不是非常完善,虽然市场上已经有很多很完善成熟的专业技术解决这一问题。本文章主要想传递一个思路,在遇到不同问题途中怎么解决相关问题与更多的编程思路,希望能给读者带来收获。如有不对希望大家指出,我们共同成长。
解决难点:其实当前其实最大的难点痛点主要是基于怎样优雅的代理mapper对象在不改变原有行为的情况下怎么进行代理工作。其中的代理过程比较复杂,流程基本如下:首先我们知道了mapper对象会被封装成MapperFactoryBean对象,我们就使用cglib代理该对象,这里就用到了怎么代理这一说,因为当前类不是基于接口不能使用jdk代理我们选择使用了cglib代理,在代理之后交给springioc容器,因为我们都知道在spring初始化如果发现有接口实现FactoryBean接口的会进行一个调用getObject,我们基于拦截它得到真正的代理对象在进行相关的装饰代理。