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
Related
I am making a library,
In one of the functionality, I receive an object and I have to perform an operation on fields and save them on a Map.
The object can have a field of type custom class which will again have fields and in that case, I'll need a nested hashmap. To do that I'll need to call my function recursively if the type of field in a custom class.
Now the problem is, how will I check if the type of field is a custom class or not, right now I am doing it by package name but have to make it general
private fun getAllFields(`object`: Any): MutableMap<String, Any> {
val map: MutableMap<String, Any> = HashMap()
val internalMap: MutableMap<String, Any> = HashMap()
for (field in `object`::class.java.declaredFields.toMutableList()) {
field.isAccessible = true
if (field.type.name.contains("com.example")) {
internalMap.putAll(getAllFields(field.get(`object`)))
}
As you're using reflection, you could introduce an annotation:
package org.your.library
#Target(AnnotationTarget.CLASS)
#Retention(AnnotationRetention.RUNTIME)
annotation class YourCoolAnnotation
Which then can be used by the users of your library to annotate the classes that they want to be nested:
package com.example.libraryuser
#YourCoolAnnotation
class MyCustomClass
Then you can replace your:
if (field.type.name.contains("com.example")) {
With:
if (field.type.isAnnotationPresent(YourCoolAnnotation::class.java)) {
You could also specify the annotation to be only used on fields which would make this a lot more dynamic:
#Target(AnnotationTarget.PROPERTY)
#Retention(AnnotationRetention.RUNTIME)
annotation class YourCoolAnnotation
and then check if the field has the annotation and not the type itself:
if (field.isAnnotationPresent(YourCoolAnnotation::class.java)) {
I want to mark a field of a class with my custom annotation. And whenever any method is invoke I want to do some modification on that field.
public class Message{
public Integer id;
#FreeText // this is my custom annotation
public String htmlMsg;
public String textMsg ;
}
This annotation (#FreeText) can be used in any class.
In seasar framework, I can do this by create an interceptor and override invoke method. The I can get the object of this class and the find the field that marked with my annotation and modify it. However, i cannot find a way to do it in Spring.
In spring, I found some method like MethodInvocationInterceptor, but I don't know how to implement it. Can you suggest any way to do this in Spring?
Seasar2 and Spring are very close. I have not tested but you can do something like this.
First create FreeText custom annotation
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.FIELD)
#Documented
public #interface FreeText {}
Then create the following interceptor
public class EncryptSensitiveDataInterceptor extends MethodInterceptor {
#Override
public Object invoke(MethodInvocation in) throws Throwable {
Object[] params = in.getArguments();
Object param = params[0];
for (Field field : param.getClass().getDeclaredFields()) {
for (Annotation anno : field.getDeclaredAnnotations()) {
if (anno instanceof FreeText) {
field.set(param, [YOUR CUSTOM LOGIC METHOD]);
}
}
}
return in.proceed();
}
Hope this help.
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()
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));
I have a base class, Record, that represents records in a database. I have Customer and Job classes that extend record. I've never used annotations before but what i think i would like to do is create a custom annotation and mark a method in my Customer class that return its Jobs objects so i know to save the Jobs objects to the database when i save the Customer.
Something like this
class Record{
private int id;
public void save(){
//look up all methods in the current object that are marked as #alsoSaveList,
//call those methods, and save them as well.
//look up all methods in the current object that are marked as #alsoSaveRecord,
//call those methods, and save the returned Record.
}
}
class Customer extends Record{
#alsoSaveList
public List<Job> jobs(){
return list of all of customers jobs objects;
}
}
class Job extends Record{
#alsoSaveRecord
public Customer customer(){
return its customer object;
}
}
Is this possible? can someone point me in the right direction?
I agree, typically if your using an ORM then you could let JPA or Hibernate deal with this. However if you want a programatic response like your mentioning here's a simple example based :
Define your Annotation: AlsoSaveRecord.class
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface AlsoSaveRecord {
// The record class type
Class<?> value();
}
Code to find methods to invoke: Code you could add to your class example above
public void save() {
List<Method> toSaveRecords = findMethodsAnnotatedWith(AlsoSaveRecord.class, obj);
for (Method rec : toSaveRecords) {
AlsoSaveRecord anno = rec.getAnnotation(AlsoSaveRecord.class);
Class<?> recordType = anno.value();
Object objToSave = rec.invoke(obj);
}
}
List<Method> findMethodsAnnotatedWith(Class<? extends Annotation> annotation, Object instance)
{
Method[] methods = instance.getClass().getDeclaredMethods();
List<Method> result = new ArrayList<Method>();
for (Method m : methods) {
if (m.isAnnotationPresent(annotation)) {
result.add(m);
}
}
return result;
}
The above will scan for AlsoSaveRecord annotations in the Object in hand and return any applicable methods. You can then invoke those methods returned which were of a result of being annotated. The invoke will return the Object which you can cast or do something with.
Edited as requested to have the "Record Type" defined within the annotation (ie. #AlsoSaveRecord(MyRecord.class);
The method above can now grab the recordType which is the defined class when annotated