Let's say I need to enhance the shower() method with a #MusicAround advice to give me some music before and after executing the shower() method.
public class Me {
#MusicAround
public void shower() {
// shower code omitted
}
}
First I created the new annotation #MusicAround.
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface MusicAround {
Then bind it with an aspect MusicAspect.
#Aspect
public class MusicAspect {
#Around("#annotation(MusicAround)")
public Object musicAround(ProceedingJoinPoint joinPoint) throws Throwable {
IPhone iphone = new IPhone();
Iphone.music();
joinPoint.proceed();
iphone.music();
}
}
Configure MusicAspect as a Bean. #EnableAspectJAutoProxy annotation leaves spring to encapsulate the aspect proxy for me.
#Configuration
#EnableAspectJAutoProxy
public class ApplicationConfig {
// ... other beans omitted
#Bean
public MusicAspect musicAspect() {
return new MusicAspect();
}
}
In main method, get Me instance from context, and execute shower() method.
public static void main(String[] args) {
try {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ApplicationConfig.class);
Me me = context.getBean(Me.class);
me.shower();
context.close();
} catch (ApplicationContextException ace) {
// handle exception
}
}
Now I can enjoin the music during the shower.
<hey jude>
I'm showering
<don't be so serious>
The problem is that in this way MusicAspect class is coupled with IPhone class. I want to decouple them by injecting IPhone object as a parameter as below,
#Aspect
public class MusicAspect {
#Around("#annotation(MusicAround)")
public Object musicAround(ProceedingJoinPoint joinPoint, IPhone iphone) throws Throwable {
iphone.music();
joinPoint.proceed();
iphone.music();
}
}
Of cause a second parameter "iphone" in musicAround() method will not be allowed here. Is there any spring features that I can use to decouple IPhone and MusicAspect in this case?
!Note: thanks #kriegaex for the proofreading.
This is a preliminary answer, because the content is not suitable for a comment.
When looking at the code in your updated question, some things strike me as strange:
I wonder why everyone is so eager to always use aspects in combination with annotations. Why not use a pointcut which directly targets packages, classes or methods of interest? All this annotation pollution is horrible, if not absolutely necessary. Ideally, the application code should be completely agnostic of the existence of aspects.
You use the #annotation pointcut designator incorrectly. Instead of #annotation(#MusicAround) it should be #annotation(MusicAround). But also that only works if the annotation happens to be in the exact same package as the aspect, otherwise you need #annotation(fully.qualified.package.name.MusicAround).
You use a MusicAspect, but then declare a MinstrelAroundAdvice bean. That does not seem to match. Besides, an aspect is an aspect, the method inside it which actually does something is the advice. So the class name *Advice for an aspect is simply wrong. Better use *Aspect instead or something else which properly describes what the aspect does. In this case, MusicAspect seems just fine to me.
Now concerning your actual question, it is still unclear to me. Is it about how to inject (auto-wire) another bean into an aspect instance?
Of course I was not allowed to do so.
Why "of course"? What was not allowed? How did you notice? Did something not work? Did you get an error message? A stack trace? Please explain clearly what you tried, what the expected result is and what happened instead. Make your problem reproducible. Your code snippets do not do that, unfortunately. Just imagine for a minute that someone else would ask you the same question without you seeing the full code and without other context information necessary to understand the problem. Could you answer it? If your helpers do not understand the problem, how can they answer your question? Please be advised to learn what an MCVE is.
Problem solved with the help of #k-wasilewski and #kriegaex. Thanks bro.
The answer is #Autowired annotation.
Define IPhone as a field of MusicAspect class. Add #Autowired tag, which tells spring context to initialize the IPhone instance for us.
#Aspect
public class MusicAspect {
#Autowired
private IPhone iphone;
#Around("#annotation(MusicAround)")
public Object musicAround(ProceedingJoinPoint joinPoint) throws Throwable {
Iphone.music();
joinPoint.proceed();
iphone.music();
}
}
Don't forget to register IPhone bean in ApplicationConfig. The rest part remain the same.
#Configuration
#EnableAspectJAutoProxy
public class ApplicationConfig {
// ... other beans omitted
#Bean
public IPhone iphone() {
return new IPhone();
}
}
The code passed unit-test on my laptop.
Related
I'm starting to work with AspectJ and I'm trying to do something that I don't know if it's possible. This is my code:
public abstract class MyAbstractObject<T> {
private T myOtherObject;
public T getMyOtherObject() {
return myOtherObject;
}
}
#Component
public class MyObject extends MyAbstractObject<WhateverObject> {
}
#Aspect
#Component
public class MyAspects {
#Before("execution(* mypackage.MyAbstractObject.getMyOtherObject().set*(*))")
public void beforeExample(JoinPoint joinPoint) {
// DO THINGS
}
}
This code fails, with the error:
Caused by: java.lang.IllegalArgumentException: Pointcut is not well-formed: expecting ')' at character position 58 execution(* mypackage.MyAbstractObject.getMyOtherObject().set*(*))
However, I can intercept MyOtherObject like this, but not it's setters:
#Aspect
#Component
public class MyAspects {
#Before("execution(* mypackage.MyAbstractObject.getMyOtherObject())")
public void beforeExample(JoinPoint joinPoint) {
// DO THINGS
}
}
I don't want to intercept the setters of the object MyOtherObject everywhere, because this object is used in more places in the program where I don't need aspects. However I want to intercept them only when used in a class that extends MyAbstractObject.
I'm using Spring with AspectJ.
Thanks.
The syntax you just invented is illegal, unfortunately. You cannot describe chained method calls the ways you dreamed up.
What you really want is to intercept method executions of type WhateverObject, not of MyAbstractObject or MyObject. I.e., your pointcut should rather bet something like
execution(* mypackage.WhateverObject.set*(*))
I am just guessing, but if you want to limit matching to control flows where the setter method is called (directly or indirectly) from getMyOtherObject(), in AspectJ your would add something like
&& cflow(execution(* mypackage.MyAbstractObject.getMyOtherObject()))
to the first pointcut. But cflow pointcuts are unavailable in Spring AOP, which is what you seem to be using. So
either you switch to native AspectJ, which is easy to integrate into Spring applications, but can also be used outside of the Spring context because it is an idependent product,
or you check if Spring AOP's "lite" version of control flow pointcuts does what you need. I posted a few answers and links mentioning this almost undocumented feature here and there.
As a native AspectJ fan who does not normally use Spring, you can imagine which option I would recommend regarding control flow pointcuts, but I think you can make your own decision.
I would like to use custom Java annotation to insert a value in a private class property using Spring AOP (and/or AspectJ). Quick example:
MyAnnotation.java:
#Retention(RetentionPolicy.RUNTIME)
#Target({ ElementType.FIELD })
public #interface MyAnnotation {
}
MyController.java:
public class MyControllerImpl implements MyController {
...
#MyAnnotation
private String var1;
#Override
public String getVarExample() {
// imagine this is a REST API that gets called on #GET
// request and returns a string
System.out.println(this.var1); // <-- I'd like this to be "helloworld"
// this is just for illustration
// of course, I will want to do
// something more meaningful with
// the 'var1' variable
return "ok"; <- unimportant for this example
}
...
MyAspect.java:
#Aspect
#Component
public class MyAspect {
#Pointcut("#annotation(com.mypackage.annotation.MyAnnotation)")
public void fieldAnnotatedWithMyAnnotation() {
}
#Around("fieldAnnotatedWithMyAnnotation()")
public Object enrichVar1(ProceedingJoinPoint pjp) throws Throwable {
// problem #1 - the program never enters here
// problem #2 - I need to figure out how to set up the var1 here
// to "helloworld" , how?
return pjp.proceed();
}
...
}
What would I like to happen?
I will call and get into the getVarExample() and after it returns I would like to see "helloworld" in console or log. I would like to somehow set the var1 to a custom value using AOP. Any property variable that will be annotated with #MyAnnotation will be set to "helloworld". I hope the example above is clear.
What have I tried?
I made sure there is no typo in the package names, also fiddled with different AOP advice annotations like #Around and #Before. I also tried different targets in the MyAnnotation and ended up with ElementType.FIELD which should be correct.
Can you help me to get it working?
I know this can be done, but couldn't find any working example online. Again, I would like to see 2 answers:
1. How to get the pointcut to trigger on MyController entrance? I want to catch a breakpoint inside the enrichVar1(..) method of the MyAspect class.
2. How can I modify the annotated var1 value inenrichVar1(..) method of the MyAspect class?
I don't know what I am doing wrong. Any help will be greatly appreciated. Thank you!
The AOP is set up correctly in my project. I know that because I am already using AOP for different things (logging for example).
Update #1:
Please, note there are not getters or setters for the var1 private variable. The variable will be only used within the MyControllerImpl. To illustrate this better I changed the return value of the getVarExample.
Like I said in my comment:
The pointcut designator #annotation() intercepts annotated methods, not annotated fields. For that, native AspectJ has get() and set(). I.e., the pointcut would also need to be changed if migrating to AspectJ. But I agree that sticking to Spring AOP and annotating getter methods instead of fields is probably enough here.
But because you insist that you want to keep the controller class unchanged, here is the native AspectJ solution. Please read chapter Using AspectJ with Spring Applications for how to configure that with #EnableLoadTimeWeaving and JVM parameter -javaagent:/path/to/aspectjweaver.jar.
In order to demonstrate that this solution really does work independently of Spring, I am using no Spring classes or annotations at all, only POJOs and native AspectJ. You can simply do the same within your Spring application. Please note that native AspectJ aspects do not need #Component annotations, in contrast to Spring AOP aspects.
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.FIELD })
public #interface MyAnnotation {}
package de.scrum_master.app;
public interface MyController {
String getVarExample();
}
package de.scrum_master.app;
public class MyControllerImpl implements MyController {
#MyAnnotation
private String var1;
#Override
public String getVarExample() {
System.out.println(this.var1);
return "ok";
}
}
package de.scrum_master.app;
public class Application {
public static void main(String[] args) {
MyController myController = new MyControllerImpl();
myController.getVarExample();
}
}
package de.scrum_master.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
#Aspect
public class MyAspect {
#Pointcut("get(#de.scrum_master.app.MyAnnotation * *)")
public void fieldAnnotatedWithMyAnnotation() {}
#Around("fieldAnnotatedWithMyAnnotation()")
public Object enrichVar1(ProceedingJoinPoint pjp) throws Throwable {
System.out.println(pjp);
return "helloworld";
}
}
When running Application, the console log is going to be:
get(String de.scrum_master.app.MyControllerImpl.var1)
helloworld
The AspectJ manual explains the syntax of field get and set join point signatures and field patterns.
Note: I think that your use case might be a hack rather than a valid application design. You ought to refactor rather than hack into an application like this.
As it goes from Spring docs Spring AOP does support Spring beans' method execution join points. To make field access join points work you need to use AspectJ's backend with load time weaving for AOP.
But for your case it's not required to use field join points, you can put your annotation on the getter and this should work.
I was wondering how to do dependency injection in the most effective way inside my code.
I have this code:
#Configuration
public class SomeName {
#Autowired
private Other other;
#Bean
public void method() {
other.someMethod();
// some code
}
}
Can this code be changed into the following code(other will be used only inside this function)?
#Configuration
public class SomeName {
#Bean
public void method(Other other) {
other.someMethod();
// some code
}
}
You should avoid #Autowired if possible and inject using a constructor or method.
Starting with Java 9 and java modules (project jigsaw) there are some strict rules that make it harder for your framework to change the values of a private field.
What Spring is doing in the first example is essentially that - it breaks encapsulation to change the value of a private value. (There is a way to overcome this with "opens" directive in module-info..)
You are also becoming dependent on the framework you are using and your code becomes harder to test compared to when using a simple setter.
You are also not explicitly declaring that your class depends on another class since I can easily instantiate it and "Other" will be null.
Some resources:
https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-scanning-autodetection (search for jigsaw)
https://blog.marcnuri.com/field-injection-is-not-recommended/
PS: You are probably missing #Configuration on your class
I have a SpringBoot Application.
I have defined an Annotation say "Track", and I have annotated few methods in different packages which I want aop to consider.
The annotation has been defined as below :
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface Track {
}
I have not missed the #EnableAspectJAutoProxy in the #Configuration class of my package.
I have a Pointcut and an Advice defined in the Aspect like below :
#Aspect
#Component
public class MyAspect {
#Pointcut("execution(#Track * *.*(..))")
void annotatedMethod() {
// No Implementation required
}
#Around("annotatedMethod() && #annotation(methodLevelTrack)")
public void adviseAnnotatedMethods(ProceedingJoinPoint proceedingJoinPoint,
Track methodLevelTrack) throws Throwable {
// do some task
proceedingJoinPoint.proceed();
// do some task after the method is executed.
}
}
My intention is: for any method (annotated with #Track) in any package, with any access modifier, and any number of input arguments, and any return type, to follow the aspect's #Around advice.
Now, the interesting situation is as below :
I have a class say "Engine" which calls other classes and downstream systems to perform a long-running operation. Let's define the class as follows :
public class Engine {
// bunch of other autowired objects
public void processTask() {
<autowired_object_A>.someMethod() // this method has been annotated with #Track
<autowired_object_B>.someMethod() // this method has also been annotated with # Track
.... // bunch of other methods in other autowired objects that have been annotated with # Track
someMethodOfEngineClass(); // Now this has been defined in the Engine class as below, but pointcut doesn't recognize this method!
}
#Track
private void someMethodOfEngineClass() {
// do something
}
}
All the "other" autowired objects' methods are getting recognized by pointcut as expected but the method within this Engine class, that has been annotated with #Track, is not recognized. What's the mystery?
I have tried making "someMethodOfEngineClass" method public, return something instead of void and all those combinations and it doesn't work.
What am I missing?
Is it the pointcut definition expression?
I have defined the aspect in one of the sub packages, is aspect supposed to be defined at the top level in the package structure?
Can you folks please suggest something that can work? I am kinda stuck at this.
When you define aop spring creates proxy around the class,
so when the method is called, actually call is delegated to proxy, sth like
your.package.Engine$$FastClassBySpringCGLIB$$c82923b4.someMethodOfEngineClass()
But this works only when a method is called from outside it's class
If you call class method from the same class you are effectively calling it by this.someMethodOfEngineClass()
here -> http://www.nurkiewicz.com/2011/10/spring-pitfalls-proxying.html
you can find more info about proxying
so proxy is bypassed and aop is not working.
I'm trying to print a "Hello, AOP!" message whenever Guice/AOP Alliance intercepts a method marked with a particular (custom) annotation. I have followed the official docs (a PDF that can be found here - AOP method interception stuff on pg. 11) and cannot get it to work, only compile.
First, my annotation:
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.METHOD})
#BindingAnnotation
public #interface Validating {
// Do nothing; used by Google Guice to intercept certain methods.
}
Then, my Module implementation:
public class ValidatingModule implements com.google.inject.Module {
public void configure(Binder binder) {
binder.bindInterceptor(Matchers.any(),
Matchers.annotatedWith(Validating.class,
new ValidatingMethodInterceptor()),
}
}
Next, my method interceptor:
public class ValidatingMethodInterceptor implements MethodInterceptor {
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("Hello, AOP!");
}
}
Finally, the driver that attempts to make use of all this AOP stuff:
public class AopTest {
#Validating
public int doSomething() {
// do whatever
}
public static main(String[] args) {
AopTest test = new AopTest();
Injector injector = Guice.createInjector(new ValidatingModule());
System.out.println("About to use AOP...");
test.doSomething();
}
}
When I run this little test driver, the only console output I get is About to use AOP...... the Hello, AOP! never gets executed, which means the #Validating doSomething() method is never being intercepted the way the Guice docs show.
The only thing I can think of is the fact that in my Module implementation I am specifying the MethodInterceptor to bind to (as the 3rd argument to the bindInterceptor method) as being a new ValidatingMethodInterceptor(), whereas in that interceptor, I am only defining a required invoke(MethodInvocation) method.
Perhaps I am not wiring these two together correctly? Perhaps Guice doesn't implicitly know that the invoke method should be ran when an intercept occurs?!?!
Then again, not only have I followed the Guice docs, I have also followed several other tutorials to no avail.
Is there something obvious I am missing here? Thanks in advance!
Edit One other discrepancy between my code and the examples I followed, although small, is the fact that my invoke method (inside the interceptor) is not annotated with #Override. If I try to add this annotation, I get the following compile error:
The method invoke(MethodInvocation) of type ValidatingMethodInterceptor must override a superclass method.
This error makes sense, because org.aopalliance.intercept.MethodInterceptor is an interface (not a class). Then again, every example using Guice/AOP Alliance uses this #Override annotation on the invoke method, so it obviously works/compiles for some people...weird.
If you don't let Guice construct your object, it can't provide you an instance with the interceptor wrapped around. You must not use new AopTest() to get an instance of your object. Instead, you must ask Guice to give you one instance:
Injector injector = Guice.createInjector(new ValidatingModule ());
AopTest test = injector.getInstance(AopTest.class);
See http://code.google.com/p/google-guice/wiki/GettingStarted