0

I want to initialize a collection and fill it with data at the startup of my application. Then I would like to access it everywhere in my application. I want for example that my REST API can access the shared collection that is already populated with data.

I first tried to do it with an startup class annotated with @Startup and @Singleton. When I injected my userService in there I had some problems and because of the advice in this post: @Inject and @PostConstruct not working in singleton pattern I tried to do it with an @ApplicationScoped annotation:

@Named
@ApplicationScoped
public class KwetterApp {

    @Inject
    private UserService service;

    @PostConstruct
    public void init() {
        try {
            User harry = new User("Harry", "harry@outlook.com", "New York", "http://harry.com", "Hi, I'm Harry!", UserType.REGULAR);
            User nick = new User("Nick", "nick@outlook.com", "California", "http://nick.com", "Hi, I'm Nick!", UserType.REGULAR);
            User jane = new User("Jane", "jane@outlook.com", "Texas", "http://jane.com", "Hi, I'm Jane!", UserType.REGULAR);

            Tweet tweet = new Tweet("eating...", harry);
            Tweet tweet1 = new Tweet("swimming...", harry);
            Tweet tweet2 = new Tweet("jogging...", jane);

            harry.addTweet(tweet);
            harry.addTweet(tweet1);
            jane.addTweet(tweet2);
            service.create(harry);
            service.create(nick);
            service.create(jane);
        }
        catch (Exception e){
            e.printStackTrace();
        }
    }

    public UserService getService() {
        return service;
    }
}

I inject this class in my rest service:

@RequestScoped
@Path("/user")
public class UserRest {

//    @Inject
//    private UserService userService;

@Inject
private KwetterApp kwetterApp;

//    private KwetterApp kwetterApp = KwetterApp.getInstance();
private UserService userService = kwetterApp.getService();

@GET
@Produces({"application/json"})
public List<User> get() throws UserException {

    return userService.getAll();
    }
}

When injecting this KwetterApp it leads to the following exception:

StandardWrapperValve[rest.RestApplication]: Servlet.service() for servlet rest.RestApplication threw exception
java.lang.NullPointerException
    at rest.UserRest.<init>(UserRest.java:27)
    at rest.UserRest$Proxy$_$$_WeldClientProxy.<init>(Unknown Source)

I have an empty beans.xml file with bean-discovery-mode set to 'all'. The CDI framework should recognize my KwetterApp class for injection, right? Why is it null?

Thanks in advance,

Mike

Maikkeyy
  • 1,017
  • 1
  • 11
  • 18

2 Answers2

1

Here

@Inject
private KwetterApp kwetterApp;
private UserService userService = kwetterApp.getService();

I do not think the kwetterApp field is going to be set before userService.
CDI will set that field after the object has been constructed.

An alternative, which should be used anyway, is constructor injection

@RequestScoped
@Path("/user")
public class UserRest {
  private KwetterApp kwetterApp;
  private UserService userService;

  protected UserRest() {}

  @Inject
  public UserRest(final KwetterApp kwetterApp) {
    this.kwetterApp = kwetterApp;
    this.userService = kwetterApp.getService();
  }

  @GET
  @Produces({"application/json"})
  @Override
  public List<User> get() throws UserException {
    return userService.getAll();
  }
}

A protected constructor is needed because @RequestScoped is a normal-scoped bean, and it must be proxiable, as described in the specification.

The only annotation that doesn't require an empty constructor is @Singleton (from javax.inject).

LppEdd
  • 20,274
  • 11
  • 84
  • 139
  • Thanks for your reaction. When I do that I get an "Normal scoped bean class rest.UserRest is not proxyable because it has no no-args constructor " error. Making an empty no-args constructor however leads to an stackoverflow error. – Maikkeyy Mar 13 '19 at 15:26
  • @Maikkeyy see https://stackoverflow.com/questions/46543657/cdi-object-not-proxyable-with-injected-constructor I would go with the interface route. This happens because of how proxies are created. (in Spring a no-arg constructor isn't required because of the version of CGLIB) – LppEdd Mar 13 '19 at 15:30
  • Let me know if you need an example. – LppEdd Mar 13 '19 at 15:31
  • An example never hurts :). But I don't understand why it does work with interfaces then instead of the actual classes itself? – Maikkeyy Mar 13 '19 at 15:35
  • @Maikkeyy I think that happens because the CDI implementation *implements* the interface instead of *extending* the class. *Extending* a class means having to call the super-constructor (with arguments, in this case, so CDI doesn't know what to do) – LppEdd Mar 13 '19 at 15:40
  • @Maikkeyy Strange! Anyway, if `UserService` is an `ApplicationScoped` Bean, you can just inject it in both classes. The contained data is shared. – LppEdd Mar 13 '19 at 16:03
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/189958/discussion-between-maikkeyy-and-lppedd). – Maikkeyy Mar 13 '19 at 16:04
0

If you want to initialize an object by using an injected bean, then you have to use a @PostConstruct annotated method, because injected CDI beans are only available in CDI in a @PostContruct annotated method and afterwards and not during field initialization or the constructor invocation.

Therefore that the UserService is a CDI bean already, you can just inject it into your rest service bean, because it will be the same bean used within the current active scope. KwetterApp is available within the current active scope, so UserService will be as well. Only @Dependend scoped beans behave different, whereby each bean gets its own instance provided.

Thomas Herzog
  • 506
  • 2
  • 6