当前位置: 首页 > 知识库问答 >
问题:

使用Bytebuddy拦截setter

应子真
2023-03-14

让我有一个这样的界面:

public interface User extends Element {

    String getName();

    String getPassword();

}

还有这样一个实现类:

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;

}

现在我想使用bytebuddy创建一个拦截器/代理,它捕捉对setter的调用,存储更改后的值,并调用real方法。

最后,我想向拦截器/代理“询问”被调用的setter和更改的值。

我尝试了很多考虑也教程,但到目前为止,我发现没有工作的解决方案。也许有人可以帮助我。

这是拦截器:

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);
    }

}

以下是我当前的“测试”代码:

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");
}

编辑:

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个答案

逑阳泽
2023-03-14

可以使用MethodDelegation调用orignal方法。到(…)。然后(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;

  }
}
 类似资料:
  • 我一直在使用字节好友来监控应用程序的行为,我想在执行特定方法之前检查其中一个应用程序类的数组字段是否已更新。我已经阅读了字节好友留档和堆栈溢出问题,并找到了一些有用的留档,了解如何使用拦截字段访问。 然而,因为我感兴趣的领域是一个数组,中的和事件似乎无关紧要。 是否可以使用ByteBuddy跟踪数组字段的更新?

  • 我正在尝试使用bytebuddy拦截和访问。我已经阅读了网站上相当全面的文档,但据我所知,它涵盖了向字段添加getter和setter,而不是拦截字段访问。 以下是我想做的: 在这两种情况下,我都在尝试在字段访问之前/之后调用一些方法(或插入一些字节码)。我正在考虑使用来做到这一点,但我无法找到一种方法将其用于方法以外的东西。 编辑: 我正在使用Java代理来做这件事。我想添加一个来复制对象引用,

  • 让我们有一个简单的界面: 现在我这样创建代理: 当我执行测试时,我看到以下输出: 似乎我永远无法访问被调用方法的方法签名,如果它起源于接口,并且不是由超类实现的?! 当我调用我会看到,它也接受所有输入参数。 我错过了什么?我看了留档,但我仍然很困惑。 一般来说,我想创建,它捕获代理类中的所有方法,并处理以下情况: 执行一些操作,然后将调用传播到原始方法 做某事并返回它自己的结果,而不调用原始meh

  • 如果我有课,说: ...我想使用Java代理拦截“某种方法”,所以我做了一些事情: 在截取方法中,如何使用ByteBuddy访问类Foo的“name”字段? ByteBuddy是否可以公开这个私有变量供我检查(或者修改,但对于我的用例,只读是可以的)?

  • 我想忽略对源类和目标对象抽象的方法的检测,但是如果它只是源的抽象方法,那么我想在目标对象上调用等价的方法。目前我正在使用这个 但是得到这些类型的异常 在源类和目标对象中都没有实现的抽象方法失败并出现以下错误。 无法调用公共抽象java.lang.Objectjava.sql.Wraper.unwrap(java.lang.Class)throws java.sql。公共静态易失性java.lang

  • Spring的处理器映射机制包含了处理器拦截器。拦截器在你需要为特定类型的请求应用一些功能时可能很有用,比如,检查用户身份等。 处理器映射处理过程配置的拦截器,必须实现 org.springframework.web.servlet包下的 HandlerInterceptor接口。这个接口定义了三个方法: preHandle(..),它在处理器实际执行 之前 会被执行; postHandle(..