使用ByteBuddy创建Step Builder

时间:2017-12-04 22:14:40

标签: java byte-buddy

我试图让ByteBuddy实现一个步骤构建器,给定该构建器的接口。我被困在2个地方。

  1. 如何创建一个返回方法链的当前实例的setter?
  2. 我开始时:

    .method(ElementMatchers.isSetter())
    .intercept(FieldAccessor.ofBeanProperty());
    

    只有我想返回当前的构建器实例,以便我们可以链接调用,如:

    final Object obj = ...builder().id(100).name("test").build();
    

    所以我创建了一个像这样的拦截器,这似乎是一个黑客,我想尽可能避免反射:

    @RuntimeType
    public Object intercept(@RuntimeType Object arg, @This Object source, @Origin Method method)
    {
      try
      {
        // Field name is same as method name.
        final Field field = source.getClass().getDeclaredField(method.getName());
        field.setAccessible(true);
        field.set(source, arg);
      }
      catch (Throwable ex)
      {
        throw new Error(ex);
      }
    
      // Return current builder instance.
      return source;
    }
    
    1. 是否有一种简单的方法可以访问I类定义的字段而不进行反射?
    2. 目前我在循环中向构建器类添加字段,并且构建器上的构建方法被截取如下:

      private static final class InterBuilder
      {
        private final Collection<String> fields;
        private final Constructor<?> constructor;
      
        InterBuilder(final Constructor<?> constructor, final Collection<String> fields)
        {
          this.constructor = constructor;
          this.fields = fields;
        }
      
        @RuntimeType
        public Object intercept(@This Object source, @Origin Method method)
        {
          try
          {
            final Object[] args = Arrays.stream(source.getClass().getDeclaredFields())
              .filter(f -> this.fields.contains(f.getName()))
              .map(f ->  { try {
                f.setAccessible(true);
                return f.get(source); }
                catch (Throwable ex) { throw new Error(ex); } })
              .toArray();
      
            // Invoke a constructor passing in the private field values from the builder...
            return this.constructor.newInstance(args);
          }
          catch (Throwable ex)
          {
            throw new Error(ex);
          }
        }
      }
      

      我看到了@FieldValue年份。我不认为有些东西可以在不知道自己名字的情况下给我所有的领域?

      此时代码是一个概念证明。有没有更好的方法来做我在这里做的事情?
      谢谢!

2 个答案:

答案 0 :(得分:2)

您可以撰写两个实现:

FieldAccessor.ofBeanProperty().setsArgumentAt(0).andThen(FixedValue.self());

这将首先设置setter(索引0)参数,然后返回this

如果您想在没有反射的情况下设置MethodDelegation的字段,请查看FieldProxy

答案 1 :(得分:0)

拉斐尔给了我提供解决方案所需的信息,所以答案归功于他,但我希望将我的解决方案包括在其他人中以便将来发现。

DynamicType.Builder<?> builder = new ByteBuddy()
  .subclass(Object.class)
  .implement(interfaces)
  .name(builderClassName);

// Find all of the setters on the builder...  
// Here I'm assuming all field names match setter names like:
//   MyClass x = theBuilder.name("hi").id(1000).isValid(true).build();
final List<Method> setters = ...

for (final Method setter : setters)
{
  // This will define a field and a setter that will set the value and return the current instance.
  builder = builder
    .defineField(setter.getName(), setter.getParameterTypes()[0], Visibility.PRIVATE)
    .define(setter)
    .intercept(FieldAccessor.ofField(setter.getName()).setsArgumentAt(0).andThen(FixedValue.self()));
}

// Find the "build" method on the builder.
final Method buildMethod = ...

// Get a constructor that you want the builder to call and return the new instance.
final Constructor<?> constructor = ...

// Get the field names from the setters.
final List<String> fieldNames = setters.stream()
  .map(Method::getName)
  .collect(Collectors.toList());

// This will define a "build" method that will invoke the constructor of some object and
// pass in the fields (in order) of the builder to that constructor.
builder = builder
  .define(buildMethod)
  .intercept(MethodCall.construct(constructor)
    .withField(fieldNames.toArray(new String[fieldNames.size()])));