Spring AOP execution with method name doesn't match - spring

I just tried an #Around for a method defined with my repository interface.
#Repository
public interface MyRepository {
Page<Some> getAllSome();
}
With my Aspect I can't specify the getAllSome method.
#Aspect
#Component
public class MyRepositoryAspect {
#Around("execution(* my.package.MyRepository.*(..))") // works
//#Around("execution(* my.package.MyRepository.getAllSome(..))") // does't work
public Object aroundGetAllSome(ProceedingJoinPoint joinPoint) throws Throwable {
}
}
Why the version with .getAllSome(..) doesn't work? How can I match a specific method?

Related

advice is not executing if the annotations contains parameters

I'm trying to get some advice to execute.
When I use annotation without parameters its do execute but when the annotation includes parameters it's not.
#Aspect
class a{
#Pointcut("execution(#com.annotations.AnnotationName* *(..))")
void someMethod() {}
#Around("someMethod()")
public Object aroundSomeMethod(ProceedingJoinPoint pjp) throws Throwable
{
// some code
}
}
Annotation:
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface AnnotationName
{
public String someString();
public boolean someBoolean();
}
The use of the annotation:
#AnnotationName(
someString= "string",
someBoolean = false
)
private void mycode()
{//code }
Following aspect code would advice a target method annotated with #AnnotationName
#Component
#Aspect
public class SomeMethodAspect {
#Pointcut("#annotation(annotationName) && within(so.qn69016852..*)")
private void someMethod(AnnotationName annotationName) {}
#Around("someMethod(annotationName)")
public Object aroundSomeMethod(ProceedingJoinPoint pjp,AnnotationName annotationName) throws Throwable
{
System.out.println(annotationName.someString());
System.out.println(annotationName.someBoolean());
return pjp.proceed();
}
}
Couple of corrections/observations .
Spring AOP cannot advice a private method of a Spring bean. The mycode() method should be in a bean and ideally public. ( Refer )
The Aspect should also be a spring bean. This can be achieved by annotating the aspect with #Component
Remember to limit the scope : https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#writing-good-pointcuts
You may also go through this answer from #kriegaex to understand why an #annotation has a global scope.
Update :
The code shared by OP also works with modifying a typo ( a space between the AnnotationName and * in the pointcut expression ) . The observations shared earlier holds good here as well.
#Component
#Aspect
public class SomeMethodAspect {
#Pointcut("execution(#so.qn69016852.anno.AnnotationName * so.qn69016852..*.*(..))")
private void someMethod() {}
#Around("someMethod() && #annotation(annotationName)")
public Object aroundSomeMethod(ProceedingJoinPoint pjp,AnnotationName annotationName) throws Throwable
{
System.out.println(annotationName.someBoolean());
System.out.println(annotationName.someString());
return pjp.proceed();
}
}

Transactional and Stream in Spring

I try to understand why this code doesn't work
In component:
#PostConstruct
public void runAtStart(){
testStream();
}
#Transactional(readOnly = true)
public void testStream(){
try(Stream<Person> top10ByFirstName = personRepository.findTop10ByFirstName("Tom")){
top10ByFirstName.forEach(System.out::println);
}
}
And repository :
public interface PersonRepository extends JpaRepository<Person, Long> {
Stream<Person> findTop10ByFirstName(String firstName);
}
I get:
org.springframework.dao.InvalidDataAccessApiUsageException: You're trying to execute a streaming query method without a surrounding transaction that keeps the connection open so that the Stream can actually be consumed. Make sure the code consuming the stream uses #Transactional or any other way of declaring a (read-only) transaction.
One key thing about Spring is that many annotated features use proxies to provide the annotation functionality. That is #Transactional, #Cacheable and #Async all rely on Spring detecting those annotations and wrapping those beans in a proxy bean.
That being the case, a proxied method can only be used when invoked on the class and not from within the class. See this about the topic.
Try:
Refactoring and call this #Transactional method from another class in your context, or
By self-autowiring the class into itself and calling the #Transactional method that way.
To demonstrate (1):
public class MyOtherClass {
#Autowired
private MyTestStreamClass myTestStreamClass;
#PostConstruct
public void runAtStart(){
// This will invoke the proxied interceptors for `#Transactional`
myTestStreamClass.testStream();
}
}
To demonstrate (2):
#Component
public class MyTestStreamClass {
#Autowired
private MyTestStreamClass myTestStreamClass;
#PostConstruct
public void runAtStart(){
// This will invoke the proxied interceptors for `#Transactional` since it's self-autowired
myTestStreamClass.testStream();
}
#Transactional(readOnly = true)
public void testStream(){
try(Stream<Person> top10ByFirstName = personRepository.findTop10ByFirstName("Tom")){
top10ByFirstName.forEach(System.out::println);
}
}
}

Spring AOP with bean scanned using mybatis.spring.*.MapperScan

I am working on one component to achieve audit using Spring AOP. I could use it for most of the service's methods. But found that with Mybatis mappers AOP point-cuts don't work.
Basically, Spring AOP only works with Spring-managed beans. But these mapper beans have been scanned using mybatis.spring.*.MapperScan and can be autowired in other Spring components.
Why can these beans not be scanned for Spring AOP? Any idea?
I can use AspectJ but was keen to find out how mybatis.spring.*.MapperScan works.
for example -
I have these configurations one for Mybatis mapper scan and other config for application specific configurations.
#Configuration
#MapperScan("com.test.mapper")
public class ProviderConfiguration {
#Bean
public SqlSessionFactory sqlSessionFactory(final DataSource src) throws Exception {
...
}
}
#Configuration
#EnableAspectJAutoProxy
public class MainConfiguration {
}
My Dao logic where i call mapper method -
#Component
public class TestDao {
//injecting mybatis mapper here
#Inject
private SaveTableData saveTableData;
public TableData save(TableData tableData) {
saveTableData.updateTableData(tableData);
}
}
I have registered my pointcuts as below
#Component
#Aspect
public class TestAdvices {
#Pointcut("execution(* com.test.mapper.SaveTableData.updateTableData(*))")
public void commonSaveTableData(TableData tableData) {
}
#Pointcut("execution(* com.test.service.CreateTableData.createTableData(*))")
public void commonCreateTableData(TableData tableData) {
}
//advices
#After("commonSaveTableData(tableData)")
public void addHistoryWhenSaveTableData(TableData tableData) throws Throwable {
//do stuff
}
//advices
#After("commonCreateTableData(tableData)")
public void addHistoryWhenCreateTableData(TableData tableData) throws Throwable {
//do stuff
}
}
Issue is commonCreateTableData which is on service method works as expected. But commonSaveTableData which is on Mybatis mapper method does't get invoke.
Question is if i can autowire these Mappers in any Spring bean why can't Spring AOP intercept method call using these pointcuts?
I think your pointcut expression is not correct, try this
#Component
#Aspect
public class TestAdvices {
#Pointcut("execution(* com.test.mapper.SaveTableData.updateTableData(*)) && args(tableData)", argNames="tableData")
public void commonSaveTableData(TableData tableData) {
}
//advices
#After("commonSaveTableData(tableData)", argNames="tableData")
public void addHistoryWhenSaveTableData(TableData tableData) throws Throwable {
//do stuff
}
//...
}
The reason you can't cut into the mapper like this is that when mapper is scanned by mybatis, it's bean definition has bean changed in a way that its interface is still the mapper interface but its class has been changed to MapperFactoryBean

Java method introspection with Spring AOP

I use spring-aop to make some treatments on my services methods. The methods on wich the treatment must occur are annotated with #History. Moreover, the annotation can have some params like "comment". Here is on exemple :
#Service
public class MyServiceImpl implements IMyService {
#Override
#History(comment = "my comment")
public void myMethod() {...}
}
public interface IMyService {
void create();
}
And, I have a aspect defined like this :
#Aspect
#Component
public class MyHistoryAspect {
#AfterReturning(pointcut = "execution(* my.service.package.*(..)) && #annotation(history)", returning = "result")
public void myTreatment(JoinPoint joinPoint, History history, Object result) {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method method = methodSignature.getMethod();
...
}
}
Now, my problem : when I use reflection to find out the value of "comment" in my aspect, I can't find it. The reason : the method is the method signature of IMyService, not the method signature of MyServiceImpl. And if I put my annotation on the interface instead of the service, my Aspect is never reached.
Am I missing something or is it the normal behavior of spring aop ?
Thank you

Mocking a property of a CGLIB proxied service not working

I'm having an issue when trying to mock a property of a service from within a Junit test:
#ContextConfiguration("classpath:application-config.xml")
#RunWith(SpringJUnit4ClassRunner.class)
public class FooServiceTests {
#Autowired
private FooServiceImpl fooService;
#Test
public void testFoo() {
String str = fooService.foo();
assertEquals("Var", str);
}
#Before
public void mockFooDao() throws Exception {
FooDao mockFooDao = Mockito.mock(FooDao.class);
Mockito.when(mockFooDao.foo()).thenReturn("Var");
ReflectionTestUtils.setField(fooService, "fooDao", mockFooDao);
}
}
Mocking fooDao has no effect since the the result is not the expected. Here is the code of both the service and the dao:
#Service("fooService")
public class FooServiceImpl implements FooService {
#Autowired
protected FooDao fooDao;
#Override
public String foo() {
return fooDao.foo();
}
}
#Repository
public class FooDaoImpl implements FooDao {
#Override
public String foo() {
return "foo";
}
}
As we can see the actual service is meant to return "foo", but the test mocks the dao so the service returns "var". I know it's a CGLIB proxy related thing but I can't figure out how to make it work without using a setter for the fooDao property. Any help would be appreciated.
Regards and thanks in advance.
Short answer
You have to unwrap the proxy and set the field on the target object:
ReflectionTestUtils.setField(unwrapFooService(), "fooDao", mockFooDao);
The unwrapFooService() can be defined as follows:
private FooServiceImpl unwrapFooService() {
if(AopUtils.isAopProxy(fooService) && fooService instanceof Advised) {
Object target = ((Advised) fooService).getTargetSource().getTarget();
return (FooServiceImpl)target;
}
return null;
}
...long one
The problem is quite complex, but solvable. As you have guessed this is a side-effect of CGLIB proxies being used. In principle, Spring creates a subclass of your FooServiceImpl named similar to FooServiceImpl$EnhancerByCGLIB. This subclass contains a reference to the original FooServiceImpl as well as... all the fields FooServiceImpl has (which is understandable - this is a subclass).
So there are actually two variables: FooServiceImpl$EnhancerByCGLIB.fooDao and FooServiceImpl.fooDao. You are assigning a mock to the former but your service uses the latter... I wrote about this pitfalls some time ago.

Resources