Jason Winnebeck
Jason Winnebeck

Reputation: 1304

How to handle Spring beans required by library?

I am refactoring an application using Spring by moving some shared components (a webservice client) into a library. The components cannot work on their own so still need some beans from the application that uses the library. What is the best practice for this?

I have created a @Configuration class so the application only needs to @Import it to use the library, but the application also needs to supply a Jackson ObjectMapper and a Settings object containing how to contact the webservice. I autowire the ObjectMapper and Settings beans into various beans used in the library. The application uses the library by injecting the Client into its code and calling it.

This works, but I'm not sure it's the right style. In IntelliJ IDEA as I develop the library, it complains that the beans the library injects don't exist with a red underline, which is true, they don't exist. But normally when I see red all over files that cannot be resolved, that tells me maybe I'm not doing it the right way.

The library needs to be used with applications using Spring 3 and 5. What is the proper way for a library to ask for things like ObjectMapper when it's not appropriate to define its own (because the app will already have Jackson), and "proprietary" beans like the Settings object?

Upvotes: 2

Views: 2101

Answers (2)

Jason Winnebeck
Jason Winnebeck

Reputation: 1304

Using inspiration from @Kayaman and @Ralph, I decided it's not appropriate to expose Spring as a part of a library to be used directly from the application's context. I realize now it's also not appropriate because the library could define duplicate "private" beans it did not want to expose. I was overthinking it. If I wanted to use Spring, I found out I could do this:

public class Factory {
    public static Client createClient(ObjectMapper mapper, Settings settings) {
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        beanFactory.registerSingleton("mapper", mapper);
        beanFactory.registerSingleton("settings", settings);
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(beanFactory);
        ctx.registerBean(ClientConfiguration.class);
        ctx.refresh();
        return ctx.getBean(Client.class);
    }
}

Essentially, it's OK to use Spring as an implementation detail. Since the configuration I made exposed only one bean, it makes sense as a factory method. In the application I would create a method like this:

@Bean public Client makeClient(ObjectMapper mapper, Settings settings) {
    return Factory.createClient(mapper, settings);
}

Of course the Bean method would have ObjectMapper and Settings injected in from the application's context, or could be inline constructors for ObjectMapper/Settings.

Instead, what I decided was since the client had few enough beans and none were lazy, I just removed Spring annotations entirely and just built the object graph by hand in almost as much code as the spring context took. Now the library has the benefit of not requiring Spring at all at runtime, in a supposed non-Spring application.

Upvotes: 2

Ralph
Ralph

Reputation: 120851

Your question is a bit broad but hopefully I can give you a hint to the right direction.

The components cannot work on their own so still need some beans from the application that uses the library. What is the best practice for this?

  • First: This components should use an interface instead of some concrete beans.
  • Second: If you have a reusable library then this typical needs some configuration, that can not been part of the library itself because it depends on application that use that library
  • Third: because of second (and first): your library should not been based on any form of auto wiring, instead your library should been based on explicit (or default) configuration.

And this solve the problem. Use interfaces and an explicit configuration of your library in your application. As well as add an good documentation of the needed configuration to your lib.

Upvotes: 3

Related Questions