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();
}
Related
Working with Springboot 2.7.0. I had a a working application and I made these changes on top of it
Aspect Configuration
#Configuration
#EnableAspectJAutoProxy
#ComponentScan
public class AspectConfig {}
Aspect Interface
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface Timed { }
Aspect Class to Measure method execution time
#Around("#annotation(Timed)")
public Object measureExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
Object proceed = joinPoint.proceed();
LOG.info("Time taken for {} is {} ms, joinPoint.getSignature(), System.currentTimeMillis() - start,);
return proceed;
}
Added the new #Timed annotation to an existing method in a bean (omitting non relevant code)
#Component
#ConditionalOnExpression("${oauth.enabled}")
public class JwtAuthFilter extends OncePerRequestFilter {
#Timed
public boolean verifySignatureAndExpiry(String bearerToken){
// method logic
}
}
This causes the Springboot application to fail startup.
I can get it to start if I add #Aspect to the JwtAuthFilter class.
but why would I need to do that? It makes the #Timed annotation limited use if I have to annotate every class that needs to use it with #Aspect. Not to mention, though there are no errors, the functionality won't work because an Aspect cannot work on another Aspect.
#Timed works on my controller method though.
#RestController
#RequestMapping(value = "/api/v1", produces = MediaType.APPLICATION_JSON_VALUE)
public class HealthController {
#GetMapping("/health")
#Timed
public Map<String, String> health(){
return Map.of("status", "up");
}
}
This causes the Spring Boot application to fail startup.
You should always post error messages and relevant stack traces, not just say "fails to start up". You are lucky that in this case, I remember the situation, so I can answer your question. Normally, I would be unable to do so without further information.
I can get it to start if I add #Aspect to the JwtAuthFilter class.
That does not make any sense. Why would you add #Aspect to something which is not an aspect? Of course, it makes the start-up error go away, but it also makes your real aspect not fire, because one Spring AOP aspect cannot advise another one, as you already mentioned. Therefore, this approach is - with all due respect - complete nonsense.
The reason for the exception is: You cannot advise your filter by Spring AOP, because it is derived from GenericFilterBean, which has some final methods. Final methods cannot be overriden, therefore not be proxied either. This has the effect of those methods being called upon the proxy instance directly instead of being delegated to the target object, i.e. if such a method accesses an instance field, it shall find it uninitialised, because the proxy's fields are not meant to be initialised, only the target object's ones. See also my answer here for more info.
In this case, final method org.springframework.web.filter.GenericFilterBean#init is trying to access this.logger, which leads to the NPE which makes Spring Boot's Tomcat fail to start up. This has been reported and briefly explained in this comment in Spring issue #27963, which has been closed as invalid.
#Timed works on my controller method though.
Yes, because your controller does not have a problem with accessing an instance field from a final method.
If you absolutely think that you need to measure your filter method's execution time from an aspect, you can switch from Spring AOP to native AspectJ, either for the whole project via load-time weaving or selectively for some target classes via compile-time weaving. I have tried locally, it works with the right pointcut. Then you can also advise your filter. FYI, the pointcut would be something like:
// Annotated class
#Around("execution(* *(..)) && !within(MyAspect) && #target(Timed)")
// Annotated method
#Around("execution(* *(..)) && !within(MyAspect) && #annotation(Timed)")
AspectJ is more powerful than Spring AOP, so you explicitly need to limit matching to method executions, otherwise other joinpoints such as method calls, constructor calls and others would be affected, too. You also need to make sure that the aspect does not advise itself or other aspects, which is perfectly possible in AspectJ.
I excluded part of my project for easier reproduce problem: GitHub repo.
When I compile it by Javac everything works as expected. I see logging in console when I open URLs /user/ and /user/2/:
Access: execution(List ru.krivochenko.demo.user.UserController.getAll())
Access: execution(User ru.krivochenko.demo.user.UserController.getOne(Integer))
But I wanna use AspectJ compiler. When I switch to it, error occurs:
java.lang.NoSuchMethodError: ru.krivochenko.demo.logging.LoggingAspect: method <init>()V not found
As I understood it happens because there is not no-args constructor in LoggingAspect. If I add it, I get another error, because logger is not injected:
java.lang.NullPointerException: null
at ru.krivochenko.demo.logging.LoggingAspect.beforeGettingUsers(LoggingAspect.java:28) ~[classes/:na]
So, how we can see, AspectJ ignores Autowired constructor with args.
In branch via-setter of my repo I implemented another solution. I removed #Component annotation of LoggingAspect and replaced constructor injection to setter injection. In DemoApplication.java I added #Bean configuration of LoggingAspect. It works fine, but in some situations it requires getting dependencies from application context. What is the best practice to resolve it?
Thanks for help.
Spring Aspects and compile time weaving don't automatically integrate. This is primary because aspectj and spring are fairly separate and I suspect Spring's recommended approach is not to use compile time weaving.
So thus by default Aspects are not spring magic and we need to add a little bit of plumbing to ensure they are.
In this regard, it is important to note that Aspects are not spring managed (they are managed by aspectj so we need to add something to ensure they are).
Thus the reason why you need a parameterless constructor on your aspect (so must use field injection).
Traditionally I have had to add the following piece of xml to my xml config files:
<bean id="securityAspect" class="com.<skip>.security.AuthorizationAspect"
factory-method="aspectOf" autowire="byType" />
So this works because the AspectJ compiler adds the static method aspectOf to the aspects and this method is available for acquiring the instance of the Aspect that aspectj creates (and uses).
This method is obviously not available in the source so we can't just add to our application class (DemoApplication):
#Bean
public LoggingAspect loggingAspect() {
return LoggingAspect.aspectOf();
}
Then what to do? My next option was to write some reflective code to call this method then having looked at this very helpful example that demonstrates exactly what you need - The Aspects class from AspectJ has a utilty method that does this work for us, so adding the following to our DemoApplication we have success:
#Bean
public LoggingAspect loggingAspect() {
return Aspects.aspectOf(LoggingAspect.class);
}
Btw, remove the #Component from the LoggingAspect as that will mean both Aspectj and Spring create an instance of the class...
Btw, I'd also suggest you add the following to your test class to demonstrate the problem in a test:
#Autowired
private UserController controller;
#Test
public void contextLoads() {
controller.getAll();
controller.getOne(1);
}
Btw, other suggestions to address this problem used #Configurable. I suspect this might work but you'll need to make sure you include the spring aspects java in your aspectj compile time config and I suspect it may still not work as I'm not sure the Spring context will be ready in time. i.e. if the Aspect is created before the spring context then #Configurable won't work as the beans to be injected will not yet be created.
Your approach to configure the aspect via setter injection looks valid to me. For more information about how to use AspectJ in combination with Spring check out the corresponding chapter in the Spring manual, specifically the description about how to configure AspectJ aspects by Spring IoC. It is mostly explained in the context of LTW, but it should work pretty much the same for CTW.
A simple question that might have an advanced answer.
The Question:
My question is, is there a way to instantiate only the classes, in your application context, needed for that specific JUnit test ?
The Reason:
My application context is getting quite big. I also do a lot of integration tests so you I guess you would understand when I say that every time I run a test all the classes in my application context get instantiated and this takes time.
The Example:
Say class Foo inject only bar
public class Foo {
#Inject
Bar bar;
#Test
public void testrunSomeMethod() throws RegisterFault {
bar.runSomeMethod();
}
but the application context has beans foobar and bar. I know this is not a vaild application context but rest assure all my code works.
<beans>
<bean id="foobar" class="some.package.FooBar"/>
<bean id="bar" class="some.package.Bar"/>
<beans>
So how do I tell spring to only instantiate Bar and ignore FooBar for the test class foo.
Thank you.
Consider adding default-lazy-init="true" to your spring context xml beans tag (or add lazy-init="true" to those specific beans that take a long time starting up).
This will ensure that only those beans are created that called with applicationContext.getBean(class-or-bean-name) or injected via #Autowired / #Inject into your tests. (Some other types of beans like #Scheduled beans will be created nevertheless but you need to check if that's a problem or not)
(if you use spring Java configuration, add #Lazy to the config files)
Caveat - If there is a bean that is not initialized explicitly with applicationContext.getBean() or injected as a dependency used by the bean obtained by using applicationContext.getBean(), then that bean will NO LONGER be constructed or initialized. Depending upon your application, that can cause things to fail OR not. Maybe you can selectively mark those beans as lazy-init="false"
Yes, we can do that, using context per test case. Prepare a test context xml file with the beans required for your test case.
If you use maven, place the test-context.xml under src/test/resources folder.
Annotate your required test class with the following annotation
#ContextConfiguration(locations = "classpath:test-application-context.xml")
This helps in loading only specific beans for the test case.
If you have two kinds of test cases, then
#Runwith(SpringJUnit4Runner.class)
#ContextConfiguration(locations = "classpath:test-context-case1.xml")
public class TestClassCase1 {}
#Runwith(SpringJUnit4Runner.class)
#ContextConfiguration(locations = "classpath:test-context-case2.xml")
public class TestClassCase2 {}
It's not direct answer, so I'd would not mark as solution. But hope it's helpful.
Generally I see three options.
As VinayVeluri answered nicely. Create separate contexts and launch them in every tests separately.
Create context one time per all tests. Just like here: Reuse spring application context across junit test classes It's a big optimization for testing all tests at once.
Mix those two first points. Create one smaller context only for testing purpose. Mock that, what's never is tested but can throw NPE etc. Like here: Injecting Mockito mocks into a Spring bean to boost up context build. And re-use it like in point 2. One time build for all tests. Personally I'd go with that one.
This one waiting for answer about some kind of smart test runner, which creates minimum needed context per test.
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.
My project is based on spring framework 2.5.4. And I try to add aspects for some controllers (I use aspectj 1.5.3).
I've enabled auto-proxy in application-servlet.xml, just pasted these lines to the end of the xml file:
<aop:aspectj-autoproxy />
<bean id="auditLogProcessor" class="com.example.bg.web.utils.AuditLogProcessor" />
Created aspect:
package com.example.bg.web.utils;
import org.apache.log4j.Logger;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
#Aspect
public class AuditLogProcessor
{
private final static Logger log = Logger.getLogger(AuditLogProcessor.class);
#After("execution(* com.example.bg.web.controllers.assets.AssetThumbnailRebuildController.rebuildThumbnail(..))")
public void afterHandleRequest() {
log.info("test111");
}
#After("execution(* com.example.bg.web.controllers.assets.AssetThumbnailRebuildController.rebuildThumbnail(..))")
public void afterRebuildThumbnail() {
log.info("test222");
}
}
My controllers:
class AssetAddController implements Controller
class AssetThumbnailRebuildController extends MultiActionController
When I set brake points in aspect advisors and invoke controllers I catch only afterHandleRequest() but not afterRebildThumbnail()
What did I do wrong?
NOTE
I'm asking this question on behalf of my friend who doesn't have access to SO beta, and I don't have a clue what it's all about.
EDIT
There were indeed some misspellings, thanks Cheekysoft. But the problem still persists.
Your breakpoints aren't being hit because you are using Spring's AOP Proxies. See understanding-aop-proxies for a description of how AOP Proxies are special.
Basically, the MVC framework is going to call the handleRequest method on your controller's proxy (which for example the MultiActionController you're using as a base class implements), this method will then make an "internal" call to its rebuildThumbnail method, but this won't go through the proxy and thus won't pick up any aspects. (This has nothing to do with the methods being final.)
To achieve what you want, investigate using "real" AOP via load time weaving (which Spring supports very nicely).
AspectJ doesn't work well with classes in the Spring Web MVC framework. Read the bottom of the "Open for extension..." box on the right side of the page
Instead, take a look at the HandlerInterceptor interface.
The new Spring MVC Annotations may work as well since then the Controller classes are all POJOs, but I haven't tried it myself.
The basic setup looks ok.
The syntax can be simplified slightly by not defining an in-place pointcut and just specifying the method to which the after-advice should be applied. (The named pointcuts for methods are automatically created for you.)
e.g.
#After( "com.example.bg.web.controllers.assets.AssetAddController.handleRequest()" )
public void afterHandleRequest() {
log.info( "test111" );
}
#After( "com.example.bg.web.controllers.assets.AssetThumbnailRebuildController.rebuildThumbnail()" )
public void afterRebuildThumbnail() {
log.info( "test222" );
}
As long as the rebuildThumbnail method is not final, and the method name and class are correct. I don't see why this won't work.
see http://static.springframework.org/spring/docs/2.0.x/reference/aop.html
Is this as simple as spelling? or are there just typos in the question?
Sometimes you write rebuildThumbnail and sometimes you write rebildThumbnail
The methods you are trying to override with advice are not final methods in the MVC framework, so whilst bpapas answer is useful, my understanding is that this is not the problem in this case. However, do make sure that the rebuildThumbnail controller action is not final
#bpapas: please correct me if I'm wrong. The programmer's own controller action is what he is trying to override. Looking at the MultiActionController source (and its parents') the only finalized method potentially in the stack is MultiActionController.invokeNamedMethod, although I'm not 100% sure if this would be in the stack at that time or not. Would having a finalized method higher up the stack cause a problem adding AOP advice to a method further down?