Run aspect on proxy object - spring

I have following simple service:
#Service
public class TestServiceImpl implements TestService {
#Override
public void countExternal(Integer arg1) {
System.out.println("test - lock external");
count(arg1, new Integer(1));
}
public void count(Integer arg1, Integer arg2) {
System.out.println("test - lock internal");
}
}
that implements my simple interface:
public interface TestService {
void countExternal(Integer arg1);
}
Here's the aspect that I am using to do some validation etc. during count method:
#Aspect
#Component
public class TestAdvicer {
#Around("execution(* count(..))")
public Object advice(ProceedingJoinPoint joinPoint) throws Throwable {
// do som magic here
return joinPoint.proceed();
}
}
In my Spring configuration I have included autoproxying:
#EnableAspectJAutoProxy(proxyTargetClass = true)
Unfortunately, my TestAdvicer is never executed since count method is invoked from countExternal method. count method is executed on Proxy object and because of that advice didn't run.
Do you know how can I run my advice on Proxy object? What is the best way to solve this problem?

Related

How to test a try...finally method only been called once in SpringBoot?

I am following this article to implement a database read/write separation feature by calling different methods. However, I got the error:
Missing method call for verify(mock) here: verify(spyDatabaseContextHolder, times(1)).set(DatabaseEnvironment.READONLY);
when doing the testing.
My test case is trying to verify DatabaseEnvironment.READONLY has been set once when using TransactionReadonlyAspect AOP annotation:
// TransactionReadonlyAspectTest.java
#RunWith(SpringRunner.class)
#ContextConfiguration(classes = {LoadServiceImpl.class, TransactionReadonlyAspect.class})
public class TransactionReadonlyAspectTest {
#Autowired
private TransactionReadonlyAspect transactionReadonlyAspect;
#MockBean
private LoadServiceImpl loadService;
#Test
public void testReadOnlyTransaction() throws Throwable {
ProceedingJoinPoint mockProceedingJoinPoint = mock(ProceedingJoinPoint.class);
Transactional mockTransactional = mock(Transactional.class);
DatabaseContextHolder spyDatabaseContextHolder = mock(DatabaseContextHolder.class);
when(mockTransactional.readOnly()).thenReturn(true);
when(loadService.findById(16)).thenReturn(null);
when(mockProceedingJoinPoint.proceed()).thenAnswer(invocation -> loadService.findById(16));
transactionReadonlyAspect.proceed(mockProceedingJoinPoint, mockTransactional);
verify(spyDatabaseContextHolder, times(1)).set(DatabaseEnvironment.READONLY); // got the error: Missing method call for verify(mock)
verify(loadService, times(1)).findById(16);
assertEquals(DatabaseContextHolder.getEnvironment(), DatabaseEnvironment.UPDATABLE);
}
}
//TransactionReadonlyAspect.java
#Aspect
#Component
#Order(0)
#Slf4j
public class TransactionReadonlyAspect {
#Around("#annotation(transactional)")
public Object proceed(ProceedingJoinPoint proceedingJoinPoint,
org.springframework.transaction.annotation.Transactional transactional) throws Throwable {
try {
if (transactional.readOnly()) {
log.info("Inside method " + proceedingJoinPoint.getSignature());
DatabaseContextHolder.set(DatabaseEnvironment.READONLY);
}
return proceedingJoinPoint.proceed();
} finally {
DatabaseContextHolder.reset();
}
}
}
// DatabaseContextHolder.java
public class DatabaseContextHolder {
private static final ThreadLocal<DatabaseEnvironment> CONTEXT = new ThreadLocal<>();
public static void set(DatabaseEnvironment databaseEnvironment) {
CONTEXT.set(databaseEnvironment);
}
public static DatabaseEnvironment getEnvironment() {
DatabaseEnvironment context = CONTEXT.get();
System.out.println("context: " + context);
return CONTEXT.get();
}
public static void reset() {
CONTEXT.set(DatabaseEnvironment.UPDATABLE);
}
}
//DatabaseEnvironment.java
public enum DatabaseEnvironment {
UPDATABLE,READONLY
}
// LoadServiceImpl.java
#Service
public class LoadServiceImpl implements LoadService {
#Override
#Transactional(readOnly = true)
public LoadEntity findById(Integer Id) {
return this.loadDAO.findById(Id);
}
...
}
I just want to test DatabaseContextHolder.set(DatabaseEnvironment.READONLY) has been used once then in the TransactionReadonlyAspect finally block it will be reset to DatabaseEnvironment.UPDATABLE which make sense.
However, how to test DatabaseContextHolder.set(DatabaseEnvironment.READONLY) gets called once? Why does this error occur? Is there a better way to test TransactionReadonlyAspect?

Verifying pointcuts being called in tests

I have a dummy project where I try figure out how to test pointcuts being triggered.
My project consists of 1 aspect bean which just prints after a foo method is called
#Component
#Aspect
public class SystemArchitecture {
#After("execution(* foo(..))")
public void after() {
System.out.println("#After");
}
}
And a FooServiceImpl with implemented foo method
#Service
public class FooServiceImpl implements FooService{
#Override
public FooDto foo(String msg) {
return new FooDto(msg);
}
}
The code works and and I can see "#After" being printed to console, but I can't check programatically if after pointcut was called using the test below.
#SpringBootTest
public class AspectTest {
#Autowired
private FooService fooService;
#Test
void shouldPass() {
fooService.foo("hello");
}
}
I've also tried using non-bean proxy as was adviced in https://stackoverflow.com/a/56312984/18224588, but this time I'm getting an obvious error cannot extend concrete aspect because my spy proxy is no longer viewed as an aspect:
public class AspectNoContextTest {
#Test
void shouldPass() {
FooService fooService = Mockito.mock(FooService.class);
SystemArchitecture systemArchitecture = Mockito.spy(new SystemArchitecture());
AspectJProxyFactory aspectJProxyFactory = new AspectJProxyFactory(fooService);
aspectJProxyFactory.addAspect(systemArchitecture);
DefaultAopProxyFactory proxyFactory = new DefaultAopProxyFactory();
AopProxy aopProxy = proxyFactory.createAopProxy(aspectJProxyFactory);
FooService proxy = (FooService) aopProxy.getProxy();
proxy.foo("foo");
verify(systemArchitecture, times(1)).after();
}
}
Ok, after some digging, I found that it's possible to accomplish this by making an aspect a #SpyBean. Also AopUtils can be used for performing additional checks
#SpringBootTest
public class AspectTest {
#Autowired
private FooService fooService;
#SpyBean
private SystemArchitecture systemArchitecture;
#Test
void shouldPass() {
assertTrue(AopUtils.isAopProxy(fooService));
assertTrue(AopUtils.isCglibProxy(fooService));
fooService.foo("foo");
verify(systemArchitecture, times(1)).after();
}
}

Annotation and Pointcut on same class method

I have two aspects but only TryCatchLog is working even when method annotated with CatchRedBanner.
One on all methods returning AssertionData in package pageActions
package com.abc.acceptance.b2b.aspects;
#Aspect
#Component
public class TryCatchLogAspect {
#Pointcut(
"execution(com.abc.acceptance.b2b.annotations.AssertionData com.abc.acceptance.b2b.pageActions..*(..))")
private void pageActionsTryCatchLog() {
}
#Around("pageActionsTryCatchLog()")
public Object tryCatchLog(ProceedingJoinPoint joinPoint) throws Throwable { ...
This one for methods with my annotation
import java.lang.annotation.*;
#Target({ElementType.METHOD, ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
public #interface CheckRedBanner {
}
-----
package com.abc.acceptance.b2b.annotations;
#Aspect
#Component
#Slf4j
public class CheckRedBannerAspect {
#Before("#annotation(CheckRedBanner)")
public void myAdviceForMethodAnnotation(JoinPoint joinPoint) {
handleBeforeExecution(joinPoint);
}
protected void handleBeforeExecution(JoinPoint joinPoint) {
System.out.println("Made iitttt !!! ");
}
}
My code is calling only TryCatchLog but not CheckRedBanner even when I annotate the method
package com.abc.acceptance.b2b.pageActions;
#Component
#Slf4j
#Scope(SCOPE_CUCUMBER_GLUE)
public class BroadbandPanelActions extends PageActions {
#CheckRedBanner
public AssertionData clickFindAddress() {
broadbandPanel.getFindAddressButton().click();
return new AssertionData();
}
...
From the reference docs regarding Advice Ordering
When two pieces of advice defined in different aspects both need to
run at the same join point, unless you specify otherwise, the order of
execution is undefined. You can control the order of execution by
specifying precedence.
For me the behaviour is reproducible when I order the Aspects as follows . I have ordered so that Around advice is executed before Before advice . Also note that I have commented the joinPoint.proceed(); call.
#Component
#Aspect
#Order(0)
public class TryCatchLogAspect {
#Pointcut("execution(sec2.aop.bean.AssertionData sec2.aop.bean..*(..))")
private void pageActionsTryCatchLog() {
}
#Around("pageActionsTryCatchLog()")
public Object tryCatchLog(ProceedingJoinPoint joinPoint) throws Throwable {
// Object result = joinPoint.proceed();
System.out.println("pageActionsTryCatchLog");
return new AssertionData();
}
}
and
#Component
#Aspect
#Order(1)
public class CheckRedBannerAspect {
#Before("#annotation(CheckRedBanner)")
public void myAdviceForMethodAnnotation(JoinPoint joinPoint) {
handleBeforeExecution(joinPoint);
}
protected void handleBeforeExecution(JoinPoint joinPoint) {
System.out.println("Made iitttt !!! ");
}
}
when I uncomment the proceed() call both the aspects work.
#Around("pageActionsTryCatchLog()")
public Object tryCatchLog(ProceedingJoinPoint joinPoint) throws Throwable {
Object result = joinPoint.proceed();
System.out.println("pageActionsTryCatchLog");
return new AssertionData();
}
When we are not explicitly calling joinPoint.proceed(); spring does that for us. But in the first case we are returning without calling the underlying method and the #Before advice fails to execute.

Why Spring Boot AOP pointcut not triggered

wanner test spring boot(1.5.20) aop with minimum code
class being aopped,
#Component
public class Test {
public Test() {
System.out.println("test constr");
}
public void print() {
System.out.println("test print");
}
}
aop class
#Aspect
#Component
public class LoggingAspect {
public LoggingAspect() {
System.out.println("aspect constr");
}
#After("execution(* *.Test.*(..))")
public void log(JoinPoint joinPoint) {
System.out.println("aspect print");
}
}
main class
#SpringBootApplication
#EnableAspectJAutoProxy(proxyTargetClass = true)
public class AopApplication implements CommandLineRunner {
public static void main(String[] args) {
SpringApplication.run(AopApplication.class, args);
}
#Autowired
private Test test;
#Override
public void run(String... strings) throws Exception {
test.print();
}
}
both Test bean and LoggingAspect bean is created. Test.pring is executed. However, the pointcut log() is never triggered. I searched so and found no answer. I also tried #EnableAspectJAutoProxy with proxyTargetClass = True or False. In my understanding this params force to use cglib for Test class.
please let me know what I missed
figure out. change from .Test. to com.example.aop.Test.*, then works.

How to go about Spring autowiring?

public class ProcessSchedulerServlet implements javax.servlet.Servlet {
Timer timer=new Timer();
#Override
public void init(ServletConfig arg0) throws ServletException {
timer.scheduleAtFixedRate(new TimerTask() {
public void run() {
LogProcessorService logProcessorService=new LogProcessorServiceImpl();
logProcessorService.processPageRequestsLogs();
}
}, 60*1000, 120*1000);
}
This is ugly and it doesn't work, anyway. The LogProcessorServiceImpl has properties with #Autowired annotation. These properties are not autowired when this code runs. This may be expected.
The real question is: how to make this run() method work. It seems to me that Spring wants the logProcessorService to be autowired to have properties within LogProcessorServiceImpl autowired, as well.
=== SCENARIO 1 ==============================================================
public void run() {
final LogProcessorService logProcessorService=null;
WebApplicationContextUtils.getWebApplicationContext(arg0.getServletContext()).getAutowireCapableBeanFactory().autowireBean(logProcessorService);
logProcessorService.processPageRequestsLogs();
}
Result: compile time error: Cannot refer to a non-final variable arg0 inside an inner class defined in a different method
=== SCENARIO 2 ==============================================================
#Autowired
LogProcessorService logProcessorService;
public void run() {
logProcessorService.processPageRequestsLogs();
}
Result: run time error: logProcessorService is null;
==== SOLUTION (from Boris) ======================================================
public class ProcessSchedulerServlet implements javax.servlet.Servlet {
Timer timer=new Timer();
#Autowired
LogProcessorService logProcessorService;
#Override
public void init(ServletConfig arg0) throws ServletException {
final AutowireCapableBeanFactory autowireCapableBeanFactory=WebApplicationContextUtils.getWebApplicationContext(arg0.getServletContext()).getAutowireCapableBeanFactory();
autowireCapableBeanFactory.autowireBean(this);
timer.scheduleAtFixedRate(new TimerTask() {
public void run() {
logProcessorService.processPageRequestsLogs();
}
}, 60*1000, 120*1000);
}
Why bother with servlets and Timer class if Spring has a built in scheduling support:
#Service
public class LogProcessorService {
#Scheduled(fixedRate=120*1000, initialDelay=60*1000)
public void processPageRequestsLogs() {
//...
}
}
That's it! No timers, runnables and servlets. Note: initialDelay was introduced in Spring 3.2 M1 (see SPR-7022).

Resources