最近写了一个类似生产者和消费者的多线程程序,由客户(生产者)往队列中放入值, 后台有一个守护线程间歇的从队列中取值交给一个消费者(如存到数据库中)最终达到减轻保存数据库的频率.
写完我想知道中途是否有漏掉的值,也就是已经放到队列中但消费者未消费的值, 这时都需要有一个程序侦听生产者和消费者的记录数, 两者一致时即没有错误反之都是存在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) {}