Ramanujan R Ramanujan R - 19 days ago 7
Java Question

JAX-RS dependency injection

I've done projects using Spring Rest. Now we have a small rest project and planning to do with Jersey JAX-RS. I'm new to this and referred SO and other blogs to successfully implement a Rest api with dependency injection.

Have following code.

AppConfig.java

import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;

@ApplicationPath("/")
public class AppConfig extends Application {
@Override
public Set<Class<?>> getClasses() {
System.out.println("AppConfig");
final Set<Class<?>> s = new HashSet<Class<?>>();
s.add(Controller.class);
s.add(AppFeature.class);
return s;
}
}


AppBinder.java

import org.glassfish.hk2.utilities.binding.AbstractBinder;

public class AppBinder extends AbstractBinder {
@Override
protected void configure() {
System.out.println("AppBinder");
bind(ReflectionService.class).to(ReflectionService.class);
}
}


AppFeature.java

import javax.ws.rs.core.Feature;
import javax.ws.rs.core.FeatureContext;

public class AppFeature implements Feature {
@Override
public boolean configure(FeatureContext context) {
System.out.println("AppFeature");
context.register(new AppBinder());
return true;
}
}


Controller.java

@Path("/")
public class Controller {
@Inject
Service service;
public Controller(){
System.out.println("Controller created");
}
// other methods
}


Service.java

@Singleton
public class Service
public Service(){
System.out.println("Service instance created");
}
// other methods
}


I assume that each instance of Controller and Service is created on Tomcat 8 server startup and dependency injection is done. But during startup, got this on console


INFO: Registering the Jersey servlet application, named
com.sample.auto2.AppConfig, at the servlet mapping /*, with the
Application class of the same name.

AppConfig

AppConfig

Nov 15, 2016 12:22:20 PM org.glassfish.jersey.server.ApplicationHandler initialize
INFO: Initiating Jersey application, version Jersey: 2.6 2014-02-18
21:52:53...

AppFeature

AppBinder

Nov 15, 2016 12:22:21 PM
org.apache.catalina.startup.HostConfig deployDirectory


Each time, we send a request, got following in console


Service instance created

Controller created


My Questions


  1. Service,Controller constructors is being called whenever we send an
    http request; does it create instances in each request or just
    calling constructor?

  2. Why System.out in AppConfig is called twice?

  3. Is there a better way for setting up my small project, which does not have any db access and only three post endpoints?



EDIT:

As per the links provided by @Harikrishnan, added
@Singleton
for
Controller
class. Now the constructors called only once (at the very first request - Why not during server startup!!).

But why Service class constructor called on each request (earlier before adding @Singleton to Controller), even if its singleton? Also the other problems remain.

EDIT 2:

Thanks @peeskillet. So these are the results for me.


  1. This called constructors only once at very first request

    bind(ReflectionService.class).to(ReflectionService.class).in(Singleton.class);
    bind(Controller.class).to(Controller.class).in(Singleton.class);

  2. This give the error on http request

    bind(ReflectionService.class).to(ReflectionService.class).in(Immediate.class);

    java.lang.IllegalStateException: Could not find an active context for org.glassfish.hk2.api.Immediate


    But it's ok, because

  3. This called constructor on server startup and only once

    bind(new ReflectionService()).to(ReflectionService.class);
    bind(new Controller()).to(Controller.class);

  4. By this, injection done on startup, but 404 error on http request. (Thought Controller is configured in AppBinder, then why in AppConfig)

    @ApplicationPath("/")
    public class AppConfig extends ResourceConfig {
    public AppConfig() {
    register(new AppBinder());
    }
    }

  5. And this make it run, as you said!

    @ApplicationPath("/")
    public class AppConfig extends ResourceConfig {
    public AppConfig() {
    register(new AppBinder());
    register(Controller.class);
    }
    }



FINALLY THESE ARE ALL I NEEDED

public class AppBinder extends AbstractBinder {
@Override
protected void configure() {
bind(new ReflectionService()).to(ReflectionService.class);
bind(new Controller()).to(Controller.class);
}
}

@ApplicationPath("/")
public class AppConfig extends ResourceConfig {
public AppConfig() {
register(new AppBinder());
register(Controller.class);
}
}

Answer

Service,Controller constructors is being called whenever we send an http request; does it create instances in each request or just calling constructor?

Unfortunately, the @Singleton doesn't have any effect when binding using the AbstractBinder. We need to explicitly say it should be a singleton

bind(ReflectionService.class).to(ReflectionService.class).in(Singleton.class);

The default behavior is a "per lookup" scope, which means that a new instance is created every time the service is requested

(at the very first request - Why not during server startup!!)

This is how it works. There is also an ImmediateScope, which will cause it to be created on startup

 bind(ReflectionService.class).to(ReflectionService.class).in(ImmediateScope.class)

Or you can just use an instance instead of class, and it will automatically be a singleton

bind(new ReflectionService()).to(ReflectionService.class)

Service,Controller constructors is being called whenever we send an http request; does it create instances in each request or just calling constructor?

This is the default behavior. A new instance of the resource class for each request. As mentioned, if you only want once instance, then mark it as a @Singleton

Why System.out in AppConfig is called twice?

Not sure, probably just required for Jersey internal processing on bootstrap

Is there a better way for setting up my small project, which does not have any db access and only three post endpoints?

Your set up is fine, but if you are using Jersey then you should use the ResourceConfig (extension of Application) class instead of Application

@ApplicationPath("/")
public class AppConfig extends ResourceConfig {
    public AppConfig() {
        register(new AppBinder());
        register(Controller.class);
    }
}

With this, you don't need to wrap the AppBinder in the AppFeature. Jersey already knows how to process the AppBinder

Comments