简体   繁体   中英

Global injectors with Guice

I'm creating a library and to help with ergonomics, I'm passing the injector created at application startup to different components in the library so that users can do getInstance() from within certain contexts without having to pre-plan where to stick their @Inject annotations.

Here's an example of the API design:

public static void main(String[] args) {
  // Main injector is created here behind the scenes. 
  ApexApplication app = new ApexApplication()

  app.get("/users/:id", context -> {
    // Users can do this instead of @Inject UserDAO dao
    UserDAO dao = context.getInstance(UserDao.class)
    User user = dao.findUserById(context.param("id"))
    //... 
  })
  app.start();
}

Here are links to the key implementation details for clarity:

  • #1 : Here is where I create the initial (and only) injector
  • #2 : I then pass it along to another library component as part of its constructor
  • #3 : The other component then delegates calls to getInstance() to the original injector

I know that the best practice wrt Guice injectors is to create one and only one throughout the application, use it to inject all dependencies, then throw it away; However, given what I am trying to achieve, would this approach be recommended or is there another pattern I should look into?

I do not think that it is a good idea to send the context with injector to every other class in order to have the access to the injector.

There is another approach, actually pretty similar to the idea of MyModuleHelper of @Jameson. The DIContainer does static initialization of the injector and provides the public static API getInjector(). This way you can have the injector, which is created only once and it is available to whatever class without boilerplate code:

public final class DIContainer {
     private static final Injector injector;
     static {
         injector = Guice.createInjector(new AppModule());
     } 
     public static Injector getInjector() {
         return injector;
     }
     private DIContainer() {
     }
}

Usage example:

ServiceProvider service = DIContainer.getInjector().getInstance(ServiceProvider.class);

May be this post is useful as well.

I took your advice @asch but changed it up a bit to allow the user to set his own configuration module.

public class DependencyManager {
    private static Injector injector;
    private static Logger logger = LoggerFactory.getLogger(DependencyManager.class);

    public static Injector initializeWith(ApexConfiguration configuration) {
        if(injector == null) {
            logger.debug("Initializing injector")
            injector = Guice.createInjector(configuration);
        }
        return getInjector();
    }

    public static Injector getInjector() {
        if(injector == null) {
            logger.error("Application hasn't been configured yet.");
            return null;
        }
        return injector;
    }

    private DependencyManager() {}
}

I'm not entirely certain about it's thread safety, but throughout the execution of all my tests, the logging message Initializing injector displays only once and I never get a NullPointerException when calling getInjector() .

I believe this is because of how the library is structured where the DependencyManager must and is automatically initialized before any other application operations take place.

Any comments/suggestions to improve this are welcomed.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM