Spring AOP pointcut definition for string setter - java

I'm developing an aspect that checks string arguments of setter methods of my entity package for empty strings and replace them with null values. But unfortunately my aspect doesn't works well :(. I guess it is because of my pointcut definition, but I'm not sure.
My aspect looks like:
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
#Aspect
public class EmptyStringToNullSetter {
private static final Logger LOGGER = LoggerFactory
.getLogger(EmptyStringToNullSetter.class);
public void check(final JoinPoint jp) {
LOGGER.debug(jp.getSignature().toLongString());
}
}
My spring config looks like:
<bean id="emptyStringToNullSetter" class="de.foo.util.aop.parameter.EmptyStringToNullSetter" />
<aop:config>
<aop:pointcut id="entityStringSetter" expression="execution(* de.foo.entity.*.set*(..)) and args(java.lang.String)" />
<aop:aspect id="checkEmptyStringsAspect" ref="emptyStringToNullSetter">
<aop:before method="check" pointcut-ref="entityStringSetter" />
</aop:aspect>
</aop:config>
My test class looks like:
import de.foo.entity.Period;
#ContextConfiguration(locations = { "/spring/test-util-context.xml" })
public class EmptyStringToNullSetterTest extends
AbstractJUnit4SpringContextTests {
#Test
public void testCheck() {
Period period = new Period();
period.setName("");
Assert.assertNull(period.getName());
}
}
When I execute my test the aspect doesn't intercept my setter. Do anyone has any idea why?!
Cheers,
Kevin

Since you are using proxy-based AOP, the advice will apply only to Spring beans and the "period" object isn't a bean. You need to either have "period" as a bean or use AspectJ's weaving based AOP. In either case, you will also need to use an around advice instead of before.

This design is very tricky and error-prone with Spring JDK proxy based AOP.
I've mentionned this point here: http://doanduyhai.wordpress.com/2011/08/08/spring-aop-advices-on-setters-not-trigged/
Basically, an aspect define with Spring AOP is implemented at runtime as a proxy object wrapping around the original target.
In the bean lifecycle, Spring will create proxies only after the bean is fully initialized, e.g. after all properties injection by setter.
So the first time your setter is called, it will not be intercepted by the advice because the proxy does not exist yet.
However all subsequent calls to the setter will be intercepted.
Furthermore, be careful about self-invocation issues, e.g. calling the setter() inside another target method.

Related

Method Interceptor not being called in Test

My method interceptor isn't called in test context. the test context is configured for my test class: with #ContextConfiguration(locations = {"classpath:testContext.xml"}) and I have a method interceptor:
public class MyMethodInterceptor implements MethodInterceptor {
#Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("invocation >>>> " + invocation);
}
and it's configured in my context.xml file like this:
<bean id="myMethodInterceptor" class="path.to.interceptor.MyMethodInterceptor"/>
<aop:config>
<aop:advisor pointcut="#within(path.to.annotation.MyAnnotation)"
advice-ref="myMethodInterceptor" order="10"/>
</aop:config>
my expectation is in my test when I call a method with MyAnnotation the interceptor is being called, but it will not. I'm using JUnit 4.12
when I call a method with MyAnnotation
This will never work, neither in a test nor in production code. The reason is simple: A method interceptor and use of the #within() pointcut designator are diametrically opposed to each other:
#within() intercepts everything (i.e. in Spring AOP method executions) inside annotated classes, while according to your description you want to intercept annotated methods.
The solution would be to either use #annotation(path.to.annotation.MyAnnotation) and annotate all target methods, or to continue using #within(path.to.annotation.MyAnnotation), but annotate target classes instead.

AspectJ, SpringAOP, Aspect runs twice

My aspect runs twice and I don't see a reason why. Maybe someone could point me on my mistake?
Here is a code:
Creating annotation for pointcut
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.METHOD})
public #interface CustodianMetrics {
String metricId();
}
Creating an aspect
#Aspect
#Component("custodianMetricsAspect")
public class CustodianMetricsAspect {
private final MonitoringService monitoringService;
#Autowired
public CustodianMetricsAspect(MonitoringService monitoringService) {
this.monitoringService = monitoringService;
}
#After("#annotation(custodianMetricsAnnotation)")
public void count(CustodianMetrics custodianMetricsAnnotation) {
Counter metric = monitoringService.metric(custodianMetricsAnnotation.metricId(), Counter.class);
metric.inc();
}
}
Configuring xml for spring
<aop:aspectj-autoproxy>
<aop:include name="path"/>
</aop:aspectj-autoproxy>
<aop:config>
<aop:aspect id="custodianMetricsAspect" ref="custodianMetricsAspect">
<aop:after method="count"
pointcut="#annotation(custodianMetricsAnnotation)"/>
</aop:aspect>
</aop:config>
I tried to change poitcut on this
#After("#annotation(custodianMetricsAnnotation) && execution(* *(..))")
But the same result - aspect runs twice. Any suggestions?
It happens because you have configured the aspect twice - both Spring XML configuration and the #Aspect annotation.
Read the note in section 8.1.2 Spring AOP capabilities and goals of Spring framework documentation, it states the following:
One such choice that is relevant to this chapter is that of which AOP framework (and which AOP style) to choose. You have the choice of AspectJ and/or Spring AOP, and you also have the choice of either the #AspectJ annotation-style approach or the Spring XML configuration-style approach.
In this case, based on my personal experience, I highly recommend you to stick with the annotations. However, it depends on your personal taste. You may find 8.4 Choosing which AOP declaration style to use relevant.
Edit:
If you choose the annotation-based configuration, don't forget to create a Java configuration class instead of deleted <aop:aspectj-autoproxy>... line.
#Configuration
#EnableAspectJAutoProxy
public class AspectJAutoProxyConfiguration { }

How to use spring aop with class from external jar

I need to intercept org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#handle method and i use spring aop for that. It is spring class from spring-webmvc.jar. It uses in org.springframework.web.servlet.DispatcherServlet during request proccessing to my rest mvc service.
AOP config:
<bean id="contextAspect" class="com.eightbitlab.spring3rest.config.ContextAspect"/>
<aop:aspectj-autoproxy proxy-target-class="true"/>
<aop:config>
<aop:aspect ref="contextAspect">
<aop:pointcut id="contextPointcut" expression="execution (* org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handle(..))"/>
<aop:around method="around" pointcut-ref="contextPointcut"/>
</aop:aspect>
</aop:config>
Aspect bean:
#Aspect
public class ContextAspect {
public void around(ProceedingJoinPoint joinPoint) throws Throwable {
Logger.getAnonymousLogger().info("start aspect");
Object proceeded = joinPoint.proceed();
Logger.getAnonymousLogger().info("end aspect");
}
}
If I use this aspect with own classes the aspect method is executed. I think that the problem is a class from external jar, but I can't find the solution...
If you look at the signature of handle method it is marked as final.
As Spring AOP cannot advise final methods (see below excerpts from Spring docs here) consider intercepting / advising your controller instead.
You cannot add advice to final methods when you use Spring MVC. For example, you cannot add advice to the AbstractController.setSynchronizeOnSession() method. Refer to Section 10.6.1, “Understanding AOP proxies” for more information on AOP proxies and why you cannot add advice to final methods.
P.S.: Additionally have a look here as well for further understanding of AOP proxies.

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.
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>
If i understand you whant to perform action on aspect advices what i would do is something like that (Using aspectj can be change to spring annotation) :
public abstract aspect AspectPerformanceLogging {
private static Logger LOG = LoggerFactory.getLogger(AspectPerformanceLogging.class);
before() : methodExecution() {
LOG.info("Loggin stat");
doBefore(thisJoinPoint);
LOG.info("Loggin stat end");
}
after() : methodExecution() {
LOG.info("Loggin stat");
doAfter(thisJoinPoint);
LOG.info("Loggin stat end");
}
Object around() : methodExecution() {
LOG.info("Loggin stat");
Object result = doAround((ProceedingJoinPoint)thisJoinPoint);
LOG.info("Loggin stat end");
return result;
}
protected abstract pointcut methodExecution();
protected abstract Object doBefore(JoinPoint joinPoint);
protected abstract Object doAfter(JoinPoint joinPoint);
protected abstract Object doAround(ProceedingJoinPoint joinPoint);
}
Then i create others aspects extending this aspect.
So (from what I understand) you want to re-use the advise of your first project in your second project. But additionally you also want to add further logic to the advise. This can be done by wrapping the advise of project-1 with a
custom implementation in project-2.
You can do that with wrapping additional advices (see Advice ordering):
Project 1 requires some minor modifications (alternatively to implementing Ordered you could use the #Order annotation / and of course you could also inject the ordering instead of hardcoding it):
public class LoggingPerformanceAdvice implements org.springframework.core.Ordered {
Object log(final ProceedingJoinPoint pjp) throws Throwable {
// does the logging/statistics
...
Object result = pjp.proceed(pjp.getArgs());
...
return result;
}
#Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE;
}
}
Create the custom Advice-advising implemenation in your Project 2
public class AdditionalPerformanceAdvice implements Ordered {
Object log(final ProceedingJoinPoint pjp) throws Throwable {
...
Object result = pjp.proceed(pjp.getArgs());
...
return result;
}
#Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE;
}
}
Wire the components together:
<!-- component of project 1 -->
<bean id="loggingPerformanceAdvice" class="com.acme.project1.LoggingPerformanceAdvice"/>
<!-- component of project 2 -->
<bean id="additionalPerformanceAdvice" class="com.acme.project2.AdditionalPerformanceAdvice"/>
<aop:config>
<aop:aspect ref="loggingPerformanceAdvice">
<aop:around pointcut="execution(* com.acme..*(..))" method="log"/>
</aop:aspect>
<aop:aspect ref="additionalPerformanceAdvice">
<aop:around pointcut="execution(* com.acme..*(..))" method="log"/>
</aop:aspect>
</aop:config>
Aspect with the Ordered.HIGHEST_PRECEDENCE is called first.
First of all Spring does not use aspectJ to implement AOP but either JDK or cglib dynamic proxies. The aspectJ style here just refers to the syntax.
Aspectj aspects are static and use bytecode injection applied either at compile or at class-loading time.
Then spring is only able to apply proxy on objects it manages because dynamic proxies are applied during dependency injection. Moreover if you have 2 differents projects, you must ensure they share the same spring context or they will be isolated and applying aspect from 1 project to beans of the 2nd won't work.
And yes you will probably have to use real aspectJ aspects here. For performance logging purpose it's more suitable as there will be about no performance impact.

Testing a Spring AOP Aspect

When writing aspects, how can I test that they do match and that they are invoked when I want them to?
I'm using #Aspect declarations with Spring 2.5.6.
I don't care about the functionality, that's extracted and tested otherwise.
There's three different things to test here:
Are your pointcuts matching what you expect?
Do your advice reference the right pointcut?
Does the advice do as you expect?
To test the pointcuts, you can define some test types that have the same package/type/method signatures as the intended "real" targets, then define a test advice against the pointcuts to ensure they are matched (also define some types that shouldn't be matched to ensure the pointcuts aren't too liberal). I usually do this by defining the advice to do a callback to a method in the test target, setting a flag, then assert the flag was set.
To test the advice is trickier. I tend to delegate all the advice body to a normal method, then focus on testing the method rather than the advice.
If you've done that, the only missing part is that your advice is applied to the correct pointcuts and actually calls the methods. If you're concerned this might be a problem you can do this by creating another aspect that matches your advice execution and sets a flag to show the expected delegated method was invoked by the aspect, and override the method to do nothing.
I ended up creating something which is a bit of an integration test, in the following way:
Created a Spring-aware JUnit test
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = { "aspects-test.xml" })
public class SomeAspectTest {
}
Created a spring configuration for this test which:
enables #AspectJ usage;
configures my aspect with dummy dependencies
declares a bean which should be picked up by the aspect
<aop:aspectj-autoproxy />
<bean class="SomeAspect">
<property name="userDetailsSource">
<bean class="StubUserDetailsSource"/>
</property>
</bean>
<bean class="DummyService"/>
In the unit test I retrieve the dummy service and invoke its methods
#Autowired
private DummyService _dummyService;
#Test(expected = ApplicationSecurityException.class)
public void adminOnlyFails() throws ApplicationSecurityException {
_dummyService.adminOnly();
}

Categories