0

I'm trying to make a new X entity, which has a relation to my User entity. When someone is posting a new X entity, im making an "XForm", to validate the results, etc. And if everything is valid, in the "execute" method, i'm trying to find the user from the userRepository based on the id, from the form.

package app.form;

public class XForm {

@Autowired
private UserRepository userRepository;

private long userId;
//[.. other fields + getters and setters]

public X execute() throws Exception {
    X myX= new X();
    Optional<User> user = userRepository.findById(getUserId());
    if (!user.isPresent()) {
        throw new Exception("User not found");
    }
    myX.setUser(user.get());
    return myX;

}

And the userRepository is null. I tried to annotate it with @Component, @Service etc, but its still null. And as you can see i'm not trying to make a "new" UserRepository either. Auto wiring the repository works fine everywhere else (In the Controllers, and the Authentication handlers, etc).

Here is the controller:

public ResponseEntity<Object> testNewAction(@RequestBody @Valid XForm form,
                                         BindingResult result) {
    try {
        if (isValid(result)) {
            X myX = form.execute();
            XRepository.save(myX);
            //return success json
        }
        //return form errors json
    } catch (Exception ex) {
        //return exception json
    }
}

The base application class look like this, i made sure its scanned too (app.form):

@SpringBootApplication
@ComponentScan(basePackages = {"config", "app"})
@EntityScan(basePackages = {"app.entity"})
@EnableJpaRepositories(basePackages = {"config", "app"})
@EnableTransactionManagement
public class Application {

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

What am i doing wrong?

iron24
  • 101
  • 1
  • 4
  • 19
  • Possible duplicate of [Why is my Spring @Autowired field null?](https://stackoverflow.com/questions/19896870/why-is-my-spring-autowired-field-null) – Nicholas K Oct 28 '18 at 16:01
  • Yes, annotate the XForm owner of the repository as a Component, Bean, or Service. As written it's not under Spring's control. – duffymo Oct 28 '18 at 16:01
  • Like i said in the description, i tried to annotate the XForm with "@Service" and "@Component" and it didn't work. And i'm not using new, i'm using autowired. – iron24 Oct 28 '18 at 16:03
  • Can you show us your package structure? That `@ComponentScan` is not good. And it might be unecessary if you main class is in the base package of all other classes – Urosh T. Oct 28 '18 at 16:23
  • From a design POV, I don't think execute() belongs in XForm - traditionally those forms are just transfer objects... – moilejter Oct 28 '18 at 16:38
  • I'm using command pattern, but tried to keep it simple in the "example error". (Using the same code to reproduce the error). Thanks for the suggestion tho @moilejter – iron24 Oct 28 '18 at 16:45

3 Answers3

2

Your problem is that XForm is not created as a Spring bean, but just as a plain Java object. In such cases, you can make the class @Configurable, and Spring will help you instantiate at the time new gets called. Here's an example of how to do it: https://sichernmic.blogspot.com/2015/04/use-of-configurable-annotation.html

moilejter
  • 958
  • 8
  • 9
  • i annotated the XForm with #Configurable (org.springframework.beans.factory.annotation.Configurable) And the Application with #EnableSpringConfigured ( org.springframework.context.annotation.aspectj.EnableSpringConfigured) It's still not working. I think your answer is right tho. Am i missing something critical in the example? (Cannot use @ in the annotations) – iron24 Oct 28 '18 at 16:42
  • Did you add all the Maven dependencies for it? Spring Boot will not activate a feature if the right pieces are not in the classpath, I think ... – moilejter Oct 28 '18 at 16:45
  • i did, but when i added the plugin to the build, then it wouldn't compile. If i don't add it, it's compiling but not working. – iron24 Oct 28 '18 at 16:55
  • What error do you get? Could you post this project somewhere, so we can try tinkering with it, rather than creating a parallel one from scratch? – moilejter Oct 28 '18 at 17:32
1

Diagnosis

I have faced the exact same thing and its very frustrating problem to solve. You don't understand whats wrong. But I finally figured out what the issue was. It is not that Spring is unable to detect XForm, no! Spring will very well create a bean of XForm for you. You can check that out using the following code :

@Bean
public CommandLineRunner commandLineRunner(ApplicationContext ctx) {
    return args -> {
    System.out.println("Let's inspect the beans provided by Spring:");

    String[] beanNames = ctx.getBeanDefinitionNames();
    Arrays.sort(beanNames);
    for (String beanName : beanNames) {
      System.out.println(beanName);
    }
  };
}

Problem

The real problem lies with @RequestBody. What @RequestBody does (with the help of HttpMessageConverter) is that it tells the MVC controller to create an instance of XForm using the no-arg constructor, and then call setter methods with the values of the incoming HTTP POST request. Now since @RequestBody creates object using the no-arg constructor, the dependencies (UserRepository, in this case) are never injected and you get a null pointer for your service.

Kind of Solution

What you can try doing is, try creating another augmented constructor and call it from within the no-arg constructor like so :

public XFrom(UserService userService){
    this.userService = userService;
}

I am not sure if this will work, but its definitely worth a shot.

Pranjal Gore
  • 532
  • 5
  • 14
0

You need all of the following:

  1. Annotate XForm with @Component
  2. Annotate UserRepository in XForm with @Autowired
  3. In your Java config define the UserRepository bean along with the creation logic
  4. In your @ComponentScan mention the package of XForm (ensure the syntax is correct)

An example below:

@ComponentScan(basePackages = {
    "com.mycompany.app1",
    "com.mycompany.app2"
})

From your description, it seems you did the first 3 points. Only the last one is missing.

Saptarshi Basu
  • 8,640
  • 4
  • 39
  • 58
  • The "app" is my base package, and the XForm is in "app.form", since its just a base package it should be included, isn't it? – iron24 Oct 28 '18 at 16:31
  • @iron24 How are you launching the app? from a jar or IDE? If it is from a jar, how are you creating the jar? If the jar is being created from IDE, make sure to check the options that creates the jar with the package folder structure – Saptarshi Basu Oct 28 '18 at 16:38
  • Using IntellIJ Idea, and just casually pressing the "run" button. Used mvn clean, and package as well. – iron24 Oct 28 '18 at 16:43
  • @iron24 Please post you configuration for `UserRepository` bean – Saptarshi Basu Oct 28 '18 at 16:45
  • @Repository public interface UserRepository extends JpaRepository , QuerydslPredicateExecutor, QuerydslBinderCustomizer – iron24 Oct 28 '18 at 16:47