Spring AOP matches invalid function - spring

This is small sample from bigger project, trying to replicate issue that we see.
Implementation:
#Component
public class TestService {
public void test(String target, String key, Object message) {
System.out.println("Wrong method");
}
public void test(String target, Object message, SomeProcessor processor) {
System.out.println("Correct method");
}
}
Wrapper:
#Aspect
#Component
public class TestServiceAspect {
#Around(value = "execution(* com.example.TestService.test(..))" +
" && args(target, key, message)",
argNames = "pjp,target,key,message"))
public Object traceTest(ProceedingJoinPoint pjp,
String target, String key, Object message) throws Throwable {
System.out.println("Wrong wrapper");
return pjp.proceed();
}
#Around(value = "execution(* com.example.TestService.test(..))" +
" && args(target, message, processor)",
argNames = "pjp,target,message,processor")
public Object traceTest(ProceedingJoinPoint pjp,
String target, Object message, SomeProcessor processor) throws Throwable {
System.out.println("Correct wrapper");
return pjp.proceed();
}
}
Calling code:
#RestController
public class Tester {
private final TestService tst;
public Tester(TestService tst) {
this.tst = tst;
}
#RequestMapping(value = "/test", method = RequestMethod.POST)
public void testHandler() {
System.out.println("START");
tst.test("foo", (Object)"hello", new SomeProcessorImpl());
System.out.println("END");
}
}
We get following output:
START
Wrong wrapper
Correct wrapper
Correct method
END
Why is it OK for second pointcut ("Wrong wrapper") to match (is it correct at all)? I know why it happens, because they internally check if type can be coerced into expected type. Based on runtime information, framework will detect that passed object ("hello") can be cast into String and SomeProcessor can be converted to Object. But does it still make right thing to do?

Just make your pointcuts more precise, not just the bound parameter types. Binding parameters is only necessary, if you actually want to use them in the advice methods.
Here is an MCVE for native AspectJ, which should work the same way in Spring AOP. I was just lazy to set up a Spring project with #Component and #Service stuff.
package de.scrum_master.app;
public interface SomeProcessor {}
package de.scrum_master.app;
public class SomeProcessorImpl implements SomeProcessor {}
package de.scrum_master.app;
public class TestService {
public void test(String target, String key, Object message) {
System.out.println("Wrong method");
}
public void test(String target, Object message, SomeProcessor processor) {
System.out.println("Correct method");
}
}
package de.scrum_master.app;
public class Tester {
private final TestService tst;
public Tester(TestService tst) {
this.tst = tst;
}
public void testHandler() {
System.out.println("START");
tst.test("foo", (Object) "hello", new SomeProcessorImpl());
// tst.test("foo", "hello", (Object) new SomeProcessorImpl());
System.out.println("END");
}
public static void main(String[] args) {
new Tester(new TestService()).testHandler();
}
}
package de.scrum_master.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import de.scrum_master.app.SomeProcessor;
#Aspect
public class TestServiceAspect {
#Around("execution(* de.scrum_master.app.TestService.test(String, String, Object)) && args(target, key, message)")
public Object traceTest(ProceedingJoinPoint pjp, String target, String key, Object message) throws Throwable {
System.out.println(pjp + " -> wrong wrapper");
return pjp.proceed();
}
#Around("execution(* de.scrum_master.app.TestService.test(String, Object, SomeProcessor)) && args(target, message, processor)")
public Object traceTest(ProceedingJoinPoint pjp, String target, Object message, SomeProcessor processor)
throws Throwable {
System.out.println(pjp + " -> correct wrapper");
return pjp.proceed();
}
}
Console output when running the driver application:
START
execution(void de.scrum_master.app.TestService.test(String, Object, SomeProcessor)) -> correct wrapper
Correct method
END

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?

Intercept all (including inherited) methods of one interface(type) annotated

I'm trying to make an pointcut of type #Around to intercept all methods of beans annotated with #Repository.
I've tried
#Around("execution(* (#org.springframework.stereotype.Repository *).*(..))")
public void aspect() {
}
Also(should be same)
#Around("#within(org.springframework.stereotype.Repository)")
public void aspect() {
}
From my testing, these poincut expressions match just the methods from the type directly annotated(not the ones inherited like the ones autogenerated DAO (findAll, findById). I want to intercept all methods (from child type annotated and also inherited methods from other types like CrudRepository.
One of usages is this
#org.springframework.stereotype.Repository
public interface SubRepository extends CrudRepository {
}
How could i implement this or it is not possible?. Thank you.
Here is an MCVE. Next time, please provide one by yourself. It should not be my job to do that for you.
Interface + sub-interface:
package de.scrum_master.spring.q70824392;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
#Repository
public interface SubRepository extends CrudRepository {
String greet(String who);
}
package de.scrum_master.spring.q70824392;
public interface SubSubRepository extends SubRepository {
int add(int a, int b);
}
Abstract class extending sub-interface + concrete class extending it:
package de.scrum_master.spring.q70824392;
import java.util.Optional;
public abstract class MyAbstractRepository implements SubSubRepository {
#Override public Object save(Object entity) { return null; }
#Override public Iterable saveAll(Iterable entities) { return null; }
#Override public Optional findById(Object o) { return Optional.empty(); }
#Override public boolean existsById(Object o) { return false; }
#Override public Iterable findAll() { return null; }
#Override public Iterable findAllById(Iterable iterable) { return null; }
#Override public long count() { return 0; }
#Override public void deleteById(Object o) {}
#Override public void delete(Object entity) {}
#Override public void deleteAllById(Iterable iterable) {}
#Override public void deleteAll(Iterable entities) {}
#Override public void deleteAll() {}
}
package de.scrum_master.spring.q70824392;
import org.springframework.stereotype.Component;
#Component
public class MyRepository extends MyAbstractRepository {
#Override
public String greet(String who) {
return "Hello " + who;
}
#Override
public int add(int a, int b) {
return a + b;
}
}
Class directly annotated with #Repository as a counter-example:
package de.scrum_master.spring.q70824392;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Repository;
import java.util.Optional;
#Component
#Repository
public class DirectlyAnnotatedRepository implements CrudRepository {
public void doSomething() {}
#Override public Object save(Object entity) { return null; }
#Override public Iterable saveAll(Iterable entities) { return null; }
#Override public Optional findById(Object o) { return Optional.empty(); }
#Override public boolean existsById(Object o) { return false; }
#Override public Iterable findAll() { return null; }
#Override public Iterable findAllById(Iterable iterable) { return null; }
#Override public long count() { return 0; }
#Override public void deleteById(Object o) {}
#Override public void delete(Object entity) {}
#Override public void deleteAllById(Iterable iterable) {}
#Override public void deleteAll(Iterable entities) {}
#Override public void deleteAll() {}
}
Demo application:
package de.scrum_master.spring.q70824392;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Configuration;
#SpringBootApplication
#Configuration
public class DemoApplication {
public static void main(String[] args) throws Throwable {
try (ConfigurableApplicationContext appContext = SpringApplication.run(DemoApplication.class, args)) {
doStuff(appContext);
}
}
private static void doStuff(ConfigurableApplicationContext appContext) {
DirectlyAnnotatedRepository directlyAnnotatedRepository = appContext.getBean(DirectlyAnnotatedRepository.class);
directlyAnnotatedRepository.doSomething();
directlyAnnotatedRepository.deleteAll();
System.out.println("----------");
SubSubRepository repo = appContext.getBean(SubSubRepository.class);
repo.add(3, 4);
repo.greet("world");
repo.deleteAll();
}
}
Aspect:
package de.scrum_master.spring.q70824392;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
#Aspect
#Component
public class MyAspect {
#Before("#within(org.springframework.stereotype.Repository)")
public void directlyAnnotated(JoinPoint joinPoint) {
System.out.println("Directly annotated: " + joinPoint);
}
#Before(
"within((#org.springframework.stereotype.Repository *)+) && " +
"within(de.scrum_master.spring.q70824392..*)"
)
public void subClassesSubInterfaces(JoinPoint joinPoint) {
System.out.println("Sub-interfaces, sub-classes: " + joinPoint);
}
}
Like you said, the first advice should only kick in for classes directly annotated with #Repository, i.e. in this example for the DirectlyAnnotatedRepository bean.
The second advice however targets all types annotated with #Repository and their subtypes - please note the +. Depending on what we want, we could add one more execution pointcut to limit the intercepted methods, but I simply chose all. Please also note that without the second within pointcut, we would target many Spring-internal components, too, which would work for some, but throw exceptions for others. So we are limiting the scope to only repository beans in our own application. Instead, we could also exclude the Spring ones we do not want.
Console log:
Directly annotated: execution(void de.scrum_master.spring.q70824392.DirectlyAnnotatedRepository.doSomething())
Sub-interfaces, sub-classes: execution(void de.scrum_master.spring.q70824392.DirectlyAnnotatedRepository.doSomething())
Directly annotated: execution(void de.scrum_master.spring.q70824392.DirectlyAnnotatedRepository.deleteAll())
Sub-interfaces, sub-classes: execution(void de.scrum_master.spring.q70824392.DirectlyAnnotatedRepository.deleteAll())
----------
Sub-interfaces, sub-classes: execution(int de.scrum_master.spring.q70824392.MyRepository.add(int,int))
Sub-interfaces, sub-classes: execution(String de.scrum_master.spring.q70824392.MyRepository.greet(String))
Sub-interfaces, sub-classes: execution(void de.scrum_master.spring.q70824392.MyAbstractRepository.deleteAll())
If you have your repository classes/interfaces all named with *Repository suffix, what you need can be archived with the following:
#Around("execution(public * *..*Repository.*(..))")
public Object aroundAnyRepositoryMethod(
ProceedingJoinPoint joinPoint) throws Throwable {
...
}
or if you usually put your repositories in the separate sub-packages, then something like this to further filter:
#Around("execution(public * com.pkg1.pkg2..repository..*Repository.*(..))")
public Object aroundAnyRepositoryMethod(
ProceedingJoinPoint joinPoint) throws Throwable {
...
}
This will pick out all methods in your repository and all methods that it inherits or overrides.

How to ignore some parameter in JPA

I pass arguments to get data from database by JPA.
There are two arguments.
The first argument is ServiceType ; to switch database from Aspect (AOP)
The second argument is used to make query.
However there is org.springframework.data.jpa.repository.query.ParameterBinder.bind error
in conclusion, how to ignore the first(ServiceType) argument to make JPA query.
The code is like that.
#Repository
public interface BlogRepository extends JpaRepository<Blog, Integer> {
List<Blog> findByName(ServiceType serviceType, String name, Pageable pageable);
}
#Aspect
#Order(1)
#Component
public class MDBDecisionAspect {
private static ThreadLocal<ServiceType> localService = new ThreadLocal<>();
#Pointcut("execution(public * com.test.jpa.mdb..*.*(com.test.enums.ServiceType, ..))")
public void repositoryAspectTarget() {
}
#Around("repositoryAspectTarget()")
public Object initDaoService(ProceedingJoinPoint joinPoint) throws Throwable {
Object[] args = joinPoint.getArgs();
if (args[0] != null && ServiceType.class.equals(args[0].getClass())) {
setServiceType(((ServiceType) args[0]));
}
return joinPoint.proceed();
}
#AfterReturning("repositoryAspectTarget()")
public void afterInitDaoServiceReturningTargetMethod() {
setServiceType(null);
}
public static ServiceType getServiceType() {
return localService.get();
}
public static void setServiceType(ServiceType serviceType) {
localService.set(serviceType);
}
}
create a service class and call repository in it. So you can use serviceType inside service class.
#Repository
public interface BlogRepository extends JpaRepository<Blog, Integer> {
List<Blog> findByName(String name, Pageable pageable);
}
class BlogService{
public List<Blog> getListOfBlog(ServiceType st, String name){
blogRepository. findByName(name);
}
}
change aspect pointcut to check service class.

Spring aop: advice never reached

I've create this pointcut:
#Pointcut(value = "execution(* net.space.service.RepositoryService.createDocumentFromBytes(..))")
public void groupBytesMethod() {
}
and this advice:
#Around("groupBytesMethod()")
public Object groupMetrics(ProceedingJoinPoint point) throws Throwable {
Object result = point.proceed();
}
I've set a breakpoint, but it's never reached.
package net.space.service;
#Service
public class RepositoryService {
private Reference createDocumentFromBytes(String id, byte[] content) throws IOException {...}
public Reference groupDocuments(#NotNull RepositoryGroupForm groupForm) {
return this.createDocumentFromBytes("id", new byte[10]);
}
}
There are many ways to make this work, I would suggest use an annotation,something like :
#Documented
#Target(ElementType.METHOD)
#Inherited
#Retention(RetentionPolicy.RUNTIME)
public #interface GroupBytesMethod {
}
Then
#Aspect
#Component
public class MyAspect {
#Around("#annotation(GroupBytesMethod)") // use full package name of annotation
public Object groupMetrics(ProceedingJoinPoint point) throws Throwable {
// do something before
Object result = null;
try {
result = point.proceed();
}
catch (Throwable t) {
// handle an exception
}
// do something after
return result;
}
}
Example here

Run aspect on proxy object

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?

Resources