Property 값 주입

Spring Boot 프로젝트가 커지면 공통으로 사용되는 글로벌 값을 별도로 관리할 필요가 생긴다.

@Value 어노테이션은 properties 파일에 세팅한 내용을 Spring 변수에 주입하는 역할을 한다.

@Value 어노테이션의 사용법에 대해 알아보도록 하자.


1. @Value("") 사용

greetingMessage 변수에 "Hello World" 를 주입해서 사용할 수 있다.

@RestController
public class ValueController {

    @Value("Hello World")
    private String greetingMessage;

    @GetMapping("")
    public String sendGreeting(){
        return greetingMessage;
    }
}

String 뿐만 아니라 다른 타입에도 사용할 수 있다.

@Value("1.234")
private double doubleValue; //could be Double

@Value("1234")
private Integer intValue; //could be int

@Value("true")
private boolean boolValue; //could be Boolean

@Value("2000")
private long longValue;

2. @Value("${...}") 사용

application.properties 에 정의한 내용을 가져와서 사용할 수 있다.

# application.properties
greeting.message=Hello World!
@RestController
public class ValueController {

    @Value("${greeting.message}") 
    private String greetingMessage;

    @GetMapping("")
    public String sendGreeting(){
        return greetingMessage;
    }
}

속성 값은 런타임에 변수로 주입되며 만약 속성값이 properties 파일에 없으면 아래와 같은 오류가 발생한다.

Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2020-02-29 21:54:43.953 ERROR 2996 --- [main] o.s.boot.SpringApplication: Application run failed
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'valueController': Injection of autowired dependencies failed; nested exception is java.lang.IllegalArgumentException: Could not resolve placeholder 'greeting.message' in value "${greeting.message}"

오류를 방지하기 위해 @Value 어노테이션에는 기본값을 세팅할 수 있다.

@Value("${greeting.message:Greeting not found!}") 처럼 콜론을 붙이고 뒤에 기본값을 붙이면 된다.

만약 콜론 앞에 공백이 있으면 에러가 발생한다.

# application.properties

my.int.value=20
my.double.value=3.142
my.boolean.value=true
@Value("${my.int.value:0}")
private int intValue; // 런타임에 20 주입

@Value("${my.double.value: 0.0}")
private double doubleValue; // 런타임에 3.142 주입

// property 에 값이 있어도 공백 때문에 기본값이 들어감
@Value("${my.boolean.value :false}")
private boolean boolValue; // 공백 때문에 false 주입

// proprety 에 값이 없어서 기본값 사용
@Value("${my.long.value:300}")
private long longValue; // 런타임에 300 주입

3. @Value("${...}") 로 List 주입

# application.properties
my.weekdays=Mon,Tue,Wed,Thu,Fri
@Value("${my.weekdays}")
private List<String> strList; // injects [Mon, Tue, Wed, Thu, Fri]

//Providing default value
@Value("${my.weekends:Sat,Sun,Fri}")
private List<String> strList2; // injects [Sat, Sun, Fri]

4. @Value("#{${...}}") 로 Map 주입

# application.properties
database.values={url:'http://127.0.0.1:3306/', db:'mySql', username:'root', password:'root'}
@RestController
public class ValueController {

    @Value("#{${database.values: {url: 'http://127.0.0.1:3308/', db: 'mySql', username: 'root', password: ''}}}")
    private Map<String, String> dbValues;

    @GetMapping("")
    public Map getDBProps(){
        return dbValues;
    }
}

5. @Value("${...}") 생성자 파라미터에 주입

생성자에 파라미터로 넘기면서 값을 주입할 수도 있다.

# application.properties

company.name= Scopesuite Pty ltd
# company.location= Sydney
@Service
public class CompanyService {
   private String compName;
   private String location;

   public CompanyService(
    @Value("${company.name}") String compName,
    @Value("${company.location:Washington}") String location
   ) {
       this.compName = compName;
       this.location = location;
   }
}

Spring 이란?

간단히 정리하면 스프링은 "자바 엔터프라이즈 개발을 편하게 해주는 오픈소스 프레임워크" 이며

코드의 가독성과 의존성 (클래스가 다른 클래스에 종속적임) 을 해결하기 위해 Application Context 라는 컨테이너를 제공한다.

이 컨테이너에서는 Bean 들을 관리하며 각 클래스에서 사용할 수 있도록 Bean 을 생성해주는 것을 DI (의존성 주입) 이라고 한다.

개발자가 아닌 컨테이너가 직접 Bean 을 생성, 관리하기 때문에 IoC (제어의 역전) 컨테이너라고도 한다.

스프링을 한 문장으로 표현하자면 자바 엔터프라이즈 개발을 편하게 해주는 오픈소스 경량급 애플리케이션 프레임워크다.

스프링은 스프링 애플리케이션 컨텍스트 (Spring Application Context) 라는 컨테이너 (Container) 를 제공하는데, 이것은 애플리케이션 컴포넌트들을 생성하고 관리한다.

애플리케이션 컴포넌트 또는 빈 (Bean) 들이 컨테이너 내부에서 서로 연결되어 완전한 애플리케이션을 만든다.


의존성 주입 (Dependency Injection, DI)

빈의 상호 연결은 의존성 주입 이라고 알려진 패턴을 기반으로 수행된다.

컨테이너에서 모든 애플리케이션 컴포넌트를 생성, 관리하고 해당 컴포넌트를 필요로 하는 빈에 주입 (연결) 한다.

일반적으로 생성자 인자 또는 속성의 접근자 메서드를 통해 처리된다.

지금까지 스프링 버전에서는 XML 파일을 사용해서 빈을 상호 연결하도록 컨테이너에 전달했다.

그러나 최신 버전의 스프링에서는 @Configuration 애노테이션을 사용하여 각 빈을 컨테이너에 제공하는 클래스라는 걸 명시한다.

아래 두 코드는 똑같은 설정을 각각 XML 과 애노테이션을 사용한 예시이다.

<bean id="inventoryService" class="com.example.InventoryService" />
<bean id="productService" class="com.example.ProductService" />
  <constructor-arg ref="inventoryService" />
</bean>
@Configuration
public class ServiceConfiguration {

  @Bean
  public InventoryService inventoryService() {
    return new InventoryService();
  }

  @Bean
  public ProductService productService() {
    return new ProductService(inventoryService());
  }
}

애노테이션 (Annotation)

애노테이션은 클래스, 인터페이스, 함수, 매개변수, 속성, 생성자에 어떤 의미를 추가할 수 있는 기능이며, 자바 컴파일러가 컴파일 시에 처리한다. 소스 코드에 추가된 애노테이션 자체는 바이트 코드로 생성되지 않고 주석으로 처리되지만, 컴파일러가 작업을 수행해준다.


제어의 역전 (Inversion of Control, IoC)

간단히 말하면 객체에 대한 제어권이 개발자로부터 컨테이너로 넘어간 것

객체의 생성부터 생명주기 관리까지 전부 컨테이너가 맡아서 하기 때문에 제어를 컨테이너가 갖고 있다.

스프링에서 제공하는 컨테이너를 IoC 컨테이너라고 하기도 한다.

컨테이너가 직접 빈을 생성/관리하기 때문에 개발자는 코드에 new 등으로 선언하지 않아도 되며 이는 각 클래스들의 의존도를 줄여준다.


IoC 용어 정리

  • bean : 스프링에서 제어권을 가지고 직접 만들어 관계를 부여하는 오브젝트
    • 스프링을 사용하는 애플리케이션에서 만들어지는 모든 오브젝트가 빈은 아니다. 스프링의 빈은 스프링 컨테이너가 생성하고 관계설정, 사용을 제어해주는 오브젝트를 말한다.
  • bean factory : 스프링의 IoC를 담당하는 핵심 컨테이너
    • Bean을 등록/생성/조회/반환/관리 한다. 보통 bean factory를 바로 사용하지 않고 이를 확장한 application context를 이용한다. BeanFactory는 bean factory가 구현하는 interface이다. (getBean() 등의 메서드가 정의되어 있다)
  • application context : bean factory를 확장한 IoC 컨테이너
    • Bean의 등록/생성/조회/반환/관리 기능은 bean factory와 같지만, 추가적으로 spring의 각종 부가 서비스를 제공한다. ApplicationContext 는 application context 가 구현해야 하는 interface이며, BeanFactory를 상속한다.
  • configuration metadata : application context 혹은 bean factory가 IoC를 적용하기 위해 사용하는 메타정보
    • 스프링의 설정정보는 컨테이너에 어떤 기능을 세팅하거나 조정하는 경우에도 사용하지만 주로 bean을 생성/구성하는 용도로 사용한다.
  • container (ioC container) : IoC 방식으로 bean을 관리한다는 의미에서 bean factory나 application context를 가리킨다.
    • application context는 그 자체로 ApplicationContext 인터페이스를 구현한 오브젝트를 말하기도 하는데, 하나의 애플리케이션에 보통 여러개의 ApplicationContext 객체가 만들어진다. 이를 통칭해서 spring container라고 부를 수 있다.

자동 구성 (Auto Configuration)

자동 연결 (Autowiring) 과 컴포넌트 스캔 (Component Scanning) 이라는 스프링 기법을 기반으로 한다.

스프링은 컴포넌트 스캔을 사용하여 애플리케이션의 classpath 에 지정된 컴포넌트를 찾은 후 컨테이너의 빈으로 생성한다.

스프링 부트의 Auto Configuration 라이브러리는 다음 일들을 수행한다.

  • 스프링 MVC 를 활성화 하기 위해 컨테이너 (스프링 애플리케이션 컨텍스트) 에 관련된 Bean 들을 구성한다.
  • 내장된 Tomcat 서버를 컨테이너에 구성한다.
  • 스프링 MVC 뷰를 나타내기 위해 사용하는 템플릿 (JSP, Thymeleaf, Mustache 등) 의 뷰 리졸버 (View Resolver) 를 구성한다.

Spring JPA

요약

1. ORM (Object-Relational Mapping) 이란?
  - 객체 지향 프로그래밍과 관계형 데이터베이스 사이의 구조적 문제를 해결해주는 프레임워크
  - 개발자가 객체 지향 프로그래밍에 집중할 수 있게 해준다.

2. JPA (Java Persistence API) 란?
  - 자바 ORM 기술에 대한 API 표준 명세로, Java 에서 제공한다 (Spring 아님)
  - JPA 의 구현체로 Hibernate 가 존재한다.

3. Spring Data JPA 란?
  - Hibernate 와 같은 JPA 구현체를 좀 더 쉽게 사용할 수 있도록 Spring 에서 제공하는 모듈

SQL 중심 개발의 문제점

MyBatis 와 같은 SQL Mapper 는 데이터베이스의 쿼리를 직접 작성하기 때문에 객체지향 프로그래밍 보다는 데이터베이스 테이블 모델링에 집중하게 된다.

  • 반복적인 SQL 작업

    • RDB 를 사용하면서 개발자들은 객체 지향 관점보다는 SQL 중심으로 코드를 짜게 된다.
    • RDB 는 SQL 만 인식할 수 있기 때문에 반복되는 SQL 의 사용을 피할 수 없었고 수십, 수백 개의 테이블마다 각각 SQL 문을 작성해줘야 했다.
  • RDB 와 객체지향 프로그래밍의 목적은 다르다

    • RDB 는 데이터 저장에 초점이 맞추어져 있다.
    • 객체지향 프로그래밍은 기능과 속성을 한 곳에서 관리하는 기술이다.
    • 추상화, 캡슐화, 다형성 등 객체지향의 패러다임을 RDB 로는 표현할 수 없다.

JPA (Java Persistence API) 의 등장

RDB 를 사용하는 프로젝트에서 객체지향 프로그래밍을 할 수 있게 하는 자바 표준 ORM (Object Relational Mapping) 기술이 생겼다.

JPA 는 Java 에서 제공하는 기술 명세 인터페이스이다.

JPA 는 RDB 와 객체지향 프로그래밍 두 개의 영역을 연결해주는 역할을 한다.

개발자는 객체 지향적으로 프로그래밍을 하고, JPA 는 RDB 에 맞게 SQL 을 대신 생성해서 실행해준다.


ORM 과 SQL Mapper 의 차이

  • ORM (Object-Relation Mapping)

    • 객체를 매핑하여 간접적으로 DB 를 다룸
    • 개발자는 SQL 쿼리 대신 메서드로 데이터를 조작하며 ORM 이 SQL 을 자동으로 생성해준다.
    • JPA
  • SQL Mapper

    • 쿼리를 매핑하여 SQL 문으로 직접 DB 를 조작한다.
    • MyBatis, jdbcTemplate

Spring Data JPA (Repository)

JPA 는 인터페이스이기 때문에 구현체가 필요하다.

대표적으로 Hibernate, Eclipse Link 등이 있다.

Spring 에서는 이 구현체들을 좀 더 쉽게 사용할 수 있도록 추상화시킨 Spring Data JPA 모듈을 사용한다.

개발자가 Repository 인터페이스에 정해진 규칙대로 메소드를 입력하면, Spring 이 알아서 해당 메소드 이름에 적합한 쿼리를 날리는 구현체를 만들어서 Bean으로 등록해준다.

Hibernate 와 Spring Data JPA 를 사용하는 데에는 사실 큰 차이가 없지만 Spring Data JPA 가 권장되는 이유는 크게 두 가지가 있다.

  • 구현체 교체가 쉽다

    • Hibernate 외에 다른 구현체로 쉽게 교체가 가능하다.
    • Spring Data JPA 내부에서 구현체 매핑을 지원해주기 때문에 언젠가 Hibernate 외의 다른 구현체로 넘어갈 일이 생겨도 쉽게 교체 가능하다.
  • 저장소 교체가 쉽다

    • RDB 외의 다른 DB 로 쉽게 교체 가능하다.
    • Spring Data 의 하위 프로젝트들은 기본적인 CRUD 인터페이스가 같기 때문에 의존성만 교체하면 쉽게 변경이 가능하다.
    • 예를 들어 MongoDB 로 교체가 필요하다면 Spring Data JPA 에서 Spring Data MongoDB 로 의존성만 교체하면 된다.

JPA 의 장단점

  • 장점
    • CRUD 쿼리를 직접 작성할 필요가 없음
    • 부모-자식 관계 표현, 1:N 관계 표현, 상태와 행위를 한 곳에서 관리 등 객체 지향 개발에 집중 가능
  • 단점
    • 높은 러닝커브 (객체지향 프로그래밍과 RDB 를 둘 다 이해해야 함)
    • 제대로 사용하지 못하면 성능 문제가 발생함

Lombok 이란?


Lombok은 애너테이션을 기반으로 constructor, getter, setter 등 반복적으로 작성해야 하는 메서드를 자동으로 생성하는 라이브러리입니다.

Model 이나 Entity 같은 도메인 클래스에서 반복되는 코드를 @ 어노테이션을 이용하여 간단하게 적용할 수 있습니다.

개발자는 어노테이션만 사용하면 컴파일 되는 과정에서 자동으로 그에 맞는 코드들을 생성해줍니다.


Lombok 을 사용하지 않는 클래스

class Person {
  private String name;
  private Integer age;

  public Person() { }
  public Person(String name, Integer age) {
    this.name = name;
    this.age = age;
  }

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public Integer getAge() {
    return age;
  }

  public void setAge(Integer age) {
    this.age = age;
  }

  @Override
  public String toString() {
    return "Person{" +
           "name='" + name + '\'' +
           "age='" + age + '\'' +
           '}';
  }
}

클래스에 필요한 메서드들을 직접 입력해야 합니다.

(요즘은 IDE 의 도움을 받아 자동으로 Generate 할 수 있습니다)


Lombok 을 사용하는 클래스

@ToString
@Getter
@Setter
class Person {
  private String name;
  private Integer age;
}

롬복을 사용하면 위와 같이 코드를 간단하게 줄일 수 있으며 어떤 롬복이 구현되어있는지 어노테이션만 보고도 확인할 수 있습니다.

Lombok 의 더 많은 종류와 기능을 확인하시려면 Reference 의 활용법 링크를 참고해주세요.


장점

  1. 코드의 길이가 짧아진다.
  2. 사용하려는 어노테이션이 명시적이다.

단점

  1. Lombok 을 모르는 사람들에게는 한눈에 와닿지 않고 어노테이션의 무분별한 사용은 오히려 가독성을 해칠 수 있다.
  2. Lombok 을 제대로 이해하지 않고 코드를 작성하거나 수정하면 의도치 않은 결과를 얻을 수 있다. (아래 Reference 의 주의점 링크 참고)

RSpec - Ruby Test Framework

RSpec 프레임워크는 BDD 프로세스에서 사용하는 툴입니다.

따라서 RSpec 테스트 코드에는 자세한 설명이 첨부되어 있습니다.

BDD (Behavior-Driven Development) 란 ?

TDD (Test-Driven Development) 의 유닛 테스트에서 더 나아가 테스트 케이스 자체가 요구사양이 되도록 개발하는 방식입니다. 단위 테스트 보다는 행위에 집중하여 테스트 메소드의 이름을 "이 클래스가 어떤 행위를 해야한다. (should do something)" 라는 식의 문장으로 작성하며 행위를 위한 테스트에 집중을 합니다.

  • Feature : 테스트에 대상의 기능/책임을 명시한다.
  • Scenario : 테스트 목적에 대한 상황을 설명한다.
  • Given : 시나리오 진행에 필요한 값을 설정한다.
  • When : 시나리오를 진행하는데 필요한 조건을 명시한다.
  • Then : 시나리오를 완료했을 때 보장해야 하는 결과를 명시한다.


RSpec 기본 구조

테스트의 기대되는 값과 실제 값을 비교하여 성공 여부를 리턴해줍니다.

expect(테스트 객체).to 비교 matcher(예상되는 )


describe

describe 키워드를 사용해서 어떤 함수를 설명하려는 지 명확하게 합니다.

예를 들어 클래스 함수를 참조할 때는 . 또는 :: 을 접두어로 하고 인스턴스 함수를 참조할 때는 # 을 접두어로 사용합니다.

describe '.authenticate' do
describe '#admin?' do


설명을 짧게 유지하기

명세의 설명이 40문자를 넘어가면 context 를 사용해서 쪼개야 합니다.

# Bad
it 'has 422 status code if an unexcepted params will be added' do

# Good
context 'when not valid' do
  it { is_expected.to respond_with 422 }
end


각 테스트는 한 가지만을 확인

테스트가 한 가지만을 확인해야 에러를 찾기도 쉽고 실패하는 테스트를 바로 찾을 수 있습니다.

같은 예제 안에서 여러 가지 예상이 나온다면 여러 행동으로 나누어야 합니다.

# 분리형
it { is_expected.to rsespond_with_content_type(:json) }
it { is_expected.to assign_to(:resource) }

# 통합형
it 'creates a resource' do
  expect(response).to respond_with_content_type(:json)
  expect(response).to assign_to(:resource)
end


가능한 모든 케이스를 테스트

유효한 경우, 유효하지 않은 경우를 모두 테스트 해야 합니다.

예를 들어 User 삭제 기능을 테스트 하려는데 before_action 으로 User 찾기 가 걸려있다면

before_action :find_user

def destory
  render 'show'
  @user.destory
end

보통은 삭제가 잘 되었는지만 확인하지만 유저를 찾지 못한 경우에 대해서도 테스트를 해주어야 합니다.

# Bad
it 'show user'

# Good
describe '#destory' do
  context 'when user is found' do
    it 'responds with 200'
    it 'shows the user'
  end

  context 'when user is not found' do
    it 'responds with 404'
  end
end


Expect vs Should

새 프로젝트에서는 항상 expect 문법만 사용합니다.

# Bad
it 'creates a resource' do
  response.should respond_with_content_type(:json)
end

context 'when not valid' do
  it { should respond_with 422 }
end

# Good
it 'creates a resource' do
  expect(response).to respond_with_content_type(:json)
end

context 'when not valid' do
  it { is_expected.to respond_with 422 }
end


필요한 데이터만 만들기

필요 이상으로 많은 데이터를 만들 필요는 없습니다.

최대한 적은 양의 데이터로 모든 케이스를 테스트 할 수 있어야 합니다.


Reference


#nil? #empty? #blank? #present?

Ruby 에는 존재 여부를 확인 할 때 위와 같은 네가지의 메소드를 사용합니다.

이 메소드들은 조금씩 차이점이 존재하는데 어떤 건지 알아봅시다.


#nil?

#nil? 은 Object 클래스의 메소드입니다.

Ruby 의 모든 클래스는 Object 메소드를 상속 받기 때문에 #nil? 은 어떤 클래스에서도 사용할 수 있습니다.

NilClass 값에 대해서만 true 를 리턴하고 이 외에는 전부 false 를 리턴합니다.

nil.nil?    # true

true.nil?   # false

5.nil?      # false

"".nil?     # false

[].nil?     # flase


#empty?

#empty? 는 string, array, hash, set 에서 사용되는 메소드입니다.

자료구조나 문자열의 길이가 0 일 때 true 를 리턴합니다.

nil 처럼 #empty? 메소드가 정의되어 있지 않은 오브젝트에서 사용하면 NoMethodError 가 발생합니다.

"".empty?           # true

" ".empty?          # false

"\t\n".empty?       # false

[].empty?           # true

{}.empty?           # true

Set.new.empty?      # true


#blank?

#blank? 는 Rails 메소드이며 모든 오브젝트에서 사용 가능합니다.

문자열에서 사용할 경우 빈 문자열 뿐만 아니라 공백까지 전부 true 로 리턴합니다.

"".blank?       # true

" ".blank?      # true

"\t\n".blank?   # true


array, hash, set 에서는 #empty? 와 동일하게 원소가 없는 경우에 true 를 리턴합니다.

[].blank?       # true

{}.blank?       # true

Set.new.blank?  # true

[nil].blank?    # false

["", ""].blank? # false


true, false, nil 값에 대해서 각각 다음과 같이 리턴합니다.

true.blank?     # false

false.blank?    # true

nil.blank?      # true


#present?

#present? 는 #blank? 와 완전히 반대되며, 마찬가지로 Rails 메소드입니다.


Table


Diagram


Reference


Ruby Regular Expressions

루비 정규 표현식은 문자열 내의 특정 패턴을 찾는 데 도움을 줍니다.

루비에서 정규식을 사용할 때는 양 끝에 슬래쉬(/) 를 사용합니다.


Example

1. =~

# 'world' 단어 찾기
"Hello world!" =~ /world/   # 6
"Hello world!" =~ /worla/   # nil

=~ 식으로 비교하면 정규 표현식이 시작되는 인덱스를 리턴합니다.

해당되는 인덱스가 존재하지 않으면 nil 을 리턴합니다.


2. match

if "Hello world!".match(/world/)    # <MatchData "world">
    puts "world is found"
end

match(/regex/) 메소드를 통해 정규식 포함 여부를 알 수 있습니다.

만약 정규 표현식에 해당되는 값이 없다면 nil 을 리턴합니다.

간단히 true / false 여부만 알고싶다면 include? 메소드를 사용할 수 있습니다.

+ Recent posts