byte-buddy icon indicating copy to clipboard operation
byte-buddy copied to clipboard

Subclassing with generics

Open adelavina opened this issue 3 years ago • 5 comments

Hey, I've got the following issue when subclassing typed class. The idea behind it is to create proper Spring MVC controller classes.

Base Entity:

@MappedSuperclass
public abstract class RegistrableEntity {
    @Id @GeneratedValue protected Long id;
}

Base Controller:

@RequiredArgsConstructor
public class RegistrableController<K extends RegistrableEntity>  {

  protected final AbstractBaseRepository<K> repository;

  @Transactional
  public ResponseEntity<K> save(K entity) {
    return new ResponseEntity<K>(repository.save(entity), HttpStatus.CREATED);
  }

  public ResponseEntity<List<K>> findAll() {
    return new ResponseEntity<List<K>>(repository.findAll(), HttpStatus.OK );
  }
}

Specific domain class:

public class Customer extends RegistrableEntity {

  protected String name;
}

Specific controller generation:

@Test
    public void testGeneric() {
        Class clazz = Customer.class;
        Class<? extends RegistrableController<?>> controllerClazz =
            (Class<? extends RegistrableController<?>>)
                new ByteBuddy()
                    .subclass(
                        TypeDescription.Generic.Builder.parameterizedType(RegistrableController.class, clazz)
                            .build(),
                        ConstructorStrategy.Default.IMITATE_SUPER_CLASS_OPENING)
                    .name("com.example.controller.CustomerController").merge()
                    .make()
                    .load(GenericConfigurationTest.class.getClassLoader(), ClassLoadingStrategy.Default.INJECTION)
                    .getLoaded();
        System.out.println(controllerClazz.getMethods()[1].getName()+ " ("+controllerClazz.getMethods()[1].getParameters()[0].getType()+")");
    }

My problem seems to be that the K generic is not being properly replaced upon inheritance and the resulting line is:

save (class com.library.entitities.RegistrableEntity)

While it should be:

save (class com.library.entitities.Customer)

Is the K type erasure being lost at some point? Through ByteBuddy should I be rewriting this method's arguments with the specific entity instead?

Thanks for the great work being done here.

adelavina avatar Aug 23 '22 07:08 adelavina

There shoukd be both, a bridge method with the erasure and another method with the substituted type. Did you iterate over the declared methods?

raphw avatar Aug 23 '22 07:08 raphw

  Arrays.stream(controllerClazz.getMethods())
        .map(m -> m.getName() + " (" + Arrays.stream(m.getParameters()).map(Parameter::getType).collect(Collectors.toList())+")").forEach(System.out::println);

Results in:

findAll ([])
save ([class com.library.entitities.RegistrableEntity])
wait ([long])
wait ([long, int])
wait ([])
equals ([class java.lang.Object])
toString ([])
hashCode ([])
getClass ([])
notify ([])
notifyAll ([])

save is there only once with the superclass and not the actual K entity

adelavina avatar Aug 23 '22 08:08 adelavina

But you are not overwriting the method, are you? In this case, the reflection API will pick up the super method. Note that the reflection API is not aware of generic inheritance as the runtime assumes type erasure.

raphw avatar Aug 23 '22 20:08 raphw

I'm not, simply subclassing with the Generic spec.

Understood your point. Any tip or guide on how to substitute the methods parameter and return type to match the expected behaviour of Generics inference?

Cheers for your help and your work in general.

adelavina avatar Aug 23 '22 22:08 adelavina

You can override the method and simply call the super method (SuperMethodCall.INSTANCE), then it should declare it.

raphw avatar Aug 24 '22 14:08 raphw