[Spring MVC] Controller

    반응형

     

    이 글은 코드로 배우는 스프링 웹 프로젝트(남가람북스, 구멍가게 코딩단)을 읽고 공부한 내용을 바탕으로 정리한 글입니다.


     

    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 객체 안에 nameage 속성이 제대로 수집된 것을 볼 수 있습니다. 

    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이 아닌 idage입니다. 하지만 @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";
    }

    전달한 파라미터 값이 dtopage에 잘 들어가지만, 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는 전달되지 않습니다. 이때 pageModel에 담아서 전달하기 위해서 @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.jspviewResolver에서 지정한 prefix와 suffix입니다.) 

    @GetMapping("ex07")
    public void ex07() {
        log.info("---------- ex07 ----------");
    }

    localhost:8080/ex01_war/sample/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의 사상이 HttpServletRequestHttpServletResponse를 직접 핸들링하지 않는 것이지만 이런 작업이 가능하긴 합니다.) ResponseEntityHttpHeaders 객체를 같이 전달할 수 있고, 따라서 원하는 헤더 정보나 데이터를 전달합니다.
    @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

    댓글