Spring AOP: get access to argument names - java

I'm using Spring 3.x, Java 6.
I have an #Around aspect with the following joinpoint:
#Around("execution(public * my.service.*.*Connector.*(..))")
So, I'm basically interested in intercepting all calls to public methods of classes with the class name ending with "Connector". So far so good.
Now, in my aspect I would like to access the actual argument names of the methods:
public doStuff(String myarg, Long anotherArg)
myarg and anotherArg
I understand that using:
CodeSignature signature = (CodeSignature)jointPoint.getSignature();
return signature.getParameterNames();
will actually work but only if I compile the code with the "-g" flag (full debug) and I would rather not do it.
Is there any other way to get access to that kind of runtime information.
Thanks
L

Unfortunately you can't do this :-(. It is a well known limitation of JVM/bytecode - argument names can't be obtained using reflection, as they are not always stored in bytecode (in the contrary to method/class names).
As a workaround several frameworks/specification introduce custom annotations over arguments like WebParam (name property) or PathParam.
For the time being all you can get without annotations is an array of values.

Check the implementations of org.springframework.core.ParameterNameDiscoverer.
Annotations like #RequestParam used by spring inspect the parameter name if no value is set. So #RequestParam String foo will in fact fetch the request parameter named "foo". It uses the ParameterNameDiscoverer mechanism. I'm just not sure which of the implementations are used, by try each of them.
The LocalVariableTableParameterNameDiscoverer reads the .class and uses asm to inspect the names.
So, it is possible. But make sure to cache this information (for example - store a parameter name in a map, with key = class+method+parameter index).
But, as it is noted in the docs, you need the debug information. From the docs of #PathVariable:
The matching of method parameter names to URI Template variable names can only be done if your code is compiled with debugging enabled. If you do not have debugging enabled, you must specify the name of the URI Template variable name in the #PathVariable annotation in order to bind the resolved value of the variable name to a method parameter
So, if you really don't want to include that information, Tomasz Nurkiewicz's answer explains the workaround.

In Java 8 there is a new compiler flag that allows additional metadata to be stored with byte code and these parameter names can be extracted using the Parameter object in reflection. See JDK 8 spec. In newer versions of hibernate org.springframework.core.ParameterNameDiscoverer uses this feature. To use it compile using javac with this flag:
-parameters Generate metadata for reflection on method parameters
Access parameters using reflection's Parameter class.

I am not sure if its a best way, but I added a Annotation on my method:
My Annotation:
#Retention (RetentionPolicy.RUNTIME)
#Target (ElementType.METHOD)
public #interface ReadApi
{
String[] paramNames() default "";
}
#ReadApi (paramNames={"name","id","phone"})
public Address findCustomerInfo(String name, String id, String phone)
{ ..... }
And in the Aspect:
#Around("aspect param && #annotation(readApi)")
public Object logParams(ProceedingJoinPoint pjp,
ReadApi readApi)
{
//use pjp.getArgs() and readApi.paramNames();
}
This is probably a hack but i did not want to compile with more options to get information. Anyways, its working well for me. Only downside is that i need to keep the names in annotation and method in sync.

Related

Unable to Get Fully-Qualified Type Name from Javadoc Element

Short and sweet: despite the fact that I am asking the Javadoc API to give me the fully-qualified name of an annotation, it is only returning the simple type name.
I am writing a Javadoc doclet that relies heavily on the inspection of annotations. As such, I have created a utility function that will take an array of AnnotationDesc objects and return a Map that associates the fully-qualified name of the annotation to the AnnotationDesc object that describes it. Here are the relevant functions:
public static final Map<String, AnnotationDesc> getAnnotationMap(AnnotationDesc[] notes)
{
if (notes == null)
{
return Collections.emptyMap();
}
return Collections.unmodifiableMap(Arrays.stream(notes).collect(Collectors.toMap(AnnotationUtils::getNoteName, note -> note)));
}
private static String getNoteName(AnnotationDesc note) { return note.annotationType().qualifiedTypeName(); }
For what it's worth, I have also tried using qualifiedName, which is another method exposed by the return value of the AnnotationDesc.annotationType method.
In my integration tests, this is all working great. However, when I push my doclet out to Artifactory, pull it down into another project and try to invoke it by way of a Gradle task, the keys in my map are the simple type names of the annotations.
Here is the definition of my Gradle task:
task myDocletTask(type: Javadoc) {
source = sourceSets.main.allJava
destinationDir = reporting.file("my-doclet-dir")
options.docletpath = configurations.jaxDoclet.files.asType(List)
options.doclet = <redacted - fully qualified type name of custom doclet>
}
I have noticed that if a program element is annotated with a fully-qualified annotation, then the fully-qualified name is actually picked up by the Javadoc API. E.g: #com.package.Annotation results in the expected behavior, but #Annotation does not.
Can someone please help me to understand why this is happening and, more importantly, how I can achieve the expected/desired behavior?
The problem was that the annotations were not on the classpath of the doclet at "documentation time". In order to fix this problem I augmented my Gradle Javadoc task with the following line: options.classpath = sourceSets.main.runtimeClasspath.asType(List).

nameof equivalent in Java

C# 6.0 introduced the nameof() operator, that returns a string representing the name of any class / function / method / local-variable / property identifier put inside it.
If I have a class like this:
class MyClass
{
public SomeOtherClass MyProperty { get; set; }
public void MyMethod()
{
var aLocalVariable = 12;
}
}
I can use the operator like this:
// with class name:
var s = nameof(MyClass); // s == "MyClass"
// with properties:
var s = nameof(MyClass.OneProperty); // s == "OneProperty"
// with methods:
var s = nameof(MyClass.MyMethod); // s == "MyMethod"
// with local variables:
var s = nameof(aLocalVariable); // s == "aLocalVariable".
This is useful since the correct string is checked at compile time. If I misspell the name of some property/method/variable, the compiler returns an error. Also, if I refactor, all the strings are automatically updated. See for example this documentation for real use cases.
Is there any equivalent of that operator in Java? Otherwise, how can I achieve the same result (or similar)?
It can be done using runtime byte code instrumentation, for instance using Byte Buddy library.
See this library: https://github.com/strangeway-org/nameof
The approach is described here: http://in.relation.to/2016/04/14/emulating-property-literals-with-java-8-method-references/
Usage example:
public class NameOfTest {
#Test
public void direct() {
assertEquals("name", $$(Person.class, Person::getName));
}
#Test
public void properties() {
assertEquals("summary", Person.$(Person::getSummary));
}
}
Sadly, there is nothing like this. I had been looking for this functionality a while back and the answer seemed to be that generally speaking, this stuff does not exist.
See Get name of a field
You could, of course, annotate your field with a "Named" annotation to essentially accomplish this goal for your own classes. There's a large variety of frameworks that depend upon similar concepts, actually. Even so, this isn't automatic.
You can't.
You can get a Method or Field using reflection, but you'd have to hardcode the method name as a String, which eliminates the whole purpose.
The concept of properties is not built into java like it is in C#. Getters and setters are just regular methods. You cannot even reference a method as easily as you do in your question. You could try around with reflection to get a handle to a getter method and then cut off the get to get the name of the "property" it resembles, but that's ugly and not the same.
As for local variables, it's not possible at all.
You can't.
If you compile with debug symbols then the .class file will contain a table of variable names (which is how debuggers map variables back to your source code), but there's no guarantee this will be there and it's not exposed in the runtime.
I was also annoyed that there is nothing comparable in Java, so I implemented it myself: https://github.com/mobiuscode-de/nameof
You can simply use it like this:
Name.of(MyClass.class, MyClass::getProperty)
which would just return the String
"property"
It's also on , so you can add it to your project like this:
<dependency>
<groupId>de.mobiuscode.nameof</groupId>
<artifactId>nameof</artifactId>
<version>1.0</version>
</dependency>
or for Gradle:
implementation 'de.mobiuscode.nameof:nameof:1.0'
I realize that it is quite similar to the library from strangeway, but I thought it might be better not to introduce the strange $/$$ notation and enhanced byte code engineering. My library just uses a proxy class on which the getter is called on to determine the name of the passed method. This allows to simply extract the property name.
I also created a blog post about the library with more details.
Lombok has an experimental feature #FieldNameConstants
After adding annotation you get inner type Fields with field names.
#FieldNameConstants
class MyClass {
String myProperty;
}
...
String s = MyClass.Fields.myProperty; // s == "myProperty"

How can I create an annotation processor that processes a Local Variable?

I'm trying to create an annotation for a local variable. I know that I can't retain the annotation in the generated bytecode, but I should be able to have access to the information at compile time by doing something like this:
#Target({ElementType.LOCAL_VARIABLE})
#Retention(RetentionPolicy.SOURCE)
public #interface Junk {
String value();
}
only, this doesn't get processed by apt, or javac when I specify a ProcessorFactory that has "Junk" in it's supported types in the following:
class JunkTester {
public static void main(String[] args) {
#Junk String tmp = "Hello World";
System.out.println(tmp);
}
}
It will however work when I move the #Junk annotation before public static
Thoughts and or workarounds?
Did some quick tests and searched a little, and it's looking like hooking into LOCAL_VARIABLE isn't really supported...yet:
http://forums.sun.com/thread.jspa?threadID=775449
http://www.cs.rice.edu/~mgricken/research/laptjavac/
https://checkerframework.org/jsr308/
Could be totally wrong, but that's how it's looking...
It seems that the Type Annotations Specification (JSR 308), will hopefully address this subject in the future (JDK 8 ?).
As of Java 8, local variable annotations are stored in the classfile.
A standard Java annotation processor does not process the bodies of methods.
However, the Checker Framework enables you to write an annotation processor that processes every annotation including on local variables. Its annotation processors can even examine every statement of the program, whether annotated or not.

annotation = comment?

do they by annotation mean a comment in a code with // or /* */?
No, an annotation is not a comment. An annotation is added to a field, class or method, using the syntax #Annotation. One of the best known annotations is #Override, used to signal a method is overriding one from a super class. For example:
public class MyClass {
#Override
public boolean equals(Object other) {
//...
}
}
See http://download.oracle.com/javase/1,5.0/docs/guide/language/annotations.html for more info.
No, annotations take the form:
#Annotation(property="A")
public class {
#Annotation(property="B")
Object field;
#Annotation(property="C")
public void method() {
}
}
Annotations can be placed on classes, methods or fields. They can provide information at runtime via reflection or compile time via apt (short for Annotation Processing Tool and not the apt package manager).
They are defined as:
#interface Annotation {
String property();
}
See http://java.sun.com/j2se/1.5.0/docs/guide/language/annotations.html for more
Actually, before Java5 (i.e. 1.3 or 1.4), comments (// or /* */) were the only way to add annotation (i.e. "metadata") to be acted upon.
One classic example is the way the unit-testing framework TestNg propose all its Java5 #Annotations as comments if you are using TestNg with Java 1.4.
But that means, for Testng to launch the proper test suite, it had to access the sources of your program, not just the compiled binary.
Unlike Javadoc tags, Java annotations can be reflective in that they can be embedded in class files generated by the compiler and may be retained by the Java VM to be made retrievable at run-time.
No.
An annotation is a special construct introduced with java 1.5. An annotation adds some meta information to a java class, method or variable. This meta information can be evaluated at compile time (e.g. for generating some extra code with apt) or at runtime (e.g. to match a class to a database table).
Example for a built in annotation:
#Deprecated // this is an annotation
public void myMethod() {
...
}
Annotations are not just for java they also exist in c++, they are somehow similar with those from java.
// MyCode.h
# include <CodeAnalysis/SourceAnnotations.h>
using namespace vc_attributes;
class CMyClass
{
public:
void f ( [Pre ( Valid = Yes )] int *pWidth );
// code ...
};
// MyCode.cpp
#include "MyCode.h"
void CMyClass::f ( [Pre (Valid = Yes)] int pWidth )
{
}
You can check the MSDN for more information:
http://msdn.microsoft.com/en-us/library/ms182036(VS.80).aspx
An annotation is not a comment but it is used for many purposes such as error debugging as well it is the instruction set to the compiler but it hasn't any effect on the runtime code.
#override,#deprecated and others are the examples of annotation. It can be used with methods,constructors,parameters,variables.
Annotations are used to give detailed information to the compiler whereas Comments are for the convenience of the programmer so that he know how the code is structured.
of course not, but I think annotation ≈ comment.
the core of them is describe, but annotation has more confinement, you are not easy to make mistak, also, you can find mistake in compile time.

Java annotation returns cryptic class names

I am somewhat new to Java so perhaps I misunderstand the use cases for annotations in java. My issue is the following:
After annotating a method I receive class names such as $Proxy31 when inspecting the annotations on the method. I am curious why I am receiving class names for my annotations that are similar to this, and what I can do to fix this problem.
Method m = this.remoteServiceClass.getMethod(rpcRequest.getMethod().getName());
RequiredPermission a = m.getAnnotation(RequiredPermission.class);
This returns a null annotation even though I know that the method it is looking up has the RequiredPermission annotation implemented.
for(Annotation a : m.getAnnotations())
{
System.out.println(a.getClass().getName());
}
This prints out the $Proxy31 class names.
Given Annotation a, you need to call annotationType(), not getClass() to determine the type of the annotation. An Annotation object is just a proxy that represents that instance of the annotation on that class.
Object o = ...;
Class c = o.getClass();
Annotation[] as = c.getAnnotations();
for (Annotation a : as) {
// prints out the proxy class name
System.out.println(a.getClass().getName());
// prints out the name of the actual annotation
System.out.println(a.annotationType().getName());
}
When you add annotations in the source code, Java actually creates a bunch of interfaces and classes "under the hood" to allow you (or your tools) to ask the program things about the annotations using restrictions. Method annotations create "dyanmic proxies", and accordingly Java creates classes for you, probably with the name Proxy.
If you are interested in this, read on java.lang.reflect.InvocationHandler and its subtype, AnnotationInvocationHandler
That being said, you should not have to worry about what Java actually generates. I suspect you are not using reflection correctly to inspect your annotations from within a Java program.
also.. remember to set this:
#Retention(RetentionPolicy.RUNTIME)
on your annotation so that it lives beyond the compile.

Categories

Resources