Notice
Recent Posts
Recent Comments
Link
«   2024/10   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31
Tags
more
Archives
Today
Total
관리 메뉴

블로그 이름 뭐하지

[Spring] Spring MVC 본문

Spring

[Spring] Spring MVC

가는말이고우면오는말은come 2024. 9. 30. 10:20

MVC 디자인 패턴

Model-View-Controller의 약자로 소프트웨어 디자인 패턴 중 하나이다.

소프트웨어를 구성하는 요소들을 Model, View, Controller로 구분하여 각각의 역할을 분리한다.

코드의 재사용성과 유지보수성을 높이고 개발자들 간의 협업을 용이하게 한다.

 

Model

데이터와 비즈니스 로직을 담당한다.

DB와 연동하여 데이터를 저장하고 불러오는 작업을 수행한다.

 

View

사용자 인터페이스를 담당한다.

사용자가 보는 화면과 버튼, 폼 등을 디자인하고 구현한다.

 

Controller

Model과 View 사이의 상호작용을 조정하고 제어한다.

사용자의 입력을 Model에 전달하고, Model의 결과를 바탕으로 View를 업데이트한다.

 

Spring MVC

Sevlet API를 기반으로 구축된 웹 프레임워크.

중앙의 DispatcherSevlet이 HTTP 요청을 처리하며 Front Controller 패턴을 중심으로 설계되어있다.

 

Servlet

자바를 이용하여 웹페이지를 동적으로 생성하는 서버 측 프로그램 혹은 그 사양.

사용자가 HTTP(API)를 요청했을 때, 서버의 Servlet 동작구조

 

1. 사용자가 Client(브라우저)를 통해 서버에 HTTP Request(API 요청).

2. 요청을 받은 Servlet 컨테이너가 HttpServletRequest, HttpServletResponse 객체를 생성.

3. 설정된 정보를 통해 어떤 Servlet에 대한 요청인지 찾음

4. 해당 Servlet에서 service 메서드를 호출 후, 브라우저의 요청 Method에 따라 doGet 또는 doPost 메서드 호출

5. 호출한 메서드들의 결과를 그대로 반환하거나 동적 페이지 생성.

HttpServletResponse 객체에 응답을 담아 Client로 반환

6. 응답 완료 시 생성한 HttpServletRequest, HttpServletResponse 객체 소멸

 

Front Controller

모든 API 요청을 Servlet의 동작 방식에 맞춰 코드를 구현한다면 너무 많은 Servlet 클래스가 필요해진다.

Spring은 DispatcherServlet 을 이용해 Front Controller 패턴 방식으로 API 요청을 효율적으로 처리한다.

Front Controller 패턴의 동작과정

 

1. Client(브라우저)에서 HTTP 요청이 들어오면 DispatcherServlet 객체가 요청을 분석.

2. DispatcherServlet 객체는 분석한 데이터를 토대로 Handler mapping을 통해 Controller를 찾아 요청 전달

Servlet을 직접 구현할 필요없이 DispatcherServlet에 의해 간편하게 HTTP 요청 처리.

//요청
GET/api/hello >> HelloController의 hello 함수

//Handler mapping에는 API path와 Controller 메서드가 매칭되어있다.
@RestController
public Class HelloController {
	@GetMapping("/api/hello")
    public String hello(){
    	return "Hello World";
    }
}

3. Controller > DispatcherServlet

해당 Controller는 요청에 대한 처리 완료 후, 처리 결과 즉 데이터(Model)와 View 정보를 전달.

4.DispatcherServlet > Client

ViewResolver를 통해 View에 Model을 적용하여 View를 Client에게 응답으로 전달.

 

Servlet과 Controller 코드의 차이

//Servlet 코드
//API를 처리할 때마다 클래스를 만들어야함

@WebServlet(urlPatterns = "/user/login")
public class UserLoginServlet extends HttpServlet {
	@Override
  protected void doGet(HttpServletRequest request, HttpServletResponse response) {
		// ... 
	}
}

@WebServlet(urlPatterns = "/user/logout")
public class UserLogoutServlet extends HttpServlet {
	@Override
  protected void doGet(HttpServletRequest request, HttpServletResponse response) {
		// ... 
	}
}

@WebServlet(urlPatterns = "/user/signup")
public class UserSingUpServlet extends HttpServlet {
	@Override
  protected void doGet(HttpServletRequest request, HttpServletResponse response) {
		// ... 
	}

	@Override
  protected void doPost(HttpServletRequest request, HttpServletResponse response) {
		// ... 
	}

}

//Controller 코드
//유사한 성격의 API를 하나의 Controller로 관리

@Controller
@RequestMapping("/user")
public class UserController {
	@GetMapping("/login")
	public String login() {
	    // ...
	}

    @GetMapping("/logout")
    public String logout() {
        // ...
    }

	@GetMapping("/signup")
	public String signup() { 
		// ... 
	}
	
	@PostMapping("/signup")
  public String registerUser(SignupRequestDto requestDto) {
		// ... 
	}
}

 

애너테이션

@Controller

해당 클래스가 Controller의 역할을 수행하도록 등록한다.

 

@GET, @POST, @PUT, @DELETE

각각의 HTTP Method에 매핑된다.

 

@RequestMapping

중복되는 Url을 단축시킨다.

@Controller // 해당 클래스가 컨트롤러의 역할을 수행할 수 있게 함
public class HelloController{
    @GetMapping("/api/get") // GET Method 매핑
    @ResponseBody
    public String get(){
    	return "Get Method 요청"
    }
    
    @PostMapping("/api/post") // POST Method 매핑
    @ResponseBody
    public String post(){
    	return "Post Method 요청"
    }
}

-------------------------RequestMapping 적용 후

@Controller
@RequestMapping("/api")
public class HelloController{
    @GetMapping("/get")
    @ResponseBody
    public String get(){
    	return "Get Method 요청"
    }
    
    @PostMapping("/post")
    @ResponseBody
    public String post(){
    	return "Post Method 요청"
    }
}

 

 

정적페이지와 동적페이지

 

정적페이지 처리

1. Static 파일에 html 파일 만들기

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Hello Spring</title>
</head>
<body>
Hello, Spring 정적 웹 페이지!! (static)
</body>
</html>

 

http://localhost:8080/hello.html로 접근이 가능하다.

 

2. Controller 거쳐서 반환하기

build gradle에서 thymeleaf dependcy를 주석 처리한다.

thymeleaf : 동적 페이지 처리를 위한 템플릿 엔진

추가시 자동으로 controller에서 html 파일 경로를 /resourses/templates로 설정해준다.

주석 처리 후 Controller에 아래처럼 기입한다.

hello.html 문자열로 반환하면 static 폴더의 해당 html을 찾아 반환해준다.

http://localhost:8080/static-hello로 접근이 가능하다.

@GetMapping("/static-hello")
public String hello() {
    return "hello.html";
}

 

3. Redirect

thymeleaf를 적용한 상태에서 static 폴더에 있는 html을 Controller를 거쳐 처리하고 싶을 때,

redirect:/hello.html 문자열로 반환한다.

http://localhost:8080/hello.html 요청이 재수행되며 파일을 반환할 수 있다.

http://localhost:8080/html/redirect로 접근이 가능하다.

@GetMapping("/html/redirect")
public String htmlStatic() {
    return "redirect:/hello.html";
}

 

4. Template engine에 View 전달

static이 아니라 templates에 html 파일을 생성하고, Controller는 아래와 같이 작성한다.

외부에서 브라우저에 바로 접근하지 못하게 하고 싶거나 특정 상황에서 Controller를 통해 제어하고 싶을 때,

Templates 폴더에 정적 html 파일을 추가하고 hello 문자열로 반환하여 처리가 가능하다(.html 생략가능)

http://localhost:8080/html/templates로 접근이 가능하다.

@GetMapping("/html/templates")
public String htmlTemplates() {
    return "hello";
}

 

동적페이지 처리

//templates 파일에 넣을 동적 html
hello-visit.html

<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"><title>Hello Spring</title></head>
<body>
  <div>
    Hello, Spring 동적 웹 페이지!!
  </div>
  <div>
  	//새로 고침할 때마다 방문자 수가 늘어난다.
    (방문자 수: <span th:text="${visits}"></span>)
  </div>
</body>
</html>

//Controller에 넣을 동적 html 처리

private static long visitCount = 0;

...

@GetMapping("/html/dynamic")
//spring framework ui에 있는 Model의 객체를 매개변수로 넣음
public String htmlDynamic(Model model) {
    visitCount++;
    model.addAttribute("visits", visitCount);
    //반환: view 이름 정보
    return "hello-visit";
}

 

1. Client의 요청을 Controller에서 Model로 처리한다.

DB 조회가 필요하다면 DB 작업 후 처리한 데이터를 Model에 저장한다.

 

2. Thymeleaf(Template engine)에게 View와 Model을 전달한다

View : 동적 HTML 파일

Model : View에 적용할 정보들

 

3. Template engine

View에 Model을 적용 > 동적 웹페이지 생성

ex. 로그인 성공시 로그인된 사용자의 닉네임을 페이지에 추가

(Template engine 종류 : Thymeleaf, Groovy, Jade, JSP 등등)

 

데이터를 Client에 반환하는 법

최근의 서버는 View(html,css,js)를 직접 반환하기보다는

요청에 맞는 특정한 정보(Json)를 데이터로 반환하는 형태를 취한다.

 

JSON 데이터 반환하는 법

템플릿 엔진이 적용된 Spring boot 에서는 Controller에서 문자열을 반환하면

templates 폴더에서 해당 문자열의 .html 파일을 찾아 반환한다

따라서 html 파일이 아니라 json 데이터를 반환하고 싶으면

해당 메서드에 @ResponseBody 에너테이션을 추가해야 한다.

 

1. 반환값이 String일 때

Java는 Json 타입을 지원하지 않으므로 Json 형태의 String 타입으로 변환하여 사용한다.

Content-Type은 text/html이고

Response  body는 {"name" : "Robbie", "age" : 95} 이다.

@GetMapping("/response/json/string")
@ResponseBody
public String helloStringJson() {
    return "{\"name\":\"Robbie\",\"age\":95}";
}

 

2. 반환 값이 String 외 자바 클래스일 때

자바 객체를 그대로 반환했으므로 json형태로 변환된다.

Content-Type은 application/json이고

Response  body는 {"name" : "Robbie", "age" : 95} 이다.

//반환할 Star 클래스
package com.sparta.springmvc.response;

import lombok.Getter;

@Getter
public class Star {
    String name;
    int age;

    public Star(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public Star() {}
}

//Controller

@GetMapping("/response/json/class")
@ResponseBody
public Star helloClassJson() {
    return new Star("Robbie", 95);
}

 

3. @RestController

RestController를 사용하면 해당 클래스의 모든 메서드에 @ResponseBody 에너테이션이 추가된다.

package com.sparta.springmvc.response;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/response/rest")
public class ResponseRestController {
    // [Response header]
    //   Content-Type: text/html
    // [Response body]
    //   {"name":"Robbie","age":95}
    @GetMapping("/json/string")
    public String helloStringJson() {
        return "{\"name\":\"Robbie\",\"age\":95}";
    }

    // [Response header]
    //   Content-Type: application/json
    // [Response body]
    //   {"name":"Robbie","age":95}
    @GetMapping("/json/class")
    public Star helloClassJson() {
        return new Star("Robbie", 95);
    }
}

 

Jackson

Json 데이터 구조를 처리해주는 라이브러리.

Object를 Json 타입의 String으로 변환하거나, Json 타입의 String을 Object로 변환한다.

Spring은 3.0 이후로 Jackson과 관련된 Api를 제공하여 코드를 작성하지 않아도 자동으로 처리하고 있다.

직접 Json 데이터를 처리할 때는 Jackson 라이브러리의 ObjectMapper를 사용할 수 있다.

 

Object > Json

@Test
//Object를 JSON타입의 String으로 변환할 때는 해당 Object에 getter가 필요하다
@DisplayName("Object To JSON : get Method 필요")
void test1() throws JsonProcessingException {
    Star star = new Star("Robbie", 95);
    
    // Jackson 라이브러리의 ObjectMapper
    ObjectMapper objectMapper = new ObjectMapper();
    
    //objectMapper의 writeValueAsString 메서드를 이용해 변환할 수 있다.
    //파라미터에 Json으로 변환시킬 Object의 객체를 주면 된다.
    String json = objectMapper.writeValueAsString(star);

    System.out.println("json = " + json);
}

 

Json > Object

@Test
//JSON 타입의 String을 Object로 변환하려면
//해당 Object에 기본생성자와 getter또는 setter가 필요하다
@DisplayName("JSON To Object : 기본 생성자 & (get OR set) Method 필요")
void test2() throws JsonProcessingException {
    String json = "{\"name\":\"Robbie\",\"age\":95}"; // JSON 타입의 String
	
    // Jackson 라이브러리의 ObjectMapper
    ObjectMapper objectMapper = new ObjectMapper();
	
    //objectMapper의 readValue 메서드를 사용해 변환할 수 있다.
    //첫번째 파라미터는 json 타입의 String, 
    //두번째 파라미터는 변환할 Object의 class 타입을 준다.
    Star star = objectMapper.readValue(json, Star.class);
    
    System.out.println("star.getName() = " + star.getName());
}

 

Path Variable과 Request Param

Client(브라우저)에서 서버로 HTTP 요청을 보낼 때 데이터를 함께 보낼 수 있다.

데이터를 보내는 방식이 여러가지이므로 모든 방식에 대한 처리방법을 학습해야한다.

 

Path Variable

서버에 보내려는 데이터를 URL 경로에 추가할 수 있다.

 http://localhost:8080/hello/request/star/Robbie/age/95
//html 파일
<h2>GET /star/{name}/age/{age}</h2>
<form id="helloPathForm">
  <div>
    이름: <input name="name" type="text">
  </div>
  <div>
    나이: <input name="age" type="text">
  </div>
</form>
<div>
  <button id="helloPathFormSend">전송</button>
</div>


// Path Variable 방식 Controller
// 서버에 보내려는 경로를 Url 경로에 추가한다.
// GET http://localhost:8080/hello/request/star/Robbie/age/95

//form에서 받아올 데이터에 중괄호{}
@GetMapping("/star/{name}/age/{age}")
@ResponseBody
//@PathVariable : 중괄호로 묶은 데이터의 값을 파라미터로 받아온다.
public String helloRequestPath(@PathVariable String name, @PathVariable int age)
{
    return String.format("Hello, @PathVariable.<br> name = %s, age = %d", name, age);
}

 

Request Param

1) Get 방식

서버에 보내려는 데이터를 Url 경로 마지막에 ?와 &를 사용하여 추가한다.

http://localhost:8080/hello/request/form/param?name=Robbie&age=95
//html파일
<h2>GET /hello/request/form/param</h2>
<form method="GET" action="/hello/request/form/param">
  <div>
    이름: <input name="name" type="text">
  </div>
  <div>
    나이: <input name="age" type="text">
  </div>
  <button>전송</button>
</form>


// Get 방식: Request Param 방식(=쿼리 스트링방식)
// 서버에 보내려는 데이터를 Url 경로 마지막에 ?과 &을 사용하여 추가한다
// GET http://localhost:8080/hello/request/form/param?name=Robbie&age=95

@GetMapping("/form/param")
@ResponseBody
//@RequestParam 받아올 데이터를 파라미터로 받아온다.
//@RequestParam은 생략이 가능하나 오류가 발생할 수 있어 (required = false)를 넣어 방지한다.
//(required = false): 일부 값이 들어오지 않아도 오류x (대신 해당 값은 null을 넣음)
// : 해당 옵션은 past Variable 방식에서도 사용가능
public String helloGetRequestParam(@RequestParam String name(required =false), @RequestParam int age) {
    return String.format("Hello, @RequestParam.<br> name = %s, age = %d", name, age);
}

 

2) Post 방식

데이터는 HTTP Body에 name=Robbie&age=95 형태로 담겨져 서버로 전달된다.

Post 방식을 사용하는 것은 권장하지 않는다.

http://localhost:8080/hello/request/form/param
//html파일
<h2>POST /hello/request/form/param</h2>
<form method="POST" action="/hello/request/form/param">
  <div>
    이름: <input name="name" type="text">
  </div>
  <div>
    나이: <input name="age" type="text">
  </div>
  <button>전송</button>
</form>

// Post 방식(Body가 있음): Request Param 방식(=쿼리 스트링방식)
// POST http://localhost:8080/hello/request/form/param
// Header
//  Content type: application/x-www-form-urlencoded
// Body
//  name=Robbie&age=95
@PostMapping("/form/param")
@ResponseBody
public String helloPostRequestParam(@RequestParam String name, @RequestParam int age) {
    return String.format("Hello, @RequestParam.<br> name = %s, age = %d", name, age);
}

 

HTTP 데이터를 객체로 처리하는 방법

 

1. @ModelAttribute

 

Html form 태그를 이용해 Post 방식으로 HTTP 요청을 보낼 수 있다.

해당 데이터는 Body에 name=Robbie&age=95 형태로 담겨져서 서버로 전달된다.

해당 데이터를 Java의 객체 형태로 받는 방법은 ModelAttribute 에너테이션을 이용한 후

Body 데이터를 받아올 객체를 선언하는 것이다.

 http://localhost:8080/hello/request/form/model
//html파일
<h2>POST /hello/request/form/model</h2>
<form method="POST" action="/hello/request/form/model">
    <div>
        이름: <input name="name" type="text">
    </div>
    <div>
        나이: <input name="age" type="text">
    </div>
    <button>전송</button>
</form>

//@ModelAttribute
// POST http://localhost:8080/hello/request/form/model
    // Header
    //  Content type: application/x-www-form-urlencoded
    // Body
    //  name=Robbie&age=95
    @PostMapping("/form/model")
    @ResponseBody
    //@ModelAttribute: 쿼리스트링 방식으로 된 데이터를 객체에 매핑하여 가져올 수 있음
    public String helloRequestBodyForm(@ModelAttribute Star star) {
        return String.format("Hello, @ModelAttribute.<br> (name = %s, age = %d) ", star.name, star.age);
    }

 

▷Query String 방식(Get)

?name=Robbie&age=95 처럼 데이터가 두 개 정도면 괜찮지만, 데이터의 수가 늘어나면

@RequestParam 애너테이션으로 받아오기 힘들 수 있다.

@ModelAttribute 애너테이션을 사용하면 Java의 객체로 데이터를 받아올 수 있다.

<h2>GET /hello/request/form/param/model </h2>
<form method="GET" action="/hello/request/form/param/model">
    <div>
        이름: <input name="name" type="text">
    </div>
    <div>
        나이: <input name="age" type="text">
    </div>
    <button>전송</button>
</form>
<br>

//@RequestParam으로 많은 값을 다 받아오기에는
//코드가 길어지고 작성이 힘들기 때문에 객체로 한 번에 받아옴
// GET http://localhost:8080/hello/request/form/param/model?name=Robbie&age=95
@GetMapping("/form/param/model")
@ResponseBody

//@ModelAttribute도 생략이 가능
//@RequestParam도 생략이 가능하므로
//Spring은 simple value type(원시타입(int), Wrapper, Date...)이 들어오는 경우, @RequestParam가,
//클래스, 객체일 경우에는 @ModelAttribute가 생략 되었다고 생각한다.
//public String helloRequestParam(@ModelAttribute Star star) {

//파라미터에 선언한 Star의 객체가 생성되고
//오버로딩된 생성자 또는 Setter 메서드를 통해 요청된 name&age의 값이 담겨진다.
public String helloRequestParam(Star star) {
    return String.format("Hello, @ModelAttribute.<br> (name = %s, age = %d) ", star.name, star.age);
}

 

 

2. @RequestBody

HTTP Body에 JSON 데이터를 담아 서버에 전달 할 때 해당 Body 데이터를 Java의 객체로 전달받는다.

<h2>POST /hello/request/form/json</h2>
<form id="helloJsonForm">
    <div>
        이름: <input name="name" type="text">
    </div>
    <div>
        나이: <input name="age" type="text">
    </div>
</form>
<div>
    <button id="helloJsonSend">전송</button>
</div>
<div>
    <div id="helloJsonResult"></div>
</div>

    // body가 json으로 넘어온 데이터를 @RequestBody로 객체로 변환
    // [Request sample]
    // POST http://localhost:8080/hello/request/form/json
    // Header
    //  Content type: application/json
    // Body
    //  {"name":"Robbie","age":"95"}
    @PostMapping("/form/json")
    @ResponseBody
    //HTTP Body에 Json 형태로 데이터가 서버에 연결되었을 때,
    //@RequestBody 애너테이션을 사용해 데이터를 객체 형태로 받을 수 있다.
    //데이터를 자바의 객체로 받아올 때는 오버로딩된 생성자 또는 get,set 메서드가 필요.
    public String helloPostRequestJson(@RequestBody Star star) {
        return String.format("Hello, @RequestBody.<br> (name = %s, age = %d) ", star.name, star.age);
    }

 

'Spring' 카테고리의 다른 글

[Spring] 3 Layer Architecture  (0) 2024.10.10
[Spring] JDBC  (0) 2024.09.30
[Spring] application.properties  (0) 2024.09.29
[Spring] Lombok  (0) 2024.09.29
[Spring] IntelliJ 테스트 코드 설정  (0) 2024.09.29