how can spring get args name from the method - java

In spring, it express the arg-names like this :
#Before(
value="com.xyz.lib.Pointcuts.anyPublicMethod() && target(bean) && #annotation(auditable)",
argNames="bean,auditable")
public void audit(JoinPoint jp, Object bean, Auditable auditable) {
AuditCode code = auditable.value();
// ... use code, bean, and jp
}
In the doc , it says you may leave out the name of the parameter from the value of the "argNames" attribute. How can spring get argNames("bean,auditable") from anyPublicMethod?
Did java provide some api to get the parameter names?
I think it use the aspectJ to parse the expression, did aspectJ provide the feature?

In Java 8 we can use reflection to get parameter name, see http://docs.oracle.com/javase/tutorial/reflect/member/methodparameterreflection.html. Though argument names will be present if the classes have been compiled with -g:vars.
In earlier versions we need to use some tool, like Javassist:
public static void main(String[] args) throws Exception {
ClassPool pool = ClassPool.getDefault();
CtClass c = pool.get("test.Test");
CtMethod m = c.getDeclaredMethod("main");
MethodInfo methodInfo = m.getMethodInfo();
LocalVariableAttribute t = (LocalVariableAttribute) methodInfo.getCodeAttribute().getAttribute(javassist.bytecode.LocalVariableAttribute.tag);
int i = t.nameIndex(0);
String v = methodInfo.getConstPool().getUtf8Info(i);
System.out.println(v);
}
prints
args

Your annotation
#Before(
value="com.xyz.lib.Pointcuts.anyPublicMethod() && target(bean) && #annotation(auditable)",
argNames="bean,auditable")
, assuming anyPublicMethod() matches any public method, will match any public method annotated with whatever type a parameter named auditable is and invoked on an object of bound to the parameter named bean.
In your method, a parameter named auditable is of type Auditable, so this #Before advice would match something like
public class SomeComponent {
#Auditable
public void someMethod() {}
}
I will bind the SomeComponent type bean to the bean parameter and the instance for the #Auditable annotation to the auditable parameter when invoking the advice.
Java does not have an API for getting parameter names. Parameter names are also not always present in the byte code. When they are, Spring uses the ASM library to parse the bytecode and retrieve them.

Just a complement to the other answers. Spring documentation is explicit : Spring does its best to retrieve correct parameters :
if using Java 8 it uses Java 8 reflection
if classes are compiled in debug mode, parameter name is present and spring uses it, same if ajc (aspectJ compiler) has been used)
it can eventually uses #Param annotations
it uses the type if not ambiguous

Related

Using static variables in Spring annotations

I'm using spring's PreAuthorize annotation as follows:
#PreAuthorize("hasRole('role')");
However, I already have 'role' defined as a static String on another class. If I try to use this value:
#PreAuthorize("hasRole(OtherClass.ROLE)");
I get an error:
org.springframework.expression.spel.SpelEvaluationException: EL1008E:(pos 14): Field or property 'OtherClass' cannot be found on object of type 'org.springframework.security.access.expression.method.MethodSecurityExpressionRoot'
Is there a way to access static variables like this with a PreAuthorize annotation?
Try the following which uses Spring Expression Language to evaluate the type:
#PreAuthorize("hasRole(T(fully.qualified.OtherClass).ROLE)");
Be sure to specify the fully qualified class name.
Documentation
You can also create a bean container with roles, like:
#Component("R")
public final class RoleContainer {
public static final String ROLE_A = "ROLE_A";
}
then on controller you can use:
#PreAuthorize("hasRole(#R.ROLE_A)")
To make it possible to write expressions without package names:
<sec:global-method-security>
<sec:expression-handler ref="methodSecurityExpressionHandler"/>
</sec:global-method-security>
<bean id="methodSecurityExpressionHandler" class="my.example.DefaultMethodSecurityExpressionHandler"/>
Then extend the DefaultMethodSecurityExpressionHandler:
public class DefaultMethodSecurityExpressionHandler extends org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler {
#Override
public StandardEvaluationContext createEvaluationContextInternal(final Authentication auth, final MethodInvocation mi) {
StandardEvaluationContext standardEvaluationContext = super.createEvaluationContextInternal(auth, mi);
((StandardTypeLocator) standardEvaluationContext.getTypeLocator()).registerImport("my.example");
return standardEvaluationContext;
}
}
Now create my.example.Roles.java :
public class Roles {
public static final String ROLE_UNAUTHENTICATED = "ROLE_UNAUTHENTICATED";
public static final String ROLE_AUTHENTICATED = "ROLE_AUTHENTICATED";
}
And refer to it without package name in annotations:
#PreAuthorize("hasRole(T(Roles).ROLE_AUTHENTICATED)")
instead of:
#PreAuthorize("hasRole(T(my.example.Roles).ROLE_AUTHENTICATED)")
Makes it more readable imho. Also roles are now typed. Write:
#PreAuthorize("hasRole(T(Roles).ROLE_AUTHENTICATEDDDD)")
and you will get startup errors that wouldn't have been there if you wrote:
#PreAuthorize("hasRole('ROLE_AUTHENTICATEDDDD')")
Try something like this:
#PreAuthorize("hasRole(T(com.company.enumpackage.OtherClass).ROLE.name())");
If your OtherClass enum is declared as public static, then you need to use $ sign:
#PreAuthorize("hasRole(T(com.company.ParentTopLevelClass$OtherClass).ROLE.name())");
name() to prevent futer problems if toString() will be overriden later
The accepted answer from Kevin Bowersox works, but I didn't like having the T(fully.qualified.path) stuff so I kept looking. I started by creating a custom security method using the answer from James Watkins here:
How to create custom methods for use in spring security expression language annotations
However, instead of a String, I used my enums.Permissions class as the parameter type:
#Component
public class MySecurityService {
public boolean hasPermission(enums.Permissions permission) {
...do some work here...
return true;
}
}
Now the neat part is that when I call the hasPermission from an annotation, I don't have to have to type the whole path, but I do have to enclose it in single quotes:
#PreAuthorize("#mySecurityService.hasPermission('SOME_ROLE_NAME')")
Because the hasPermission method expects an Enum, it will automatically find the Enum value with that name. If it doesn't find it you'll get an exception:
org.springframework.expression.spel.SpelEvaluationException: Type conversion problem, cannot convert from java.lang.String to enums.Permissions
You can rename hasPermission to hasRole, in which case the only trade off is that you are trading T(fully.qualified.path) for #mySecurityService and extra single quotes.
Not sure if it is any better, but there it is. Since none of this is going to verify the values at compile time anyways, my next step is to make an annotation processor.
I also have to give credit to krosenvold for pointing out that spring can automatically convert to an enum:
https://stackoverflow.com/a/516899/618881

PersistenceConstructor argument variable name doesn't match instance variable name

I'm trying to persist the following object with spring-data-mongodb version 1.1.1.RELEASE:
#Document
public static class TestObject {
private final int m_property;
#PersistenceConstructor
public TestObject(int a_property) {
m_property = a_property;
}
public int property() {
return m_property;
}
}
I get a MappingException when I try to read the object back from the database (see full stacktrace below)
The naming convention my group uses requires argument variable names to be prefaced by a_ and instance variable names to be prefaced by m_. It seems like spring-data-mongodb is making the assumption that the constructor argument variable names must match the object instance variable names.
Why doesn't spring-data-mongodb use the constructor argument to instance variable mapping that I define within the constructor?
Is there another way to define this mapping such that spring-data-mongodb will properly construct my object, or is my only option to break the naming convention?
.
Exception in thread "main" org.springframework.data.mapping.model.MappingException: No property a_property found on entity class com.recorder.TestRecorder$TestObject to bind constructor parameter to!
at org.springframework.data.mapping.model.PersistentEntityParameterValueProvider.getParameterValue(PersistentEntityParameterValueProvider.java:90)
at org.springframework.data.convert.ReflectionEntityInstantiator.createInstance(ReflectionEntityInstantiator.java:70)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.read(MappingMongoConverter.java:229)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.read(MappingMongoConverter.java:209)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.read(MappingMongoConverter.java:173)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.read(MappingMongoConverter.java:169)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.read(MappingMongoConverter.java:72)
at org.springframework.data.mongodb.core.MongoTemplate$ReadDbObjectCallback.doWith(MongoTemplate.java:1820)
at org.springframework.data.mongodb.core.MongoTemplate.executeFindMultiInternal(MongoTemplate.java:1542)
at org.springframework.data.mongodb.core.MongoTemplate.findAll(MongoTemplate.java:1064)
at com.recorder.TestRecorder.main(TestRecorder.java:43)
tl;dr
We need to rely on constructor argument names to match field names to find out which field of the document to pull in. If you want to customize this use #Value("#root.field_name") on the constructor argument.
Long story
If you're using a constructor with arguments to let Spring Data instantiate the given class using this constructor we have to hand parameters to the constructor upon invocation. To find out which document field we have to hand in, we need to inspect the matching property for potential field name customization. See the following example:
#Document
class MyEntity {
#Field("foo")
private String myField;
public MyEntity(String myField) {
this.myField = myField;
}
}
In this case we need to pipe the field foo into the constructor and there's no way to find out about this if we don't somehow can obtain a reference to the property. If the constructor parameter name was something different, how should we reliably find out which field value should actually be used as argument? The example you've shown in your question can never work out of the box, as your document would contain a m_property field and there's absolutely no way to find out you actually want that to be injected, except adding more explicit configuration.
To customize this behavior you can use Spring's #Value annotation and inject a custom document field into the constructor. The document itself is available through the #root variable. So you could easily alter my sample above to:
#Document
class MyEntity {
#Field("foo")
private String myField;
public MyEntity(#Value("#root.foo") String somethingDifferent) {
this.myField = somethingDifferent;
}
}
I'd strongly recommend that you add custom field names to your properties as well as you don't want to expose your property naming conventions to the database. The usage pf #Value is briefly mentioned in the reference docs but I've created a ticket to improve the docs and make this more obvious.
You can use some custom converters (and remove #PersistenceConstructor):
// DB => Java
package com.recorder.converters;
public class TestObjectReadConverter implements Converter<DBObject, TestObject>
{
public TestObject convert(final DBObject source) {
return new TestObject((Integer) source.get("m_property"));
}
}
.
// JAVA => DB
package com.recorder.converters;
public class TestObjectWriteConverter implements Converter<TestObject, DBObject>
{
public DBObject convert(final TestObject source) {
return new BasicDBObjectBuilder("m_property", source.property()).get();
}
}
Don't forget to declare those (xml config):
<mongo:mapping-converter base-package="com.recorder">
<mongo:custom-converters>
<mongo:converter>
<bean class="com.recorder.converters.TestObjectReadConverter" />
</mongo:converter>
<mongo:converter>
<bean class="com.recorder.converters.TestObjectWriteConverter"/>
</mongo:converter>
</mongo:custom-converters>
</mongo:mapping-converter>
see this reference
Side note: this is a work around, I don't think naming convention are meant to be so tight that you need to work around. Perhaps it's time for your group to "rethink" those naming convention (for productivity sake in that case).

Java Annotations values provided in dynamic manner

I want to provide annotations with some values generated by some methods.
I tried this so far:
public #interface MyInterface {
String aString();
}
#MyInterface(aString = MyClass.GENERIC_GENERATED_NAME)
public class MyClass {
static final String GENERIC_GENERATED_NAME = MyClass.generateName(MyClass.class);
public static final String generateName(final Class<?> c) {
return c.getClass().getName();
}
}
Thought GENERIC_GENERATED_NAME is static final, it complains that
The value for annotation attribute MyInterface.aString must be a constant expression
So how to achieve this ?
There is no way to dynamically generate a string used in an annotation. The compiler evaluates annotation metadata for RetentionPolicy.RUNTIME annotations at compile time, but GENERIC_GENERATED_NAME isn't known until runtime. And you can't use generated values for annotations that are RetentionPolicy.SOURCE because they are discarded after compile time, so those generated values would never be known.
The solution is to use an annotated method instead. Call that method (with reflection) to get the dynamic value.
From the user's perspective we'd have:
#MyInterface
public class MyClass {
#MyName
public String generateName() {
return MyClass.class.getName();
}
}
The annotation itself would be defined as
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface #MyName {
}
Implementing the lookup for both of these annotations is rather straight-forward.
// as looked up by #MyInterface
Class<?> clazz;
Method[] methods = clazz.getDeclaredMethods();
if (methods.length != 1) {
// error
}
Method method = methods[0];
if (!method.isAnnotationPresent(MyName.class)) {
// error as well
}
// This works if the class has a public empty constructor
// (otherwise, get constructor & use setAccessible(true))
Object instance = clazz.newInstance();
// the dynamic value is here:
String name = (String) method.invoke(instance);
There is no way to modify the properties of an annotation dynamically like others said. Still if you want to achieve that, there are two ways to do this.
Assign an expression to the property in the annotation and process that expression whenever you retrieve the annotation. In your case your annotation can be
#MyInterface(aString = "objectA.doSomething(args1, args2)")
When you read that, you can process the string and make the method invocation and retrieve the value. Spring does that by SPEL (Spring expression language). This is resource intensive and the cpu cycles are wasted every time we want to process the expression. If you are using spring, you can hook in a beanPostProcessor and process the expression once and store the result somewhere. (Either a global properties object or in a map which can be retrieved anywhere).
This is a hacky way of doing what we want. Java stores a private variable which maintains a map of annotations on the class/field/method. You can use reflection and get hold of that map. So while processing the annotation for the first time, we resolve the expression and find the actual value. Then we create an annotation object of the required type. We can put the newly created annotation with the actual value (which is constant) on the property of the annotation and override the actual annotation in the retrieved map.
The way jdk stores the annotation map is java version dependent and is not reliable since it is not exposed for use (it is private).
You can find a reference implementation here.
https://rationaleemotions.wordpress.com/2016/05/27/changing-annotation-values-at-runtime/
P.S: I haven't tried and tested the second method.

Getting the name of a method parameter

In Java 6, imagine I have the following method signature:
public void makeSandwich(Bread slice1, Bread slice2, List<Filling> fillings, boolean mustard)
I would like to know, at runtime, the value that was passed on to slice2 or any other parameter, the important bit here is that I want to get the value by parameter name.
I know how to get the list of parameter types with getParameterTypes or getGenericParameterTypes.
Ideally I would like to get a list of parameter names instead of types. Is there a way to do so?
Parameter names are available if you have told the compiler to include them (compile with debug information). Spring has ParameterNameDiscoverer which can help you obtain the names. The default implementation uses asm ClassReader to do so.
With javac you should include the -g argument to include debug information. With Eclipse I think it is there by default; it can be configured using the preferences: Java -> Compiler and then enable "Store information about method parameters (usable via reflection)" (see also this answer).
Some frameworks use this. For example spring-mvc has #RequestParam which defaults to the param name, if resolvable. It also supports explicit naming - #RequestParam("foo") in case no debug information is provided.
I have found another solution after marking this question as answered. The solution is Paranamer.
Example:
Method method = Foo.class.getMethod(...);
Paranamer paranamer = new CachingParanamer();
String[] parameterNames = paranamer.lookupParameterNames(method) // throws ParameterNamesNotFoundException if not found
// or ...
parameterNames = paranamer.lookupParameterNames(method, false) // will return null if not found
Since Java 1.8, this can be done as long as the parameter names are in the class files. Using javac this is done passing the -parameters flag. From the javac help
-parameters Generate metadata for reflection on method parameters
From IDEs you will need to look at the compiler settings.
If the parameter names are in the class files then here is an example of doing this
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
public class ParameterNamesExamples {
public static void main(String[] args) throws Exception {
Method theDoSomethingMethod = ExampleClass.class.getMethods()[0];
// Now loop through the parameters printing the names
for(Parameter parameter : theDoSomethingMethod.getParameters()) {
System.out.println(parameter.getName());
}
}
private class ExampleClass {
public void doSomething(String myFirstParameter, String mySecondParameter) {
// No-op
}
}
}
The output will depend on if the parameter names are in the class files. If they are the output is:
myFirstParameter
mySecondParameter
If not the output is:
arg0
arg1
More information on this from Oracle can be found at Obtaining Names of Method Parameters
In addition to this answer:
"Parameter names are available if you have told the compiler to include them"
If you're using Eclipse go to project -> properties -> Java Compiler -> check "Store information about method parameters (usable via reflection)
In Java parameter names are not available via reflection.
This is not possible. Class files do not contains the argument names, as you can see with your IDE's autocompletion when the source is not available.
Therefore, the reflection API is not able to give out parameter names.
You can simply assign the value of the parameter to another value
Bread slice2;
public void makeSandwich(Bread slice1, Bread slice2, List<Filling> fillings, boolean mustard) {
this.slice2 = slice2;
System.out.println(this.slice2.getSomething());
}
Do you own the code of the method? You could annotate the parameters and pass names as arguments #Param("slice1"). Later you will be able to get the annotation and extract parameter name from it.

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