Java 如何使用ByteBuddy代理现有对象
我想使用AOP自动向注释类添加一些功能 例如,假设有一个接口(StoredOnDatabase),其中包含一些有用的方法来从数据库读取和写入bean。假设有一些类(POJO)没有实现这个接口,并且用注释@Bean进行注释。当出现此注释时,我希望:Java 如何使用ByteBuddy代理现有对象,java,proxy,aop,byte-buddy,Java,Proxy,Aop,Byte Buddy,我想使用AOP自动向注释类添加一些功能 例如,假设有一个接口(StoredOnDatabase),其中包含一些有用的方法来从数据库读取和写入bean。假设有一些类(POJO)没有实现这个接口,并且用注释@Bean进行注释。当出现此注释时,我希望: 创建实现接口StoredOnDatabase的bean代理 为setter添加拦截器,当bean的属性被修改时,我可以使用它来“跟踪” 使用对所有这些bean都有效的泛型equals()和hashCode()方法 我不想改变POJO的类。一个简单的解决
我遗漏了什么吗?我想出了以下解决方案。最后,它完成了我想要的一切,代码也比Spring AOP+CGLIB少(是的,有点神秘):
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.description.modifier.Visibility;
import net.bytebuddy.implementation.FieldAccessor;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.implementation.bind.annotation.Origin;
import net.bytebuddy.implementation.bind.annotation.SuperCall;
import net.bytebuddy.implementation.bind.annotation.This;
import net.bytebuddy.matcher.ElementMatchers;
import org.junit.Before;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.Method;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
public class ByteBuddyTest {
private static final Logger logger = LoggerFactory.getLogger(ByteBuddyTest.class);
private Logger mockedLogger;
@Before
public void setup() {
mockedLogger = mock(Logger.class);
}
public interface ByteBuddyProxy {
public Resource getTarget();
public void setTarget(Resource target);
}
public class LoggerInterceptor {
public void logger(@Origin Method method, @SuperCall Runnable zuper, @This ByteBuddyProxy self) {
logger.debug("Method {}", method);
logger.debug("Called on {} ", self.getTarget());
mockedLogger.info("Called on {} ", self.getTarget());
/* Proceed */
zuper.run();
}
}
public static class ResourceComparator {
public static boolean equalBeans(Object that, @This ByteBuddyProxy self) {
if (that == self) {
return true;
}
if (!(that instanceof ByteBuddyProxy)) {
return false;
}
Resource someBeanThis = (Resource)self;
Resource someBeanThat = (Resource)that;
logger.debug("someBeanThis: {}", someBeanThis.getId());
logger.debug("someBeanThat: {}", someBeanThat.getId());
return someBeanThis.getId().equals(someBeanThat.getId());
}
}
public static class Resource {
private String id;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
@Test
public void useTarget() throws IllegalAccessException, InstantiationException {
Class<?> dynamicType = new ByteBuddy()
.subclass(Resource.class)
.defineField("target", Resource.class, Visibility.PRIVATE)
.method(ElementMatchers.any())
.intercept(MethodDelegation.to(new LoggerInterceptor())
.andThen(MethodDelegation.toField("target")))
.implement(ByteBuddyProxy.class)
.intercept(FieldAccessor.ofField("target"))
.method(ElementMatchers.named("equals"))
.intercept(MethodDelegation.to(ResourceComparator.class))
.make()
.load(getClass().getClassLoader())
.getLoaded();
Resource someBean = new Resource();
someBean.setId("id-000");
ByteBuddyProxy someBeanProxied = (ByteBuddyProxy)dynamicType.newInstance();
someBeanProxied.setTarget(someBean);
Resource sameBean = new Resource();
sameBean.setId("id-000");
ByteBuddyProxy sameBeanProxied = (ByteBuddyProxy)dynamicType.newInstance();
sameBeanProxied.setTarget(sameBean);
Resource someOtherBean = new Resource();
someOtherBean.setId("id-001");
ByteBuddyProxy someOtherBeanProxied = (ByteBuddyProxy)dynamicType.newInstance();
someOtherBeanProxied.setTarget(someOtherBean);
assertEquals("Target", someBean, someBeanProxied.getTarget());
assertFalse("someBeanProxied is equal to sameBean", someBeanProxied.equals(sameBean));
assertFalse("sameBean is equal to someBeanProxied", sameBean.equals(someBeanProxied));
assertTrue("sameBeanProxied is not equal to someBeanProxied", someBeanProxied.equals(sameBeanProxied));
assertFalse("someBeanProxied is equal to Some other bean", someBeanProxied.equals(someOtherBeanProxied));
assertFalse("equals(null) returned true", someBeanProxied.equals(null));
/* Reset counters */
mockedLogger = mock(Logger.class);
String id = ((Resource)someBeanProxied).getId();
@SuppressWarnings("unused")
String id2 = ((Resource)someBeanProxied).getId();
@SuppressWarnings("unused")
String id3 = ((Resource)someOtherBeanProxied).getId();
assertEquals("Id", someBean.getId(), id);
verify(mockedLogger, times(3)).info(any(String.class), any(Resource.class));
}
}
导入net.bytebuddy.bytebuddy;
导入net.bytebuddy.description.modifier.Visibility;
导入net.bytebuddy.implementation.FieldAccessor;
导入net.bytebuddy.implementation.MethodDelegation;
导入net.bytebuddy.implementation.bind.annotation.Origin;
导入net.bytebuddy.implementation.bind.annotation.SuperCall;
导入net.bytebuddy.implementation.bind.annotation.This;
导入net.bytebuddy.matcher.ElementMatchers;
导入org.junit.Before;
导入org.junit.Test;
导入org.slf4j.Logger;
导入org.slf4j.LoggerFactory;
导入java.lang.reflect.Method;
导入静态org.junit.Assert.*;
导入静态org.mockito.mockito.*;
公共类ByteBuddyTest{
私有静态最终记录器Logger=LoggerFactory.getLogger(ByteBuddyTest.class);
私人记录器模拟记录器;
@以前
公共作废设置(){
mockedLogger=mock(Logger.class);
}
公共接口ByteBuddyProxy{
公共资源getTarget();
公共目标(资源目标);
}
公共类日志拦截器{
公共void记录器(@Origin Method、@SuperCall Runnable zuper、@This ByteBuddyProxy self){
debug(“方法{}”,方法);
debug(“在{},self.getTarget()上调用);
info(“调用{}”,self.getTarget());
/*进行*/
zuper.run();
}
}
公共静态类ResourceComparator{
公共静态布尔等式(对象,@This ByteBuddyProxy self){
如果(那=self){
返回true;
}
如果(!(ByteBuddyProxy的实例)){
返回false;
}
资源somebeans=(资源)self;
Resource someBeanThat=(Resource)那;
debug(“somebeans:{}”,somebeans.getId());
debug(“someBeanThat:{}”,someBeanThat.getId());
返回somebeansh.getId().equals(somebeansh.getId());
}
}
公共静态类资源{
私有字符串id;
公共字符串getId(){
返回id;
}
公共无效集合id(字符串id){
this.id=id;
}
}
@试验
public void useTarget()引发IllegaAccessException,实例化Exception{
类dynamicType=newbytebuddy()
.subclass(Resource.class)
.defineField(“目标”、Resource.class、Visibility.PRIVATE)
.method(ElementMatchers.any())
.intercept(MethodDelegation.to)(新的LoggerInterceptor())
第三种方法(MethodDelegation.toField(“目标”))
.implement(ByteBuddyProxy.class)
.截获(FieldAccessor.ofField(“目标”))
.method(ElementMatchers.named(“equals”))
.intercept(MethodDelegation.to(ResourceComparator.class))
.make()
.load(getClass().getClassLoader())
.getLoaded();
Resource someBean=新资源();
setId(“id-000”);
ByteBuddyProxy someBeanProxied=(ByteBuddyProxy)dynamicType.newInstance();
setTarget(someBean);
Resource sameBean=新资源();
sameBean.setId(“id-000”);
ByteBuddyProxy SamebeanProxy=(ByteBuddyProxy)dynamicType.newInstance();
setTarget(sameBean);
Resource someOtherBean=新资源();
setId(“id-001”);
ByteBuddyProxy someOtherBeanProxied=(ByteBuddyProxy)dynamicType.newInstance();
setTarget(someOtherBean);
assertEquals(“目标”、someBean、someBeanProxied.getT
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.description.modifier.Visibility;
import net.bytebuddy.implementation.FieldAccessor;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.implementation.bind.annotation.Origin;
import net.bytebuddy.implementation.bind.annotation.SuperCall;
import net.bytebuddy.implementation.bind.annotation.This;
import net.bytebuddy.matcher.ElementMatchers;
import org.junit.Before;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.Method;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
public class ByteBuddyTest {
private static final Logger logger = LoggerFactory.getLogger(ByteBuddyTest.class);
private Logger mockedLogger;
@Before
public void setup() {
mockedLogger = mock(Logger.class);
}
public interface ByteBuddyProxy {
public Resource getTarget();
public void setTarget(Resource target);
}
public class LoggerInterceptor {
public void logger(@Origin Method method, @SuperCall Runnable zuper, @This ByteBuddyProxy self) {
logger.debug("Method {}", method);
logger.debug("Called on {} ", self.getTarget());
mockedLogger.info("Called on {} ", self.getTarget());
/* Proceed */
zuper.run();
}
}
public static class ResourceComparator {
public static boolean equalBeans(Object that, @This ByteBuddyProxy self) {
if (that == self) {
return true;
}
if (!(that instanceof ByteBuddyProxy)) {
return false;
}
Resource someBeanThis = (Resource)self;
Resource someBeanThat = (Resource)that;
logger.debug("someBeanThis: {}", someBeanThis.getId());
logger.debug("someBeanThat: {}", someBeanThat.getId());
return someBeanThis.getId().equals(someBeanThat.getId());
}
}
public static class Resource {
private String id;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
@Test
public void useTarget() throws IllegalAccessException, InstantiationException {
Class<?> dynamicType = new ByteBuddy()
.subclass(Resource.class)
.defineField("target", Resource.class, Visibility.PRIVATE)
.method(ElementMatchers.any())
.intercept(MethodDelegation.to(new LoggerInterceptor())
.andThen(MethodDelegation.toField("target")))
.implement(ByteBuddyProxy.class)
.intercept(FieldAccessor.ofField("target"))
.method(ElementMatchers.named("equals"))
.intercept(MethodDelegation.to(ResourceComparator.class))
.make()
.load(getClass().getClassLoader())
.getLoaded();
Resource someBean = new Resource();
someBean.setId("id-000");
ByteBuddyProxy someBeanProxied = (ByteBuddyProxy)dynamicType.newInstance();
someBeanProxied.setTarget(someBean);
Resource sameBean = new Resource();
sameBean.setId("id-000");
ByteBuddyProxy sameBeanProxied = (ByteBuddyProxy)dynamicType.newInstance();
sameBeanProxied.setTarget(sameBean);
Resource someOtherBean = new Resource();
someOtherBean.setId("id-001");
ByteBuddyProxy someOtherBeanProxied = (ByteBuddyProxy)dynamicType.newInstance();
someOtherBeanProxied.setTarget(someOtherBean);
assertEquals("Target", someBean, someBeanProxied.getTarget());
assertFalse("someBeanProxied is equal to sameBean", someBeanProxied.equals(sameBean));
assertFalse("sameBean is equal to someBeanProxied", sameBean.equals(someBeanProxied));
assertTrue("sameBeanProxied is not equal to someBeanProxied", someBeanProxied.equals(sameBeanProxied));
assertFalse("someBeanProxied is equal to Some other bean", someBeanProxied.equals(someOtherBeanProxied));
assertFalse("equals(null) returned true", someBeanProxied.equals(null));
/* Reset counters */
mockedLogger = mock(Logger.class);
String id = ((Resource)someBeanProxied).getId();
@SuppressWarnings("unused")
String id2 = ((Resource)someBeanProxied).getId();
@SuppressWarnings("unused")
String id3 = ((Resource)someOtherBeanProxied).getId();
assertEquals("Id", someBean.getId(), id);
verify(mockedLogger, times(3)).info(any(String.class), any(Resource.class));
}
}