Spring AOP pointcut that matches annotation on interface

37,180

Solution 1

If I understand you correct, you want a pointcut that finds all methods in classes that extends MyService and is annotated and with the preferred arguments.

I propose that you replace:

execution(public * com.mycompany.myserviceimpl.*(..))

with:

execution(public * com.mycompany.myservice.MyService+.*(..))

The plus sign is used if you want a joinpoint to match the MyService class or a class that extends it.

I hope it helps!

Solution 2

Espen, your code works only for one class:

execution(public * com.mycompany.myservice.MyService+.*(..))

but what if I want this behaviour for all services in *com.mycompany.services.** package?

Share:
37,180
Sean Patrick Floyd
Author by

Sean Patrick Floyd

About me: Austrian / American Java developer / architect with 25+ years of experience, working in the Seattle Area for Domino Data Lab (views are my own). 50 y/o, married to a lovely German wife and father of three. Hold a 5th degree black belt in Taekwondo (Kukkiwon), love Monty Python and appreciate a good glass of Whisk(e)y. He / him. Java aficionado, dabbling in Scala / Kotlin / Groovy, off and on also JS, and Python or GoLang when I have to. Connect: Twitter, LinkedIn

Updated on December 02, 2020

Comments

  • Sean Patrick Floyd
    Sean Patrick Floyd over 3 years

    I have a service class implemented in Java 6 / Spring 3 that needs an annotation to restrict access by role.

    I have defined an annotation called RequiredPermission that has as its value attribute one or more values from an enum called OperationType:

    public @interface RequiredPermission {
    
    /**
     * One or more {@link OperationType}s that map to the permissions required
     * to execute this method.
     * 
     * @return
     */
    OperationType[] value();}
    
    public enum OperationType {
          TYPE1,
          TYPE2;
    }
    
    package com.mycompany.myservice;
    public interface MyService{
       @RequiredPermission(OperationType.TYPE1)
       void myMethod( MyParameterObject obj );
    }
    
    package com.mycompany.myserviceimpl;
    public class MyServiceImpl implements MyService{
       public myMethod( MyParameterObject obj ){
           // do stuff here
       }
    }
    

    I also have the following aspect definition:

    /**
     * Security advice around methods that are annotated with
     * {@link RequiredPermission}.
     * 
     * @param pjp
     * @param param
     * @param requiredPermission
     * @return
     * @throws Throwable
     */
    @Around(value = "execution(public *"
            + " com.mycompany.myserviceimpl.*(..))"
            + " && args(param)" + // parameter object
            " && @annotation( requiredPermission )" // permission annotation
    
    , argNames = "param,requiredPermission")
    public Object processRequest(final ProceedingJoinPoint pjp,
            final MyParameterObject param,
            final RequiredPermission requiredPermission) throws Throwable {
        if(userService.userHasRoles(param.getUsername(),requiredPermission.values()){
            return pjp.proceed();
        }else{
            throw new SorryButYouAreNotAllowedToDoThatException(
                param.getUsername(),requiredPermission.value());
        }
    }
    

    The parameter object contains a user name and I want to look up the required role for the user before allowing access to the method.

    When I put the annotation on the method in MyServiceImpl, everything works just fine, the pointcut is matched and the aspect kicks in. However, I believe the annotation is part of the service contract and should be published with the interface in a separate API package. And obviously, I would not like to put the annotation on both service definition and implementation (DRY).

    I know there are cases in Spring AOP where aspects are triggered by annotations one interface methods (e.g. Transactional). Is there a special syntax here or is it just plain impossible out of the box.

    PS: I have not posted my spring config, as it seems to be working just fine. And no, those are neither my original class nor method names.

    PPS: Actually, here is the relevant part of my spring config:

    <aop:aspectj-autoproxy proxy-target-class="false" />
    
    <bean class="com.mycompany.aspect.MyAspect">
        <property name="userService" ref="userService" />
    </bean>