네로개발일기

개발자 네로의 개발 일기, 자바를 좋아합니다 !

'전체 글'에 해당되는 글 194건


반응형

# private 생성자나 열거타입으로 싱글턴임을 보증하라.

 

## 싱글턴이란?

* 싱글턴(singleton): 인스턴스를 오직 하나만 생성할 수 있는 클래스

- 함수와 같은 무상태(stateless) 객체

- 설계상 유일해야하는 시스템 컴포넌트

- 클래스를 싱글턴으로 만들면 이를 사용하는 클라이언트를 테스트하기 어려울 수 있음

 

* Mock 객체란?

- 실제 객체를 다양한 조건으로 인해 제대로 구현하기 어려울 경우 **가짜 객체를 만들어 사용**하는데, 이를 Mock 객체라 한다.

 

* Mock 객체가 필요한 경우

- 테스트 작성을 위한 환경 구축이 어려운 경우

- 테스트가 특정 경우나 순간에 의존적인 경우

- 시간이 걸리는 경우

 

## 1. public static 멤버가 final 필드

public class Elvis {

    public static final Elvis INSTANCE = new Elvis();

    private Elvis() { ... }
}

 

- private 생성자가 public static final 필드를 초기화할 때 딱 한번만 호출된다.

- public 혹은 protected 생성자가 없으므로, Elvis 클래스가 초기화될 때 만들어진 인스턴스는 하나뿐임이 보장된다.

 

### 장점

1. public 필드 방식은 해당 클래스가 싱글턴임이 API에 명백하게 드러난다. (final이므로 다른 객체 참조 불가)

2. 간결하다.

 

## 2. static factory 방식

public class Elvis {

    private static final Elvis INSTANCE = new Elvis();

    private Elvis() { ... }

    public static Elvis getInstance() {
        return INSTANCE;
    }
}

 

- Elvis.getInstance()는 항상 같은 객체의 참조를 반환하므로 인스턴스가 하나임을 보장한다.

 

### 장점

1. 현재는 singleton 객체를 리턴하는 정적 메서드지만, 향후에 필요에 따라 변경할 수 있는 확장성이 있다. 유일한 메서드를 반환하는 팩터리 메서드가 호출하는 스레드별로 다른 인스턴스를 넘겨주도록 리턴하는 방법과 같이 확장성이 열려있다.

2. 정적 팩터리를 제네릭 싱글턴 팩터리로 만들 수 있다.

3. 정적 팩터리의 메서드 참조를 공급자(supplier)로 사용할 수 있다.

 

- 다음과 같은 장점이 필요하지 않다면, public static final 필드 방식이 더 좋다.

 

### Reflection 방어

- public static final 방식과 static factory 방식은 권한이 있는 클라이언트가 Reflection API인 AccessibleObject.setAccessible을 사용해 private 생성자를 호출할 수 있는 문제점이 있다. 이러한 공격을 방어하려면 두번째 객체가 생성되려 할 때 다음과 같이 예외처리가 가능하다.

public class Elvis {

    public static final Elvis INSTANCE = new Elvis();

    private Elvis() { 
        if( INSTANCE != null) {
            throw new RuntimeException("Can't create Constructor");
        }
          //... 
    }

}

 

### singleton class 직렬화

- singleton class를 직렬화하려면 단순히 Serializable을 구현하는 것만으로는 부족하다. 모든 인스턴스 필드를 transient(일시적) 약어를 선언하고 readResolve 메서드를 제공해야 한다. 이렇게 하지 않으면 역직렬화시 새로운 인스턴스가 생성된다.

public class Elvis implements Serializable {

    private static final Elvis INSTANCE = new Elvis();

    private Elvis() { ... }

    public class Elvis getInstance() {
        return INSTANCE;
    }

    private Object readResolve() { // singleton임을 보장
        return INSTANCE; 
        // 역직렬화가 되어 새로운 인스턴스가 생성되더라도 INSTANCE를 반환하여 싱글턴 보장
        // 새로운 인스턴스는 GC에 의해 UnReachable 형태로 판별되어 제거
    }
}

 

## 3. Enum 방식

public enum Elvis {

    INSTANCE;
}

- 원소가 하나인 Enum 타입을 선언해 singleton을 만들 수 있다.

 

### 장점

1. public static 방식보다 더 간결하다.

2. 추가 코드 없이 직렬화 가능(기본적으로 직렬화되어있음.)

3. Reflection 공격과 아주 복잡한 직렬화 상황에도 제 2의 인스턴스가 생기는 일을 완벽히 방어

 

- 대부분의 상황에서 원소가 하나뿐인 열거 타입이 singleton을 만드는 가장 좋은 방법이다. 하지만, 만들려는 singleton이 Enum 이외의 클래스를 상속해야하는 경우 이 방법은 사용할 수 없다.

728x90
반응형
blog image

Written by ner.o

개발자 네로의 개발 일기, 자바를 좋아합니다 !

반응형

# 생성자에 매개변수가 많다면 빌더를 고려하라

- 생성자나 정적 팩터리가 처리해야 할 매개변수가 많다면 빌더 패턴을 선택하는 것이 낫다.

- 매개변수 중 다수가 필수가 아니거나 같은 타입이면 빌더 패턴을 선택하는 것이 낫다.

- 빌더는 점층적 생성자보다 클라이언트를 읽고 쓰기가 훨씬 간결하다.

- 빌더는 자바빈즈보다 훨씬 안전하다.

 

정적 팩터리와 생성자는 "선택적 매개변수"가 많을 때 적절하게 대응하기 어렵다는 단점이 있다.

 

## 점층적 생성자 패턴(Telescoping Constructor Pattern)

- 필수 매개변수를 받는 생성자 1개, 그리고 선택 매개변수를 하나씩 늘려가며 생성자를 만드는 패턴      

=> 단점     

1. 클라이언트가 초기화하고 싶은 필드만 포함한 생성자가 없으면, 원하지 않는 필드까지 불가피하게 초기화

2. 복잡하고 읽기 어려움

 

## 자바빈즈 패턴(JavaBeans Pattern)

- setter를 사용하자       

=> 단점     

1. 객체 하나를 만들려면 메서드를 계속해서 호출해야하고,

2. 그 사이 일관성을 유지하기 어렵다. => 런타임 문제 디버깅이 하드해진다. (1회의 함수 호출로 객체 생성을 끝낼 수 없으므로...)   

    - 즉, setter가 가지는 모든 단점을 가지게 된다. "불변 객체로 만들 수 없다." => Thread 안전하지 않다.       

 

## 빌더 패턴

- 필수 매개변수만으로 생성자(정적 팩터리)를 호출해 빌더 객체를 얻는다.

- 일종의 setter 메서드로 원하는 선택 매개변수를 설정한다.

- build 메서드를 호출해 필요한 객체를 얻는다. (주로 불변)

 

## 빌더 패턴의 쓰임새

- 계층적으로 설계된 클래스와 함께 사용하기 좋다.

- simulated self-type: 추상 메서드 self를 더해 하위 클래스에서 형 변환없이 메서드 연쇄를 지원한다. 

- 재귀적 한정 타입: 자기 자신이 들어간 표현식을 사용해 타입 매개변수의 허용 범위를 한정한다.

- 공변 반환 타이핑(covariant return typing): 하위 클래스의 메서드가 상위 클래스의 메서드가 정의한 반환 타입이 아닌, 하위 타입을 반환하는 기능

 

## 빌더 패턴의 단점

- 객체를 만들 때 빌더부터 만들어야 하는데, 빌더 생성비용이 크지는 않지만, 성능에 민감한 상황에서는 문제다.

- 매개변수가 4개 이상은 되어야...

728x90
반응형
blog image

Written by ner.o

개발자 네로의 개발 일기, 자바를 좋아합니다 !

반응형

# 생성자 대신 정적 팩터리 메서드를 고려하라

 

인스턴스를 생성하는 방법은  

(1) public 생성자   

(2) 정적 팩터리 메서드

 

## 정적 팩터리 메서드의 장점

### 1. 이름을 가질 수 있다.

생성자는 클래스명 형태로만 구현할 수 있지만,   

정적 팩터리 메서드는 자신의 이름을 가질 수 있다.    

직접 네이밍을 할 수 있기 때문에 반환될 객체의 특성을 쉽게 파악할 수 있다.   

 

### 2. 호출될 때마다 인스턴스를 새로 생성하지 않아도 된다.

**static 자원은 JVM 클래스로더의 "초기화" 부분에서 할당된다.**

반복되는 요청에 같은 객체를 반환하는 정적 팩터리 방식의 클래스는 언제 어느 인스턴스를 살아있게 할지를 철저히 통제할 수 있다.

인스턴스를 통제하면 해당 클래스로 만들어질 객체의 수량을 조절할 수 있다.

- 객체 1개 제한: Singleton

 

=> 불변 클래스 (immutable class)를 만들 수 있음.

@Test
void booleanTest() {

    Boolean boolean1 = Boolean.valueOf(true);
    Boolean boolean2 = Boolean.valueOf("true");
    Boolean boolean3 = new Boolean(true);

    assertThat(boolean1).isSameAs(boolean2);
    assertThat(boolean1).isSameAs(boolean3);    
}

Boolean.valueOf()는 Boolean 객체를 반환하지만 객체를 생성하지 않음.  

true를 지칭하는 Boolean 타입의 변수는 같은 레퍼런스를 바라보고있다. 하지만 new 를 통해 생성하는 인스턴스는 새로운 인스턴스이기 때문에 같은 레퍼런스를 바라보고 있지 않다.

 

* 플라이웨이트 패턴

- 데이터를 공유하여 메모리를 절약하는 패턴

- 공통으로 사용되는 객체는 한 번만 생성되고 공유를 통해 풀(Pool)에 의해 관리, 사용된다. 없으면 만들고 있으면 있는 것을 반환하는 패턴

 

### 3. 반환 타입의 하위 타입 객체를 반환할 수 있다.

반환되는 객체의 클래스를 훨씬 유연하게 결정할 수 있다.  

인터페이스로 정적 팩토리 메서드를 구현하면 다형성을 가진 객체를 제공할 수 있다.     

 

### 4. 매개변수에 따라 다른 클래스 객체를 반환할 수 있다.

정적 팩터리 메서드를 사용하면 동적으로 적절한 타입의 객체를 반환할 수 있다.     

public class Ticket {

    public static Ticket getTicketByType(String type) {
        if (type.equals("vip"))
            return VipTicket.INSTANCE;
            
        if (type.equals("general"))
            return GeneralTicket.INSTANCE;

        new IllegalArgumentException("wrong type");
    }
}

 

### 5. 정적 팩터리 메서드를 작성하는 시점에는 반환할 객체의 클래스가 존재하지 않아도 된다.

- 반환값이 인터페이스여도 된다.

- 정적 팩터리 메서드의 변경없이 구현체를 바꿔끼울 수 있다. => 반환값의 구현체이기만 한다면 가능함. 이를 통해 **유연한 시스템 구현이 가능하다.**

 

## 정적 팩터리 메서드의 활용 예시 - 서비스 제공자 프레임워크

- 서비스 인터페이스: 구현체의 동작 정의

- 제공자 등록 API: 제공자가 구현체 등록

- 서비스 접근 API: 클라이언트가 서비스의 인스턴스 얻을 때 => 원하는 구현체의 조건을 입력하고 그에 따라 기본 구현체를 반환할 수 있음. 이것을 정적 팩터리로 작성할 수 있다.

- 서비스 제공자 인터페이스 (SPI): 서비스 인터페이스 인스턴스를 생성하는 팩터리 객체 설명

 

## 정적 팩터리 메서드의 단점

### 1. 상속을 하려면 public이나 protected 정적 팩터리 메서드만 제공하면 하위 클래스를 만들 수 없다.

- Collections는 상속할 수 없다. 생성자가 private으로 구현되어 있다.

- 상속보다 컴포지션을 유도할 수 있기 때문에 OCP에 적합하다.

 

### 2. 찾기 어려움. 통용되는 네이밍 준수

- from: 매개변수를 받아서 해당 타입의 인스턴스 반환. 형 변환

- of: 여러 매개변수를 받아 적합한 인스턴스 반환

- valueOf: from, of 보다 자세한 버전

- instance, getInstance: 매개변수 인스턴스를 반환하지만 보장하지는 않음

- create, newInstance: 매번 새로운 인스턴스 생성해 반환

- getType: 반환 타입과 팩터리 메서드 클래스가 다름. Type은 반환타입 명시

- type: getType, newType 간결한 버전

 

## 핵심 정리

- 정적 팩터리 메서드와 public 생성자는 각자의 쓰임새가 있으니 상대적인 장단점을 이해하고 사용하는 것이 좋다.

- 정적 팩터리 메서드를 사용하는 게 유리한 경우가 있을 수 있으므로 public 생성자를 제공하던 습관이 있다면 고치도록 한다. 

 

728x90
반응형
blog image

Written by ner.o

개발자 네로의 개발 일기, 자바를 좋아합니다 !

반응형

발단>

1. Gradle 버전이 4.*에서부터 5.* 최근에는 6.7까지 출시

2. Spring boot 2.3부터 Gradle 버전 6.3 이상을 요구

 

개발환경>

spring boot 2.4

Gradle 6.7.1 

 

인텔리제이에서 프로젝트를 진행하면서 QueryDsl을 이용해서 생성한 Q클래스를 생성하였다.

빌드시 에러가 발생함. 

 

에러 메시지>

package com.querydsl.core.types does not exist

수정 전 build.gradle>

plugins {
    id 'org.springframework.boot' version '2.4.2'
    id 'io.spring.dependency-management' version '1.0.11.RELEASE'
    id 'com.ewerk.gradle.plugins.querydsl' version '1.0.10'
    id 'java'
}

group = 'com.jiyoon'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'

configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-validation'
    compileOnly 'org.projectlombok:lombok'
    runtimeOnly 'com.h2database:h2'
    annotationProcessor 'org.projectlombok:lombok'
    testImplementation('org.springframework.boot:spring-boot-starter-test') {
        exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
    }
    testImplementation 'io.rest-assured:rest-assured:4.3.3'
    compile group: 'io.springfox', name: 'springfox-swagger2', version: '2.9.2'
    compile group: 'io.springfox', name: 'springfox-swagger-ui', version: '2.9.2'
    implementation 'com.querydsl:querydsl-jpa'
    implementation 'com.querydsl:querydsl-apt'
}

test {
    useJUnitPlatform()
}

def querydslSrcDir = "src/main/generated"

querydsl {
    library = "com.querydsl:querydsl-apt"
    jpa = true
    querydslSourcesDir = querydslSrcDir
}

sourceSets {
    main.java.srcDirs = ['src/main/java', querydslSrcDir]
}

configurations {
    querydsl.extendsFrom compileClasspath
}

compileQuerydsl {
    options.annotationProcessorPath = configurations.querydsl
}

Gradle Plugin “com.ewerk.gradle.plugins.querydsl” 로 설정하였다. 해당 프로젝트에서는 빌드 시 에러가 발생

 

수정 후 build.gradle>

plugins {
    id 'org.springframework.boot' version '2.4.2'
    id 'io.spring.dependency-management' version '1.0.11.RELEASE'
    id 'java'
}

group = 'com.jiyoon'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'

def querydslVersion = '4.3.1'

configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-validation'
    compileOnly 'org.projectlombok:lombok'
    runtimeOnly 'com.h2database:h2'
    annotationProcessor 'org.projectlombok:lombok'
    testImplementation('org.springframework.boot:spring-boot-starter-test') {
        exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
    }
    testImplementation 'io.rest-assured:rest-assured:4.3.3'
    compile group: 'io.springfox', name: 'springfox-swagger2', version: '2.9.2'
    compile group: 'io.springfox', name: 'springfox-swagger-ui', version: '2.9.2'

// Querydsl
    implementation group: 'com.querydsl', name: 'querydsl-jpa', version: querydslVersion
    implementation group: 'com.querydsl', name: 'querydsl-apt', version: querydslVersion
    implementation group: 'com.querydsl', name: 'querydsl-core', version: querydslVersion

    annotationProcessor group: 'com.querydsl', name: 'querydsl-apt', version: querydslVersion
    annotationProcessor group: 'com.querydsl', name: 'querydsl-apt', version: querydslVersion, classifier: 'jpa'
    annotationProcessor("jakarta.persistence:jakarta.persistence-api")
    annotationProcessor("jakarta.annotation:jakarta.annotation-api")
}

test {
    useJUnitPlatform()
}

clean {
    delete file('src/main/generated') // 인텔리제이 Annotation processor 생성물 생성위치
}

참고

Gradle 5.0이상 & IntelliJ 2020.* 사용 시

 

Spring Boot Data Jpa 프로젝트에 Querydsl 적용하기

안녕하세요? 이번 시간에는 Spring Boot Data Jpa 프로젝트에 Querydsl을 적용하는 방법을 소개 드리겠습니다. 모든 코드는 Github에 있습니다. Spring Data Jpa를 써보신 분들은 아시겠지만, 기본으로 제공해

jojoldu.tistory.com

[gradle] 그레이들 Annotation processor 와 Querydsl

 

[gradle] 그레이들 Annotation processor 와 Querydsl - I'm honeymon(JiHeon Kim).

이 글에서 다룰 예정인 ‘Querydsl’과 ‘Annotation processor’ 에 관한 내용도, 스프링 부트를 버전업하는 과정에서 겪게 된다. 사내 개발기기 교체주기가 되어 새로운 맥북을 받고 스프링 부트 버전

honeymon.io

problem-with-generating-querydsl-classes-with-gradle

 

Problem with generating Querydsl classes with Gradle

I want to generate 'Q' classes using querydsl. The problem that I'm facing is described by errors: > Task :compileJava FAILED Attempt to recreate a file for type com.my.dinner.rest.database.ent...

stackoverflow.com

 

728x90
반응형
blog image

Written by ner.o

개발자 네로의 개발 일기, 자바를 좋아합니다 !

반응형

728x90
반응형
blog image

Written by ner.o

개발자 네로의 개발 일기, 자바를 좋아합니다 !