1. IO
    • 11.1. 캐싱(Caching)
      • 11.1.1. 지원되는 캐시 프로바이더(Supported Cache Providers)
        • 제네릭(Generic)
        • JCache (JSR-107)
        • 헤이즐캐스트(Hazelcast)
        • 인피니스판(Infinispan)
        • 카우치베이스(Couchbase)
        • 레디스(Redis)
        • 카페인(Caffeine)
        • 캐시2k(Cache2k)
        • 심플(Simple)
        • 없음(None)
    • 11.2. 헤이즐캐스트(Hazelcast)
    • 11.3. 쿼츠 스케줄러(Quartz Scheduler)
    • 11.4. 이메일 전송(Sending Email)
    • 11.5. 유효성 검사(Validation)
    • 11.6. REST 서비스 호출(Calling REST Services)
      • 11.6.1. 레스트템플릿(RestTemplate)
        • 커스텀 레스트템플릿(RestTemplate Customization)
        • 레스트템플릿의 SSL 지원(RestTemplate SSL Support)
      • 11.6.2. 웹클라이언트(WebClient)
        • 웹클라이언트 런타임(WebClient Runtime)
        • 커스텀 웹클라이언트(WebClient Customization)
        • 웹클라이언트의 SSL 지원(WebClient SSL Support)
    • 11.7. 웹 서비스(Web Services)
      • 11.7.1. 웹서비스템플릿을 사용하여 웹 서비스 호출(Calling Web Services with WebServiceTemplate)
    • 11.8. JTA와 분산 트랜잭션(Distributed Transactions With JTA)
      • 11.8.1. 자카르타 EE 관리 트랜잭션 매니저 사용(Using a Jakarta EE Managed Transaction Manager)
      • 11.8.2. XA 및 Non-XA JMS 연결 혼합(Mixing XA and Non-XA JMS Connections)
      • 11.8.3. 임베디드 트랜잭션 매니저 지원(Supporting an Embedded Transaction Manager)
    • 11.9. 다음에 읽을 내용(What to Read Next)

11. IO

대부분의 애플리케이션은 어느 시점에서는 입력 및 출력 문제를 처리해야 한다. 스프링 부트는 IO 기능이 필요할 때 도움이 되는 다양한 기술과의 통합 및 유틸리티를 제공한다. 이 장에서는 캐싱 및 유효성 검사와 같은 표준 IO 기능뿐만 아니라 예약 및 분산 트랜잭션과 같은 고급 주제도 다훈다. 또한 원격 REST 또는 SOAP 서비스 호출 및 이메일 전송도 다룬다.

11.1. 캐싱(Caching)

스프링 프레임워크는 애플리케이션에 캐싱을 지원한다. 기본적으로 추상화는 메서드에 캐싱을 적용하여 캐시에서 사용 가능한 정보를 기반으로 실행 횟수를 줄인다. 캐싱 로직는 호출자에게 어떠한 간섭도 없이 투명하게 적용된다. 스프링 부트는 @EnableCaching 어노테이션을 사용하여 캐싱 지원 활성화 후 캐시 인프라를 자동 구성한다.

자세한 내용은 스프링 프레임워크 레퍼런스 관련 장을 확인하자.

간단히 말해, 서비스 작업에 캐싱을 추가하려면 다음 예제와 같이 해당 메서드에 관련 어노테이션을 추가하자.

자바

import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Component;

@Component
public class MyMathService {
    @Cacheable("piDecimals")
    public int computePiDecimal(int precision) {
        ... 
    }
}

코틀린

import org.springframework.cache.annotation.Cacheable
import org.springframework.stereotype.Component
@Component
class MyMathService {
    @Cacheable("piDecimals")
    fun computePiDecimal(precision: Int): Int {
        ... 
    }
}

이 예제에서는 잠재적으로 비용이 많이 드는 작업에 캐싱을 사용하는 방법을 보여준다. 컴퓨트파이데시말(ComputePiDecimal)을 호출하기 전에 추상화는 i 인수와 일치하는 piDecimals 캐시에서 아이템을 찾는다. 아이템이 발견되면 캐시의 콘텐츠가 호출자에게 즉시 반환되고 메서드는 호출되지 않는다. 그렇지 않으면 메서드가 호출되고 값을 반환하기 전에 캐시가 업데이트된다.

표준 JSR-107(JCache) 어노테이션(예: @CacheResult)을 투명하게 사용할 수도 있다. 그러나 스프링 캐시와 JCache 어노테이션을 혼합하여 사용하지 않는 것이 좋다.

특정 캐시 라이브러리를 추가하지 않으면, 스프링 부트는 메모리에서 컨커런트 맵(concurrent maps)을 사용하는 간단한 프로바이더(provider)를 자동 구성한다. 캐시가 필요한 경우(예: 이전 예의 piDecimals) 이 프로바이더가 이를 생성한다. 간단한 프로바이더는 프로덕션 용도로 실제로 권장되지는 않지만 시작하고 기능을 이해하는 데 좋다. 사용할 캐시 프로바이더를 결정한 경우, 해당 문서를 읽고 애플리케이션에서 사용하는 캐시를 구성하는 방법을 알아보자. 거의 모든 프로바이더는 애플리케이션에서 사용하는 모든 캐시를 명시적으로 구성하도록 요구한다. 일부는 spring.cache.cache-names 프로퍼티로 정의된 기본 캐시를 커스텀하는 방법을 제공한다.

캐시에서 데이터를 투명하게 업데이트하거나 제거하는 것도 가능하다.

11.1.1. 지원되는 캐시 프로바이더(Supported Cache Providers)

캐시 추상화는 실제 저장소를 제공하지 않으며 org.springframework.cache.Cacheorg.springframework.cache.CacheManager 인터페이스에 의해 구체화된 추상화에 의존한다.

캐시매니저(CacheManager) 타입의 빈 또는 캐시리졸버(CacheResolver)(캐싱컨피규어러(CachingConfigurer) 참고)명을 정의하지 않은 경우 스프링 부트는 다음 프로바이더를 지정된 순서대로 감지하려고 시도한다.

  1. 제네릭(Generic)
  2. JCache (JSR-107) (EhCache 3, Hazelcast, Infinispan, and others)
  3. 헤이즐캐스트(Hazelcast)
  4. 인피니스판(Infinispan)
  5. 카우치베이스(Couchbase)
  6. 레디스(Redis)
  7. 카페인(Caffeine)
  8. 캐시2k(Cache2k)
  9. 심플(Simple)

또한, 아파치 지오드(Apache Geode)용 스프링 부트는 아파치 지오드를 캐시 프로바이더로 사용하기 위한 자동 구성을 제공한다.

spring.cache.type 프로퍼티를 설정하여 특정 캐시 프로바이더를 강제하는 것도 가능하다. 특정 환경(예: 테스트)에서 캐싱을 완전히 비활성화해야 하는 경우 이 프로퍼티를 사용하자.

spring-boot-starter-cache “스타터(Starter)”를 사용하여 기본 캐싱 의존성을 빠르게 추가하자. 스타터는 스프링 컨텍스트 지원을 제공한다. 의존성을 수동으로 추가하는 경우 JCache 또는 카페인(Caffeine) 지원을 사용하려면 spring-context-support를 포함해야 한다.

캐시매니저(CacheManager)가 스프링 부트에 의해 자동 구성되는 경우 캐시매니저커스터마이저(CacheManagerCustomizer) 인터페이스를 구현하는 빈을 노출하여 완전히 초기화되기 전에 해당 구성을 추가로 조정할 수 있다. 다음 예제에서는 null 값이 기본 맵에 전달되어서는 안 된다는 플래그를 설정한다.

자바

import org.springframework.boot.autoconfigure.cache.CacheManagerCustomizer;
import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration(proxyBeanMethods = false)
public class MyCacheManagerConfiguration {
    @Bean
    public CacheManagerCustomizer<ConcurrentMapCacheManager> cacheManagerCustomizer() {
        return (cacheManager) -> cacheManager.setAllowNullValues(false);
    }
}

코틀린

import org.springframework.boot.autoconfigure.cache.CacheManagerCustomizer
import org.springframework.cache.concurrent.ConcurrentMapCacheManager
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration

@Configuration(proxyBeanMethods = false)
class MyCacheManagerConfiguration {
    @Bean
    fun cacheManagerCustomizer(): CacheManagerCustomizer<ConcurrentMapCacheManager> {
        return CacheManagerCustomizer { cacheManager ->
            cacheManager.isAllowNullValues = false
        }    
    }
}

앞의 예제에서, 자동 구성된 컨커런트맵캐시매니저(ConcurrentMapCacheManager)를 기대한다. 그렇지 않은 경우(자신의 구성을 제공했거나 다른 캐시 프로바이더가 자동으로 구성된 경우) 커스터마이저가 전혀 호출되지 않는다. 원하는 만큼 커스터마이저를 가질 수 있으며 @Order 또는 Ordered를 사용하여 순서를 정할수도 있다.

제네릭(Generic)

컨텍스트가 최소한 하나의 org.springframework.cache.Cache 빈을 정의하는 경우 제네릭 캐싱이 사용된다. 해당 타입의 모든 빈을 래핑하는 캐시매니저(CacheManager)가 생성된다.

JCache (JSR-107)

JCache는 클래스패스 있는 javax.cache.spi.CachingProvider를 통해 부트스트랩되며(즉, JSR-107 호환 캐싱 라이브러리가 클래스패스에 존재함) J캐시캐시매니저(JCacheCacheManager)는 spring-boot-starter-cache에 의해 제공된다. 다양한 호환 라이브러리를 사용할 수 있으며 스프링 부트는 Ehcache 3, 헤이즐캐스트(Hazelcast) 및 인피니스판(Infinispan)에 대한 의존성 관리를 제공한다. 다른 호환 라이브러리도 추가할 수 있다.

둘 이상의 프로바이더가 존재할 수 있으며, 이 경우 프로바이더를 명시적으로 지정해야 한다. JSR-107 표준이 구성 파일의 위치를 ​​정의하는 표준화된 방법을 적용하지 않더라도 스프링 부트는 다음 예제와 같이 구현 세부 사항으로 캐시 설정을 수용하기 위해 최선을 다한다.

프로퍼티스(Properties)

# 둘 이상의 프로바이더가 있는 경우에만 필요하다.
spring.cache.jcache.provider=com.example.MyCachingProvider
spring.cache.jcache.config=classpath:example.xml

Yaml

# 둘 이상의 프로바이더가 있는 경우에만 필요하다.
spring:
  cache:
    jcache:
      provider: "com.example.MyCachingProvider"
      config: "classpath:example.xml"

캐시 라이브러리가 기본 구현과 JSR-107 지원을 모두 제공하는 경우 스프링 부트는 JSR-107 지원을 선호하므로 다른 JSR-107 구현으로 전환해도 동일한 기능을 사용할 수 있다.

스프링 부트 헤이즐캐스트를 일반적으로 지원합니다. 단일 해이즐캐스트인스턴스(HazelcastInstance)를 사용할 수 있는 경우 spring.cache.jcache.config 프로퍼티를 지정하지 않는 한 캐시매니저(CacheManager)에도 자동 재사용된다.

기본 javax.cache.cacheManager를 커스텀하는 방법에는 두 가지가 있다.

  • spring.cache.cache-names 프로퍼티를 설정하여 시작 시 캐시를 생성할 수 있다. 커스텀 javax.cache.configuration.Configuration 빈이 정의된 경우 이를 커스텀하는 데 사용된다.
  • org.springframework.boot.autoconfigure.cache.JCacheManagerCustomizer 빈은 전체 커스텀를 위해 캐시매니저(CacheManager)의 레퍼런스로 호출된다.

표준 javax.cache.CacheManager 빈이 정의되면 추상화가 기대하는 org.springframework.cache.CacheManager 구현에 자동으로 래핑된다. 더 이상 커스텀이 적용되지 않는다.

헤이즐캐스트(Hazelcast)

스프링 부트는 헤이즐캐스트(Hazelcast)를 지원한다. 헤이즐캐스트인스턴스(HazelcastInstance)가 자동 구성되고, com.hazelcast:hazelcast-spring이 클래스패스에 있는 경우 자동으로 캐시매니저(CacheManager)에 래핑된다.

헤이즐캐스트(Hazelcast)는 J캐시(JCache) 호환 또는 스프링 캐시매니저(CacheManager) 호환 캐시로 사용할 수 있다. spring.cache.typehazelcast로 설정하면 스프링 부트는 캐시매니저(CacheManager) 기반 구현을 사용한다. 헤이즐캐스트(Hazelcast)를 J캐시(JCache) 호환 캐시로 사용하려면 spring.cache.typejcache로 설정하자. J캐시(JCache) 호환 캐시 프로바이더가 여러 개 있고 헤이즐캐스트(Hazelcast)를 강제로 사용하려는 경우 J캐시(JCache) 프로바이더를 명시적으로 설정해야 한다.

인피니스판(Infinispan)

인피니스판(Infinispan)에는 기본 구성 파일 위치가 없으므로 명시적으로 지정해야 한다. 그렇지 않으면 기본 부트스트랩이 사용된다.

프로퍼티스(Properties)

spring.cache.infinispan.config=infinispan.xml

Yaml

spring:
  cache:
    infinispan:
      config: "infinispan.xml"

spring.cache.cache-names 프로퍼티를 설정하여 시작 시 캐시를 생성할 수 있다. 커스텀 컨피규레이션빌더(ConfigurationBuilder) 빈이 정의된 경우 캐시를 커스텀하는 데 사용된다.

스프링 부트의 자카르타 EE 9 기준과 호환되려면 인피니스판의 -jakarta 모듈을 사용해야 한다. -jakarta 변형이 있는 모든 모듈의 경우 표준 모듈 대신 변형을 사용해야 한다. 예를 들어 infinispan-core-jakartainfinispan-commons-jakarta는 각각 infinispan-coreinfinispan-commons 대신 사용해야 한다.

카우치베이스(Couchbase)

스프링 데이터 카우치베이스를 사용할 수 있고 카우치베이스가 구성된 경우 카우치베이스캐시매니저(CouchbaseCacheManager)가 자동 구성된다. spring.cache.cache-names 프로퍼티를 설정하여 시작 시 추가 캐시를 생성할 수 있으며, spring.cache.couchbase.* 프로퍼티스를 사용하여 캐시 기본값을 구성할 수 있다. 예를 들어 다음 구성은 항목 만료 시간이 10분인 캐시1 및 캐시2 캐시를 생성한다.

프로퍼티스(Properties)

spring.cache.cache-names=cache1,cache2
spring.cache.couchbase.expiration=10m

Yaml

spring:
  cache:
    cache-names: "cache1,cache2"
    couchbase:
      expiration: "10m"

구성에 대한 추가 제어가 필요한 경우 카우치베이스캐시매니저빌더커스터마이저(CouchbaseCacheManagerBuilderCustomizer) 빈 등록을 고려하자. 다음 예제에서는 캐시1 및 캐시2에 대한 특정 아이템 만료를 구성하는 커스터마이저를 보여준다.

자바

import java.time.Duration;
import org.springframework.boot.autoconfigure.cache.CouchbaseCacheManagerBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.couchbase.cache.CouchbaseCacheConfiguration;

@Configuration(proxyBeanMethods = false)
public class MyCouchbaseCacheManagerConfiguration {
    @Bean
    public CouchbaseCacheManagerBuilderCustomizer myCouchbaseCacheManagerBuilderCustomizer() {
        return (builder) -> builder
                .withCacheConfiguration(
                    "cache1", 
                    CouchbaseCacheConfiguration.defaultCacheConfig()
                    .entryExpiry(Duration.ofSeconds(10))
                )
                .withCacheConfiguration(
                    "cache2", 
                    CouchbaseCacheConfiguration.defaultCacheConfig()
                    .entryExpiry(Duration.ofMinutes(1))
                );
    }
}

코틀린

import org.springframework.boot.autoconfigure.cache.CouchbaseCacheManagerBuilderCustomizer
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.data.couchbase.cache.CouchbaseCacheConfiguration
import java.time.Duration

@Configuration(proxyBeanMethods = false)
class MyCouchbaseCacheManagerConfiguration {
    @Bean
    fun myCouchbaseCacheManagerBuilderCustomizer(): CouchbaseCacheManagerBuilderCustomizer {
        return CouchbaseCacheManagerBuilderCustomizer { builder ->
            builder
                .withCacheConfiguration(
                    "cache1", 
                    CouchbaseCacheConfiguration.defaultCacheConfig()
                    .entryExpiry(Duration.ofSeconds(10))
                )
                .withCacheConfiguration(
                    "cache2", 
                    CouchbaseCacheConfiguration.defaultCacheConfig()
                    .entryExpiry(Duration.ofMinutes(1))) 
        }
    } 
}

레디스(Redis)

레디스를 구성한 경우, 레디스캐시매니저(RedisCacheManager)가 자동으로 구성된다. spring.cache.cache-names 프로퍼티스를 설정하여 시작 시 추가 캐시를 생성할 수 있으며, spring.cache.redis.* 프로퍼티스를 사용하여, 캐시 기본값을 구성할 수 있다. 예를 들어 다음 구성은 TTL(Time to Live)이 10분인 캐시1 및 캐시2 캐시를 생성한다.

프로퍼티스(Properties)

spring.cache.cache-names=cache1,cache2
spring.cache.redis.time-to-live=10m

Yaml

spring:
  cache:
    cache-names: "cache1,cache2"
    redis:
      time-to-live: "10m"

기본적으로, 두 개의 별도 캐시가 동일한 키를 사용하는 경우 레디스는 겹치는 키가 없고 잘못된 값을 반환할 수 없도록 키 접두사를 추가한다. 자체 레디스캐시매니저(RedisCacheManager)를 생성하는 경우 이 설정을 활성화된 상태로 유지하는 것이 좋다.

자신만의 레디스캐시컨피규레이션(RedisCacheConfiguration) @Bean을 추가하여 기본 구성을 완전히 제어할 수 있다. 이는 기본 직렬화 전략을 커스텀해야 하는 경우 유용할 수 있다.

구성에 대한 추가 제어가 필요한 경우 레디스캐시매니저빌더커스터마이저(RedisCacheManagerBuilderCustomizer) 빈 등록을 고려하자. 다음 예제에서는 캐시1 및 캐시2에 대한 특정 TTL(Time to Live)을 구성하는 커스터마이저를 보여준다.

자바

import java.time.Duration;
import org.springframework.boot.autoconfigure.cache.RedisCacheManagerBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;

@Configuration(proxyBeanMethods = false)
public class MyRedisCacheManagerConfiguration {
    @Bean
    public RedisCacheManagerBuilderCustomizer myRedisCacheManagerBuilderCustomizer() {
        return (builder) -> 
            builder.withCacheConfiguration(
                "cache1", 
                RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofSeconds(10))
            )
            .withCacheConfiguration(
                "cache2", 
                RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofMinutes(1))
            );
    } 
}

코틀린

import org.springframework.boot.autoconfigure.cache.RedisCacheManagerBuilderCustomizer
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.data.redis.cache.RedisCacheConfiguration
import java.time.Duration

@Configuration(proxyBeanMethods = false)
class MyRedisCacheManagerConfiguration {
    @Bean
    fun myRedisCacheManagerBuilderCustomizer(): RedisCacheManagerBuilderCustomizer {
        return RedisCacheManagerBuilderCustomizer { builder ->
            builder.withCacheConfiguration(
                "cache1", 
                RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofSeconds(10))
            ).withCacheConfiguration(
                "cache2", 
                RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofMinutes(1))
            ) 
        }
    } 
}

카페인(Caffeine)

카페인은 구아바(Guava)에 대한 지원을 대체하는 구아바 캐시의 자바 8 재작성이다. 카페인(Caffeine)이 있으면 카페인캐시매니저(CaffeineCacheManager)(spring-boot-starter-cache “스타터”에서 제공)가 자동으로 구성된다. spring.cache.cache-names 프로퍼티를 설정하여 시작 시 캐시를 생성할 수 있으며 다음 중 하나를 사용하여 표시된 순서대로 커스텀할 수 있다:

  1. spring.cache.caffeine.spec에 정의된 캐시 사양
  2. com.github.benmanes.caffeine.cache.CaffeineSpec 빈이 정의됐다.
  3. com.github.benmanes.caffeine.cache.Caffeine 빈이 정의됐다.

예를 들어, 다음 구성은 최대 크기가 500이고 TTL(Time to Live)이 10분인 캐시1 및 캐시2 캐시를 생성한다.

프로퍼티스(Properties)

spring.cache.cache-names=cache1,cache2
spring.cache.caffeine.spec=maximumSize=500,expireAfterAccess=600s

Yaml

spring:
  cache:
    cache-names: "cache1,cache2"
    caffeine:
      spec: "maximumSize=500,expireAfterAccess=600s"

com.github.benmanes.caffeine.cache.CacheLoader 빈이 정의되면 자동으로 카페인캐시매니저(CaffeineCacheManager)에 연결된다. 캐시로더(CacheLoader)는 캐시매니저가 관리하는 모든 캐시와 연결되므로 CacheLoader<Object, Object>로 정의해야 한다. 자동 구성은 다른 제네릭 타입을 무시한다.

캐시2k(Cache2k)

캐시2k(Cache2k)는 인메모리 캐시다. 캐시2k 스프링 인테그레이션이 있는 경우 스프링캐시2k캐시매니저(SpringCache2kCacheManager)가 자동 구성된다.

spring.cache.cache-names 프로퍼티를 설정하여 시작 시 캐시를 생성할 수 있다. 캐시 기본값은 캐시2빌더커스터마이저(Cache2kBuilderCustomizer) 빈을 사용하여 커스텀할 수 있다. 다음 예제에서는 만료 시간이 5분인 캐시 용량을 항목 200개로 구성하는 커스터마이저를 보여준다.

자바

import java.util.concurrent.TimeUnit;
import org.springframework.boot.autoconfigure.cache.Cache2kBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration(proxyBeanMethods = false)
public class MyCache2kDefaultsConfiguration {
    @Bean
    public Cache2kBuilderCustomizer myCache2kDefaultsCustomizer() {
        return (builder) -> builder.entryCapacity(200)
                .expireAfterWrite(5, TimeUnit.MINUTES);
    } 
}

코틀린

import org.springframework.boot.autoconfigure.cache.Cache2kBuilderCustomizer
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import java.util.concurrent.TimeUnit

@Configuration(proxyBeanMethods = false)
class MyCache2kDefaultsConfiguration {
    @Bean
    fun myCache2kDefaultsCustomizer(): Cache2kBuilderCustomizer {
        return Cache2kBuilderCustomizer { builder ->
            builder.entryCapacity(200)
            .expireAfterWrite(5, TimeUnit.MINUTES)
        } 
    }
}

심플(Simple)

다른 프로바이더를 찾을 수 없으면 컨커런트해시맵(ConcurrentHashMap)을 캐시 저장소로 사용하는 간단한 구현이 구성된다. 애플리케이션에 캐싱 라이브러리가 없는 경우 이것이 기본값이다. 기본적으로 캐시는 필요에 따라 생성되지만, cache-names 프로퍼티를 설정하여 사용 가능한 캐시 목록을 제한할 수 있다. 예를 들어, 캐시1 및 캐시2 캐시만 원하는 경우 cache-names 프로퍼티를 다음과 같이 설정한다.

프로퍼티스(Properties)

spring.cache.cache-names=cache1,cache2

Yaml

spring:
  cache:
    cache-names: "cache1,cache2"

그렇게 하고 애플리케이션이 나열되지 않은 캐시를 사용하는 경우, 캐시가 필요할 때 런타임에서 실패하지만 시작 시에는 실패하지 않는다. 이는 선언되지 않은 캐시를 사용할 경우 “실제” 캐시 프로바이더가 작동하는 방식과 유사하다.

없음(None)

@EnableCaching이 구성에 있으면 적절한 캐시 구성도 필요하다. 특정 환경에서 캐싱을 완전히 비활성화해야 하는 경우, 다음 예제와 같이 캐시 타입을 없음으로 강제 실행하여 무작동 구현(no-op)을 사용한다.

프로퍼티스(Properties)

spring.cache.type=none

Yaml

spring:
  cache:
    type: "none"

11.2. 헤이즐캐스트(Hazelcast)

헤이즐캐스트(Hazelcast)가 클래스패스에 있고 적절한 구성이 발견되면 스프링 부트는 애플리케이션에 주입할 수 있는 헤이즐캐스트인스턴스(HazelcastInstance)를 자동 구성한다.

스프링 부트는 먼저 다음 구성 옵션을 확인하여 클라이언트 생성을 시도한다.

  • com.hazelcast.client.config.ClientConfig 빈이 있다.
  • spring.hazelcast.config 프로퍼티에 의해 정의된 구성 파일이다.
  • hazelcast.client.config 시스템 프로퍼티가 있다.
  • 작업 디렉토리 또는 클래스패스 루트에 있는 hazelcast-client.xml.
  • 작업 디렉토리 또는 클래스패스 루트에 있는 hazelcast-client.yaml(또는 hazelcast-client.yml).

클라이언트를 생성할 수 없는 경우 스프링 부트는 임베디드 서버를 구성하려고 시도한다. com.hazelcast.config.Config 빈을 정의하면 스프링 부트는 이를 사용한다. 구성에서 인스턴스명을 정의하는 경우 스프링 부트는 새 인스턴스를 생성하는 대신 기존 인스턴스를 찾으려고 시도한다.

다음 예제와 같은 구성을 통해 사용할 헤이즐캐스트(Hazelcast) 구성 파일을 지정할 수도 있다.

프로퍼티스(Properties)

spring.hazelcast.config=classpath:config/my-hazelcast.xml

Yaml

spring:
  hazelcast:
    config: "classpath:config/my-hazelcast.xml"

그렇지 않으면, 스프링 부트는 기본 위치(작업 디렉토리나 클래스패스 루트에 있는 hazelcast.xml 또는 동일한 위치에 있는 YAML 대응 아이템)에서 헤이즐캐스트(Hazelcast) 구성을 찾으려고 시도한다. 또한 hazelcast.config 시스템 프로퍼티스가 설정되어 있는지 확인한다. 자세한 내용은 헤이즐캐스트 문서를 참고하자.

기본적으로, 헤이즐캐스트 컴포넌트의 @SpringAware가 지원된다. @Order를 사용하여 헤이즐캐스트컨피그커스터마이저(HazelcastConfigCustomizer) 빈을 선언하여 매니지먼트컨텍스트(ManagementContext)를 오버라이드할 수 있다. 보다 높습니다.

스프링 부트에는 헤이즐캐스트(Hazelcast)에 대한 명시적인 캐싱 지원도 있다. 캐싱이 활성화되면 헤이즐캐스트인스턴스(HazelcastInstance)는 캐시매니저(CacheManager) 구현에 자동으로 래핑된다다

11.3. 쿼츠 스케줄러(Quartz Scheduler)

스프링 부트는 spring-boot-starter-quartz “스타터(Starter)”를 포함하여 쿼츠(Quartz) 스케줄러 작업에 대한 몇 가지 편의를 제공한다. 쿼츠를 사용할 수 있으면, 스케줄러팬토리빈(SchedulerFactoryBean) 추상화를 통해 스케줄러(Scheduler)가 자동 구성된다.

다음 타입의 빈은 자동으로 선택되어 스케줄러와 연결된다. • 잡디테일(JobDetail): 특정 작업을 정의한다. 잡디테일(JobDetail) 인스턴스는 잡빌더(JobBuilder) API를 사용하여 구축할 수 있다. • 캘린더(Calendar). • 트리거(Trigger): 특정 잡이 트리거되는 시기를 정의한다.

기본적으로 인메모리 잡스토어(JobStore)가 사용된다. 그러나 다음 예제와 같이 애플리케이션에서 데이터소스(DataSource) 빈을 사용할 수 있고 spring.quartz.job-store-type 프로퍼티에 따라 구성된 경우 JDBC 기반 저장소를 구성할 수 있다.

프로퍼티스(Properties)

spring.quartz.job-store-type=jdbc

Yaml

spring:
  quartz:
    job-store-type: "jdbc"

JDBC 저장소를 사용하면, 다음 예제와 같이 시작 시 스키마를 초기화할 수 있다.

프로퍼티스(Properties)

spring.quartz.jdbc.initialize-schema=always

Yaml

spring:
  quartz:
    jdbc:
      initialize-schema: "always"

기본적으로, 데이터베이스는 쿼츠(Quartz) 라이브러리와 함께 제공되는 표준 스크립트를 사용하여 감지하고 초기화된다. 이 스크립트는 기존 테이블을 삭제하고 재시작할 때마다 모든 트리거를 삭제한다. spring.quartz.jdbc.schema 프로퍼티를 설정하여 커스텀 스크립트를 제공하는 것도 가능하다.

쿼츠(Quartz)가 애플리케이션의 기본 데이터소스(DataSource)가 아닌 다른 데이터소스(DataSource)를 사용하도록 하려면 @Bean 메소드에 @QuartzDataSource로 어노테이션을 달아 데이터소스(DataSource) 빈을 선언하자. 이렇게 하면 쿼츠(Quartz) 전용 데이터소스(DataSource)가 스케줄러팩토리빈(SchedulerFactoryBean)과 스키마 초기화 모두에 사용되도록 보장된다. 마찬가지로 쿼츠가 애플리케이션의 기본 트랙젝션매니저(TransactionManager)가 아닌 트랜젝션매니저(TransactionManager)를 사용하도록 하려면 트랜젝션매니저(TransactionManager) 빈을 선언하고 @Bean 메소드에 @QuartzTransactionManager를 추가하자.

기본적으로, 구성에 의해 생성된 잡은 영구 잡 저장소(persistent job store)에서 읽은 이미 등록된 잡을 덮어쓰지 않는다. 기존 잡 정의 덮어쓰기를 활성화하려면 spring.quartz.overwrite-existing-jobs 프로퍼티를 설정하자.

쿼츠 스케줄러(Quartz Scheduler) 구성은 프로그래밍 방식의 스케줄러팩토리빈(SchedulerFactoryBean) 커스텀을 허용하는 spring.quartz 프로퍼티스와 스케줄러팩토리빈커스터마이저(SchedulerFactoryBeanCustomizer) 빈을 사용하여 커스텀할 수 있다. 고급 쿼츠 구성 프로퍼티는 spring.quartz.properties.*를 사용하여 커스텀할 수 있다.

특히, 쿼츠는 spring.quartz.properties를 통해 스케줄러를 구성하는 방법을 제공하므로 익스큐터(Executor) 빈은 스케줄러와 연결되지 않는다. 잡 익스큐터를 커스텀해야 하는 경우 스케줄러팩토리빈커스터마이저(SchedulerFactoryBeanCustomizer) 구현을 고려해보자.

잡은 데이터 맵 프로퍼티스를 주입하는 setter를 정의할 수 있다. 다음 예제와 같이 일반 비도 비슷한 방식으로 주입할 수 있다.

자바

import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.scheduling.quartz.QuartzJobBean;

public class MySampleJob extends QuartzJobBean {
    private MyService myService;
    private String name;

    // MyService빈 주입
    public void setMyService(MyService myService) {
        this.myService = myService;
    }

    // "name" 잡 데이터 프로퍼티 주입
    public void setName(String name) {
        this.name = name;
    }

    @Override
    protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
        this.myService.someMethod(context.getFireTime(), this.name);
    }
}

코틀린

import org.quartz.JobExecutionContext
import org.springframework.scheduling.quartz.QuartzJobBean

class MySampleJob : QuartzJobBean() {
    private var myService: MyService? = null
    private var name: String? = null

    // MyService빈 주입
    fun setMyService(myService: MyService?) {
        this.myService = myService
    }

    // "name" 잡 데이터 프로퍼티 주입
    fun setName(name: String?) {
        this.name = name
    }

    override fun executeInternal(context: JobExecutionContext) {
        myService!!.someMethod(context.fireTime, name)
    } 
}

11.4. 이메일 전송(Sending Email)

The Spring Framework provides an abstraction for sending email by using the JavaMailSender interface, and Spring Boot provides auto-configuration for it as well as a starter module.

See the reference documentation for a detailed explanation of how you can use JavaMailSender.

11.5. 유효성 검사(Validation)

빈 밸리데이션(Bean Validation) 1.1에서 지원하는 메서드 유효성 검사 기능은 JSR-303 구현(예: 하이버네이트(Hibernate) 유효성 검사기)이 클래스패스에 있으면 자동으로 활성화된다. 이를 통해 빈(Bean) 메소드에 해당 파라미터 및/또는 반환 값에 대한 jakarta.validation 제약 조건이 어노테이션으로 추가될 수 있다. 이러한 어노테이션이 달린 메서드가 있는 대상 클래스는 해당 메서드가 인라인 제약 조건 어노테이션을 검색할 수 있도록 타입 레벨에서 @Validated 어노테이션을 달아야 한다.

예를 들어, 다음 서비스는 첫 번째 아규먼트의 유효성 검사를 트리거하여 크기가 8에서 10 사이인지 확인한다.

자바

import jakarta.validation.constraints.Size;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;

@Service
@Validated
public class MyBean {
    public Archive findByCodeAndAuthor(@Size(min = 8, max = 10) String code, Author author) {
        return ... 
    }
}

코틀린

import jakarta.validation.constraints.Size
import org.springframework.stereotype.Service
import org.springframework.validation.annotation.Validated

@Service
@Validated
class MyBean {
    fun findByCodeAndAuthor(code: @Size(min = 8, max = 10) String?, author: Author?): Archive? {
        return null 
    }
}

애플리케이션의 메세지소스(MessageSource)는 제약 조건 메시지의 {parameters}를 확인할 때 사용된다. 이를 통해 빈(Bean) 유효성 검증 메시지에 애플리케이션의 message.properties 파일을 사용할 수 있다. 파라미터가 확인되면 빈 밸리데이션(Bean Validation)의 기본 보간기(interpolator)를 사용하여 메시지 보간(interpolation)이 완료된다.

밸리데이터팩토리(ValidatorFactory)를 빌드하는 데 사용되는 구성을 커스텀하려면 밸리데이션컨피규레이션커스터마이저(ValidationConfigurationCustomizer) 빈을 정의하자. 다중 커스텀 빈이 정의되면 @Order 어노테이션 또는 Ordered 구현체를 기반 순서로 호출한다.

11.6. REST 서비스 호출(Calling REST Services)

애플리케이션이 원격 REST 서비스를 호출하는 경우 스프링 부트는 레스트템플릿(RestTemplate) 또는 웹클라이언트(WebClient)를 사용하여 이를 매우 편리하게 만든다.

11.6.1. 레스트템플릿(RestTemplate)

애플리케이션에서 원격 REST 서비스를 호출해야 하는 경우 스프링 프레임워크의 레스트템플릿(RestTemplate) 클래스를 사용할 수 있다. 레스트템플릿(RestTemplate) 인스턴스를 사용하기 전에 커스텀해야 하는 경우가 많기 때문에 스프링 부트는 자동으로 구성된 단일 레스트템플릿(RestTemplate) 빈을 제공하지 않는다. 그러나 필요할 때 레스트템플릿(RestTemplate) 인스턴스를 생성하는 데 사용할 수 있는 레스트템플릿빌더(RestTemplateBuilder)를 자동 구성한다. 자동 구성된 레스트템플릿빌더(RestTemplateBuilder)는 합리적인 Http메세지컨버터(HttpMessageConverters)가 레스트템플릿(RestTemplate) 인스턴스에 적용되도록 보장한다.

다음 코드는 일반적인 예제를 보여준다.

자바

import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

@Service
public class MyService {
    private final RestTemplate restTemplate;
    
    public MyService(RestTemplateBuilder restTemplateBuilder) {
        this.restTemplate = restTemplateBuilder.build();
    }
    
    public Details someRestCall(String name) {
        return this.restTemplate.getForObject("/{name}/details", Details.class, name);
    } 
}

코틀린

import org.springframework.boot.web.client.RestTemplateBuilder
import org.springframework.stereotype.Service
import org.springframework.web.client.RestTemplate

@Service
class MyService(restTemplateBuilder: RestTemplateBuilder) {
    private val restTemplate: RestTemplate

    init {
        restTemplate = restTemplateBuilder.build()
    }

    fun someRestCall(name: String): Details {
        return restTemplate.getForObject("/{name}/details", Details::class.java, name)!! 
    }
}

레스트템플릿빌더(RestTemplateBuilder)에는 레스트템플릿(RestTemplate)을 빠르게 구성하는 데 사용할 수 있는 유용한 메서드가 많이 포함되어 있다. 예를 들어 BASIC 인증 지원을 추가하려면 builder.basicAuthentication("user", "password").build()를 사용할 수 있다.

커스텀 레스트템플릿(RestTemplate Customization)

커스텀을 얼마나 광범위하게 적용할지에 따라 레스트템플릿(RestTemplate) 커스텀에 대한 세 가지 주요 접근 방식이 있다.

커스텀 범위를 최대한 좁히려면 자동 구성된 레스트템플릿빌더(RestTemplateBuilder)를 삽입한 다음 필요에 따라 해당 메서드를 호출하자. 각 메서드 호출은 새로운 레스트템플릿빌더(RestTemplateBuilder) 인스턴스를 반환하므로 커스텀은 이 빌더에만 영향을 미친다.

애플리케이션 전반에 걸쳐 추가 커스텀을 수행하려면 레스트템플릿커스터마이저(RestTemplateCustomizer) 빈을 사용하자. 이러한 모든 빈은 자동 구성된 레스트템플릿빌더(RestTemplateBuilder)에 자동으로 등록되며 이를 사용하여 빌드된 모든 템플릿에 적용된다.

다음 예제에서는 192.168.0.5를 제외한 모든 호스트에 대해 프록시 사용을 구성하는 커스터마이저를 보여준다.

자바

import org.apache.hc.client5.http.classic.HttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
import org.apache.hc.client5.http.impl.routing.DefaultProxyRoutePlanner;
import org.apache.hc.client5.http.routing.HttpRoutePlanner;
import org.apache.hc.core5.http.HttpException;
import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.http.protocol.HttpContext;
import org.springframework.boot.web.client.RestTemplateCustomizer;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;

public class MyRestTemplateCustomizer implements RestTemplateCustomizer {

    @Override
    public void customize(RestTemplate restTemplate) {
        HttpRoutePlanner routePlanner = new CustomRoutePlanner(new HttpHost("proxy.example.com"));
        HttpClient httpClient = HttpClientBuilder.create().setRoutePlanner(routePlanner).build();
        restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory(httpClient));
    }
    
    static class CustomRoutePlanner extends DefaultProxyRoutePlanner {
        CustomRoutePlanner(HttpHost proxy) {
            super(proxy);
        }
    
        @Override
        protected HttpHost determineProxy(HttpHost target, HttpContext context) throws HttpException {
            if (target.getHostName().equals("192.168.0.5")) {
                return null;
            }
            
            return super.determineProxy(target, context);
        }
    } 
}

코틀린

import org.apache.hc.client5.http.classic.HttpClient
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder
import org.apache.hc.client5.http.impl.routing.DefaultProxyRoutePlanner
import org.apache.hc.client5.http.routing.HttpRoutePlanner
import org.apache.hc.core5.http.HttpException
import org.apache.hc.core5.http.HttpHost
import org.apache.hc.core5.http.protocol.HttpContext
import org.springframework.boot.web.client.RestTemplateCustomizer
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory
import org.springframework.web.client.RestTemplate

class MyRestTemplateCustomizer : RestTemplateCustomizer {
    override fun customize(restTemplate: RestTemplate) {
        val routePlanner: HttpRoutePlanner = CustomRoutePlanner(HttpHost("proxy.example.com"))
        val httpClient: HttpClient = HttpClientBuilder.create().setRoutePlanner(routePlanner).build()
        restTemplate.requestFactory = HttpComponentsClientHttpRequestFactory(httpClient)
    }

    internal class CustomRoutePlanner(proxy: HttpHost?) : DefaultProxyRoutePlanner(proxy) {
        @Throws(HttpException::class)
        public override fun determineProxy(target: HttpHost, context: HttpContext): HttpHost? {
            if (target.hostName == "192.168.0.5") {
                return null
            }
            return  super.determineProxy(target, context)
        }
    }
}

마지막으로, 자신만의 레스트템플릿빌더(RestTemplateBuilder) 빈을 정의할 수 있다. 그렇게 하면 자동 구성된 빌더가 대체된다. 자동 구성처럼 레스트템플릿커스터마이저(RestTemplateCustomizer) 빈을 커스텀 빌더에 적용하려면 레스트템플릿빌더컨피규어러(RestTemplateBuilderConfigurer)를 사용하여 구성하자. 다음 예제에서는 커스텀 커넥션 및 읽기 시간 제한도 지정된다는 점을 제외하고 스프링 부트의 자동 구성과 일치하는 레스트템플릿빌더(RestTemplateBuilder)를 노출한다.

자바

import java.time.Duration;
import org.springframework.boot.autoconfigure.web.client.RestTemplateBuilderConfigurer;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration(proxyBeanMethods = false)
public class MyRestTemplateBuilderConfiguration {
    @Bean
    public RestTemplateBuilder restTemplateBuilder(RestTemplateBuilderConfigurer configurer) {
        return configurer.configure(new RestTemplateBuilder())
            .setConnectTimeout(Duration.ofSeconds(5))
            .setReadTimeout(Duration.ofSeconds(2));
    } 
}

코틀린

import org.springframework.boot.autoconfigure.web.client.RestTemplateBuilderConfigurer
import org.springframework.boot.web.client.RestTemplateBuilder
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import java.time.Duration

@Configuration(proxyBeanMethods = false)
class MyRestTemplateBuilderConfiguration {
    @Bean
    fun restTemplateBuilder(configurer: RestTemplateBuilderConfigurer): RestTemplateBuilder {
        return configurer.configure(RestTemplateBuilder()).setConnectTimeout(Duration.ofSeconds(5))
            .setReadTimeout(Duration.ofSeconds(2))
    } 
}

가장 극단적이고 거의 사용되지 않는 옵션은 컨피규어러(configurer)를 사용하지 않고 자신만의 레스트템플릿빌더(RestTemplateBuilder) 빈을 생성하는 것이다. 자동 구성된 빌더를 교체하는 것 외에도 레스트템플릿커스터마이저(RestTemplateCustomizer) 빈을 사용할 수 없게 된다.

레스트템플릿의 SSL 지원(RestTemplate SSL Support)

레스트템플릿(RestTemplate)에 커스텀 SSL 구성이 필요한 경우 다음 예제와 같이 레스트템플릿빌더(RestTemplateBuilder)에 SSL 번들을 적용할 수 있다.

자바

import org.springframework.boot.docs.io.restclient.resttemplate.Details;
import org.springframework.boot.ssl.SslBundles;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

@Service
public class MyService {
    private final RestTemplate restTemplate;

    public MyService(RestTemplateBuilder restTemplateBuilder, SslBundles sslBundles) {
        this.restTemplate = restTemplateBuilder.setSslBundle(sslBundles.getBundle("mybundle")).build();
    }

    public Details someRestCall(String name) {
        return this.restTemplate.getForObject("/{name}/details", Details.class, name);
    } 
}

코틀린

import org.springframework.boot.docs.io.restclient.resttemplate.Details
import org.springframework.boot.ssl.SslBundles
import org.springframework.boot.web.client.RestTemplateBuilder
import org.springframework.stereotype.Service
import org.springframework.web.client.RestTemplate

@Service
class MyService(restTemplateBuilder: RestTemplateBuilder, sslBundles: SslBundles) {
    private val restTemplate: RestTemplate
    
    init {
        restTemplate = restTemplateBuilder.setSslBundle(sslBundles.getBundle("mybundle")).build()
    }

    fun someRestCall(name: String): Details {
        return restTemplate.getForObject("/{name}/details", Details::class.java, name)!! 
    }
}

11.6.2. 웹클라이언드(WebClient)

클래스패스에 스프링 웹플럭스가 있는 경우 웹클라이언트(WebClient)를 사용하여 원격 REST 서비스를 호출하도록 선택할 수도 있다. 레스트템플릿(RestTemplate)에 비해 이 클라이언트는 더 기능적인 느낌을 갖고 있으며 완벽하게 반응한다. 스프링 프레임워크의 문서의 전용 장에서 웹클라이언트(WebClient)에 대해 자세히 알아볼 수 있다.

스프링 부트는 WebClient.Builder를 생성하고 사전 구성한다. 이를 컴포넌트에 주입하고 이를 사용하여 웹클라이언트(WebClient) 인스턴스를 만드는 것이 좋다. 스프링 부트는 HTTP 리소스를 공유하고 서버와 동일한 방식으로 코덱 설정을 반영하도록 해당 빌더를 구성한다(웹플럭스 HTTP 코덱 자동 구성 참고).

다음 코드는 일반적인 예제를 보여준다.

자바

import reactor.core.publisher.Mono;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;

@Service
public class MyService {
    private final WebClient webClient;
    
    public MyService(WebClient.Builder webClientBuilder) {
        this.webClient = webClientBuilder.baseUrl("https://example.org").build();
    }
    
    public Mono<Details> someRestCall(String name) {
        return this.webClient.get().uri("/{name}/details", name).retrieve().bodyToMono(Details.class);
    }
}

코틀린

import org.springframework.stereotype.Service
import org.springframework.web.reactive.function.client.WebClient
import reactor.core.publisher.Mono

@Service
class MyService(webClientBuilder: WebClient.Builder) {
    private val webClient: WebClient
    
    init {
        webClient = webClientBuilder.baseUrl("https://example.org").build()
    }

    fun someRestCall(name: String?): Mono<Details> {
        return webClient.get().uri("/{name}/details", name).retrieve().bodyToMono(Details::class.java)
    }
}

웹클라이언트 런타임(WebClient Runtime)

스프링 부트는 애플리케이션 클래스패스에서 사용 가능한 라이브러리에 따라 웹클라이언트(WebClient)를 구동하는 데 사용할 클라이언트Http커넥터(ClientHttpConnector)를 자동 감지한다. 현재 리액터 네티(Reactor Netty), 제티 리액티브스트림(Jetty ReactiveStream) 클라이언트, 아파치 Http클라이언트(Apache HttpClient) 및 JDK의 HttpClient가 지원된다.

spring-boot-starter-webflux 스타터는 기본적으로 io.projectreactor.netty:reactor-netty에 의존하며, 이는 서버와 클라이언트 구현을 모두 제공한다. 대신 제티를 리액티브 서버로 사용하기로 선택한 경우 제티 리액티브(Jetty Reactive) HTTP 클라이언트 라이브러리인 org.eclipse.jetty:jetty-reactive-httpclient에 대한 의존성을 추가해야 한다. 서버와 클라이언트에 동일한 기술을 사용하면 클라이언트와 서버 간에 HTTP 리소스를 자동으로 공유하므로 장점이 있다.

개발자는 커스텀 리액터리소스팩토리(ReactorResourceFactory) 또는 제티리소스팩토리(JettyResourceFactory) 빈을 제공하여 제티 및 리액터 네티(Reactor Netty)에 대한 리소스 구성을 오버라이드할 수 있다. 이는 클라이언트와 서버 모두에 적용된다.

클라이언트에 대한 선택을 무시하려면 고유 클라이언트Http커넥터(ClientHttpConnector) 빈을 정의하고 클라이언트 구성을 완전히 제어할 수 있다.

스프링 프레임워크 레퍼런스 문서에서 웹클라이언트(WebClient) 구성 옵션에 대해 자세히 알아볼 수 있다.

커스텀 웹클라이언트(WebClient Customization)

커스텀을 얼마나 광범위하게 적용할지에 따라 웹클라이언트(WebClient) 커스텀에 대한 세 가지 주요 접근 방식이 있다.

커스텀 범위를 최대한 좁히려면 자동 구성된 WebClient.Builder를 주입한 다음 필요에 따라 해당 메서드를 호출하자. WebClient.Builder 인스턴스는 스테이트풀(stateful) 하다. 빌더에 대한 모든 변경 사항은 이후에 생성된 모든 클라이언트에 반영된다. 동일한 빌더로 여러 클라이언트를 생성하려는 경우 WebClient.Builder other = builder.clone();을 사용하여 빌더 복제를 고려할 수도 있다.

모든 WebClient.Builder 인스턴스를 애플리케이션 전체에 대해 추가 커스텀을 수행하려면 웹클라이언트커스터마이저(WebClientCustomizer) 빈을 선언하고 주입 시점에서 WebClient.Builder를 로컬로 변경할 수 있다.

마지막으로, 원래 API로 돌아가 WebClient.create()를 사용할 수 있다. 이 경우 자동 구성이나 웹클라이언트커스터마이저(WebClientCustomizer)가 적용되지 않는다.

웹클라이언트의 SSL 지원(WebClient SSL Support)

웹클라이언트에서 사용하는 클라이언트Http커넥터(ClientHttpConnector)에 커스텀 SSL 구성이 필요한 경우 빌더의 적용 메소드와 함께 사용할 수 있는 웹클라이언트Ssl(WebClientSsl) 인스턴스를 삽입할 수 있다.

웹클라이언트Ssl(WebClientSsl) 인터페이스는 application.properties 또는 application.yaml 파일에 정의한 모든 SSL 번들에 대한 접근을 제공한다.

다음 코드는 일반적인 예제를 보여준다.

자바

import reactor.core.publisher.Mono;
import org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientSsl;
import org.springframework.boot.docs.io.restclient.webclient.Details;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;

@Service
public class MyService {
    private final WebClient webClient;
    
    public MyService(WebClient.Builder webClientBuilder, WebClientSsl ssl) {
        this.webClient = webClientBuilder.baseUrl("https://example.org").apply(ssl.fromBundle("mybundle")).build();
    }

    public Mono<Details> someRestCall(String name) {
        return this.webClient.get().uri("/{name}/details", name).retrieve().bodyToMono(Details.class);
    }
}

코틀린

import org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientSsl
import org.springframework.boot.docs.io.restclient.webclient.Details
import org.springframework.stereotype.Service
import org.springframework.web.reactive.function.client.WebClient
import reactor.core.publisher.Mono

@Service
class MyService(webClientBuilder: WebClient.Builder, ssl: WebClientSsl) {
    private val webClient: WebClient
    init {
        webClient = webClientBuilder.baseUrl("https://example.org").apply(ssl.fromBundle("mybundle")).build()
    }
    
    fun someRestCall(name: String?): Mono<Details> {
        return webClient.get().uri("/{name}/details", name).retrieve().bodyToMono(Details::class.java)
    }
}

11.7. 웹 서비스(Web Services)

스프링 부트는 웹 서비스 자동 구성을 제공하므로 사용자는 엔드포인트를 정의하기만 하면 된다.

스프링 웹 서비스 기능은 spring-boot-starter-webservices 모듈을 통해 쉽게 접근할 수 있다.

SimpleWsdl11DefinitionSimpleXsdSchema 빈은 WSDLXSD에 대해 각각 자동으로 생성될 수 있다. 이렇게 하려면 다음 예제와 같이 location을 구성해보자.

프로퍼티스(Properties)

spring.webservices.wsdl-locations=classpath:/wsdl

Yaml

spring:
  webservices:
    wsdl-locations: "classpath:/wsdl"

11.7.1. 웹서비스템플릿을 사용하여 웹 서비스 호출(Calling Web Services with WebServiceTemplate)

애플리케이션에서 원격 웹 서비스를 호출해야 하는 경우, 웹서비스템플릿(WebServiceTemplate) 클래스를 사용할 수 있다. 웹서비스템플릿(WebServiceTemplate) 인스턴스를 사용하기 전에 커스텀해야 하는 경우가 많기 때문에, 스프링 부트는 자동으로 구성된 단일 웹서비스템플릿(WebServiceTemplate) 빈을 제공하지 않는다. 그러나 필요할 때 웹서비스템플릿(WebServiceTemplate) 인스턴스를 생성하는 데 사용할 수 있는 웹서비스템플릿빌더(WebServiceTemplateBuilder)를 자동 구성한다.

다음 코드는 일반적인 예제를 보여준다.

자바

import org.springframework.boot.webservices.client.WebServiceTemplateBuilder;
import org.springframework.stereotype.Service;
import org.springframework.ws.client.core.WebServiceTemplate;
import org.springframework.ws.soap.client.core.SoapActionCallback;

@Service
public class MyService {
    private final WebServiceTemplate webServiceTemplate;
    public MyService(WebServiceTemplateBuilder webServiceTemplateBuilder) {
        this.webServiceTemplate = webServiceTemplateBuilder.build();
    }
    
    public SomeResponse someWsCall(SomeRequest detailsReq) {
        return (SomeResponse)this.webServiceTemplate.marshalSendAndReceive(detailsReq, new SoapActionCallback("https://ws.example.com/action"));
    } 
}

코틀린

import org.springframework.boot.webservices.client.WebServiceTemplateBuilder
import org.springframework.stereotype.Service
import org.springframework.ws.client.core.WebServiceTemplate
import org.springframework.ws.soap.client.core.SoapActionCallback

@Service
class MyService(webServiceTemplateBuilder: WebServiceTemplateBuilder) {
    private val webServiceTemplate: WebServiceTemplate
    init {
        webServiceTemplate = webServiceTemplateBuilder.build()
    }

    fun someWsCall(detailsReq: SomeRequest?): SomeResponse {
        return webServiceTemplate.marshalSendAndReceive(detailsReq,SoapActionCallback("https://ws.example.com/action")) as SomeResponse
    } 
}

기본적으로, 웹서비스템플릿빌더(WebServiceTemplateBuilder)는 클래스패스에서 사용 가능한 HTTP 클라이언트 라이브러리를 사용하여 적합한 HTTP 기반 웹서비스메세지센더(WebServiceMessageSender)를 감지한다. 다음과 같이 읽기(read) 및 커넥션 제한시간(connection timeouts)를 커스텀할 수도 있다.

자바

import java.time.Duration;
import org.springframework.boot.webservices.client.HttpWebServiceMessageSenderBuilder;
import org.springframework.boot.webservices.client.WebServiceTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.ws.client.core.WebServiceTemplate;
import org.springframework.ws.transport.WebServiceMessageSender;

@Configuration(proxyBeanMethods = false)
public class MyWebServiceTemplateConfiguration {
    @Bean
    public WebServiceTemplate webServiceTemplate(WebServiceTemplateBuilder builder) {
        WebServiceMessageSender sender = new HttpWebServiceMessageSenderBuilder()
                .setConnectTimeout(Duration.ofSeconds(5))
                .setReadTimeout(Duration.ofSeconds(2))
                .build();
        return builder.messageSenders(sender).build();
    }
}

코틀린

import org.springframework.boot.webservices.client.HttpWebServiceMessageSenderBuilder
import org.springframework.boot.webservices.client.WebServiceTemplateBuilder
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.ws.client.core.WebServiceTemplate
import java.time.Duration

@Configuration(proxyBeanMethods = false)
class MyWebServiceTemplateConfiguration {
    @Bean
    fun webServiceTemplate(builder: WebServiceTemplateBuilder): WebServiceTemplate {
        val sender = HttpWebServiceMessageSenderBuilder()
            .setConnectTimeout(Duration.ofSeconds(5))
            .setReadTimeout(Duration.ofSeconds(2))
            .build()
        return builder.messageSenders(sender).build()
    }
}

11.8. JTA와 분산 트랜잭션(Distributed Transactions With JTA)

스프링 부트는 JNDI에서 검색된 트랜잭션 매니저를 사용하여 여러 XA 리소스에 걸쳐 분산된 JTA 트랜잭션을 지원한다.

JTA 환경이 감지되면 스프링의 JtaTransactionManager가 트랜잭션을 관리하는 데 사용된다. 자동 구성된 JMS, 데이터소스(DataSource) 및 JPA 빈은 XA 트랜잭션을 지원하도록 업그레이드됐다. @Transactional과 같은 표준 스프링 관용구를 사용하여 분산 트랜잭션에 참여할 수 있다. JTA 환경 내에 있고 여전히 로컬 트랜잭션을 사용하려는 경우 spring.jta.enabled 프로퍼티를 false로 설정하여 JTA 자동 구성을 비활성화할 수 있다.

11.8.1. 자카르타 EE 관리 트랜잭션 매니저 사용(Using a Jakarta EE Managed Transaction Manager)

스프링 부트 애플리케이션을 war 또는 ear 파일로 패키징하고 이를 자카르타 EE 애플리케이션 서버에 배포하는 경우, 애플리케이션 서버에 내장된 트랜잭션 매니저를 사용할 수 있다. 스프링 부트는 일반적인 JNDI 위치(java:comp/UserTransaction, java:comp/TransactionManager 등)를 확인하여 트랜잭션 매니저를 자동 구성하려고 한다. 애플리케이션 서버에서 제공하는 트랜잭션 서비스를 사용할 때 일반적으로 모든 리소스가 서버에서 관리되고 JNDI를 통해 노출되는지 확인한다. 스프링 부트는 JNDI 경로(java:/JmsXA 또는 java:/XAConnectionFactory)에서 커넥션팩토리(ConnectionFactory)를 찾아 JMS 자동 구성을 시도하며 spring.datasource.jndi-name 프로퍼티를 사용하여 데이터소스(DataSource)를 구성할 수 있다.

11.8.2. XA 및 Non-XA JMS 연결 혼합(Mixing XA and Non-XA JMS Connections)

JTA를 사용할 때 기본 JMS 커넥션팩토리(ConnectionFactory) 빈은 XA를 인식하고 분산 트랜잭션에 참여한다. @Qualifier를 사용할 필요 없이 빈에 주입할 수 있다.

자바

public MyBean(ConnectionFactory connectionFactory) {
      // ...
}

코틀린


어떤 상황에서는 non-XA 커넥션팩토리(ConnectionFactory)를 사용하여 특정 JMS 메시지를 처리해야 할 수도 있다. 예를 들어 JMS 처리 로직는 XA 제한 시간보다 오래 걸릴 수 있다.

non-XA 커넥션팩토리(ConnectionFactory)를 사용하려는 경우, nonXaJmsConnectionFactory 빈을 사용할 수 있다.

자바

public MyBean(@Qualifier("nonXaJmsConnectionFactory") ConnectionFactory connectionFactory) {
        // ... 
    }

코틀린


일관성을 위해, jmsConnectionFactory 빈은 빈 별칭(alias) xaJmsConnectionFactory를 사용하여 제공된다.

자바

public MyBean(@Qualifier("xaJmsConnectionFactory") ConnectionFactory connectionFactory) {
    // ... 
}

코틀린


11.8.3. 임베디드 트랜잭션 매니저 지원(Supporting an Embedded Transaction Manager)

XAConnectionFactoryWrapperXADataSourceWrapper 인터페이스를 사용하여 임베디드 트랜잭션 매니저를 지원할 수 있다. 인터페이스는 XAConnectionFactoryXADataSource 빈을 래핑하고 분산 트랜잭션에 투명하게 등록하는 일반 ConnectionFactoryDataSource 빈으로 노출하는 역할을 한다. DataSource 및 JMS 자동 구성은 JtaTransactionManager 빈과 애플리케이션컨텍스트(ApplicationContext) 내에 등록된 적절한 XA 래퍼 빈이 있는 경우 JTA 변형을 사용한다.

이제 스프링 부트의 코어 기능과 스프링 부트가 자동 구성을 통해 지원하는 다양한 기술을 이해했다.

다음 몇 장에서는 클라우드 플랫폼에 애플리케이션을 배포하는 방법에 대해 자세히 설명한다. 다음 장에서 컨테이너 이미지 빌드에 대해 읽거나 프로덕션에 즉시 사용 가능한 기능 절으로 건너뛸 수 있다.