Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to make Spring @Import or @Configuration parametrized?

Tags:

I've created a lot of common small bean-definition containers (@Configuration) which I use to rapidly develop applications with Spring Boot like:

@Import({
   FreemarkerViewResolver.class, // registers freemarker that auto appends <#escape etc.
   ConfigurationFromPropertiesFile.class, // loads conf/configuration.properties
   UtfContentTypeResponse.class, // sets proper Content-language and Content-type
   LocaleResolverWithLanguageSwitchController // Locale resolver + switch controller
 );
 class MySpringBootApp ...

For example, one of such @Configurations can set up session storage for locale cookie with web controller to switch to selected language etc.

They are very fun to work with and reuse, but it would be really great to make it parametrized, which could allow lot more reusege. I mean something like:

Pseudo code:

@Imports( imports = {
  @FreemarkerViewResolver( escapeHtml = true, autoIncludeSpringMacros = true),
  @ConfigurationFromProperties( path = "conf/configuration.properties" ),
  @ContentTypeResponse( encoding = "UTF-8" ),
  @LocaleResolver( switchLocaleUrl = "/locale/{loc}", defaultLocale = "en"
})

So, I basically mean "configurable @Configurations". What would be the best way to make the configuration that way?

Maybe something more like this (again, pseudo code):

@Configuration
public class MyAppConfiguration {

    @Configuration
    public FreemarkerConfiguration freemarkerConfiguration() {
       return FreemarkerConfigurationBuilder.withEscpeAutoAppend();
    }

    @Configuration
    public ConfigurationFromPropertiesFile conf() {
       return ConfigurationFromPropertiesFile.fromPath("...");
    }

    @Configuration
    public LocaleResolverConfigurator loc() {
       return LocaleResolverConfigurator.trackedInCookie().withDefaultLocale("en").withSwitchUrl("/switchlocale/{loc}");
    }
like image 873
Piotr Müller Avatar asked Sep 30 '15 21:09

Piotr Müller


2 Answers

Let me quote Spring Boot Reference Guide - Externalized Configuration:

"Spring Boot allows you to externalize your configuration so you can work with the same application code in different environments."

In my opinion the customization is not done at import time via annotation parameters like in your 2nd pseudo code block, instead the customization happens at run time e.g. in the configuration classes. Let me adapt your 3rd code block (only one function):

@Configuration
public class MyAppConfiguration {

    @Autowired
    private Environment env;

    // Provide a default implementation for FreeMarkerConfigurer only
    // if the user of our config doesn't define her own configurer.
    @Bean
    @ConditionalOnMissingBean(FreeMarkerConfigurer.class)
    public FreeMarkerConfigurer freemarkerConfig() {
        FreeMarkerConfigurer result = new FreeMarkerConfigurer();
        result.setTemplateLoaderPath("/WEB-INF/views/");
        return result;
    }

    ...

    @Bean
    public LocaleResolverConfigurator loc() {
        String defaultLocale = env.getProperty("my.app.config.defaultlocale", "en");
        String switchLocale = env.getProperty("my.app.config.switchlocale", "/switchlocale/{loc}");

        return LocaleResolverConfigurator.trackedInCookie().withDefaultLocale(defaultLocale).withSwitchUrl(switchLocale);
    }

For LocaleResolverConfigurator the configuration is read from the environment, meaningful default values are defined. It is easy to change the default value(s) by providing a different value for a config parameter in any of the supported ways (documented in the first link) - via command line or a yaml file. The advantage over annotation parameters is that you can change the behavior at run time instead of compile time.

You could also inject the config parameters (if you prefer to have them as instance variable) or use a lot of other conditions, e.g. @ConditionalOnMissingBean, @ConditionalOnClass, @ConditionalOnExpression and so on. For example with @ConditionalOnClass you could check if a particular class is on your class path and provide a setting for the library identified by this class. With @ConditionalOnMissingClass you could provide an alternative implementation. In the example above I used ConditionalOnMissingBean to provide a default implementation for the FreeMarkerConfigurer. This implementation is only used when no FreeMarkerConfigurer bean is available thus can be overridden easily.

Take a look at the starters provided by Spring Boot or the community. A good read is also this blog entry. I learned a lot from spring-boot-starter-batch-web, they had an article series in a German Java magazine, but parts are also online, see Boot your own infrastructure – Extending Spring Boot in five steps (MUST READ) and especially the paragraph "Make your starter configurable by using properties".

like image 50
ChrLipp Avatar answered Sep 23 '22 21:09

ChrLipp


Though I like the idea of having imports be parameterized, I think that as it stands now using @Import and @Configuration not a good fit.

I can think of two ways to use dynamic configurations, that don't rely on PropertySource style configuration.

  1. Create a custom @ImportConfig annotation and annotation processor that accepts configuration properties that are hard-coded into the generated source files.
  2. Use a BeanFactoryPostProcessor or BeanPostProcessor to add or manipulate your included beans respectively.

Neither is particularly simple IMO, but since it looks like you have a particular way of working. So it could be worth the time invested.

like image 2
Kafkaesque Avatar answered Sep 22 '22 21:09

Kafkaesque