How to make Spring #Cacheable work on top of AspectJ aspect? - java

I created an AspectJ aspect which runs fine within a spring application. Now I want to add caching, using springs Cacheable annotation.
To check that #Cacheable gets picked up, I'm using the name of a non-existing cache manager. The regular run-time behavior is that an exception is thrown. But in this case, no exception is being thrown, which suggests that the #Cacheable annotation isn't being applied to the intercepting object.
/* { package, some more imports... } */
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.cache.annotation.Cacheable;
#Aspect
public class GetPropertyInterceptor
{
#Around( "call(* *.getProperty(..))" )
#Cacheable( cacheManager = "nonExistingCacheManager", value = "thisShouldBlowUp", key = "#nosuchkey" )
public Object intercepting( ProceedingJoinPoint pjp ) throws Throwable
{
Object o;
/* { modify o } */
return o;
}
}
Given that my Aspect is working already, how can I make #Cacheable work on top of it?

You can achieve similar results, by using Spring regular dependency injection mechanism and inject a org.springframework.cache.CacheManager into your aspect:
#Autowired
CacheManager cacheManager;
Then you can use the cache manager in the around advice:
#Around( "call(* *.getProperty(..))" )
public Object intercepting( ProceedingJoinPoint pjp ) throws Throwable
{
Cache cache = cacheManager.getCache("aopCache");
String key = "whatEverKeyYouGenerateFromPjp";
Cache.ValueWrapper valueWrapper = cache.get(key);
if (valueWrapper == null) {
Object o;
/* { modify o } */
cache.put(key, o);
return o;
}
else {
return valueWrapper.get();
}
}

Related

How to audit methods in Java Spring Boot

I am writing a Spring Boot Application. I want to audit methods with my annotation #AuditMetod: For example I have method foo() with the annotation:
#AuditMetod(name = "SomeValue")
foo() {...}
I want to handle and audit such methods like this (the simplest example):
auditMethod(Method method) {
if (method.hasAnnotation(AuditMethod.class)) {
System.out.println (method.getName() + " was called at " + new Date())
}
}
upd
Thanks to #Karthikeyan #Swapnil Khante and #misha2048 I understood, that I need to use AOP. But I have 2 problems:
The only method in Aspect class in not being called and I don't see the inscription "----------ASPECT METHOD IS CALLED-----------" in log
How can I check in aspect method what method it is intercepting. To get an instance of Method class.
Now I have the following code:
Controller:
#PostMapping
#LoggingRest(executor = "USER", method = "CREATE", model = "SUBSCRIPTION")
public ResponseEntity<?> create(#Valid #RequestBody SubscriptionRequestDto dto) {
...
}
Aspect:
`#Aspect
#Slf4j
#Component
public class AuditAspect {
#Pointcut(value = "#annotation(com.aspect.annotations.LoggingRest)")
public void auditMethod(ProceedingJoinPoint proceedingJoinPoint) {
log.info("----------ASPECT METHOD IS CALLED------------");
}`
And annotation:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface LoggingRest {
String executor() default "SYSTEM";
String method() default "";
String model() default "";
}
Auditing is a cross-cutting concern and can be handled using AOP.
Another solution would be to use a low-level solution by writing a custom annotation and using a Spring interceptorto write your business logic.
To use the Spring interceptor you will need to implement the HandlerInterceptor interface
Example of the annotation
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface Audit {
boolean active() default true;
}
Interceptor example
#Component
public class AuditInterceptor implements HandlerInterceptor {
#Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
Audit annotation = handlerMethod.getMethodAnnotation(Audit.class);
if (annotation != null && annotation.active()) {
// your business logic
}
}
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
check this interceptor example
I think one of the solutions here, as #Karthikeyan mentioned, is to use Spring AOP.
If you are not aware a brief introduction - spring-aop module implements the aspect oriented programming paradigm. We extract some common functionality, that we generally want to apply to some subset of functions/methods, to an entity called Aspect (see class annotated with #Aspect). This class will contain out cross-cutting functionality - such as auditing, for instance we want to audit the methods execution time, lets say. We just put the code to be executed, the condition, which tell the spring what exact beans methods should be affect by this aspect, see below.
For example, if I can audit the method execution duration with the following very simple example (in my case I said that any public method, returning void inside the Class com.example.stackoverflow.BusinessLogicClass must be inspected by this Aspect):
#SpringBootApplication
#EnableAspectJAutoProxy
public class StackoverflowApplication implements ApplicationRunner {
#Autowired
private BusinessLogicClass businessLogicClass;
public static void main(String[] args) {
SpringApplication.run(StackoverflowApplication.class, args);
}
#Override
public void run(ApplicationArguments args) throws Exception {
businessLogicClass.test();
}
}
#Aspect
#Component
class MyAspectLogicClass {
#Around("execution(public void com.example.stackoverflow.BusinessLogicClass.*(..))")
public Object hangAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
long before = System.currentTimeMillis();
Object returnedValue = proceedingJoinPoint.proceed();
long after = System.currentTimeMillis();
System.out.printf("Retruned in '%s' ms %n", (after - before));
return returnedValue;
}
}
#Component
class BusinessLogicClass {
public void test() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
In my case, I will get the time before method execution, then by the means of
proceedingJoinPoint.proceed() call I delegate the execution to the real method, and then, once I get the response back, I will get the current system time and calculate the execution time, fairly simple.
I hope I have at least directed you somewhere, if you are looking for documentation, this are the resources I suggest you should look for:
https://docs.spring.io/spring-framework/docs/2.5.x/reference/aop.html offical spring doc (stale a bit, but there are some valuable things to learn)
https://docs.spring.io/spring-framework/docs/4.3.15.RELEASE/spring-framework-reference/html/aop.html is more fresh doc
Hope it helped :)
The problem was in right annotation. In Aspect class I tried #Around and everything works as I need.
#Aspect
#Slf4j
#Component
public class AuditAspect {
#Around(value = "#annotation(com.aspect.annotations.LoggingRest)")
public void auditMethod(ProceedingJoinPoint proceedingJoinPoint) {
var method = ((MethodSignature) proceedingJoinPoint.getSignature()).getMethod();
log.info("----------ASPECT METHOD IS CALLED------------");
}
}
For getting a Method instance I use fallowing code
Method method = ((MethodSignature) proceedingJoinPoint.getSignature()).getMethod();

Call different proxyied method from Spring Aspect

I have two different aspects. How do I ensure that when calling method from one aspect, it will still go through proxy chain?
Here is relevant code:
Inner aspect:
#Around("withinReplicatedRepository() && entityMethod() && insertMethod()")
public Object trackInsert(ProceedingJoinPoint jp) throws Throwable {
return trackChange(jp, ChangeType.INSERT, jp.getArgs()[0]);
}
Outer aspect:
#Around("withinReplicatedRepository() && entityMethod() && autoSaveRepository() && saveMethod()")
public Object saveEntity(ProceedingJoinPoint jp) throws Throwable {
TransactionUtil.ensureTransactional();
Object entity = jp.getArgs()[0];
AutoSaveRepository repository = (AutoSaveRepository)jp.getTarget();
if (repository.exists(entity)) {
repository.update(entity);
} else {
repository.insert(entity);
}
return null;
}
Usage:
AutoSaveRepository<MyEntity> repo = ...;
repo.save(entity);
My problem is that jp.getTarget() will return original class, thus repository.insert() will not be captured by trackInsert.
You can try
AopContext.currentProxy()
See Javadoc.
The prerequisite is that you activate proxy exposure, though:
In XML you can do this since Spring 3.0.3 via<aop:aspectj-autoproxy expose-proxy="true"/>.
In annotation-style config your can do it since 4.3.1 via #EnableAspectJAutoProxy(proxyTargetClass = true, exposeProxy = true),see Javadoc

Spring: automatic rollback on checked exceptions

One way to configure Spring to rollback on a non RuntimeExceptions is using #Transactional(rollbackFor=...) annotation on the service classes. The problem with this approach is that we need to define (rollbackFor=...) for almost all the service classes which seems really redundant.
My question: Is there any way to configure a default behaviour for Spring transaction manager to rollback on a non RuntimeException whenever it happens without declaring it on every #Transactional annotation. Something like using #ApplicationException(rollback=true) annotation on an exception class in EJB.
You can't do it for application level with #Transactional , but you can :
variant 1 : extend #Transactional annotation and put it as default value for rollbackfor. But set rollbackFor unchecked exceptions only that you need .With this you can control rollbacks only for case that you sure , and avoid copy past of #Transactional(rollbackFor =MyCheckedException.class)
Like:
#Target({ElementType.METHOD, ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
#Transactional(rollbackFor=MyCheckedException.class)
public #interface TransactionalWithRollback {
}
And use this annotation instead of standard #Transactional.
variant 2 : you can create extension from AnnotationTransactionAttributeSource and override method determineTransactionAttribute:
protected TransactionAttribute determineTransactionAttribute(AnnotatedElement ae)
//Determine the transaction attribute for the given method or class.
TransactionAttribute see TransactionAttribute api , there is a method
boolean rollbackOn(Throwable ex) Should we roll back on the given exception?
protected TransactionAttribute determineTransactionAttribute(
AnnotatedElement ae) {
return new DelegatingTransactionAttribute(target) {
#Override
public boolean rollbackOn(Throwable ex) {
return (check is exception type as you need for rollback );
}
};
}
Second approach is not so good as first as you do it really global for transaction manager. Better use custom annotation as you can control it any apply only for methods/classes where you really need it. But if you need it in any case use second variant , it will be your default transnational behavior.
This config solves it:
#Configuration
public class MyProxyTransactionManagementConfiguration extends ProxyTransactionManagementConfiguration {
#Bean
#Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public TransactionAttributeSource transactionAttributeSource() {
return new AnnotationTransactionAttributeSource() {
#Nullable
protected TransactionAttribute determineTransactionAttribute(AnnotatedElement element) {
TransactionAttribute ta = super.determineTransactionAttribute(element);
if (ta == null) {
return null;
} else {
return new DelegatingTransactionAttribute(ta) {
#Override
public boolean rollbackOn(Throwable ex) {
return super.rollbackOn(ex) || ex instanceof Exception;
}
};
}
}
};
}
}
This is a similar approach as this answer, i.e. changing the default globally, but with as minimal change to Spring's config as possible, and still leaving the possibility to customize rollback rules per method as usual (with rollbackFor, noRollbackFor etc.).
This is achieved by simply adding a default RollbackRule for Exception.class. Since the rules have precedence according to the exception class hierarchy (the rule for the most specific exception class applicable wins), the new rule has basically lowest precendence, if no other rules are defined on the annotation.
#Configuration
public class MyTransactionManagementConfiguration {
/**
* Note: This custom config does NOT recognize {#code javax.transaction.Transactional} annotations in contrast to
* the original Spring behaviour. Check the original {#code AnnotationTransactionAttributeSource} source code for an idea how to add that.
*
* #see AnnotationTransactionAttributeSource#AnnotationTransactionAttributeSource(boolean)
*/
#Bean
#Primary
#Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public TransactionAttributeSource transactionAttributeSourceWithDefaultRollBackForAllExceptions() {
return new AnnotationTransactionAttributeSource(
new SpringTransactionAnnotationParser() {
#Override
protected TransactionAttribute parseTransactionAnnotation(AnnotationAttributes attributes) {
RuleBasedTransactionAttribute rbta = (RuleBasedTransactionAttribute) super.parseTransactionAnnotation(attributes);
List<RollbackRuleAttribute> rules = new ArrayList<>(rbta.getRollbackRules());
rules.add(new RollbackRuleAttribute(Exception.class));
rbta.setRollbackRules(rules);
return rbta;
}
}
);
}
}

Java Spring AOP: Can I ignore Xlint:invalidAbsoluteTypeName error which caused by pointcuts to classes which I don't use?

I have an issue concerning a generic component and one (of a dozen) application(s). My component has point cuts to many annotations, which could be used within classes and methods in my apps. When all annotations are present on the classpath, everything works fine. But not in all my apps I have these dependencies. The quick fix is, of course, add them, but that gives my app a lot of code which I don't need in that app. I'm searching for a way to ignore the Xlint:invalidAbsoluteTypeName error as stated here: Xlint:invalidAbsoluteTypeName
So what I have:
I have many apps with Soap/JMS connections, and all are annotated with the #Annotation org.springframework.ws.server.endpoint.annotation.Endpoint.
I have my pointcut in my generic component (jar):
#Around("within(#org.springframework.ws.server.endpoint.annotation.Endpoint *)")
And the result is:
All apps having the Spring WS dependency along with my generic component have no issues
Apps which don't have the annotation, cannot start due to java.lang.IllegalArgumentException: warning no match for this type name: org.springframework.ws.server.endpoint.annotation.Endpoint [Xlint:invalidAbsoluteTypeName] (which is obvious, see the link)
So the problem looks like Xlint:invalidAbsoluteTypeName BUT I don't want to add Spring dependencies which I'm not using. I just want this AOP pointcut ignored. Other workarounds like splitting up the pointcuts to different jars imho give too much overhead. Is there any way to have Spring AOP just ignore this pointcut, or e.g. set the pointcut to st like if-exists(class)?
To show why I think separating is causing way too much overhead have a look at my aspect structure:
#Aspect
public class PerformanceLoggingAspect {
private LogWriter logWriter;
#Inject
public PerformanceLoggingAspect(LogWriter logWriter) {
this.logWriter = logWriter;
}
#Around("within(#org.springframework.web.bind.annotation.RestController *)")
public Object withinARestController(ProceedingJoinPoint pjp) throws Throwable {
return proceedWithLogging(pjp, MetingType.REST);
}
#Around("within(#org.springframework.ws.server.endpoint.annotation.Endpoint *)")
public Object withinAnEndpoint(ProceedingJoinPoint pjp) throws Throwable {
return proceedWithLogging(pjp, MetingType.BERICHT);
}
#Around("within(#javax.inject.Named *)")
public Object withinAService(ProceedingJoinPoint pjp) throws Throwable {
return proceedWithLogging(pjp, MetingType.SERVICE);
}
private Object proceedWithLogging(ProceedingJoinPoint pjp, String metingType) throws Throwable {
(... Working code (performance logging) if the annotation is on the classpath...)
}
}
Update: I tried creating a #NeedsClass("any.package.Class") which is a #Conditional annotation from spring-context. The condition class is a ClasspathCondition which checked if the classloader could load that given class. But the error occurs before the condition gets evaluated so I'm afraid this is a dead end. But if you're curious:
The #NeedsClass annotation I tried
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.TYPE, ElementType.METHOD})
#Documented
#Conditional(ClasspathCondition.class)
public #interface NeedsClass {
String[] value();
}
The Condition implementation. I had logging here, which never got written
public class ClasspathCondition implements Condition {
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
try {
String[] classes = (String[]) metadata.getAnnotationAttributes(NeedsClass.class.getName()).get("classes");
for (String clazz : classes) {
ClassUtils.resolveClassName(clazz, context.getClassLoader());
}
return true;
} catch (Throwable t) { /* noOp() */}
return false;
}
}
For now I have a workaround:
I created a superclass with the method:
protected Object proceedWithLogging(ProceedingJoinPoint pjp, String metingType) throws Throwable {
(... code which adds performance logging ...)
}
I created 4 subclasses with each the #Aspect annotation, and 1 method calling the super. For example this one targets JMS:
#Aspect
public class JmsPerformanceLogger extends PerformanceLoggingAspect {
#Inject
private LogWriter logWriter;
#Around("within(#org.springframework.ws.server.endpoint.annotation.Endpoint *)")
public Object withinAnEndpoint(ProceedingJoinPoint pjp) throws Throwable {
return proceedWithLogging(pjp, MetingType.BERICHT);
}
}
As a downside I have to configure all different beans which I need within my app, and I cannot add one simple configuration file as shown below, with all beans preconfigured:
#Configuration
public class PerformanceloggingConfig {
#Bean
public LogWriter performanceLogWriter(){
return new DefaultLogWriter();
}
#Bean
public JmsPerformanceLogger jmsPerformanceLogger(){
return new JmsPerformanceLogger();
}
#Bean
public RestPerformanceLogger restPerformanceLogger(){
return new RestPerformanceLogger();
}
#Bean
public ServicesPerformanceLogger servicesPerformanceLogger(){
return new ServicesPerformanceLogger();
}
#Bean
public DaoPerformanceLogger daoPerformanceLogger(){
return new DaoPerformanceLogger();
}
}
And therefore also not the handy annotation to autoconfig the class:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE)
#Import(PerformanceloggingConfig.class)
public #interface EnablePerformanceLogging {
}
But for now adding these 4 beans when I need them, makes it possible to differentiate per app. But of course this is still a workaround, as I want to use #EnablePerformanceLogging and be done with it. If anyone has a better answer, pls tell me

Logging with aspects

Have a RESTful web-serice with Facade layer, Service layer and Dao layer.
Trying to journalize all invokes of all methods of classes, marked with annotation #Log
#Target({ElementType.TYPE, ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
public #interface Log {
}
Here's the aspect code:
public class LoggingAspect {
#Around("#target(Log)")
public Object log(ProceedingJoinPoint pjp) throws Throwable {
log.debug("Start " + pjp.getSignature().getName());
Object result = pjp.proceed();
log.debug("End " + pjp.getSignature().getName());
return result;
}
}
Facade, Service and Dao are marked with #Log.
Some example:
public Obj Facade.createObj(String name){ //1
return service.createObj(name);
}
public Obj Service.createObj(String name){ //2
return dao.createObj(name);
}
public Obj Dao.createObj(String name){ //3
Long idOfCreatedObj = /*code that creates an object and returns it's ID*/;
return loadObjById(idOfCreatedObj); /* load the created object by id */
}
public Obj Dao.loadObjById(Long id){ //4
return /* code that loads the object by it's id */;
}
In this example methods 1, 2, 3 are logged successfully. But the nested dao method (loadObjById) is not logged.
WHY?
P.S. In spring-config.xml there's
<aop:aspectj-autoproxy proxy-target-class="true"/>
The issue is self calls (this.methodcall()) bypass the dynamic/cglib proxy that is created by Spring to handle the cross cutting concern.
The fix is either to use full Aspectj (compile time or load time weaving) or make the call by getting hold of the proxy (instead of alling this.methodCall(), use proxy.methodCall()
You can get hold of the proxy this way:
<aop:aspectj-autoproxy expose-proxy="true"/>
And in your code: AopContext.currentProxy() will give you a reference to the proxy. Here is one blog article on this if you are interested - http://www.java-allandsundry.com/2012/07/reference-to-dynamic-proxy-in-proxied.html

Categories