Search code examples
javaspringdependency-injectionaopaspectj

How to Autowire members reliably inside an Aspect - even after a context refresh?


I have an AspectJ aspect in which I want to have @Autowired fields. Given that by default, the aspects are singletons created outside the Spring container, Spring does not manage any of the dependency injection for the aspect.

Searching around on SO, Spring autowired bean for @Aspect aspect is null encountered the same problem, and using the @Configurable annotation on the aspect somehow magically allows Spring to do the dependency injection (see @codebrickie response). I'm still not entirely clear how that magic works, but it seems to work fine.

My problem, now, is that if I refresh the Spring context, Spring does not update the dependencies to point to the new beans. This is a problem in my unit tests. I have a class with @DirtiesContext(classMode=ClassMode.AFTER_EACH_TEST_METHOD) defined, indicating that I want spring to refresh the context after each test method. However, given that it doesn't update the Aspect's dependencies, any tests after my first test fail given that the referenced beans (left over autowired from the first run) are no longer valid.

Sample Test class:

@DirtiesContext(classMode=ClassMode.AFTER_EACH_TEST_METHOD)
@Transactional
public class UserServiceImplTest extends TestBase {
    @Test
    @RequiredUserDetails(permissions={Permission.USER_LIST})
    public void testFindUser() throws Exception {
        User u = dod.getRandomUser();
        long userId = u.getId();

        User u2 = userService.findUser(userId);

        assertThat(u2, equalTo(u));
    }

    @Test(expected=AccessDeniedException.class)
    public void testFindUserWithoutPermissions() throws Exception {
        User u = dod.getRandomUser();
        long userId = u.getId();

        User u2 = userService.findUser(userId);

        assertThat(u2, equalTo(u));
    }
}

Aspect snippet:

@Configurable
@Aspect
public class RequiredUserDetailsAspect {

    @Autowired UserRepository userRepository;

    @Pointcut("execution(public * *(..)) && @annotation(org.junit.Test)")
    public void testMethod() {};

    /**
     * Inject the specific permissions before test executes
     */
    @Before("testMethod() && requiresPermission(requiredUserDetails)")
    public void beforeTest( RequiredUserDetails requiredUserDetails){
        authenticateUser( userRepository.findOne( requiredUserDetails.id ) );
    }

    ...
    ...
    ...


}

If I put a breakpoint in my aspect in the beforeTest method, I can see that the userRepository bean reference is the same between the 2 unit tests, even though from the logs I can see that Spring has instantiated a new userRepository bean. Consequently, during the second test, the aspect is pointing to a stale bean.

How can I instruct Spring to refresh the dependencies that it injected into the aspect via the @Configurable instantiation?


Solution

  • I've have often wondered why they don't make it easier for spring to manage the aspect.

    But to make the aspect spring managed you put the following into your xml... Don't know how you'd do it with java configuration.

    Though I think configuration should work as well... although you may have issues with life cycle conflicts.

    <bean id="securityAspect" 
       class="com.afrozaar.ashes.core.security.AuthorizationAspect"> 
       factory-method="aspectOf" autowire="byType" />
    

    When the aspect compiler creates an aspect it adds that "aspectOf" method to the class. This method gives access to the object that is the aspect and having this config in your xml will expose that object to injection etc by spring.