I have seen numerous examples of Spring functionality related to @Cacheable
, @Transactional
, @Async
, etc. where the same options are reiterated every time:
Self invocation made through a proxy object gotten through either the ApplicationContext.getBean(MyService.class)
or an autowired MyService.class
proxy object in addition to @Scope(proxyMode=ScopedProxyMode.TARGET_CLASS)
,
Relocating the target method to a separate @Service
class,
Using AspectJ load-time weaving.
While the first two approaches are usually fine, there are times when we need to attach the functionality of the above three (and other) annotations to private methods, whether it be for code clarity, design, or other reasons.
There are many example of the first two approaches, but very few of the last. As I understand, due to the nature of AspectJ LTW, by default it is mutually exclusive with the usual Spring AOP behaviour that enables the @Cacheable
, etc. behaviour without much hassle. My questions are as follows:
Are there any decent examples on enabling the above behaviour usually done with the first two options using AspectJ LTW?
Is there a way to enable AspectJ LTW selectively, e.g. not for @Async
and @Transactional
but just @Cacheable
? An example use case of this would perhaps be: due to a team's design, all cacheable methods (some of which may be private) should be located in a facade class which calls private methods that perform heavy calculations, but might update some state (i.e. 'last-queried-at: #'
) before returning to the external called.
This question is from the viewpoint of a spring-boot
user, but I believe it applies generally to Spring AOP and AspectJ LTW. Please correct me if there are special considerations needed in this case.
- Are there any decent examples on enabling the above behavior usually done with the first two options using AspectJ LTW?
I have a couple of AspectJ examples on my GitHub account. Both these examples show how to intercept calls within the same target object (self-invocation) and also intercept private methods.
Both the examples are similar except the way aspects are woven into target classes.
Please read the examples' READMEs to find out more about each type of weaving and on how to use each of the examples.
- Is there a way to enable AspectJ LTW selectively, e.g. not for @Async and @Transactional but just @Cacheable?
Yes, you can filter based on either of the following:
By the annotation type of the caller method.
@Before("call(* com.basaki.service.UselessService.sayHello(..))" +
" && cflow(@annotation(trx))")
public void inspectMethod(JoinPoint jp,
JoinPoint.EnclosingStaticPart esjp, Transactional trx) {
log.info(
"Entering FilterCallerAnnotationAspect.inspectMethod() in class "
+ jp.getSignature().getDeclaringTypeName()
+ " - method: " + jp.getSignature().getName());
}
By the name of the caller method.
@Before("call(* com.basaki.service.UselessService.sayHello(..))" +
" && cflow(execution(* com.basaki.service.BookService.read(..)))")
public void inspectMethod(JoinPoint jp,
JoinPoint.EnclosingStaticPart esjp) {
log.info(
"Entering FilterCallerMethodAspect.inspectMethod() in class "
+ jp.getSignature().getDeclaringTypeName()
+ " - method: " + jp.getSignature().getName());
}
You can find working examples here.
Q. Do I understand correctly then, that if I wanted to enable compile-time weaving for transactionality, I would: 1. No longer use a TransactionAwareDataSourceProxy anywhere in my DataSource configuration; 2. Add the following to my application: @EnableTransactionManagement(mode=AdviceMode.ASPECTJ).
Spring AOP and CTW/LTW AspectJ weavings are completely orthogonal, i.e., they are independent of each other.
You will need @EnableTransactionManagement
if you want to enable Spring's annotation-driven transaction management capability.
Q. In your examples, I see that you do not start the application in any special way for CTW. Would this suffice, or have I missed anything?
Yes, in CTW you don't need anything special during start-up since the extra bytecode is already injected in the original code by the AspectJ compiler (ajc
) during compile time. For example, here is the original source code:
@CustomAnnotation(description = "Validates book request.")
private Book validateRequest(BookRequest request) {
log.info("Validating book request!");
Assert.notNull(request, "Book request cannot be empty!");
Assert.notNull(request.getTitle(), "Book title cannot be missing!");
Assert.notNull(request.getAuthor(), "Book author cannot be missing!");
Book entity = new Book();
entity.setTitle(request.getTitle());
entity.setAuthor(request.getAuthor());
return entity;
}
Here is the same piece of code after compilation by AspectJ compiler, ajc
:
private Book validateRequest(BookRequest request) {
JoinPoint var3 = Factory.makeJP(ajc$tjp_0, this, this, request);
CustomAnnotationAspect var10000 = CustomAnnotationAspect.aspectOf();
Annotation var10002 = ajc$anno$0;
if (ajc$anno$0 == null) {
var10002 = ajc$anno$0 = BookService.class.getDeclaredMethod("validateRequest", BookRequest.class).getAnnotation(CustomAnnotation.class);
}
var10000.inspectMethod(var3, (CustomAnnotation)var10002);
log.info("Validating book request!");
Assert.notNull(request, "Book request cannot be empty!");
Assert.notNull(request.getTitle(), "Book title cannot be missing!");
Assert.notNull(request.getAuthor(), "Book author cannot be missing!");
Book entity = new Book();
entity.setTitle(request.getTitle());
entity.setAuthor(request.getAuthor());
return entity;
}
While in LTW, you need the Java Agent since the code gets modified during load-time, i.e., when the classes are being loaded by Java class loaders.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With