web

코드로 배우는 스프링웹프로젝트 - 06 스프링 MVC의 Controller

늉_늉 2020. 10. 19. 14:41

Chap06  스프링 MVC의 Controller

 

 

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

 

 

 - HttpServletRequest, HttpServletResponse를 거의 사용할 필요없이 필요한 기능구현

 

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

 

-  Get방식, POST방식 등 전송방식등에 대한 처리를 어노테이션으로 처리가능

 

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

 

 

 

 

6.1 @Cotroller  , @RequestMapping

 

 

@RequestMapping   은 현재 클래스의 모든 메서드들의 기본적인 URL주소가 된다.

    -> 클래스 선언과, 메서드 선언에 사용할 수 있다.

package org.zerock.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import lombok.extern.log4j.Log4j;

@Controller
//@RequestMapping은 현재 클래스의 모든 메서드들의 기본적인 URL경로가 된다.
//SampleControlleㄱ 클래스를 아래와 같이 '/sample/*' 이라는 경로로 지정했다면 다음과같은 URL은 모두 SampleController에서 처리된다

// /sample/aaa
// /sample/bbb
@RequestMapping("/sample/*")
@Log4j
public class SampleController {
	
	@RequestMapping("")
	public void basic() {
		log.info("basic..........................");
	}
	

}

 

 

 

 

 

log4j부분이 에러가 나서 구글링을했더니

<!-- <scope>runtime</scope> -->

 

pom.xml의 runtime 부분을 주석처리했더니 에러는 해결됐다 하지만 왜 주석을 쳐야할까?

 

 

 

프로젝트를 실행하면 아래와 같은 로그가 보인다. src/source 폴더내에 log4j.xml의 info를 debug로 수정하면보인다.

 

 

 

 

현재 프로젝트의 경우 '/'와 '/sample/*'는 호출이 가능한 경로라는것을 확인할 수 있다.

 

 

@RequestMapping 의 변화

 

@Controller 어노테이션은 추가적인 속성을 지정할수 없다. 하지만 @RequestMapping의 경우 몇가지 속성을 추가 할수있다.

 

 

 

스프링 4.3부터는 @RequestMapping 을 줄여서 사용 할 수 있는  @GetMapping  @PostMapping 이 등장하는데 축약형의 

 

표현이므로 아래와 같이 비교해서 학습해 두자

 

 

 

 

 

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 lombok.extern.log4j.Log4j;

@Controller
//@RequestMapping은 현재 클래스의 모든 메서드들의 기본적인 URL경로가 된다.
//SampleControlleㄱ 클래스를 아래와 같이 '/sample/*' 이라는 경로로 지정했다면 다음과같은 URL은 모두 SampleController에서 처리된다

// /sample/aaa
// /sample/bbb
@RequestMapping("/sample/*")
@Log4j
public class SampleController {

	@RequestMapping("")
	public void basic() {
		log.info("basic..........................");
	}

	@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...");

	}
}

 

 

 

실행시켜보면

 

 

 

 

 

localhost:8080/controller/sample결과

 

 

 

 

 

 

 

 

 

http://localhost:8080/controller/sample/basic  

 

결과

 

 

 

 

 

 

 

http://localhost:8080/controller/sample/basicOnlyGet

 

 

 

 

 

 

 

@Controller의 파라미터 수집

 

 

controller 작성할때 가장 편리한 기능은 파라미터가 자동으로 수집되는 기능이다.

 

이 기능을 활용하면 매번 requset.getParameter()를 이용하는 불편함을 없앨 수 있다.

 

ex01 과jex01의 소스코드상의 차이가 없으니  하나만 올려두었다.

 

 

 

 

 

org.zerock.domain클래스 작성후

 

SampleDTO클래스를 만들어 준다.

 

 

 

SampleDTO 클래스는 @Data어노테이션을 이용한다. -> getter/setter 메서드등을 자동으로 생성해준다.

 

 

SampleController의 메서드가 SampleDTO를 파라미터로 사용하게 되면 자동으로 setter 메서드가 동작하면서 파라미터를

 

수집하게된다. 

 

 

 

 

SampleController

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은 현재 클래스의 모든 메서드들의 기본적인 URL경로가 된다.
//SampleControlleㄱ 클래스를 아래와 같이 '/sample/*' 이라는 경로로 지정했다면 다음과같은 URL은 모두 SampleController에서 처리된다

// /sample/aaa
// /sample/bbb
@RequestMapping("/sample/*")
@Log4j
public class SampleController {

	@RequestMapping("")
	public void basic() {
		log.info("basic..........................");
	}

	@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...");

	}
	
	@GetMapping("/ex01")
	public String ex01(SampleDTO dto) {
		
		log.info("" + dto);
		return "ex01";
	}
}

 

 

 

-> 해설하자면 SampleController의 경로가 '/sample/*' 이므로 ex01() 메서드를 호출 하는 경로는 '/sample/ex01' 이 된다.

 

메서드에는 @GetMapping이 사용되었으므로, 필요한 파라미터를 URL 뒤에 '?name=젤리킹&age=999' 와같이 추가해서 호출 할 수있다.

 

 

 

 

 

 

 

 

 

 

 

6.3.1 파라미터의 수집과 변환

 

Controller가 파라미터를 수집 하는 방식은 파라미터 타입에 따라 자동으로 변환하는 방식을 이용한다.

 

ex) 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("name") String nick 이라면

  

파라미터부분은 name = 이 되고 controller에서는 nick으로 받는다.

 

 

 

 

 

 

 

 

 

http://localhost:8080/sample/ex02?name=%EB%9D%BC%EB%9D%BC&age=11

 

 

 

 

 

 

 

 

 

 

http://localhost:8080/sample/ex02?name=%EC%A0%A4%EB%A6%AC&age=999

 

 

주소창에 위와 같이 입력하면

 

 

 

 

아래의 결과값을 얻을 수 있다.

 

 

 

 

 

 

6.3.2 리스트, 배열 처리

 

 

 

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

 

 

 

 

SampleController에 추가한다.

 

	
	@GetMapping("/ex02List")
	public String ex02List(@RequestParam("ids")ArrayList<String> ids) {
		
		log.info("ids: " + ids);
		
		return "ex02List";
	}
	

 

-> 스프링은 파라미터의 타입을 보고 객체를 생성하므로 파라미터의 타입은 List<>와 같이 인터페이스 타입이 아닌 실제적인 

 

클래스 타입으로 지정한다.  위 코드의 경우 'ids' 라는 이름의 파라미터가 여러개 전달 되더라도 ArrayList<String>이 생성되어

 

자동으로 수집된다.

 

 

 

 

 

다음과 같이 입력한다면

 

http://localhost:8080/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";
	}

//  자바에서 배열 내용을 출력하기 위해 배열자체에서 toString()을 사용하면 배열의주소값이 나온다.

 

// 배열의 내용을 출력하려면Array.toString()을 사용해야한다.

 

 

http://localhost:8080/sample/ex02Array?ids=aaa&ids=bbb

 

 

http://localhost:8080/sample/ex02Array?ids=111&ids=222&ids=333

 

 

 

 

 

 

그렇다면

 

 

arrayList랑 배열의 차이는 무엇일까하는 궁금증이 생긴다.

 

 

 

배열은 크기가 고정되어있지만 arrayList는 사이즈가 동적인 배열 (1.5배씩 늘어난다고한다.)

 

배열은 primitive type(int, byte, char 등)과 object 모두를 담을 수 있지만, arrayList는 object element(객체)만 담을 수 있다.

 

 

배열은 제네릭을 사용할 수 없지만, arrayList는 타입 안정성을 보장해주는 제네릭을 사용할 수 있다.

 

 

길이에 대해 배열은 length 변수를 쓰고, arrayList는 size() 메서드를 써야한다.

 

배열은 element들을 할당하기 위해 assignment(할당) 연산자를 써야하고, arrayList는 add() 메서드를 통해 element를 삽입한다.

 

 

 

 

 

 

 

6.3.3 객체 리스트

 

 

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

 

약간의 작업을 통해 한 번에 처리할 수있다. ex ) SampleDTO를 여러개 전달받아  처리하고 싶다면

 

다음과 같이 SampleDTO의 리스트를 포함하는 SampleDTOList를  설계한다.

 

 

 

 

 

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<>();
		
	}
}

 

->생성자와 메서드의 차이

   

생성자 

 

   접근제한 수식어인 public ,protected, private만을 쓸수 있다.

 

   메소드가 아니므로 반환값은 void조차 허용 되지 않는다.( 리턴값이 없다)

 

   반드시 클래스 이름과 같아야 한다.

 

 

 

SampleController 에 추가

 

SampleDTOList타입을 파라미터로 사용하는 메서드

	//SampleDTOList 타입을 파라미터로 사용하는 메서드 작성
	@GetMapping("/ex02Bean")
	public String ex02Bean(SampleDTOList list) {
		
		log.info("list dto: " + list);
		
		return "ex02Bean";
		
	}

( 리스트는 배열과 비슷한 자바의 자료형이다. 하지만 배열과 다르게 크기의 유동성을 가지고 있다.

  List의 자료형에는 ArrayList, LinkedList 등 List 인터페이스를 구현한 자료형이 있다. )

 

 

 

 

 

 

tomcat 버전에 따라 문자열에서 [] 문자를 특수문자로 허용하지 않을수 있다. 

그럴경우

 

[ 는 %5B 로 ] 는 %5D로 바꿔주자

 

 

 

그렇다면 

 

아래 로그를 얻을 수 있다.

 

           출력결과를 보면 3개의 SampleDTO 객체가 생성된 것을 볼 수있다.

 

 

http://localhost:8080/list%5B0%5D.name=환타&list%5B0%5D.age=999&list%5B1%5D.name=콜라&list%5B1%5D.age=111

 

 

 

 

 

 

 

 

list%5B0%5D.name=%EC%A0%A4%EB%A6%AC1&list%5B0%5D.age=121&list%5B2%5D.name=%EA%B3%A0%EB%9E%98%EB%B0%A5

 

 

 

 

 

 

 

 

6.3.4 @InitBinder

 

 

 파라미터의 수집을 'binding'(바인딩) 이라고 한다.

 

변환이 가능한 데이터는 자동으로 변환이 되지만 경우에 따라 파라미터를 변환해서 처리해야하는 경우도 존재한다.

 

ex) 화면에서 '2018-01-01' 과 같이 문자열로 전달된 데이터를 java.util.Date 타입으로 변환하는 작업.

 

 

 

스프링Controller에서는 파라미터를 바인딩 할때 자동으로 호출되는 @InitBinder를 이용해서 이러한 변환을 처리 할 수 있다.

 

 

 

 

org.zerock.domain패키지에 TodoDTO클래스 작성

 

package org.zerock.domain;

import java.util.Date;

import lombok.Data;

@Data
public class TodoDTO {

	private String title;
	private Date dueDate;
}

 

 

 

 

 

 

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) {
		log.info("todo: " + todo);
		return "ex03";
		
	}
	

 

 

 

브라우저 주소창에

localhost:8080/sample/ex03?title=기이한모험&dueDate=2020-10-06

 

 이런식으로 입력해 주면

 

 

서버에에서는 정상적으로 파라미터를 수집해서 처리한다.

 

 

 

 

 

 

반면 @InitBinder 처리가 되지 않는다면 브라우저에서는 400 에러가 발생하는것을 볼 수 있다.

 

(400에러는 요청구문(syntax)이 잘못되었다는 의ㅣ)

 

 

날짜는 정상처리되어도 아직 jsp페이지가 없기에 아래와 같은 결과 이다.

 

 

 

 

 

 

 

@DateTimeFormat

@Data
public class TodoDTO { 
	private String title;
	@DateTimeFormat(pattern ="yyyy/MM/dd")
	private Date dueData;
	

}
//	@InitBinder
//	public void initBinder(WebDataBinder binder) {
//		SimpleDateFormat dateFormat = new  SimpleDateFormat("yyyy-mm-dd");
//		binder.registerCustomEditor(java.util.Date.class, new CustomDateEditor(dateFormat, false));
//	}

 

 

 

 

@InitBinder를 이용해서 날짜를 변환 할 수 도 있지만, 파라미터로 사용되는 인스턴스 변수에 @DataTimeFormat을 적용

 

해도 변환이 가능하다.(@DateTimeFormat을 이용하는 경우에는 @InitBinder는 필요하지 않다.)

 

 

 

http://localhost:8080/sample/sample03?title=%EC%B9%9C%EC%95%A0%EC%A0%81&dueData=2020/10/19

 

 

 

 

 

 

 

Model 이라는 데이터 전달자

 

controller의 메서드를 작성할 때는 Model이라는 타입을 파차미터로 지정할 수있다.

 

Model 객체는 JSP에 컨틀로러에서 생성된 데이터를 담아서 전달하는 역할을 한다.

 

 

 

이를 이용해서 JSP와 같은 View로 전달해야하는 데이터를 담아서 보낼 수있다.

 

 

메서드의 파라미터에 Model타입이 지정된 경우에는  스프리은 특별하게 Model타입의 객체를 만들어서 메서드에 주입하게 된다.

 

 

 

 

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

 

 

 

 

 

 

 

코드로 보자

 

 

//servlet에서 모델2방식으로 데이터를 전달하는 방식

requset.setAttribute("serverTime", new java.util.Date());
RequestDispatcher dispatcher = request.getRequestDispatcher("/WEB-INF/jsp/home.jsp");
dispatcher.forward(requset,response);

 

 

위와같은 코드로 했을땐..정말 코드가 길고 길었었다.

 

 

 

하지만 스프링에서는 Model을 이용해서  다음과 같이 처리하게 된다.

 

 

 

 

public String home(Model model){

    model.addAttribute("serverTime: " +new java.util.Date());
    return "home";

}

 

 

 

위와 같이 메서드의 파라미터를 Model 타입으로 선언하게 되면 자동으로 스프링MVC에서 Model타입의 객체르 ㄹ만들어 주기 때문에 개발자의입장에서는 필요한 데이터를 담아주는 작업만으로 모든작업이 완료 된다.

 

 

 

 

 

 

 

Model을 사용해야하는 경우는 주로 Controller에 전달된 데이터를 이용해서 추가적인 데이터를 가져와야하는상황.

 

 

 

예를 들어 다음과 같은 경우

 

  • 리스트 페이지 번호를 파라미터로 전달받고, 실제 데이터를 View로 전달해야하는경우
  • 파라미터들에 대한 처리 후 결과를 전달해야하는경우 

 

 

 

@ModelAttribute 어노테이션

 

 

웹페이지의 구조는 Requset에 전달된 데이터를 가지고 필요하다면 추가적인 데이터를 생성해서 화면으로 전달하는 방식으로 동작한다. 

 

Model의 경우 파라미터로 전달된 데이터는 존재하지 않지만 화면에서 필요한 데이터를 전달하기 위해 사용한다.

 

 

 

 

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

 

 

 

 

 

스프링 MVC 의 Controller는 기본적으로 Java Beans 규칙에 맞는 객체는 다시 화면으로 객체를 전달한다.

 

 

좁은 의미에서 Java Beans의 규칙은 단순히 생성자가 없거나 빈 생성자를 가져야 하며 , getter/setter를 가진 클래스의 객체들을 의미한다.

 

 

 

앞의 예제에서 파라미터로 사용된 SampleDTO의 경우는 Java Bean의 규칙에 맞기 때문에  자동으로 다시 화면까지 전달된다.

 전달될 때에는 클래스명의 앞글자는 소문자로 처리된다.

 

 

 

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

 

 

 

 

 

 

 

 

SampleController에 추가

 

 

	@GetMapping("/ex04")
	public String ex04(SampleDTO dto, int page) {
		log.info("dto: " + dto);
		log.info("page: " + page);
		return "/sample/ex04";
	}
	
	
	

 

 

 

/WEB-INF/views 폴더밑에 sample폴더 만들고 ex04.jsp를 만들어 준다.

 

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>


<h2>SAMPLEDTO  ${sampleDTO }</h2>
<h2>PAGE ${page }</h2>

</body>
</html>

 

 

 

 

서버를 실행하고 http://localhost:8080/sample/ex04?name=%EC%B4%88%EC%BD%94%ED%95%98%EC%9E%84&age=111&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가 자동으로 전달해 주는 타입 중에는 RedirectAttribute타입이 존재한다.

 

 

RedirectAttributes는 조금 특별하게도 일회성으로 데이터를 전달하는 용도로 사용된다.

 

RedirectAttributes는 기존의 Servlet에서는 response.sendRedirect()를 사용할 때와 동일한 용도로 사용된다.

 

 

 

 

Servlet에서  redirect방식

 

response.sendRedirect("/home?name=aa&age=10");

 

 

스프링 MVC를 이용하느 redirect처리

 

rttr.addFlashAttrivute("name","AAA");
    rttr.addFlashATtrivute("age,10);
    
    return "redirect:/";

 

 

 

 

 

RedirectAttribute는 Model과 같이 파라미터로 선언해서 사용하고,  addFlashAtribute(이름,값) 메서드를 이용해서 화면에 한 번만 사용하고 다음에는 사용되지 않는 데이터를 전달하기 위해 사용한다.