Reputation: 3364
I have a DoStuff
class which has instances of ServiceSAO
and ServiceInput
as its data members. Whenever I invoke a function lets say setDetail(String)
, I have set up an advice to call publishEvent()
. To call publishEvent()
, I require the ServiceSAO
and ServiceInput
instances of the DoStuff class.
The question is how do I access data members(ServiceSAO
and ServiceInput
) of a callee class(DoStuff
) from the aspect function(publishEvent
)?
DoStuff.java
public class DoStuff{
@Autowired
private ServiceSAO serviceSAO;
private ServiceInput serviceInput;
void init(){
serviceSAO = new serviceSAO();
serviceInput = ServiceUtil.getServiceInput(hostname,"test",....);
}
@PublishEventToService
public void hello(){
serviceInput.setDetail("batman");
}
}
PublishEventToServiceAspect.java
@Aspect
public class PublishEventToServiceAspect{
@After("execution(* com.xyz.ServiceInput.setDetails(..)) && @annotation(PublishEventToService)")
public void publishEvent()
{
String detail = serviceInput.getDetails(); //how can I get serviceInput here??
someFuntion(serviceSAO, serviceInput); //even tougher would be to get the serviceSAO instance??
}
}
EDIT 1 Note that I can have various DoStuff classes. All of them might want to call publishEvent() and therefore I need a generic method to extract the instances.
EDIT 2 I am able to get the ServiceInput instance now using (ServiceInput)joinPoint.getTarget() Any way to access ServiceSAO?
Upvotes: 3
Views: 1630
Reputation: 67297
First of all, a disclaimer: I know a lot about AspectJ, but very little about Spring. So I am going to give a pure AspectJ example here.
Next, a correction: You claim you want to access callee members and Serge Ballesta's answer is also about the callee (target()
binding). But this contradicts what you describe in your code snippet, because there you do not want to access anything related to the callee (ServiceInput
object), but members of the caller (DoStuff
object). For the caller you need this()
binding, not target()
.
Then another, subtle problem: The members you are trying to accesss are actually private, i.e. in order to access them you either need to have public getters for them or use a privileged aspect. I am assuming there are no getters because your code snippet does not show any. Now there is a limitation in AspectJ: Privileged aspects are only available in native AspectJ syntax, not in annotation-style @AspectJ syntax. Thus, I am going to use native syntax in my solution. If you do have public getters or can easily add them to your classes, you can convert the aspect to @AspectJ syntax without the need for privileged access.
I am further assuming that you are not limited to "AOP lite", i.e. Spring AOP, but willing to use full-blown AspectJ (which is fully compatible with Spring, too).
Dummy helper classes:
package de.scrum_master.app;
public class ServiceSAO {
private String id;
public ServiceSAO(String id) { this.id = id; }
@Override public String toString() { return "ServiceSAO [id=" + id + "]"; }
}
--
package de.scrum_master.app;
public class ServiceInput {
private String id;
private String detail;
public ServiceInput(String id) { this.id = id; }
public void setDetail(String detail) { this.detail = detail; }
@Override public String toString() { return "ServiceInput [id=" + id + ", detail=" + detail + "]"; }
}
--
package de.scrum_master.app;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface PublishEventToService {}
Application class with private members and sample main method:
package de.scrum_master.app;
public class DoStuff {
private ServiceSAO serviceSAO;
private ServiceInput serviceInput;
void init() {
serviceSAO = new ServiceSAO("SAO");
serviceInput = new ServiceInput("Input");
}
@PublishEventToService
public void hello() {
serviceInput.setDetail("batman");
}
// @PublishEventToService
public void otherMethod() {
serviceInput.setDetail("foobar");
}
public static void main(String[] args) {
DoStuff doStuff = new DoStuff();
doStuff.init();
doStuff.hello();
doStuff.otherMethod();
}
}
Please note that hello()
is annotated, but otherMethod()
is not (the annotation is commented out). I.e. we expect the advice to be triggered for the former, but not the latter.
Privileged aspect accessing private members:
package de.scrum_master.aspect;
import de.scrum_master.app.ServiceInput;
import de.scrum_master.app.PublishEventToService;
import de.scrum_master.app.DoStuff;
public privileged aspect PublishEventToServiceAspect {
pointcut setDetail(DoStuff caller) :
call(* ServiceInput.setDetail(..)) &&
cflow(execution(@PublishEventToService * DoStuff+.*(..))) &&
this(caller);
after(DoStuff caller) : setDetail(caller) {
System.out.println(thisJoinPointStaticPart);
System.out.println(" " + caller.serviceSAO);
System.out.println(" " + caller.serviceInput);
}
}
As you can see, we are intercepting the method call()
for ServiceInput.setDetail(..)
here rather than the execution()
because we want to access something in the caller's context, not the callee's. Please also note that call()
is only available in AspectJ, not in Spring AOP where you are limited to execution()
pointcuts.
We also want to make sure that only calls are intercepted which are in the control flow (cflow()
) of any currently executed methods of class DoStuff
or its subclasses (thus the "+" in DoStuff+
) annotated by @PublishEventToService
.
Last but not least we want to make sure that the caller (this()
) is assigned to a DoStuff
binding named doStuff
which we can use in our advice.
If all these conditions are true, let us print the joinpoint and the caller's private members in order to prove that it actually works.
Sample output:
execution(void de.scrum_master.app.DoStuff.hello())
ServiceSAO [id=SAO]
ServiceInput [id=Input, detail=batman]
This is exactly what we expect and what you asked for. Now if we remove the comment from the annotation of otherMethod()
, we have two annotated methods and the aspect prints:
execution(void de.scrum_master.app.DoStuff.hello())
ServiceSAO [id=SAO]
ServiceInput [id=Input, detail=batman]
execution(void de.scrum_master.app.DoStuff.otherMethod())
ServiceSAO [id=SAO]
ServiceInput [id=Input, detail=foobar]
Enjoy and feel free to ask if you do not understand or need a modification which you have not explained previously.
Upvotes: 3
Reputation: 148890
AspecJ provides the this
or target
pointcut designators and they can be used as parameter binding. They allow the advice to get the target object (in your case serviceInput
) as a parameter.
Syntax :
@After("execution(* com.xyz.ServiceInput.setDetails(..)) && @annotation(PublishEventToService)") && target(serviceInput)
public void publishEvent(ServiceInput serviceInput))
{
String detail = serviceInput.getDetails(); // should work fine
//someFuntion(serviceSAO, serviceInput); // but no use for serviceSAO instance
}
Anyway, your services should be singletons, so you can get them through dependency injection.
Upvotes: 0