记第一次Byte Buddy使用

Posted 街头小贩

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了记第一次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 Byte Buddy 教程

字节码Byte-buddy 使用委托实现抽象类方法并注入自定义 注解信息

JAVA动态字节码实现方式对比之Byte Buddy

字节码基于Byte Buddy语法创建的第一个 HelloWorld

字节码Byte-buddy 监控方法执行耗时动态获取出入参类型 和值

Buddy分配器之释放一页