2017-12-04 15 views
1

ByteBuddy가 해당 빌더에 대한 인터페이스가있는 단계 빌더를 구현하려고합니다. 나는 2 개의 장소에 붙이게된다.ByteBuddy로 스텝 빌더 생성하기

  1. 메서드 체인을위한 현재 인스턴스를 반환하는 setter를 만드는 방법은 무엇입니까?

내가 시작 : 난 그렇게 현재 빌더 인스턴스를 반환 싶습니다에만

.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. 리플렉션없이 정의하는 클래스의 정의 된 필드에 쉽게 액세스 할 수 있습니까?

은 현재 내가 루프에서 빌더 클래스에 필드를 추가하고 빌더 내 빌드 방법은 다음과 같이 차단된다

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 annoation을 보았다. 나는 그들의 이름을 알지 못하고 모든 분야를 알려줄 무언가가 있다고 생각하지 않습니까?

코드는 현재 개념 증명입니다. 내가 여기서하고있는 일을하는 더 좋은 방법이 있습니까?
감사합니다.

답변

2

당신은 두 가지 구현을 구성 할 수 있습니다

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

이 첫 번째 (인덱스 0) 인수 세터를 설정 한 다음 this를 반환합니다.

필드를 반사없이 MethodDelegation에서 설정하려면 FieldProxy을보십시오.

+0

고맙습니다! 나는 이것을 간단한 콩 세터와 함께 사용할 수있게했다. "빌드"를 구현하는 가장 좋은 방법은 무엇입니까? 나는 방금 설정 한 비공개 필드에 액세스해야한다. return new Something (this.a, this.b, this.c); 물론 클래스 작성자이기 때문에 작성자도 모두 생성됩니다. 생성자는 입니다. – akagixxer

+0

MethodCall 구현을 사용하여 생성자를 호출 할 수 있습니다. –

+0

필자가 필요한 "MethodCall.construct (...)"을 발견했습니다. 이것은 훌륭한 도구입니다, 고마워요! – akagixxer

0

라파엘 (Rafael)은 해결책을 찾는데 필요한 정보를 제공 했으므로 답 신용이 그에게 돌아 갔지만 나중에 다른 사람이 발견 할 수있는 솔루션을 포함하고 싶습니다.

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()])));