简体   繁体   中英

How can we access methods generated by ByteBuddy in compilation time?

I wrote this example:

E someCreateMethod(Class<E> clazz) {
    Class<? extends E> dynamicType = new ByteBuddy()
            .subclass(clazz)
            .name("NewEntity")
            .method(named("getNumber"))
            .intercept(FixedValue.value(100))
            .defineField("stringVal", String.class, Visibility.PRIVATE)
            .defineMethod("getStringVal", String.class, Visibility.PUBLIC)
            .intercept(FieldAccessor.ofBeanProperty())
            .make()
            .load(clazz.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
            .getLoaded();

    return dynamicType.newInstance();
}

And I would like to use it to get the redefined number atributte:

Integer num = someCreateMethod(EntityExample.class).getNumber();  //(1)

Or to get the newly defined stringVal attribute:

String sVal = someCreateMethod(EntityExample.class).getStringVal(); //(2)  

My problem is that (1) works pretty fine, while (2) doesn't. I get the following error:

Error:(40, 67) java: cannot find symbol

symbol:   method getStringVal()

Also, is it possible to do something like this with a dynamic generated class:

NewEntity newEntity = someCreateMethod(EntityExample.class);
Integer num = newEntity.getNumber();
String sVal = newEntity.getStringVal();

?

EDIT: I appreciate your help, this example was my first attempt on using ByteBuddy library. I figured that defineMethod actually defines an implementation of an interface method, not just add a random method to the class. So I decided to explain here what exactly I'm trying to accomplish.

For every Date attribute in a class E, I want to add two more fields (and theirs respectives getters and setters), let's say (atribute name)InitialDate and (atribute name)FinalDate , so that I can use intervals functinality for every date in E .

I was wondering if I could use code-generation to add those methods without having to create subclasses for every E .

PS: E can't be changed, it belongs to a legacy module.

PS2: I don't know how many date attributes there would be in each entity E , but the new attibutes and methods would be created using conventions (for example __FisrtDay , __LastDay ), as shown below:

NewA a = eb.create(A.class);
a.getDeadLine(); //inherited
a.getDeadLineFirstDay(); //added 
a.getDeadLineLastDay(); //added

NewA b = eb.create(B.class);
b.getBirthday(); //inherited
b.getBirthdayFirstDay(); //added
b.getBirthdayLastDay(); //added

b.getAnniversary(); //inherited
b.getAnniversaryFirstDay(); //added
b.getAnniversaryLastDay(); //added

PS3: Is what I'm trying to accomplish even possible with ByteBuddy or at all? Is there another way?

PS4: Should my EDIT have been a new question?

You need E to be a superclass/ or interface which includes the methods you are trying to call -- you will not be able to resolve subtyped methods which do not exist on E.

This is not a ByteBuddy issue, this is an issue of your class design -- you should design & group the functionality you intend to generate into abstractable parts, so it can be exposed via types which are meaningful at compile time.

For example, we could use a supertype 'ValueProvider' and then use ByteBuddy to define an IntConstantProvider.

public interface ValueProvider<T> {
    public T getValue();
}

Class<? extends ValueProvider<Integer>> dynamicType = new ByteBuddy()
    .subclass(clazz)
    .name("ConstantIntProvider")
    .method(named("getValue"))
    .intercept(FixedValue.value(100))
    // etc.

Your prototype had 3 separate functionalities (if we consider unreference private fields to be the stub of some intended behavior) with no obvious abstraction to encompass them. This could be better designed as 3 simple atomic behaviors, for which the abstractions would be obvious.

You could use reflection to find arbitrary methods on a arbitrary dynamically-defined class, but this is not really meaningful from a coding or design POV (how does your code know which methods to call? if it does know, why not use a type to express that?) nor is it very performant.

FOLLOWING EDIT TO QUESTION -- Java Bean properties work by reflection, so the example of finding "related properties" (such as First/ Last Date) from known properties is not unreasonable.

However it could be considered to use a DateInterval( FirstDate, LastDate) class so that only one supplementary property is needed per- base property.

As Thomas points out, Byte Buddy generates classes at runtime such that your compiler cannot validate their existance during compile time.

What you can do is to apply your code generation at build time. If your EntityExample.class exists in a specific module, you can enhance this module with the Byte Buddy Maven or Gradle plugin and then, after enhancement, allow your compiler to validate their existance.

What you can also do would be to define interfaces like

interface StringVal {
  String getStringVal();
}

which you can ask Byte Buddy to implement in your subclass which allows your compiler to validate the method's existance if you represent your subclass as this interface.

Other than that, your compiler is doing exactly what it is supposed to do: telling you that you are calling a method that does not exist (at that time).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM