Open Mingadinga opened 1 year ago
해결책 1 : 생성자 체이닝
설정하고 싶은 매개변수를 최소한으로 사용하는 생성자를 사용해서 인스턴스를 만들 수 있다.
public NutritionFacts(int servingSize, int servings)
{
//생성자 코드 대부분이 중복이 발생→ 매개변수가 적은쪽에서 많은 쪽에 생성자를 this 로 호출
this(servingSize, servings, calories:0);
/*
this.servingSize = servingSize;
this.servings = servings;
this.calories = servings;
this.calories = 0;
this.fat = 0;
this.sodium = 0;
this.carbohydrate = 0;
*/
}
public NutritionFacts(int servingSize, int servings,int calories) {
this(servingSize, servings, calories, fat:0);
/*
this.servingSize = servingSize;
this.servings = servings;
this.calories = servings;
this.calories = 0;
this.fat = 0;
this.sodium = 0;
this.carbohydrate = 0;
*/
}
public NutritionFacts(int servingSize, int servings,
int calories, int fat) {
this(servingSize, servings, calories, fat, sodium:0);
}
public NutritionFacts(int servingSize, int servings,
int calories, int fat, int sodium) {
this(servingSize, servings, calories, fat, sodium, carbohydrate: 0);
}
이런 생성자를 쓰다보면 필요없는 매개변수도 넘겨야 하는 경우가 발생하는데, 보통 0 같은 기본값을 넘긴다.
생성자를 계속 늘리는 방법의 단점 → 파라미터가 많아지면 해당 값이 어디로 들어가는지 알기 어렵고 어떤 파라미터에 값을 주고 있는지 파악하기도 어렵다 → 코드 작성, 읽기 어려움
해결책 2 : 자바빈즈
객체 생성이 간단해진다.
자바빈즈의 단점 → 최종적인 인스턴스를 만들기까지 여러번의 호출을 거쳐야 하기 때문에 자바빈이 중간에 사용되는 경우 안정적이지 않은 상태로 사용될 여지가 있다. 또한 (게터와 세터가 있어서) 불변 클래스로 만들지 못한다는 단점이 있고 쓰레드 안정성을 보장하려면 locking 이 필요하다.
해결책 3 : 빌더
생성자의 안정성과 자바빈을 사용할때 얻을 수 있었던 가독성을 모두 취할 수 있는 대안 → 빌더 패턴
빌더 패턴은 만들려는 객체를 바로 만들지 않고 클라이언트는 빌더(생성자 또는 static 팩토리)에 필수적인 매개변수를 주면서 호출해 Builder
객체를 얻는다.
필수로 받아야 하는 값들을 빌더의 생성자에 넣어준다. optional 한 값들은 빌더 객체가 제공하는 세터와 비슷한 메소드를 사용해서 부가적인 필드를 채워넣고 최종적으로 build
라는 메소드를 호출해서 만들려는 객체를 생성한다.
return 타입은 void가 아니라 builder 타입 그 자체를 리턴한다.
→ 생성자의 매개변수가 늘어나거나 immutable 하게 만들고 싶을 때 사용하는게 효과적이지만 코드를 이해하기 어렵게 만들 수 있다.
public static class Builder {
// 필수 매개변수
private final int servingSize;
private final int servings;
// 선택 매개변수 - 기본값으로 초기화한다.
private int calories = 0;
private int fat = 0;
private int sodium = 0;
private int carbohydrate = 0;
//필수로 받아야하는 것들
public Builder(int servingSize, int servings) {
this.servingSize = servingSize;
this.servings = servings;
}
//optional
public Builder calories(int val)
{ calories = val; return this; }
public Builder fat(int val)
{ fat = val; return this; }
public Builder sodium(int val)
{ sodium = val; return this; }
public Builder carbohydrate(int val)
{ carbohydrate = val; return this; }
빌더 패턴으로 파이썬이나 스칼라가 제공하는 Named Optional Parameter
를 모방할 수 있다. 빌더의 생성자나 메소드에서 유효성 확인을 할 수도 있고 여러 매개변수를 혼합해서 확인해야 하는 경우에는 build
메소드에서 호출하는 생성자에서 할 수 있다. 빌더에서 매개변수를 객체로 복사해온 다음에 확인하고 , 검증에 실패하면 IllegalArgumentException
을 던지고 에러 메시지로 어떤 매개변수가 잘못됐는지 알려줄 수 있다.
계층형 빌더
public abstract class Pizza {
public enum Topping {HAM,MUSHROOM,ONION,PEPPER,SAUSAGE}
final Set<Topping> toppings;
// 추상 빌더
// 추상 빌더가 받을 수 있는 Generic타입 : 재귀적인 타입 제한 사용(빌더 자신의 하위클래스 타입)
abstract static class Builder<T extends Builder<T>> {
EnumSet<Topping> toppings = EnumSet.noneOf(Topping.class);
public T addTopping(Topping topping) {
toppings.add(Objects.requireNonNull(topping));
// 하위클래스에 있는 빌더들은 자기 자신을 리턴할 수 있음
// 부모타입에서 하위 클래스에 있는 함수를 사용하기 위해
// self -> 하위 타입을 리턴할 수 있도록
// self -> Casting(캐스팅)이 필요없어짐 하위클래스에 있는 빌더이기 때문에
return self();
// return this;
// this(자기 자신의 타입)를 리턴하면 Builder 타입을 리턴 (하위가 아님, Pizza 하위 빌더에 있는 메소드를 쓸 수 있는게 아닌 Pizza에 있는 Builder 의 메소드만 사용이 가능)
}
abstract Pizza build();
// 하위 클래스는 이 메서드를 재정의(overriding)하여
// "this"를 반환하도록 해야 한다.
protected abstract T self();
}
Pizza(Builder<?> builder) {
toppings = builder.toppings.clone(); // 아이템 50 참조
}
}
추상 빌더를 가지고 있는 추상 클래스를 만들고 하위 클래스에서는 추상 클래스를 상속받으며 각 하위 클래스용 빌더도 추상 빌더를 상속받아 만들 수 있다.이때 추상 빌더는 재귀적인 타입 매개변수
를 사용하고 self
라는 메소드를 사용해 self-type
개념을 모방할 수 있다.
→self 를 사용하여 Builder Factory 계층 구조를 재활용할 수 있음 (형변환 줄이고, 하위 빌더 메서드 사용 가능)
목적 : 복잡한 객체를 만드는 프로세스를 독립적으로 분리할 수 있다.
Objects.requireNonNull
메서드는?매개변수가 null
인지 검사하는 유틸리티 메서드
매개변수가 null
이면 NullPointerException
을 발생시키고, 그렇지 않으면 매개변수를 그대로 반환함
null
체크 로직을 수행할 필요없어서 코드의 가독성 ⬆
추상 클래스를 사용하는 가장 일반적인 예가 인터페이스와 함께 사용하는 것.
예시 코드로 알아보는 추상 클래스, 인터페이스, 하위 클래스 관계
추상 클래스
public abstract class Animal {
protected String name;
public Animal(String name) {
this.name = name;
}
public abstract void speak();
public void eat() {
System.out.println(name + " is eating.");
}
}
인터페이스
public interface Animal {
void speak();
void eat();
}
하위 클래스
public class Dog extends Animal {
public Dog(String name) {
super(name);
}
@Override
public void speak() {
System.out.println(name + " is barking.");
}
}
@Builder
애노테이션이 빌더 패턴 적용한 라이브러리 아닐까?YES
Lombok을 사용하면 다음과 같이 쉽게 빌더 패턴을 적용할 수 있다
// 모든 변수를 초기화하는 builder 생성 시 클래스 레벨에 선언
public class User {
private String name;
private String email;
private String nickname;
private int age;
@Builder // 특정 변수만을 초기화할 때는 특정 생성자에 선언
public User(String name, String email, String nickname) {
this.name = name;
this.email = email;
this.nickname = nickname;
}
}
// 객체 생성
User user = User.builder()
.name("홍길동")
.email("aaa@gmail.com")
.nickname("홍도비")
.build();
정적 패터리와 생성자는 똑같은 제약이 있다.
“선택적 매개변수가 많을 때 적절히 대응하기 어렵다”
ex1)
class Book{
private String title;
private String publisher;
private String author;
private String translator;
private String painter;
public Book(String title, String publisher, String author, String translator, String painter){
this.title = title;
this.publisher = publisher;
this.author = author;
this.translator = translator;
this.painter = painter;
}
public Book(String title, String author, String translator, String publisher){
this(title, author, translator, null, publisher);
}
public Book(String title, String author, String publisher){
this(title, author, null, null, publisher);
}
public Book(String title, String publisher){
this(title, "미상", null, null, publisher);
}
}
위 경우에서는 author, translator는 선택적 매개변수이다.
Book book = new ("누가 내 머리에 똥쌌어?", "사계절", "베르너 홀츠바르트", null, "볼프 예를브루흐");
뭐가 작가고 그림작가고 null인 것은 무엇인지 알 수가 없다. 개수도 몇 개인지 알기 어렵다.
매개변수의 개수가 더 많아지면 클라이언트 코드를 작성하거나 읽기 어렵다.
이를 해결하기 위한 방법
자바 빈즈 패턴
ex2)
class Book{
private String title;
private String publisher;
private String author;
private String translator;
private String painter;
public void setTitle(String title){this.title = title;}
public void setPublisher(String publisher){this.publisher = publisher;}
public void setAuthor(String author){this.author = author;}
public void setTranslator(String translator){this.translator= translator;}
public void setPainter(String painter){this.painter= painter;}
}
장점 : 앞에서의 문제는 더 이상 보이지 않음
단점 : 객체가 완전히 생성되기 전까진 일관성이 무너진 상태에 놓인다.
Book book = new Book();
book.setTitle("누가 내 머리에 똥 쌌어?");
book.setPublisher("사계절");
/*여기까지만 실행된 상태는 일관성이 깨진 상태이다.*/
book.setAuthor("베르너 홀츠바르트");
book.setPainter("볼프 예를브루흐");
빌더 패턴
점층적 생성자 패턴의 안전성과 자바 빈즈 배턴의 가독성 겸비
ex3)
class Book{
private final String title;
private final String publisher;
private final String author;
private final String translator;
private final String painter;
public static class Builder{
private final String title;
private final String publisher;
private String author;
private String translator;
private String painter;
public Builder(String title, String publisher){
this.title = title;
this.publisher = publisher;
}
public Builder author(String author){
this.author= author; return this;
}
public Builder translator(String translator){
this.translator= translator; return this;
}
public Builder painter(String painter){
this.painter= painter; return this;
}
public Book build(){
return new Book(this);
}
}
public Book(Builder builder){
title = builder.title;
publisher = builder.publisher;
author = builder.author;
translator = builder.translator;
painter = builder.painter;
}
}
Book book = new Book("누가 내 머리에 똥 쌌어?", "사계절")
.author("베르너 홀츠바르트")
.painter("볼프 예를브루흐").build();
Book book = new ("누가 내 머리에 똥쌌어?", "사계절", "베르너 홀츠바르트", null, "볼프 예를브루흐");
모양 때문에 fluent API or method chaining 이라고도 한다.
잘못된 매개변수 감지(불변식 : 프로그램이 실행되는 동안 반드시 만족해야하는 규칙)를 위해 필드 검사 코드도 추가하면 좋다.
빌더 패턴은 특히 계층적으로 설계된 클래스와 쓰기 좋음
ex4) 조금은 어거지인 예시
public abstract class Book{
private final String title;
private final String publisher;
final Set<String> authors;
abstract static class Builder<T extends Builder<T>>{
private final String title;
private final String publisher;
Set<String> authors = Set.noneOf(String.class);
public T addAuthors(String author){
authors.add(Objects.requireNonNull(author));
return self();
}
public Builder(String title, String publisher){
this.title = title;
this.publisher = publisher;
}
abstract Book build();
protected abstract T self();
}
Book(Builder<?> builder){
title = builder.title;
publisher = builder.publisher;
authors = builder.authors.clone();
}
}
public class PictureBook extends Book{
private final String painter;
public static class Builder extends Book.Builder<Builder>{
private final String painter;
public Builder(String title, String publisher, String painter){
super(title, publisher);
this.painter = painter;
}
@Override public PictureBook build(){
return new PictureBook(this);
}
@Override protected Builder self() {return this; }
}
private PictureBook(Builder builder){
super(builder);
painter = builder.painter;
}
}
PictureBook book = new PictureBook.Builder("제목", "출판사", "그림작가")
.addAuthors("작가1").addAuthors("작가2").addAuthors("작가3").build();
클라이언트는 형 변환에 신경쓰지 않고도 빌더를 사용할 수 있다. 아이디, 일련번호 같은 것을 알아서 채우도록 할 수도 있다.
장점 : 위 장점 + 빌더 하나로 여러 객체를 순회할수도, 매개변수에 따라 다른 객체를 만들 수도 있다.
단점 : 빌더를 만들어야 하는데 성능에 민감한 상황에서는 문제가 될 수 있다.
외부에 있어 블로그 링크로 첨부합니다 :)
다루는 내용
빌더 객체는 정적 팩토리 메소드와 마찬가지로 인스턴스를 얻는 방법을 추상화하는 책임을 가진다. 정적 팩토리 메소드는 인스턴스를 얻는데 필요한 매개변수가 많아지면 함수 시그니처가 지저분해져서 가독성이 떨어진다.
보통 메소드에서 받는 매개변수가 많거나 여러 메소드에서 많이 사용되는 변수는 인스턴스 필드로 분리하는 것이 일반적이다. (이때 필드의 유효 범위를 객체로 확대해도 되는지 검토가 필요하다) 이런 방법을 적용하여 빌더 객체는 인스턴스 반환에 필요한 여러 정보를 필드로 가지는 자료구조이면서 동시에 해당 필드를 사용해 인스턴스를 초기화해 반환하도록 만든다. 가독성도 챙기면서 안정적으로 객체를 만들 수 있다.
빌더 패턴은 매개변수 초기화가 복잡해지면 도입을 고려해볼 수 있는 패턴이다. 매개변수 초기화가 복잡해지면 Product 객체 안에서 매개변수 초기화에 필요한 코드가 점점 많아진다. 그러면 Product 객체의 오퍼레이션과 객체의 생성 책임이 섞이게 되므로 클래스의 응집도가 떨어진다. 빌더 패턴은 객체의 행동으로부터 매개변수와 관련된 생성 책임을 별도의 객체로 분리해서 Product의 응집도를 높이는 방법이다.
정리하자면 빌더 객체는 다음과 같은 책임을 갖는다.
빌더 객체를 사용하면 정적 팩토리 메소드와 마찬가지로 인스턴스를 반환하는 방법을 구현할 수 있다. 예를 들어 다음과 같은 추가 기능을 수행할 수 있다. (대부분 매개변수 관련)
위의 코드에서 내부 클래스는 항상 static으로 선언했다. 그 이유는 내부 클래스를 static으로 선언하지 않으면 Product를 만들 때마다 내부 클래스가 생성되어 메모리 낭비일 뿐만 아니라 메모리 누수가 발생하기 때문이다.
내부 클래스를 비정적 클래스로 선언하면 외부 클래스에 대한 참조를 항상 가지고 있게 된다. javap -p 명령어로 Disassembler 결과(바이트코드를 자바코드 비슷하게 만들어줌)를 확인해보면 알 수 있다.
반면 static 내부 클래스는 외부 클래스에 대한 참조를 가지지 않는다. 사실 외부 클래스가 로딩되기 전에 static이 로딩되므로 참조를 가질 수 없다.
jc가 메모리 할당을 해제하는 시점은 객체가 더이상 사용되지 않을 때이다. 하지만 내부 클래스는 항상 외부 클래스를 참조하고 있으므로, 외부 클래스가 삭제되더라도 내부 클래스가 남아서 메모리 누수가 발생한다. 반면 static으로 선언하면 외부 클래스와는 독립적으로 생성되므로, 메모리 누수가 발생하지 않는다.
jc가 메모리 할당을 해제하는 시점은 객체가 더이상 사용되지 않을 때이다. 하지만 내부 클래스는 항상 외부 클래스를 참조하고 있으므로, 외부 클래스가 삭제되더라도 내부 클래스가 남아서 메모리 누수가 발생한다. 반면 static으로 선언하면 외부 클래스와는 독립적으로 생성되므로, 메모리 누수가 발생하지 않는다.
Builder를 인터페이스로 분리해서 객체를 반환하라는 메시지를 하나 가지게 한다. 인터페이스를 구현하는 클래스는 내부에 초기화를 위해 필요한 정보를 필드로 가지며, 객체 반환 메소드를 구현해야한다. Product의 생성자를 private으로 막을 것이라면 ConcreteBuilder를 Product 안에 정적 내부 클래스로 포함해야 한다.
이 코드는 Builder의 역할을 인터페이스로 두어서 빌더들의 구현을 바꿀 수 있다는 장점이 있다. (전략 패턴) XML 또는 Json 파일로부터 설정을 읽어오는 빌더, DB로부터 설정을 읽어오는 빌더 등을 만들 수 있다. 또 빌더의 구현을 인터페이스로 바꿀 수 있으므로 유닛 테스트용으로 단순한 빌더를 만들어 사용할 수 있다.
public interface Builder<T> {
T build();
}
public class Person {
private final String name;
private final int age;
private final String address;
private Person(String name, int age, String address) {
this.name = name;
this.age = age;
this.address = address;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public String getAddress() {
return address;
}
public static Builder<Person> builder() {
return new PersonBuilder();
}
private static class PersonBuilder implements Builder<Person> {
private String name;
private int age;
private String address;
public PersonBuilder withName(String name) {
this.name = name;
return this;
}
public PersonBuilder withAge(int age) {
this.age = age;
return this;
}
public PersonBuilder withAddress(String address) {
this.address = address;
return this;
}
@Override
public Person build() {
return new Person(name, age, address);
}
}
}
다만 주의할 것은 Builder를 대체할 때, 초기화되는 값의 집합마다 Builder를 만들어 Product를 생성하는 방법은 사용하지 않는다. Builder는 경우에 따라 많은 필드를 가지는 객체이기 때문에 Builder 자체를 여러개 생성하기보다는, Builder 자체는 Product 당 하나만 만들어두고 설정값을 추상화해서 주입하는 방법이 더 낫다.
Product 클래스 하나가 Build 클래스 하나만 사용하게 하고, 설정값은 재사용하는 패턴으로 Consumer 패턴과 Configurer 패턴이 있다.
GPT 왈 :
Customizer는 간단한 구성 변경에 유용하며, Configurer는 더 복잡한 구성 변경에 유용합니다. 또한, Configurer는 Customizer보다 높은 우선순위를 가지므로 Configurer가 먼저 적용되고 그 다음에 Customizer가 적용됩니다.
Customizer는 builder의 일부 구성 요소를 변경하는 단순한 방법을 제공합니다. 예를 들어, Customizer를 사용하여 Interceptor, MessageConverter, ErrorHandler 등을 추가하거나 제거할 수 있습니다. Customizer는 빌더에 대한 단순한 변경을 수행하므로 일반적으로 빠르게 적용할 수 있습니다.
Configurer는 builder의 전체 구성을 변경하는 더욱 강력한 방법입니다. 예를 들어, Configurer를 사용하여 빌더의 연결 시간 초과 또는 기본 요청 헤더와 같은 속성을 구성할 수 있습니다. Configurer는 빌더에 대한 복잡한 구성 변경을 수행하므로 Customizer보다 시간이 더 걸릴 수 있습니다.
적절한 예제는 스프링 부트의 자동구성 빈에서 찾을 수 있다.
Config 클래스로 인프라 스트럭처 빈을 등록할 때, 외부 설정값(application.properties 등)으로 등록될 빈의 일부 필드 값을 바꿀 수 있도록 커스터마이징 기능을 제공한다. 변경 가능한 필드를 모아둔 클래스가 Properties인데, 프로퍼티즈를 빈으로 등록해두고 application.properties와 같은 설정 파일에 적어둔 값을 바인딩하는 작업을 후처리기로 처리한다. 인프라 스트럭처 빈을 등록할 때 값이 바인딩된 Properties 빈을 @Import로 포함시켜서 외부 설정을 적용한다. (@Import는 @EnableConfigurationProperties에 메타 애노테이션으로 포함됨)
인프라 스트럭처 빈을 등록할 때 초기화해야하는 필드가 너무 많은 경우, 빌더를 사용한다. 다음은 ThreadPoolTaskExecutor 빈을 등록하기 위해 TaskExecutorBuilder을 등록하는 예이다.
@ConditionalOnClass(ThreadPoolTaskExecutor.class)
@AutoConfiguration
@EnableConfigurationProperties(TaskExecutionProperties.class)
public class TaskExecutionAutoConfiguration {
/**
* Bean name of the application {@link TaskExecutor}.
*/
public static final String APPLICATION_TASK_EXECUTOR_BEAN_NAME = "applicationTaskExecutor";
// 등록할 인프라 스트럭처 빈
@Lazy
@Bean(name = { APPLICATION_TASK_EXECUTOR_BEAN_NAME,
AsyncAnnotationBeanPostProcessor.DEFAULT_TASK_EXECUTOR_BEAN_NAME })
@ConditionalOnMissingBean(Executor.class)
public ThreadPoolTaskExecutor applicationTaskExecutor(TaskExecutorBuilder builder) {
return builder.build();
}
// 빌더를 등록하는 빈 (커스터마이저는 나중에..)
@Bean
@ConditionalOnMissingBean
public TaskExecutorBuilder taskExecutorBuilder(TaskExecutionProperties properties,
ObjectProvider<TaskExecutorCustomizer> taskExecutorCustomizers,
ObjectProvider<TaskDecorator> taskDecorator) {
TaskExecutionProperties.Pool pool = properties.getPool();
TaskExecutorBuilder builder = new TaskExecutorBuilder();
builder = builder.queueCapacity(pool.getQueueCapacity());
builder = builder.corePoolSize(pool.getCoreSize());
builder = builder.maxPoolSize(pool.getMaxSize());
builder = builder.allowCoreThreadTimeOut(pool.isAllowCoreThreadTimeout());
builder = builder.keepAlive(pool.getKeepAlive());
Shutdown shutdown = properties.getShutdown();
builder = builder.awaitTermination(shutdown.isAwaitTermination());
builder = builder.awaitTerminationPeriod(shutdown.getAwaitTerminationPeriod());
builder = builder.threadNamePrefix(properties.getThreadNamePrefix());
builder = builder.customizers(taskExecutorCustomizers.orderedStream()::iterator);
builder = builder.taskDecorator(taskDecorator.getIfUnique());
return builder;
}
}
// 빌더 클래스
public class TaskExecutorBuilder {
private final Integer queueCapacity;
private final Integer corePoolSize;
private final Integer maxPoolSize;
private final Boolean allowCoreThreadTimeOut;
private final Duration keepAlive;
private final Boolean awaitTermination;
private final Duration awaitTerminationPeriod;
private final String threadNamePrefix;
private final TaskDecorator taskDecorator;
private final Set<TaskExecutorCustomizer> customizers;
public ThreadPoolTaskExecutor build() {
return configure(new ThreadPoolTaskExecutor());
}
public <T extends ThreadPoolTaskExecutor> T build(Class<T> taskExecutorClass) {
return configure(BeanUtils.instantiateClass(taskExecutorClass));
}
}
ObjectMapper
는 Jackson 라이브러리에서 제공하는 클래스 중 하나로, JSON 데이터와 Java 객체 간의 변환을 담당하는 클래스이다.
JacksonObjectMapperConfiguration
는 Jackson 라이브러리가 클래스패스에 있는 경우에만 빈으로 등록된다. 이후에는 Jackson2ObjectMapperBuilder
클래스를 사용하여 기본적인 설정값을 지정하고, 필요에 따라 커스터마이징 할 수 있는 ObjectMapper
빈을 생성한다.
@AutoConfiguration
@ConditionalOnClass(ObjectMapper.class)
public class JacksonAutoConfiguration {
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(Jackson2ObjectMapperBuilder.class)
static class JacksonObjectMapperConfiguration {
@Bean
@Primary
@ConditionalOnMissingBean
ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) {
return builder.createXmlMapper(false).build();
}
}
}
Jackson2ObjectMapperBuilder를 빈으로 등록하는 컨피그이다. 빌더에는 Jackson2ObjectMapper를 구성하는 필드가 나열되어있다. 이 OpjectMapper는 다양한 설정단위를 허용하여, 우선순위에 따라 최종으로 적용할 설정값을 정한다. 따라서 매개변수로 Cusomizer List을 주입 받아서 최종 적용할 설정값을 정한다.
@AutoConfiguration
@ConditionalOnClass(ObjectMapper.class)
public class JacksonAutoConfiguration {
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(Jackson2ObjectMapperBuilder.class)
static class JacksonObjectMapperBuilderConfiguration {
@Bean
@Scope("prototype")
@ConditionalOnMissingBean
Jackson2ObjectMapperBuilder jacksonObjectMapperBuilder(ApplicationContext applicationContext,
List<Jackson2ObjectMapperBuilderCustomizer> customizers) {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
builder.applicationContext(applicationContext);
customize(builder, customizers);
return builder;
}
}
}
Customizer에 설정값들이 주입되므로, 커스터마이저를 등록하는 빈에 Properties가 달리는 것을 확인할 수 있다.
@AutoConfiguration
@ConditionalOnClass(ObjectMapper.class)
public class JacksonAutoConfiguration {
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(Jackson2ObjectMapperBuilder.class)
@EnableConfigurationProperties(JacksonProperties.class)
static class Jackson2ObjectMapperBuilderCustomizerConfiguration {}
}
@ConfigurationProperties(prefix = "spring.jackson")
public class JacksonProperties {
private String dateFormat;
private String propertyNamingStrategy;
private final Map<PropertyAccessor, JsonAutoDetect.Visibility> visibility = new EnumMap<>(PropertyAccessor.class);
private final Map<SerializationFeature, Boolean> serialization = new EnumMap<>(SerializationFeature.class);
private final Map<DeserializationFeature, Boolean> deserialization = new EnumMap<>(DeserializationFeature.class);
// ..
}
SerializationFeature.WRITE_DATES_AS_TIMESTAMPS
옵션을 활성화하려면 커스터마이저 구현 클래스를 만들어 빈으로 등록하면 된다.
@Component // 유저 구성정보
public class CustomJackson2ObjectMapperBuilderCustomizer implements Jackson2ObjectMapperBuilderCustomizer {
@Override
public void customize(Jackson2ObjectMapperBuilder builder) {
builder.featuresToEnable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
}
}
RestTemplate
은 HTTP 요청을 수행하기 위한 클라이언트이다. RestTemplateBuilder
는 불변 객체인 RestTemplate
을 구성하기 위한 빌더 패턴의 구현체이다.
@AutoConfiguration(after = HttpMessageConvertersAutoConfiguration.class)
@ConditionalOnClass(RestTemplate.class)
@Conditional(NotReactiveWebApplicationCondition.class)
public class RestTemplateAutoConfiguration {
@Bean
@Lazy
@ConditionalOnMissingBean
public RestTemplateBuilderConfigurer restTemplateBuilderConfigurer(
ObjectProvider<HttpMessageConverters> messageConverters,
ObjectProvider<RestTemplateCustomizer> restTemplateCustomizers,
ObjectProvider<RestTemplateRequestCustomizer<?>> restTemplateRequestCustomizers) {
RestTemplateBuilderConfigurer configurer = new RestTemplateBuilderConfigurer();
configurer.setHttpMessageConverters(messageConverters.getIfUnique());
configurer.setRestTemplateCustomizers(restTemplateCustomizers.orderedStream().collect(Collectors.toList()));
configurer.setRestTemplateRequestCustomizers(
restTemplateRequestCustomizers.orderedStream().collect(Collectors.toList()));
return configurer;
}
@Bean
@Lazy
@ConditionalOnMissingBean
public RestTemplateBuilder restTemplateBuilder(RestTemplateBuilderConfigurer restTemplateBuilderConfigurer) {
RestTemplateBuilder builder = new RestTemplateBuilder();
return restTemplateBuilderConfigurer.configure(builder);
}
이때 커스텀하게 설정하여 RestTemplate을 구성하려면 RestTemplateBuilderConfigurer을 커스텀하게 만들어 RestTemplateBuilder의 프로퍼티를 수정해서 RestTemplate을 커스텀하게 만들 수 있다.
// RestTemplate 커스톰 빈 등록
@Configuration
public class MyConfiguration {
@Bean
public RestTemplate restTemplate() {
// 커스터마이징된 RestTemplate을 생성합니다.
RestTemplate restTemplate = new RestTemplate();
// 커스터마이징 작업을 수행합니다.
// ...
return restTemplate;
}
}
// RestTemplateBuilder 커스톰 빈 등록
@Configuration
public class MyConfiguration {
@Bean
public RestTemplateBuilder restTemplateBuilder() {
return new RestTemplateBuilder();
}
}