问题
lets asume i have a Interface like that:
public interface User extends Element {
String getName();
String getPassword();
}
and a implementing class like that:
public class BaseUser implements User {
@Override
public String getId() {
return id;
}
@Override
public String getName() {
return name;
}
@Override
public String getPassword() {
return password;
}
public void setId(String id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
System.out.println("Set name to " + name);
}
public void setPassword(String password) {
this.password = password;
}
private String id;
private String name;
private String password;
}
Now i want to use bytebuddy to create a interceptor/proxy which catches the call onto the setter, store the changed value and call the real method also.
At the end i want to "ask" the interceptor/proxy for the called setter and the changed values.
I tried a lot considering also the tutorials but up to now i found no working solution. Maybe someone could help me pls.
And here is the Interceptor:
public class GenericInterceptor implements InvocationHandler {
@Override
@RuntimeType
public Object invoke(@This Object proxy, @Origin Method method, @AllArguments Object[] args) throws Throwable {
if (isSetter(method, args)) {
intercept(proxy, method, args);
}
return method.invoke(proxy, args);
}
}
Here is my current 'test' code:
public static void main(String[] args) {
final ByteBuddy bb = new ByteBuddy();
final GenericInterceptor interceptor = new GenericInterceptor();
bb.subclass(BaseUser.class)
.method(isDeclaredBy(BaseUser.class).and(isSetter()))
.intercept(MethodDelegation.to(interceptor))
.make()
.load(BaseUser.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
final BaseUser user = new BaseUser();
user.setName("my name");
}
EDIT:
public interface Element {
String getId();
}
public class GenericInterceptor<T extends Element> {
public GenericInterceptor(Class<T> type) {
this.type = type;
}
public Map<String, Object> getChanges(T obj) {
final String id = obj.getId();
return changes.get(id);
}
@RuntimeType
public void invoke(@This T proxy, @Origin Method method, @AllArguments Object[] args) throws Throwable {
System.out.println("invoke " + method.getName() + " " + Arrays.toString(args));
intercept(proxy, method, args);
}
private Object getCurrentValue(T proxy, final Field field) {
try {
return field.get(proxy);
} catch (IllegalArgumentException | IllegalAccessException e) {
return null;
}
}
private Field getSetterField(Method setter) {
final String setterName = setter.getName();
Field f = assignedFields.get(setterName);
if (f != null) return f;
final String fieldName = Character.toLowerCase(setterName.charAt(3)) + setterName.substring(4);
try {
f = type.getDeclaredField(fieldName);
if (f == null) return null;
f.setAccessible(true);
assignedFields.put(setterName, f);
return f;
} catch (NoSuchFieldException | SecurityException e) {
return null;
}
}
private void intercept(T proxy, Method setter, Object[] args) {
final Field field = getSetterField(setter);
if (field == null)
return;
final Object currentValue = getCurrentValue(proxy, field);
final Object newValue = args[0];
System.out.println("Set from " + currentValue + " to " + newValue);
final String id = proxy.getId();
Map<String, Object> changeMap = changes.get(id);
if (changeMap == null) {
changeMap = new HashMap<>();
}
changeMap.put(field.getName(), currentValue);
changes.put(id, changeMap);
}
private final Map<String, Field> assignedFields = new HashMap<>();
private final Map<String, Map<String, Object>> changes = new LinkedHashMap<>();
private final Class<T> type;
}
回答1:
You can call orignal method using MethodDelegation.to(...).andThen(SuperMethodCall.INSTANCE).
public class ByteBuddyTest {
public static void main(String[] args) throws IllegalAccessException, InvocationTargetException, InstantiationException {
GenericInterceptor interceptor = new GenericInterceptor ();
Class<?> clazz = new ByteBuddy()
.subclass(BaseUser.class)
.method(ElementMatchers.isDeclaredBy(BaseUser.class).and(ElementMatchers.isSetter()))
.intercept(SuperMethodCall.INSTANCE.andThen(MethodDelegation.to(interceptor))))
.make()
.load(ByteBuddyTest.class.getClassLoader())
.getLoaded();
BaseUser user1 = (BaseUser) clazz.getConstructors()[0].newInstance();
BaseUser user2 = (BaseUser) clazz.getConstructors()[0].newInstance();
user1.setName("user1");
user1.setPassword("password1");
user2.setName("user2");
user2.setPassword("password2");
System.out.println(interceptor.getInterceptedValue("user1", "name"));
System.out.println(interceptor.getInterceptedValue("user1", "password"));
System.out.println(interceptor.getInterceptedValue("user2", "name"));
System.out.println(interceptor.getInterceptedValue("user2", "password"));
user1.setPassword("password2");
user1.setPassword("password3");
}
public static class GenericInterceptor {
private Map<String, Object> interceptedValuesMap = new HashMap();
public void set(String obj, @This User user, @Origin Method setter) {
// assume that user name is unique so we can use it as a key in values map.
// or define equals/hashcode in GenericUser object and use it as a key directly
String setterName = setter.getName();
String propertyName = setterName.substring(3, setterName.length()).toLowerCase();
String key = user.getName() + "_" + propertyName;
System.out.println("Setting " + propertyName + " to " + obj);
System.out.println("Previous value " + interceptedValuesMap.get(key));
interceptedValuesMap.put(key, obj);
}
public Object getInterceptedValue(String userName, String fieldName) {
return interceptedValuesMap.get(userName + "_" + fieldName);
}
}
public static interface User {
String getName();
String getPassword();
}
public static class BaseUser implements User {
@Override
public String getName() {
return name;
}
@Override
public String getPassword() {
return password;
}
public void setName(String name) {
this.name = name;
}
public void setPassword(String password) {
this.password = password;
}
private String name;
private String password;
}
}
来源:https://stackoverflow.com/questions/47104098/using-bytebuddy-to-intercept-setter