728x90

첫번째 미션으로 유저 객체를 생성할 때 Builder 패턴을 이용해야 했다.

프로그래머스 스터디 뿐만 아니라 이전에 참고했던 여러 서적들(책 ‘처음 배우는 스프링 부트2’와 ‘스프링 부트와 AWS로 혼자 구현하는 웹서비스’)에서도 빌더 패턴을 사용해서 구현한다. 무엇보다도 찾아봤던 블로그, 책마다 빌더 패턴을 구현하는 모습이 서로 조금씩 달라서 빌더패턴을 코드로 이렇게 작성하는게 맞는건지 너무 헷갈렸다.

왜 빌더 패턴을 사용해야 하는 걸까? 빌더 패턴과 생성자의 차이는 무엇이고 어떤 상황에서 사용해야 하는 걸까? 코드로는 어떻게 구현할까? 이에 대해 한번 정리해 봐야겠다.


먼저 생성자란,

인스턴스가 생성될 때 호출되는 인스턴스 초기화 메소드이다. 연산자 new가 인스턴스를 생성하는 것이지 생성자가 인스턴스를 생성하는 것은 아니다.

User Sara = new User();

 

빌더패턴이란

생성자처럼 인스턴스를 초기화하고 new 대신 build() 메소드를 이용해 객체를 생성하는 방법이다. 기존 생성자의 문제점을 보완해서 객체 생성을 더 깔끔하고 유연하게 할 수 있다.

 

빌더 패턴은 왜 / 언제 사용해야할까?

일반 생성자 이용시, 인자 수가 늘어나면 코드 작성이 어려워지고 읽기 어려운 코드가 되고 만다

NutritionFacts cocaCola = new NutritionFacts(240, 8, 100, 3, 35, 27);

호출 코드만 보고서는 저 많은 인자가 도대체 무슨 값인지 알 수가 없고 특히 자료형이 같은 인자들이 많을 경우 컴파일 단계에서 걸러지지 않기 때문에 런타임시 문제 발생하기가 딱 좋다 - 실수하기 좋은 구조.

대안책으로 setter메소드를 이용한 자바빈 패턴이 있다.

// cocaCola을 영양 성분 정보를 setter메소드를 통해 초기화하는 자바빈 패턴
NutritionFacts cocaCola = new NutritionFacts();
cocaCola.setServingSize(240);
cocaCola.setServings(8);
cocaCola.setCalories(100);
cocaCola.setSodium(35);
cocaCola.setCarbohdydrate(27);

가독성은 좋아졌지만, 1회의 함수 호출로 객체 생성을 끝낼 수 없고 setter메서드로 인해 Immutable한 클래스를 만들 수가 없다. Setter 메소드 사용은 지양해야하며 변경 가능성은 최소화해야한다.

따라서 가독성도 좋고 immutable 객체로도 만들 수 있는 Builder 패턴을 사용하게 된다.

NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8)
                                            .calories(100)
                                            .sodium(35)
                                            .carbohydrate(27)
                                            .build();

각 인자가 어떤 값을 의미하는지 알기가 쉽고 setter메소드가 없으므로 변경 불가능한 객체도 만들 수 있다. 한번에 객체를 생성하므로 객체 일관성이 깨지지 않으며, build() 함수로 잘못된 값이 입력되었는지 검증하게 할 수도 있다.

Builder패턴은

  • 생성자의 인자수가 많을 때 사용하며
  • 불변성과 네임드 파라미터(Named Parameter, 매개변수 이름을 통해서 인자 값을 전달)를 가져갈 수 있어
  • 안전하고 가독성이 좋다.

 

빌더패턴을 코드로 작성해보자

public class NutritionFacts {
    private final int servingSize;
    private final int servings;
    private final int calories;
    private final int fat;
    private final int sodium;
    private final int carbohydrate;

    public static class Builder {
        // Required parameters(필수 인자)
        private final int servingSize;
        private final int servings;

        // Optional parameters - initialized to default values(선택적 인자는 기본값으로 초기화)
        private int calories      = 0;
        private int fat           = 0;
        private int carbohydrate  = 0;
        private int sodium        = 0;

        public Builder() {
        }

        public Builder(int servingSize, int servings) {
            this.servingSize = servingSize;
            this.servings    = servings;
        }

        public Builder calories(int val) {
            calories = val;
            return this;    // 이렇게 하면 . 으로 체인을 이어갈 수 있다.
        }
        public Builder fat(int val) {
            fat = val;
            return this;
        }
        public Builder carbohydrate(int val) {
            carbohydrate = val;
            return this;
        }
        public Builder sodium(int val) {
            sodium = val;
            return this;
        }
        public NutritionFacts build() {
            return new NutritionFacts(this);
        }
    }

    private NutritionFacts(Builder builder) {
        servingSize  = builder.servingSize;
        servings     = builder.servings;
        calories     = builder.calories;
        fat          = builder.fat;
        sodium       = builder.sodium;
        carbohydrate = builder.carbohydrate;
    }
}

위와 같이 하면 다음처럼 객체를 생성해 사용하면 된다.

NutritionFacts cocaCola = new NutritionFacts
    .Builder(240, 8)    // 필수값 입력
    .calories(100)
    .sodium(35)
    .carbohydrate(27)
    .build();           // build() 가 객체를 생성해 돌려준다.

이렇게 정리해보니 궁금함이 생겼다. 생성자의 단점을 보완해서 나온게 빌더패턴이라면 생성자말고 빌더패턴만 사용하는게 좋지 않나? 그런데 왜 생성자와 빌더패턴을 섞어서 사용할까? => 스터디 리더분께 물어보니 명쾌하게 의견을 주셨다. 정답은 없다고. 생성자의 매개변수가 많아지면 네임드 파라미터의 빌더 패턴 사용을 고려해보는 것처럼 그 상황과 용도에 맞게 더 fit한 생성자를 이용하는게 더 좋다고. 결국 프로그래밍에 있어서 각 방법의 장단점을 확실하게 인지하고 상황에 맞는 방법을 유연하게 적용할 수 있는 능력이 정말 중요한 것 같다.

references

Effective Java 2/E
자바의 정석
https://johngrib.github.io/wiki/builder-pattern/
https://softwareengineering.stackexchange.com/questions/380397/why-do-we-need-a-builder-class-when-implementing-a-builder-pattern

 

Why do we need a Builder class when implementing a Builder pattern?

I have seen many implementations of the Builder pattern (mainly in Java). All of them have an entity class (let's say a Person class), and a builder class PersonBuilder. The builder "stacks" a vari...

softwareengineering.stackexchange.com

 

 

https://yjna2316.github.io/study/2020/11/06/%EC%83%9D%EC%84%B1%EC%9E%90%EC%99%80-%EB%B9%8C%EB%8D%94%ED%8C%A8%ED%84%B4/

 

 

728x90

자바의 InputStream과 OutputStream은 외부와의 I/O를 위한 인터페이스를 제공한다.

이번에 다룰 주제인 InputStream은 I/O 중 I에 대한 부분을 다룬다.

 

대표적인 3가지 사용처는 콘솔과의 표준 입출력, 파일 입출력, 소켓과의 입출력이다.

 

오늘 다룰 내용은 자바 ee 어플리케이션에서의 inputstream이다.

 

자바 ee 서버에서는 HttpServletRequest가 존재하고,

이 클래스를 통해 inputstream을 가져올 수 있게 되어 있다. 

그렇기 때문에, request.getInputStream(); 과 같은 형태로 서블릿 내에서 inputstream을 받아와 사용할 수 있는 것이다.

톰캣에서는 CoyoteInputStream이란 이름으로 단일한 형태의 inputstream을 제공하고,

제우스의 경우에는 요청의 종류에 따라 WjpInputStream, Ajp13InputStream, ServletInputStreamImpl을 제공한다.

 

오늘 다룰 내용은 inputstream 사용 시에 주의할 점에 대한 내용이다.

 

다시 한번 말하지만, inputstream은 외부와 통신하기 위한 인터페이스이다.

자바는 C언어와 다르다.

OS의 민감한 부분에 직접적으로 접근하는 부분은 jdk에서 제공한 수단으로만 접근할 수 있다는 점에 유의한다.

 

inputstream은 기본적으로 socket에서 가져온 inputstream을 사용한다.

그리고, read 메서드를 호출하면, 인자에 따라 1byte를 읽거나, 정해진 양만큼 읽어오려고 시도한다.

어플리케이션에서 요구한 양보다 버퍼에 있는 양이 더 적으면, 그냥 적은걸 던져준다.

-1을 리턴하는 경우는 연결이 끊겼을 때이고, 단순히 읽을 것이 없는 경우엔 0을 던저주는 것이 아니라, blocking 된다.

 

따라서 만약에 어플리케이션이 구현이 잘못되었다면, 

 

1. read 메서드에 의해 blocking이 될 수 있다.

2. 읽어오고자 하는 데이터를 전부다 읽어오지 못할 수도 있다. 

 

위와 같은 위험성을 제거하기 위해, inputstream은 available 메서드를 제공한다. 

available 메서드는 지금 읽어올 수 있는 데이터의 양을 제공해주는 메서드이다.

이 메서드의 이름만 봐서는 현재 읽을 수 있는 값을 정확하게 돌려줄 수 있을 것 같다.

그러나 결코 그렇지 않기 때문에 사용에 많은 주의가 필요하다.

 

엄밀히 말하면, 이 메서드는 "현재 확실히 읽을 수 있는 버퍼의 크기"를 제공한다.

java doc에 가보면, "estimated value"를 제공한다고 되어 있다.

이는 이 메서드가 call 되는 시점과, 버퍼가 차오르는 시점이 다를수 있기 때문이다.

available 메서드로 얼마나 읽을 수 있을지 확인하고 있는 그 시점에도, 

새로운 데이터는 버퍼에 계속 쌓이고 있기 때문이다. 

 

이 때문에, available 메서드가 의도한대로 동작하지 않는 경우도 존재한다.

예를 들어, available이 0일 때, 루프를 탈출하도록 작성했다고 가정한다.

 

InputStream is = socket.inputStream();

while(is.available()>0) {}

클라이언트에서 데이터를 보내도, 해당 루프를 아예 돌지 않을 수 있다는 것이다.

왜냐하면, is.available을 호출하는 시점에는 클라이언트에서 보낸 데이터가 도착하지 않았지만,

그 이후엔 계속 클라이언트에서 보낸 데이터가 버퍼에 쌓일 수 있다. 

 

자바 ee 서버에서는 inputstream 구현체를 만들면서(tomcat의 경우 CoyoteInputStream),

해당 메서드도 오버라이드 해버리는 경우도 있어 위 설명과 동작이 다를 수도 있다.

어찌되었든 그로 인해 다른 문제가 생길수도 있기 때문에 문서를 보거나 구현을 확인한 후에 사용하도록 하자.

 

결론은 아래와 같다.

 

자바 어플리케이션 내에서 inputstream으로 값을 읽어올 때에는, 위의 상황을 고려하지 않으면,

데이터를 끝까지 읽지 못하거나, blocking이 될 위험성이 있다. 

그렇기 때문에 자바 어플리케이션 내에서 inputstream으로 값을 읽어올 때에는 주의가 필요하다.

 

특히 tomcat의 AJP 프로토콜은 read 메서드로 한번에 8186바이트만 읽을 수 있기 때문에,

그거보다 큰 데이터를 읽어오는 경우엔 별도의 처리를 해주는게 맞아보인다.

 

참고로 request#getParameter() 메서드로 호출할 때에는, http header에 포함된 content-length 만큼 데이터를 읽어

맵을 만들도록 구현하기 때문에, 이 문제에 대해 고려할 필요가 없다.

 

https://jongqui.tistory.com/6

 

 

 

 

728x90

Luna HSMs have many capabilities that are not certified by NIST. To be FIPS-compliant, the HSM must be set to FIPS mode, where any mechanisms or cryptographic operations that are not FIPS-certified are blocked from use. FIPS mode is set using HSM or partition policies as described below.

 

Setting FIPS Mode on the HSM

You can set the HSM to FIPS mode using HSM policy 12: Allow non-FIPS algorithms. When this policy is set to 0, algorithms that are not FIPS-validated are blocked from use on every partition on the HSM, and the HSM is operating in FIPS mode. There are two methods of setting this policy:

>The HSM SO can use a policy template to set the policy at initialization (see Setting HSM Policies Using a Template). This method is recommended for auditing purposes -- it ensures that the HSM is in FIPS mode for its entire use cycle.

>The HSM SO can set the policy manually after initializing the HSM (see Setting HSM Policies Manually).

'Cryptography' 카테고리의 다른 글

HOTP and TOTP  (31) 2024.03.21
The group Zp*  (31) 2024.03.11
Padding oracles and the decline of CBC-mode cipher suites  (86) 2024.03.08
CBC-bit Flipping  (24) 2024.03.08
AES Cipher  (0) 2024.03.07
728x90

JSONException and JSONObject components called from JSON Library were not imported in Jenkins compiling stage.

Components are not included in or are not recognized at the JVM level, which is expected.

 

Despite these issues causing some users, I have no idea why they happen.

 

Change Below 

import org.springframework.boot.configurationprocessor.json.JSONObject;
import org.springframework.boot.configurationprocessor.json.JSONException;

 

to

import org.json.JSONException;
import org.json.JSONObject;

 

Also, import JSON directly

implementation 'org.json:json:20240303'

 

'Java > Spring Boot JPA' 카테고리의 다른 글

생성자와 Builder패턴  (1) 2024.04.26
JobParametersIncrementer  (0) 2024.04.03
Running Jobs from within a Web Container  (31) 2024.04.03
Builder and AllArgsConstructor Annotation  (31) 2024.04.02
Item Processing in Spring Batch  (0) 2024.04.01

+ Recent posts