ByteBuddy - get annotation of declaring class inside intercept() - java

I need to get the annotation (a value inside of it) of the declaring class of a method during intercept():
#RuntimeType
public static Object intercept(#SuperCall Callable callable, #Origin Method method) throws Exception {
method.getDeclaringClass().getDeclaredAnnotation(SomeAnnotationOnClass.class);
The last line returns null.
new AgentBuilder.Default().with(AgentBuilder.Listener.StreamWriting.toSystemOut()).type(ElementMatchers.isAnnotatedWith(SomeAnnotationOnClass.class))
.transform((builder, type, clazzLoader, javaModule) -> {
return builder.method(ElementMatchers.any()).intercept(MethodDelegation.to(MyInterceptor.class));
This works though... So the annotation is present (on class level!). But not when intercept() is called

You can define your own binder that is executed during instrumentation where the class file-level information is still available. Assuming you define an annotation #interface Foo with runtime retention, you can implement some:
class FooBinder extends ParameterBinder.ForFixedValue<Foo>
where you can extract the constant value from the annotation. This value is then made available to any interceptor method annotated with #Foo.

Related

Not able to run ByteBuddy interceptor when #Morph argument is specified

I need to create a custom classes based on some input. What I have atm is this:
final Class service = ...;
final Method method = ...;
final DynamicType.Unloaded unloadedType = new ByteBuddy()
.subclass(Object.class)
.name(service.getClass().getSimpleName() + "DynamicResolver")
.defineMethod(
endpointName,
resolveReturnType(method),
Modifier.PUBLIC)
.withParameters(parameters)
.intercept(MethodDelegation
.withDefaultConfiguration()
.withBinders(Morph.Binder.install(Morphing.class))
.to(interceptor).andThen(
MethodCall.invoke(method).on(service).withArgument(arguments)
))
.make()
What I am doing here is creating a class with a single method that delegates to provided one. However, the created method and delegate method have a bit different parameters. The created method has one argument more (in parameters). The created method does not take that argument, hence the arguments array with argument indexes (one argument less).
So far it's OK. Now, I need to add additional argument when calling delegation method. For the sake of simplicity of the example, imagine we have to add one more string to delegate call.
As I saw from the documentation, the way to manipulate the arguments is using #Morph. So I did:
public interface Morphing<T> {
T invoke(Object[] args);
}
and my interceptor:
public class Interceptor {
#RuntimeType
public Object invoke(
#Morph final Morphing<Integer> m,
#AllArguments final Object[] args
) {
System.out.println("!!!");
return m.invoke(args);
}
}
Unfortunately, this is not working. When I remove the #Morph argument, the interceptor gets called.
What am I missing here?
EDIT: Is the #Morph used only for subclasses and not when delegating to another instance?
EDIT2: example
Byte Buddy is binding a method of the Object class such that your desired interceptor is no longer triggered. You can add filter(isDeclaredBy(MyInterceptor.class)) after the withDefaultConfiguration() to avoid this. Doing so, you will get an exception that Byte Buddy cannot bind any of your methods.
The reason that #Morph makes the class ineligable is that there is no super method to be called. In your example, you are defining a new method which does not have an original implementation. You'd need to override an existing method to use the annotation.

ByteBuddy: newly defined fields not visible through reflection

I use ByteBuddy in an Agent to add a tracking variable to each Runnable in a test program:
new AgentBuilder.Default()
.with(AgentBuilder.LambdaInstrumentationStrategy.ENABLED)
.type(ElementMatchers.isSubTypeOf(Runnable.class)
.and(ElementMatchers.not(ElementMatchers.isInterface())))
.and(ElementMatchers.not(ElementMatchers.isAbstract()))
.transform((builder, typeDescription, classLoader, module) -> builder
.defineField("foo", String.class)
.constructor(ElementMatchers.any())
.intercept(Advice.to(TestRunnableConstructorInterceptor.class))
.method(ElementMatchers.named("run"))
.intercept(Advice.to(TestRunnableRunInterceptor.class))
)
With my Interceptor classes looking like this:
public static class TestRunnableConstructorInterceptor {
#Advice.OnMethodExit
public static void intercept(#Advice.This Object thiz, #Advice.FieldValue(value="foo",readOnly=false) String foo) throws Exception {
foo = "baz"; // this sets the value successfully
}
}
public static class TestRunnableRunInterceptor {
#Advice.OnMethodEnter
public static void intercept(#Advice.This Object thiz, #Advice.FieldValue("foo") String foo) throws Exception {
System.out.println(foo); //prints "baz"
thiz.getClass().getField("foo"); // java.lang.NoSuchFieldException
}
}
I can see that ByteBuddy is passing through the newly defined field via the FieldValue annotation, but reflectively the variable is not visible - perhaps because the reflection is being applied to the original class, and not the 'rebased' class?
Is this the expected behavior? Is there a way to access this new field via reflection?
Could this be something to do with the Runnables being lambdas? I'm using Advice rather than MethodDelegation because if I try to use MethodDelegation on Runnable#run I get errors like this (from my interception Listener)
Failed to transform java.util.concurrent.ThreadPoolExecutor$Worker$auxiliary$8cXEOSRS$auxiliary$7BgjnLbO (before loading) + Exception: java.lang.IllegalStateException: Cannot resolve type description for java.util.concurrent.ThreadPoolExecutor$Worker$auxiliary$8cXEOSRS Cannot resolve type description for java.util.concurrent.ThreadPoolExecutor$Worker$auxiliary$8cXEOSRSnet.bytebuddy.pool.TypePool$Resolution$Illegal.resolve(TypePool.java:134)
As mentioned in the comments, you need to use the getDeclaredField method instead of getField when you want to locate non-public fields.
I assume that your MethodDelegation renders errors as you instrument any Runnable. Do you request #SuperMethodCall proxies in your delegation method? In this case, you are instructing Byte Buddy to also instrument these classes what is impossible as their byte code is not persisted.
Normally, Byte Buddy excludes synthetic classes from instrumentation. As you instrument the java.* namespace, I assume that you are not using the default exclusion matcher? You should ideally restrain your space of instrumented types, for example by name where you could also exclude classes containing $auxiliary$. Otherwise, you can still exclude synthetic classes as it is by default.

Get a in a interface annotated method from class

I want to access Method via reflection. The problem is that the Method is annotated in an interface:
public interface MyRepository extends CrudRepository<MyClass, Long> {
#CustomAnnotation
MyClass findByName(String name);
}
As you see I use Spring which provides a class that will implement this Repository interface.
I want to create a method, that will get a Repository and invoke all methods that are annotated with #CustomAnnotation.
public void do(Repository<?, ?> repository){
Method[] methods=repository.getClass().getMethodThatAreAnnotatedInInterfaceWith(CustomAnnotation.class);
....
}
Because the implementation of an interface won’t have the annotations of the interface present, I do not know how to query these methods.
Since you are using Spring, use the AnnotationUtils#findAnnotation(Method method, Class<A> annotationType):
Find a single Annotation of annotationType on the supplied Method, traversing its super methods (i.e., from superclasses and interfaces) if the annotation is not directly present on the given method itself.
Iterate over the methods of getClass().get[Declared]Methods() and for each of them check if it is annotated with your annotation using the above utility.
Get the method of super class by repository.getClass().getDeclaredMethod().
Get the interface of class by repository.getClass().getInterfaces().
check the method of interface whether has the annotation.
One way to get a method's hierarchy would be to use Apache Commons Lang's class MethodUtils. Provided you get the implementation of a method you then can use that class to get (and check) the hierarchy of that method:
Set<Method> hierarchy = MethodUtils.getOverrideHierarchy( method, Interfaces.INCLUDE );
Then check the methods in that hierarchy for the annotation.
Alternatively you could check out the former Google Reflections library which has the method Reflections#getMethodsAnnotatedWith(SomeAnnotation.class). You'd then use the returned set to check whether the actual implementation is an instance of the class/interface declaring those methods.
Here s the solution:
for (Class c : r.getClass().getInterfaces()) {
for (Method m : c.getDeclaredMethods()) {
if (m.getDeclaredAnnotation(CustomAnnotation.class) != null) {
m.invoke(r, params);
}
}
}

How to annotate injector.getInstance?

I want to inject an instance from Guice injector in my unitTest.
Which diffrentiator can I use?
I know #annotation mechanism is used in ctor params
but junit doesn't allow ctor with params.
should I use class fields' #annotation?
public void setUp() throws Exception {
RoutingResponseRepository routingResponseRepository = injector.getInstance(RoutingResponseRepository.class);
}
e.g.
I want
#firstType RoutingResponseRepository
and
#secondType RoutingResponseRepository
For testing, you could just inject into your test-case-instance. Then you can use your injection-points just as you would in production code:
#Inject
#SecondType
private RoutingResponseRepository;
#Before
public void setUp() {
Guice.createInjector().injectMembers(this);
}
without caring about Types and Keys.
Use Injector.getInstance(Key):
injector.getInstance(Key.get(RoutingResponseRepository.class, firstType.class))
When referring to a binding, Guice internally uses an immutable Key instance, which refers to an annotation status (a binding annotation class, a binding annotation instance, or no binding annotation) combined with a type (a class literal, a Type instance, or a TypeLiteral). Matching this matrix, there are 9 different overloads of Key.get, which is the static factory method to get these Key objects. Overloads that take Class<?> are offered just for the sake of convenience.
In general, any time you want to represent a parameterized or annotated type (like in getInstance or bind), you can use an overload that takes a Key instead.

Spring AOP: how to get the annotations of the adviced method

I'd like to implement declarative security with Spring/AOP and annotations.
As you see in the next code sample I have the Restricted Annotations with the paramter "allowedRoles" for defining who is allowed to execute an adviced method.
#Restricted(allowedRoles="jira-administrators")
public void setPassword(...) throws UserMgmtException {
// set password code
...
}
Now, the problem is that in my Advice I have no access to the defined Annotations:
public Object checkPermission(ProceedingJoinPoint pjp) throws Throwable {
Signature signature = pjp.getSignature();
System.out.println("Allowed:" + rolesAllowedForJoinPoint(pjp));
...
}
private Restricted rolesAllowedForJoinPoint(ProceedingJoinPoint thisJoinPoint)
{
MethodSignature methodSignature = (MethodSignature) thisJoinPoint.getSignature();
Method targetMethod = methodSignature.getMethod();
return targetMethod.getAnnotation(Restricted.class);
}
The method above always returns null (there are no annotations found at all).
Is there a simple solution to this?
I read something about using the AspectJ agent but I would prefer not to use this agent.
To whoever is still having problem after changing annotation retention to Runtime, you might be having the same problem I had: getMethod() returns interface method instead of the implementing class. So, if you have your annotations in the class then naturally getAnnotations() on the interface method returns null.
The following solution solved this problem:
final String methodName = pjp.getSignature().getName();
final MethodSignature methodSignature = (MethodSignature)pjp.getSignature();
Method method = methodSignature.getMethod();
if (method.getDeclaringClass().isInterface()) {
method = pjp.getTarget().getClass().getDeclaredMethod(methodName, method.getParameterTypes());
}
and if you like, you have the option of handling interface annotations here too.
Some more comments available here:
getting template method instance from ProceedingJoinPoint
Oleg
I assume #Restricted is your annotation. If that is the case, make sure you have:
#Retention(RetentionPolicy.RUNTIME)
in your annotation definition. This means that the annotation is retained at runtime.
Even after changing the retention policy like Bozho mentioned this call to get annotation returns null:
targetMethod.getAnnotation(Restricted.class);
What I found is you have to bind the annotation. Given the interface is declared like this:
#Retention(RetentionPolicy.RUNTIME)
public #interface Restricted {
String[] allowedRoles();
}
The advice would need to be declared like this:
#Before("#annotation( restrictedAnnotation )")
public Object processRequest(final ProceedingJoinPoint pjp, Restricted restrictedAnnotation) throws Throwable {
String[] roles = restrictedAnnotation.allowedRoles();
System.out.println("Allowed:" + roles);
}
What this does is bind the annotation to the parameter in the method signature, restrictedAnnotation. The part I am not sure about is how it gets the annotation type, it seems to be based on the parameter. And once you have the annotation you can get the values.
Why don't you just use Spring Security ? It's a brief to implement and use, I don't really see the point in wasting time reinventing the wheel.
Whith Spring AOP if you have a situation like MyManagerImpl implements MyManager the pointcut is applied to the interface method so MethodSignature describes the method defined on MyManager that doesn't have any annotation. the only way I've found to fix this is to inspect the class of the jp.getTarget() object and retrieve the corresponding method.

Categories