5

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'
}
halfer
  • 19,824
  • 17
  • 99
  • 186
sparkyspider
  • 13,195
  • 10
  • 89
  • 133
  • Have you followed the instructions in http://docs.spring.io/spring/docs/4.2.0.RC1/spring-framework-reference/htmlsingle/#aop-aj-ltw to properly enable load time weaving when you run your application? – dunni Jun 17 '15 at 12:03
  • @dunni I have in the documentation for 4.1.6.RELEASE. – sparkyspider Jun 17 '15 at 12:09

2 Answers2

6

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.

Baptiste
  • 449
  • 4
  • 7
  • Thank you for your answer, and you are right! This being said, I'd like to continue using the `new` operator as there are thousands of instances where I'll be instantiating these object and I'd rather do it in an intuitive fashion. I'm particularly looking for an AOP way of doing it. – sparkyspider Jun 17 '15 at 14:08
1

From your configuration, I am assuming that you are somehow expecting Spring to manage AOP for you. However since you are looking to @Autowired on a non managed bean you will have to do the weaving yourself either through load time weaving or compile team weaving. Spring will only support method level aspects by default.

Because Load time weaving involves the use of javaagent as explained in 9.8.4(not always practical in a production scenario) I have gone ahead and used compile time weaving. Following code and config works for me.

Boot Config

@SpringBootApplication
@EnableSpringConfigured
public class App {
    public static void main(String[] args) {
        System.out.println("Hello World!");

        ApplicationContext ctx = SpringApplication.run(App.class, args);
        Account account = new Account();
        account.testConfigurable();
    }
}

Account.java

@Configurable(autowire = Autowire.BY_TYPE, dependencyCheck = true)
public class Account {

    @Autowired
    private SpringService service;

    public void testConfigurable() {
        System.out.println(service.returnHello());
    }
}

SpringService.java

@Service
public class SpringService {

    public String returnHello() {
        return "Hello";
    }

}

Ugly pom.xml

<plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>aspectj-maven-plugin</artifactId>
                <version>1.7</version>
                <configuration>
                    <showWeaveInfo>true</showWeaveInfo>
                    <source>1.8</source>
                    <target>1.8</target>
                    <Xlint>ignore</Xlint>
                    <complianceLevel>1.8</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>1.8.5</version>
                    </dependency>
                    <dependency>
                        <groupId>org.aspectj</groupId>
                        <artifactId>aspectjtools</artifactId>
                        <version>1.8.5</version>
                    </dependency>
                </dependencies>
            </plugin>

Following are the links I refered to.

  1. 9.8.1 in spring doc
  2. Maven Config from here.

Since I am no expert on AOP, I am not sure of the knock on effect of configuring AOP the above way on ordinary aspect. A discussion here. If load time weaving is an option for you, you should go ahead and use this as discussed in the answer.

Community
  • 1
  • 1
ArunM
  • 2,274
  • 3
  • 25
  • 47
  • I still get a NPE on `passwordHash = securityService.hashPassword(password);` Can you please elaborate. – sparkyspider Jun 18 '15 at 12:29
  • Since you are looking to use the new operator, please ignore my earlier answer. Infact I rewrote the answer and managed to get the dependency injection working when using new operator. – ArunM Jun 19 '15 at 21:11
  • I can see your gradle script using aspectj plugin . Is that for compile time weaving ? In case the answer seems to be explaining too much .. I just wanted to write down how I reached the answer for better clarity .. – ArunM Jun 19 '15 at 21:34