Hot questions for Using AspectJ in spring data jpa

Top Java Programmings / AspectJ / spring data jpa

Question:

I'm failing in my effort to advice a spring data jpa repository. The goal is to instrument (around) all non-void public methods in a particular repository annotated with a custom annotation (ResourceNotFound in this example) and throw an exception when the return value is either null or an empty collection.

@Repository 
@ResourceNotFound
@Transactional(readOnly = true)
public interface CityRepository extends JpaRepository<City, Long>, JpaSpecificationExecutor<City> { … }

The following advice is to wire all public methods of the implementations of the interface annotated with @ResourceNotFound.

@Pointcut("within(com.digitalmisfits.spring.aop.annotation.ResourceNotFound *)")
public void beanAnnotatedWithResourceNotFound() {}

@Pointcut("execution(public * *(..))")
public void publicMethod() {}

@Around("beanAnnotatedWithResourceNotFound() && publicMethod()")
public Object publicMethodInsideAClassMarkedWithResourceNotFound(ProceedingJoinPoint pjp) throws Throwable {

    System.out.println("publicMethodInsideAClassMarkedWithResourceNotFound " + pjp.getTarget().toString());;

    Object retVal =  pjp.proceed();

    if(((MethodSignature) pjp.getSignature()).getReturnType() != Void.TYPE && isObjectEmpty(retVal))
        throw new RuntimeException("isObjectEmpty == true");

    return retVal;
}

The publicMethodInsideAClassMarkedWithResourceNotFound(…) method works when the pointcut isspecified as:

@Pointcut("execution(public * package.CityRepository+.*(..))")

However, the @ResourceNotFound annotation is not being picked up. This might be due to the fact that the underlying class of the repository interface is a (proxied) SimpleJpaRepository which does not have that particular annotation.

Is there a way to propagate @ResourceNotFound to the implementation?

-- update --

Changed the question to reflect the fact that the advice (around) only should apply to repositories with a custom annotation.


Answer:

If you want to intercept the repository call on the repository level, you don't actually need to introduce a custom annotation for that. You should be able to get this working with a plain type match:

 @Pointcut("execution(public !void org.springframework.data.repository.Repository+.*(..))")

This will intercept the execution of all non-void methods of all Spring beans that extend the Spring Data Repository interface.

A slightly related example can be found in the Spring Data examples repository.

Question:

im trying to create some pointcuts and before advices for Repositories in order to enable filtering over entitymanager for some Repositories in Spring Data in Spring Boot. i also have web and service layer in project and AspectLogging works for both. But i couldnt do same for repositories. i have been struggling for 2 days and i tried so many things for fix it. i read almost every docs, issues and threads about this( proxy issues CGlib and JDK Proxy etc). i used jhipster for creating project.

i cant deploy Application except @Pointcut with CrudRepository. and even its deployed @Before isnt called for a method call in Repository. i think i have a similar Problem like in following question. proxy confusion

    Caused by: org.springframework.beans.factory.BeanCreationException: Could not autowire field: private com.xxx.zzz.business.repository.ApplyRepository com.xxx.zzz.web.rest.applyResource.ApplyRepository; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'applyRepository': Post-processing of FactoryBean's singleton object failed; nested exception is org.springframework.aop.framework.AopConfigException: Could not generate CGLIB subclass of class [class com.sun.proxy.$Proxy173]: Common causes of this problem include using a final class or a non-visible class; nested exception is java.lang.IllegalArgumentException: Cannot subclass final class class com.sun.proxy.$Proxy173
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:561)
    at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:88)
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:331)
    ... 61 more
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'applyRepository': Post-processing of FactoryBean's singleton object failed; nested exception is org.springframework.aop.framework.AopConfigException: Could not generate CGLIB subclass of class [class com.sun.proxy.$Proxy173]: Common causes of this problem include using a final class or a non-visible class; nested exception is java.lang.IllegalArgumentException: Cannot subclass final class class com.sun.proxy.$Proxy173
    at org.springframework.beans.factory.support.FactoryBeanRegistrySupport.getObjectFromFactoryBean(FactoryBeanRegistrySupport.java:116)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getObjectForBeanInstance(AbstractBeanFactory.java:1523)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:314)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:194)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.findAutowireCandidates(DefaultListableBeanFactory.java:1120)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1044)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:942)
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:533)
    ... 63 more
Caused by: org.springframework.aop.framework.AopConfigException: Could not generate CGLIB subclass of class [class com.sun.proxy.$Proxy173]: Common causes of this problem include using a final class or a non-visible class; nested exception is java.lang.IllegalArgumentException: Cannot subclass final class class com.sun.proxy.$Proxy173
    at org.springframework.aop.framework.CglibAopProxy.getProxy(CglibAopProxy.java:212)
    at org.springframework.aop.framework.ProxyFactory.getProxy(ProxyFactory.java:109)
    at org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator.createProxy(AbstractAutoProxyCreator.java:447)
    at org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator.wrapIfNecessary(AbstractAutoProxyCreator.java:333)
    at org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator.postProcessAfterInitialization(AbstractAutoProxyCreator.java:293)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsAfterInitialization(AbstractAutowireCapableBeanFactory.java:422)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.postProcessObjectFromFactoryBean(AbstractAutowireCapableBeanFactory.java:1719)
    at org.springframework.beans.factory.support.FactoryBeanRegistrySupport.getObjectFromFactoryBean(FactoryBeanRegistrySupport.java:113)
    ... 70 more
Caused by: java.lang.IllegalArgumentException: Cannot subclass final class class com.sun.proxy.$Proxy173
    at org.springframework.cglib.proxy.Enhancer.generateClass(Enhancer.java:446)
    at org.springframework.cglib.transform.TransformingClassGenerator.generateClass(TransformingClassGenerator.java:33)
    at org.springframework.cglib.core.DefaultGeneratorStrategy.generate(DefaultGeneratorStrategy.java:25)
    at org.springframework.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:216)
    at org.springframework.cglib.proxy.Enhancer.createHelper(Enhancer.java:377)
    at org.springframework.cglib.proxy.Enhancer.createClass(Enhancer.java:317)
    at org.springframework.aop.framework.ObjenesisCglibAopProxy.createProxyClassAndInstance(ObjenesisCglibAopProxy.java:56)
    at org.springframework.aop.framework.CglibAopProxy.getProxy(CglibAopProxy.java:202)
    ... 77 more

Does anyone know what its could be ?

Classes and Configs look like following.

Repository:

public interface ApplyRepository extends JpaRepository<Apply,Long>,QueryDslPredicateExecutor<Apply> {

public Page<Apply> findAll(Predicate predicate, Pageable p); 
}
...
}

Database Config:

@Configuration
@EnableJpaRepositories("com.xxx.zzz.business.repository")
@EnableJpaAuditing(auditorAwareRef = "springSecurityAuditorAware")
@EnableTransactionManagement//(proxyTargetClass = false)
public class DatabaseConfiguration  {
....

AspectJ Config:

@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true)
// @EnableLoadTimeWeaving(aspectjWeaving = ...     )
public class LoggingAspectConfiguration {
...

System Architecture:

@Aspect
public class SystemArchitecture {

/**
 * A join point is in the web layer if the method is defined
 * in a type in the com.xyz.someapp.web package or any sub-package
 * under that.W
 */
@Pointcut("within(com.xxx.zzz.web.rest..*)")
public void inWebLayer() {
}

/**
 * A join point is in the service layer if the method is defined
 * in a type in the com.xyz.someapp.service package or any sub-package
 * under that.
 */
@Pointcut("within(com.xxx.zzz.business.service..*)")
public void inServiceLayer() {
}

/**
 * A join point is in the data access layer if the method is defined
 * in a type in the com.xyz.someapp.dao package or any sub-package
 * under that.
 */
@Pointcut("within(com.xxx.zzz.business.repository..*)")
public void inDataAccessLayer() {
}



/**
 *  All layers
 */
@Pointcut("inWebLayer() || inServiceLayer() || inDataAccessLayer()")
public void inALL(){
}



 @Pointcut("within(org.springframework.data.repository.CrudRepository)")
    //@Pointcut("execution(*org.springframework.data.repository.Repository+.*               (..))")
    //@Pointcut("execution(* com.xxx.zzz.business.repository+.*(..))")
    //@Pointcut("execution(* org.springframework.data.jpa.repository.JpaRepository+.*(..))")
     //@Pointcut("execution(*        com.xxx.zzz.business.repository.ApplyRepository.*(..))")
    public void inDATAExec(){}

}

FilterAspect:

@Aspect
@Transactional
public class FilterAspect {

    private final Logger log = LoggerFactory.getLogger(this.getClass());

    @PersistenceContext
    private EntityManager entitymanager;

    @Before("com.xxx.zzz.aop.logging.SystemArchitecture.inDATAExec())") //  "execution(* com.xxx.zzz.business.repository.InvoiceRepository.*(..))"
    public void doAccessCheck() {
        if (TransactionSynchronizationManager.isActualTransactionActive() && SecurityUtils.isAuthenticated()) {
            Session session = entitymanager.unwrap(Session.class);
            session.enableFilter("GLOBAL_FILTER").setParameter("customerId", SecurityUtils.getCurrentCustomerId());
        }
    }

EDIT: i solved problem. it was related somehow to wrong pointscuts and names. i tried to change pointcuts for custom annotation in Repository. it doesnt work for method or class level. i read in following links issues about this. advice 1 advice 2 im struggling for hours for target and annotation. but no result. is it really imposible to add custom annotations in Spring Data Repositories for advicing ?

Annotation:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
//@Inherited
public @interface CustomerRequired {
    String value() default "customerrequired";
}

Repository:

public interface InvoiceRepository extends JpaRepository<Invoice,String>, QueryDslPredicateExecutor<Invoice> {

    @CustomerRequired
    public Page<Invoice> findAll(Predicate predicate, Pageable p);

     ...
}

Pointcut and Advice:

@Pointcut(value = "@target(customerRequired)",argNames = "customerRequired")
    public void targetCustomer(@SuppressWarnings("unused") CustomerRequired customerRequired) {/**/}


@Before(value = "com.xxx.zzz.aop.logging.SystemArchitecture.targetCustomer(customerRequired) && com.xxx.zzz.aop.logging.SystemArchitecture.inDataLayer()")
public void doAccessCheck(JoinPoint joinPoint, CustomerRequired customerRequired) {
    if (TransactionSynchronizationManager.isActualTransactionActive() && SecurityUtils.isAuthenticated()) {
        Session session = entitymanager.unwrap(Session.class);
        session.enableFilter("GLOBAL_FILTER").setParameter("customerId", SecurityUtils.getCurrentCustomerId());
    }
}

Answer:

Instead of using

@Pointcut("within(org.springframework.data.repository.CrudRepository)")
public void inDATAExec(){}

use like following

@Pointcut("this(org.springframework.data.repository.Repository)")
public void inDATAExec(){}

and what it does is

any join point (method execution only in Spring AOP) where the 
proxy implements the Repository interface

You can have a look it at http://docs.spring.io/spring/docs/current/spring-framework-reference/html/aop.html

Hope it helps!

Question:

I am trying to generate a warning if the programmer returns an Arraylist instead of a list. I use Spring Boot , Spring Data JPA.

A sample Pojo

@Entity
public class Box {

    @Id
    @GeneratedValue
    private long id;

    private long prio;


    public long getPrio() {
        return prio;
    }

    public void setPrio(long prio) {
        this.prio = prio;
    }

    public long getId() {
        return id;
    }   

    public void setId(long id) {
        this.id = id;
    }

}

my repository:

@Repository
public interface BoxRepository extends JpaRepository<Box, Long>{

    public List findByPrio(long prio);  
}

now my Aspect:

@Aspect
@Component
public class ReturnList {

    @AfterReturning(value = "within(de.fhb.*) && !within(org.springframework.*) && call(* de.fhb..*(..))", returning = "returnValue")
    public void logServiceAccess(JoinPoint joinPoint, Object returnValue) {

        if (returnValue != null) {
            if (returnValue.getClass() != null) {

                Class<?> clazz = returnValue.getClass();

                if (java.util.List.class.isAssignableFrom(clazz)) {
                    System.out
                            .println("Please use List instead of a concrete implementation ( "+ returnValue.getClass() + " ) for method: "
                                + joinPoint.getSignature().getName() + ".");
                }

            }
        }

    }

}

My problem

looks like spring data (jpa repository) is returning an Arraylist. I dont want to catch methods from the jpa repository, i excluded the org.springframework but the aspect is still triggered if i run something like this line:

System.out.println(boxRepository.findByPrio(1));

Any hints that will stop trigger the aspect from calling spring jparepository methods ?

complete code : https://github.com/svenhornberg/MDSD


Answer:

First of all, List.class.isAssignableFrom(clazz) is true for List as well as for ArrayList, i.e. you cannot differentiate between the two like this. And by the way, returnValue.getClass() will never evaluate to List because that is an interface type. It will always evaluate to the actual implementation class such as ArrayList. So your contrived way of trying to find out what you want to know via reflection is doomed anyway.

The good news is: AspectJ enables you to do what you want. You seem to use real AspectJ via load or compile time weaving, otherwise you could not use the call() pointcut because it is unavailable in Spring AOP. Edit: Yes, your Gradle build shows you are using compile time weaving. But hey, why are you using the compiler in the current version 1.8.4 and the AspectJ runtime in a massively outdated 1.5.4? You should harmonise the two and also use aspectjrt in version 1.8.4.

Now let us deconstruct your pointcut:

within(de.fhb.*) &&
!within(org.springframework.*) &&
call(* de.fhb..*(..))

It means: Intercept calls to any methods defined in de.fhb or its subpackages (..* notation), but only if the calls are also made from classes defined in de.fhb but not in subpackages (.* notation). The !within(org.springframework.*) part is redundant because code defined in de.fhb.* can never be also come from org.springframework.* at the same time. It makes no sense.

What you probably really want is find out if in your own packages there are methods returning a concrete type like ArrayList instead of the interface type List. Correct? I think the pointcut should rather look like this:

within(de.fhb..*) &&
execution(java.util.List+ *(..)) &&
!execution(java.util.List *(..))

It means: For all classes within de.fhb or its subpackages, intercept all methods returning a List+ (the + means: a List or its subtypes) according to its method signature. Because this would also match methods returning the parent type List and you only want the subtypes, you must exclude that type in the third part of the pointcut via !execution(java.util.List *(..)).

I am matching method executions rather than calls because that is more efficient. If a method is called from 100 places in your package, call() would weave aspect code into 100 calling joinpoints while execution() really just weaves place where the method is actually defined.

Here is some sample code:

Driver application:

You see that there is one method correctly declaring (according to your wish) a List return type while others declare "forbidden" return types such as ArrayList, LinkedList and Vector.

package de.fhb.app;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Vector;

public class Application {
    public static List<String> methodReturningList() {
        return new ArrayList<String>();
    }

    public static ArrayList<String> methodReturningArrayList() {
        return new ArrayList<String>();
    }

    public static LinkedList<String> methodReturningLinkedList() {
        return new LinkedList<String>();
    }

    public static Vector<String> methodReturningVector() {
        return new Vector<String>();
    }

    public static void main(String[] args) {
        methodReturningList();
        methodReturningArrayList();
        methodReturningLinkedList();
        methodReturningVector();
    }
}

Type checking aspect, variant A (runtime checking):

package de.fhb.aspect;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;

@Aspect
public class ReturnTypeChecker {
    @AfterReturning("within(de.fhb..*) && execution(java.util.List+ *(..)) && !execution(java.util.List *(..))")
    public void logServiceAccess(JoinPoint thisJoinPoint) {
        System.out.println(thisJoinPoint);
    }
}

Console output:

execution(ArrayList de.fhb.app.Application.methodReturningArrayList())
execution(LinkedList de.fhb.app.Application.methodReturningLinkedList())
execution(Vector de.fhb.app.Application.methodReturningVector())

As you can see, you get exactly those methods intercepted which are problematic according to your definition.

Type checking aspect, variant B (compile time checking):

But AspectJ can do more. Why not throw a warning or even an error during compilation, i.e. before the software is even packaged and deployed? Your development team can fix the bugs before they get into the production code. In order to do that, use @DeclareWarning or @DeclareError:

package de.fhb.aspect;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.DeclareError;

@Aspect
public class ReturnTypeChecker {
    @DeclareError("within(de.fhb..*) && execution(java.util.List+ *(..)) && !execution(java.util.List *(..))")
    private static final String typeWarning = "Please do not declare methods returning concrete subclasses of List";
}

Now you will get compilation errors on the console. In Eclipse it looks like this: