自定义Interceptor实现对Mybatis超时执行SQL的监控

从事代码一年多了,遇到了很多的bug,也解决了很多bug,特此趁机记录一下:
      责任链设计模式是很多框架经常采用设计模式,一定程度上拥抱了面向对象的开放和扩展,给用户暴露接口的形式能够无侵入性让用户能够做一些系统性的工作。责任链设计模式 让这些对象形成一条链,并沿着这条链传递请求,直到链上的某一个对象决定处理此请求。
    在Spring中Aop中定义过切面Aspect可以实现对方法执行的前后等进行拦截,在Netty中ChannelPipeline中持有众多自定义的ChannelHandler来完成编解码,序列化和反序列化,拆包和其他业务功能,在Tomcat也采用责任链模式,Pipline拥有很多valve,实现Engine,Host,Context,Wrapper的处理和装饰功能。Servlet也使用Filter和Listener形成责任链模式,Log4j也是同样的模型。Dubbo等是通过Invoker来实现责任链模式。类似的框架太多了,希望以后能够详细见识。

     今天要说的基于动态代理的方式的Mybatis中的Plugin插件,这个可能比较陌生,虽然性能也据说不高,但还是在此演示下。首先思考三个问题,插件在哪里侵入代码?插件如何实现对原有功能和对象的代理和操作?插件要实现什么功能?

首先:配置文件中配置如下

 <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"
          p:dataSource-ref="dataSource"
          p:configLocation="classpath:mybatis-config.xml">
        <property name="plugins">
            <array>
                <bean class="com.XX.XX.aopmethod.NotifyInterceptor">
                    <property name="properties">
                        <value>
                            notifyTime=30
                        </value>
                    </property>
                </bean>
            </array>
        </property>
    </bean>
其次定义拦截器,
/**
 * Created by wisdom on 2018/6/13.
 */
@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 NotifyInterceptor implements Interceptor {
    private static final Logger LOGGER = LogManager.getLogger(NotifyInterceptor.class);
    private Properties properties;

    @Override
    public Object intercept(Invocation arg0) throws Throwable {
        MappedStatement mappedStatement = (MappedStatement) arg0.getArgs()[0];
        Object parameter = null;
        if (arg0.getArgs().length < 1) {
            parameter = arg0.getArgs()[1];
        }
        long start = System.currentTimeMillis();
        Object returnValue = arg0.proceed();
        long end = System.currentTimeMillis();
        long time = (end - start);
        String sql = this.getActualSql(mappedStatement.getConfiguration(), mappedStatement.getBoundSql(parameter),
                mappedStatement.getId(), time);
        if (time > Long.parseLong(String.valueOf(properties.get("notifyTime")))) {
            LOGGER.warn(sql);
        }

        return returnValue;
    }

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

    public String getActualSql(Configuration configuration, BoundSql boundSql, String sqlId, long time) {
        String sql = showSql(configuration, boundSql);
        StringBuilder str = new StringBuilder("执行缓慢的SQL:");
        str.append(sqlId);
        str.append(":");
        str.append(sql);
        str.append(":");
        str.append(time);
        str.append("ms");
        return str.toString();
    }

    public String showSql(Configuration configuration, BoundSql boundSql) {
        Object parameterObject = boundSql.getParameterObject();
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        String sql = boundSql.getSql().replaceAll("[\\s]+", " ");
        if (parameterMappings.size() > 0 && parameterObject != null) {
            TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
            if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                sql = sql.replaceFirst("\\?", getParameterValue(parameterObject));

            } else {
                MetaObject metaObject = configuration.newMetaObject(parameterObject);
                for (ParameterMapping parameterMapping : parameterMappings) {
                    String propertyName = parameterMapping.getProperty();
                    if (metaObject.hasGetter(propertyName)) {
                        Object obj = metaObject.getValue(propertyName);
                        sql = sql.replaceFirst("\\?", getParameterValue(obj));
                    } else if (boundSql.hasAdditionalParameter(propertyName)) {
                        Object obj = boundSql.getAdditionalParameter(propertyName);
                        sql = sql.replaceFirst("\\?", getParameterValue(obj));
                    }
                }
            }
        }
        return sql;
    }

    private static String getParameterValue(Object obj) {
        String value;
        if (obj instanceof String) {
            value = "'" + obj.toString() + "'";
        } else if (obj instanceof Date) {
            value = "'" + FormatUtil.dateToString((Date) obj) + "'";
        } else {
            if (obj != null) {
                value = obj.toString();
            } else {
                value = "";
            }

        }
        return value;
    }

    @Override
    public void setProperties(Properties properties) {
        this.properties = properties;
    }

     该setProperties方法,这个方法在Configuration初始化当前的Interceptor时就会执行,这里存放超时时间。看MyInterceptor类上我们用@Intercepts标记了这是一个Interceptor,然后在@Intercepts中定义了两个@Signature,即两个拦截点。第一个@Signature我们定义了该Interceptor将拦截Executor接口中参数类型为MappedStatement、Object的update方法;第二个@Signature我们定义了该Interceptor将拦截Executor中参数类型为MappedStatement、Object,RowBounds和ResultHandler的query方法。方法内部通过计算方法执行前后的时间差,来衡量执行耗时,如果大于指定值,则可以报警和通知,不过这里为了简单,只是记录下来。运行效果如下:

com.XX.XX.getUserInfoByDsId:select top 1 * from UserInfo WITH(nolock) where DsId = 'test':5709ms(项目启动,处理缓慢)
com.XX.XX.getUserInfoByDsId:select top 1 * from UserInfo WITH(nolock) where DsId = 'test':10ms

   通过如上的代码,就可以实现监视每条SQL的执行效率,并且实现超时自动通知功能。

   同时也可以猜测各种框架的责任链模式使用都是大同小异,对用户来说,要做的开发不是很大,熟练掌握其API就可以完成功能。当然要对该模式的实现依赖的动态代理和持有的链表或者其他数据容器有所了解,就可以游刃有余啦。




猜你喜欢

转载自blog.csdn.net/qq_21190979/article/details/80683925