Simple getter invoker with Byte buddy - java

I am just learning byte buddy, and want to create the simplest getter invoker, but, unfortunately could find how to do it. So, the problem:
I have a lot classes, that I scan and if I find in a class a field that is annotated with FooAnnotation annotation I must be able to get the value of this field using this class instance getter method.
This is my code:
interface GetterInvoker<T, S> {
//this method must `return instance.getterMethod()`;
S getValueFrom(T instance);
}
abstract class AsbtractGetterInvoker implements GetterInvoker {
//...some logic
}
Main code
//input
Class<?> scannedClass = ...
Field annotatedField = ....
Method getterMethod = ....
//generating class
Class<?> tempClass = new ByteBuddy()
.subclass(AsbtractGetterInvoker.class, ConstructorStrategy.Default.IMITATE_SUPER_CLASS)
.method(named("getValueFrom").and(takesArguments(1)))
.intercept(???????)
.make()
.load(getClass().getClassLoader())
.getLoaded();
Could anyone say how to do it?

You'd create such an implementation using MethodCall which allows you to do exactly this.

Related

Setting up #FieldProxy field dynamically in Byte Buddy

I have to implement an interceptor that can be used for dynamically specified fields regardless of the field name.
On the comment for the answer here
https://stackoverflow.com/a/35113359/11390192
I've read
you can really just use reflection on a #This object. As long as you
cache the Field instances, this has no relevance to performance.
However I doubt the following interceptor implementation is an effecient one (if I understood the comment right).
public static class DynamicFieldInterceptor {
private final String fieldName;
public DynamicFieldInterceptor(String fieldName) {
this.fieldName = fieldName;
}
public void intercept(#This Object thiz) throws NoSuchFieldException, IllegalAccessException {
Field field = thiz.getClass().getDeclaredField(fieldName);
boolean oldAccessible = field.isAccessible();
field.setAccessible(true);
Long fieldValue = (Long)field.get(thiz);
field.set(thiz, fieldValue + 1L); // !< Instead of my business logic
field.setAccessible(oldAccessible);
}
}
I've also tried the following idea: to generate interceptor classes for each field with the different annotations on the #FieldProxy argument. Than use the generated class as an interceptor to the target class.
public interface Metaclass {
void intercept(GetterAndSetter field);
}
public static class MetaclassInterceptor implements Metaclass{
#Override
public void intercept(GetterAndSetter field) {
field.set((Long)field.get() + 1L);
}
}
public static Class<?> annotateInterceptorClass(final String annotation)
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
return new ByteBuddy()
.subclass(MetaclassInterceptor.class)
.topLevelType()
.name("ClassForIntercepting_" + annotation + "_Field")
.modifiers(Visibility.PUBLIC, Ownership.STATIC)
.defineMethod("intercept", void.class, Visibility.PUBLIC)
.withParameter(GetterAndSetter.class, "intercept")
.annotateParameter(AnnotationDescription.Builder.ofType(FieldProxy.class)
.define("value", annotation).build())
.intercept(SuperMethodCall.INSTANCE)
.make()
.load(MetaclassInterceptor.class.getClassLoader())
.getLoaded();
}
The class seems to be generated well. The method in the generated class exists and the parameter is annotated with the expected annotation.
However when I tried to use the generated class as an interceptor, I've got an exception.
Class<?> klass = new ByteBuddy()
.subclass(Object.class)
.defineProperty("index0", Long.class, false)
.defineProperty("index1", Long.class, false)
.defineMethod("doSomeActions", void.class, Visibility.PUBLIC)
.intercept(
MethodDelegation
.withDefaultConfiguration()
.withBinders(FieldProxy.Binder.install(GetterAndSetter.class))
// Use dynamically generated interceptor, see abode
.to(annotateInterceptor("index0"))
.andThen(
MethodDelegation
.withDefaultConfiguration()
.withBinders(FieldProxy.Binder.install(GetterAndSetter.class))
// Use dynamically generated interceptor, see abode
.to(annotateInterceptor("index1"))
)
)
.make()
.load(MetaclassInterceptor.class.getClassLoader())
.getLoaded();
Exception in thread "main" java.lang.NoClassDefFoundError: LClassForIntercepting_index0_Field;
at java.base/java.lang.Class.getDeclaredFields0(Native Method)
at java.base/java.lang.Class.privateGetDeclaredFields(Class.java:3062)
at java.base/java.lang.Class.getDeclaredField(Class.java:2410)
at net.bytebuddy.implementation.LoadedTypeInitializer$ForStaticField.onLoad(LoadedTypeInitializer.java:120)
at net.bytebuddy.implementation.LoadedTypeInitializer$Compound.onLoad(LoadedTypeInitializer.java:187)
at net.bytebuddy.dynamic.TypeResolutionStrategy$Passive.initialize(TypeResolutionStrategy.java:102)
at net.bytebuddy.dynamic.DynamicType$Default$Unloaded.load(DynamicType.java:5662)
at net.bytebuddy.dynamic.DynamicType$Default$Unloaded.load(DynamicType.java:5651)
at MainClass4.main(MainClass4.java:107)
Even if I succeeded with dynamic implementation of interceptors, I'd be sure that it's not the perfect way. I think it has to be the possibility to make it in the easier way. Really, #FieldProxy annotation can get the field from both explicitly specified name and bean property if the field name in the annotation is not specified, so I think it is the technical opportunity to map it to any other field.
When you load a class using load(MetaclassInterceptor.class.getClassLoader()), you are creating a new class loader that does not become visible to any other classes on other loaders unless you reuse it.
You can:
a) Combine the two DynamicTypes that are created by the make step and load them together. This way, they will end up in the same class loader.
b) Take the class loader of the first generated class and cast it to an InjectionClassLoader. You will also need to specify the ClassLoadingStrategy.WRAPPER.opened() and use it together with InjectionClassLoader.Strategy.INSTANCE. Note that this will allow anybody with a reference to an instance of your generated class to define classes in the same package.
c) Use ClassLoadingStrategy.Default.INJECTION what defines classes in the original class loader without creating a wrapper. Not that this strategy relies on Unsafe API.

JPA: Is it possible to pass in the subclass type as an argument using a superclass constructor? [duplicate]

Is there a way to create an instance of a particular class given the class name (dynamic) and pass parameters to its constructor.
Something like:
Object object = createInstance("mypackage.MyClass","MyAttributeValue");
Where "MyAttributeValue" is an argument to the constructor of MyClass.
Yes, something like:
Class<?> clazz = Class.forName(className);
Constructor<?> ctor = clazz.getConstructor(String.class);
Object object = ctor.newInstance(new Object[] { ctorArgument });
That will only work for a single string parameter of course, but you can modify it pretty easily.
Note that the class name has to be a fully-qualified one, i.e. including the namespace. For nested classes, you need to use a dollar (as that's what the compiler uses). For example:
package foo;
public class Outer
{
public static class Nested {}
}
To obtain the Class object for that, you'd need Class.forName("foo.Outer$Nested").
You can use Class.forName() to get a Class object of the desired class.
Then use getConstructor() to find the desired Constructor object.
Finally, call newInstance() on that object to get your new instance.
Class<?> c = Class.forName("mypackage.MyClass");
Constructor<?> cons = c.getConstructor(String.class);
Object object = cons.newInstance("MyAttributeValue");
You can use reflections
return Class.forName(className).getConstructor(String.class).newInstance(arg);
If class has only one empty constructor (like Activity or Fragment etc, android classes):
Class<?> myClass = Class.forName("com.example.MyClass");
Constructor<?> constructor = myClass.getConstructors()[0];
when using (i.e.) getConstructor(String.lang) the constructor has to be declared public.
Otherwise a NoSuchMethodException is thrown.
if you want to access a non-public constructor you have to use instead (i.e.) getDeclaredConstructor(String.lang).
If anyone is looking for a way to create an instance of a class despite the class following the Singleton Pattern, here is a way to do it.
// Get Class instance
Class<?> clazz = Class.forName("myPackage.MyClass");
// Get the private constructor.
Constructor<?> cons = clazz.getDeclaredConstructor();
// Since it is private, make it accessible.
cons.setAccessible(true);
// Create new object.
Object obj = cons.newInstance();
This only works for classes that implement singleton pattern using a private constructor.
Another helpful answer. How do I use getConstructor(params).newInstance(args)?
return Class.forName(**complete classname**)
.getConstructor(**here pass parameters passed in constructor**)
.newInstance(**here pass arguments**);
In my case, my class's constructor takes Webdriver as parameter, so used below code:
return Class.forName("com.page.BillablePage")
.getConstructor(WebDriver.class)
.newInstance(this.driver);
You want to be using java.lang.reflect.Constructor.newInstance(Object...)
Very Simple way to create an object in Java using Class<?> with constructor argument(s) passing:
Case 1:-
Here, is a small code in this Main class:
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class Main {
public static void main(String args[]) throws ClassNotFoundException, NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
// Get class name as string.
String myClassName = Base.class.getName();
// Create class of type Base.
Class<?> myClass = Class.forName(myClassName);
// Create constructor call with argument types.
Constructor<?> ctr = myClass.getConstructor(String.class);
// Finally create object of type Base and pass data to constructor.
String arg1 = "My User Data";
Object object = ctr.newInstance(new Object[] { arg1 });
// Type-cast and access the data from class Base.
Base base = (Base)object;
System.out.println(base.data);
}
}
And, here is the Base class structure:
public class Base {
public String data = null;
public Base()
{
data = "default";
System.out.println("Base()");
}
public Base(String arg1) {
data = arg1;
System.out.println("Base("+arg1+")");
}
}
Case 2:- You, can code similarly for constructor with multiple argument and copy constructor. For example, passing 3 arguments as parameter to the Base constructor will need the constructor to be created in class and a code change in above as:
Constructor<?> ctr = myClass.getConstructor(String.class, String.class, String.class);
Object object = ctr.newInstance(new Object[] { "Arg1", "Arg2", "Arg3" });
And here the Base class should somehow look like:
public class Base {
public Base(String a, String b, String c){
// This constructor need to be created in this case.
}
}
Note:- Don't forget to handle the various exceptions which need to be handled in the code.
You can also invoke methods inside the created object.
You can create object instant by invoking the first constractor and then invoke the first method in the created object.
Class<?> c = Class.forName("mypackage.MyClass");
Constructor<?> ctor = c.getConstructors()[0];
Object object=ctor.newInstance(new Object[]{"ContstractorArgs"});
c.getDeclaredMethods()[0].invoke(object,Object... MethodArgs);

Add field to Proxy class created with Javassist

I am creating a Proxy class using Javassist ProxyFactory with the following code:
ProxyFactory factory = new ProxyFactory();
factory.setSuperclass(entity.getClass());
factory.setInterfaces(new Class[] { MyCustomInterface.class });
.....
Class clazz = factory.createClass();
Object result = clazz.newInstance();
The problem is that I also need to add a field to the class. But if I do CtClass proxy = ClassPool.getDefault().get(clazz.getName()); it gaves a NotFoundException
How can I add a field the class created with createClass? Is there a better way to do what I am trying to do?
This is based in your reply to my comment.
You can indeed use MyCustomInterface and your proxyClass to create sort of a mixin in Java. But you'll still have to cast from proxy class to MyCustomInterface to be able to call the methods.
Let's get started.
Creating your proxy
First you create your proxy has you already were doing:
// this is the code you've already posted
ProxyFactory factory = new ProxyFactory();
factory.setSuperclass(entity.getClass());
factory.setInterfaces(new Class[] { MyCustomInterface.class });
Method handler: Doing the magic
Javassist proxies allow you to add a MethodHandler. It basically acts has a an InvocationHandler would in a regular Java Proxy, that means it works as a method interceptor.
The method handler will be your mixin! First you create a new MethodHandler with the custom field you actually want to add to the class, along with the entity object you've started proxying:
public class CustomMethodHandler implements MethodHandler {
private MyEntity objectBeingProxied;
private MyFieldType myCustomField;
public CustomMethodHandler(MyEntity entity) {
this.objectBeingProxied = entity;
}
// code here with the implementation of MyCustomInterface
// handling the entity and your customField
public Object invoke(Object self, Method method, Method proceed, Object[] args) throws Throwable {
String methodName = method.getName();
if(methodNameFromMyCustomInterface(methodName)) {
// handle methodCall internally:
// you can either do it by reflection
// or if needed if/then/else to dispatch
// to the correct method (*)
}else {
// it's just a method from entity let them
// go. Notice we're using proceed not method!
proceed.invoke(objectBeingProxied,args);
}
}
}
(*) Notice that even if I say in the comment to handle the call internally you can have the interface implementation in another place that isn't your method handler and just call it from here.
Getting everything together
ProxyFactory factory = new ProxyFactory();
factory.setSuperclass(entity.getClass());
factory.setInterfaces(new Class[] { MyCustomInterface.class });
Class cls = factory.createClass();
// bind your newly methodHandler to your proxy
((javassist.util.proxy.Proxy) cls).setHandler(new CustomMethodHandler(entity));
EntityClass proxyEntity = cls.newInstance();
You should now be able to do ((MyCustomInterface)proxyEntity).someMethodFromTheInterface() and let it be handled by your CustomMethodHandler
Summing up
You create a proxy using Proxy Factory from javassist
You create your own MethodHandler class that can receive your proxied entity and the field you want to operate
You bind the methodHandler to your proxy so you can delegate interface implementation
Keep in mind that these approach isn't perfect, being one of the short comings the code in Entity class cannot refer to the interface unless you first create the proxy.
If anything wasn't very clear for you, just comment and I'll do my best to clarify you.

Instantiate parameterized class using a field type of another class

I would like to use a type which I get from a class' field (using reflection) to instantiate a class with generics.
Note: I omitted the exceptions hoping for easy reading.
public class AClass {
class BClass<T> {
T aMemba;
}
public void AMethod() {
Class c = Class.forName("com.bla.flipper");
Field f = c.getField("flipIt");
// Here is my difficulty, I want to instantiate BClass with the type of
// field 'f' but the compiler won't let me.
Class typeClass = f.getType();
BClass<typeClass> = new BClass<typeClass>();
}
}
Is what I want to achieve reasonable? Any thought on how I can solve this problem?
Thanks!
Whatever you are trying to do is not reasonable because if you look at the following line:
BClass<typeClass> = new BClass<typeClass>();
typeClass is something that compiler should be aware of. But in your case it's only known in the runtime through reflection.
Compiler needs to erase T in BClass<T> and replace it with a concrete type which in your case is unknown at compile time so logically it's invalid.
You can capture the type argument of the type of typeClass:
Field f = ...;
Class<?> typeClass = f.getType();
withClassCapture(typeClass);
private <T> void withClassCapture(Class<T> klazz) {
BClass<T> instance = new BClass<T>();
// ... do your thing
}

Instantiate object by it's path got from annotation property

1. Have following annotation:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE)
public #interface Trackable {
String builder();
}
2. usage of this annotation:
#Trackable(builder = "pkg1.SomeVO")
public class MyService
3. pkg1.SomeVO - is path to the Java object,that should be instantiated further in my aspect class.
4. I've got String value of build,that is equals to 'pkg1.SomeVO' from reflection.
The question is,how actually to instantiate SomeVO object?
I need to it like:
MyBuilder mb=new SomeVO();
where MyBuilder is abstract class,already defined.
It may be any object,e. g. SomeVO2 etc.,so I definitely doesn't know in my aspect(see step 3.),what class should be instantiated.
Do something like this to get the annotation value and create the class. Also, you may want to use the default value field and a Class instead of a String in your annotations to make things easier.
for (Method m : MyService.class.getDelcaredMethods())
if (m.getAnnotation(Trackable.class) != null) {
String className = m.getAnnotation(Trackable.class).builder()
Class.forName(className).newInstance();
}
If your class has no default constructor you need to figure out what args it takes:
for (Constructor c : Class.forName(className).getConstructors())
if (c.getParameterTypes() == /* your expected arg types */)
return c.newInstance(/* your args */);
To ensure your class is of a certain type (e.g. MyBuilder) you can make your annotation be:
public #interface Trackable {
Class<? extends MyBuilder> value()
}
Assuming by path, you mean the package your class is in (which is hopefully on the classpath).
String myClass = getClassNameFromAnnotationReflectively(); // YOU define this method...
Class<SomeVO> someVoClass = Class.forName(myClass);
SomeVO someVo = someVoClass.newInstance();
If your class doesn't have a default constructor, you can find the proper one using getConstructor(Class<?> ...) (where you pass in the types of the arguments of the constructor you are looking for), and then calling newInstance(Object...) on that (passing in the actual values).
There are a lot of fun methods for use on the Class object. I recommend taking a look.

Categories