2

I am facing an issue with my custom spring boot starter and a spring boot app consumer that uses as a dependency. I have in both an application.yml but it seems that the configuration I am looking for it is only pressent if it is defined in the consumer.

My config in the starter is like this:

@Getter
@Setter
@Configuration
@ConfigurationProperties(prefix = "security")
public class StarterSecurityConfig {
    private boolean jwtEnabled;
    private String[] unsecuredPaths;
    private String[] securedPaths;
}

And I have this bean defined in the AutoConfiguration class:

@Bean
public StarterSecurityConfig starterSecurityConfig() {
    return new StarterSecurityConfig();
}

It is perfectly retrieved by the consumer which has this application.yml and another variables:

    security:
  jwt-enabled: true
  secured-paths:
    - /user/**
  unsecured-paths:
    - /**

But if I remove that from the consumer and I put it in the application.yml of the starter, the starter beans does not have these properties when creating them.

Maybe am I missing something?

mannuk
  • 1,259
  • 5
  • 21
  • 43
  • We use Spring Cloud Common (bootstrap context) for this use-case, if you do not care that your starter brings an additional dependency: https://spring.io/projects/spring-cloud-commons Actually we enable a few profiles by default enabling our configs, so the teams do not have to care about e.g. oauth settings. – Andreas Dec 08 '21 at 22:36

3 Answers3

2

If I understood properly your issue, I have faced such problem just last week ... I was inspecting this issue and I have some findings (they are not supported by official documentation): if you add dependency and want to use its resources, you have a situation when both application.yml files have the same location - classpath:application.yml, and or they cannot be loaded together, or one of them is overridden by other. In any case, in my application, it did not work.

The straight and simple solution if you just need to load configuration from dependent config file - rename it and load in a possible way (manual loading from YAML, property source's initializer, etc.)

But if this config file should be used anywhere, we can load properties manually in the context. In a dependency (consumer in your case) create another configuration file, e.g. consumer-application.yml and next bean in @configuration class:

@Bean
public static PropertySourcesPlaceholderConfigurer properties() {
    var propertySourcesPlaceholderConfigurer = new PropertySourcesPlaceholderConfigurer();
    var yamlPropertiesFactoryBean = new YamlPropertiesFactoryBean();
    yamlPropertiesFactoryBean.setResources(new ClassPathResource("consumer-application.yaml"));
    propertySourcesPlaceholderConfigurer.setProperties(yamlPropertiesFactoryBean.getObject());
    return propertySourcesPlaceholderConfigurer;
}

And you can use properties from YAML-file in both applications with @Value.

But the simplest way - to use properties configs. In that case, you can just set @PropertySource("classpath:consumer-application.properties") in consumer and @PropertySource(value = {"classpath:application.properties", "classpath:consumer-application.properties"}) In my case both variants work correctly.

Dmitry Ionash
  • 763
  • 5
  • 11
  • thanks for commenting Dmitry. The issue with this is that I need to do the reverse. Load the application.yml when the consumer has one. My idea was to do the reverse as you. set as resource somethink like application-starter.yml and let the consumer to have the application.yml but it seems not to work :/ – mannuk Nov 28 '19 at 21:47
0

You can try initializing the member variables on the starter itself. If consumer wants to override the values they can do it with they're application configuration.

@Getter
@Setter
@Configuration
@ConfigurationProperties(prefix = "security")
public class StarterSecurityConfig {
    private boolean jwtEnabled = true;
    private String[] unsecuredPaths = { "/user/**" };
    private String[] securedPaths = { "/**" };
}

Fews more ideas:

I would make jwtEnabled as false and would remove the @Configuration and @ConfigurationProperties from the above Class and create an SecurityAutoConfiguration Class with other beans.

@Configuration
public class SecurityAutoConfiguration{

   @Bean
   @ConfigurationProperties(prefix = "security")
   public StarterSecurityConfig starterSecurityConfig(){
     return new StarterSecurityConfig();
   }

   @Bean
   @ConditionalOnProperty(value="security.jwtEnabled", havingValue = "true")
   public JwtService jwtService(StarterSecurityConfig starterSecurityConfig) {
     return new JwtService(starterSecurityConfig);
   }   

}

the consumers will be able to enable or disable the security-starter with their application configuration using security.jwtEnabled flag.

Sam
  • 143
  • 1
  • 8
  • Hi Sam, I have the conditionalOnProperty in the SecurityAutoConfiguration already and works ok. My issue is related to the default application.yml of the starter. If there is another application.yml for the consumer, the starter take into account the consumer one there is no overriding. And I need some spring defaults in the starter like spring: jpa: hibernate: ddl-auto: validate. I've tried to do also the approach of Dmitry but it is not valid to me in the way that I don't want to specify the yaml's of the consumers in the starter – mannuk Nov 28 '19 at 21:44
  • So if I understand correctly, you dont want the consumer to override the values? – Sam Nov 29 '19 at 02:59
  • yes but the consumer is not only overriding the values. if there is an application.yml in both you remove all the keys present in the starter. It is not only overriding it is like the starter it is not taken into account – mannuk Nov 29 '19 at 11:39
0

We have 2 options to set properties defined inside the custom starter to make them available in the application(that uses the starter).

  1. Put all the properties in any custom property file(ex.- custom.properties) and put it in src/main/resources folder. Now use @PropertySource as a convenient mechanism for adding property sources to the environment. This @PropertySource("classpath:custom.properties") has to put on your auto configuration.
    How it works:- When you add your custom starter in the spring boot application then at the time of startup, your auto configuration will get configured in the spring application context and since we are using @PropertySource("classpath:custom.properties") on auto configuration so properties defined inside the custom.properties will also be available in application context. If you want to override some of the properties, define those properties in application.properties|yml in the application itself. Example:-
    custom.properties

welcome.message=Hello Akshay

    @AutoConfiguration
    @PropertySource("classpath:custom.properties")
    public class MyAutoConfiguration{
      // spring beans 
    }

                                                                               
  1. We know, If both .properties|yml in your custom starter and application that included custom starter are named as application.properties|yml will cause your properties set in custom starter not working since both properties have same name and in same path. Though we cannot have the same properties with same name and same path But we can have both application.yml and application.properties at same time in same project. Read here- Does spring boot support using both properties and yml files at the same time?
    Solution:- Put all the properties in application.properties|yml file inside src/main/resources folder. This way you do not have to use @PropertySource. But there is only one thing you keep in your mind while using this option (a) If your application has resource file name as application.yml then In starter, you should have application.properties --> In this case you cannot override the properties via application.yml of application because first application.yml will be loaded, later application.properties will be loaded. (b) If your application has resource file name as application.properties then In starter, you should have application.yml --> In this case you can override the properties via application.properties of application because as per the above statement, application.properties will be loaded after application.yml.