Using @Autowired with AspectJ and Springboot

maxiplay picture maxiplay · Jan 25, 2014 · Viewed 12.5k times · Source

I want to use @Autowired annotation into an "Aspect". I want to inject a repository in my aspect but when I try to call a method of my autowired class a NullPointException occurs.

@Aspect
public class AspectSecurity {

@Autowired
private UserRepository userRepository;


@After("execution(public * dash.*.*Controller.*(..))")
public void authentication(JoinPoint jp) throws UnauthorizedException {
    System.out.println("SECURITY !");

    System.out.println(userRepository.findAll().toString());

   }
}

I have already tried to add @Component above my aspect Class but I have the same error.

If I don't use an aspect class but a @Controller I can call my repository without problems.

Some documentation speak about spring configuration with xml files but with spring boot I don't have these files.

Here a part of my pom.xml which calls the aspectJ plugin:

<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>aspectj-maven-plugin</artifactId>
    <version>1.4</version>
    <configuration>
        <showWeaveInfo>true</showWeaveInfo>
        <source>1.6</source>
        <target>1.6</target>
        <Xlint>ignore</Xlint>
        <complianceLevel>${compiler.version}</complianceLevel>
        <encoding>UTF-8</encoding>
        <verbose>false</verbose>
        <aspectLibraries>
            <aspectLibrary>
                <groupId>org.springframework</groupId>
                <artifactId>spring-aspects</artifactId>
            </aspectLibrary>
        </aspectLibraries>
    </configuration>
    <executions>
        <execution>
            <goals>
                <goal>compile</goal>
                <goal>test-compile</goal>
            </goals>
        </execution>
    </executions>
    <dependencies>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>${aspectj.version}</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjtools</artifactId>
            <version>${aspectj.version}</version>
        </dependency>
    </dependencies>
</plugin>

Here my Application class:

package dash;

import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.SpringApplication;
import org.springframework.context.annotation.ComponentScan;

@ComponentScan
@EnableAutoConfiguration
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

Here the Controller class which the aspect is called:

package dash.user;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import dash.GenericController;

@Controller
@RequestMapping("/user")
public class UserController extends GenericController {

@Autowired
private UserRepository repository;

@RequestMapping("/findAll")
public @ResponseBody User create(
        @RequestParam(value="login", required=true) String login,
        @RequestParam(value="password", required=true) String password) {
    
    System.out.println(login);
    System.out.println(password);
    

    System.out.println("Users found with findAll():");
    System.out.println("-------------------------------");
    for (User user : repository.findAll()) {
        System.out.println(user);
    }
    
    
    return  repository.findOne("root");
    }
}

Note: I have already tried to add @EnableAspectJAutoProxy above my application class.

Answer

Dave Syer picture Dave Syer · Jan 26, 2014

It's quite tricky to set up AspectJ weaving so a few things could be wrong here. I would suggest that you do not use @Component on your @Aspect (or at least make sure it is excluded from a @ComponentScan). The reason for that is you have to create a @Bean of that type and explicitly use the same creation mechanism that AspectJ does, so that Spring and AspectJ agree on the value of the singleton instance. I believe the correct way to do that is to use the static convenience methods in Aspects in your @Bean definition. E.g.

@Bean
public AspectSecurity interceptor() {
    AspectSecurity aspect = Aspects.aspectOf(AspectSecurity.class);
    // ... inject dependencies here if not using @Autowired
    return aspect;
}

In addition you will need an aop.xml to ensure that the compiled aspect is on the AspectJ weaver path. It could be that is what you are doing with the Maven AspectJ plugin, but if it was me doing this I would probably just create an aop.xml manually, use @EnableLoadTimeWeaving, and ditch the plugin. You can probably decide yourself based on what works.

There can also be lifecycle issues if the aspect needs to intercept something that is used during the construction of the application context. You can maybe avoid that by not relying on any interception in @Bean methods, or else you end up playing games with @DependsOn to try and force a particular order of bean creation. Whether your app suffers from that yet I can't say.

PREVIOUSLY (obsolete with Spring Boot 1.3):

Another stumbling block is that you are using Spring Boot and @EnableAutoConfiguration which explicitly switches on @EnableAspectJAutoProxy, and that switches off the AspectJ weaving for the Spring bean aspects. I actually have no idea if that is an intended side effect of @EnableAspectJAutoProxy, but you can disable it by excluding it from the autoconfig, e.g.

@ComponentScan
@EnableAutoConfiguration(exclude=AopAutoConfiguration.class)
public class Application {    
    ...
}

N.B. you might not notice that weaving is switched off if you forget to exclude this config because Spring will create proxies for you and many of your aspects will just work anyway.