여니의 프로그래밍 study/Spring & Spring Boot

[Spring] Chapter 15 ~ 17 : 간단한 웹 어플리케이션의 구조, JSON 응답과 요청처리, 프로필과 프로퍼티 파일

여니's 2023. 12. 29. 17:09

 

참고 자료 : 초보 개발자를 위한 스프링5 프로그래밍 입문


<Chapter 15>

1. 간단한 웹 어플리케이션의 구성 요소

(1) 프론트 서블릿

-> 웹 브라우저의 모든 요청을 받는 창구 역할,

요청 분석 후 알맞은 컨트롤러에 전달함

스프링 MVC에서는

DispatcherServle이 역할을 수행함

 

 

(2) 컨트롤러 + 뷰 

-> 실제 웹 브라우저의 요청을 처리함

클라이언트의 요청을 처리하기 위해 알맞은 기능을 실행하고

그 결봐를 뷰에 전달함

 

컨트롤러의 주요역할

1) 클라이언트가 요구한 기능 실행

2) 응답 결과를 생성하는데 필요한 모델 생성

3) 응답 결과를 생성할 뷰 선택

 

컨트롤러는 어플리케이션이 제공하는 기능과

사용자 요청을 연결하는 매개체로서

기능 제공을 위한 로직을 직접 수행하진 않음.

대신 해당 로직을 제공하는 서비스에 그 처리를 위임함

 

 

(3) 서비스

-> 기능의 로직을 구현함

 

 

(4) DAO

> DB 연동이 필요하면 DAO를 사용함

> DAO는 Data Access Object의 약자로

DB와 웹 어플리케이션 간에 데이터를 이동시켜 주는 역할을 맡음

 

어플리케이션은 DAO를 통해 

DB에 데이터를 추가하거나 DB에서 데이터를 읽어옴

 

[순서]

DispatcherServlet -> 컨트롤러 -> 서비스 -> DAO

 

 


 

2. 서비스의 구현

> 서비스는 핵심이 되는 기능의 로직을 제공함

 

예를 들어 비밀번호 변경 기능은 다음 로직을 서비스에서 수행함

- 디비에서 비밀번호를 변경할 회원의 데이터를 구함

- 존재하지 않으면 익셉션

- 회원 데이터의 비밀번호를 변경

- 변경 내역을 디비에 반영함

 

서비스 메서드를 트랜잭션 범위에서 실행함.

( @Transactional을 이용해서 )

 

서비스 메서드는 기능을 실행한 후

결과를 알려줘야한다.

결과는 크게 2가지 방식으로 알려줌

 

1) 리턴 값을 이용한 정상 결과

2) 익셉션을 이용한 비정상 결과

 

 


 

3. 컨트롤러에서의 DAO 접근

서비스 메서드에서 

어떤 로직도 수행하지 않고

단순히 DAO의 메서드만 호출하고

끝나는 코드가 있다.

예를 들면 회원 데이터 조회를 위한 메서드.

 

public class MemberService {
	...
    public Member getMember(Long id) {
    	return memberDao.selectbtId(id);
    }
}

 

 

MemberService 클래스의 getMember() 메서드는

MemberDao의 selectByEmail() 메서드만 실행할 뿐

추가 로직은 없음.

 

컨트롤러 클래스는 이 서비스 메서드를 이용하여

회원 정보를 구하게 된다

 

@RequestMapping("/member/detail/{id}")
public String detail(@PathVariable("id") Long id, Model model) {
	// 사실상 DAO를 직접 호출하는 것과 동일
    Member member = memberService.getMember(id);
    if (member == null) {
    	return "member/notFound";
    }
    model.addAttribute("member",member);
    return "member/memberDetail";
}

 

 


<Chapter 16>

1. JSON 응답과 요청 처리

(1) JSON 개요 (Javascript Object Notation)

 

-> 웹 요청에 대한 응답으로 HTML 대신 JSON이나 XML을 사용함

웹 요청에도 쿼리 문자열 대신

JSON이나 XML을 데이터로 보내기도 함.

 

JSON은 간단한 형식을 갖는 문자열로

데이터 교환에 주로 사용함

 

{
	"name":"유관순"
    "birthday":"1902-12-16",
    "edu":[
    	{
        	"title":"호호",
            "year":1917
        }
 	]
}

 

 

중괄호를 사용하여 객체를 표현함

객체는 (이름,값) 쌍을 갖는다.

이름과 값은 : 콜론으로 구분함

 

값에는 문자열, 숫자, 불리언, null, 배열, 다른객체가 올 수 있음

 

문자열은 큰 따옴표나 작은 따옴표 사이에 위치한 값

 

배열은 대괄호로 표현함

대괄호 안에 콤마로 구분한 값 목록을 갖는다. 

 

 

(2). Jackson 의존 설정

-> Jackson 은 자바 객체와 JSON 형식 문자열 간 변환을 처리하는 라이브러리

스프링 MVC에서 Jackson 라이브러리를 이용해서

자바 객체를 JSON으로 변환하려면

클래스패스에 해당 라이브러리를 추가하면 된다. (pom.xml에 의존 추가)

 

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.9.4</version>
</dependency>
<!-- java8 date/time -->
<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jsr310</artifactId>
    <version>2.9.4</version>
</dependency>
<!-- java8 Optional, etc -->
<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jdk8</artifactId>
    <version>2.9.4</version>
</dependency>

 

 

public class Person {

private String name;

private int age;

 

... get/set 메서드

}

 

<-> 

 

{

"name" : "이름",

"age" : 10

}

 

Jackson은 프로퍼티의 이름과 값을

JSON 객체의 (이름, 값) 쌍으로 사용함.

 

 

Person 객체의 name 프로퍼티 값이

"이름"이라고 할 때 생성되는 JSON 형식 데이터는 이름이 "name" 이고

값이 "이름"인 데이터를 갖는다.

 

 


3. @RestController 로 JSON 형식 응답

JSON 형식으로 데이터를 응답하는 것은

@Controller 대신에

@RestController 어노테이션을 사용하면 된다.

 

package controller;

import java.io.IOException;
import java.net.URI;
import java.util.List;
import java.util.stream.Collectors;

import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.Errors;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import spring.DuplicateMemberException;
import spring.Member;
import spring.MemberDao;
import spring.MemberNotFoundException;
import spring.MemberRegisterService;
import spring.RegisterRequest;

@RestController
public class RestMemberController {
	private MemberDao memberDao;
	private MemberRegisterService registerService;

	@GetMapping("/api/members")
	public List<Member> members() {
		return memberDao.selectAll();
	}
	
	@GetMapping("/api/members/{id}")
	public ResponseEntity<Object> member(@PathVariable Long id) {
		Member member = memberDao.selectById(id);
		if (member == null) {
			return ResponseEntity
					.status(HttpStatus.NOT_FOUND)
					.body(new ErrorResponse("no member"));
			// return ResponseEntity.notFound().build();
		}
		return ResponseEntity.ok(member);
	}

	@GetMapping("/api/members2/{id}")
	public Member member2(@PathVariable Long id, HttpServletResponse response) throws IOException {
		Member member = memberDao.selectById(id);
		if (member == null) {
			response.sendError(HttpServletResponse.SC_NOT_FOUND);
			return null;
		}
		return member;
	}

	@GetMapping("/api/members3/{id}")
	public Member member3(@PathVariable Long id) {
		Member member = memberDao.selectById(id);
		if (member == null) {
			throw new MemberNotFoundException();
		}
		return member;
	}

	@PostMapping("/api/members")
	public ResponseEntity<Object> newMember(
			@RequestBody @Valid RegisterRequest regReq /*,
			Errors errors */) {
		/*
		if (errors.hasErrors()) {
			String errorCodes = errors.getAllErrors()
					.stream()
					.map(error -> error.getCodes()[0])
					.collect(Collectors.joining(","));
			return ResponseEntity
					.status(HttpStatus.BAD_REQUEST)
					.body(new ErrorResponse("errorCodes = " + errorCodes));
		}
		*/
		try {
			Long newMemberId = registerService.regist(regReq);
			URI uri = URI.create("/api/members/" + newMemberId);
			return ResponseEntity.created(uri).build();
		} catch (DuplicateMemberException dupEx) {
			return ResponseEntity.status(HttpStatus.CONFLICT).build();
		}
	}

	@PostMapping("/api/members2")
	public void newMember2(
			@RequestBody RegisterRequest regReq, 
			Errors errors, 
			HttpServletResponse response) throws IOException {
		try {
			new RegisterRequestValidator().validate(regReq, errors);
			if (errors.hasErrors()) {
				response.sendError(HttpServletResponse.SC_BAD_REQUEST);
				return;
			}
			Long newMemberId = registerService.regist(regReq);
			response.setHeader("Location", "/api/members/" + newMemberId);
			response.setStatus(HttpServletResponse.SC_CREATED);
		} catch (DuplicateMemberException dupEx) {
			response.sendError(HttpServletResponse.SC_CONFLICT);
		}
	}

	public void setMemberDao(MemberDao memberDao) {
		this.memberDao = memberDao;
	}

	public void setRegisterService(MemberRegisterService registerService) {
		this.registerService = registerService;
	}
}

 

 

@RestController 어노테이션을 붙인 경우

스프링 MVC는 요청 매핑 어노테이션을 붙인 메서드가

리턴한 객체를 알맞은 형식으로 변환해서

응답 데이터로 전송한다. 

 

 

이때 클래스패스에

Jackson이 존재한다면 JSON 형식의 문자열로 변환해서 응답한다. 

 

 

위 코드에서 members() 메서드는

리턴 타입이 List<Member>인데

이 경우 해당 List 객체를 JSON 형식의 배열로 변환해서 응답한다.

 

 

(1) @JsonIgnore를 이용한 제외 처리

> 비밀번호와 같은 민감한 데이터는 응답 결과에 포함시키면 안된다

Jackson이 제공하는 @JsonIgnore 어노테이션을 사용하면

이를 간단히 처리할 수 있음

 

@JsonIgnore
private String password;
private String name;
private String email;

 

 

(2) 날짜 형식 변환 처리 : @JsonFormat 사용

 

 

(3) 날짜 형식 변환 처리 : 기본 적용 설정

> 스프링 MVC는 자바 객체를 HTTP 응답으로 변환할 때

HttpMessageConverter라는 것을 사용함

 

 

(4) @RequestBody로 JSON 요청처리

> JSON 형식의 요청 데이터를

자바 객체로 변환하는 기능에 대해 알아보기. 

 

POST 방식이나 PUT방식을 사용하면

JSON 형식의 데이터를 요청 데이터로 전송할 수 있음

 

JSON 형식으로 전송된 요청 데이터를

커맨드 객체로 전달하는 방법은

커맨드 객체에 @RequestBody 어노테이션을 붙이면 된다.

 

그러면 JSON 형식의 문자열을

해당 객체로 변환한다. 

 

@PostMapping("/api/members")
	public void newMember(
			@RequestBody RegisterRequest regReq /*,
			HttpServletResponse response) throws IOException {
		try {
			Long newMemberId = registerService.regist(regReq);
			URI uri = URI.create("/api/members/" + newMemberId);
			return ResponseEntity.created(uri).build();
		} catch (DuplicateMemberException dupEx) {
			return ResponseEntity.status(HttpStatus.CONFLICT).build();
		}
	}

 

 

스프링 MVC가 JSON 형식으로 전송된 데이터를

올바르게 처리하려면

요청 컨텐츠 타입이 application/json이어야함

 

(4.1) JSON 데이터의 날짜 형식 다루기

deserializerByType()는 JSON 데이터를 LocalDateTime 타입으로

변환할 때 사용할 패턴을 지정하고

simpleDateFormat()은 Date 타입으로 변환할 때 사용할 패턴을 지정함

 

(4.2) 요청 객체 검증하기

JSON 형식으로 전송한 데이터를 변환한 객체도

동일한 방식으로 @Valid 어노테이션이나

별도 Validator를 이용해서 검증할 수 있음

 

@Valid 어노테이션을 사용한 경우

검증에 실패하면 400 상태 코드를 응답함.

 


 

5. ResponseEntity로 객체 리턴하고 응답 코드 지정하기

HttpServletResponse를 이용해서 404 응답을 하면

JSON 형식이 아닌 서버가 기본으로 제공하는 HTML을 응답 결과로 제공함

 

(5.1) ResponseEntity를 이용한 응답 데이터 처리

정상인 경우와 비정상인 경우 모두 JSON 응답을 전송하는 방법은

ResponseEntity를 사용하는 것임.

 

@PostMapping("/api/members")
	public ResponseEntity<Object> newMember(
			@RequestBody @Valid RegisterRequest regReq /*,
			Errors errors */) {
		/*
		if (errors.hasErrors()) {
			String errorCodes = errors.getAllErrors()
					.stream()
					.map(error -> error.getCodes()[0])
					.collect(Collectors.joining(","));
			return ResponseEntity
					.status(HttpStatus.BAD_REQUEST)
					.body(new ErrorResponse("errorCodes = " + errorCodes));
		}
		*/
		try {
			Long newMemberId = registerService.regist(regReq);
			URI uri = URI.create("/api/members/" + newMemberId);
			return ResponseEntity.created(uri).build();
		} catch (DuplicateMemberException dupEx) {
			return ResponseEntity.status(HttpStatus.CONFLICT).build();
		}
	}

 

 

스프링 MVC는 리턴 타입이 ResponseEntity이면

ResponseEntity의 Body로 지정한 객체를 사용해서

변환을 처리한다. 

 

member 객체를 JSON으로 변환함

member가 null이면 

ErrorResponse를 JSON으로 변환한다.

 

ResponseEntity의 status로 지정한 값을

응답 상태 코드로 사용함 (13~14행)

 

return ResponseEntity
    .status(HttpStatus.BAD_REQUEST)
    .body(new ErrorResponse("errorCodes = " + errorCodes));

 

 

ResponseEntity.status(상태코드).body(객체)

상태코드는 HttpStatus 열거 타입에 정의된 값을 이용하여 정의함

 

200(OK) 응답 코드와 몸체 데이터 생성 시 

ok메서드를 이용해서 생성할 수 있음

 

ResponseEntity.ok(member)

 

 

만약 몸체 내용이 없다면 다음과 같이 body를 지정하지 않고

build()로 바로 생성함

 

ResponseEntity.status(HttpStatus.NOT_FOUND).build()

 

 

몸체 내용이 없는 경우 status() 메서드 대신에

다음과 같이 관련 메서드를 사용해도 된다.

 

ResponseEntity.notFound().build()

 

noContent() : 204

badRequest() : 400

notFound() : 404

 

 

(5.2) @ExceptionHandler 적용 메서드에서 ResponseEntity로 응답하기

> 한 메서드에서 정상 응답과 에러 응답을

ResponseBody로 생성하면 

코드가 중복될 수 있음

 

@GetMapping("/api/members/{id}")
public ResponseEntity<Object> member(@PathVariable Long id){
	Member member = memberDao.selectById(id);
    if (member == null) {
    	return ResponseEntity
        	.status(HttpStatus.NOT_FOUND)
            .body(new ErrorResponse("no member"));
    )
    return ResponseEntity.ok(member);
}

 

 

member가 존재하지 않을 때

JSON 응답을 제공하기 위해 ResponseEntity를 사용했다..

 

그런데 회원이 존재하지 않을 때 404 상태 코드를 응답해야 하는 기능이 많다면

에러 응답을 위해 ResponseEntity를 생성하는 코드가 여러 곳에 중복됨

 

이럴 때 @ExceptionHandler 어노테이션을 적용한 메서드에서

에러 응답을 처리하도록 구현하면

중복을 없앨 수 있음

 

@GetMapping("/api/members/{id}")
public Member member(@PathVariable Long id) {
	Member memeber = memberDao.selectById(id);
    if (member == null) {
    	throw new MemberNotFoundException();
    }
    return member;
}

@ExceptionHandler(MemberNotFoundException.class)
public ResponseEntity<ErrorResponse> handleNoData() {
	return ResponseEntity
    	.status(HttpStatus.NOT_FOUND)
        .body(new ErrorResponse("no memeber"));
)

 

member() 메서드는 Member 자체를 리턴함.

회원데이터가 존재하면 Member 객체를 리턴하므로 

JSON 으로 변환한 결과를 응답함

 

 

회원데이터가 존재하지 않으면 MemberNotFOundException을 발생함

해당 익셉션이 발생하면

@ExceptionHandler 를 사용한

handleNoDate() 메서드가 에러를 처리함

 

 

404 상태 코드와 ErrorResponse 객체를 

몸체로 갖는 ResponseEntity를 리턴함

상태코드가 404이고 몸체가 JSON 형식인 응답을 전송한다.

 

 

@RestControllerAdvice 어노테이션을 이용하여

에러 처리 코드를 별도 클래스로 분리할 수 있음

 

 

@RestControllerAdvice는 @RestController와 동일하게 

응답을 JSON이나 XML과 같은 형식으로 변환한다는 차이

(@ControllerAdvic와의)

 

package controller;

import java.util.stream.Collectors;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import spring.MemberNotFoundException;

@RestControllerAdvice("controller")
public class ApiExceptionAdvice {

	@ExceptionHandler(MemberNotFoundException.class)
	public ResponseEntity<ErrorResponse> handleNoData() {
		return ResponseEntity
				.status(HttpStatus.NOT_FOUND)
				.body(new ErrorResponse("no member"));
	}

	@ExceptionHandler(MethodArgumentNotValidException.class)
	public ResponseEntity<ErrorResponse> handleBindException(MethodArgumentNotValidException ex) {
		String errorCodes = ex.getBindingResult().getAllErrors()
				.stream()
				.map(error -> error.getCodes()[0])
				.collect(Collectors.joining(","));
		return ResponseEntity
				.status(HttpStatus.BAD_REQUEST)
				.body(new ErrorResponse("errorCodes = " + errorCodes));
	}

}

 

 

(5.3) @Valid 에러 결과를 JSON으로 응답하기

 

@Valid 어노테이션을 붙인 커맨드 객체가

값 검증에 실패하면 400 상태 코드를 응답함.

 

단 문제는 HTML 응답을 전송함.

HTML 응답 대신에 JSON 형식 응답을 제공하려면

Errors 타입 파라미터를 추가하여

직접 에러 응답을 생성하면 된다. 

 

@PostMapping("/api/members")
public ResponseEntity<Object> newMember(
	@RequestBody @Valid RegisterRequest regReq,
    Errors errors) {
 	if (errors.hasErrors()) {
    	String errorCodes = errors.getAllErrors() // List<ObjectError>
        	.stream()
            .map(error -> error.getCodes()[0]) // error는 ObjectError
            .collect(Collectors.joining(","));
    return ResponseEntity
    	.status(HttpStatus.BAD_REQUEST)
        .body(new ErrorResponse("errorCodes="+errorCodes));
    }
    ... 생략
    
 }

 

 

hasErrors() 메서드를 이용해서

검증 에러가 존재하는지 확인함

 

검증 시 에러가 존재하면 getAllErrors() 메서드로

모든 에러 정보를 구하고

각 에러의 코드 값을 연결한 문자열을 생성해서 errorCodes 변수에 할당함

 

코드를 수정한 뒤, 검증에 실패하는 데이터를 전송하면

HTML 대신 JSON 응답이 온다.


<Chapter 17>

1. 프로필

실제 서비스 환경에서는

웹서버와 디비 서버가 서로 다른 장비에 설치된 경우가 많음

 

처음부터 개발 목적 설정과 실 서비스 목적의 설정을

구분해서 작성하는 것이 실수를 방지하는 방법이다.

 

이를 위한 스프링 기능이 프로필이다. 

프로필은 논리적인 이름으로서

설정 집합에 프로필을 지정할 수 있음

 

스프링 컨테이너는 설정 집합 중에서 지정한 이름을 사용하는 프로필을 선택하고

해당 프로필에 속한 설정을 이용하여 컨테이너를 초기화할 수 있음

 

예를 들어 개발 환경을 위한 DataSource 설정을 dev 프로필로

실 서비스 환경을 위한 DataSource 설정을 real 프로필로 지정한 뒤

dev 프로필을 사용하여 스프링 컨테이너를 초기화할 수 있음

그러면스프링은 dev 프로필에 정의된 빈을 사용하게 된다.

 

 

(1) @Configuration 설정에서 프로필 사용하기

@Configuration 어노테이션을 이용한 설정에서

프로필을 지정하려면 @Profile 어노테이션을 이용함

 

package config;

import org.apache.tomcat.jdbc.pool.DataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;

@Configuration
@Profile("dev")
public class DsDevConfig {

	@Bean(destroyMethod = "close")
	public DataSource dataSource() {
		DataSource ds = new DataSource();
		ds.setDriverClassName("com.mysql.jdbc.Driver");
		ds.setUrl("jdbc:mysql://localhost/spring5fs?characterEncoding=utf8");
		ds.setUsername("spring5");
		ds.setPassword("spring5");
		ds.setInitialSize(2);
		ds.setMaxActive(10);
		ds.setTestWhileIdle(true);
		ds.setMinEvictableIdleTimeMillis(60000 * 3);
		ds.setTimeBetweenEvictionRunsMillis(10 * 1000);
		return ds;
	}
}

 

 

@Profile은 dev를 값으로 갖는다.

스프링 컨테이너를 초기화할 때 dev 프로필을 활성화하려면

DsDevConfig 클래스를 설정으로 사용한다.

 

 

dev가 아닌 real 프로필을 활성화했을 때

사용할 설정 클래스는

@Profile 어노테이션의 값으로 real을 지정하면 된다.

 

package config;

import org.apache.tomcat.jdbc.pool.DataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;

@Configuration
@Profile("real")
public class DsRealConfig {

	@Bean(destroyMethod = "close")
	public DataSource dataSource() {
		DataSource ds = new DataSource();
		ds.setDriverClassName("com.mysql.jdbc.Driver");
		ds.setUrl("jdbc:mysql://realdb/spring5fs?characterEncoding=utf8");
		ds.setUsername("spring5");
		ds.setPassword("spring5");
		ds.setInitialSize(2);
		ds.setMaxActive(10);
		ds.setTestWhileIdle(true);
		ds.setMinEvictableIdleTimeMillis(60000 * 3);
		ds.setTimeBetweenEvictionRunsMillis(10 * 1000);
		return ds;
	}
}

 

두 클래스 모두

dataSource인 Datasource 타입의 빈을 설정하고 있음

 

두 DataSource 빈 중에서

어떤 빈을 사용할지는

활성화한 프로필에 따라 달라진다.

 

특정 프로필을 선택하려면

컨테이너를 초기화하기 전에

setActiveProfiles() 메서드를 사용하여

프로필을 선택해야 함.

 

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.getEnvironment().setActiveProfiles("dev");
context.register(MemberConfig.class, DsDevConfig.class, DsRealConfig.class);
context.refresh();

 

getEnvironment 메서드는 스프링 실행 환경을 설정하는데 사용되는

Environment를 리턴함.

 

이 Environment의 setActiveProfiles 메서드를 사용하여

사용할 프로필을 지정할 수 있다.

 

위 코드는

dev 프로필에 속한 설정이 사용된다는 의미임

 

 

프로필 사용 시 주의할 점

> 설정 정보를 전달하기 전에 어떤 프로필을 사용할지 지정해야 함

setActiveProfiles 메서드로 dev 프로필을 사용한다고 설정한 후

register()메서드로 설정 파일 목록을 지정함.

그런 뒤에 refresh 메서드를 실행하여 컨테이너를 초기화함

 

이 순서를 지키지 않고 

프로필 선택 전, 설정 정보를 먼저 지정하게 되면

프로필을 지정한 설정이 사용되지 않아서

익셉션이 발생하게 된다

 

 

만약, 두 개 이상의 프로필을 설정하고 싶다면

각 프로필 이름을 메서드에 파라미터로 전달하면 된다.

 

context.getEnvironment().setActiveProfiles("dev","mysql");

 

 

프로필을 선택하는 또 다른 방법은

spring.profiles.active 시스템 프로퍼티에 사용할

프로필을 지정하는 것이다. 

 

java -Dspring.profiles.active=dev main.Main

 

 

(2) @Configuration을 이용한 프로필 설정

> 중첩 클래스를 이용해서 프로필 설정을 한 곳으로 모을 수 있음

이때 주의할 점은 중첩 클래스는 static 이어야함.

 

 

(3) 다수 프로필 설정

> 스프링 설정은 두 개 이상의 프로필 이름을 가질 수 있음

@Configuration
@Profile("real,test")
public class DataSourceJndiConfig {
...

 

real 프로필을 사용할 때와

test 프로필을 사용할 때 모두 해당 설정을 사용함.

 

프로필 값 지정 시, 느낌표를 사용할 수도 있음

@Profile(!real)

> !real 값은 real 프로필이 활성화되지 않을 때 사용한다는 의미임.

즉, 특정 프로필이 사용되지 않을 때 기본으로 사용할 설정을 지정하는 용도임

 

 

(4) 어플리케이션에서 프로필 설정하기

> spring.profiles.active 시스템 프로퍼티나

환경변수를 사용해서 사용할 프로필을 선택할 수 있음

web.xml에서 spring.profiles.active 초기화 파라미터를 이용하여 프로필 선택이 가능함

 

<init-param>
	<param-name>spring.profiles.active</param-name>
    <param-value>dev</param-value>
</init-param>

 


2. 프로퍼티 파일을 이용한 프로퍼티 설정

> 스프링은 외부의 프로퍼티 파일을 이용하여

스프링 빈을 설정하는 방법을 제공함

 

db.properties 파일을 보면

이 파일의 프로퍼티 값을 자바 설정에서 사용할 수 있고

이를 통해 설정 일부를

외부 프로퍼티 파일을 사용하여 변경할 수 있다.

 

 

(1) @Configuration 어노테이션 이용 자바 설정에서의 프로퍼티 사용

자바 설정에서 프로퍼티 파일 사용하려면

다음 2가지를 설정해야 함

- PropertySourcePlaceholderConfigurer 빈 설정

- @Value 어노테이션으로 프로퍼티값 사용

 

 

PropertySourcePlaceholderConfigurerLocations() 메서드는

프로퍼티 파일 목록을 인자로 받음

이때 스프링의 Resource 타입을 이용하여 파일 경로를 전달함

 

 

ClassPathResource > 클래스 패스에 위치한 자원으로부터 데이터를 읽음

FileSystemResource > 파일 시스템에 위치한 자원으로부터 데이터를 읽음

 

 

이때 주의할 점은 PropertySourcePlaceholderConfigurer 타입 빈을 설정하는 메서드가

정적(static) 메서드라는 점이다. 

정적 메서드로 지정하지 않으면 원하는 방식으로 동작하지 않음

 

 

PropertySourcePlaceholderConfigurer 타입의 빈은

setLocations() 메서드로 전달받은

프로퍼티 파일 목록 정보를 읽어와 필요할 때 사용함

이를 위한 것이 @Value 어노테이션임.

 

package config;

import org.apache.tomcat.jdbc.pool.DataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class DsConfigWithProp {
    @Value("${db.driver}")
    private String driver;
    @Value("${db.url}")
    private String jdbcUrl;
    @Value("${db.user}")
    private String user;
    @Value("${db.password}")
    private String password;

	@Bean(destroyMethod = "close")
	public DataSource dataSource() {
		DataSource ds = new DataSource();
		ds.setDriverClassName(driver);
		ds.setUrl(jdbcUrl);
		ds.setUsername(user);
		ds.setPassword(password);
		ds.setInitialSize(2);
		ds.setMaxActive(10);
		ds.setTestWhileIdle(true);
		ds.setMinEvictableIdleTimeMillis(60000 * 3);
		ds.setTimeBetweenEvictionRunsMillis(10 * 1000);
		return ds;
	}

}

 

 

@Value 어노테이션이 ${구분자} 형식의 플레이스 홀더를 가지고 있음

이 경우 PropertySourcePlaceholderConfigurer는

플레이스 홀더의 값을 일치하는 프로퍼티 값으로 치환함

 

위 코드의 경우

${db.drive} 플레이스 홀더를

db.properties 파일 내 정의되어 있는

db.driver 프로퍼티 값으로 치환한다.

 

 

따라서 실제 빈을 생성하는 메서드(dataSource())는

@Value 어노테이션이 붙은 필드를 통해

해당 프로퍼티의 값을 사용할 수 있음

 

(2) 빈 클래스에서 사용하기

빈으로 사용할 클래스에서도

@Value 어노테이션을 붙일 수 있음

플레이스홀더에 해당하는 프로퍼티를 

필드에 할당하게 된다. 

 

set메서드에도 적용이 가능함