#Loggable Annotation only works for some methods - java

I have the following interface:
/**
* Annotation for methods, whose execution should be logged.
*/
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.METHOD, ElementType.TYPE})
public #interface Loggable {
/**
* Log severity level of 'before' and 'after' log statements.
*/
enum Level {
DEBUG,
INFO,
WARN,
ERROR
}
/**
* Defines the severity which should be used when logging the method arguments.
*/
Level level() default Level.FATAL;
}
I also have the following class:
/**
* Class for logging input and output parameters of any method with annotation #Loggable.
*/
#Aspect
public final class LoggingAspect {
private final Logger log = LoggerFactory.getLogger(getClass());
/**
* #param jp - ProceedingJointPoint
* #param loggable - Loggable
* #return returns the next executable point to proceed in target
* #throws Throwable - throws exception when proceeding with joint point
*/
#Around("execution(* *(..)) && #annotation(loggable)")
public Object loggingAroundMethod(#Nonnull final ProceedingJoinPoint jp,
#Nonnull final Loggable loggable) throws Throwable {
final String signature = jp.getTarget().getClass().getName() + '.' + jp.getSignature().getName();
final List<Object> arguments = Arrays.asList(jp.getArgs());
final Object result;
try {
doLog(loggable.level(), "[BEFORE] {}{}", signature, arguments);
result = jp.proceed();
doLog(loggable.level(), "[AFTER] {}{} result={}", signature, arguments, result);
} catch (Exception e) {
log.error("[AFTER] {}{} exception={}", signature, arguments, e);
throw e;
}
return result;
}
/**
* Logs the message with appropriate log level.
* #param level - level to log
* #param format - format for logging
* #param arguments - arguments for logging
*/
private void doLog(#Nonnull final Loggable.Level level, #Nonnull final String format, final Object... arguments) {
switch (level) {
case DEBUG:
log.debug(format, arguments);
return;
case INFO:
log.info(format, arguments);
return;
case WARN:
log.warn(format, arguments);
return;
case ERROR:
break;
default:
log.error("Unable to appropriately handle given log level={}", level);
}
log.error(format, arguments);
}
}
And here is my XML:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.5.xsd"
default-autowire="no">
<aop:aspectj-autoproxy proxy-target-class="true"/>
<bean class="path.to.my.package.LoggingAspect" />
</beans>
Now, when I add the #Loggable annotation to an existing method that is being called elsewhere in my program, everything shows up correctly in my logs as expected. The working annotations with method looks something like this:
#Loggable
public boolean testString(String test) {
return test.equals("foo");
}
However, when I try to add the annotation to a helper method rather than the method that is already being called in my program, no logs show up. So now the code that doesn't work looks something like this:
public boolean testString(String test) {
return testStringHelper(test);
}
#Loggable
public boolean testStringHelper(String test) {
return test.equals("foo");
}
Can anyone offer insight into why the first scenario works, but the second scenario with the helper method doesn't? As an aside, the helper methods are all public. Also, if I add a regular log statement inside the helper method, it does show up in my logs. It's just the annotation that doesn't work with the helper method for some reason.

Spring can only advise methods of Spring beans that have been injected into other Spring beans. If a bean calls one of its own methods, then the advice will not be executed.
Spring AOP proxies are explained in the docs.

It is because of proxies!
Spring works on concept of proxy. It cannot proxy your internal calls. So it won't work that way.
You can go through Spring Doc on Understanding AOP proxies to understand it in more details.

Related

Custom Spring AOP Annotation Not Working for Default Method

I'm trying to add already working AOP annotation around a new method in the class where it is already placed. Its not working for the new method which I have defined as a default method to an interface (not working even when not overriden) and I'm unable to find the cause of the same. Code is
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface PaymentPlanLocking {}
#Aspect
#Component
public class PaymentPlanLockAspect
{
..
#Around("#annotation(PaymentPlanLocking)")
public Object paymentPlanLocking(ProceedingJoinPoint joinPoint) throws Throwable
{
..
public interface PaymentOrchestratorService<RQ, RS>
{
/**
* #param request to validate
*/
void validate(RQ request) throws PaymentServiceException;
/**
* #param request to execute
* #return response
*/
RS execute(RQ request) throws PaymentServiceException;
/**
* #param request to execute
* #return response
*/
default RS doExecute(RQ request) throws PaymentServiceException{
throw new RuntimeException("please override this method in subclass if using old model with execute-wrapped");
}
}
#Service("holdPaymentService")
public class HoldPaymentOrchestrationService extends AbstractService<HoldResponse, HoldRequest>
implements PaymentOrchestratorService<HoldRequest, HoldResponse>
{
...
#PaymentPlanLocking
#Override
public HoldResponse execute(HoldRequest holdRequest) throws PaymentServiceException
#PaymentPlanLocking
#Override
public HoldResponse doExecute(HoldRequest holdRequest) throws PaymentServiceException
Interception working for execute(HoldRequest holdRequest) but not for doExecute(HoldRequest holdRequest). Please help me with the fix for this.
This works flawlessly for me. The only explanation why doExecute(..) interception is not working for you is that you use self-invocation, e.g. like this:
#PaymentPlanLocking
#Override
public HoldResponse execute(HoldRequest holdRequest) throws PaymentServiceException {
return doExecute(holdRequest);
}
#PaymentPlanLocking
#Override
public HoldResponse doExecute(HoldRequest holdRequest) throws PaymentServiceException {
return new HoldResponse();
}
It is a classical Spring AOP beginner's mistake to assume that this is working, even though it is clearly documented otherwise (search for the term "self-invocation").
So the problem was not in the code you showed in your question, but it is in the code you chose to hide from us. Please be advised to learn why an MCVE in every question is so important and ask better next time. Thank you.

Dynamic Kafka Consumer with AOP

I have got few dynamic Kafka consumers (based upon the department id, etc..) and you can find the code below.
Basically, I wanted to log the time taken for each onMessage() method call and so I have created a #LogExecutionTime method level custom annotation and added it for onMessage() method .
But my logExecutionTime() of LogExecutionTimeAspect never gets called even though my onMessage() is being invoked whenever there is a message on to the topic and everything else works fine.
Could you please help on what am I missing LogExecutionTimeAspect class so that it starts working?
LogExecutionTime:
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface LogExecutionTime {
}
LogExecutionTimeAspect class:
#Aspect
#Component
public class LogExecutionTimeAspect {
#Around("within(com.myproject..*) && #annotation(LogExecutionTime)")
public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
Object object = joinPoint.proceed();
long endTime = System.currentTimeMillis();
System.out.println(" Time taken by Listener ::"+(endTime-startTime)+"ms");
return object;
}
}
DepartmentsMessageConsumer class:
#Component
public class DepartmentsMessageConsumer implements MessageListener {
#Value(value = "${spring.kafka.bootstrap-servers}" )
private String bootstrapAddress;
#PostConstruct
public void init() {
Map<String, Object> consumerProperties = new HashMap<>();
consumerProperties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,
bootstrapAddress);
consumerProperties.put(ConsumerConfig.GROUP_ID_CONFIG, "DEPT_ID_HERE");
ContainerProperties containerProperties =
new ContainerProperties("com.myproj.depts.topic");
containerProperties.setMessageListener(this);
DefaultKafkaConsumerFactory<String, Greeting> consumerFactory =
new DefaultKafkaConsumerFactory<>(consumerProperties,
new StringDeserializer(),
new JsonDeserializer<>(Department.class));
ConcurrentMessageListenerContainer container =
new ConcurrentMessageListenerContainer<>(consumerFactory,
containerProperties);
container.start();
}
#Override
#LogExecutionTime
public void onMessage(Object message) {
ConsumerRecord record = (ConsumerRecord) message;
Department department = (Department)record.value();
System.out.println(" department :: "+department);
}
}
ApplicationLauncher class:
#SpringBootApplication
#EnableKafka
#EnableAspectJAutoProxy
#ComponentScan(basePackages = { "com.myproject" })
public class ApplicationLauncher extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run(ApplicationLauncher.class, args);
}
}
EDIT:
I have tried #EnableAspectJAutoProxy(exposeProxy=true), but did not work.
You should consider to turn on this option on the #EnableAspectJAutoProxy:
/**
* Indicate that the proxy should be exposed by the AOP framework as a {#code ThreadLocal}
* for retrieval via the {#link org.springframework.aop.framework.AopContext} class.
* Off by default, i.e. no guarantees that {#code AopContext} access will work.
* #since 4.3.1
*/
boolean exposeProxy() default false;
On the other hand there is something like this, which is going to be better than AOP:
/**
* A plugin interface that allows you to intercept (and possibly mutate) records received by the consumer. A primary use-case
* is for third-party components to hook into the consumer applications for custom monitoring, logging, etc.
*
* <p>
* This class will get consumer config properties via <code>configure()</code> method, including clientId assigned
* by KafkaConsumer if not specified in the consumer config. The interceptor implementation needs to be aware that it will be
* sharing consumer config namespace with other interceptors and serializers, and ensure that there are no conflicts.
* <p>
* Exceptions thrown by ConsumerInterceptor methods will be caught, logged, but not propagated further. As a result, if
* the user configures the interceptor with the wrong key and value type parameters, the consumer will not throw an exception,
* just log the errors.
* <p>
* ConsumerInterceptor callbacks are called from the same thread that invokes {#link org.apache.kafka.clients.consumer.KafkaConsumer#poll(long)}.
* <p>
* Implement {#link org.apache.kafka.common.ClusterResourceListener} to receive cluster metadata once it's available. Please see the class documentation for ClusterResourceListener for more information.
*/
public interface ConsumerInterceptor<K, V> extends Configurable {
UPDATE
#EnableAspectJAutoProxy(exposeProxy=true) did not work and I know that I could use interceptor, but I wanted to make it working with AOP.
Then I suggest you to consider to separate a DepartmentsMessageConsumer and the ConcurrentMessageListenerContainer. I mean move that ConcurrentMessageListenerContainer into the separate #Configuration class. The ApplicationLauncher is a good candidate. Make it as a #Bean and dependent on your DepartmentsMessageConsumer for injection. The point is that you need to give an AOP a chance to instrument your DepartmentsMessageConsumer, but with the #PostConstruct, that's too early to instantiate and start consumption from Kafka.

aspect is not invoked

I've got a simple aspect that supposed to set the value of class fied, that has annotation #GuiceInject.
Originally I have this
#GuiceInject(module=RepositoryModule.class)
private IRacesRepository repository;
And I expect to get similar to this
private IRacesRepository repository = GuiceInject.getInstance(IRacesRepository.class);
And here is my aspect
public aspect InjectionAspect {
Object around(): get(#GuiceInject * *) {
System.out.println(thisJoinPointStaticPart);
// instantiate object as it supposed to be null originally
return GuiceInjector.getInstance(thisJoinPoint.getTarget().getClass());
}
}
As far as I understand - I am new to AOP - it supposed to replace get invokations of the field with the code in aspect.
It compiles fine, but when I run the application - nothing happens. I get NullPointerException for readRaces method as it stays null so aspect did not work.
My main class looks like this
public class Example {
#GuiceInject(module=RepositoryModule.class)
private IRacesRepository racesRepository;
private void execute() {
System.out.println("List of races: " + racesRepository.readRaces());
}
public static void main(String[] args) {
new Example().execute();
}
}
What is the problem? Annotation has this definition
#Target(ElementType.FIELD)
// make annotation visible in runtime for AspectJ
#Retention(RetentionPolicy.RUNTIME)
public #interface GuiceInject {
Class<? extends AbstractModule> module();
}
Please try to redefine pointcut syntax as
Object around(): get(#package.subpackage.GuiceInject * *.*)
Correct field signature must specify the type of the field, the declaring type, and name. If your annotation is in different package, it should be fully qualified.

Create a pointcut based on annotation parameters

I'm trying to create a pointcut based on the parameter of an annotaion
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.METHOD})
public #interface MyAnnotation {
Class<? extends ABC> style() default A.class;
}
And the pointcut I'm currently using is:
#Pointcut("execution(#com.something.MyAnnotation * *(..))")
public void dummyMethod() {
}
#Around("method()")
public Object actualFunc(ProceedingJoinPoint joinPoint) throws Throwable {
//stuff
}
But that unfortunately activates on all values of style.
Obviously you could check in the advice if the advised method had the annotation value you are looking for, but that is less than ideal (it is a runtime check). In your case you can just use the syntax:
#Pointcut("execution(#com.something.MyAnnotation(style=B.class) * *(..))")
There is a little bit of info here on annotation value matching: https://eclipse.org/aspectj/doc/released/README-160.html

Spring AOP pointcut that matches annotation on interface

I have a service class implemented in Java 6 / Spring 3 that needs an annotation to restrict access by role.
I have defined an annotation called RequiredPermission that has as its value attribute one or more values from an enum called OperationType:
public #interface RequiredPermission {
/**
* One or more {#link OperationType}s that map to the permissions required
* to execute this method.
*
* #return
*/
OperationType[] value();}
public enum OperationType {
TYPE1,
TYPE2;
}
package com.mycompany.myservice;
public interface MyService{
#RequiredPermission(OperationType.TYPE1)
void myMethod( MyParameterObject obj );
}
package com.mycompany.myserviceimpl;
public class MyServiceImpl implements MyService{
public myMethod( MyParameterObject obj ){
// do stuff here
}
}
I also have the following aspect definition:
/**
* Security advice around methods that are annotated with
* {#link RequiredPermission}.
*
* #param pjp
* #param param
* #param requiredPermission
* #return
* #throws Throwable
*/
#Around(value = "execution(public *"
+ " com.mycompany.myserviceimpl.*(..))"
+ " && args(param)" + // parameter object
" && #annotation( requiredPermission )" // permission annotation
, argNames = "param,requiredPermission")
public Object processRequest(final ProceedingJoinPoint pjp,
final MyParameterObject param,
final RequiredPermission requiredPermission) throws Throwable {
if(userService.userHasRoles(param.getUsername(),requiredPermission.values()){
return pjp.proceed();
}else{
throw new SorryButYouAreNotAllowedToDoThatException(
param.getUsername(),requiredPermission.value());
}
}
The parameter object contains a user name and I want to look up the required role for the user before allowing access to the method.
When I put the annotation on the method in MyServiceImpl, everything works just fine, the pointcut is matched and the aspect kicks in. However, I believe the annotation is part of the service contract and should be published with the interface in a separate API package. And obviously, I would not like to put the annotation on both service definition and implementation (DRY).
I know there are cases in Spring AOP where aspects are triggered by annotations one interface methods (e.g. Transactional). Is there a special syntax here or is it just plain impossible out of the box.
PS: I have not posted my spring config, as it seems to be working just fine. And no, those are neither my original class nor method names.
PPS: Actually, here is the relevant part of my spring config:
<aop:aspectj-autoproxy proxy-target-class="false" />
<bean class="com.mycompany.aspect.MyAspect">
<property name="userService" ref="userService" />
</bean>
If I understand you correct, you want a pointcut that finds all methods in classes that extends MyService and is annotated and with the preferred arguments.
I propose that you replace:
execution(public * com.mycompany.myserviceimpl.*(..))
with:
execution(public * com.mycompany.myservice.MyService+.*(..))
The plus sign is used if you want a joinpoint to match the MyService class or a class that extends it.
I hope it helps!
Espen, your code works only for one class:
execution(public * com.mycompany.myservice.MyService+.*(..))
but what if I want this behaviour for all services in *com.mycompany.services.** package?

Categories

Resources