Immutable @ConfigurationProperties

南楼画角 提交于 2019-11-29 02:09:04

问题


Is it possible to have immutable (final) fields with Spring Boot's @ConfigurationProperties annotation? Example below

@ConfigurationProperties(prefix = "example")
public final class MyProps {

  private final String neededProperty;

  public MyProps(String neededProperty) {
    this.neededProperty = neededProperty;
  }

  public String getNeededProperty() { .. }
}

Approaches I've tried so far:

  1. Creating a @Bean of the MyProps class with two constructors
    • Providing two constructors: empty and with neededProperty argument
    • The bean is created with new MyProps()
    • Results in the field being null
  2. Using @ComponentScan and @Component to provide the MyProps bean.
    • Results in BeanInstantiationException -> NoSuchMethodException: MyProps.<init>()

The only way I have got it working is by providing getter/setter for each non-final field.


回答1:


From Spring Boot 2.2, it is at last possible to define immutable class decorated with @ConfigurationProperties.
The documentation shows an example.
You just need to declare a constructor with the fields to bind (instead of the setter way) .
So your actual code without any setter is now fine :

@ConfigurationProperties(prefix = "example")
public final class MyProps {

  private final String neededProperty;

  public MyProps(String neededProperty) {
    this.neededProperty = neededProperty;
  }

  public String getNeededProperty() { .. }
}



回答2:


I have to resolve that problem very often and I use a bit different approach, which allows me to use final variables in a class.

First of all, I keep all my configuration in a single place (class), say, called ApplicationProperties. That class has @ConfigurationProperties annotation with a specific prefix. It is also listed in @EnableConfigurationProperties annotation against configuration class (or main class).

Then I provide my ApplicationProperties as a constructor argument and perform assignment to a final field inside a constructor.

Example:

Main class:

@SpringBootApplication
@EnableConfigurationProperties(ApplicationProperties.class)
public class Application {
    public static void main(String... args) throws Exception {
        SpringApplication.run(Application.class, args);
    }
}

ApplicationProperties class

@ConfigurationProperties(prefix = "myapp")
public class ApplicationProperties {

    private String someProperty;

    // ... other properties and getters

   public String getSomeProperty() {
       return someProperty;
   }
}

And a class with final properties

@Service
public class SomeImplementation implements SomeInterface {
    private final String someProperty;

    @Autowired
    public SomeImplementation(ApplicationProperties properties) {
        this.someProperty = properties.getSomeProperty();
    }

    // ... other methods / properties 
}

I prefer this approach for many different reasons e.g. if I have to setup more properties in a constructor, my list of constructor arguments is not "huge" as I always have one argument (ApplicationProperties in my case); if there is a need to add more final properties, my constructor stays the same (only one argument) - that may reduce number of changes elsewhere etc.

I hope that will help




回答3:


In the end, if you want an immutable object you can also "hack" the setter that is

@ConfigurationProperties(prefix = "myapp")
public class ApplicationProperties {
    private String someProperty;

    // ... other properties and getters

    public String getSomeProperty() {
       return someProperty;
    }

    public String setSomeProperty(String someProperty) {
      if (someProperty == null) {
        this.someProperty = someProperty;
      }       
    }
}

Obviously if the property is not just a String, that is a mutable object, things are more complicated but that's another story.

Even better you can create a Configuration container

@ConfigurationProperties(prefix = "myapp")
public class ApplicationProperties {
   private final List<MyConfiguration> configurations  = new ArrayList<>();

   public List<MyConfiguration> getConfigurations() {
      return configurations
   }
}

where now the configuration is a clas without

public class MyConfiguration {
    private String someProperty;

    // ... other properties and getters

    public String getSomeProperty() {
       return someProperty;
    }

    public String setSomeProperty(String someProperty) {
      if (this.someProperty == null) {
        this.someProperty = someProperty;
      }       
    }
}

and application.yml as

myapp:
  configurations:
    - someProperty: one
    - someProperty: two
    - someProperty: other



回答4:


My idea is to encapsulate property groups via inner classes and expose interfaces with getters only.

Properties file:

myapp.security.token-duration=30m
myapp.security.expired-tokens-check-interval=5m

myapp.scheduler.pool-size=2

Code:

@Component
@ConfigurationProperties("myapp")
@Validated
public class ApplicationProperties
{
    private final Security security = new Security();
    private final Scheduler scheduler = new Scheduler();

    public interface SecurityProperties
    {
        Duration getTokenDuration();
        Duration getExpiredTokensCheckInterval();
    }

    public interface SchedulerProperties
    {
        int getPoolSize();
    }

    static private class Security implements SecurityProperties
    {
        @DurationUnit(ChronoUnit.MINUTES)
        private Duration tokenDuration = Duration.ofMinutes(30);

        @DurationUnit(ChronoUnit.MINUTES)
        private Duration expiredTokensCheckInterval = Duration.ofMinutes(10);

        @Override
        public Duration getTokenDuration()
        {
            return tokenDuration;
        }

        @Override
        public Duration getExpiredTokensCheckInterval()
        {
            return expiredTokensCheckInterval;
        }

        public void setTokenDuration(Duration duration)
        {
            this.tokenDuration = duration;
        }

        public void setExpiredTokensCheckInterval(Duration duration)
        {
            this.expiredTokensCheckInterval = duration;
        }

        @Override
        public String toString()
        {
            final StringBuffer sb = new StringBuffer("{ ");
            sb.append("tokenDuration=").append(tokenDuration);
            sb.append(", expiredTokensCheckInterval=").append(expiredTokensCheckInterval);
            sb.append(" }");
            return sb.toString();
        }
    }

    static private class Scheduler implements SchedulerProperties
    {
        @Min(1)
        @Max(5)
        private int poolSize = 1;

        @Override
        public int getPoolSize()
        {
            return poolSize;
        }

        public void setPoolSize(int poolSize)
        {
            this.poolSize = poolSize;
        }

        @Override
        public String toString()
        {
            final StringBuilder sb = new StringBuilder("{ ");
            sb.append("poolSize=").append(poolSize);
            sb.append(" }");
            return sb.toString();
        }
    }

    public SecurityProperties getSecurity()     { return security; }
    public SchedulerProperties getScheduler()   { return scheduler; }

    @Override
    public String toString()
    {
        final StringBuilder sb = new StringBuilder("{ ");
        sb.append("security=").append(security);
        sb.append(", scheduler=").append(scheduler);
        sb.append(" }");
        return sb.toString();
    }
}



回答5:


You can set the field values through @Value annotations. These can be placed directly on the fields and don't require any setters:

@Component
public final class MyProps {

  @Value("${example.neededProperty}")
  private final String neededProperty;

  public String getNeededProperty() { .. }
}

The downside of this approach is:

  • You'll need to specified the fully-qualified property name on each field.
  • Validation doesn't work (cf. this question)


来源:https://stackoverflow.com/questions/26137932/immutable-configurationproperties

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!