읽은 책 정리/코드로 배우는 스프링 웹 프로젝트

[Spring] 스프링의 MVC의 Controller

포포015 2020. 12. 15. 13:53
 

스프링 컨트롤러는 다음과 같은 특징이 있다

1. jsp에서 쓰던 HttpServletRequest,HttpServletResponse를 거의 사용할필요없이 기능구현

2. 다양한 타입의 파라미터 처리, 다양한 타입의 리턴타입 사용가능

3. get,방식 post방식 등 전송방식에 대한 처리를 어노테이션 처리가능 

4. 상속/인터페이스 방식 대신에 어노테이션만으로도 필요한 설정기능

 

컨트롤러 클래스에는 @Controller 어노테이션을 필수로 적용 / 그래야 자동으로 스프링의 객체로 등록됨

그 이유는 servlet-context.xml에 <context:component-scan>이라는 태그를 이용해 스캔해서 객체로 생성함.

1
2
3
4
5
6
7
8
9
10
11
package org.zerock.controller;
 
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
 
@Controller
@RequestMapping("/sample/*")
public class SampleController {
 
}
 
cs

- 스프링에서 관리되면 화면상에선 클래스옆에 작게 's' 모양의 아이콘 추가됨

 

@Controller 어노테이션은 추가적인 속성은 지정불가 , @RequestMapping의 경우 몇가지 속성 추가 가능

가장 많이 사용하는 method 속성은 흔히 GET방식,POST 방식을 구분해서 사용하는데

스프링 4.3부턴 줄여서 사용할수 있는 @GETMapping , @PostMapping이 등장해서 사용가능.

일반적인 경우 GET,POST 방식을 사용하지만 최근에는 PUT,DELETE방식도 점점 많이 사용중.

@GetMapping은 간편하지만 GET방식만 사용가능하므로 기능제약이 많은편

1
2
3
4
5
6
7
8
    @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...");
    }
cs

 

*Controller의 파라미터 수집

Controller를 작성할때 가장 편리한기능은 파라미터가 자동으로 수집됨.

예제를 위해 org.zerock.domain패키지를 생성하고,SampleDTO 클래스 작성.

Lombok의 @Data 어노테이션을 이용해 처리, @Data를 이용하면 getter,setter,equals(),toString()등 메서드 자동생성

1
2
3
4
5
6
7
8
9
10
package org.zerock.domain;
 
import lombok.Data;
 
@Data
public class SampleDTO {
    private String name;
    private int age;
}
 
cs

Controllr에서 ex01메서드가 SampleDTO를 파라미터로 사용하게되면

자동으로 setter 메서드가 동작하면서 파라미터수집

1
2
3
4
5
@GetMapping("/ex01")
    public String basicGet2(SampleDTO dto) {
        log.info(""+dto);
        return "ex01";
    }
c

 

주목할점은 자동으로 타입을 변환해서 처리해줌. Controller가 파라미터를 수집하는 방식은 

파라미터 타입에 따라 자동으로 변환하는 방식을 이용.

만약 파라미터로 사용된 변수의 이름과 전달되는 파라미터의 이름이 다른경우 @RequestParam 을 사용

*리스트,배열처리

동일한 이름의 파라미터가 여러개 전달되는 경우ArrayList<>등을 이용해 처리가능

스프링은 파라미터의 타입을 보고 객체를 생성하므로 파라미터타입은

List<>같이 인터페이스 타입이 아닌 실제적인 클래스타입으로 지정

아래의 코드같연궁으 'ids'라는 이름의 파라미터가 여러개 전달되어도 ArrayList<String>이 생성되어 자동수집됨

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
   @GetMapping("/ex02List")
    public String ex02List(@RequestParam("ids")ArrayList<String> ids) {
        log.info("ids:" + ids);
        return "ex02List";
    }
//배열의 경우도 동일하게 처리가능
@GetMapping("/ex02Array")
    public String ex02Array(@RequestParam("ids"String[] ids) {
        log.info("array ids:" + Arrays.toString(ids));
        return "ex02Array";
    }
cs

*객체 리스트

만일 전달하는 데이터가 SampleDTO 같이 객체타입이고 여러개 처리해야한다면 

SampleDTO의 리스트를 포함하는 SampleDTOList 클래스를 설계함

1
2
3
4
5
6
7
8
@Data
public class SampleDTOList {
    private List<SampleDTO> list;
    
    public SampleDTOList() {
        list = new ArrayList<>();
    }
}
cs

SampleControlle에 추가 파라미터는 '[인덱스]'와 같은 형식으로 전달해서 처리할수 있으나

http://localhost:8080/sample/ex02Bean?list[0].name=aaa&list[2].name=bbb

Tomcat 버전에 따라 위와같이 문자열에서 '[]'문자를 특수문자로 허용하지않을수 있음

자바스크립트를 이용해 encodeURIComponent()와 같은방법으로 해결할수 있으나 테스트는

list%5B0%5D.name=aaa&list%5B1%5D.name=BBB 같은식으로 전송하면 여러개의 객체를 생성한다

1
2
3
4
5
@GetMapping("/ex02Bean")
    public String ex02Bean(SampleDTOList list) {
        log.info("list dtos:"+ list);
        return "ex02Bean";
    }
cs

 

* @Model 이라는 데이터 전달자

Model 객체는 jsp에 컨트롤러에서 생성된 데이터를 담아서 전달하는 역할을 하는 존재.

모델2 방식에서 사용하는 request.setAttribute()와 유사한 역할

@Model의 경우는 파라미터로 전달된 데이터는 존재하지 않지만

화면에서 필요한 데이터를 전달하기 위해 사용

예를들어 페이지번호는 파라미터로 전달되지만 ,결과데이터를 전달하려면 Model에 담아서 전달

 

* @ModelAttribute 어노테이션

웹 페이지구조는 Request에 전달된 데이터를 가지고 필요하면 추가적인 데이터를 생성해서 화면으로 전달

기본 자료형의 경우는 파라미터로 선언하더라도 기본적으로 화면까지 전달되지 않음

아래와 같은 메소드는 dto는 전달되더라도 기본 자료형인 page는 파라미터로 선언하더라도 화면까지 전달안됨

1
2
3
4
5
6
@GetMapping("/ex04")
    public String ex04(SampleDTO dto,int page) {
        log.info("dto"+ dto);
        log.info("page"+ page);
        return "/sample/ex04";
    }
cs

@ModelAttribute는 강제로 전달 받은 파라미터를 Model에 담아서 전달하도록 할대 필요한 어노테이션

타입에 관계 없이 무조건 Model에 담아서 전달됨 

 

 아래와같이 사용해야함 @ModelAttribute("page")와 같이 값(value)를 지정.

1
2
3
4
5
6
    @GetMapping("/ex04")
    public String ex04(SampleDTO dto,@ModelAttribute("page")int page) {
        log.info("dto"+ dto);
        log.info("page"+ page);
        return "/sample/ex04";
    }
cs

 

* @RedirectAttributes

일회성으로 데이터를 전달하는 용도로 사용

 

Controller의 리턴타입

 - Stirng : jsp를 이용하는경우에는 jsp파일의 경로와 파일이름을 나타내기 위해서 사용(redirect,forward지정가능)

 - void : 호출하는 URL과 동일한 이름의 jsp를 의미

 - VO,DTO 타입: 주로 JSON 타입의 데이터를 만들어서 반환하는 용도로 사용

 - ResponseEntity 타입: response할때 Http 헤더 정보와 내용을 가공하는 용도로 사용

 - Model,ModelAndView : Model로 데이터를 반환하거나 화면까지 같이 지정하는경우 사용

 - HttpHeaders : 응답에 내용없이 Http 헤더 메시지만을 전달하는 용도

 

*파일 업로드 처리

Servlet 3.0전까지는 commons의 파일업로드를 이용하거나 cos.jar등을 이용해 처리 했음.

'Spring Legacy Project'로 생성되는 프로젝트의 경우 Servlet 2.5를 기준으로 생성되기때문에  

일반적으로 많이 사용하는 commos-fileupload를 이용하는 예제를 작성해보겠음

  라이브러리 추가후 파일이 임시로 업로드될 폴더를 C드라이브 아래 upload/tmp로 생성

1
2
3
4
5
6
7
<!-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload -->
<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.3.3</version>
</dependency>
 
cs

 

//servlet-context설정 다른 객체를 설정하는것과 달리 파일업로드의 경우 반드시 id속성의 값을

'multipartResolver'로 정확하게 지정해야함

1
2
3
4
5
6
7
8
<beans:bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <beans:property name="defaultEncoding" value="utf-8"></beans:property>
        <!-- 1024 * 1024 *10 bytes 10MB -->
        <beans:property name="maxUploadSize" value="104857560"></beans:property>
        <beans:property name="maxUploadSizePerFile" value="2097152"></beans:property>
        <beans:property name="uploadTempDir" value="file:/C:/upload/tmp"></beans:property>
        <beans:property name="maxInMemorySize" value="10485756"></beans:property>
    </beans:bean>
cs

- defaultEncoding은 업로드하는 파일의 이름이 한글일경우 깨지는 문제를 처리

- maxUploadSize는 한번에 Request로 전달될수 있는 최대 크기를 의미

- maxUploadSizePerFile은 하나의 파일 최대크기

- maxInMemorySize는 메모리 상에서 유지하는 최대의 크기를 의미 (만일 이크기 이상데이터는 uploadTempDir에 저장

 

//exUpload.jsp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<form action="/sample/exUploadPost" method="post" enctype="multipart/form-data">
 
<div>
    <input type='file' name='files'>
</div>
 
<div>
    <input type='file' name='files'>
</div>
 
<div>
    <input type='file' name='files'>
</div>
 
<div>
    <input type='file' name='files'>
</div>
<input type='submit'>
</form>
cs

여러개의 파일을 한꺼번에 업로드하는 예제로 작성 속성에 주의해서 작성. 

form에서 파일을 업로드해서 /exUploadPost로 보내주니 컨트롤러에서 Post메서드 작성(테스트 코드)

1
2
3
4
5
6
7
8
9
    @PostMapping("/exUploadPost")
    public void exUploadPost(ArrayList<MultipartFile> files) {
        files.forEach(file ->{
            log.info("-----");
            log.info("name:" + file.getOriginalFilename());
            log.info("size:"+ file.getSize());
        });
    }
 
cs

 

*Controller의 Exception 처리

 - @ExceptionHandler와 @ControllerAdvice를 이용한처리

 - @ResponseEntity를 이용하는 예외 메시지 구성

 1. @ControllerAdvice 는 AOP를 이용하는 방식 공통적인 예외사항에 대해서는 별도로 어노테이션을 이용해 분리

 

예제를 위해 프로젝트에 org.zerock.excpetion이라는 패키지 생성하고 클래스 생성.

예외 처리를 목적으로 생성하는 클래스이므로 별도의 로직x

1
2
3
4
5
6
7
8
9
10
11
12
13
@ControllerAdvice
@Log4j
public class CommonExceptionAdvice {
 
    @ExceptionHandler(Exception.class)
    public String except(Exception ex, Model model) {
        log.error("Exception ...." + ex.getMessage());
        model.addAttribute("exception", ex);
        log.error(model);
        return "error_page";
    }
}
 
cs

@ControllerAdvice는 해당객체가 스프링의 컨트롤러에서 발생하는 예외를 처리하는 존재임을 명시

@ExceptionHandler는 해당 메서드가 () 들어가는 예외타입을 처리한다는것을 의미

만일 jsp화면에서 구체적인 메시지를 보고싶다면 Model을 이용해서 전달해야함 

예제의 패키지는 servlet-context.xml에서 인식하지 않기떄문에 <component-scan>을 이용해 내용을 조사하도록해야함

 

아래와 같이 error_page.jsp를 생성하고 파라미터 값을 변환에 문제있게 호출해보면

예를들어 int page 값을 String 값으로 파라미터를 보내면 예외의 로그가 찍힌다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h4><c:out value="${exception.getMessage()}"></c:out></h4>
 
<ul>
<c:forEach items="${exception.getStackTrace()}" var="stack">
    <li><c:out value="${stack}"></c:out></li>
</c:forEach>
</ul>
</body>
</html>
cs

* 404 에러 페이지

WAS의 구동중 가장 흔한 에러와 관련된 HTTP 상태 코드는 '404' '500'에러 코드이다

500 메시지는 'Internal Server Error'이므로 @ExceptionHandler를 이용해 처리되지만,

404 메시지는 잘못된 URL을 호출할때 이기때문에 web.xml을 이용해 별도의 에러페이지를 지정할수 있다\

 

 // 스프링 mvc의 모든 요청은 DispatcherServlet을 이용해서 치뢰므로 404에러도 같이 처리하도록 web.xml 수정

 param-name > throwExceptionIfNoHandlerFound 추가

1
2
3
4
5
6
7
8
9
10
11
12
13
14
    <!-- Processes application requests -->
    <servlet>
        <servlet-name>appServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value>
        </init-param>
        <init-param>
            <param-name>throwExceptionIfNoHandlerFound</param-name>
            <param-value>true</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
cs

 

CommonExceptionAdvice 클래스에 메서드 추가하면 모든 404에러는 custom404.jsp 페이지가 보이도록 할수있다

1
2
3
4
5
@ExceptionHandler(NoHandlerFoundException.class)
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public String handle404(NoHandlerFoundException ex) {
        return "/custom404";
    }
cs
404 발생시 보이는 웹 페이지