Jaims Jaims - 11 days ago 8
Java Question

Aspect advising other aspects

I am currently developing two Spring applications that makes use of Spring-AOP. I have an aspect that allows simple performance logging which is defined as such:

@Aspect
final class PerformanceAdvice {
private Logger logger = LoggerFactory.getLogger("perfLogger");

public Object log(final ProceedingJoinPoint call) throws Throwable {
logger.info("Logging statistics.");
}
}


This advice can then be created through Spring AOP configuration with the following XML:

<bean id="performanceAdvice" class="com.acme.PerformanceAdvice" />

<aop:config>
<aop:aspect ref="performanceAdvice">
<aop:around pointcut="execution(* com.acme..*(..))" method="log"/>
</aop:aspect>
</aop:config>


This works fine for classes that are created by Spring such as classes annotated with
@Service
. But I'd like this aspect to also advise other aspects in the secondary project. I'm aware Spring doesn't support this as noted in their docs:


In Spring AOP, it is not possible to have aspects themselves be the target of advice from other aspects. The @Aspect annotation on a class marks it as an aspect, and hence excludes it from auto-proxying.


Thus I'm probably in need of something more powerful such as
AspectJ
. Or is it possible to make Spring aware of the aspect and still allow advising? From numerous other questions (that aren't directly related to this specific problem) on StackOverflow I have tried making aspects
@Configurable
, making them Spring-aware by defining them as a
@Component
and played around with various XML and plugin settings such as:

<context:spring-configured />
<context:annotation-config/>
<context:load-time-weaver/>
<aop:aspectj-autoproxy/>


I'm running out of ideas now. Will I be needing to write fully-fledged
AspectJ
aspects? If so, can it use the same configuration such as Spring, referencing the existing aspect and defining a new pointcut? This would be useful so I don't have to re-work the
PerformanceAdvice
for
Project 1
but still reference and use it in
Project 2
.

edit regarding this comment:
To make myself more clear, I have the following example.

I have a Service in
Project 2
.

@Service
public class TargetSpringServiceImpl implements TargetSpringService {
@Override
public String doSomeComplexThings(String parameter) {
return "Complex stuff";
}
}


When this method gets called, I have an aspect that does some validation.

@Aspect
public class ValidationAdvice {
@Autowired
ValidationService validationService

public void validate(JoinPoint jp) throws Throwable {
//Calls the validationService to validate the parameters
}
}


With the following pointcut as execution:

<bean id="validationAdvice" class="com.acme.advice.ValidationAdvice" />

<aop:config>
<aop:aspect ref="validationAdvice">
<aop:before pointcut="execution(* com.acme.service..*(..))" method="validate"/>
</aop:aspect>
</aop:config>


I'd like to have my
PerformanceAdvice
's
log()
method to be invoked on the
ValidationAdvice
's
validate()
method. NOT on the
doSomeComplexThings()
method of the
TargetSpringService
class. As this is just an additional pointcut. The problem lies with advising the method of another aspect.

Answer

I have figured out two possible solutions to my problem. One is actually advising the aspect, the other works around the problem but is actually more elegant.

Solution 1: Advise the aspect

In AspectJ it's possible to weave just about anything. With the help of a META-INF/aop.xml file as stated in the AspectJ LTW documentation, I could reference the aspect and define a new pointcut in the following way.

Changes to project 1

The PerformanceAdvice

To allow AspectJ to define a new pointcut, the advice has to be abstract and have an abstract pointcut method that can be hooked into.

@Aspect
final class PerformanceAdvice extends AbstractPerformanceAdvice {
    @Override
    void externalPointcut(){}
}

@Aspect
public abstract class AbstractPerformanceAdvice {
    private Logger logger = LoggerFactory.getLogger("perfLogger");

    @Pointcut
    abstract void externalPointcut();

    @Around("externalPointcut()")
    public Object log(final ProceedingJoinPoint call) throws Throwable {
        logger.info("Logging statistics.");
    }
}

Changes to project 2

The META-INF/aop.xml

The aop.xml file defines a new aspect called ConcretePerformanceAdvice. It extends of the AbstractPerformanceAdvice as well but defines a new pointcut. Then, in AspectJ it IS possible (unlike in Spring-AOP) to define a pointcut to another aspect.

<aspectj>
    <aspects>
        <concrete-aspect name="com.example.project2.ConcretePerformanceAdvice" extends="com.example.project1.AbstractPerformanceAdvice">
            <pointcut name="externalPointcut" expression="execution(* com.example.project2.ValidationAdvice.validate(..))"/>
        </concrete-aspect>
    </aspects>    
    <weaver options="-verbose"/>
</aspectj>

The pom.xml

Weaving the aspect requires some instrumentation. This requires both a dependency and a plugin to execute it. As for the dependency, it is the following:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-instrument</artifactId>
    <version>${org.springframework.version}</version>
</dependency>

At the moment, during testing, I do the instrumentation through the surefire-plugin. That requires the following bit:

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-surefire-plugin</artifactId>
            <version>2.8</version>
            <configuration>
                <forkMode>once</forkMode>
                <argLine>
                    -javaagent:"${settings.localRepository}/org/springframework/spring-instrument/${org.springframework.version}/spring-instrument-${org.springframework.version}.jar"
                </argLine>
                <useSystemClassloader>true</useSystemClassloader>
            </configuration>
        </plugin>
    </plugins>
</build>

The Spring context

To enable the load-time weaving, it's also necessary to activate the weaving. So the following has to be added to the Spring context.

<context:load-time-weaver/>

Solution 2: Delegate to a Spring bean

Spring-AOP does not allow aspects to advise other aspects. But it does allow advice to be run on a Spring @Component of course. So the other solution would be to move the validation done in the advice, to another Spring bean. This Spring bean is then autowired into the advice and executed, but the PerformanceAdvice has its pointcut on the validation component and not on the validation aspect. So it would look like the following:

Changes to project 1

None!

Changes to project 2

The advice autowires the Spring @Component and delegates its logic to the component.

@Aspect
public class ValidationAdvice {
    @Autowired
    private ValidatorDefault validatorDefault;

    public void validate(JoinPoint jp) throws Throwable {
        validatorDefault.validate(jp);
    }
}

@Component
public class ValidatorDefault {
    @Autowired
    ValidationService validationService

    public void validate(JoinPoint jp) throws Throwable {
        //Calls the validationService to validate the parameters
    }
}

Then in the Spring context it's possible to define the pointcut on the @Component while the ValidationAdvice autowires the @Component.

<!-- Scan the package to find the ValidatorDefault component for autowiring -->
<context:component-scan base-package="com.example.project2" />

<bean id="validationAdvice" class="com.example.project2.ValidationAdvice" />
<bean id="performanceAdvice" class="com.example.project1.PerformanceAdvice" />

<aop:config>
    <aop:aspect ref="validationAdvice">
        <aop:before pointcut="execution(* com.acme.service..*.*(..))" method="validate"/>
    </aop:aspect>
    <aop:aspect ref="performanceAdvice">
        <aop:around pointcut="execution(* com.example.project2.ValidatorDefault.validate(..))" method="log"/>
    </aop:aspect>
</aop:config>