Unveränderliche @ConfigurationProperties

Lesezeit: 8 Minuten

Ist es möglich, mit Spring Boot unveränderliche (endgültige) Felder zu haben? @ConfigurationProperties Anmerkung? Beispiel unten

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

  private final String neededProperty;

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

  public String getNeededProperty() { .. }
}

Ansätze, die ich bisher ausprobiert habe:

  1. Ein … Erstellen @Bean des MyProps Klasse mit zwei Konstruktoren
    • Bereitstellung von zwei Konstruktoren: leer und mit neededProperty Streit
    • Die Bohne wird mit erstellt new MyProps()
    • Ergebnisse im Bereich Sein null
  2. Verwenden @ComponentScan und @Component die bereitzustellen MyProps Bohne.
    • Ergebnisse in BeanInstantiationException -> NoSuchMethodException: MyProps.<init>()

Die einzige Möglichkeit, wie ich es zum Laufen gebracht habe, besteht darin, Getter / Setter für jedes nicht endgültige Feld bereitzustellen.

  • Meines Wissens wird das, was Sie zu tun versuchen, nicht sofort funktionieren.

    – geound

    1. Oktober 2014 um 10:17 Uhr

  • Das ist traurig. Natürlich kann ich es immer mit einfachem Spring machen, indem ich Konstruktorparameter mit verwende @Value Anmerkung. Es wäre jedoch schön, wenn Spring Boot dies auch unterstützen würde.

    – RJo

    1. Oktober 2014 um 11:05 Uhr

  • Ich habe einen kleinen Blick auf den Quellcode geworfen, aber es scheint nicht trivial zu sein, so etwas wie das, was Sie fragen, zu unterstützen. Natürlich bin ich kein Experte für Spring-Interna, daher übersehe ich möglicherweise etwas Offensichtliches

    – geound

    1. Oktober 2014 um 12:22 Uhr

  • Es ist nicht genau das, wonach Sie suchen, aber dieses vorhandene Spring Boot-Problem könnte von Interesse sein: github.com/spring-projects/spring-boot/issues/1254

    – Andy Wilkinson

    1. Oktober 2014 um 12:29 Uhr

  • Die in den Kommentaren vorgeschlagene Lösung würde auch mein Problem lösen. Wenn die Setter nicht sichtbar wären, wären die Konfigurationseigenschaften nicht änderbar, ohne auf Gewalt zurückzugreifen 🙂

    – RJo

    2. Oktober 2014 um 4:45 Uhr

Benutzer-Avatar
davidxxx

Ab Spring Boot 2.2 ist es endlich möglich, eine unveränderliche Klasse zu definieren, mit der dekoriert wird @ConfigurationProperties.
Die Dokumentation zeigt ein Beispiel.
Sie müssen nur einen Konstruktor mit den zu bindenden Feldern deklarieren (anstelle des Setter-Wegs) und die hinzufügen @ConstructorBinding Anmerkung auf Klassenebene, um anzugeben, dass die Konstruktorbindung verwendet werden sollte.
Ihr tatsächlicher Code ohne Setter ist also jetzt in Ordnung:

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

  private final String neededProperty;

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

  public String getNeededProperty() { .. }
}

  • Beachten Sie, dass Sie jetzt die verwenden müssen @ConstructorBinding Anmerkung, damit dies funktioniert. Davor (RC1) musste man die verwenden @ImmutableConfigurationProperties stattdessen. Weitere Informationen darüber, warum diese Anmerkung ausgewählt wurde, finden Sie unter Ausgabe 18563.

    – g00glen00b

    23. Oktober 2019 um 8:37 Uhr


  • @g00glen00b Danke für deinen Kommentar. Ich habe mit der aktuellen Methode aktualisiert, dies zu tun.

    – davidxxx

    18. April 2020 um 10:56 Uhr

  • Es war sehr hilfreich, tolle Antwort. Vielen Dank !

    – Ravindra Ranwala

    5. April 2021 um 9:06 Uhr

Ich muss dieses Problem sehr oft lösen und verwende einen etwas anderen Ansatz, der es mir ermöglicht, es zu verwenden final Variablen in einer Klasse.

Zunächst bewahre ich meine gesamte Konfiguration an einem einzigen Ort (Klasse) auf, sagen wir, genannt ApplicationProperties. Diese Klasse hat @ConfigurationProperties Anmerkung mit einem bestimmten Präfix. Es ist auch aufgeführt in @EnableConfigurationProperties Anmerkung zur Konfigurationsklasse (oder Hauptklasse).

Dann stelle ich meine zur Verfügung ApplicationProperties als Konstruktorargument und führen Sie eine Zuweisung an a durch final Feld innerhalb eines Konstruktors.

Beispiel:

Hauptsächlich Klasse:

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

ApplicationProperties Klasse

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

    private String someProperty;

    // ... other properties and getters

   public String getSomeProperty() {
       return someProperty;
   }
}

Und eine Klasse mit endgültigen Eigenschaften

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

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

    // ... other methods / properties 
}

Ich bevorzuge diesen Ansatz aus vielen verschiedenen Gründen, z. B. wenn ich mehr Eigenschaften in einem Konstruktor einrichten muss, ist meine Liste der Konstruktorargumente nicht “riesig”, da ich immer ein Argument habe (ApplicationProperties in meinem Fall); wenn es notwendig ist, mehr hinzuzufügen final Eigenschaften bleibt mein Konstruktor gleich (nur ein Argument) – das kann die Anzahl der Änderungen an anderer Stelle usw. reduzieren.

Ich hoffe, das wird helfen

  • Das ist eine Menge Boiler Plate im Vergleich zur Verwendung von @Value

    – Peter Davis

    9. November 2016 um 19:56 Uhr

  • Das ist Java. Mehr Boilerplate bedeutet besseren Code

    – Clijsters

    6. Februar 2018 um 14:55 Uhr


  • @Clijsters Ich kann ehrlich gesagt nicht sagen, ob du scherzhaft bist, aber ich meine, es ist nicht ganz richtig, aber es ist auch nicht weit davon entfernt!

    – Dylan Watson

    12. Februar 2018 um 0:34 Uhr

  • Ja! Es sollte scherzhaft sein (aber ein Witz hat oft etwas Echtes).

    – Clijsters

    12. Februar 2018 um 7:35 Uhr

Benutzer-Avatar
Benutzer2688838

Wenn Sie am Ende ein unveränderliches Objekt wollen, können Sie auch den Setter “hacken”.

@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;
      }       
    }
}

Wenn die Eigenschaft nicht nur ein String ist, also ein veränderliches Objekt, sind die Dinge natürlich komplizierter, aber das ist eine andere Geschichte.

Noch besser können Sie einen Konfigurationscontainer erstellen

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

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

wobei jetzt die konfiguration eine klasse ohne ist

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;
      }       
    }
}

und application.yml als

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

  • Ich glaube du meintest if (this.someProperty == null) { this.someProperty = someProperty; }

    – Rumidisch

    19. Oktober 2018 um 9:09 Uhr

  • Ihr Design ist nicht unveränderlich, es ist nur gegen zweimaliges Setzen geschützt, dh. Zum Zeitpunkt A könnten die Eigenschaften einen anderen Zustand haben als zum Zeitpunkt B.

    – Patrick Favre

    31. Januar 2019 um 14:37 Uhr

  • patrickf du hast recht, tatsächlich habe ich den begriff “unveränderlich” falsch verwendet. Danke für den Kommentar.

    – Benutzer2688838

    31. Januar 2019 um 21:09 Uhr

Meine Idee ist es, Eigenschaftsgruppen über innere Klassen zu kapseln und Schnittstellen nur mit Gettern verfügbar zu machen.

Eigenschaftendatei:

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();
    }
}

Nur ein Update zur neuesten Unterstützung neuerer Spring-Boot-Versionen:

Wenn Sie eine jdk-Version >= 14 verwenden, können Sie verwenden record Typ, der mehr oder weniger das gleiche macht wie die Lombok-Version, aber ohne Lombok.

@ConfigurationProperties(prefix = "example")
public record MyProps(String neededProperty) {
}

Sie können den Datensatz auch innerhalb von verwenden MyProps record, um verschachtelte Eigenschaften zu verwalten. Ein Beispiel können Sie hier sehen.

Ein weiterer interessanter Beitrag hier, der zeigt, dass die @ConstructorBinding Eine Annotation ist sogar nicht mehr erforderlich, wenn nur ein Konstruktor deklariert wird.

Benutzer-Avatar
Radouxca

Unter Verwendung von Lombok-Anmerkungen würde der Code so aussehen:

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

  private final String neededProperty;

}

Zusätzlich, wenn Sie diese Eigenschaftsklasse direkt und nicht verwenden möchten @Configuration Klasse und @EnableConfigurationPropertiesmüssen Sie hinzufügen @ConfigurationPropertiesScan zur Hauptanwendungsklasse, die mit annotiert ist @SpringBootApplication.

Siehe zugehörige Dokumentation hier: https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-external-config-constructor-binding

Benutzer-Avatar
anjey

Verwenden Sie einen ähnlichen Ansatz wie unter https://stackoverflow.com/a/60442151/11770752

Aber statt AllArgsConstructor du kannst den … benutzen RequiredArgsConstructor.

Betrachten Sie Folgendes applications.properties

myprops.example.firstName=Peter
myprops.example.last-name=Pan
myprops.example.age=28

Notiz: Verwenden Sie Konsistenz mit Ihren Eigenschaften, ich wollte nur zeigen, dass beide korrekt waren (fistName und last-name).


Java-Klasse, die die Eigenschaften aufnimmt

@Getter
@ConstructorBinding
@RequiredArgsConstructor
@ConfigurationProperties(prefix = "myprops.example")
public class StageConfig
{
    private final String firstName;
    private final Integer lastName;
    private final Integer age;

    // ...
}


Zusätzlich müssen Sie Ihrem Build-Tool eine Abhängigkeit hinzufügen.

build.gradle

    annotationProcessor('org.springframework.boot:spring-boot-configuration-processor')

oder

pom.xml

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    <version>${spring.boot.version}</version>
</dependency>

Wenn Sie noch einen Schritt weiter gehen, um schöne und präzise Beschreibungen für Ihre Konfigurationen bereitzustellen, sollten Sie eine Datei erstellen additional-spring-configuration-metadata.json im Verzeichnis src/main/resources/META-INF.

{ "Eigenschaften": [
    {
      "name": "myprops.example.firstName",
      "type": "java.lang.String",
      "description": "First name of the product owner from this web-service."
    },
    {
      "name": "myprops.example.lastName",
      "type": "java.lang.String",
      "description": "Last name of the product owner from this web-service."
    },
    {
      "name": "myprops.example.age",
      "type": "java.lang.Integer",
      "description": "Current age of this web-service, since development started."
    }
}

(clean & compile to take effect)


At least in IntelliJ, when you hover over the properties inside application.propoerties, you get a clear despriction of your custom properties. Very useful for other developers.

This is giving me a nice and concise structure of my properties, which i am using in my service with spring.

1381060cookie-checkUnveränderliche @ConfigurationProperties

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy