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
Related
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.
I am trying to execute a set code in my advice but can't able to weave the code inside the function which has the #SecuredAPI annotation and calls the
setQuery() function.
Previously I tried the following pointcut and it worked very well
call(* org.elasticsearch.action.search.SearchRequestBuilder.setQuery(org.elasticsearch.index.query.QueryBuilder)) && args(queryBuilder)
But need to also include the annotated condition in this. Please help me with that.
My poincut and advice looks like this
#Around(value = "#annotation(SecuredAPI) && call(* org.elasticsearch.action.search.SearchRequestBuilder.setQuery(org.elasticsearch.index.query.QueryBuilder)) && args(queryBuilder)" )
public Object decorateQuery(ProceedingJoinPoint proceedingJoinPoint, QueryBuilder queryBuilder) throws Throwable {
// ...
}
And my function looks like this
#SecuredAPI
public List<Integer> getAllIds() {
// ...
SearchResponse response = conn
.getESClient().prepareSearch(ElasticSearchConstants.ATTRIBUTE_INDEX)
.setSearchType(SearchType.DEFAULT)
//.setQuery(QueryBuilders.queryStringQuery(searchQuery))
.setQuery(qb)
.setFrom(0).setSize(10000).setExplain(true).get();
}
Please help me to find a way to may it work for following condition
Okay, while editing your question (the code formatting was a bit chaotic) I read it again and you said that call() actually works for you. So you are not using Spring AOP because call() is not supported there. You must be using AspectJ, probably via LTW (load-time weaving) or maybe via CTW (compile-time weaving). It does not make a difference for the answer.
The problem is that #annotation(SecuredAPI) would actually work inside an execution() pointcut defined on your annotated method, but the method you call from there is not annotated, so the advice does not get triggered for call(). It only would if the target method setQuery(..) was annotated, but it is not. Consequently, #annotation() is not the right pointcut for your purpose.
What you want to express is: "a call to setQuery(..) from within code annotated by #SecuredAPI". This is done as follows (AspectJ example without Spring, please adjust class and package names to your needs):
package de.scrum_master.app;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
#Retention(RUNTIME)
#Target({ TYPE, FIELD, METHOD })
public #interface SecuredAPI {}
package de.scrum_master.app;
public class Application {
public static void main(String[] args) {
Application application = new Application();
application.doSomething();
application.doSomethingElse();
}
#SecuredAPI
public void doSomething() {
System.out.println("Doing something before setting query");
setQuery("my first query");
System.out.println("Doing something after setting query");
}
public void doSomethingElse() {
System.out.println("Doing something else before setting query");
setQuery("my second query");
System.out.println("Doing something else after setting query");
}
public void setQuery(String query) {
System.out.println("Setting query to: " + query);
}
}
package de.scrum_master.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
#Aspect
public class SecuredAPIAspect {
#Around("#withincode(de.scrum_master.app.SecuredAPI) && call(* setQuery(..))")
public Object myAdvice(ProceedingJoinPoint thisJoinPoint) throws Throwable {
System.out.println(thisJoinPoint);
return thisJoinPoint.proceed();
}
}
See? #withincode() is your friend in this case. The console log looks as follows:
Doing something before setting query
call(void de.scrum_master.app.Application.setQuery(String))
Setting query to: my first query
Doing something after setting query
Doing something else before setting query
Setting query to: my second query
Doing something else after setting query
Besides, you also need to use a fully-qualified class name for the annotation such as de.scrum_master.app.SecuredAPI, not just SecuredAPI, unless the annotation happens to be in the same package as your aspect.
I want to inject code in methods at compile time. I tried aspectj but it have problems. I cannot make it work properly. Also it is poorly supported in Android Studio.
example of what I want-
Before compilation
public void doSomething() {
doing..
}
After Compiling the code should look
public void doSomething() {
code injected here
doing..
code injected here
}
how can I do something like annotate the method #aroundMethod and it can generate that code
AspectJ is used for this kind of things. Here is a nice Android example how to do aspects in the Android world: https://github.com/firstthumb/AspectJ-Android-Example
One example from their code:
package android.mobile.peakgames.net.aspectjandroid.aspect;
import android.util.Log;
import android.widget.Button;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
#Aspect
public class LoggingAspect {
private static final String TAG = LoggingAspect.class.getName();
#Pointcut("execution(* android.view.View.OnClickListener.onClick(..))")
public void onClickEntryPoint() {
}
#Before("onClickEntryPoint()")
public void onClickBefore(JoinPoint joinPoint) {
Log.d(TAG, "Before Advice ==> Clicked on : " + ((Button)joinPoint.getArgs()[0]).getText());
}
#Around("onClickEntryPoint()")
public void onClickAround(ProceedingJoinPoint joinPoint) throws Throwable {
Log.d(TAG, "Around Advice ==> Clicked on : " + ((Button)joinPoint.getArgs()[0]).getText());
joinPoint.proceed();
}
#After("onClickEntryPoint()")
public void onClickAfter(JoinPoint joinPoint) {
Log.d(TAG, "After Advice ==> Clicked on : " + ((Button)joinPoint.getArgs()[0]).getText());
}
#AfterReturning(pointcut = "onClickEntryPoint()")
public void onClickAfterReturning() {
Log.d(TAG, "AfterReturning Advice ==>");
}
}
In general, the idea is to register an aspect class with something like #Aspect and then define when a particular method is going to be called using #Pointcut, #Before, #Around and #After which methods will be affected by the aspect. For example, method onClickBefore will be executed before someone executes onClickEntryPoint from this class. The JoinPoint argument can be used for different purposes like getting arguments of the method affected by the aspect.
I've got a custom #Traceable annotation with a TraceAspect implementation. We use the #Traceable annotation on methods where we want to log before and after the method invocation. We've just added the ability to specify a log level to use via value attribute within #Traceable (the old version always just used INFO). What I have works, but I am wondering if there is a way to make it more performant. Specifically, checking the value set on #Traceable for a given method could be done once at app startup if there was some sort of aspect context.
#Traceable annotation:
#Documented
#Retention(RUNTIME)
#Target(METHOD)
public #interface Traceable {
Level value() default Level.INFO;
}
Current TraceAspect impl:
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.event.Level;
import org.springframework.stereotype.Component;
#Component
#Aspect
public class TraceAspect {
#Around("#annotation(com.collaterate.logging.Traceable)")
public Object traceAround(ProceedingJoinPoint joinPoint) throws Throwable {
Traceable traceable = ((MethodSignature) joinPoint.getSignature()).getMethod().getAnnotation(Traceable.class);
Logger classLog = LoggerFactory.getLogger(joinPoint.getSignature().getDeclaringType());
LoggingHelper loggingHelper = getLoggingHelper(traceable, classLog);
String methodName = joinPoint.getSignature().getName();
loggingHelper.log("{}() - started", methodName);
Object returnVal = joinPoint.proceed();
loggingHelper.log("{}() - ended", methodName);
return returnVal;
}
private LoggingHelper getLoggingHelper(Traceable traceable, Logger classLog) {
if (Level.INFO == traceable.value() || null == traceable.value()) {
// try to avoid the switch below... most of the time it will be INFO
return (format, args) -> classLog.info(format, args);
} else {
switch (traceable.value()) {
case ERROR :
return (format, args) -> classLog.error(format, args);
case WARN :
return (format, args) -> classLog.warn(format, args);
case DEBUG :
return (format, args) -> classLog.debug(format, args);
case TRACE :
return (format, args) -> classLog.trace(format, args);
default :
return (format, args) -> classLog.info(format, args);
}
}
}
#FunctionalInterface
interface LoggingHelper {
void log(String format, Object... args);
}
}
The only other idea I had is to create multiple annotations (one for each log level) and then the TraceAspect would impl an #Around handler for each annotation and we avoid the reflection / switch at runtime. What I don't like about this is we already use the existing #Traceable annotation all over production code in multiple projects. I would like to keep the 1 annotation and allow it to specify log level via the attribute.
In theory, what I want to do should be possible since all the info is there at app startup when the proxy is created. There would just have to be some kind of context for each annotated method.
I have done something similar wrt to metrics. You can maintain a log registry with class and method pair as the key and LoggingHelper as the value.
If the class method pair doesn't have an entry in the log registry, create a logging helper and store it in the registry. The second time around, you just look it up in the registry.
The registry is a custom Spring bean which should be autowired into your aspect.
Here is an example of modified TraceAspect.
#Component
#Aspect
public class TraceAspect {
private LogRegistry registry;
#Autowired
public TraceAspect(LogRegistry registry) {
this.registry = registry;
}
#Around("#annotation(com.collaterate.logging.Traceable)")
public Object traceAround(ProceedingJoinPoint joinPoint) throws Throwable {
String loggerName = joinPoint.getSignature()
.getDeclaringType().getCanonicalName() + "."
+ joinPoint.getSignature().getName();
LoggingHelper loggingHelper = registry.get(loggerName);
if (loggingHelper == null) {
Traceable traceable = ((MethodSignature) joinPoint.getSignature()).getMethod().getAnnotation(Traceable.class);
Logger classLog = LoggerFactory.getLogger(joinPoint.getSignature().getDeclaringType());
loggingHelper = getLoggingHelper(traceable, classLog);
registry.put(loggerName, loggingHelper)
}
String methodName = joinPoint.getSignature().getName();
loggingHelper.log("{}() - started", methodName);
Object returnVal = joinPoint.proceed();
loggingHelper.log("{}() - ended", methodName);
return returnVal;
}
}
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!