728x90
반응형

[Spring Boot] Spring Boot @RequestParam 완벽 가이드: HTTP 요청 파라미터 처리하기

 

[Spring Boot] Spring Boot @RequestParam 완벽 가이드: HTTP 요청 파라미터 처리하기

[Spring Boot] Spring Boot Handler Method의 WebRequest 파라미터 사용법과 실무 예제 [Spring Boot] Spring Boot Handler Method의 WebRequest 파라미터 사용법과 실무 예제[Spring Boot] @RequestMapping Class Level 매핑 - Spring Boot Cont

crushed-taro.tistory.com

1. Handler Method

1. @ModelAttrribute

index.html에서 GET 방식의 /first/search요청을 전달한다.

<h3>3. @ModelAttribute를 이용하여 파라미터 전달 받기</h3>
<button onclick="location.href='/first/search'">@ModelAttribute 이용하기</button>

 

Controller 클래스의 핸들러 메소드를 통해 파라미터 전달 테스트를 위해 값을 입력할 수 있는 뷰를 응답한다.

@Controller
@RequestMapping("/first/*")
public class FirstController {

	@GetMapping("search")
	public void search() {}

	...생략

}

 

resources/templates/first 의 하위에 search.html파일을 생성한다.

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>search</title>
</head>
<body>
    <h1>@ModelAttribute를 이용한 커맨드 객체로 파라미터 값 전달받기</h1>

    <form action="search" method="post">
        검색할 메뉴 이름 : <input type="text" name="name"><br>
        검색할 메뉴 가격 : <input type="number" name="price"><br>
        검색할 메뉴의 카테고리 :
        <select name="categoryCode">
            <option value="1">식사</option>
            <option value="2">음료</option>
            <option value="3">디저트</option>
        </select><br>
        검색할 판매 상태 : <input type="text" name="orderableStatus"><br>
        <input type="submit" value="검색하기">
    </form>
</body>
</html>

 

해당 화면에서 사용자 입력 양식에 값을 입력하고 submit 을 누르면 POST 방식의 /first/search요청이 발생한다.

 

DTO 같은 모델을 커맨드 객체로 전달 받는 테스트를 위해서 MenuDTO를 만든다.

DTO를 작성할 때 커맨드 객체로 이용하기 위해서는 form의 name값과 필드명을 일치하게 만들어야 한다.

또한 커맨드 객체는 기본생성자를 이용하여 인스턴스를 만들기 때문에 기본생성자가 반드시 필요하다.

요청 파라미터의 name과 일치하는 필드의 setter를 이용하기 때문에 네이밍 룰에 맞는 setter메소드가 작성되어야 한다.

public class MenuDTO {
		
	private String name;
	private int price;
	private int categoryCode;
	private String orderableStatus;
	
	public MenuDTO() {}

	public MenuDTO(String name, int price, int categoryCode, String orderableStatus) {
		super();
		this.name = name;
		this.price = price;
		this.categoryCode = categoryCode;
		this.orderableStatus = orderableStatus;
	}
	
	public String getName() {
		return name;
	}

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

	public int getPrice() {
		return price;
	}

	public void setPrice(int price) {
		this.price = price;
	}

	public int getCategoryCode() {
		return categoryCode;
	}

	public void setCategoryCode(int categoryCode) {
		this.categoryCode = categoryCode;
	}

	public String getOrderableStatus() {
		return orderableStatus;
	}

	public void setOrderableStatus(String orderableStatus) {
		this.orderableStatus = orderableStatus;
	}

	@Override
	public String toString() {
		return "MenuDTO [name=" + name + ", price=" + price + ", categoryCode=" + categoryCode 
				+ ", orderableStatus=" + orderableStatus + "]";
	}
	
}

 

발생하는 요청을 매핑할 controller의 handler method이다.

 

@ModelAttribute의 경우 커맨드 객체를 생성하여 매개변수로 전달해 준 뒤 해당 인스턴스를 model에 담는다.

 

화면에서 출력해보면 모델에 담겨진 값을 확인할 수 있다. 경우에 따라 폼에서 입력한 값을 다음 화면으로 바로 전달해야 하는 경우가 발생하는데 이 때 유용하게 사용할 수 있다.

 

@ModelAttribute("모델에담을key값")을 지정할 수 있으며, 지정하지 않으면 타입의 앞글자를 소문자로 한 네이밍 규칙을 따른다.

 

해당 어노테이션은 생략이 가능하지만 명시적으로 작성하는 것이 좋다.

 

@PostMapping("search")
public String searchMenu(@ModelAttribute("menu") MenuDTO menu) {
		
	System.out.println(menu);
		
	return "first/searchResult";
}

 

응답 화면을 구성하기 위해 resources/templates/first 의 하위에 searchResult.html파일을 생성한다.

 

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>searchResult</title>
</head>
<body>
    <h1>Model에 담긴 커맨드 객체의 정보 출력</h1>
    <h3 th:text="|메뉴의 이름 : ${ menu.name }|"></h3>
    <h3 th:text="|메뉴의 가격 : ${ menu.price }|"></h3>
    <h3 th:text="|메뉴의 카테고리 : ${ menu.categoryCode }|"></h3>
    <h3 th:text="|메뉴의 판매상태 : ${ menu.orderableStatus }|"></h3>
</body>
</html>

 

클라이언트에서 입력 된 값이 컨트롤러 핸들러 메서드의 MenuDTO타입의 객체에 잘 담겨서 전달 되었음을 응답 화면을 통해 확인할 수 있다.

728x90
반응형
728x90
반응형

[Spring Boot] Spring Boot Handler Method의 WebRequest 파라미터 사용법과 실무 예제

 

[Spring Boot] Spring Boot Handler Method의 WebRequest 파라미터 사용법과 실무 예제

[Spring Boot] @RequestMapping Class Level 매핑 - Spring Boot Controller 구조화 전략 [Spring Boot] @RequestMapping Class Level 매핑 - Spring Boot Controller 구조화 전략[Spring Boot] @RequestMapping Method 매핑 완벽 가이드 (GET, POST, PU

crushed-taro.tistory.com

1. Handler Method

1. @RequestParam

index.html에서 GET 방식의 /first/modify요청을 전달한다.

<h3>2. @RequestParam 이용하여 파라미터 전달 받기</h3>
<button onclick="location.href='/first/modify'">@RequestParam 이용하기</button>

 

Controller 클래스의 핸들러 메소드를 통해 파라미터 전달 테스트를 위해 값을 입력할 수 있는 뷰를 응답한다.

@Controller
@RequestMapping("/first/*")
public class FirstController {
	
	@GetMapping("modify")
	public void modify() {}

	...생략
}

 

resources/templates/first의 하위에 modify.html파일을 생성한다.

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>modify</title>
</head>
<body>
    <h1>@RequestParam을 이용하여 전달된 파리미터 꺼내기</h1>

    <h3>메뉴 수정하기</h3>
    <form action="modify" method="post">
        수정할 메뉴의 이름 : <input type="text" name="modifyName"><br>
        수정할 메뉴의 가격 : <input type="number" name="modifyPrice"><br>
        <button type="submit">수정하기</button>
    </form>

</body>
</html>

해당 화면에서 사용자 입력 양식에 값을 입력하고 submit을 누르면 POST방식의 /first/modify요청이 발생한다.

 

발생하는 요청을 매핑할 controller의 handler method이다.

@RequestParam 은 요청 파라미터를 매핑하여 호출 시 값을 넣어주는 어노테이션으로 매개 변수 앞에 작성한다.

form의 name 속성값과 매개변수의 이름이 다른 경우 @RequestParam("name")을 설정하면 된다.

또한 어노테이션은 생략 가능하지만 명시적으로 작성하는 것이 의미 파악에 쉽다.

전달하는 form의 name속성이 일치하는 것이 없는 경우 400에러가 발생하는데 이는 required 속성의 기본 값이 true이기 때문이다. required 속성을 false로 하게 되면 해당 name값이 존재하지 않아도 null로 처리하며 에러가 발생하지 않는다.

값이 넘어오지 않게 되면 "" 와 같이 빈 문자열이 넘어오게 되는데, 이 때 parsing 관련 에러가 발생할 수 있다. 값이 넘어오지 않는 경우 defaultValue를 이용하게 되면 기본값으로 사용할 수 있다.

@PostMapping("modify")
public String modifyMenuPrice(Model model, @RequestParam(required = false) String modifyName,
		@RequestParam(defaultValue = "0") int modifyPrice) {
		
	String message = modifyName + "메뉴의 가격을 " + modifyPrice + "로 가격을 변경하였습니다.";
	System.out.println(message);
	
	model.addAttribute("message", message);
		
	return "first/messagePrinter";
}

 

클라이언트에서 입력 된 값이 컨트롤러 핸들러 메서드의 @RequestParam이 선언 된 변수에 잘 담겨서 전달 되었음을 응답 화면을 통해 확인할 수 있다.

 

resources/templates/first의 하위에 modify.html파일에 메뉴 수정하기2를 추가하여 수정한다.
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>modify</title>
</head>
<body>
		...생략

		<h3>메뉴 수정하기2</h3>
    <form action="modifyAll" method="post">
        수정할 메뉴의 이름 : <input type="text" name="modifyName2"><br>
        수정할 메뉴의 가격 : <input type="number" name="modifyPrice2"><br>
        <button type="submit">수정하기</button>
    </form>

</body>
</html>

해당 화면에서 사용자 입력 양식에 값을 입력하고 submit 을 누르면 POST 방식의 /first/modifyAll 요청이 발생한다.

 

발생하는 요청을 매핑할 controller의 handler method이다.

파라미터가 여러 개 인 경우 맵으로 한 번에 처리할 수 도 있다. 이 때 맵의 키는 form의 name 속성값이 된다.

@PostMapping("modifyAll")
public String modifyMenu(Model model, @RequestParam Map<String, String> parameters) {
		
	String modifyMenu = parameters.get("modifyName2");
	int modifyPrice = Integer.parseInt(parameters.get("modifyPrice2"));
		
	String message = "메뉴의 이름을 " + modifyMenu + "(으)로, 가격을 " + modifyPrice + "원 으로 변경하였습니다.";
	System.out.println(message);
		
	model.addAttribute("message", message);
		
	return "first/messagePrinter";
}

 

클라이언트에서 입력 된 값이 컨트롤러 핸들러 메서드의 @RequestParam이 선언 된 Map타입의 변수에 잘 담겨서 전달 되었음을 응답 화면을 통해 확인할 수 있다.

728x90
반응형
728x90
반응형

[Spring Boot] @RequestMapping Class Level 매핑 - Spring Boot Controller 구조화 전략

 

[Spring Boot] @RequestMapping Class Level 매핑 - Spring Boot Controller 구조화 전략

[Spring Boot] @RequestMapping Method 매핑 완벽 가이드 (GET, POST, PUT, DELETE) [Spring Boot] @RequestMapping Method 매핑 완벽 가이드 (GET, POST, PUT, DELETE)[Spring Boot] Spring Boot 개요 - 초보자를 위한 핵심 개념 정리 [Spring B

crushed-taro.tistory.com

1. Handler Method

  • 핸들러 메서드는 사용자 요청에 대한 응답을 생성하는 메서드이다. 핸들러 메소드에 파라미터로 특정 몇 가지 타입을 선언하게 되면 핸들러 메소드 호출 시 인자로 값을 전달해준다. 이번 차시에서는 핸들러 메서드의 다양한 파라미터 전달 방법에 대해 알아 본다.

 

1. WebRequest

index.html에GET 방식의 /first/regist 요청을 전달한다.

<h3>1. WebRequest로 요청 파라미터 전달 받기</h3>
<button onclick="location.href='/first/regist'">파라미터 전달하기</button>

 

Controller 클래스의 핸들러 메소드를 통해 파라미터 전달 테스트를 위해 값을 입력할 수 있는 뷰를 응답한다.

@Controller
@RequestMapping("/first/*")
public class FirstController {

	/* 컨트롤러 핸들러 메서드의 반환 값을 void로 설정하면 요청 주소가 view의 이름이 된다. 
	 * => /first/regist 요청이 들어오면 /first/regist 뷰를 응답한다.
	 * */
	@GetMapping("regist")
	public void regist() {}

}

 

resources/templates/first의 하위에 regist.html파일을 생성한다.
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>regist</title>
</head>
<body>
    <h1>WebRequest로 파라미터 값 전달받기</h1>
    <h3>신규 메뉴 등록하기</h3>
    <form action="regist" method="post">
        등록할 메뉴의 이름 : <input type="text" name="name"><br>
        등록할 메뉴의 가격 : <input type="number" name="price"><br>
        등록할 메뉴의 카테고리 :
        <select name="categoryCode">
            <option value="1">식사</option>
            <option value="2">음료</option>
            <option value="3">디저트</option>
        </select><br>
        <button type="submit">등록하기</button>
    </form>
</body>
</html>

 

해당 화면에서 사용자 입력 양식에 값을 입력하submit을 누르면 POST 방식의 /first/regist요청이 발생한다.

 

발생하는 요청을 매핑할 controller의 handler method이다.

이 때 파라미터 선언부에 WebRequest 타입은 선언하면 해당 메소드 호출 시 인자로 값을 전달해 준다.

핸들러 메소드 매개변수로 HttpServletRequest, HttpServletResponse도 사용 가능하다.

상위 타입인 ServletRequest, ServletResponse도 사용 가능하다.

WebRequest는 HttpServletRequest의 요청 정보를 거의 대부분 그대로 가지고 있는 API로 Servlet에 종속적이지 않다.

HttpServletRequest는 Servlet API의 일부이고, WebRequest는 Spring의 일부이기 때문에 Spring 기반의 프로젝트에서 더 자주 사용 된다.

 

@PostMapping("regist")
public String registMenu(Model model, WebRequest request) {
	
	/* WebRequest 객체의 getParameter 등의 메서드를 통해 클라이언트로부터 전달 된 파라미터를 가져올 수 있다. */	
	String name = request.getParameter("name");
	int price = Integer.parseInt(request.getParameter("price"));
	int categoryCode = Integer.parseInt(request.getParameter("categoryCode"));
	
	/* 클라이언트로부터 전달 받은 값을 통해 응답할 화면의 메세지를 생성한다. */
	String message 
		= name + "을(를) 신규 메뉴 목록의 " + categoryCode + "번 카테고리에 " + price + "원으로 등록 하셨습니다!";
	System.out.println(message);
		
	model.addAttribute("message", message);
		
	return "first/messagePrinter";
}

 

클라이언트에서 입력 된 값이 컨트롤러 핸들러 메서드의 WebRequest객체에 잘 담겨서 전달 되었음을 응답 화면을 통해 확인할 수 있다.

728x90
반응형
728x90
반응형

[Spring Boot] @RequestMapping Method 매핑 완벽 가이드 (GET, POST, PUT, DELETE)

 

[Spring Boot] @RequestMapping Method 매핑 완벽 가이드 (GET, POST, PUT, DELETE)

[Spring Boot] Spring Boot 개요 - 초보자를 위한 핵심 개념 정리 [Spring Boot] Spring Boot 개요 - 초보자를 위한 핵심 개념 정리1. 스프링 부트(Spring Boot) 개요1. 스프링 부트란?필요한 환경 설정을 최소화하고

crushed-taro.tistory.com

1. RequestMapping

1. Class Mapping

1. Class 레벨 매핑

<h3>GET : /menu/delete</h3>
<button onclick="location.href='/menu/delete'">GET 메뉴 삭제 요청</button>

<h3>POST : /menu/delete</h3>
<form action="/menu/delete" method="post">
    <button type="submit">POST 메뉴 삭제 요청</button>
</form>

클래스 레벨@RequestMapping어노테이션 사용이 가능하다. 클래스 레벨에 URL을 공통 부분을 이용해 설정하고 나면 매번 핸들러 메소드에 URL의 중복되는 내용을 작성하지 않아도 된다. 이 때 와일드카드를 이용해서 조금 더 포괄적인 URL 패턴을 설정할 수 있다.

@Controller
@RequestMapping("/order/*")
public class ClassMappingTestController {

@GetMapping("/regist")
	public String registOrder(Model model) {
		
		model.addAttribute("message", "GET 방식의 주문 등록용 핸들러 메소드 호출함...");
		
		return "mappingResult";
	}
}

index.html 페이지에서 알맞은URL과 Method를 이용해 요청을 전달하여 테스트 해본다.

<h3>GET : /order/regist</h3>
<button onclick="location.href='/order/regist'">GET 주문 등록 요청</button>

클래스 레벨과 메소드 레벨URL이 고려되어 요청이 잘 매핑 되고 응답이 돌아온다.

 

1. 여려 개의 패턴 매핑

하나의 컨트롤러 핸들러 메소드에 여러 패턴의 요청도 매핑할 수 있다.

/* value 속성에 중괄호를 이용해 매핑할 URL을 나열한다. */
@RequestMapping(value= {"modify", "delete"}, method = RequestMethod.POST)
public String modifyAndDelete(Model model) {
		
	model.addAttribute("message", "POST 방식의 주문 정보 수정과 주문 정보 삭제 공통 처리용 핸들러 메소드 호출함...");
		
	return "mappingResult";
}

index.html 페이지에서 두 가지 URL과 알맞은 Method를 이용해 요청을 전달하여 테스트 해본다.

 

<h3>POST : /order/modify</h3>
<form action="/order/modify" method="post">
    <button type="submit">POST 주문 수정 요청</button>
</form>

<h3>POST : /order/delete</h3>
<form action="/order/delete" method="post">
    <button type="submit">POST 주문 삭제 요청</button>
</form>

 

3. path variable

@PathVariable 어노테이션을 이용해 요청 path로부터 변수를 받아올 수 있다. path variable로 전달되는 {변수명} 값은 반드시 매개변수명과 동일해야 한다. 만약 동일하지 않으면 @PathVariable("이름") 을 설정해주어야 한다. 이는 REST형 웹 서비스를 설계할 때 유용하게 사용 된다.

@GetMapping("/detail/{orderNo}")
public String selectOrderDetail(Model model, @PathVariable("orderNo") int orderNo) {
		
	model.addAttribute("message", orderNo + "번 주문 상세 내용 조회용 핸들러 메소드 호출함...");
	
	return "mappingResult";
}

index.html 페이지에서 알맞URL과 Method를 이용해 요청을 전달하여 테스트 해본다.

 

 <h3>GET : /detail/{orderNo}</h3>
<button onclick="location.href='/order/detail/3'">GET 주문 상세보기 요청</button>

 

만약 parsing 불가능한 값이 전달되면 400 에러가 발생한다.

localhost:8080/order/detail/three 라는 요청의 결과는 아래와 같다.

 

또는 PathVariable이 없으면 해당 핸들러 메소드를 찾지 못한다.

localhost:8080/order/detail/ 의 결과는 아래와 같다.

 

4. 그 외의 다른 요청

@RequestMapping 어노테이션에 아무런 URL을 설정하지 않으면 요청 처리에 대한 핸들러 메소드가 준비되지 않았을 때 해당 메소드를 호출한다.

 

@RequestMapping
public String otherRequest(Model model) {
		
	model.addAttribute("message", "order 요청이긴 하지만 다른 기능은 아직 준비되지 않음...");
		
	return "mappingResult";
}
728x90
반응형
728x90
반응형

[Spring Boot] Spring Boot 개요 - 초보자를 위한 핵심 개념 정리

 

[Spring Boot] Spring Boot 개요 - 초보자를 위한 핵심 개념 정리

1. 스프링 부트(Spring Boot) 개요1. 스프링 부트란?필요한 환경 설정을 최소화하고 개발자가 비즈니스 로직에 집중할 수 있도록 도와줘 생산성을 크게 향상시킬 수 있도록 스프링의 단점을 보완하

crushed-taro.tistory.com

1. @RequestMapping

@RequestMapping은 Spring Web MVC에서 요청(Request)을 처리하는 데 사용되는 어노테이션이며, 클래스 레벨이나 메소드 레벨에서 사용 가능하다. 이를 통해 어떤 URL이 어떤 메소드에서 처리되는지, 어떤 HTTP Method를 처리할지 등을 정할 수 있다.
  • DispatcherServlet은 웹 요청을 받으면 @Controller가 달린 컨트롤러 클래스에 처리를 위임한다. 그 과정은 컨트롤러 클래스의 핸들러 메서드에 선언된 다양한 @RequestMapping 설정 내용에 따른다.

 

1. Method Mapping

클래스를 생성하고 @Controller 어노테이션을 설정 한 뒤 요청 매핑 테스트를 진행한다.

@Controller 어노테이션이 붙은 클래스는 웹 요청을 처리하는 컨트롤러임을 나타내며, Spring MVC에서는 @Controller 어노테이션이 붙은 클래스를 자동으로 스캔해서 Bean으로 등록한다. 이후 요청이 들어오면 @RequestMapping 어노테이션을 이용하여 어떤 메소드가 요청을 처리할지 지정한다.

 

1. Method 방식 미지정

@Controller 어노테이션이 설정 된 클래스에 @RequestMapping 어노테이션을 설정한 메소드를 정의한다. 그리고  @RequestMapping에는 요청 URL 값을 설정한다.

 

/* 요청 URL 설정 */
@RequestMapping("/menu/regist")
public String registMenu(Model model) {
	
	/* Model 객체에 addAttribute 메서드를 이용해 
	 * key, value를 추가하면 추후 view에서 사용할 수 있다.
   * chap03-view-resolver에서 다시 다룬다. */	
	model.addAttribute("message", "신규 메뉴 등록용 핸들러 메소드 호출함...");
	
	/* 반환 하고자 하는 view의 경로를 포함한 이름을 작성한다. 
   * resources/templates 하위부터의 경로를 작성한다.
   * chap03-view-resolver에서 다시 다룬다. */	
	return "mappingResult";
}

 

요청에 대해 반환하고자 하는 view를 생성하고 model 객체에 추가했던 message를 화면에 출력하는 thymeleaf 코드를 작성한다. thymeleaf는 뷰 템플릿의 한 종류인데 자세한 문법은 추후 다룬다. 여기에서는 th:text="${key}"를 통해 값을 화면에 출력하도록 한다.

 

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>result</title>
</head>
<body>
    <h3 th:text="${ message }"></h3>
</body>
</html>

 

index.html 페이지에서GET 방식과 POST 방식의 요청을 둘 다 전달하여 테스트 해본다.

 

<h3>GET : /menu/regist</h3>
<button onclick="location.href='/menu/regist'">GET 메뉴 등록 요청</button>

<h3>POST : /menu/regist</h3>
<form action="/menu/regist" method="post">
	<button type="submit">POST 메뉴 등록 요청</button>
</form>

 

GET 방식과 POST 방식 모두 응답을 확인할 수 있다. 즉 RequestMapping 설정에 method 방식을 지정하지 않으면 get/post 요청을 다 처리한다.

 

2. Method 방식 지정

이번에는 @RequestMapping 어노테이션에 value 속성과 method 속성을 지정해 본다.  method 속성에는 RequestMethod.GET이라고 method 방식을 제한해서 기재한다.

 

/* 요청 URL을 value 속성에 요청 method를 method 속성에 설정 */
@RequestMapping(value = "/menu/modify", method = RequestMethod.GET)
public String modifyMenu(Model model) {
		
	model.addAttribute("message", "GET 방식의 메뉴 수정용 핸드러 메소드 호출함...");
		
	return "mappingResult";
}

 

index.html 페이지에GET 방식과 POST 방식의 요청을 둘 다 전달하여 테스트 해본다.

 

<h3>GET : /menu/modify</h3>
<button onclick="location.href='/menu/modify'">GET 메뉴 수정 요청</button>

<h3>POST : /menu/modify</h3>
<form action="/menu/modify" method="post">
  <button type="submit">POST 메뉴 수정 요청</button>
</form>

 

GET 방식은 응답이 오지만 POST 방식은 에러가 발생하여 스프링 부트 프로젝트의 기본 에러 페이지가 응답된다. 405 에러는 허용 되지 않는 메소드를 요청했을 때 발생하며 POST방식은 제공 되지 않으므로 에러가 발생하는 것이다.
 

3. 요청 메소드 전용 어노테이션(since 4.3)

요청 메소드 어노테이션
GET @GetMapping
POST @PostMapping
PUT @PutMapping
DELETE @DeleteMapping
PATCH @PatchMapping

 

이 어노테이션들은 @RequestMapping 어노테이션에 method 속성을 사용하여 요청 방법을 지정하는 것과 같다. 각 어노테이션은 해당하는 요청 메소드에 대해서만 처리할 수 있도록 제한하는 역할을 한다.

 

@GetMapping("/menu/delete")
public String getDeleteMenu(Model model) {
		
	model.addAttribute("message", "GET 방식의 삭제용 핸들러 메소드 호출함...");
		
	return "mappingResult";
}
	
@PostMapping("/menu/delete")
public String postDeleteMenu(Model model) {
		
	model.addAttribute("message", "POST 방식의 삭제용 핸들러 메소드 호출함...");
		
	return "mappingResult";
}

 

index.html 페이지에서 GET 방식과 POST 방식의 요청을 둘 다 전달하여 테스트 해본다.

GET 방식과 POST 방식 모두 각각의 핸들러 메소드와 잘 매핑 되어 응답이 돌아오는 것을 확인할 수 있다.

728x90
반응형
728x90
반응형

1. 스프링 부트(Spring Boot) 개요

1. 스프링 부트란?

  • 필요한 환경 설정을 최소화하고 개발자가 비즈니스 로직에 집중할 수 있도록 도와줘 생산성을 크게 향상시킬 수 있도록 스프링의 단점을 보완하여 만든 프로젝트이다.
  • 스프링 부트는 틀림없이 자바에서 REST 기반 마이크로서비스 웹 애플리케이션을 개발하는 가장 빠른 방법 중 하나로 도커 컨테이너 배포 및 빠른 프로토타이핑에도 매우 적합하다.
  • 간혹 스프링부트를 스프링 프레임워크와 전혀 다른 것으로 오해하지만 스프링부트는 스프링프레임워크라는 큰 틀 속에 속하는 도구일 뿐이다.

 

Spring Boot를 사용하면 "그냥 실행할 수 있는" 독립 실행형 production-grade Spring 기반 애플리케이션을 쉽게 생성할 수 있다.우리는 당신이 최소한의 고민으로 시작할 수 있도록 Spring 플랫폼과 third-party libraries에 대한 견해를 가지고 있다. 대부분의 Spring Boot 애플리케이션은 최소한의 Spring 구성으로 동작시킬 수 있다.
출처 : https://spring.io/projects/spring-boot
 

Spring Boot

 

spring.io

 

2. 스프링 부트 역사

  • 2012년 10월 Mike Youngstrom는 Spring 프레임워크에서 컨테이너 없는 웹 애플리케이션 아키텍처에 대한 지원을 요청하는 기능 요청했다. main 메소드에서 부트스트랩된 스프링 컨테이너 내에서 웹 컨테이너 서비스를 구성하는 것에 대해 말했는데 다음은 '이슈' 기반의 프로젝트 관리 도구 jira 요청에서 발췌한 내용이다.
Spring 컴포넌트와 설정 모델을 위에서 아래로 활용하는 도구와 참조 아키텍처를 제공한다면 Spring의 웹 애플리케이션 아키텍처는 상당히 단순화될 수 있다고 생각한다. 간단한 main() 메소드에서 부트스트랩된 Spring Container 내에 이러한 공통 웹 컨테이너 서비스의 구성을 포함하고 통합한다.
  • Mike Youngstrom의 요청으로 인해 2013년 초에 시작되는 스프링 부트 프로젝트 개발이 시작되었고 2014년 4월에 스프링 부트 1.0.0이 릴리즈 되었다. 그 이후로 많은 스프링 부트 마이너 버전이 나왔다.
  • https://github.com/spring-projects/spring-boot/wiki
 

Home

Spring Boot helps you to create Spring-powered, production-grade applications and services with absolute minimum fuss. - spring-projects/spring-boot

github.com

 

3. 스프링 부트의 특징

  • 임베디드 톰캣(Embed Tomcat), 제티, 언더토우를 사용하여 독립 실행이 가능한 스프링 애플리케이션 개발
  • 통합 스타터를 제공하여 메이븐/그레이들 구성 간소화
  • 스타터를 통한 자동화된 스프링 설정 제공
  • 번거로운 XML 설정을 요구하지 않음
  • JAR 를 사용하여 자바 옵션만으로 배포 가능
  • 애플리케이션의 모니터링과 관리를 위한 스프링 액츄에이터(Spring Actuator) 제공

 

4. 스프링 부트의 사용 이유

1. 스프링 부트의 장점

  • 각각의 의존성 버젼을 쉽게 올리는 것이 가능하다. (수동으로 설정하던 기존의 방식에 비해 안정된 버전 제공을 보장받을 수 있음)
  • 간단한 어노테이션/프로퍼티 설정으로 원하는 기능을 빠르게 적용할 수 있다.
  • 별도의 외장 톰캣을 설치할 필요가 없고 톰캣 버젼도 편리하게 관리할 수 있다.
  • 특정 라이브러리에 버그가 있더라도 이후에 스프링팀에서 버그를 수정하면 수정된 버전을 받기에 편리하다.

 

2. 스프링 부트의 단점

  • 설정을 커스터마이징 해야 하는 경우 기존 스프링프레임워크를 사용하는 것과 같은 어려움을 겪을 수 있다.
  • 설정을 변경하고 싶은 경우 정확한 동작 원리와 내부 코드를 살펴봐야 하는 불편함이 있다.

 

5. 스프링 부트의 핵심 요소

1. 스프링 부트의 핵심 요소의 종류

요소 기능
스타터(Starter) 스프링의 특정 모듈을 사용할 수 있도록 관련된 라이브러리 의존성을 해결
자동설정(Autoconfiguration) Starter로 추가한 모듈을 사용할 수 있도록 빈 설정을 자동으로 관리
액추에이터(Actuator) 스프링부트로 개발된 시스템을 모니터링할 수 있는 기능들을 제공

 

6. 스타터(Starter)

1. 스타터란?

  • 의존성과 설정을 자동화 해주는 모듈을 뜻하며 필요한 라이브러리들을 관련있는 것들을 묶어서 제공한다.
  • 라이브러리간의 의존관계를 파악하고 dependency들의 상속관계를 통해 작성되어 필요한 라이브러리를 다운로드 받아준다.

 

2. 스프링 부트 스타터 명명 규칙

  • spring-boot-starter-*
  • 스프링부트의 기본 스타터 종류
스타터명 설명
spring-boot-starter 스프링 부트의 코어 (auto-configuration, logging, yaml 등을 제공)
spring-boot-starter-aop AOP(Aspect Oriented Programming)를 위한 스타터
spring-boot-starter-batch Spring Batch를 사용하기 위한 스타터
spring-boot-starter-jpa Spring Data JPA와 Hibernate를 위한 스타터
spring-boot-starter-data-redis Redis와 Jedis 사용에 필요한 스타터
spring-boot-starter-data-rest Spring Data REST를 사용하기 위한 스타터
spring-boot-starter-thymleaf Thymeleaf 템플릿 엔진 사용에 필요한 스타터
spring-boot-starter-jdbc JDBC Connection Pool 사용에 필요한 스타터
spring-boot-starter-security Sprint Security 사용에 필요한 스타터
spring-boot-starter-oauth2 OAuth2 인증 사용에 필요한 스타터
spring-boot-starter-validation Java Bean Validation 사용에 필요한 스타터
spring-boot-starter-web 웹 개발을 위해 필요한 스타터(Spring MVC, REST, Embed Tomcat, 기타 라이브러리 등)

 

3. 스프링부트 스타터 의존성 확인

 

Build Systems :: Spring Boot

Each release of Spring Boot provides a curated list of dependencies that it supports. In practice, you do not need to provide a version for any of these dependencies in your build configuration, as Spring Boot manages that for you. When you upgrade Spring

docs.spring.io

728x90
반응형
728x90
반응형

1. Proxy

Java에서 프록시(Proxy)는 대리자를 의미한다. 프록시는 기존의 객체를 감싸서 그 객체의 기능을 확장하거나 변경할 수 있게 해준다. 예를 들어, 프록시 객체를 사용하면 객체에 대한 접근을 제어하거나, 객체의 메소드 호출 전후에 로깅 작업 등을 수행할 수 있다. 또한, 프록시 객체를 사용하여 원격으로 실행되는 객체를 호출할 수도 있다. 프록시는 주로 AOP(Aspect Oriented Programming)에서 사용된다.
  • 프록시 생성은 크게 두 가지 방식이 제공된다.
  1. 1. JDK Dynamic Proxy 방식
    • 리플렉션을 이용해서 proxy 클래스를 동적으로 생성해주는 방식으로, 타겟의 인터페이스를 기준으로 proxy를 생성해준다. 사용자의 요청이 타겟을 바라보고 실행될 수 있도록 타겟 자체에 대한 코드 수정이 아닌 리플렉션을 이용한 방식으로, 타겟의 위임 코드를InvocationHandler를 이용하여 작성하게 된다. 하지만 사용자가 타겟에 대한 정보를 잘못 주입하는 경우가 발생할 수 있기 때문에 내부적으로 주입된 타겟에 대한 검증 코드를 거친 후 invoke가 동작하게 된다.
  2. CGLib 방식
    • 동적으로 Proxy를 생성하지만 바이트코드를 조작하여 프록시를 생성해주는 방식이다. 인터페이스 뿐 아니라 타겟의 클래스가 인터페이스를 구현하지 않아도 프록시를 생성해준다. CGLib(Code Generator Library)의 경우에는 처음 메소드가 호출된 당시 동적으로 타켓 클래스의 바이트 코드를 조작하게 되고, 그 이후 호출 시부터는 변경된 코드를 재사용한다. 따라서 매번 검증 코드를 거치는 1번 방식보다는 invoke시 더 빠르게 된다. 또한 리플렉션에 의한 것이 아닌 바이트코드를 조작하는 방식이기 때문에 성능면에서는 더 우수하다.

하지만 CGLib 방식은 스프링에서 기본적으로 제공되는 방식은 아니었기에 별도로 의존성을 추가하여 개발해야 했고, 파라미터가 없는 default 생성자가 반드시 필요했으며, 생성된 프록시의 메소드를 호출하면 타겟의 생성자가 2번 호출되는 등의 문제점들이 있었다.

스프링 4.3, 스프링부트 1.3 이후부터 CGLib의 문제가 된 부분이 개선되어 기본 core 패키지에 포함되게 되었고, 스프링에서 기본적으로 사용하는 프록시 방식이 CGLib 방식이 되었다.

 

1. 로직을 포함하는 코드 작성

  • Student 인터페이스 작성
public interface Student {
	
	void study(int hours);
}

 

  • Student 클래스 작성 (Student인터페이스 구현)
public class Student implements Student {
	
	 @Override
     public void study(int hours) {
        System.out.println(hours + "시간 동안 열심히 공부합니다.");
     }
}

 

2. dynamic

1. Handler 클래스 작성

java.lang.reflect.InvocationHandler를 구현한 클래스를 작성한다.

Student 클래스를 타겟 인스턴스로 설정하고 invoke 메소드를 정의한다.

public class Handler implements InvocationHandler {
	
	/* 메소드 호출을 위해 타겟 인스턴스가 필요하다 */
	private final Student student;
     
  public Handler(Student student) {
    this.student = student;
  }
    
  /* 생성된 proxy 인스턴스와 타겟 메소드, 전달받은 인자를 매개변수로 한다. */
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) 
		throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
    	
    System.out.println("============ 공부가 너무 하고 싶습니다. ==============");
    System.out.println("호출 대상 메소드 : " + method);
    for(Object arg : args) {
    	System.out.println("전달된 인자 : " + arg);
    }
    	
   /* 타켓 메소드를 호출한다. 타겟 Object와 인자를 매개변수로 전달한다. 
    * 여기서 프록시를 전달하면 다시 타겟을 호출할 때 다시 프록시를 생성하고 다시 또 전달하는 무한 루프에 빠지게 된다.
    * */
   method.invoke(student, args);
    	 
   System.out.println("============ 공부를 마치고 수면 학습을 시작합니다. ============");
    	 
   return proxy;
    	 
  }
}

 

2. Application 실행 클래스 작성

Student student = new Student();
Handler handler = new Handler(student);
		
/* 클래스로더, 프록시를 만들 클래스 메타 정보(인터페이스만 가능), 프록시 동작할 때 적용될 핸들러 */
Student proxy 
= (Student) Proxy.newProxyInstance(Student.class.getClassLoader(), new Class[] {Student.class}, handler);
	    
/* 프록시로 감싸진 인스턴스의 메소드를 호출하게 되면 핸들러에 정의한 메소드가 호출된다. */
proxy.study(16);

/*
============ 공부가 너무 하고 싶습니다. ==============
호출 대상 메소드 : public abstract void Student.study(int)
전달된 인자 : 16
16시간 동안 열심히 공부합니다.
============ 공부를 마치고 수면 학습을 시작합니다. ============
*/

 

  • study 메소드 호출 시 proxy 객체의 동작을 확인할 수 있다.

 

3. cglib

1. Handler 클래스 작성

org.springframework.cglib.proxy.InvocationHandler를 구현한 클래스를 작성한다.

Student 클래스를 타겟 인스턴스로 설정하고 invoke 메소드를 정의한다.

public class Handler implements InvocationHandler {
	
	private final Student student;
     
  public Handler(Student student) {
   this.student = student;
  }
    
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) 
		throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
    	
  	System.out.println("============ 공부가 너무 하고 싶습니다. ==============");
   	System.out.println("호출 대상 메소드 : " + method);
   	for(Object arg : args) {
   		System.out.println("전달된 인자 : " + arg);
   	}
    	
   	method.invoke(student, args);
    	 
   	System.out.println("============ 공부를 마치고 수면 학습을 시작합니다. ============");
    	 
   	return proxy;
   	 
  }
}

 

2. Application 실행 클래스 작성

Student student = new Student();
Handler handler = new Handler(student);
		
/* Enhancer 클래스의 create static 메소드는 타겟 클래스의 메타정보와 핸들러를 전달하면 proxy를 생성해서 반환해준다. */
Student proxy 
= (Student) Enhancer.create(Student.class, new Handler(new Student()));

proxy.study(20);

/*
============ 공부가 너무 하고 싶습니다. ==============
호출 대상 메소드 : public void Student.study(int)
전달된 인자 : 20
20시간 동안 열심히 공부합니다.
============ 공부를 마치고 수면 학습을 시작합니다. ============
*/
  • study 메소드 호출 시 proxy 객체의 동작을 확인할 수 있다.
728x90
반응형
728x90
반응형

[Spring Framework] Spring AOP 구현하기 | 예제 포함

 

[Spring Framework] Spring AOP 구현하기 | 예제 포함

[Spring Framework] [Spring AOP] 종류와 특징 총정리 – 입문자를 위한 기초 가이드 [Spring Framework] [Spring AOP] 종류와 특징 총정리 – 입문자를 위한 기초 가이드[Spring Framework] 초보자를 위한 Spring Bean Proper

crushed-taro.tistory.com

1. Reflection

Java 리플렉션(Reflection)은 실행 중인 자바 프로그램 내부의 클래스, 메소드, 필드 등의 정보를 분석하여 다루는 기법을 말한다. 이를 통해 프로그램의 동적인 특성을 구현할 수 있다. 예를 들어, 리플렉션을 이용하면 실행 중인 객체의 클래스 정보를 얻어오거나, 클래스 내부의 필드나 메소드에 접근할 수 있다. 이러한 기능들은 프레임워크, 라이브러리, 테스트 코드 등에서 유용하게 활용된다.

⇒ 스프링에서는 이 Reflection 기술을 사용해 런타임 시 등록한 빈을 애플리케이션 내에서 사용할 수 있게 한다.

 

1. 로직을 포함하는 코드 작성

플렉션 테스트의 대상이 될Account클래스를 생성한다.

public class Account {
	
	private String bankCode;
	private String accNo;
	private String accPwd;
	private int balance;
	
	public Account() {}
	
	public Account(String bankCode, String accNo, String accPwd) {
		this.backCode = bankCode;
		this.accNo = accNo;
		this.accPwd = accPwd;
	}
	
	public Account(String bankCode, String accNo, String accPwd, int balance) {
		this(bankCode, accNo, accPwd);
		this.balance = balance;
	}
	
	/* 현재 잔액을 출력해주는 메소드 */
	public String getBalance() {
		
		return this.accNo + " 계좌의 현재 잔액은 " + this.balance + "원 입니다.";
	}
	
	/* 금액을 매개변수로 전달 받아 잔액을 증가(입금) 시켜주는 메소드 */
	public String deposit(int money) {
		
		String str = "";
		
		if(money >= 0) {
			this.balance += money;
			str = money + "원이 입급되었습니다.";
		}else {
			str = "금액을 잘못 입력하셨습니다.";
		}
		
		return str;
	}
	
	/* 금액을 매개변수로 받아 잔액을 감소(출금) 시켜주는 메소드 */
	public String withDraw(int money) {
		
		String str = "";
		
		if(this.balance >= money) {
			this.balance -= money;
			str = money + "원이 출금되었습니다.";
		}else {
			str = "잔액이 부족합니다. 잔액을 확인해주세요.";
		}

		return str;
	}
}

 

2. 리플렉션 테스트

1. Class

Class타입의 인스턴스는 해당 클래스의 메타정보를 가지고 있는 클래스이다.

/* .class 문법을 이용하여 Class 타입의 인스턴스를 생성할 수 있다. */
Class class1 = Account.class;
System.out.println("class1 : " + class1);
		
/* Object 클래스의 getClass() 메소드를 이용하면 Class 타입으로 리턴받아 이용할 수 있다. */
Class class2 = new Account().getClass();
System.out.println("class2 : " + class2);

/* Class.forName() 메소드를 이용하여 런타임시 로딩을 하고 그 클래스 메타정보를 Class 타입으로 반환받을 수 있다. */
try {
	Class class3 = Class.forName("project.reflection.Account");
	System.out.println("class3 : " + class3);
			
	/* Double자료형 배열을 로드할 수 있다. */
	Class class4 = Class.forName("[D");
	Class class5 = double[].class;
			
	System.out.println("class4 : " + class4);
	System.out.println("class5 : " + class5);
			
	/* String자료형 배열을 로드할 수 있다. */
	Class class6 = Class.forName("[Ljava.lang.String;");
	Class class7 = String[].class;
	System.out.println("class6 : " + class6);
	System.out.println("class7 : " + class7);
			
} catch (ClassNotFoundException e) {
	e.printStackTrace();
}
		
/* 원시 자료형을 사용하면 컴파일 에러 발생 */
//		double d = 1.0;
//		Class class8 = d.getClass();
		
/* TYPE 필드를 이용하여 원시형 클래스를 반환받을 수 있다. */
Class class8 = Double.TYPE;
System.out.println("class8 : " + class8);
		
Class class9 = Void.TYPE;
System.out.println("class9 : " + class9);
		
/* 클래스의 메타 정보를 이용하여 여러 가지 정보를 반환받는 메소드를 제공한다. */
/* 상속된 부모 클래스를 반환한다. */
Class superClass = class1.getSuperclass();
System.out.println("superClass : " + superClass);

/*
class1 : class project.reflection.Account
class2 : class project.reflection.Account
class3 : class project.reflection.Account
class4 : class [D
class5 : class [D
class6 : class [Ljava.lang.String;
class7 : class [Ljava.lang.String;
class8 : double
class9 : void
superClass : class java.lang.Object
*/

 

2. field

field 정보에 접근할 수 있다.

Field[] fields = Account.class.getDeclaredFields();
for(Field field : fields) {
	System.out.println("modifiers : " + Modifier.toString(field.getModifiers()) + 
			", type : " + field.getType() + 
			", name : " + field.getName() );
}

/*
modifiers : private, type : class java.lang.String, name : backCode
modifiers : private, type : class java.lang.String, name : accNo
modifiers : private, type : class java.lang.String, name : accPwd
modifiers : private, type : int, name : balance
*/

 

3. 생성자

생성자 정보에 접근할 수 있다.

Constructor[] constructors = Account.class.getConstructors();
for(Constructor con : constructors) {
	System.out.println("name : " + con.getName());
			
	Class[] params = con.getParameterTypes();
	for(Class param : params) {
		System.out.println("paramType : " + param.getTypeName());
	}
}

/*
name : project.reflection.Account
paramType : java.lang.String
paramType : java.lang.String
paramType : java.lang.String
paramType : int
name : project.reflection.Account
paramType : java.lang.String
paramType : java.lang.String
paramType : java.lang.String
name : project.reflection.Account
*/

 

생성자를 이용하여 인스턴스를 생성할 수 있다.

try {
	Account acc = (Account) constructors[0].newInstance("20", "110-223-123456", "1234", 10000);
	System.out.println(acc.getBalance());
} catch (InstantiationException | IllegalAccessException | IllegalArgumentException
		| InvocationTargetException e) {
		e.printStackTrace();
}

/*
110-223-123456 계좌의 현재 잔액은 10000원 입니다.
*/

 

4. 생성자

메소드 정보에 접근할 수 있다.

Method[] methods = Account.class.getMethods();
Method getBalanceMethod = null;
for(Method method : methods) {
	System.out.println(Modifier.toString(method.getModifiers()) + " " + 
					method.getReturnType().getSimpleName() + " " + 
					method.getName());
			
	if("getBalance".equals(method.getName())) {
		getBalanceMethod = method;
	}
}

/*
public String getBalance
public String withDraw
public String deposit
public final native void wait
public final void wait
public final void wait
public boolean equals
public String toString
public native int hashCode
public final native Class getClass
public final native void notify
public final native void notifyAll
*/

 

invoke 메소드로 메소드를 호출할 수 있다.

try {
	System.out.println(getBalanceMethod.invoke(((Account) constructors[2].newInstance())));
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
	e.printStackTrace();
} catch (InstantiationException e) {
	e.printStackTrace();
}

/*
null 계좌의 현재 잔액은 0원 입니다.
*/
728x90
반응형
728x90
반응형

[Spring Framework] 초보자를 위한 Spring Bean Properties 가이드

 

[Spring Framework] 초보자를 위한 Spring Bean Properties 가이드

[Spring Framework] Spring Boot Bean 초기화(init)와 소멸(destroy) 메서드 완전 정리 [Spring Framework] Spring Boot Bean 초기화(init)와 소멸(destroy) 메서드 완전 정리[Spring Framework] Spring Bean Scope 완벽 가이드 | Singleton

crushed-taro.tistory.com

1. AOP

1. AOP란?

AOP는 관점 지향 프로그래밍(Aspect Oriented Programming)의 약자이다. 중복되는 공통 코드를 분리하고 코드 실행 전이나 후의 시점에 해당 코드를 삽입함으로써 소스 코드의 중복을 줄이고, 필요할 때마다 가져다 쓸 수 있게 객체화하는 기술을 말한다.

AOP 사진 1

 

2. AOP 핵심 용어

용어 설명
Aspect 핵심 비즈니스 로직과는 별도로 수행되는 횡단 관심사를 말한다.
Advice Aspect의 기능 자체를 말한다.
Join point Advice가 적용될 수 있는 위치를 말한다.
Pointcut Join point 중에서 Advice가 적용될 가능성이 있는 부분을 선별한 것을 말한다.
Weaving Advice를 핵심 비즈니스 로직에 적용하는 것을 말한다.

AOP 사진 2

 

3. Advice의 종류

종류 설명
Before 대상 메소드가 실행되기 이전에 실행되는 어드바이스
After-returning 대상 메소드가 정상적으로 실행된 이후에 실행되는 어드바이스
After-throwing 예외가 발생했을 때 실행되는 어드바이스
After 대상 메소드가 실행된 이후에(정상, 예외 관계없이) 실행되는 어드바이스
Around 대상 메소드 실행 전/후에 적용되는 어드바이스

 

4. Spring AOP

스프링 프레임워크에서 제공하는 AOP는 다음과 같은 특징을 가진다.

  • 프록시 기반의 AOP 구현체 : 대상 객체(Target Object)에 대한 프록시를 만들어 제공하며, 타겟을 감싸는 프록시는 서버 Runtime 시에 생성된다.
  • 메서드 조인 포인트만 제공 : 핵심기능(대상 객체)의 메소드가 호출되는 런타임 시점에만 부가기능(어드바이스)을 적용할 수 있다.

AOP 사진 3

728x90
반응형
728x90
반응형

[Spring Framework] Spring Boot Bean 초기화(init)와 소멸(destroy) 메서드 완전 정리

 

[Spring Framework] Spring Boot Bean 초기화(init)와 소멸(destroy) 메서드 완전 정리

[Spring Framework] Spring Bean Scope 완벽 가이드 | Singleton부터 Prototype까지 [Spring Framework] Spring Bean Scope 완벽 가이드 | Singleton부터 Prototype까지[Spring Framework] Spring Framework에서 @Inject 애너테이션 완벽 가이

crushed-taro.tistory.com

1. Bean

1. Properties

1. Properties

Properties는 키/값 쌍으로 이루어진 간단한 파일이다. 보통 소프트웨어 설정 정보를 저장할 때 사용된다. 스프링에서는 Properties를 이용하여 빈의 속성 값을 저장하고 읽어올 수 있다.

Properties 파일의 각 줄은 다음과 같은 형식으로 구성된다. 주석은#으로 시작하며, 빈 줄은 무시된다.

# 주석
key=value

product-info.properties라는 이름의 파일을 생성하고 Product 타입의 값이 될 value를 적절한 key를 설정하여 정의한다.

bread.carpbread.name=붕어빵
bread.carpbread.price=1000
beverage.milk.name=딸기우유
beverage.milk.price=1500
beverage.milk.capacity=500
beverage.water.name=지리산암반수
beverage.water.price=3000
beverage.water.capacity=500
bread.carpBread.name=\uBD95\uC5B4\uBE75
bread.carpBread.price=800
beverage.milk.name=\uC544\uBAAC\uB4DC\uC6B0\uC720
beverage.milk.price=2800
beverage.milk.capacity=950
beverage.water.name=\uC0BC\uB2E4\uC218
beverage.water.price=1000
beverage.water.capacity=2000

상품들을 빈으로 등록할 설정 파일을 작성한다. 이 때product-info.properties 파일에 기재한 값으로 상품들의 값을 초기화 하려고 한다. properties 파일을 읽어올 때 @PropertySource 어노테이션에 경로를 기재하여 읽어올 수 있으므로 읽어올 properties 파일의 경로를 작성한다.

@Configuration
/* resources 폴더 하위 경로를 기술한다. 폴더의 구분은 슬러쉬(/) 혹은 역슬러쉬(\\)로 한다. */
@PropertySource("properties/product-info.properties")
public class ContextConfiguration {

}

빈 설정 파일 내부에@Value 어노테이션을 사용하여 properties의 값을 읽어온다. @Value어노테이션은 빈의 속성 값을 자동으로 주입받을 수 있는 어노테이션이다.

@Configuration
/* resources 폴더 하위 경로를 기술한다. 폴더의 구분은 슬러쉬(/) 혹은 역슬러쉬(\\)로 한다. */
@PropertySource("properties/product-info.properties")
public class ContextConfiguration {

	/* 치환자(placeholder) 문법을 이용하여 properties에 저장된 key를 입력하면 value에 해당하는 값을 꺼내온다.
   * 공백을 사용하면 값을 읽어오지 못하니 주의한다.
   * : 을 사용하면 값을 읽어오지 못하는 경우 사용할 대체 값을 작성할 수 있다.
   **/
	@Value("${bread.carpbread.name:팥붕어빵}")
	private String carpBreadName;

	/* 값은 여러 번 불러올 수 있다. */
//	@Value("${bread.carpbread.name:슈크림붕어빵}")
//	private String carpBreadName2;

  @Value("${bread.carpbread.price:0}")
  private int carpBreadPrice;

  @Value("${beverage.milk.name:}")
  private String milkName;

  @Value("${beverage.milk.price:0}")
  private int milkPrice;

  @Value("${beverage.milk.capacity:0}")
  private int milkCapacity;

  @Bean
  public Product carpBread() {

      return new Bread(carpBreadName, carpBreadPrice, new java.util.Date());
  }

  @Bean
  public Product milk() {

      return new Beverage(milkName, milkPrice, milkCapacity);
  }

  @Bean
  public Product water(@Value("${beverage.water.name:}") String waterName,
                       @Value("${beverage.water.price:0}") int waterPrice,
                       @Value("${beverage.water.capacity:0}") int waterCapacity) {

      return new Beverage(waterName, waterPrice, waterCapacity);
  }

  @Bean
  @Scope("prototype")
  public ShoppingCart cart() {

      return new ShoppingCart();
  }
}

필드 또는 파라미터에@Value 어노테이션을 사용할 수 있으며 해당 어노테이션에 ${key} 와 같이 치환자(placeholder) 문법을 이용하여 properties에 저장된 key를 입력하면 value에 해당하는 값을 꺼내와 필드 또는 파라미터에 주입한다. key 뒤에 :을 이용하여 해당 key 값이 없을 경우에는 주입할 기본 값을 입력할 수 있다.

/* 빈 설정 파일을 기반으로 IoC 컨테이너 생성 */
ApplicationContext context = new AnnotationConfigApplicationContext(ContextConfiguration.class);

...생략

/*
cart1에 담긴 내용 : [붕어빵 1000 Thu Jun 08 23:07:58 KST 2023, 딸기우유 1500 500]
cart2에 담긴 내용 : [지리산암반수 3000 500]
cart1의 hashcode : 716487794
cart2의 hashcode : 987249254
*/

해당 빈 설정 파일을 읽어 IoC 컨테이너를 생성하고 컨테이너에서 상품 객체들을 꺼내 쇼핑 카트에 담아 출력하는 코드를 실행해보면Properties파일의 값이 잘 주입 된 것을 확인할 수 있다.

 

2. 국제화

국제화(Internationalization 또는 i18n)란, 소프트웨어를 다양한 언어와 문화권에 맞게 번역할 수 있도록 디자인하는 과정이다. Spring Framework에서 i18n은 MessageSource 인터페이스와 property 파일을 이용하여 구현된다. 각 언어별로 property 파일을 정의하면, Spring은 사용자의 로케일에 맞게 적절한 파일을 선택하여 애플리케이션 텍스트를 올바르게 번역할 수 있다.

먼저 resources 폴더에 한국어 와 영어 버전의 에러 메세지를 properties 파일로 정의한다. properties 파일에 {인덱스} 의 형식으로 문자를 입력하면 값을 전달하면서 value를 불러올 수 있다. 각 파일의 끝에는 Locale 이 적절하게 입력 되어야 한다. 다음은 일반적으로 사용되는 Locale코드들의 목록이다.

언어 국가 코드
한국어 대한민국 ko_KR
영어 미국 en_US
영어 영국 en_UK
일본어 일본 ja_JP
스페인어 스페인 es_ES
# 한국어 버전
# message_ko_KR.properties
error.404=페이지를 찾을 수 없습니다!
error.500=개발자의 잘못입니다. 개발자는 누구? {0} 입니다. 현재시간 {1}
# 영어 버전
# message_en_US.properties
error.404=Page Not Found!!
error.500=something wrong! The developer''s fault. who is devloper? It''s {0} at {1}

빈 설정 파일에MessageSource 타입의 빈을 등록한다. 지정 된 명칭이므로 컨테이너에 등록 되는 빈 이름(메소드명)을 정확하게 작성하도록 한다.

@Configuration
public class ContextConfiguration {
	
	@Bean
	public ReloadableResourceBundleMessageSource messageSource() {
		
		/* 접속하는 세션의 로케일에 따라 자동 재로딩하는 용도의 MessageSource 구현체 */
		ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
		
		/* 다국어메세지를 읽어올 properties 파일의 파일 이름을 설정한다. */
		messageSource.setBasename("properties/subsection02/i18n/message");
		/* 불러온 메세지를 해당 시간 동안 캐싱한다. */
		messageSource.setCacheSeconds(10);
		/* 기본 인코딩 셋을 설정할 수 있다. */
		messageSource.setDefaultEncoding("UTF-8");
		
		return messageSource;
	}
}

ReloadableResourceBundleMessageSource 은 MessageSource 인터페이스의 구현체 중 한 종류로 접속하는 세션의 로케일에 따라 자동 재로딩하는 기능을 가지고 있다. basename 속성에 다국어 메세지를 읽어올 properties 파일의 이름을 설정한다. 그 외에도 MessageSource에 대해 다양한 설정을 할 수 있다.

ApplicationContext context = new AnnotationConfigApplicationContext(ContextConfiguration.class);

String error404MessageKR = context.getMessage("error.404", null, Locale.KOREA);
String error500MessageKR = context.getMessage("error.500", 
	new Object[] {"여러분", new Date()}, Locale.KOREA);

System.out.println("I18N error.404 메세지 : " + error404MessageKR);
System.out.println("I18N error.500 메세지  : " + error500MessageKR);

/*
I18N error.404 메세지 : 페이지를 찾을 수 없습니다!
I18N error.500 메세지 : 개발자의 잘못입니다. 개발자는 누구? 여러분 입니다. 현재시간 23. 6. 8. 오후 11:31
*/

빈 설정 파일을 읽어와 IoC 컨테이너를 구동시키고getMessage 메소드를 통해 읽어올 메세지의 key 값과 Locale.KOREA 라고 하는 Locale을 전달한다. ReloadableResourceBundleMessageSource 가 기능하여 한국어 메세지를 로드해오는 것을 출력을 통해 확인할 수 있다. 이 때 getMessage 메소드의 두 번째 인자는 전달 값으로 배열로 전달 시 properties 파일에 {인덱스} 로 작성했던 영역에 인덱스 순서대로 채워지는 것을 확인할 수 있다.

ApplicationContext context = new AnnotationConfigApplicationContext(ContextConfiguration.class);

String error404MessageUS = context.getMessage("error.404", null, Locale.US);
String error500MessageUS = context.getMessage("error.500", 
	new Object[] {"you", new Date()}, Locale.US);
		
System.out.println("The I18N message for error.404 : " + error404MessageUS);
System.out.println("The I18N message for error.500 : " + error500MessageUS);

/*
The I18N message for error.404 : Page Not Found!!
The I18N message for error.500 : something wrong! The developer's fault. who is devloper? 
It's you at 6/8/23, 11:31 PM
*/

다시 한 번 빈 설정 파일을 읽어와 IoC 컨테이너를 구동시키고getMessage 메소드를 통해 읽어올 메세지의 key 값과  Locale.US 라고 하는 Locale을 전달한다. ReloadableResourceBundleMessageSource 가 기능하여 영어 메세지를 로드해오는 것을 출력을 통해 확인할 수 있다. getMessage메소드의 두 번째 인자로 전달 된 값도 함께 잘 출력 되는 것을 확인할 수 있다.

728x90
반응형

+ Recent posts