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.
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 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 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 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 want to log all method call i make in my code except the ones within the logger, using AspectJ.
#Aspect
public class Logger
{
// Point Cuts
//-----------
#Pointcut("execution(* org.myDomain.*..*.*(..))")
public void selectAll(){}
#Pointcut("within(Logger) && call(* *(..))")
public void codeWithinAspect(){}
// Advices
//-----------
#Before("selectAll()")
public void adviceThatWorksFine(JoinPoint joinPoint)
{
System.out.print(joinPoint.getSignature().toString());
//Utils.printToConsole(joinPoint.getSignature().toString());
}
#Before("selectAll() && !codeWithinAspect")
public void adviceWithInfiniteLoop(JoinPoint joinPoint)
{
//System.out.print(joinPoint.getSignature().toString());
Utils.printToConsole(joinPoint.getSignature().toString());
}
}
the first advice in the class works fine (it writes every method call to the console), the second advice causes an infinite loop when calling org.myDomain.utils.Utils.printToConsole() method, which is advised by the calling advice.
I have found it is a common problem as described in the link
http://www.eclipse.org/aspectj/doc/released/faq.php#q:infiniterecursion
but i could not understand how to write the pointcut so an infinite loop would not be created.
plaes help
There are several problems in your code:
!codeWithinAspect needs parentheses: !codeWithinAspect()
adviceWithInfiniteLoop() combines execution() and call() pointcuts in this way: execution(foo) && !call(bar). Because a call joinpoint can never be an execution joinpoint the second part of the condition is always true and has no effect. Thus, it does not avoid the infinite loop.
You do not just want to exclude joinpoints within aspect Logger but also those within the control flow (cflow()) of that aspect's methods, i.e. stuff directly or indirectly called by them.
The solution is as follows:
Utility class for log output:
package org.myDomain.app;
public class Utils {
public static void printToConsole(Object object) {
System.out.println(object);
}
}
Driver application:
package org.myDomain.app;
public class Application {
public static void sayHelloTo(String counterpart) {
Utils.printToConsole("Hello " + counterpart + "!");
}
public static void main(String[] args) {
sayHelloTo("world");
}
}
Logger aspect:
package org.myDomain.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.myDomain.app.Utils;
#Aspect
public class Logger {
#Pointcut("execution(* org.myDomain..*(..))")
public void selectAll() {}
#Pointcut("cflow(within(Logger))")
public void codeWithinAspect() {}
#Before("selectAll() && !codeWithinAspect()")
public void advice(JoinPoint joinPoint) {
Utils.printToConsole(joinPoint);
}
}
Console output:
execution(void org.myDomain.app.Application.main(String[]))
execution(void org.myDomain.app.Application.sayHelloTo(String))
execution(void org.myDomain.app.Utils.printToConsole(Object))
Hello world!
Enjoy!
Update: If you want to exclude all advice execution control flows you can also use this pointcut:
#Pointcut("cflow(adviceexecution())")
public void codeWithinAspect() {}