Sangwon Coding

6. 스프링 MVC의 Controller (1) 본문

Spring/코드로 배우는 스프링 웹 프로젝트

6. 스프링 MVC의 Controller (1)

SW1 2019. 11. 8. 00:20

스프링MVC를 이용하는 경우 작성되는 Controller는 다음과 같은 특징이 있습니다.

  • HttpServletRequest,HttpServletResponse를 거의 사용할 필요가 없이 필요한 기능 구현
  • 다양한  타입의 파라미터 처리,다양한 타입의 리턴 타입 사용가능
  • GET방식,POST방식 등 전송 방식에 대한 처리를 어노테이션으로 처리가능
  • 상속/인터페이스 방식 대신에 어노테이션으로도 필요한 설정 기능

 

프로젝트 내 'org.zerock.controller'패키지 폴더에 SampleController클래스를 생성합니다.

 

 

package org.zerock.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 {
 
}

 

SampleController의 클래스 선언부에는 @Controller라는 스프링 MVC에서 사용하는 어노테이션을 적용하고 있습니다. 작성된 SampleController클래스는 저번 포스팅의 그림 처럼 자동으로 스프링 객체(Bean)로 등록되는데 servlet-context.xml에 그 이유가 있습니다.

 

servlet-context.xml마지막 부분

 

<context:component-scan base-package="org.zerock.controller" />

</beans:beans>

 

servlet-context.xml에는 <context:component-scan>이라는 태그를 이용해서 지정된 패키지를 조사(스캔) 하도록 설정 되어있습니다. 해당 패키지에 선언된 클래스들을 조사하면서 스프링에서 객체(Bean) 설정에 사용되는 어노테이션들을 가진 클래스들을  파악하고 필요하다면 이를 객체로 생성 해서 관리하게 됩니다.

 

클래스 선언부에는@Controller와 함께@RequstMappring을 많이 사용합니다.

 

@RequstMappring은 현재 클래스의 모는 메서드들의 기본적인 URL 경로가 됩니다.

예를 들어, SampleController클래스를 다음과 같이 '/sample/*'이라고 경로를 지정하면 다음과 같은 URL은 모두SampleController에서 처리가 됨.

  • /sample/ddd
  • /sample/aaa

 

@RequstMappring 어노테이션은 클래스의 선언과 메서드 선언에 사용할 수 있습니다.

 

package org.zerock.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의 @Log4j를 사용합니다. @Log4j는 @Log가 java.util.Logging을 이용하는데 반해 Log4j 라이브러리를 활용합니다. Spring Legacy Project로 하면 기본적으로 Log4j가 추가되어있으므로 별도 설정이 필요가 없습니다.

 

@Controller어노테이션은 추가적인 속성을 지정할 수 없지만, @RequestMapping의 경우 몇 가지의 속성을 추가할 수 있습니다. 이 중에서도 가장 많이 사용하는 속성이 method 속성입니다. Method 속성은 흔히 GET방식, POST방식을 구분해서 사용할 때 이용합니다.

스프링 4.3버전 부터는 이러한 @RequestMapping을 줄여서 사용할 수 있습니다. @GetMapping, @PostMapping이 등장하는데 축약형의 표현이므로, 아래와 같습니다.

 

    @RequestMapping(value="/basic", method= {RequestMethod.GET,RequestMethod.POST})

    public void basicGet1() {

        log.info("get basic ..........");

    }

    @GetMapping("/basicOnlyGet")

    public void basicGet2() {

        log.info("basic get only get ..........");

    }

 

@RequestMapping은 GET, POST방식모두를 지원해야 하는 경우에 배열로 처리해서 저장할 수 있습니다. 일반적인 경우에만 GET, POST방식만을 사용하지만 최근에는 PUT, DELETE방식 등도 점점 많이 사용하고 있습니다.

@GetMapping의 경우 오직 GET방식에만 사용할 수 있으므로, 간편하기는 하지만 기능에 대한 제한은 많은 편입니다.

 

 

Controller를 작성할 때 가장 편리한 기능은 파라미터가 자동으로 수집되는 기능입니다. 이 기능을 이용하면 매번 request.getParameter()를  이용하는 불편함을 없앨 수 있습니다.

 

예제를 위해 'org.zerock.domain'패키지를 만들고 SampleDTO클래스를 만들어줍니다.

 

 

package org.zerock.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메서드가 동적하면서 파라미터를 수집하게 됩니다. (이를 확인하고 싶다면 직접set메서드를 제작하고 set메서드 내 간단한 로그 등을 출력해 보면 확인할 수 있습니다.)

 

package org.zerock.controller;

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 org.zerock.domain.SampleDTO;

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/'라는 경로로 동작하므로 , 이를 '/'로 동작하도록 변경해서 실행해야 합니다.)

 

 

Controller가 파라미터를 수집하는 방식은 파라미터 타입에 따라 자동으로 변환하는 방식을 이용합니다. 예를 들어, SampleDTO에는 int 타입으로 선언된 age가 자동으로 숫자로 변환되는 것을 볼 수 있습니다.

 

만일 기본 자료형이나 문자열 등을 이용한다면 파라미터의 타입만을 맞게 선언해주는 방식을 사용할 수 있습니다.

 

    --추가 코드--

    @GetMapping("/ex02")
    public String ex02(@RequestParam("name") String name,@RequestParam("age") int age) {

        System.out.println("name: "+ name);

        System.out.println("age: "+ age);



        return "ex02";

    }

ex02( )메서드 파라미터에 @ReauestParam어노테이션을 사용해서 작성되었는데, @ReauestParam은 파라미터로 사용된 변수의 이름과 전달되는 파라미터의 이름이 다른 경우에 유용하게 사용됩니다. (지금 예제의 경우 변수명과 파라미터의 이름이 동일하기 때문에 사용할 필요는 없었지만 @ReauestParam의 소개 차원에서 사용해 보았습니다.)

 

브라우저에서 http://localhost:8080/sample/ex01?name=AAA&age=10과 같이 호출하면 이전과 동일하게 데이터가 수집 된 것을 볼 수 있습니다.

 

 

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

 

SampleController클래스에 일부 추가해보겠습니다. 

 

	--추가코드--
   
    @GetMapping("/ex02List")
    public String ex02List(@RequestParam("ids") ArrayList<String> ids) {

        System.out.println("ids: "+ ids);

    

        return "ex02List";

    }

 

스프링은 파라미터의 타입을 보고 객체를 생성하므로 파라미터의 타입은 List<>와 같이 인터페이스 타입이 아닌 실제적인 클래스 타입으로 지정합니다. 위 코드의 경우 'ids'라는 이름의 파라미터가 여러 개 전달 되더라도 ArrayList<String>이 생성되어 자동으로 수집됩니다.

브라우저 등을 이용해서 '프로젝트 경로/sample/ex02List?ids=111&ids=222&ids=333'을 호출하면 아래와 같은 결과를 볼 수 있습니다.

 

 

배열의 경우도 동일하게 처리할 수 있고 결과는 같습니다.

 

    --추가 코드--
    
    @GetMapping("/ex02Array")    
    public String ex02Array(@RequestParam("ids") String[]ids) {

    System.out.println("array ids: "+ Arrays.toString(ids));

        return "ex02Array";    

    }

 

 

 

만일 전달하는 데이터가 SampleDTO와 같이 객체 타입이고 여러 개를 처리해야 한다면 약간의 작업을 통해서 한 번에 처리를 할 수 있습니다. 예를 들어 SampleDTO를 여러 개 전달받아서 처리하고 싶다면 다음과 같이 SampleDTO의 리스트를 포함하는 SampleDTOList클래스를 설계합니다.

 

 

package org.zerock.domain;

import lombok.Data;

@Data
public class SampleDTO {

	private String name;
	private int age;
}

 

package org.zerock.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<>(); 
    }
}

 

package org.zerock.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;
}

 

SampleController에서는 SampleDTOList 타입을 파라미터로 사용하는 메서드를 작성합니다.

 

	@GetMapping("/ex02Bean")
	public String ex02Bean(SampleDTOList list) {
		System.out.println("list dtos: " + list);
		return "ex02Bean";
	}

 

파라미터는 '[인덱스,]'와 같은 형식으로 전달해서 처리할 수 있습니다.

 

전송하려고 하는 URL :

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

 

Tomcat은 버전에 따라서 위와 같은 문자열에서 '[ ]'문자를 특수문자로 허용하지 않을 수 있습니다.

 

JavaScript를 이용하는 경우에는 encodeURIComponent()와 같은 방법으로 해결할 수 있으나 현재 예제의 경우에는 '['는 '%5B'로']'는 '%5D'로 변경하도록 합시다!

 

http://localhost:8080/sample/ex02Bean?list%5B0%5D.name=aaa&list%5B1%5D.name=bbb&list%5B2%5D.name=ccc

 

위의  URL을 호출하면 다음과 같이 여러 개의 SampleDTO 객체를 생성하는 것을 볼 수 있습니다.

 

 

출력된 결과를 보면 3개의 SampleDTO 객체가 생성된 것을 볼 수 있고,'[ ]'안에 인덱스 번호에 맞게 객체의 속성값이 세팅 된 것을 확인 할 수 있습니다.

 

@InitBinder를 설명드리자면 파라미터의 수집을 다른 용어로는 'binding(바인딩)'이 라고 합니다. 변환이 가능한 데이터는 자동으로 변환되지만 경우에 따라서는 파라미터를 변환해서 처리해야 하는 경우도 존재합니다. 예를 들어, 화면에서 '2018-01-01'과 같이 문자열로 전달된 데이터를 java.util.Date타입으로 변환하는 작업이 그러합니다. 스프링 Controller에서는 파라미터를 바인딩할 때 자동으로 호출되는 @InitBinder를 이용해서 이러한 변환을 처리할 수 있음.

 

TodoDTO클래스에는 특별하게 dueDate 변수의 타입이 java.util.Date타입입니다. 만일 사용자가 '2018-01-01'과 같이 들어오는 데이터를 변환하고자 할 때 문제가 발생하게 됩니다. 이러한 문제의 간단한 해결책은 @InitBinder를 이용하는 것입니다.

 

SampleController에 @InitBinder를 추가해줍니다.

 

package org.zerock.controller;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
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 org.zerock.domain.SampleDTO;
import org.zerock.domain.SampleDTOList;
import org.zerock.domain.TodoDTO;

@Controller
@RequestMapping("/sample/*")
@Log4j


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("/ex03")
    public String ex03(TodoDTO todo) {
        System.out.println("todo: "+todo);
        return "ex03";
    }

}

만일 브라우저에 'http://localhost:8080/sample/ex03?title=test&dueDate=2018-01-01'과 같이 입력했다면 서버에서는 정상적으로 파라미터를 수집해서 처리합니다.

 

 

 

@InitBinder를 이용해서 날짜를 변환할 수도 있지만 ,파라미터로 사용되는 인스턴스변수에 @DateTimeFormat을 적용해도 변환이 가능합니다.(@DateTimeFormat을 이용하는 경우에는 @InitBinder는 필요하지 않습니다.)

 

TodoDTO클래스를 변경해줍니다.

 

package org.zerock.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'의 형식에 맞다면 자동으로 날짜 타입으로 변환이 됩니다.

(InitBinder는 주석처리 해줍시다!)

 

 

 

이상으로 포스팅을 마치고 다음 포스팅은 스프링 MVC의 Controller 이어서 진행하겠습니다~!

Comments