이 글은 코드로 배우는 스프링 웹 프로젝트(남가람북스, 구멍가게 코딩단)을 읽고 공부한 내용을 바탕으로 정리한 글입니다.
Controller는 개발자가 작성하는 클래스입니다. 실제 Request를 처리하는 로직을 작성하게 됩니다. 스프링 MVC는 어노테이션을 중심으로 구성되는데, @Controller
어노테이션과 @RequestMapping
어노테이션 사용법을 예제를 통해서 공부해보겠습니다.
1. @Controller
와 @RequestMapping
예제로 controller 패키지에 SampleController
클래스를 다음과 같이 작성해줍니다.
import lombok.extern.log4j.Log4j;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/sample/*")
@Log4j
public class SampleController {
@RequestMapping("")
public void basic() {
log.info("basic..........");
}
}
그리고 controller 패키지를 스캔할 수 있도록 dispatcher-servlet.xml
에 <context:component-scan>
을 추가합니다.
<context:component-scan base-package="com.hellomygreenworld.ex01.controller"/>
controller 패키지를 스캔하면서 스프링에서 객체(Bean) 설정에 사용되는 어노테이션들을 가진 클래스들을 파악하고, 필요하면 이를 객체로 생성해서 관리합니다.
@Controller
어노테이션은 해당 클래스가 컨틀롤러임을 스프링에 알려줍니다.@RequestMapping
어노테이션은 현재 클래스의 모든 메서드들의 기본적인 URL 경로가 됩니다. 위의 코드에서는/sample/*
으로 경로를 지정했으므로/sample
로 시작하는 모든 URL은 모두SampleController
에서 처리됩니다.
프로젝트를 실행해보면 /
와 /sample/*
이 RequestMappingHandlerMapping
에 매핑되어있음을 확인할 수 있습니다.
2. @RequestMapping
@RequestMapping
에는 몇 가지 속성을 추가할 수 있습니다. 가장 많이 사용하는 속성이 method 속성입니다. method 속성으로 GET 방식과 POST 방식을 구분해서 사용할 수 있습니다.
@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 ----------");
}
- GET과 POST 방식을 모두 지원해야 하는 경우, method 속성값을 배열로 넣어주면 됩니다.
@GetMapping
과@PostMapping
은@RequestMapping
에 method 속성을 지정하는 것을 줄여서 사용할 수 있는 어노테이션입니다. (스프링 4.3버전부터 사용할 수 있습니다.)
3. Controller의 파라미터 수집
1. 파라미터 수집
Controller를 작성할 때 가장 편리한 기능은 파라미터가 자동으로 수집되는 기능입니다. 이 기능을 이용하면 매번 request.getParameter()
를 사용하지 않아도됩니다.
예제로 domain
패키지를 만들고 SampleDTO
클래스를 작성해보겠습니다.
import lombok.Data;
@Data
public class SampleDTO {
private String name;
private int age;
}
(@Data
어노테이션으로 getter
/setter
, equals()
, toString()
등의 메서드를 자동으로 생성합니다.)
SampleController
의 메서드가 SampleDTO
를 파라미터로 사용하게 되면, 자동으로 setter 메서드가 동작하며 파라미터를 수집합니다. 컨트롤러에 다음 코드를 추가하고, URL 뒤에 파라미터를 붙여 GET 요청을 해보겠습니다.
@GetMapping("/ex01")
public String ex01(SampleDTO dto) {
log.info("" + dto);
return "ex01";
}
실행된 결과를 보면 SampleDTO
객체 안에 name
과 age
속성이 제대로 수집된 것을 볼 수 있습니다.
SampleDTO
에서 @Data
어노테이션을 사용하지 않고 직접 Getter와 Setter 메서드를 작성하고, 로그를 찍어보겠습니다.
@Log4j
public class SampleDTO {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
log.info("----- setName() called -----");
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
log.info("---- getAge() called -----");
}
}
이렇게 작성하고 다시 파라미터를 URL 뒤에 붙여서 다시 요청을 보내면 다음과 같이 set 메서드가 호출되고 있음을 확인할 수 있습니다.
여기서 중요한 점은, Controller가 파라미터 타입에 따라 자동으로 변환하여 파라미터를 수집한다는 것입니다. SampleDTO
에 int 타입으로 선언된 age
가 자동으로 숫자로 변환되었습니다.(URL 뒤에 파라미터로 전달된 것은 모두 String으로 전달되지만 int 타입으로 자동으로 변환되었습니다.)
2. @RequestParam
@RequestParam
은 파라미터로 사용된 변수의 이름과 전달되는 파라미터의 이름이 다른 경우에 유용하게 사용됩니다. 예제로 Controller 코드에 다음 코드를 추가한 후, URL을 통해 파라미터를 전달해보겠습니다.
@GetMapping("/ex02")
public String ex02(@RequestParam("id") String name, @RequestParam("age") int age) {
log.info("name: " + name);
log.info("age: " + age);
return "ex02";
}
이때, URL에서는 파라미터가 name
이 아닌 id
와 age
입니다. 하지만 @RequestParam
어노테이션으로 id
로 전달된 파라미터 값이 name
에 할당된 것을 확인할 수 있습니다.
3. 리스트, 배열 처리
동일한 이름의 파라미터가 여러 개 전달되는 경우에는 ArrayList<>
등을 이용해서 처리가 가능합니다.
@GetMapping("ex03List")
public String ex03List(@RequestParam("ids") ArrayList<String> ids) {
log.info("ids: " + ids);
return "ex03List";
}
@GetMapping("ex03Array")
public String ex03Array(@RequestParam("ids") String[] ids) {
log.info("array ids: " + Arrays.toString(ids));
return "ex03Array";
}
4. 객체 리스트
파라미터로 전달하는 데이터가 여러 개의 객체 타입일 때도 한 번에 처리 할 수 있습니다. 예를 들어 SampleDTO
를 여러 개 전달받아서 처리하고 시팓면 SampleDTO
리스트를 포함하는 SampleDTOList
클래스를 설계합니다.
import java.util.List;
import java.util.ArrayList;
import lombok.Data;
@Data
public class SampleDTOList {
private List<SampleDTO> dtoList;
public SampleDTOList() {
dtoList = new ArrayList<>();
}
}
그리고 SampleController
에서 SampleDTOList
타입을 파라미터로 사용하는 메서드를 생성합니다.
@GetMapping("ex04Bean")
public String ex04Bean(SampleDTOList dtoList) {
log.info("list dtos: " + dtoList);
return "ex04Bean";
}
http://localhost:8080/ex01_war/sample/ex04Bean?dtoList%5B0%5D.name=zero&dtoList%5B0%5D.age=0&dtoList%5B2%5D.name=two
파라미터는 []
안에 인덱스를 사용해 전달하는데, 톰캣이 []
를 특수문자로 허용하지 않으므로 [
는 %5B로, ]
는 %5D로 바꾸어 입력합니다. 이렇게 GET 요청을 보내면 다음과 같이 여러 개의 SampleDTO
객체가 생성됨을 볼 수 있습니다.
5. @InitBinder
파라미터 수집을 다른 용어로는 binding이라고 합니다. 변환이 가능한 데이터는 자동으로 변환되지만, 경우에 따라 파라미터를 변환해서 처리해야 하는 경우가 있습니다. '2018-01-01'로 전달된 파라미터를 java.util.Date
타입으로 변환해야 하는 경우를 예로 들 수 있습니다. 이 때, 스프링 컨트롤러에서는 파라미터를 바인딩할 때 자동으로 호출되는 @InitBinder
를 이용해서 이러한 변환을 처리할 수 있습니다.
예시로, 멤버 변수로 dueDate
를 갖는 TodoDTO
클래스를 작성해보겠습니다.
import lombok.Data;
import java.util.Date;
@Data
public class TodoDTO {
private String title;
private Date dueDate;
}
컨트롤러에는 @InitBinder
메소드를 사용하여 initBinder
메서드를 작성하고, TodoDTO
를 매개변수로 받는 메서드도 작성했습니다.
public class SampleController {
@InitBinder
public void initBinder(WebDataBinder binder) {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
binder.registerCustomEditor(java.util.Date.class, new CustomDateEditor(dateFormat, false));
}
...
@GetMapping("ex05")
public String ex03(TodoDTO todo) {
log.info("todo: " + todo);
return "ex05";
}
}
파라미터 수집 후, java.util.Date
타입으로 잘 바인딩 된 것을 확인할 수 있습니다.
+) @DateTimeFormat
- @InitBinder
를 이용해서 날짜를 변환할 수도 있지만, 파라미터로 사용되는 인스턴스 변수에 @DateTimeFormat
을 적용해도 변환이 가능합니다.
import java.util.Date;
@Data
public class TodoDTO {
private String title;
@DateTimeFormat(pattern = "yyyy/MM/dd")
private Date dueDate;
}
4. Model
1. Model
객체
Controller의 메서드를 작성할 때 Model
타입을 파라미터로 지정할 수 있습니다. Model
객체는 JSP에 컨트롤러에서 생성된 데이터를 담아서 전달하는 역할을 하는 존재입니다. Model
로 JSP 같은 뷰(View)로 전달해야 하는 데이터를 담아서 보낼 수 있습니다. 메서드의 파라미터에 Model
타입이 지정된 경우에는 스프링은 Model
타입의 객체를 만들어서 메서드에 주입해줍니다.
Model은 모델2 방식에서 사용하는 request.setAttribute()와 유사한 역할을 합니다. Servlet에서 모델2 방식으로 데이터를 전달하는 방식과 Model 을 이용해서 처리하는 방식은 각각 다음과 같습니다.
- Servlet에서 모델2 방식으로 데이터를 전달하는 방식:
request.setAttribute("serverTime", new java.util.Date());
RequestDispatcher dispatcher = request.getRequestDispatcher("/WEB-INF/view/home.jsp");
dispatcher.forward(request, response);
- 스프링 MVC에서 Model을 이용해서 데이터를 전달하는 방식:
public String home(Model model) {
model.addAttrubute("serverTime", new java.util.Date());
return "home";
}
2. @ModelAttribute
@ModelAttribute
는 강제로 전달받은 파라미터를 Model
에 담아서 전달할 때 사용하는 어노테이션입니다. @ModelAttribute
가 걸린 파라미터는 타입에 관계없이 무조건 Model
에 담아서 잔달되므로 파라미터로 전달된 데이터를 다시 화면에서 사용할 때 유용합니다. 예를 들어, SampleDTO
객체와, page
를 같이 전달해야 하는 상황을 생각해봅시다.
@GetMapping("ex06")
public String ex06(SampleDTO dto, int page) {
log.info("dto: " + dto);
log.info("page: " + page);
return "ex06";
}
전달한 파라미터 값이 dto
와 page
에 잘 들어가지만, page
는 화면(view)까지 전달되지 않습니다. 왜냐하면 스프링 MVC의 컨트롤러는 기본적으로 자바 Beans 규칙에 맞는 객체를 다시 화면으로 객체를 전달하기 때문입니다. 따라서 기본 자료형의 경우, 파라미터로 선언하더라도 화면까지 전달되지는 않습니다. View를 작성하여 확인해보겠습니다. (views/ex06.jsp 작성)
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h2>SampleDTO ${sampleDTO}</h2>
<h2>PAGE ${page}</h2>
</body>
</html>
SampleDTO
는 화면까지 데이터가 잘 전달되지만 page
는 전달되지 않습니다. 이때 page
도 Model
에 담아서 전달하기 위해서 @ModelAttribute
를 사용하면됩니다.
@GetMapping("ex06")
public String ex06(SampleDTO dto, @ModelAttribute("page") int page) {
log.info("dto: " + dto);
log.info("page: " + page);
return "ex06";
}
5. 컨트롤러의 리턴 타입
컨트롤러의 메서드가 사용할 수 있는 리턴 타입은 주로 다음과 같습니다.
String
: jsp 파일 이름을 나타내기 위해 사용합니다.String
타입은 상황에 따라 다른 화면을 보여줄 필요가 있을 경우 유용하게 사용합니다.
예시로 HomeController.java
를 보면 home
을 리턴하여 'WEB-INF/views/home.jsp
' 파일이 화면에 보이는 것을 알 수 있습니다.
@Controller
public class HomeController {
@RequestMapping("/")
public String home(Model model) {
model.addAttribute("greeting", "hello world");
return "home";
}
}
이때, String
타입에는 redirect
, forward
같은 키워드를 붙여서 사용할 수 있습니다.
void
: 호출하는 URL의 경로와 동일한 이름을 jsp 파일명으로 사용합니다.
아래 예시에서 ex07
의 리턴 타입이 void
이므로 URL(/sample/ex07
)과 같은 경로인(/WEB-INF/views/sample/ex07.jsp
) jsp 파일을 찾습니다. (/WEB-INF/views
와 .jsp
는 viewResolver
에서 지정한 prefix와 suffix입니다.)
@GetMapping("ex07")
public void ex07() {
log.info("---------- ex07 ----------");
}
VO
(Value Object),DTO
(Data Transfer Object) 타입(객체 타입): 주로 JSON 타입의 데이터를 만들어서 변환하는 용도로 사용합니다.
이를 위해 jackson-databind
라이브러리를 pom.xml
에 추가하고, SampleController
에 리턴타입이 SampleDTO
인 메서드를 작성해보겠습니다.
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.11.4</version>
</dependency>
@GetMapping("ex08")
public @ResponseBody SampleDTO ex08() {
log.info("----- ex08 -----");
SampleDTO dto = new SampleDTO();
dto.setAge(20);
dto.setName("eunbin");
return dto;
}
브라우저가 get 요청을 보냈을 때, 스프링 MVC가 자동으로 브라우저에 JSON 타입으로 객체를 변환해서 전달하는 것을 확인할 수 있습니다.
ResponseEntity
타입: response할 때 Http 헤더 정보와 내용을 가공하고 싶을 때 사용합니다. (스프링 MVC의 사상이HttpServletRequest
와HttpServletResponse
를 직접 핸들링하지 않는 것이지만 이런 작업이 가능하긴 합니다.)ResponseEntity
는HttpHeaders
객체를 같이 전달할 수 있고, 따라서 원하는 헤더 정보나 데이터를 전달합니다.
@Getmapping("ex09")
public ResponseEntity<String> ex09() {
log.info("----- ex09 -----");
String msg = "{\"name\": \"홍길동\"}";
HttpHeaders header = new HttpHeaders();
header.add("Content-Type", "application/json;charset=UTF-8");
return new ResponseEntity<>(msg, header, HttpStatus.OK);
}
Model
,ModelAndView
타입: Model로 데이터를 반환하거나 화면까지 같이 지정할 때 사용합니다. (최근에는 많이 사용하지 않음)HttpHeaders
타입: 응답에 내용없이 Http 헤더 메시지만 전달하고 싶을 때 사용합니다.
'Java > Spring' 카테고리의 다른 글
[Spring MVC] 컨트롤러의 Exception 처리 (0) | 2023.01.03 |
---|---|
[Spring MVC] 파일업로드 처리 (0) | 2023.01.03 |
[Spring MVC] 기본 구조 (0) | 2023.01.02 |
[Spring] MyBatis와 스프링 연동 (+log4jdbc) (0) | 2023.01.02 |
[Spring] JDBC(MySQL) 연결 (0) | 2023.01.02 |
댓글