Why AfterReturning PointCut is not working? - java

My pointcut for method public int quantity() in ShoppingCart.java is not working. I am trying to create a pointcut to capture the return value of the method public int quantity() but the relevant advice for #AfterReturning pointcut doesn't seem to get invoked even after the quantity method is invoked by main method (See the file AuthenticationAspect.java which is trying to define an afterReturning Pointcut).
File ShoppingCart.java
package org.example;
import org.springframework.stereotype.Component;
#Component
public class ShoppingCart {
public int quantity()
{
System.out.println("\n Quantity method called");
return 2;
}
}
AuthenticationAspect.java
package org.example;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
#Aspect
#Component
public class AuthenticationAspect {
#Pointcut("execution(* org.example.ShoppingCart.quantity())")
public void afterReturningPointCut()
{
}
#AfterReturning(pointcut = "afterReturningPointCut()", returning = "retVal")
public void afterReturning(String retVal)
{
System.out.println("The quantity used is " + retVal);
}
}
Main.java
package org.example;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Main {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(BeanConfig.class);
ShoppingCart cart = context.getBean(ShoppingCart.class);
cart.quantity();
}
}

A returning clause restricts matching to only those method executions that return a value of the specified type.
The ShoppingCart.quantity() method returns int, therefore it doesn't match the String return type declared in the advice. You can change the retVal type to int or to Object, which matches any return value.
For reference: After Returning Advice

Related

How to test Aspects

I have found and tried to follow this answer by Roman Puchkovskiy with a detailed example, but I am missing some detail.
Here is the aspect I am trying to test:
package com.company.reporting.logger.consumer.prometheusmetrics;
import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.MeterRegistry;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.kafka.KafkaException;
import org.springframework.stereotype.Component;
#Aspect
#Component
#Slf4j
/**
* AOP Class to capture error count due to kafka exception
*/
public class MetricsCollection {
MeterRegistry meterRegistry;
private final Counter counter;
public MetricsCollection(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
counter = Counter.builder("Kafka.producer.error.count").description("Custom Kafka Producer metrics for business exception").tags("behavior", "exception").register(meterRegistry);
}
/***
* point cut for jointPoint within service class execution
*/
#Pointcut("within (#org.springframework.stereotype.Service *)")
public void serviceBean() {
// this is pointcut
}
/***
* point cut for all the jointPoints
*/
#Pointcut("execution(* *(..))")
public void methodPointcut() {
// this is pointcut
}
/***
*
* increasing counter upon kafka exception
*/
#AfterThrowing(pointcut = "serviceBean() && methodPointcut()", throwing = "e")
public void handleException(Exception e) {
if (e instanceof KafkaException || e instanceof org.apache.kafka.common.KafkaException) {
LOGGER.error("*** In Aspect ErrorHandler *** " + e.getLocalizedMessage());
counter.increment();
}
}
}
And here is my unit test class:
package com.company.reporting.logger.consumer.prometheusmetrics;
import com.company.reporting.logger.consumer.utils.TestUtils;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.read.ListAppender;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.composite.CompositeMeterRegistry;
import org.apache.kafka.common.KafkaException;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.MockitoAnnotations;
import org.springframework.aop.aspectj.annotation.AspectJProxyFactory;
import org.springframework.aop.framework.AopProxy;
import org.springframework.aop.framework.DefaultAopProxyFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Controller;
class MetricsCollectionTest {
private MetricsCollection aspect;
private TestController controllerProxy;
MeterRegistry meterRegistry;
#BeforeEach
void setUp() {
MockitoAnnotations.openMocks(this);
meterRegistry = new CompositeMeterRegistry();
aspect = new MetricsCollection(meterRegistry);
AspectJProxyFactory aspectJProxyFactory = new AspectJProxyFactory(new TestController());
aspectJProxyFactory.addAspect(aspect);
DefaultAopProxyFactory proxyFactory = new DefaultAopProxyFactory();
AopProxy aopProxy = proxyFactory.createAopProxy(aspectJProxyFactory);
controllerProxy = (TestController) aopProxy.getProxy();
}
#Test
void MetricsCollection() {
MetricsCollection metricsCollection = new MetricsCollection(meterRegistry);
assertNotNull(metricsCollection);
}
#Test
void handleException() {
ListAppender<ILoggingEvent> listAppender = TestUtils.getiLoggingEventListAppender(MetricsCollection.class);
try {
controllerProxy.throwKafkaException();
} catch (Exception ex) {
if (! (ex instanceof KafkaException)) {
fail();
}
} finally {
List<ILoggingEvent> logList = listAppender.list;
assertEquals(1, logList.size());
}
}
#Controller
static
class TestController {
#Bean
String throwKafkaException() {
throw new KafkaException();
}
}
}
Finally, here is my TestUtils class:
package com.company.reporting.logger.consumer.utils;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.read.ListAppender;
import org.slf4j.LoggerFactory;
public class TestUtils {
#NotNull
public static ListAppender<ILoggingEvent> getiLoggingEventListAppender(Class clazz) {
Logger logger = (Logger) LoggerFactory.getLogger(clazz);
ListAppender<ILoggingEvent> listAppender = new ListAppender<>();
listAppender.setName(clazz.getName());
listAppender.start();
logger.addAppender(listAppender);
return listAppender;
}
}
My constructor test passes... :)
But the handleException() test is failing to trigger my aspect. Which dot on an i or cross bar on a t did I miss?
Your "unit test" is closer to an integration test, and you are testing the AOP framework more than the aspect itself. The only thing you do seem to test is the side effect of logging, which is a topic unrelated to AOP. For other ways to unit-test or integration-test aspects, see my linked answers.
Apart from that and without having tried to copy and compile your code yet, what immediately strikes me as odd is that your aspect has a within (#org.springframework.stereotype.Service *) pointcut, but your target class seems to be a #Controller. I would not expect the aspect to match there. What happens if you fix that?

X-Ray trace doesn't shows inner method call

I'm new to aws x-ray and trying to use x-ray with AOP based approach in a springboot application. I was able to get the traces in the aws console, but traces doesn't show inner method call method2() details. Am I missing anything here.
Controller class
import com.amazonaws.xray.spring.aop.XRayEnabled;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
#RestController
#RequestMapping("/xray")
#XRayEnabled
public class XrayController {
#GetMapping(value = "/method1")
public String method1() {
return method2();
}
public String method2() {
return "Hello";
}
}
Aspect Class
import com.amazonaws.xray.entities.Subsegment;
import com.amazonaws.xray.spring.aop.BaseAbstractXRayInterceptor;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import java.util.Map;
#Aspect
#Component
public class XRayInspector extends BaseAbstractXRayInterceptor {
#Override
protected Map<String, Map<String, Object>> generateMetadata(ProceedingJoinPoint proceedingJoinPoint, Subsegment subsegment) {
return super.generateMetadata(proceedingJoinPoint, subsegment);
}
#Override
#Pointcut("#within(com.amazonaws.xray.spring.aop.XRayEnabled) && (bean(*Controller) || bean(*Service) || bean(*Client) || bean(*Mapper))")
public void xrayEnabledClasses() {}
}
When I hit http://localhost:8080/xray/method1 endpoint,
AWS Xray Console doesn't show method2() details
As I later understood with the use of M. Deinum's comment AOP prevents you to wrap a function if it is in the caller is in the same class of the callee.
Read more on AOP https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop-understanding-aop-proxies
You can use a work around using below self injection
public class Test {
#Autowire
private Test test;
public void method1(){
.........
test.method2();
...........
}
public void method2(){
...............
...............
}
}
notice here we call the method 2 by test.method2(); instead of this.method2()
I referred below answer also for this solution Spring AOP not working for method call inside another method

How Spring's Cacheable Annotation can work for class initailized through new Keyword. (In a Class Constructor, initialized through Bean)

In our service, we are initializing a bean (say "A") and that internally constructing a CacheableService Object by using - new CacheableService(). And as I know spring's #Cacheable annotations won't work on class method if the class is initialized using "new" Keyword.
Then what is an alternative or a way to cache method response?
Scenario :
<bean class="com.package.src.A"/>
public class A {
Map<String, CacheableService> map;
public CacheableService2() {
map = new HashedMap();
map.put("a", new CacheableService());
}
}
import org.springframework.cache.annotation.Cacheable;
public class CacheableService {
#Cacheable(value = "entityCount", key = "#criteria.toString()")
public int someEntityCount(final String criteria) {
System.out.println("Inside function : " + criteria);
return 5;
}
}
Here is a minimum example which demonstrates caching using Spring Boot. The code for the examples below can be found here.
Go to https://start.spring.io/ and create a new Spring Boot project. Make sure to include "Spring cache abstraction" which results in this entry being added to your pom:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
Add the #EnableCaching annotation to your application:
package com.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
#EnableCaching
#SpringBootApplication
public class CacheableApplication {
public static void main(String[] args) {
SpringApplication.run(CacheableApplication.class, args);
}
}
Your service:
package com.example;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
#Service
public class CacheableService {
#Cacheable(value = "entityCount")
public int someEntityCount(final String criteria) {
System.out.print(String.format("Inside function: %s", criteria));
return 5;
}
}
Class A:
package com.example;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
#Component
public class A {
private CacheableService cacheableService;
public A(#Autowired CacheableService cacheableService) {
this.cacheableService = cacheableService;
}
public int getEntityCount(String criteria) {
return cacheableService.someEntityCount(criteria);
}
}
And then here is a test that demonstrates that the caching is working. As you can see in the test a.getEntityCount("foo") is being called twice, but in standard out we only see "Inside function: foo" being printed once. Therefore we have verified that the second call resulted in the cache being used to produce the result.
package com.example;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import static org.junit.jupiter.api.Assertions.assertEquals;
#SpringBootTest
class CacheableTest {
private final ByteArrayOutputStream outContent = new ByteArrayOutputStream();
#Autowired
private A a;
#BeforeEach
public void init() {
System.setOut(new PrintStream(outContent));
}
#Test
public void testCaching() {
a.getEntityCount("foo");
a.getEntityCount("foo");
assertEquals("Inside function: foo", outContent.toString());
}
}
EDIT:
If you want to move the cache outside of the Spring lifecycle and manually manage it then I would recommend using Caffeine. Here is the same example but now without any Spring involved.
Your service:
package com.example.withoutspring;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import java.util.concurrent.TimeUnit;
public class CaffeineCachingService {
private LoadingCache<String, Integer> entityCountCache = Caffeine.newBuilder()
.expireAfterAccess(5, TimeUnit.MINUTES)
.build(key -> someEntityCount(key));
public int cachedEntityCount(final String criteria) {
return entityCountCache.get(criteria);
}
private int someEntityCount(final String criteria) {
System.out.print(String.format("Inside function: %s", criteria));
return 5;
}
}
Class B:
package com.example.withoutspring;
public class B {
private CaffeineCachingService cacheableService;
public B() {
cacheableService = new CaffeineCachingService();
}
public int getEntityCount(String criteria) {
return cacheableService.cachedEntityCount(criteria);
}
}
And the same test but without Spring:
package com.example.withoutspring;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class CaffeineCacheableTest {
private final ByteArrayOutputStream outContent = new ByteArrayOutputStream();
private B b = new B();
#BeforeEach
public void init() {
System.setOut(new PrintStream(outContent));
}
#Test
public void testCaching() {
b.getEntityCount("foo");
b.getEntityCount("foo");
assertEquals("Inside function: foo", outContent.toString());
}
}
Obviously you need to tune the cache to perform how you want it so probably evicting the cached values after 5 minutes is not what you want but if you visit the Caffeine Github page you will see a lot of detailed examples how to configure the cache to meet your use-case.
Hope this helps!

trying to test spring caching . i need to test my #cacheble annatation on my repository

i am actually trying to test my caching mechanism . i am using caffine cache.
Test: i am calling caching method twice and expecting the same result for multiple method invocation. i.e When i cal method second time with same signature it shouldn't cal the method it should get the data from cache.
Problem: My code is actually invoking the method twice . i am mocking my repository. Please guide me, if anyone has solved this kind of problem.
my repo :
public class TemplateRepositoryOracle implements TemplateRepository
#Cacheable("Templates")
#Override
public Optional<NotificationTemplate> getNotificationTemplate(String eventTypeId, String destinationType, String destinationSubType) {}
Test:
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.Ticker;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.caffeine.CaffeineCache;
import org.springframework.cache.support.SimpleCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration
public class TemplateRepositoyOracleTest {
interface TemplateRepository {
#Cacheable("Templates")
Optional<Template> getNotificationTemplate(String eventTypeId, String destinationType, String destinationSubType);
}
#Configuration
#ConfigurationProperties(prefix = "caching")
#Data
#Slf4j
static class CacheConfiguration {
#Data
public static class CacheSpec {
private Integer expireAfterWrite;
}
private Map<String, CacheSpec> specs;
#Bean
public CacheManager cacheManager(Ticker ticker) {
SimpleCacheManager manager = new SimpleCacheManager();
if (specs != null) {
List<CaffeineCache> caches =
specs.entrySet().stream()
.map(entry -> buildCache(entry.getKey(),
entry.getValue(),
ticker))
.collect(Collectors.toList());
manager.setCaches(caches);
}
return manager;
}
private CaffeineCache buildCache(String name, CacheSpec cacheSpec, Ticker ticker) {
log.info("Cache {} specified timeout of {} min", name, cacheSpec.getExpireAfterWrite());
final Caffeine<Object, Object> caffeineBuilder
= Caffeine.newBuilder()
.expireAfterWrite(cacheSpec.getExpireAfterWrite(), TimeUnit.MINUTES)
.ticker(ticker);
return new CaffeineCache(name, caffeineBuilder.build());
}
#Bean
public Ticker ticker() {
return Ticker.systemTicker();
}
#Bean
TemplateRepository myRepo() {
return Mockito.mock(TemplateRepository.class);
}
}
#Autowired
CacheManager manager;
#Autowired
TemplateRepository repo;
#Test
public void methodInvocationShouldBeCached() {
Optional<Template> third = Optional.of(new NotificationTemplate(UUID.randomUUID(),"Test",DestinationType.SMS,"test","test",Optional.empty(),Optional.empty()));
Optional<Template> fourth = Optional.of(new NotificationTemplate(UUID.randomUUID(),"Test2",DestinationType.SMS,"test2","test2",Optional.empty(),Optional.empty()));
// the mock to return *different* objects for the first and second call
Mockito.when(repo.getNotificationTemplate(Mockito.any(String.class),Mockito.any(String.class),Mockito.any(String.class))).thenReturn(third);
// First invocation returns object returned by the method
Object result = repo.getNotificationTemplate("1","1","1");
assertThat(result, is(third));
// Second invocation should return cached value, *not* second (as set up above)
result = repo.getNotificationTemplate("1","1","1");
assertThat(result, is(third));
// Verify repository method was invoked once
Mockito.verify(repo, Mockito.times(1)).getNotificationTemplate("1","1","1");
assertThat(manager.getCache("notificationTemplates").get(""), is(notNullValue()));
// Third invocation with different key is triggers the second invocation of the repo method
result = repo.getNotificationTemplate("2","2","2");
assertThat(result, is(fourth));
}
}
Property file:
caching:
specs:
Templates:
expireAfterWrite: 1440

Spring Around Aspect does not get called in controller

The problem is that when I want to print some logs from some controller methods the around annotated method with LogInit annotation does not get called, but when I am annotating some other service method with the same annotation the around method does get called. I am stuck for some time in this problem and I need to make it work.
I have the following aspect:
package com.db.mybank.backend.aop;
import com.db.mybank.backend.model.presentation.CustomerDataVO;
//import org.apache.log4j.LogManager;
import org.apache.log4j.MDC;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
#Aspect
#Component
public class LogInitAspect {
private static final Logger LOGGER = LoggerFactory.getLogger("SPLUNK");
private static final String METRIC_SPLITTER = ";";
/**
* Creates AOP pointcut and advice
* #param joinPoint (the executing method which has been annotated with #LogInit )
* #return
* #throws Throwable
*/
#Around("#annotation(com.db.mybank.backend.aop.LogInit)")
public void logInitSplunk(ProceedingJoinPoint joinPoint) {
initMDC();
CustomerDataVO proceed = null;
try {
proceed = (CustomerDataVO) joinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
LOGGER.info(getFormattedLogLine());
return;
}
private String getFormattedLogLine() {
return MDC.get(MDCEnum.TIMESTAMP.getValue())+METRIC_SPLITTER+MDC.get(MDCEnum.CCCARD.getValue())+METRIC_SPLITTER
+MDC.get(MDCEnum.SESSIONID.getValue())+METRIC_SPLITTER+MDC.get(MDCEnum.TIMESTAMP_REQUEST.getValue())+METRIC_SPLITTER
+MDC.get(MDCEnum.TIMESTAMP_RESPONSE.getValue())+METRIC_SPLITTER+MDC.get(MDCEnum.SERVICE_ID.getValue())+METRIC_SPLITTER
+MDC.get(MDCEnum.SERVICE_URI.getValue())+METRIC_SPLITTER+MDC.get(MDCEnum.RESULT.getValue())+METRIC_SPLITTER
+MDC.get(MDCEnum.DEVICE_INFO.getValue())+METRIC_SPLITTER+MDC.get(MDCEnum.LOGIN_TIMESTAMP.getValue())+METRIC_SPLITTER
+MDC.get(MDCEnum.RESULT_LOGIN.getValue())+METRIC_SPLITTER+MDC.get(MDCEnum.LOGOUT_TIMESTAMP.getValue())+METRIC_SPLITTER
+MDC.get(MDCEnum.IP_ADDRESS_CLIENT.getValue())+METRIC_SPLITTER+MDC.get(MDCEnum.USER_AGENT_CLIENT.getValue())+METRIC_SPLITTER
+MDC.get(MDCEnum.TIMESTAMP_AUTHORIZATION.getValue())+METRIC_SPLITTER+MDC.get(MDCEnum.TRANSACTION_SERVICE_NAME.getValue())+METRIC_SPLITTER
+MDC.get(MDCEnum.VALUE_AND_CURRENCY.getValue())+METRIC_SPLITTER+MDC.get(MDCEnum.TRANSACTION_ID.getValue())+METRIC_SPLITTER
+MDC.get(MDCEnum.TIMESTAMP_TRANSACTION_SUBMIT_TO_BACKEND.getValue())+METRIC_SPLITTER+MDC.get(MDCEnum.NDG.getValue())+METRIC_SPLITTER;
}
private void initMDC(){
MDC.put(MDCEnum.TIMESTAMP.getValue(),"N/A");
MDC.put(MDCEnum.CCCARD.getValue(),"N/A");
MDC.put(MDCEnum.SESSIONID.getValue(),"N/A");
MDC.put(MDCEnum.TIMESTAMP_REQUEST.getValue(),"N/A");
MDC.put(MDCEnum.TIMESTAMP_RESPONSE.getValue(),"N/A");
MDC.put(MDCEnum.SERVICE_ID.getValue(),"N/A");
MDC.put(MDCEnum.SERVICE_URI.getValue(),"N/A");
MDC.put(MDCEnum.RESULT.getValue(),"N/A");
MDC.put(MDCEnum.DEVICE_INFO.getValue(),"N/A");
MDC.put(MDCEnum.LOGIN_TIMESTAMP.getValue(),"N/A");
MDC.put(MDCEnum.RESULT_LOGIN.getValue(),"N/A");
MDC.put(MDCEnum.LOGOUT_TIMESTAMP.getValue(),"N/A");
MDC.put(MDCEnum.IP_ADDRESS_CLIENT.getValue(),"N/A");
MDC.put(MDCEnum.USER_AGENT_CLIENT.getValue(),"N/A");
MDC.put(MDCEnum.TIMESTAMP_AUTHORIZATION.getValue(),"N/A");
MDC.put(MDCEnum.TRANSACTION_SERVICE_NAME.getValue(),"N/A");
MDC.put(MDCEnum.VALUE_AND_CURRENCY.getValue(),"N/A");
MDC.put(MDCEnum.TRANSACTION_ID.getValue(),"N/A");
MDC.put(MDCEnum.TIMESTAMP_TRANSACTION_SUBMIT_TO_BACKEND.getValue(),"N/A");
MDC.put(MDCEnum.NDG.getValue(),"N/A");
}
}
And I have the following controller method which is not working.
#RequestMapping(value = "/prelogin", method = RequestMethod.POST)
#ResponseBody
#LogInit
public AuthDataVO preLogin(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) {

Categories

Resources