programing

불변 @Configuration Properties

lastmemo 2023. 2. 20. 23:53
반응형

불변 @Configuration Properties

Spring Boot에서 불변(최종) 필드를 사용할 수 있습니까?@ConfigurationProperties주석?다음 예

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

  private final String neededProperty;

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

  public String getNeededProperty() { .. }
}

지금까지 해 본 접근법:

  1. 작성@BeanMyProps두 개의 컨스트럭터가 있는 클래스
    • 2개의 컨스트럭터 제공: 빈 컨스트럭터와neededProperty논쟁
    • 콩은 다음과 같이 생성됩니다.new MyProps()
    • 결과는 다음과 같습니다.null
  2. 사용.@ComponentScan그리고.@Component제공하다MyProps콩.
    • 결과:BeanInstantiationException->NoSuchMethodException: MyProps.<init>()

최종이 아닌 각 필드에 getter/setter를 제공하는 방법밖에 없습니다.

Spring Boot 2.2부터는 최종적으로 다음 명령어로 장식된 불변의 클래스를 정의할 수 있습니다.@ConfigurationProperties.
문서에는 예가 나와 있습니다.
(세터 방식이 아닌) 바인딩할 필드를 사용하여 생성자를 선언하고 생성자 바인딩을 사용해야 함을 나타내는 주석을 클래스 수준에서 추가하면 됩니다.
따라서 세터 없이 실제 코드를 사용할 수 있습니다.

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

  private final String neededProperty;

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

  public String getNeededProperty() { .. }
}

저는 이 문제를 자주 해결해야 합니다.그리고 저는 조금 다른 접근방식을 사용합니다.그러면final클래스 내의 변수입니다.

우선, 모든 설정을 1개의 장소(클래스)에 보관합니다.ApplicationProperties그 반에는@ConfigurationProperties특정 접두사를 가진 주석.에도 기재되어 있습니다.@EnableConfigurationProperties구성 클래스(또는 기본 클래스)에 대한 주석입니다.

그리고 나서 나는 나의ApplicationProperties컨스트럭터 인수로서, 그리고 에의 할당을 실행한다.final컨스트럭터 내부의 필드.

예:

메인 클래스:

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

ApplicationProperties 클래스

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

    private String someProperty;

    // ... other properties and getters

   public String getSomeProperty() {
       return someProperty;
   }
}

그리고 최종 속성을 가진 클래스는

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

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

    // ... other methods / properties 
}

저는 여러 가지 이유로 이 방법을 선호합니다. 예를 들어 컨스트럭터에서 더 많은 속성을 설정해야 하는 경우, 컨스트럭터 인수 목록은 항상 하나의 인수를 가지고 있기 때문에 "거대"가 아닙니다.ApplicationProperties내 경우); 추가가 필요한 경우finalproperties, my constructor는 같은 상태(인수 1개만)입니다.이것에 의해서, 다른 장소의 변경의 수를 줄일 수 있습니다.

그게 도움이 됐으면 좋겠다.

https://stackoverflow.com/a/60442151/11770752의 접근방식과 유사한 접근방식을 사용한다.

하지만 대신AllArgsConstructor를 사용할 수 있습니다.RequiredArgsConstructor.

다음을 고려하다applications.properties

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

주의: 속성과 일관성을 유지하십시오.는 단지 양쪽이 모두 맞았음을 보여주고 싶었습니다.fistName그리고.last-name).


Java 클래스에서 속성을 픽업

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

    // ...
}


또한 빌드 도구에 종속성을 추가해야 합니다.

build.gradle

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

또는

pom.xml

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

상세하고 한 더 파일 .additional-spring-configuration-metadata.json 「」에서src/main/resources/META-INF.

{
  "properties": [
    {
      "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."
    }
}

(깨끗하게 컴파일하여 유효하게 합니다)


에서는, 「IntelliJ」의 를 대면,application.propoerties커스텀 속성을 명확하게 확인할 수 있습니다.하다

이것은 내가 봄과 함께 봉사할 때 사용하고 있는, 내 소유물의 멋지고 간결한 구조를 내게 주고 있다.

최종적으로 불변의 오브젝트를 원하는 경우 다음과 같은 세터를 "해킹"할 수도 있습니다.

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

분명히 그 속성이 단순한 스트링(String)이 아니라 가변 객체라면 상황은 더 복잡하지만 그것은 다른 이야기다.

구성 컨테이너를 생성할 수 있습니다.

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

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

여기서 Configuration은 clas 입니다.

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

및 application.yml as

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

내 생각에는 내부 클래스를 통해 속성 그룹을 캡슐화하고 getters와의 인터페이스만 노출하는 것입니다.

속성 파일:

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

myapp.scheduler.pool-size=2

코드:

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

최신 Spring-Boot 버전에 대한 최신 지원 업데이트입니다.

버전 jdk > = 14를 사용할 수 .record롬복 버전과 거의 같지만 롬복이 없는 유형입니다.

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

, 도 사용하실 수 .MyPropsrecord를 클릭하여 중첩된 속성을 관리합니다.여기서 예를 볼 수 있습니다.

여기 또 다른 흥미로운 게시물이 있습니다.@ConstructorBinding단 한 개의 생성자만 선언된 경우에도 주석은 더 이상 필요하지 않습니다.

Lombok 주석을 사용하면 코드는 다음과 같습니다.

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

  private final String neededProperty;

}

이 를 사용하지 @Configuration 및 " " " 。@EnableConfigurationProperties , , , , , , , , , , , , , , , , , , , .@ConfigurationPropertiesScan주 응용 프로그램클래스에 주석을 붙입니다.@SpringBootApplication.

다음 URL에서 관련 문서를 참조하십시오.https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-external-config-constructor-binding

은 하다를 통해 할 수 .@Value.이것들은 필드에 직접 배치할 수 있으며 세터가 필요하지 않습니다.

@Component
public final class MyProps {

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

  public String getNeededProperty() { .. }
}

이 접근방식의 단점은 다음과 같습니다.

  • 각 필드에 정규화된 속성 이름을 지정해야 합니다.
  • 유효성 검사가 작동하지 않습니다( 질문 참조).

언급URL : https://stackoverflow.com/questions/26137932/immutable-configurationproperties

반응형