Is there a way to directly get hold of a method(non-static) annotated with given annotation present in a given object? I don't want to iterate over list of all methods and check if the given annotation present or not.
In below sample code, I have used dummy method(not exist) getMethodAnnotatedWith() . I need to replace this with actual method.
public class MyController {
#PostMapping("/sum/{platform}")
#ValidateAction
public Result sum(#RequestBody InputRequest input, #PathVariable("platform") String platform) {
log.info("input: {}, platform: {}", input, platform);
return new Result(input.getA() + input.getB());
}
}
class InputRequest {
#NotNull
private Integer a;
#NotNull
private Integer b;
#MyValidator
public boolean customMyValidator() {
log.info("From customMyValidator-----------");
return false;
}
}
#Aspect
#Component
#Slf4j
public class AspectClass {
#Before(" #annotation(com.example.ValidateAction)")
public void validateAspect(JoinPoint joinPoint) throws Throwable {
log.info(" MethodName : " + joinPoint.getSignature().getName());
Object[] args = joinPoint.getArgs();
log.info("args[0]==>"+args[0] +", args[1]==>"+args[1]);
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
Parameter[] parameters = method.getParameters();
Method customMyValidator = parameters[0].getType().getMethodAnnotatedWith(MyValidator.class); // InputRequest class type which has a method annotated with #MyValidator
customMyValidator.invoke(args[0]);
}
}
Here is my stand-alone AspectJ MCVE. I just imported some Spring classes. The syntax would be the same in Spring AOP.
Helper classes:
package de.scrum_master.app;
public class Result {
public Result(int i) {}
}
package de.scrum_master.app;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
#Retention(RUNTIME)
#Target(METHOD)
public #interface ValidateAction {}
Custom validator interface (not annotation):
package de.scrum_master.app;
public interface MyValidator {
boolean validate();
}
Class implementing custom validator interface:
package de.scrum_master.app;
public class InputRequest implements MyValidator {
private Integer a;
private Integer b;
public InputRequest(Integer a, Integer b) {
this.a = a;
this.b = b;
}
#Override
public boolean validate() {
System.out.println("Performing custom validation");
return false;
}
public Integer getA() {
return a;
}
public Integer getB() {
return b;
}
#Override
public String toString() {
return "InputRequest(a=" + a + ", b=" + b + ")";
}
}
See? Instead of annotating the validator method, you just override the interface method. It is just as simple as before, but more type-safe and feels more "standard-ish". It will also be much easier to handle by AOP, as you are going to find out below.
Controller:
The controller looks just the same as before. You still have the method annotated with #ValidateAction and taking InputRequest as its first parameter.
package de.scrum_master.app;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
public class MyController {
#PostMapping("/sum/{platform}")
#ValidateAction
public Result sum(#RequestBody InputRequest input, #PathVariable("platform") String platform) {
System.out.println("input: " + input + ", platform: " + platform);
return new Result(input.getA() + input.getB());
}
}
Sample driver application:
package de.scrum_master.app;
public class Application {
public static void main(String[] args) {
new MyController().sum(new InputRequest(11, 22), "foo");
}
}
Aspect:
The aspect is super simple now, like I said in my comment to your question. The pointcut checks for methods which
are annotated with #ValidateAction and
have a first parameter implementing MyValidator.
Then it binds the MyValidator parameter to an advice method argument by args().
Please note that you can omit the trailing && execution(* *(..)) in Spring AOP because it only supports method execution() joinpoints, while in AspectJ there are also call() joinpoints which here would lead to double validation and log output.
package de.scrum_master.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
import de.scrum_master.app.MyValidator;
#Aspect
#Component
public class MyValidatorAspect {
#Before("#annotation(de.scrum_master.app.ValidateAction) && execution(* *(..)) && args(validator, ..)")
public void validateAspect(JoinPoint joinPoint, MyValidator validator) throws Throwable {
System.out.println(joinPoint);
validator.validate();
}
}
Console log:
execution(Result de.scrum_master.app.MyController.sum(InputRequest, String))
Performing custom validation
input: InputRequest(a=11, b=22), platform: foo
Update answering follow-up questions: Please read some documentation. The Spring manual is a good source.
what does it mean && args(validator, ..)?
It is called argument binding. This specific pointcut designator means: Match all target methods where the first argument matches the type of validator in the advice method arguments list and bind the value to that argument. As you see, the argument is declared as MyValidator validator. The , .. means that any subsequent target method arguments (if any) do not matter. For more information see this manual paragraph.
What would happen if more than one class implementing MyValidator interface . I mean how would FW figure out that which implementation has to passed while invoking current controller operation ?
FW meaning what? Maybe framework? Anyway, I do not see the problem. Why would the framework have to figure out which implementation there is? That is the beauty of OOP and virtual methods: You do not need to figure out anything because each implementation has a boolean validate() which then gets called. It is simple, type-safe, hassle-free. Your solution is neither of these. This approach just works. Instead of asking, why don't you just try?
MethodUtils (Apache Commons Lang) can be used to achieve the requirement.
API used in the example code : MethodUtils.getMethodsListWithAnnotation
Aspect Code
#Component
#Aspect
public class CallAnnotatedMethodAspect {
#Pointcut("within(rg.so.q64604586.service.*) && #annotation(rg.so.q64604586.annotation.ValidateAction)")
public void validateActionMethod() {
};
#Before("validateActionMethod() && args(request,..)")
public void adviceMethod(InputRequest request)
throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
List<Method> methodList = MethodUtils.getMethodsListWithAnnotation(request.getClass(), MyValidator.class);
for (Method m : methodList) {
m.invoke(request, new Object[] {});
}
}
}
Note : The Retention Policy of MyValidator annotation is RUNTIME for this code to work.
If the InputRequest instance obtained in the aspect is a proxy , the code will need to be modified to get the actual class of the same.
Related
I need to execute some checks before the execution of a method, but I need to get this method as an object by reflection and them pass it to another object as a callback. For example:
I have a view controller and I need to make an access control on some methods with an specific annotation. This method makes a navigation to an View that need a control access by password previously configurated.
#AccessControl(accessID = "bookKeysViewId")
private void navigateToBookKeys() {
navigateTo(ControllerPasswordBookKey.class);
}
Before the execution of the method I need check de access. For example:
pointcut makeAccessControl() : execution(#AccessControl * *(..));
before() : makeAccessControl(){
Method method = // any way to get the intercepted method
String idAccess = // get de access id from method annotation
EnumTypeAccess typeAccess = ManagerAccess.checkAccess(idAccess);
switch (typeAccess ){
case NEEDED: openPasswordDialog();break; // wrong password ? throw an exception and interrupt the method execution.
case NEED_CONFIG: // create configuration view, pass "method" as callback and navigate to it
}
}
Let us take a look at this little MCVE:
Marker annotation:
package de.scrum_master.app;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
#Retention(RUNTIME)
#Target(METHOD)
public #interface AccessControl {
String accessID();
}
Driver application:
package de.scrum_master.app;
public class Application {
public void doSomething() {
navigateToBookKeys();
}
#AccessControl(accessID = "bookKeysViewId")
private void navigateToBookKeys() {}
public static void main(String[] args) {
new Application().doSomething();
}
}
Aspect:
You can bind your method annotation to an advice parameter via #annotation(accessControl) and declaring that parameter and its type in the pointcut (or directly in the advice for inline pointcuts).
The Method object you want (for whatever reason) you can get via ((MethodSignature) thisJoinPoint.getSignature()).getMethod().
package de.scrum_master.aspect;
import java.lang.reflect.Method;
import org.aspectj.lang.reflect.MethodSignature;
import de.scrum_master.app.AccessControl;
public aspect AccessControlAspect {
pointcut makeAccessControl(AccessControl accessControl) :
#annotation(accessControl) &&
execution(* *(..));
before(AccessControl accessControl) : makeAccessControl(accessControl) {
Method method = ((MethodSignature) thisJoinPoint.getSignature()).getMethod();
String accessID = accessControl.accessID();
System.out.println(thisJoinPoint);
System.out.println(" method = " + method);
System.out.println(" accessID = " + accessID);
}
}
Console log:
execution(void de.scrum_master.app.Application.navigateToBookKeys())
method = private void de.scrum_master.app.Application.navigateToBookKeys()
accessID = bookKeysViewId
I am looking to mock out a Domain Dependency while I test a Service Class, using JMockit. The problem is, that the Domain object is instantiated in one of the Service's methods, and the Domain constructor used, has parameters.
Domain Class:
package com.example;
public class DomainClass {
String str;
public DomainClass(String s) {
str=s;
}
public String domainMethod() {
return str;
}
}
Service Class
package com.example;
public class ServiceClass {
public String serviceMethod() {
return new DomainClass("original").domainMethod();
}
}
How do I mock out the DomainClass that the ServiceClass is using?
Note: I am not looking to change the Domain or Service classes. (I realize that this code is is trivial and could be written better, but it is just a simple example of more complex code.)
Test Class (Final Answer)
package com.example;
import org.testng.*;
import org.testng.annotations.*;
import mockit.*;
public class TestClass {
#Mocked DomainClass domainClass;
#Test
public void testMethod() {
new Expectations() {{
new DomainClass("original").domainMethod(); result = "dummy";
}};
String ss = new ServiceClass().serviceMethod();
System.out.println(ss);
Assert.assertEquals(ss, "dummy");
}
}
The JMockit documentation should answer that.
Anyway, to mock a class, simply use #Mocked DomainClass (as mock field or test method parameter).
BTW, having new DomainClass("original").domainMethod() is not "bad design" (on the contrary, turning it into a singleton - injected or not - would be). However, mocking such classes may very well be a bad idea; always prefer not mocking.
I am using AspectJ with annotation and trying to find how to disable all AspectJ's advices to stop advicing method from user's input (e.g. Boolean tracked = false).
Here is my code for main class.
package testMaven;
public class MainApp {
public static void main(String[] args) {
testing test = new testing();
test.aa(1000);
test.setDd(3);
}
}
Here is the Aspect annotated class.
package testMaven;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.annotation.Before;
#Aspect
public class aspecter {
public aspecter(){
}
boolean tracked = false;
#Before("execution(* testMaven.testing.aa(..)) && if(tracked)")
public void testBefore(){
System.out.println("yooi");
}
#Before("execution(* testMaven.testing.setDd(..)) && if(tracked) ")
public void testBefore2(){
System.out.println("yooi2");
}
}
if(tracked) will give an error of "Syntax error on token "execution(* testMaven.testing.aa(..)) && if(tracked) ", "in annotation style, if(...) pointcuts cannot contain code. Use if() and put the code in the annotated method" expected".
Is there anyway that I could specify the if() method based on my specification?
Thanks
If using annotation style you have to do things a little differently, as the documentation describes ( https://eclipse.org/aspectj/doc/released/adk15notebook/ataspectj-pcadvice.html ). In your case your aspect needs to be something like this:
static boolean tracked = false;
#Pointcut("if()")
public static boolean tracked() {
return tracked;
}
#Before("execution(* testMaven.testing.aa(..)) && tracked()")
public void testBefore(){
System.out.println("yooi");
}
#Before("execution(* testMaven.testing.setDd(..)) && tracked() ")
public void testBefore2(){
System.out.println("yooi2");
}
Notice the code that would normally go into the if(...) clause in a code style aspect is now in a method body, which is tagged with #Pointcut using if(). I did have to make the field static. You could probably modify the code in the tracked() method to use Aspects.aspectOf() to keep it non static.
I'm writing a method supposed to retrieve all annotations of a specific method declaring class and its superclasses.
By using the method getAnnotations() on the declaring class, the resulting table contains only the declaring class annotations and the superclass annotations are ignored.
If I remove the annotations of the declaring class, then the superclass annotation are present.
What am I missing here?
The simplified method retrieving the annotations :
public void check(Method invokedMethod) {
for (Annotation annotation : invokedMethod.getDeclaringClass().getAnnotations()) {
// Do something ...
}
}
(All annotations I'm trying the get have the #Inherited annotation)
In case you need to process several annotations of the same type, the standard approach is does not work, because annotations are stored in a Map with annotation types as the key. (See more here). Here is how I would work around this problem (just go through all super classes manually):
import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Method;
public class AnnotationReflectionTest {
public static void main(String[] args) throws Exception {
check(Class2.class.getMethod("num", new Class[0]));
}
public static void check(Method invokedMethod) {
Class<?> type = invokedMethod.getDeclaringClass();
while (type != null) {
for (Annotation annotation : type.getDeclaredAnnotations()) {
System.out.println(annotation.toString());
}
type = type.getSuperclass();
}
}
}
#Inherited
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
#interface Annot1 {
int num();
}
#Annot1(num = 5)
class Class1 {
public int num() {
return 1;
}
}
#Inherited
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
#interface Annot2 {
String text();
}
#Annot2(text = "ttt")
class Class2 extends Class1 {
public int num() {
return super.num() + 1;
}
}
What version of Java and what OS do you use?
I had to write a simple method with
private <A extends Annotation> A getAnnotationFromType(Class<?> classType, final Class<A> annotationClass) {
while ( !classType.getName().equals(Object.class.getName()) ) {
if ( classType.isAnnotationPresent(annotationClass)) {
return classType.getAnnotation(annotationClass);
}
classType = classType.getSuperclass();
}
return null;
}
This might be obvious to most people, but if you came here looking for fields of a class and its superclasses, you can use
myClass.getFields()
to get all fields, also of superclasses, instead of
myClass.getDeclaredFields()
which only returns the fields of the class itself. Similarly for methods and constructors.
Currently I have the standard one:
#DeclareParents(value = "(#moody.MyAttribute *)", defaultImpl = MoodyImpl.class)
This will add my interface+implementation to any class with #MyAttribute
I would like to do this for all classes that have this attribute AND/OR have a method with that attribute.
So this class should also get my interface+implementation:
class MyClass {
#MyAttribute
public void test()
{
}
}
Is that possible?
No, because both #DeclareParents and the newer #DeclareMixin need class name specifications in their value parameter. If I were you I would refactor my annotation so as to only be applicable to classes, not methods, and then my code to move all annotations to classes as well.
One more option if you absolutely want to stay on your path: Since AspectJ 1.8.2 there is a new annotation processing feature. You might want to explore that one and create an annotation processor creating an ITD aspect for each affected class with annotated methods.
Update: I have just remembered a non-standard compiler option -XhasMember which you can use:
ajc -X
AspectJ Compiler 1.8.2 non-standard options:
(...)
-XhasMember allow hasmethod() and hasfield type patterns in
declare parents and declare #type
(...)
Caveat: It does not seem to work with #AspectJ syntax, i.e. you cannot use annotation-style #DeclareParents but have to use native AspectJ syntax declare parents. The default interface implementation is also done differently, i.e. via ITD in the aspect, there is no need for a specific implementation class.
Here is a compileable, fully self-consistent code sample:
Marker annotation:
package de.scrum_master.app;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
#Retention(RetentionPolicy.RUNTIME)
#Target({ ElementType.TYPE, ElementType.METHOD })
public #interface MyAttribute {}
Interface to be implemented via ITD:
package de.scrum_master.app;
public interface Moody {
public void sayHelloTo(String name);
}
Sample classes with(out) marker annotation:
Class Foo has the annotation at class level, Bar at method level and Zot has no annotation at all.
package de.scrum_master.app;
#MyAttribute
public class Foo {
public static void foo() {}
}
package de.scrum_master.app;
public class Bar {
#MyAttribute
public static void bar() {}
}
package de.scrum_master.app;
public class Zot {
public static void zot() {}
}
Driver application:
For demonstration purposes, the application checks for the existence of a method sayHelloTo(String) via reflection.
package de.scrum_master.app;
import java.lang.reflect.Method;
public class Application {
public static void main(String[] args) throws Exception {
Method method;
try {
method = Foo.class.getDeclaredMethod("sayHelloTo", String.class);
} catch (NoSuchMethodException nsme) {
method = null;
}
System.out.println("Foo: " + method);
try {
method = Bar.class.getDeclaredMethod("sayHelloTo", String.class);
} catch (NoSuchMethodException nsme) {
method = null;
}
System.out.println("Bar: " + method);
try {
method = Zot.class.getDeclaredMethod("sayHelloTo", String.class);
} catch (NoSuchMethodException nsme) {
method = null;
}
System.out.println("Zot: " + method);
}
}
Console output (without aspect):
Foo: null
Bar: null
Zot: null
Aspect:
package de.scrum_master.aspect;
import de.scrum_master.app.Moody;
import de.scrum_master.app.MyAttribute;
public aspect ITDAspect {
declare parents : #MyAttribute * implements Moody;
declare parents : hasmethod(#MyAttribute * *(..)) implements Moody;
public void Moody.sayHelloTo(String name) {
System.out.println("Hello " + name);
}
}
Console output (with aspect):
Foo: public void de.scrum_master.app.Foo.sayHelloTo(java.lang.String)
Bar: public void de.scrum_master.app.Bar.sayHelloTo(java.lang.String)
Zot: null
VoilĂ ! We have successfully added the interface including its default implementation to Bar which does not have class-level annotation, but a method-level one.
Enjoy!