Generate parameter annotations in Byte Buddy - java

I would like to use ByteBuddy to generate simple interfaces like this:
public interface MyInterface {
void myMethod(#Deprecated String myDeprecatedParameter);
}
This is just an example, but the point is that the parameters of the methods need a number of custom annotations.
Does anyone have a simple example that would demonstrate how to achieve this in ByteBuddy?

You can create an interface with an annotated parameter like the following. First define the interface name and modifiers, then define the method with it's name, return type and modifiers and finally the parameters and annotations if have any.
Class<?> myInterface = new ByteBuddy()
.makeInterface()
.name("MyInterface")
.modifiers(Visibility.PUBLIC, TypeManifestation.ABSTRACT)
.defineMethod("myMethod", void.class, Visibility.PUBLIC)
.withParameter(String.class, "myDeprecatedParameter")
.annotateParameter(AnnotationDescription.Builder.ofType(Deprecated.class)
.build())
.withoutCode()
.make()
.load(this.getClass().getClassLoader())
.getLoaded();
You can call annotateParameter(...) many times if you need multiple annotations.
After the make() method you get the unloaded class, just load the class and use it.
Here are some prints with the reflection api of the interface class.
System.out.println(Modifier.toString(myInterface.getModifiers())); // public abstract interface
System.out.println(myInterface.getSimpleName()); // MyInterface
System.out.println(Arrays.toString(myInterface.getDeclaredMethods())); // [public abstract void MyInterface.myMethod(java.lang.String)]
Method method = myInterface.getDeclaredMethod("myMethod", String.class);
System.out.println(method.getName()); // myMethod
System.out.println(Arrays.toString(method.getParameters())); // [java.lang.String myDeprecatedParameter]
Parameter parameter = method.getParameters()[0];
System.out.println(parameter); // java.lang.String myDeprecatedParameter
System.out.println(parameter.getName()); // myDeprecatedParameter
System.out.println(Arrays.toString(parameter.getAnnotations())); // [#java.lang.Deprecated()]
Annotation annotation = parameter.getAnnotations()[0];
System.out.println(annotation); // #java.lang.Deprecated()

Related

Annotation not getting copied to getter and setter

So, I have this annotation class
#Target(AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER, AnnotationTarget.PROPERTY)
#Retention(AnnotationRetention.RUNTIME)
annotation class MyTestAnnotation(val text: String)
And I'm using this like this
interface MyTestInterface {
#MyTestAnnotation("temp")
var tempString: String
}
This is how I'm instantiating my interface using reflection.
fun <T> with(myInterface: Class<T>): T {
return Proxy.newProxyInstance(
myInterface.classLoader,
arrayOf<Class<*>>(myInterface),
invocationHandler
) as T
}
private val invocationHandler = InvocationHandler { _, method, args ->
Log.e("Called method:", method.name) // setTempString
Log.e("declaredAnnotations", method.declaredAnnotations.size.toString()) // 0
Log.e("annotations", method.annotations.size.toString()) // 0
Log.e("args", args.size.toString()) // 1
}
I'm calling it like this
val myInterface = with(MyTestInterface::class.java)
myInterface.tempString = "123"
I'm not able to access the member text of the annotation class because in my invocationHandler I'm not getting the annotation(as you can see both the arrays are of length zero).
My question: Is there any way I can copy the annotation to the getter and setter so I can get access to the data which I put in the annotation?
You can specify the use-site target of the annotation. For example, if you want to annotate both the setter and the getter you can do:
#set:YourAnnotation
#get:YourAnnotation
val aProperty: AType
Official documentation: https://kotlinlang.org/docs/annotations.html#annotation-use-site-targets

Implementing generated interface with JavaPoet

I would like to use JavaPoet to generate an interface and a class implementing this interface.
TypeSpec if = TypeSpec.interfaceBuilder("MyInterface")
.build();
TypeSpec cl = TypeSpec.classBuilder("MyClass")
.build();
But I am strugelling to tell JavaPoet that MyClass should implement MyInterface. The method addSuperinterface(TypeName) requires a type name and I didn't findout how to turn a TypeSpec into an TypeName. The only way I found is calling ClassName#get(String, String).
Is there an better way to achive this and to use the type specification for the interface directly?
It is not as complicated as it may seem.
The TypeSpec.Builder has two versions of the addSuperInterface method:
TypeSpec.Builder addSuperinterface(Type superinterface)
TypeSpec.Builder addSuperinterface(TypeName superinterface)
We could use the second version for example and obtain the super interface as an instance of the TypeName class using ClassName.get
One of the signatures of the get method of the ClassName class is:
public static ClassName get(String packageName, String simpleName, String... simpleNames)
So we could use it with empty string for the package name since you did not specify any package name in your interface spec. It will work because ClassName extends TypeName.
On the other hand we can obtain the interface's simple name using the type spec's name property.
Here a complete sample implementation. I modified the name of the variables (the variable name if that you used for the interface spec will not work as it is a java keyword).
#Data
public class SimpleClassSpecs {
public final TypeSpec interfaceSpec;
public final TypeSpec classSpec;
public SimpleClassSpecs() {
interfaceSpec = TypeSpec.interfaceBuilder("MyInterface")
.build();
TypeName interfaceTypeName = ClassName.get("", interfaceSpec.name);
classSpec = TypeSpec.classBuilder("MyClass")
.addSuperinterface(interfaceTypeName)
.build();
}
}
I used Lombok's #Data for the boilerplate code (getters and setters...)
Here is a corresponding test (assertion written with assertj):
#Test
public void should_generate_spec_with_superInterface() {
SimpleClassSpecs ps = new SimpleClassSpecs();
assertThat(ps.classSpec.toString()).contains("class MyClass implements MyInterface");
}
Or by simply doing doing a System.out.println(ps.classSpec), one can obtain the following result:
class MyClass implements MyInterface {
}

How to intercept method with ByteBuddy as in CGLIB with MethodInterceptor for calling MethodProxy.invokeSuper(...)

I want to intercept some methods with ByteBuddy.
When i use InvocationHandlerAdapter.of(invocationHandler) i can't invoke super method. Holding object instance is not suitable for my case. I want exactly as in CGLIB like below.
(MethodInterceptor)(obj, method, args, proxy)->{
// to do some work
Object o = proxy.invokeSuper(obj,args);
// to do some work
return o;
}
How can i achieve intercepting method in ByteBuddy like this?
I tried MethodCall type of Implemetation but it not solved my problem. Because i can't manage MethodCall.invokeSuper() in this case.
.intercept(MethodCall
.run(() -> System.out.println("Before"))
.andThen(MethodCall.invokeSuper())
.andThen(MethodCall
.run((() -> System.out.println("After")))))
Have a look at MethodDelegation, for example:
public class MyDelegation {
#RuntimeType
public static Object intercept(#SuperCall Callable<?> superCall) throws Exception {
// to do some work
Object o = superCall.call();
// to do some work
return o;
}
}
Then using:
.intercept(MethodDelegation.to(MyDelegation.class))
You can check the javadoc for MethodDelegation for more annotations that you can use to inject contextual information.

Javapoet superclass generic

Anyone know how I can do the following using javapoet
public class MyClassGenerated extends MyMapper<OtherClass>{
}
My code of generation:
TypeSpec generateClass() {
return classBuilder("MyClassGenerated")
.addModifiers(PUBLIC)
.superclass(???????????????)
.build();
}
The ParameterizedTypeName class allows you to specify generic type arguments when declaring the super class. For instance, if your MyClassGenerated class is a subclass of the MyMapper class, you can set a generic type parameter of MyMapper like so:
TypeSpec classSpec = classBuilder("MyClassGenerated")
.addModifiers(PUBLIC)
.superclass(ParameterizedTypeName.get(ClassName.get(MyMapper.class),
ClassName.get(OtherClass.class)))
.build();
This will generate a TypeSpec object that is equivalent to the following class:
public class MyClassGenerated extends MyMapper<OtherClass> { }
While not specified in the question, note that you can set any number of generic type arguments by simply adding them in the correct order to the ParameterizedTypeName.get call:
ParameterizedTypeName.get(
ClassName.get(SuperClass.class),
ClassName.get(TypeArgumentA.class),
ClassName.get(TypeArgumentB.class),
ClassName.get(TypeArgumentC.class)
); // equivalent to SuperClass<TypeArgumentA, TypeArgumentB, TypeArgumentC>
For more information about the ParameterizedTypeName.get() method, see the documentation here or the "$T for Types" section of the JavaPoet GitHub page.

How to add a field to a class in ByteBuddy and set / get that value in a method interceptor

I am using byte-buddy to build an ORM on top of Ignite, we need to add a field to a class and then access it in a method interceptor..
So here's an example where I add a field to a class
final ByteBuddy buddy = new ByteBuddy();
final Class<? extends TestDataEnh> clz = buddy.subclass(TestDataEnh.class)
.defineField("stringVal",String.class)
.method(named("setFieldVal")).intercept(
MethodDelegation.to(new SetterInterceptor())
)
.make()
.load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
.getLoaded();
final TestDataEnh enh = clz.newInstance();
enh.getFieldVal();
enh.setFieldVal();
System.out.println(enh.getClass().getName());
And the Interceptor is like this
public class SetterInterceptor {
#RuntimeType
public Object intercept() {
System.out.println("Invoked method with: ");
return null;
}
}
So how do I get the value of the new field into the interceptor so I can change it's value? (stringVal)
Thanks in advance
You can use a FieldProxy to access a field by its name. You need to install a FieldProxy.Binder and register it on the MethodDdelegation before you can use it as it requires a custom type for type-safe instrumentation. The javadoc explains how this can be done. Alternatively, you can use reflection on an instance by using #This. The JVM is quite efficient in optimizing the use of reflection.
An example would be:
interface FieldGetter {
Object getValue();
}
interface FieldSetter {
void setValue(Object value);
}
public class SetterInterceptor {
#RuntimeType
public Object intercept(#FieldProxy("stringVal") FieldGetter accessor) {
Object value = accessor.getValue();
System.out.println("Invoked method with: " + value);
return value;
}
}
For bean properties, the FieldProxy annotation does not require an explicit name but discovers the name from the name of the intercepted getter or setter.
The installation can be done as follows:
MethodDelegation.to(SetterInterceptor.class)
.appendParameterBinder(FieldProxy.Binder.install(FieldGetter.class,
FieldSetter.class));

Categories

Resources