how to pick set joinpoints in specific methods in AspectJ - java

I'm developing an Advice and I want to make it advise only on field sets in specific methods.
I tried cflow(pointcutForSpecificMethod()) && set(* *) pointcut expression but it picks field sets in other methods under control flow of specific methods.
Any idea?

This is not possible directly with an exact pointcut expression, but you can use if() pointcuts to dynamically determine from the stack trace or - like in this case - from the enclosing join point static part exposed by AspectJ - what the executing method is. Here is a little example and an aspect in two variants: native syntax (my preference, more elegant and less boilerplate) and annotation-style syntax:
package de.scrum_master.app;
public class Application {
private int field = 0;
public static void main(String[] args) {
Application app = new Application();
app.foo(11);
app.bar(22);
}
public void foo(int i) {
field = i;
bar(2 * i);
}
void bar(int i) {
field = i;
}
}
package de.scrum_master.aspect;
import org.aspectj.lang.JoinPoint;
public aspect MyAspectNative {
pointcut pointcutForSpecificMethod() : execution(* foo(*));
public static boolean executingMethodMatches(JoinPoint.StaticPart staticPart) {
return staticPart.getSignature().toLongString().contains("de.scrum_master.app.Application.foo(int)");
}
before() :
cflow(pointcutForSpecificMethod()) && set(* *) &&
if(executingMethodMatches(thisEnclosingJoinPointStaticPart))
{
System.out.println(thisEnclosingJoinPointStaticPart);
System.out.println(thisJoinPoint);
}
}
package de.scrum_master.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
#Aspect
public class MyAspect {
#Pointcut("execution(* foo(*))")
private static void pointcutForSpecificMethod() {}
#Pointcut("if()")
public static boolean executingMethodMatches(JoinPoint.EnclosingStaticPart staticPart) {
return staticPart.getSignature().toLongString().contains("de.scrum_master.app.Application.foo(int)");
}
#Before(
"cflow(pointcutForSpecificMethod()) && set(* *) && " +
"executingMethodMatches(thisEnclosingJoinPointStaticPart)"
)
public void beforeAdvice(JoinPoint thisJoinPoint, JoinPoint.EnclosingStaticPart thisEnclosingJoinPointStaticPart)
{
System.out.println(thisEnclosingJoinPointStaticPart);
System.out.println(thisJoinPoint);
}
}
I tried to keep the two aspects as similar as possible structurally. No matter which aspect syntax variant you choose, the output will be:
execution(void de.scrum_master.app.Application.foo(int))
set(int de.scrum_master.app.Application.field)

Related

Intercept all function calls inside a intercepted function by aspectj

I am new to AspectJ and reflections What I want to achieve is something like In the below example:
A test class :
public class Sample {
Home home = new Home();
Account account = new Account();
AccountAuthentication accountAuthentication = new AccountAuthentication();
#Test
public void loginInvalidCredentials(){
home.clickOnAccount();
account.login("Admin", "secret");
accountAuthentication.waitForPage();
Assert.assertTrue(true);
}
}
I want to log Output something like this :
packageName.Sample.loginInvalidCredentials
packageName.Home.clickOnAccount();
packageName.Account.login(userName = "Admin", Password ="secret");
packageName.AccountAuthentication.waitForPage();
packageName.Assert.assertTrue(value= true);
I have accessed the name of function packageName.Sample.loginInvalidCredentials with AspectJ
#Aspect
public class AspectClass {
#Around("execution(* *(..)) && #annotation(org.testng.annotations.Test)")
public void around(ProceedingJoinPoint point) throws Throwable {
Method method = MethodSignature.class.cast(point.getSignature()).getMethod();
String methodName = method.getName();
System.out.println("Aspect called for method "+ point.getSignature().getDeclaringType().name +"."+methodName);
try {
//TODO intercept each function call inside the method without any custom anotation and get the value of parameters as well
joinPoint.proceed();
}
}
}
Thanks in advance.
I am assuming that
you use full AspectJ and not something like Spring AOP (because then the answer would not apply),
you do not want to record method calls recursively but just the ones directly called from your #Test methods. For example, if Account.login(..) would internally call Account.checkPassword(..), it should not be recorded. That would also be possible, but then the solution would look differently.
all you want to do is log test execution and not do anything else, so an #Around advice is not necessary, #Before is enough.
Dummy application classes:
package de.scrum_master.app;
public class Account {
public void login(String user, String password) {
checkPassword(password);
}
public void checkPassword(String password) {}
}
package de.scrum_master.app;
public class AccountAuthentication {
public void waitForPage() {}
}
package de.scrum_master.app;
public class Home {
public void clickOnAccount() {
doSomethingElse();
}
public void doSomethingElse() {}
}
Marker annotation:
I created this one myself because I was too lazy to set up a project with TestNG which I normally do not use.
package org.testng.annotations;
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 Test {}
Sample application:
package de.scrum_master.app;
import org.testng.annotations.Test;
public class Sample {
Home home = new Home();
Account account = new Account();
AccountAuthentication accountAuthentication = new AccountAuthentication();
#Test
public void loginInvalidCredentials() {
home.clickOnAccount();
account.login("Admin", "secret");
accountAuthentication.waitForPage();
}
public static void main(String[] args) {
new Sample().loginInvalidCredentials();
}
}
Aspect:
package de.scrum_master.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
#Aspect
public class TestExecutionLogger {
#Before("execution(* *(..)) && #annotation(org.testng.annotations.Test)")
public void logTest(JoinPoint joinPoint) {
System.out.println(joinPoint);
}
#Before("call(* *(..)) && withincode(#org.testng.annotations.Test * *(..))")
public void logTestActions(JoinPoint joinPoint) {
System.out.println(" " + joinPoint);
}
}
Console log:
execution(void de.scrum_master.app.Sample.loginInvalidCredentials())
call(void de.scrum_master.app.Home.clickOnAccount())
call(void de.scrum_master.app.Account.login(String, String))
call(void de.scrum_master.app.AccountAuthentication.waitForPage())
Of course you can refine your aspect if you really think you need to strip the plain method names from the informative AspectJ log output:
#Before("execution(* *(..)) && #annotation(org.testng.annotations.Test)")
public void logTest(JoinPoint joinPoint) {
System.out.println(joinPoint.getSignature());
}
#Before("call(* *(..)) && withincode(#org.testng.annotations.Test * *(..))")
public void logTestActions(JoinPoint joinPoint) {
System.out.println(" " + joinPoint.getSignature());
}
void de.scrum_master.app.Sample.loginInvalidCredentials()
void de.scrum_master.app.Home.clickOnAccount()
void de.scrum_master.app.Account.login(String, String)
void de.scrum_master.app.AccountAuthentication.waitForPage()
You can further refine it if you deliberately want to remove the method's return type or include the actual parameter values like in your example, which is easily possible via JoinPoint.getArgs(), but I leave that up to you. Your question was about pointcuts, not about how to extract which information from joinpoints.

AspectJ: Intercept return result of method inside another method

I need help to write some Aspectj advice on this particular case:
Suppose we have this class:
package org.group;
public class Person {
public void method1(String id, String number) {
//some code
List<String> list = getList(number);
//some code
}
public List<String> getList(String number) {
return Arrays.asList(number);
}
}
I want to create an Aspectj advice into method1 to get the result of getList. I try this:
#Pointcut("execution(* org.group.Person.getList(..))")
public void methodGetList() {
}
#AfterReturning(pointcut = "methodGetList()", returning = "result")
public void afterMethodGetList(JoinPoint joinPoint, List<String> result) {
System.out.println("I can see the list result: " + result.toString());
}
This advice works on all executions of getList method, but what I want exactly, is to get the result inside the method1 call to get an information with the method1's id , for example like this:
'I can see the list result [4] for the person with id : XXX'
Thank you for your help.
You need to limit your pointcut to the executions within the control flow - cflow() - of the calling method and also bind the calling method's parameter of interest via args().
Application:
package org.group;
import java.util.Arrays;
import java.util.List;
public class Person {
public void method1(String id, String number) {
// some code
List<String> list = getList(number);
// some code
}
public List<String> getList(String number) {
return Arrays.asList(number);
}
public static void main(String[] args) {
// Should not be intercepted
new Person().getList("22");
// Should be intercepted
new Person().method1("John Doe", "11");
}
}
Aspect:
package de.scrum_master.aspect;
import java.util.List;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
#Aspect
public class MyAspect {
#Pointcut("execution(* org.group.Person.getList(..))")
public void methodGetList() {}
#Pointcut("execution(* org.group.Person.method1(..)) && args(id, *)")
public void methodMethod1(String id) {}
#AfterReturning(
pointcut = "methodGetList() && cflow(methodMethod1(id))",
returning = "result"
)
public void afterMethodGetList(JoinPoint joinPoint, String id, List<String> result) {
System.out.println(
"I can see the list result " + result +
" for the person with id " + id
);
}
}
Console log:
I can see the list result [11] for the person with id John Doe

pointcut expression to parse #ModelAttribute as method parameter

I have a #ModelAttribute(key) defined in the #controllerAdvice and i am uisng the same model attribute in multiple controller methods as method parameter because the (key) will be available throughout all the controllers.
I am adding the attribute (key) in controller class like this.
#RequestMapping(value = "/", method = RequestMethod.GET)
public String list(final Model model,#ModelAttribute("key") final boolean key) {
...
...
}
I want to intercept all the controller methods which has #ModelAttribute("key") as the method parameter.
My aspect file looks like this.
#Component
#Aspect
#EnableAspectJAutoProxy
public class myAspectclass {
#Pointcut("execution(public * *(.., #org.springframework.web.bind.annotation.ModelAttribute(boolean key)))")
public void methodWithAnnotatedParameter() {}
#Around("methodWithAnnotatedParameter()")
public String blahMethod(ProceedingJoinPoint PJP){
blah blah
....
}
However my server startup fails saying
[tomcat:launch] Caused by: java.lang.IllegalArgumentException: Pointcut is not well-formed: expecting 'name pattern' at character position 97
[tomcat:launch] execution(public * *(.., #org.springframework.web.bind.annotation.ModelAttribute(boolean key), ..))
I am unable to understand the error in this case... am i doing something wrong syntactically ?
Note: My ModelAttribute(key) does not have any specific position in the parameter list
Referenced this answer:
Here is s stand-alone AspectJ example (not a Spring application, but aspect syntax should be the same).
Driver application:
As you can see, there are several methods with and without #ModelAttribute parameter annotations, one of them even with two annotated parameters (whether it makes sense or not, but it is just an example).
package de.scrum_master.app;
import java.util.Collection;
import java.util.Map;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
public class Application {
public String list(Model model, #ModelAttribute("key") boolean key) {
return "x";
}
public String foo(Model model, #ModelAttribute("key") boolean key, #ModelAttribute(name = "key") String text) {
return "x";
}
public String bar(#ModelAttribute(name = "key") int number) {
return "x";
}
public String zot(Model model, #ModelAttribute("XXX") boolean key) {
return "x";
}
public String baz(Model model, boolean key, String text) {
return "x";
}
public String bla(#ModelAttribute("XXX") int number) {
return "x";
}
public static void main(String[] args) {
Model model = new Model() {
#Override public Model mergeAttributes(Map<String, ?> arg0) { return null; }
#Override public boolean containsAttribute(String arg0) { return false; }
#Override public Map<String, Object> asMap() { return null; }
#Override public Model addAttribute(String arg0, Object arg1) { return null; }
#Override public Model addAttribute(Object arg0) { return null; }
#Override public Model addAllAttributes(Map<String, ?> arg0) { return null; }
#Override public Model addAllAttributes(Collection<?> arg0) { return null; }
};
Application application = new Application();
application.list(model, true);
application.foo(model, true, "hey");
application.bar(11);
application.zot(model, true);
application.baz(model, true, "hey");
application.bla(11);
}
}
Aspect:
The aspect matches all method executions having at least one parameter with a #ModelAttribute annotation. The pointcut would be enough if you only want to finde those methods, regardless of annotation parameter values. But as you said, you only want to match the ones which have value = "key", we need to use reflection in order to look into the annotations themselves and filter out the unwanted ones.
Another complication that according to #ModelAttribute JavaDoc, parameters name and value are aliases for each other, i.e. we also need to check both parameters' values in order to get it completely right.
package de.scrum_master.aspect;
import java.lang.annotation.Annotation;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.ModelAttribute;
#Aspect
#Component
public class ModelAttributeInterceptor {
#Pointcut("execution(public * *(.., #org.springframework.web.bind.annotation.ModelAttribute (*), ..))")
public void methodWithAnnotatedParameter() {}
#Around("methodWithAnnotatedParameter()")
public String blahMethod(ProceedingJoinPoint thisJoinPoint) throws Throwable {
MethodSignature methodSignature = (MethodSignature) thisJoinPoint.getSignature();
Annotation[][] annotationMatrix = methodSignature.getMethod().getParameterAnnotations();
boolean foundModelAttribute = false;
for (Annotation[] annotations : annotationMatrix) {
for (Annotation annotation : annotations) {
if (!(annotation instanceof ModelAttribute))
continue;
ModelAttribute modelAttribute = (ModelAttribute) annotation;
if ("key".equals(modelAttribute.value()) || "key".equals(modelAttribute.name())) {
if (!foundModelAttribute) {
System.out.println(thisJoinPoint);
foundModelAttribute = true;
}
System.out.println(" " + modelAttribute);
}
}
}
return (String) thisJoinPoint.proceed();
}
}
Console log:
execution(String de.scrum_master.app.Application.list(Model, boolean))
#org.springframework.web.bind.annotation.ModelAttribute(name=, value=key, binding=true)
execution(String de.scrum_master.app.Application.foo(Model, boolean, String))
#org.springframework.web.bind.annotation.ModelAttribute(name=, value=key, binding=true)
#org.springframework.web.bind.annotation.ModelAttribute(name=key, value=, binding=true)
execution(String de.scrum_master.app.Application.bar(int))
#org.springframework.web.bind.annotation.ModelAttribute(name=key, value=, binding=true)

How to intercept each method call within given method using Spring AOP or AspectJ

class Test {
#override
public String a(){
b();
d();
}
private String b() {
c();
}
private String c(){
d();
}
private String d(){}
}
I want to intercept each methods of class Test that is been called from overridden method A() and want to know how much time each method like b(), c() took while processing some business logic separately.
How can I achieve it using Spring AOP or Aspectj?
In order to
weave into private methods,
handle self-invocation within one class,
dynamically determine control flow and limit interception to only methods called directly or indirectly by your interface method
you need to switch from Spring AOP (proxy-based, many limitations, slow) to AspectJ using LTW (load-time weaving) as described in the Spring manual.
Here is an example in pure AspectJ (no Spring, Just Java SE) which you can easily adapt to your needs:
Sample interface
package de.scrum_master.app;
public interface TextTransformer {
String transform(String text);
}
Class implementing interface incl. main method:
As you can see, I made up an example like yours and also made the methods spend time in order to have something to measure in the aspect later:
package de.scrum_master.app;
public class Application implements TextTransformer {
#Override
public String transform(String text) {
String geekSpelling;
try {
geekSpelling = toGeekSpelling(text);
return toUpperCase(geekSpelling);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
private String toGeekSpelling(String text) throws InterruptedException {
Thread.sleep(100);
return replaceVovels(text).replaceAll("[lL]", "1");
}
private String replaceVovels(String text) throws InterruptedException {
Thread.sleep(75);
return text.replaceAll("[oO]", "0").replaceAll("[eE]", "Ɛ");
}
private String toUpperCase(String text) throws InterruptedException {
Thread.sleep(50);
return text.toUpperCase();
}
public static void main(String[] args) throws InterruptedException {
System.out.println(new Application().transform("Hello world!"));
}
}
Aspect:
package de.scrum_master.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import static java.lang.System.currentTimeMillis;
#Aspect
public class TimingAspect {
#Around("execution(* *(..)) && cflow(execution(* de.scrum_master.app.TextTransformer.*(..)))")
public Object measureExecutionTime(ProceedingJoinPoint thisJoinPoint) throws Throwable {
long startTime = currentTimeMillis();
Object result = thisJoinPoint.proceed();
System.out.println(thisJoinPoint + " -> " + (currentTimeMillis() - startTime) + " ms");
return result;
}
}
Console log:
execution(String de.scrum_master.app.Application.replaceVovels(String)) -> 75 ms
execution(String de.scrum_master.app.Application.toGeekSpelling(String)) -> 189 ms
execution(String de.scrum_master.app.Application.toUpperCase(String)) -> 63 ms
execution(String de.scrum_master.app.Application.transform(String)) -> 252 ms
HƐ110 W0R1D!
You can also exclude the transform(..) method by just changing the pointcut from cflow() to cflowbelow():
#Around("execution(* *(..)) && cflowbelow(execution(* de.scrum_master.app.TextTransformer.*(..)))")
Then the console log is just:
execution(String de.scrum_master.app.Application.replaceVovels(String)) -> 77 ms
execution(String de.scrum_master.app.Application.toGeekSpelling(String)) -> 179 ms
execution(String de.scrum_master.app.Application.toUpperCase(String)) -> 62 ms
HƐ110 W0R1D!
Incidentally, please do read an AspectJ and/or Spring AOP manual.
Spring AOP is applied using proxies, when you call a method of the bean from outside, the proxy is used and the method could be intercepted, but when you call the method from inside the class, the proxy is not used and the class is used directly.
You have three options
The first and easy one, if you do not have problems using public methods is to move the functions b(), c(), and d() to another bean. This way each call to this methods would be intercepted.
public class Test {
public String a() { ... }
}
public class Test2 {
public String b() { ... }
public String c() { ... }
public String d() { ... }
}
You can also use it as inner static class if you want to keep all in the same file.
public class Test {
public String a() { ... }
public static class Test2 {
public String b() { ... }
public String c() { ... }
public String d() { ... }
}
}
You should autowire Test2 in the constructor of Test.
public class Test {
private final Test2 test2;
#Autowired public Test(final Test2 test2) {
this.test2 = test2;
}
public String a() {
test2.b();
test2.c();
test2.d();
}
}
And finally create the around method.
#Around(value = "execution(* package.of.the.class.Test.*(..))")
public Object aroundA(ProceedingJoinPoint pjp) throws Throwable { ... }
#Around(value = "execution(* package.of.the.class.Test2.*(..))")
public Object aroundBCD(ProceedingJoinPoint pjp) throws Throwable {
long start = System.currentTimeMillis();
Object output = pjp.proceed();
long elapsedTime = System.currentTimeMillis() - start;
// perform side efects with elapsed time e.g. print, store...
return output;
}
Or something like
#Around(value = "execution(* package.of.the.class.Test.*(..)) || " +
"execution(* package.of.the.class.Test2.*(..))")
public Object aroundABCD(ProceedingJoinPoint pjp) throws Throwable { ... }
The second option is to use a CGLIB bean, package private methods and self injection.
You declare a CGLIB bean just using the scope annotation
#Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
#Bean public Test test() {
return new Test();
}
Self injection and package private methods as follows
public class Test {
#Autowired private Test test;
// ...
public String a() {
test.b(); // call through proxy (it is intercepted)
}
String b() { ... } // package private method
// ...
}
The third solution is to use LWT Load-Time weaving that is using aspectj instead of the spring aop. This allows to intercept method calls even inside the same class. You can use the official spring documentation to implement it, but you will have to use a java agent to run.
Calling method
If you need to know if an specific method made the call to the intercepted function, you can use Thread.currentThread().getStackTrace() for the options 1 or 2. If you use aspectj (option 3), you could intercept the methods with cflow().

Combining multiple pointcuts AspectJ returns adviceDidNotMatch warning

I am trying to combining multiple pointcuts of getter and setter to create an advice that will be executed if both pointcuts are executed. I have tried in normal AspectJ class and annotation #Aspect class but still it gives me warning adviceDidNotMatch and eventually the advice is not executed. Strangely if I change && (AND) with || (OR) it works, but why && doesn't work at all?
Here is the advice declared in normal AspectJ class.
package testMaven;
pointcut getter() : execution(* testMaven.testing.getDd(..));
before() : getter(){
System.out.println("test get");
}
pointcut setter() : execution(* testMaven.testing.setDd(..));
before() : setter(){
System.out.println("test set");
}
pointcut combine(): getter() && setter();
before(): combine(){
System.out.println("testing combine");
}
}
Here is the advice declared in annotation #Aspect class
package testMaven;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.annotation.Before;
#Aspect
public class aspecter {
#Pointcut("call (* testMaven.testing.getDd(..))")
public void getter(){
}
#Pointcut("call (* testMaven.testing.setDd(..))")
public void setter(){}
#Pointcut("execution (* testMaven.tester.setZ(..))")
public void setterZ(){}
#Before("setterZ()")
public void settingZ(){
System.out.println("before set Z");
}
#Pointcut("getter() && setter()")
public void getterSetter(){}
#After("getterSetter()")
public void testerd(){
System.out.println("works");
}
#Pointcut("getter() && setterZ()")
public void getterSetter2(){}
#After("getterSetter2()")
public void testinger(){
System.out.println("ok");
}
}
Here is the testing class that I want to be advised:
package testMaven;
public class testing {
public int dd;
public int getDd() {
return dd;
}
public void setDd(int dd) {
this.dd = dd;
}
}
package testMaven;
public class testing {
public int dd;
public int getDd() {
return dd;
}
public void setDd(int dd) {
this.dd = dd;
}
public void aa(int a){
System.out.println(a);
}
}
And here is the main class:
package testMaven;
public class MainApp {
public static void main(String[] args) {
// TODO Auto-generated method stub
testing test = new testing();
test.aa(2);
test.setDd(3);
tester et = new tester();
et.setZ(3);
et.printNo(1000);
System.out.println(test.getDd());
}
}
Is there something wrong with my code? Any help is appreciated.
Thanks
You asked if there is something wrong with your code. The answer is yes. The two pointcuts setter() and getter() are mutually exclusive. Thus combining them with && - i.e. creating an intersection of two mutually disjunct sets of joinpoints - logically leads to an empty result set. Ergo your combined pointcut does not match. You should use || as uniknow suggested in his/her comment.
If you want to achieve something else, please explain it in a comprehensible way, if necessary by giving examples, in a comment or by updating your question. I really did not get what you really want.

Categories