스프링 MVC의 Controller 특징
- HttpServletRequest, HttpServletResponse를거의 사용할 필요 없이 필요한 기능 구현
- 다양한 타입의 파라미터 처리, 다양한타입의 리턴타입 사용 가능
- GET 방식, POST 방식 등 전송 방식에 대한 처리를 어노테이션으로 처리 가능
- 상속/인터페이스 방식 대신에 어노테이션만으로도 필요한 설정 가능
@Controller, @RequestMapping
프로젝트 내 org.noel.controller 패키지 폴더에 SampleController라는이름의 클래스를 작성한다.
▶ SampleController 클래스
package org.noel.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/sample/*")
public class SampleController {
}
SampleController의 클래스 선언부에는 @Controller
라는 스프링 MVC에서 사용하는 어노테이션과 @RequestMapping
을 적용하고 있습니다. 작성된 SampleController 클래스는 위의 크림과 같이 자동으로 스프링의 객체(Bean)로 등록되는데 servlet-context.xml에 그 이유가 있습니다.
▶ servlet-context.xml의 일부
<context:component-scan base-package="org.noel.controller" />
</beans:beans>
servlet-context.xml에는 <context:component-scan>
이라는 태그를 이용해서 지정된 패키지를 조사(스캔)하도록 설정되어 있습니다. 해당 패키지에 선언된 클래스들을 조사하면서 스프링에서 객체(Bean) 설정에 사용되는 어노테이션들을 가진 클래스들을 파악하고 필요하다면 이를 객체로 관리하게 됩니다.
SampleController 클래스가 스프링에서 관리되면 화면상에는 클래스 옆에 작게 's' 모양의 아이콘이 추가됩니다.
클래스 선언부에는 @Controller
와 함께 @RequestMapping
을 많이 사용합니다.
@RequestMapping
은 현재 클래스의 모든 메서드들의 기본적인 URL 경로가 됩니다.
예를 들어, SampleController 클래스를 다음과 같이 '/sample/*' 이라는 경로로 지정한다면 다음과 같은 URL은 모두 SampleController에서 처리됩니다. (ex. /sample/aaa, /sample/bbb )
@RequestMapping
어노테이션은 클래스의 선언과 메서드 선언에 사용할 수 있습니다.
▶ SampleController 클래스
package org.noel.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import lombok.extern.log4j.Log4j;
@Controller
@RequestMapping("/sample/*")
@Log4j
public class SampleController {
@RequestMapping("")
public void basic() {
log.info("basic..................");
}
}
SampleController는 Lombok의 @Logj4를 사용합니다.@Log4j
는 @Log가 java.uril.Logging을 이용하는데 반해 Log4j 라이브러리를 활용합니다. ( ※ Spring Lagacy Project로 생성한 프로젝트는 기본적으로 Log4j가 추가되어 있으므로 별도의 설정이 필요없습니다! ※)
@RequestMapping의 변화
@Controller
어노테이션은 추가적인 속성을 지정할 수 없지만, @RequestMapping
의 경우 몇 가지의 속성을 추가할 수 있습니다. 이 중에서 가장 많이 사용하는 속성이 method 속성입니다. Method 속성은 흔히 GET 방식, POST 방식을 구분해서 사용할 때 이용합니다.
스프링 4.3버전부터는 이러한 @RequestMapping을 줄여서 사용할 수 있는 @GetMapping, @PostMapping이 등장하는데 축약형의 표현이므로, 아래와 같이 비교해보는 것이 좋습니다.
▶ SampleController 클래스의 일부
@RequestMapping(value = "/basic", method = {RequestMethod.GET, RequestMethod.POST})
public void basicGet() {
log.info("basic get...........");
}
@GetMapping("/basicOnlyGet")
public void basicGet2() {
log.info("basic get only get..........");
}
@RequestMapping
은 GET, POST 방식 모두를 지원해야 하는 경우에 배열로 처리해서 지정할 수 있습니다. 최근에는 PUT,DELETE 방식 등도 점점 많이 사용하고 있습니다. @GetMapping
의 경우 오직 GET 방식에만 사용할 수 있으므로, 간편하기는 하지만 기능에 대한 제한은 많은 편입니다.
Controller의 파라미터 수집
Controller를 작성할 때 가장 편리한 기능은 파라미터가 자동으로 수집되는 기능이다.
이 기능을 이용하면 매번 request.getParameter()를 이용하는 불편함을 없앨 수 있습니다.
예제를 위해서 org.noel.domain이라는 패키지를 작성하고, SampleDTO 클래스를 작성합니다.
▶ sampleDTO 클래스
package org.noel.domain;
import lombok.Data;
@Data
public class SampleDTO {
private String name;
private int age;
}
SampleDTO 클래스는 Lombok의 @Data 어노테이션을 이용해서 처리합니다.
@Data
를 이용하게 되면 getter/setter, equals(), toString() 등의 메서드를 자동 생성하기 때문에 편리합니다. SampleController의 메서드가 SampleDTO를 파라미터로 사용하게 되면 자동으로 setter 메서드가 동작하면서 파라미터를 수집하게 됩니다.
▶ SampleController의 일부
package org.noel.controller;
import org.noel.domain.SampleDTO;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import lombok.extern.log4j.Log4j;
@Controller
@RequestMapping("/sample/*")
@Log4j
public class SampleController {
~ 생략 ~
@GetMapping("/ex01")
public String ex01(SampleDTO dto) {
log.info("" + dto);
return "ex01";
}
}
SampleController의 경로가 '/sample/*'이므로 ex01() 메서드를 호출하는 경로는 'sample/ex01'이 됩니다. 메서드에는 @GetMapping이 사용되었으므로, 필요한 파라미터를 URL 뒤에 '?name=AAA&age=10'과 같은 형태로 추가해서 호출할 수 있습니다.
실행된 결과를보면 SampleDTO 객체 안에 name과 age 속성이 제대로 수집된 것을 볼 수 있습니다.
파라미터의 수집과 변환
Controller가 파라미터를 수집하는 방식은 파라미터 타입에 따라 자동으로 변환하는 방식을 이용한다.
예를 들어, SampleDTO에는 int 타입으로 선언된 age가 자동으로 숫자로 변환되는 것을 볼 수 있습니다.
만일 기본 자료형이나 문자열 등을 이용한다면 파라미터의 타입만을 맞게 선언해주는 방식을 사용할 수 있습니다.
▶ SampleController에 추가
@GetMapping("/ex02")
public String ex02(@RequestParam("name") String name, @RequestParam("age") int age) {
log.info("name: " + name);
log.info("age: " + age);
return "ex02";
}
ex02() 메서드는 파라미터에 @RequestParam 어노테이션을 사용해서 작성되었다.
@RequestParam
은 파라미터로 사용된 변수의 이름과 전달되는 파라미터의 이름이 다른 경우에 유용하게 사용됩니다.
브라우저에서 호출하면 이전과 동일하게 데이터가 수집된 것을 볼 수 있습니다.
리스트, 배열 처리
동일한 이름의 파라미터가 여러 개 전달되는 경우에는 ArrayList<>
등을 이용해서 처리가 가능합니다.
@GetMapping("/ex02List")
public String ex02List(@RequestParam("ids") ArrayList<String> ids) {
log.info("ids: " + ids);
return "ex02List";
}
스프링은 파라미터의 타입을 보고 객체를 생성하므로 파라미터의 타입은 List<>와 같이 인터페이스 타입이 아닌 실제적인 클래스 타입으로 지정합니다. 위 코드의 경우 'ids'라는 이름의 파라미터가 여러 개 전달되더라도 ArrayList<String>이 생성되어 자동으로 수집됩니다. 브라우저를 이용해서 '프로젝트 경로/sample/ex02List?ids=111&ids=222&ids=333'을 호출하면 위와 같이 로그가 출력된다.
배열의 경우도 동일하게 처리할 수 있습니다.
▶ SampleController에 추가
@GetMapping("/ex02Array")
public String ex02Array(@RequestParam("ids") String[] ids) {
log.info("array ids: " + Arrays.toString(ids));
return "ex02Array";
}
객체 리스트
만일 전달하는 데이터가 SampleDTO와 같이 객체 타입이고 여러 개를 처리해야 한다면 약간의 작업을 통해서 한 번에 처리를 할 수 있습니다. 예를 들어 SampleDTO를 여러 개 전달받아서 처리하고 싶다면 다음과 같이 SampleDTO의 리스트를 포함하는 SampleDTOList 클래스를 설계합니다.
▶ SampleDTOList 클래스
package org.noel.domain;
import java.util.ArrayList;
import java.util.List;
import lombok.Data;
@Data
public class SampleDTOList {
private List<SampleDTO> list;
public SampleDTOList() {
list = new ArrayList<>();
}
}
SampleController에서는 SampleDTOList 타입을 파라미터로 사용하는 메서드를 작성합니다.
▶ SampleContoller에 추가
@GetMapping("/ex02Bean")
public String ex02Bean(SampleDTOList list) {
log.info("list dtos: " + list);
return "ex02Bean";
}
파라미터는 '[인덱스]'와 같은 형식으로 전달해서 처리할 수 있습니다.
JavaScript를 이용하는 경우에는 encodeURIComponent()와 같은 방법으로 해결할 수 있으나 현재 예제의 경우에는 '['는 '%5D'로 변경하도록 합니다.
출력된 결과를 보면 3개의 SampleDTO 객체가 생성된 것을 볼 수 있고, '[]' 안에 인덱스 번호에 맞게 객체의 속성값이 세팅된 것을 확인할 수 있습니다.
@InitBinder
파라미터의 수집을 다른 용어로는 'binding(바인딩)'이라고 합니다. 변환이 가능한 데이터는 자동으로 변환되지만 경우에 따라서는 파라미터를 변환해서 처리해야 하는 경우도 존재합니다.
org.noel.domain 패키지에 TodoDTO라는 클래스를 작성합니다.
▶ TodoDTO 클래스
package org.noel.domain;
import java.sql.Date;
import lombok.Data;
@Data
public class TodoDTO {
private String title;
private Date dueDate;
}
TodoDTO에는 특별하게 dueDate 변수의 타입이 java.util.Date 타입입니다. 만일 사용자가 '2018-01-01'과 같이 들어오는 데이터를 변환하고자 할 때 문제가 발생하게 됩니다. 이러한 문제의 간단한 해결책은 @InitBinder
를 이용하는 것입니다.
▶ SampleController의 일부
package org.noel.controller;
import java.sql.Date;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import org.noel.domain.SampleDTO;
import org.noel.domain.SampleDTOList;
import org.noel.domain.TodoDTO;
import org.springframework.beans.propertyeditors.CustomDateEditor;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import lombok.extern.log4j.Log4j;
@Controller
@RequestMapping("/sample/*")
@Log4j
public class SampleController {
@InitBinder
public void initBinder(WebDataBinder binder) {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat,
false));
}
~ 생략 ~
@GetMapping("/ex03")
public String ex03(TodoDTO todo) {
log.info("todo: " + todo);
return "ex02";
}
만일 브라우저에서 'http://localhost:8088/sample/ex03?title=test&dueDate=2018-01-01'과 같이 호출했다면 서버에서는 정상적으로 파라미터를 수집해서 처리합니다.
반면에 @InitBinder 처리가 되지 않는다면 브라우저에서는 400 에러가 발생하는 것을 볼 수 있습니다. 날짜가 정상적으로 처리되어도 아직 jsp 페이지는 없으므로 다음과 같은 결과를 확인할 수 있습니다.
@DateTimeFormat
@InitBinder를 이용해서 날짜를 변환할 수도 있지만, 파라미터로 사용되는 인스턴스 변수에 @DateTimeFormat
을 적용해도 변환이 가능합니다.
package org.noel.domain;
import java.util.Date;
import org.springframework.format.annotation.DateTimeFormat;
import lombok.Data;
@Data
public class TodoDTO {
private String title;
@DateTimeFormat(pattern = "yyyy/MM/dd")
private Date dueDate;
}
문자열로 'yyyy/MM/dd'의 형식이 맞다면 자동으로 날짜 타입으로 변환이 됩니다.
브라우저에서 '프로젝트 경로/sample/ex03?title=test&dueDate=2018/01/01'로 호출하면 아래와 같은 결과가 나옵니다.
Model이라는 데이터 전달자
Controller의 메서드를 작성할 땐은 특별하게 Model(Model 객체는 JSP에 컨트롤러에서 생성된 데이터를 담아서 전달하는 역할을 하는 존재) 이라는 타입을 파라미터로 지정할 수 있습니다. 이를 이용해서 JSP와 같은 뷰(View)로전달해야 하는 데이터를 담아서 보낼 수 있습니다. 메서드의 파라미터에 Model 타입이 지정된 경우에는 스프링은 특별하게 Model 타입의 객체를 만들어서 메서드에 주입하게 됩니다.
Model은 모델 2 방식에서 사용하는 request.setAttribute()와 유사한 역할을 합니다.
▶ Servlet에서 모델 2방식으로 데이터를 전달하는 방식
request.setAttribute("serverTime", new java.util.Date());
RequestDispatcher dispatcher = request.getRequestDispatcher("/WEB-INF/jsp/home.jsp");
dispatcher.forward(request, response);
위의 코드를 스프링에서는 Model을 이용해서 다음과 같이 처리하게 됩니다.
▶ 스프링 MVC에서 Model을 이용한 데이터 전달
public String home(Model model) {
model.addAttribute("serverTime", new java.util.Date());
return "home";
}
@ModelAttribute 어노테이션
스프링 MVC의 Controller는 기본적으로 Java Beans 규칙에 맞는 객체는 다시 화면으로 객체를 전달합니다. 좁은 의미에서 Java Beans의 규칙은 단순히 생성자가 없거나 빈 생성자를 가져야 하며, getter/setter를 가진 클래스의 객체들을 의미합니다. 앞의 예제에서 파라미터로 사용된 SampleDTO의 경우는 Java Bean의 규칙에 맞기 때문에 자동으로 다시 화면까지 전달됩니다. 전달될 때에는 클래스명의 앞글자는 소문자로 처리됩니다.
반면에 기본 자료형의 경우는 파라미터로 선언하더라도 기본적으로 화면까지 전달되지는 않습니다.
▶ SampleController에 추가
@GetMapping("/ex04")
public String ex04(SampleDTO dto, @ModelAttribute("page") int page) {
log.info("dto: " + dto);
log.info("page: " + page);
return "/sample/ex04";
}
ex04()는 SampleDTO 타입과 int 타입의 데이터를파라미터로 사용합니다.결과를 확인하기 위해서 '/WEB-INF/views' 폴더 아래 sample 폴더를 생성하고 리턴값에서 사용한 'ex04'에 해당하는 ex04.jsp를 작성합니다.
▶ ex04.jsp의 일부
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<h2>SAMPLEDTO ${sampleDTO }</h2>
<h2>PAGE ${page }</h2>
</body>
</html>
서버를 실행하고 브라우저를 통해서 'http://localhost:8088/sample/ex04?name=aaa&age=11&page=9'와 같이 호출하면 화면에 SampleDTO만이 전달된 것을 확인할 수 있습니다. int 타입으로 선언된 page는 전달되지 않습니다.
@ModelAttribute
는 강제로 전달받은 파라미터를 Model에 담아서 전달하도록 할 때 필요한 어노테이션입니다.
@ModelAttribute가 걸린 파라미터는 타입에 관계없이 무조건 Model에 담아서 전달되므로, 파라미터로 전달된 데이터를 다시 화면에서 사용해야 할 경우에 유용하게 사용됩니다.
기존의 코드에서 int 타입의 데이터가 화면까지 전달되지 않았으므로 @ModelAttribute를 추가하면 다음과 같은 형태가 됩니다.
@GetMapping("/ex04")
public String ex04(SampleDTO dto, @ModelAttribute("page") int page) {
log.info("dto: " + dto);
log.info("page: " + page);
return "/sample/ex04";
}
@ModelAttribute가 붙은 파라미터는 화면까지 전달되므로 브라우저를 통해서 호출하면 아래와 같이 ${page}가 출력되는 것을 확인할 수 있습니다( ※ 기본 자료형에 @ModelAttribute를 적용할 경우에는 반드시 @ModelAttribute("page")와 같이 값(value)을 지정하도록 합니다. ※)
RedirectAttributes
Model 타입과 더불어서 스프링 MVC가 자동으로 전달해 주는 타입 중에는 RedirectAttributes 타입이 존재합니다.
RedirectAttributes
는 조금 특별하게도 일회성으로 데이터를 전달하는 용도로 사용됩니다. RedirectAttributes는 기존에 Servlet에서는 response.sendRedirect()를 사용할 때와 동일한 용도로 사용됩니다.
▶ Servlet에서 redirect 방식
response.sendRedirect("/home?name=aaa&age=10");
스프링 MVC를 이용하는 경우에는 다음과 같이 변경됩니다.
▶ 스프링 MVC를 이용하는 redirect 처리
rttr.addFlashAttribute("name", "AAA");
rttr.addFlashAttribute("age",10);
return "redirect:/";
RedirectAttribute
는 Model과 같이 파라미터로 선언해서 사용하고, addFlashAttribute(이름,값) 메서드를 이용해서 화면에 한 번만 사용하고 다음에는 사용되지 않는 데이터를 전달하기 위해서 사용합니다.
'Back-end > Spring Web Project' 카테고리의 다른 글
[Spring Web Project] 스프링 MVC의 Controller(3) - 파일 업로드 처리 (0) | 2023.11.24 |
---|---|
[Spring Web Project] 스프링 MVC의 Controller(2) - Controller의 리턴 타입 (1) | 2023.11.21 |
[Spring Web Project] 스프링 MVC의 기본 구조 (0) | 2023.11.21 |
MyBatis와 스프링 연동(2) - Mapper XML 설정 (0) | 2023.11.15 |
MyBatis와 스프링 연동(1) - MyBatis 연동 (0) | 2023.11.14 |