异步任务

异步任务优化の(三) cglib

2019-04-04  本文已影响0人  Yellowtail

网上一顿乱搜之后,看到了一些文章,得到了启发
这是博客
这是源码

思路就是通过 cglib 生成一个代理类,
我们在代理类里把要执行的方法名记下来

大家看到cglib 不要怕,用起来很简单的,实现一个接口就行了

实现

以下实现基本是抄的 https://github.com/benjiman/benjiql
AsyncUtilsV2 里的代码是我自己写的

RecordingObject

cglib 实现类,也就是我们得到 代理类的地方,
基本照抄,获得参数列表功能是我自己加的

import java.lang.reflect.Method;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

public class RecordingObject implements MethodInterceptor {

    private String currentPropertyName = "";
    
    private Object[] args;
    
    private Recorder<?> currentMock = null;

    @SuppressWarnings("unchecked")
    public static <T> Recorder<T> create(Class<T> cls) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(cls);
        final RecordingObject recordingObject = new RecordingObject();

        enhancer.setCallback(recordingObject);
        return new Recorder((T) enhancer.create(), recordingObject);
    }

    public Object intercept(Object o, Method method, Object[] os, MethodProxy mp) throws Throwable {
        if (method.getName().equals("getCurrentPropertyName")) {
            return getCurrentPropertyName();
        }
        
        //把当前 方法名记录下来
        currentPropertyName = method.getName();
        
        args = os;
        
        Class<?> returnType = method.getReturnType();
        
        //在返回值是 void时,快速处理
        String name = returnType.getName();
        if ("void".equals(name)) {
            return null;
        }
        
        try {
            currentMock = create(returnType);
            return currentMock.getObject();
        } catch (IllegalArgumentException e) {
            return DefaultValues.getDefault(returnType);
        }
    }

    public String getCurrentPropertyName() {
        return currentPropertyName + (currentMock == null ? "" : ("." + currentMock.getCurrentPropertyName()));
    }
    
    /**
     * <br>得到参数值 列表
     * @return
     * @author YellowTail
     * @since 2019-03-28
     */
    public Object[] getArgs() {
        return args;
    }

}

Recorder

中间商

public class Recorder<T> {

    private T t;
    private RecordingObject recorder;

    public Recorder(T t, RecordingObject recorder) {
        this.t = t;
        this.recorder = recorder;
    }

    public String getCurrentPropertyName() {
        return recorder.getCurrentPropertyName();
    }
    
    /**
     * <br>得到参数值 列表
     * @return
     * @author YellowTail
     * @since 2019-03-28
     */
    public Object[] getArgs() {
        return recorder.getArgs();
    }

    public T getObject() {
        return t;
    }
}

AsyncUtilsV2

import java.io.UnsupportedEncodingException;
import java.util.function.Consumer;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.support.AopUtils;


/**
 * 异步任务 V2
 * @author YellowTail
 * @since 2019-03-28
 * @param <T>
 */
public class AsyncUtilsV2<T> {
    
    private static final Logger LOGGER =LoggerFactory.getLogger(AsyncUtilsV2.class);
    
    private Class<T> cls;
    
    final Recorder<T> recorder;
    
    public AsyncUtilsV2(Class<T> cls) {
        
        this.cls = cls;
        this.recorder = RecordingObject.create(cls);
        
        //创建动态代理实例所需的时间
        LOGGER.info("send async task, init dynamic proxy gap {}", System.currentTimeMillis() - begin);
        
        LOGGER.info("AsyncUtilsV2 cls is {}", cls);
    }
    
    public T getT() {
        return recorder.getObject();
    }
    
    /**
     * <br> 执行一个 <font color="red"> public </font> 方法
     * <br> 写法必须是 run(c -> c.genGroupUnit(groupId, agentId)),  "c."  不能省略,否则会出问题
     * <br> 之所以有这个要求,是因为,此异步任务使用了动态代理,加了“c.” 表示使用了该代理的方法,才能记录下来,否则直接走原生class的方法
     * @param c
     * @author YellowTail
     * @since 2019-03-30
     */
    public void run(Consumer<T> c) {
        //让方法执行一下,执行的实例是代理实例
        long t1 = System.currentTimeMillis();
        
        try {
            c.accept(getT());
        } catch (Exception e) {
            LOGGER.error("Consumer invoke ", e);
        }
        
        String methodName = recorder.getCurrentPropertyName();
        if (StringUtils.isBlank(methodName)) {
            //方法为空,说明调用的是private方法,暂时不支持
        }
        
        Object[] args = recorder.getArgs();
        
        //构造实际的消息对象
        AsyncMessage asyncMessage = new AsyncMessage();
        asyncMessage.setTargetClass(cls);
        asyncMessage.setMethodName(methodName);
        asyncMessage.setArgs(args);
        
    }
    
    /**
     * <br> 要执行的方法在哪个类里,最好使用 xx.class, 不要使用 xxx.getClass(), 因为 spring bean 得到的 getClass() 有些奇怪
     * @param cls
     * @return
     * @author YellowTail
     * @since 2019-04-02
     */
    public static <T> AsyncUtilsV2<T> from(Class<T> cls) {
        return new AsyncUtilsV2<>(cls);
    }
    
    @SuppressWarnings("unchecked")
    public static <T> AsyncUtilsV2<T> from(T object) {
        Class<T> targetClass = (Class<T>) AopUtils.getTargetClass(object);
        
        return new AsyncUtilsV2<>(targetClass);
    }
}

调用

// from 参数是 class
AsyncUtilsV2.from(NewCommunityService.class).run(d -> d.incCommentCount("123"));

//from 参数是 spring bean
AsyncUtilsV2.from(unitFollowUpsDAO).run(c -> c.delDocByUnitIdAndGroupId(unit.get_id(), unit.getGroupId()));

实际效果

实际效果还是不错的,因为都是直接调用方法,所以Java 编译器 能够帮我们识别代码重构的问题,
会在编译的时候报错

不足

虽然我们最终是选择的此方案,但是此方案依然有不足的地方

  1. 不支持private方法
    因为这个方法的原理就是先获得一个代理类,然后执行这个代理类的方法,private方法写不出来
  2. cglib 性能不行
    cglib 创建代理类实例的时候,性能不是很好,它自带缓存,如果命中了,倒挺快的,没有命中就要1-200毫秒

参考

https://benjiweber.co.uk/blog/2013/12/28/typesafe-database-interaction-with-java-8/
https://github.com/benjiman/benjiql
https://colobu.com/2014/10/28/secrets-of-java-8-functional-interface/

上一篇 下一篇

猜你喜欢

热点阅读