记第一次Byte Buddy使用

最近写了一个类似生产者和消费者的多线程程序,由客户(生产者)往队列中放入值, 后台有一个守护线程间歇的从队列中取值交给一个消费者(如存到数据库中)最终达到减轻保存数据库的频率.

写完我想知道中途是否有漏掉的值,也就是已经放到队列中但消费者未消费的值, 这时都需要有一个程序侦听生产者和消费者的记录数, 两者一致时即没有错误反之都是存在bug. 是不是有点像AOP干的事? 但又不想用AOP还有撒可以用: Java Agent!用maven引入依赖开始编译(需要下载asm jar),这时出现未知的模块错误, 项目用的是jdk 11. 不用Java Agent还能用撒?字节码修改。终于绕回来了.

maven 依赖

        <!-- https://mvnrepository.com/artifact/net.bytebuddy/byte-buddy -->
        <dependency>
            <groupId>net.bytebuddy</groupId>
            <artifactId>byte-buddy</artifactId>
            <version>1.10.13</version>
            <type>jar</type>
        </dependency>
        <dependency>
            <groupId>net.bytebuddy</groupId>
            <artifactId>byte-buddy-agent</artifactId>
            <version>1.10.13</version>
            <scope>test</scope>
        </dependency>

maven-surefire-plugin 引入一个参数

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.20</version>
                <configuration>
                    <argLine>-Dfile.encoding=UTF-8</argLine>
                    <argLine>-DfailIfNoTests=false</argLine>
                    <argLine>-javaagent:C:\\Users\xiaofanku\\.m2\repository\\net\\bytebuddy\\byte-buddy-agent\\1.10.13\\byte-buddy-agent-1.10.13.jar</argLine>
                </configuration>
            </plugin>

程序代码

public abstract class AbstractCubbyHole<T> {
    private final static Logger logger = LoggerFactory.getLogger(AbstractCubbyHole.class);
    /**
     * 保存对象
     * @param value
     * @return 
     */
    public abstract boolean put(final T value);
    
    /**
     * 保存对象
     * @param values 
     */
    public abstract void putAll(final Collection<T> values);
}

@FunctionalInterface
public interface CubbyHoleProcessor<T> {
    /**
     * 处理程序
     * 
     * @param action 动作记录
     * @return 执行成功的对像执行CubbyHole::toChecksum结果的集合
     */
    Set<String> process(Collection<T> action);
}

public final class CubbyHoleLinkedDeque<T> extends AbstractCubbyHole<T>{
    private final ConcurrentLinkedDeque<T> queue;
    private final static Logger logger = LoggerFactory.getLogger(CubbyHoleLinkedDeque.class);
    
    public CubbyHoleLinkedDeque(final CubbyHoleProcessor<T> processor) {
        super();
        this.queue = new ConcurrentLinkedDeque<>();
        Thread thread = new Thread(() -> {
            while (!Thread.currentThread().isInterrupted()) { //判断是否被中断
                List<T> rs = new ArrayList<>();
                for (T obj = queue.poll(); obj != null; obj = queue.poll()) {
                    rs.add(obj);
                }
                removeAll(rs, processor);
                try{
                    Thread.currentThread().sleep(1000);
                }catch(InterruptedException e){
                    e.printStackTrace();
                }
            }
        });
        thread.setDaemon(true);
        thread.start();
    }
    
    @Override
    public boolean put(final T value) {
        //往里放
        return queue.add(value);
    }
    
    @Override
    public void putAll(final Collection<T> values) {
        //往里放
        queue.addAll(values);
    }
    
    public void each(final Consumer<T> action) {
        queue.forEach(action);
    }
    
    private void remove(final T data, final CubbyHoleProcessor<T> processor) {
        if(null == data){
            return;
        }
        removeAll(List.of(data), processor);
    }
    
    private void removeAll(final Collection<T> data, final CubbyHoleProcessor<T> processor) {
        if(null == data || data.isEmpty()){
            return;
        }
        final Set<String> affect = processor.process(data);
        queue.removeIf(aed -> affect.contains(CubbyHoleLinkedDeque.toChecksum(aed)));
    }
}

ByteBuddy代码

/**
 *
 * @author xiaofanku
 */
public class CubbyHoleInterceptor {
    private final static String NEWLINE = "\r\n";
    
    @RuntimeType
    public static Object intercept(@Origin Method method, @Argument(0) Object arg0, @SuperCall Callable<?> callable) throws Exception {
        long start = System.currentTimeMillis();
        Object resObj = null;
        try {
            resObj = callable.call();
        } finally {
            String descrip = "[TraceCastInterceptor]" + NEWLINE;
            descrip += "/*----------------------------------------------------------------------" + NEWLINE;
            descrip += " method name: " + method.getName() + NEWLINE;
            descrip += " method ages: " + method.getParameterCount() + NEWLINE;
            descrip += " method result: " + resObj + NEWLINE;
            descrip += " method elapsed: " + (System.currentTimeMillis() - start) + "ms" + NEWLINE;
            descrip += "/*----------------------------------------------------------------------" + NEWLINE;
            System.out.println(descrip);
        }
        return resObj;
    }
}
public class CubbyHoleFirstTest {
    @Test 
    public void testBuddy() throws NoSuchMethodException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException{
        //泛型类型需要这么声明参数类型
        TypeDescription.Generic genericSuperClass = TypeDescription.Generic.Builder.parameterizedType(CubbyHoleLinkedDeque.class, UserDTO.class).build();
        Class<?> dynamicType = new ByteBuddy()
            .subclass(genericSuperClass)
            .method(ElementMatchers.any())
            .intercept(MethodDelegation.to(CubbyHoleInterceptor.class))
            .make()
            .load(CubbyHoleLinkedDeque.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent())
            .getLoaded();
        @SuppressWarnings("unchecked")
        CubbyHoleLinkedDeque<UserDTO> ch = (CubbyHoleLinkedDeque<UserDTO>) dynamicType.getConstructor​(CubbyHoleProcessor.class).newInstance​(new UserDTOCubbyHoleProcessor());
        ThreadPoolExecutor tpe=(ThreadPoolExecutor)Executors.newCachedThreadPool();
        //开几个线程以不同的频率周斯的往ch中put
        CubbyHoleRunner t1 = new CubbyHoleRunner("t1", ch);
        CubbyHoleRunner t4 = new CubbyHoleRunner("t4", ch);
        CubbyHoleRunner t3 = new CubbyHoleRunner("t3", ch);
        CubbyHoleRunner t2 = new CubbyHoleRunner("t2", ch);
        tpe.execute(t1);
        tpe.execute(t2);
        tpe.execute(t3);
        tpe.execute(t4);
        tpe.shutdown();
        while (!tpe.isTerminated()) {
            //System.out.println("thread execute now");
        }
        System.out.println("Finished all threads");
        ch.each((UserDTO ut)->System.out.println(ut));
    }
}

A: ERROR!

java.lang.IllegalArgumentException: Cannot subclass primitive, array or final types: com.apobates.forum.utils.cache.CubbyHoleLinkedDeque<com.apobates.forum.util.test.cubby.UserDTO>

CubbyHoleLinkedDeque 类有final标识, ByteBuddy因此不能创建子类, 改正:将final去掉

public class CubbyHoleLinkedDeque<T> extends AbstractCubbyHole<T>{}

B: ERROR!

java.lang.IllegalArgumentException: None of […] allows for delegation from public boolean java.lang.Object.equals(java.lang.Object)

改正

public class CubbyHoleInterceptor {
    private final static String NEWLINE = "\r\n";
    
    @RuntimeType
    public static Object intercept(@AllArguments Object[] allArguments, @net.bytebuddy.implementation.bind.annotation.Origin Method method, @SuperCall Callable<Object> callable) throws Exception {}

C: 侦听的方法没有输出: removeAll, each方法不希望输出

在这里插入图片描述
改正: 新增一下注解,为put, putAll, remove, removeAll方法标记

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface TraceCast {
    String value();
}

CubbyHoleLinkedDeque类的方法加上注解

public class CubbyHoleLinkedDeque<T> extends AbstractCubbyHole<T>{
    @TraceCast(value="push")
    @Override
    public boolean put(final T value) {}
    
    @TraceCast(value="push")
    @Override
    public void putAll(final Collection<T> values) {}
    
    @TraceCast(value="pop")
    private void remove(final T data, final CubbyHoleProcessor<T> processor) {}
    
    @TraceCast(value="pop")
    private void removeAll(final Collection<T> data, final CubbyHoleProcessor<T> processor) {}
}

CubbyHoleFirstTest.testBuddy作以下改正:

        //泛型类型需要这么声明参数类型
        TypeDescription.Generic genericSuperClass = TypeDescription.Generic.Builder.parameterizedType(CubbyHoleLinkedDeque.class, UserDTO.class).build();
        Class<?> dynamicType = new ByteBuddy()
            .subclass(genericSuperClass)
            .method(ElementMatchers.isAnnotatedWith(TraceCast.class))
            .intercept(MethodDelegation.to(CubbyHoleInterceptor.class))
            .make()
            .load(CubbyHoleLinkedDeque.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent())
            .getLoaded();
        @SuppressWarnings("unchecked")
        CubbyHoleLinkedDeque<UserDTO> ch = (CubbyHoleLinkedDeque<UserDTO>) dynamicType.getConstructor​(CubbyHoleProcessor.class).newInstance​(new UserDTOCubbyHoleProcessor());
        ThreadPoolExecutor tpe=(ThreadPoolExecutor)Executors.newCachedThreadPool();
        //开几个线程以不同的频率周斯的往ch中put
        CubbyHoleRunner t1 = new CubbyHoleRunner("t1", ch);
        CubbyHoleRunner t4 = new CubbyHoleRunner("t4", ch);
        CubbyHoleRunner t3 = new CubbyHoleRunner("t3", ch);
        CubbyHoleRunner t2 = new CubbyHoleRunner("t2", ch);
        tpe.execute(t1);
        tpe.execute(t2);
        tpe.execute(t3);
        tpe.execute(t4);
        //增加putAll测试
        CubbyHoleMultiRunner mt1 = new CubbyHoleMultiRunner("mt1",ch);
        CubbyHoleMultiRunner mt2 = new CubbyHoleMultiRunner("mt2",ch);
        CubbyHoleMultiRunner mt3 = new CubbyHoleMultiRunner("mt3",ch);
        CubbyHoleMultiRunner mt4 = new CubbyHoleMultiRunner("mt4",ch);
        CubbyHoleMultiRunner mt5 = new CubbyHoleMultiRunner("mt5",ch);
        tpe.execute(mt1);
        tpe.execute(mt2);
        tpe.execute(mt3);
        tpe.execute(mt4);
        tpe.execute(mt5);
        tpe.shutdown();
        while (!tpe.isTerminated()) {
            //System.out.println("thread execute now");
        }
        System.out.println("Finished all threads");
        ch.each((UserDTO ut)->System.out.println(ut));

在这里插入图片描述
还是没有removeAll方法调用的输出, 因为remove,removeAll都是private修饰的, 子类是不可见的, 可以将其改为public或protected. 但我不希望完全公开(public)。将其设为protected

在这里插入图片描述

总结

现在即使不用Byte Buddy也可以新增一个CubbyHoleLinkedDeque的子类,在子类的方法中(例:put)调用父类的同名方法,并增加日志输出. 可想而知语言规范不允话的修改字节码也没用

附送一个Advice示例

    @Test 
    public void testBuddy() throws NoSuchMethodException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException{
        //泛型类型需要这么声明参数类型
        TypeDescription.Generic genericSuperClass = TypeDescription.Generic.Builder.parameterizedType(CubbyHoleLinkedDeque.class, UserDTO.class).build();
        Class<?> dynamicType = new ByteBuddy()
            .subclass(genericSuperClass)
            .method(ElementMatchers.isAnnotatedWith(TraceCast.class))
            .intercept(Advice.to(TraceCasAdvisor.class))
            .make()
            .load(CubbyHoleLinkedDeque.class.getClassLoader())
            .getLoaded();
         //ETC
   }

TraceCasAdvisor类:

public class TraceCasAdvisor{
    @OnMethodEnter
    public static void onMethodEnter(@AllArguments Object[] arguments, @Origin Method method) {
        if (method.getAnnotation(TraceCast.class) != null) {
            System.out.println("Enter " + method.getName() + " with arguments: " + Arrays.toString(arguments));
        }
    }
    
    @OnMethodExit
    public static void onMethodExit(@AllArguments Object[] arguments, @Origin Method method, @Return Object returned) {
        if (method.getAnnotation(TraceCast.class) != null) {
            System.out.println("Exit " + method.getName() + " with arguments: " + Arrays.toString(arguments)+" return: "+ returned);
        }
    }
}

<<< ERROR!
java.lang.IllegalStateException: Cannot assign void to class java.lang.Object

这个示例从白度结果抄来的, 这个结果害我折腾了好几天,这篇文章还是来自所谓的大神专聚网站. 这个问题的点在于java的void不能转成java.lang.Void, 而Void是一个Object的子类

public final class Void extends Object{}

如果不需要返回结果,删除@Return Object returned也可以运行:

    @OnMethodExit
    public static void onMethodExit(@AllArguments Object[] arguments, @Origin Method method) {}

真是需要方法执行的结果可以为@Return注解完善

public static void onMethodExit(@AllArguments Object[] arguments, @Origin Method method, @Return(readOnly = false, typing = DYNAMIC) Object returned) {}

在这里插入图片描述

©️2020 CSDN 皮肤主题: 数字20 设计师:CSDN官方博客 返回首页