Search code examples
javaspringspring-bootaspectjspring-aspects

Dependency Injection into Spring non-managed beans


I have a JPA domain class that is non managed. It is instantiated via the new operator.

UserAccount account = new UserAccount();
userRepository.save(account)

In my UserAccount class, I have a beforeSave() method which is dependent on my SecurityService to hash encode a password.

My questions is "How do I get spring DI to inject the security service into my entity?". Seems that AspectJ and LoadTimeWeaving is what I need. I've tried an array for configurations, but I can't seem to get any of them to work. I always get a NullPointerException when trying to call a method on the injected object.

UserAccount.java (This is the JPA Entity)

@Entity
@Repository
@Configurable(autowire = Autowire.BY_TYPE)
public class UserAccount implements Serializable {

    @Transient
    @Autowired
    SecurityService securityService;

    private String passwordHash;

    @Transient
    private String password;

    public UserAccount() {
        super();
    }

    @PrePersist
    public void beforeSave() {
        if (password != null) {
            // NullPointerException Here!!!
            passwordHash = securityService.hashPassword(password);  
        }
    }
}

Trying to indicate to spring to use AspectJ:

NitroApp.java (The main class)

@SpringBootApplication
@EnableTransactionManagement
@EnableSpringConfigured
@PropertySources(value = {@PropertySource("classpath:application.properties")})
public class NitroApp extends SpringBootServletInitializer {


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

}

build.gradle (Configuration)

buildscript {
    repositories {
        mavenCentral()
        jcenter()
    }

    dependencies {
        classpath "org.springframework.boot:spring-boot-gradle-plugin:1.2.2.RELEASE"
        classpath "org.springframework:springloaded:1.2.2.RELEASE"
        classpath "org.springframework:spring-aspects:4.1.6.RELEASE"
    }
}

apply plugin: 'java'
apply plugin: 'aspectj'
apply plugin: 'application'
apply plugin: 'idea'
apply plugin: 'spring-boot'

repositories {
    jcenter()
    mavenLocal()
    mavenCentral()
}

mainClassName = 'com.noxgroup.nitro.NitroApp'
applicationName = "Nitro"

idea {
    module {
        inheritOutputDirs = false
        outputDir = file("$buildDir/classes/main/")
    }
}

dependencies {
    compile("org.springframework.boot:spring-boot-starter-web")
    compile("org.springframework.boot:spring-boot-starter-thymeleaf")
    compile("org.springframework.boot:spring-boot-starter-actuator")
    compile("org.springframework.boot:spring-boot-starter-data-jpa")
    compile("net.sourceforge.nekohtml:nekohtml:1.9.15")
    compile("commons-codec:commons-codec:1.9")
    compile("org.postgresql:postgresql:9.4-1201-jdbc41")
}

task wrapper(type: Wrapper) {
    gradleVersion = '2.3'
}

Solution

  • You can inject Spring applicationContext in the class used to instanciate UserAccount.

    @Autowired
    private ApplicationContext applicationContext;
    

    Then, create your UserAccount bean this way :

    UserAccount userAccount = applicationContext.getBean(UserAccount.class);
    

    This way, you can inject your required dependencies in the UserAccount class.