스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 23

Spring 정리 2022. 5. 22. 16:11

인프런 강의 37일차.

 - 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 1 (김영한 강사님)

 - 서블릿, JSP, MVC 패턴

 - 서블릿으로 1차 구현

   -> 서블릿으로 구현했을 때 불편한 점 개선을 위해 JSP로 2차 구현

   -> JSP로도 불편한 점을 개선하기 위해 MVC 패턴으로 3차 구현

 - 스프링 프로젝트 시작

   -> 스프링 기본기능 이해

   -> 스프링 MVC 웹페이지 만들기

 

4. 스프링 MVC - 기본 기능

 4.5 HTTP 요청 파라미터 - 쿼리 파라미터, HTML Form

  > HTTP 요청 데이터 조회 - 개요

   - 서블릿에서 학습했던 HTTP 요청 데이터를 조회 하는 방법을 다시 떠올려보자.

   - 그리고 서블릿으로 학습했던 내용을 스프링이 얼마나 깔끔하고 효율적으로 바꾸어주는지 알아보자.

   - HTTP 요청 메시지를 통해 클라이언트에서 서버로 데이터를 전달하는 방법을 알아보자.

 

  > 클라이언트에서 서버로 요청 데이터를 전달할 때는 주로 다음 3가지 방법을 사용한다

   - GET - 쿼리 파라미터

      /url?username=hello&age=20

      메시지 바디 없이, URL의 쿼리 파라미터에 데이터를 포함해서 전달

      예) 검색, 필터, 페이징등에서 많이 사용하는 방식

   - POST - HTML Form

      content-type: application/x-www-form-urlencoded

      메시지 바디에 쿼리 파리미터 형식으로 전달 username=hello&age=20

      예) 회원 가입, 상품 주문, HTML Form 사용

   - HTTP message body에 데이터를 직접 담아서 요청

      HTTP API에서 주로 사용, JSON, XML, TEXT

      데이터 형식은 주로 JSON 사용

      POST, PUT, PATCH

 

  > 스프링으로 요청 파라미터를 조회하는 방법

package hello.springmvc.basic.request;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Slf4j
@Controller
public class RequestParamController {

    @RequestMapping("/request-param-v1")
    public void requestParamV1(HttpServletRequest request, HttpServletResponse response) throws IOException {
        String username = request.getParameter("username");
        int age = Integer.parseInt(request.getParameter("age"));
        log.info("username={}, age={}", username, age);

        response.getWriter().write("ok");
    }
}

  - hello.springmvc.basic.request.RequestParamController.java

  - HTTP get/post 호출 처리

 

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="/request-param-v1" method="post">
    username: <input type="text" name="username" />
    age: <input type="text" name="age" />
    <button type="submit">전송</button>
</form>
</body>
</html>

  - resources/static/basic/hello-form.html

  - HTTP post 호출을 하기 위한 샘플 html

  - Jar 를 사용하면 webapp 경로를 사용할 수 없다. 이제부터 정적 리소스도 클래스 경로에 함께 포함해야 한다.

 

 4.6 HTTP 요청 파라미터 - @RequestParam

  > 스프링이 제공하는 @RequestParam 을 사용하면 요청 파라미터를 매우 편리하게 사용할 수 있다. 

/**
 * @RequestParam 사용
 * - 파라미터 이름으로 바인딩
 * @ResponseBody 추가
 * - View 조회를 무시하고, HTTP message body에 직접 해당 내용 입력
 */
@ResponseBody
@RequestMapping("/request-param-v2")
public String requestParamV2(
        @RequestParam("username") String memberName,
        @RequestParam("age") int memberAge) {
    log.info("username={}, age={}", memberName, memberAge);
    return "ok";            //String 반환일 경우 ViewResolver를 호출하는 것이지만 ResponseBody Annotation을 추가함으로써 REST API 호출로 변경할 수 있다.
}

  - requestParamV2

  - @RequestParam : 파라미터 이름으로 바인딩

  - @ResponseBody : View 조회를 무시하고, HTTP message body에 직접 해당 내용 입력

  - @RequestParam의 name(value) 속성이 파라미터 이름으로 사용된다.

  - @RequestParam("username") String memberName -> request.getParameter("username")

/**
 * @RequestParam 사용
 * HTTP 파라미터 이름이 변수 이름과 같으면 @RequestParam(name="xx") 생략 가능
 */
@ResponseBody
@RequestMapping("/request-param-v3")
public String requestParamV3(
        @RequestParam String username,
        @RequestParam int age) {
    log.info("username={}, age={}", username, age);
    return "ok";
}

  - requestParamV3

  - HTTP 파라미터 이름이 변수 이름과 같으면 @RequestParam(name="xx") 생략 가능

/**
 * @RequestParam 사용
 * String, int 등의 단순 타입이면 @RequestParam 도 생략 가능
 */
@ResponseBody
@RequestMapping("/request-param-v4")
public String requestParamV4(String username, int age) {
    log.info("username={}, age={}", username, age);
    return "ok";
}

  - requestParamV4

  - String , int , Integer 등의 단순 타입이면 @RequestParam 도 생략 가능

  * @RequestParam 애노테이션을 생략하면 스프링 MVC는 내부에서 required=false 를 적용한다.

  - @RequestParam을 생략하지 않음으로써 명확하게 요청 파리미터에서 데이터를 읽는 다는 것을 표현하기도 한다.

/**
 * @RequestParam.required
 * /request-param -> username이 없으므로 예외
 *
 * 주의!
 * /request-param?username= -> 빈문자로 통과
 *
 * 주의!
 * /request-param
 * int age -> null을 int에 입력하는 것은 불가능, 따라서 Integer 변경해야 함(또는 다음에 나오는
defaultValue 사용)
 */
@ResponseBody
@RequestMapping("/request-param-required")
public String requestParamRequired(
        @RequestParam(required = true) String username,
        @RequestParam(required = false) Integer age) {
    log.info("username={}, age={}", username, age);
    return "ok";
}

  - requestParamRequired (파라미터 필수 여부)

  - @RequestParam.required : 파라미터 필수 여부. 기본값이 파라미터 필수( true )이다.

  - /request-param 요청 : username 이 없으므로 400 예외가 발생한다.

  - 파라미터 이름만 사용 : /request-param?username= 파라미터 이름만 있고 값이 없는 경우 빈문자로 통과

  - 기본형(primitive)에 null 입력 : /request-param 요청 @RequestParam(required = false) int age

  - null 을 int 에 입력하는 것은 불가능(500 예외 발생). null 을 받을 수 있는 Integer 로 변경 혹은 defaultValue 사용

/**
 * @RequestParam
 * - defaultValue 사용
 *
 * 참고: defaultValue는 빈 문자의 경우에도 적용
 * /request-param?username=
 */
@ResponseBody
@RequestMapping("/request-param-default")
public String requestParamDefault(
        @RequestParam(required = true, defaultValue = "guest") String username,
        @RequestParam(required = false, defaultValue = "-1") int age) {
    log.info("username={}, age={}", username, age);
    return "ok";
}

  - requestParamDefault (기본 값 적용)

  - 파라미터에 값이 없는 경우 defaultValue 를 사용하면 기본 값을 적용할 수 있다. 기본 값이 있기 때문에 required 는 의미가 없다

  - defaultValue 는 빈 문자의 경우에도 설정한 기본 값이 적용된다. (e.g. /request-param?username=)

/**
 * @RequestParam Map, MultiValueMap
 * Map(key=value)
 * MultiValueMap(key=[value1, value2, ...] ex) (key=userIds, value=[id1, id2])
 */
@ResponseBody
@RequestMapping("/request-param-map")
public String requestParamMap(@RequestParam Map<String, Object> paramMap) {
    log.info("username={}, age={}", paramMap.get("username"),
            paramMap.get("age"));
    return "ok";
}

  - requestParamMap (파라미터를 Map으로 조회하기)

  - 파라미터를 Map, MultiValueMap으로 조회할 수 있다

  - @RequestParam Map : Map(key=value)

  - @RequestParam MultiValueMap : MultiValueMap(key=[value1, value2, ...] ex) (key=userIds, value=[id1, id2])

  - 파라미터의 값이 1개가 확실하다면 Map 을 사용해도 되지만, 그렇지 않다면 MultiValueMap 을 사용하자.

 

 4.7 HTTP 요청 파라미터 - @ModelAttribute

  > 실제 개발을 하면 아래와 같이 요청 파라미터를 받아서 필요한 객체를 만들고 그 객체에 값을 넣어주어야 한다.

@RequestParam String username;
@RequestParam int age;
HelloData data = new HelloData();
data.setUsername(username);
data.setAge(age);

  - 스프링은 이 과정을 완전히 자동화해주는 @ModelAttribute 기능을 제공한다

package hello.springmvc.basic;
import lombok.Data;

@Data
public class HelloData {
    private String username;
    private int age;
}

  - @Data Annotation의 역할 : 롬복에서 제공해주는 @Data의 경우 @Getter , @Setter , @ToString , @EqualsAndHashCode , @RequiredArgsConstructor 를 자동으로 적용해준다.

/**
 * @ModelAttribute 사용
 * 참고: model.addAttribute(helloData) 코드도 함께 자동 적용됨, 뒤에 model을 설명할 때
자세히 설명
 */
@ResponseBody
@RequestMapping("/model-attribute-v1")
public String modelAttributeV1(@RequestParam String username, @RequestParam int age) {
    HelloData helloData = new HelloData();
    helloData.setUsername(username);
    helloData.setAge(age);
    log.info("username={}, age={}", helloData.getUsername(),
            helloData.getAge());
    log.info("hellodata={}", helloData);        //객체를 log에 찍을 경우 @Data에서 ToString으로 변환해준다.
    return "ok";
}

@ResponseBody
@RequestMapping("/model-attribute-v1")
public String modelAttributeV1(@ModelAttribute HelloData helloData) {
    //@ModelAttribute HelloData helloData의 경우 @RequestParam String username, @RequestParam int age와 동일한 역할을 한다.
    log.info("username={}, age={}", helloData.getUsername(),
            helloData.getAge());
    return "ok";
}

  - @ModelAttribute 적용

  - HelloData 객체가 생성되고, 요청 파라미터의 값도 모두 들어가 있다

  - 스프링MVC는 @ModelAttribute 가 있으면 다음을 실행한다.

  - HelloData 객체를 생성한다 -> 요청 파라미터의 이름으로 HelloData 객체의 프로퍼티를 찾는다 -> 해당 프로퍼티의 setter를 호출해서 파라미터의 값을 입력(바인딩) 한다.

  - 예) 파라미터 이름이 username 이면 setUsername() 메서드를 찾아서 호출하면서 값을 입력한다

 

  > 프로퍼티
  - 객체에 getUsername() , setUsername() 메서드가 있으면, 이 객체는 username 이라는 프로퍼티를 가지고 있다.

  - username 프로퍼티의 값을 변경하면 setUsername() 이 호출되고, 조회하면 getUsername() 이 호출된다

class HelloData {
 getUsername();
 setUsername();
}

  > 바인딩 오류

  - age=abc 처럼 숫자가 들어가야 할 곳에 문자를 넣으면 BindException 이 발생한다. 

 

/**
 * @ModelAttribute 생략 가능
 * String, int 같은 단순 타입 = @RequestParam
 * argument resolver 로 지정해둔 타입 외 = @ModelAttribute
 */
@ResponseBody
@RequestMapping("/model-attribute-v2")
public String modelAttributeV2(HelloData helloData) {
    log.info("username={}, age={}", helloData.getUsername(),
            helloData.getAge());
    return "ok";
}

  - @ModelAttribute 생략

  - @ModelAttribute 는 생략할 수 있다. 그런데 @RequestParam 도 생략할 수 있으니 혼란이 발생할 수 있다

  - 스프링은 해당 생략시 다음과 같은 규칙을 적용한다.

  - String , int , Integer 같은 단순 타입 = @RequestParam

  - 나머지 = @ModelAttribute (argument resolver 로 지정해둔 타입 외)

스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 22

Spring 정리 2022. 5. 15. 21:34

인프런 강의 37일차.

 - 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 1 (김영한 강사님)

 - 서블릿, JSP, MVC 패턴

 - 서블릿으로 1차 구현

   -> 서블릿으로 구현했을 때 불편한 점 개선을 위해 JSP로 2차 구현

   -> JSP로도 불편한 점을 개선하기 위해 MVC 패턴으로 3차 구현

 - 스프링 프로젝트 시작

   -> 스프링 기본기능 이해

   -> 스프링 MVC 웹페이지 만들기

 

4. 스프링 MVC - 기본 기능

 4.4 HTTP 요청 - 기본, 헤더 조회

  > 애노테이션 기반의 스프링 컨트롤러는 다양한 파라미터를 지원한다

 

package hello.springmvc.basic.request;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpMethod;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Locale;

@Slf4j
@RestController
public class RequestHeaderController {

    @RequestMapping("/headers")
    public String headers(HttpServletRequest request,
                          HttpServletResponse response,
                          HttpMethod httpMethod,
                          Locale locale,
                          @RequestHeader MultiValueMap<String, String> headerMap,
                          @RequestHeader("host") String host,
                          @CookieValue(value = "myCookie", required = false) String cookie
                         ) {
        log.info("request={}", request);
        log.info("response={}", response);
        log.info("httpMethod={}", httpMethod);
        log.info("locale={}", locale);
        log.info("headerMap={}", headerMap);
        log.info("header host={}", host);
        log.info("myCookie={}", cookie);

        return "ok";
    }
}

   - HttpServletRequest 

   - HttpServletResponse 

   - HttpMethod : HTTP 메서드를 조회한다. (org.springframework.http.HttpMethod)

   - Locale : Locale 정보를 조회한다.

   - @RequestHeader MultiValueMap headerMap : 모든 HTTP 헤더를 MultiValueMap 형식으로 조회한다.

   - @RequestHeader("host") String host : 특정 HTTP 헤더를 조회한다.

      속성

       - 필수 값 여부: required

       - 기본 값 속성: defaultValue

   - @CookieValue(value = "myCookie", required = false) String cookie : 특정 쿠키를 조회한다.

      속성

       - 필수 값 여부: required

       - 기본 값 속성: defaultValue

 

MultiValueMap<String, String> map = new LinkedMultiValueMap();
map.add("keyA", "value1");
map.add("keyA", "value2");
//[value1,value2]
List<String> values = map.get("keyA");

  > MultiValueMap

   - MAP과 유사한데, 하나의 키에 여러 값을 받을 수 있다.

   - HTTP header, HTTP 쿼리 파라미터와 같이 하나의 키에 여러 값을 받을 때 사용한다. (keyA=value1&keyA=value2)

 

private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(RequestHeaderController.class);

  > @Slf4j

   - 위 코드를 자동으로 생성해서 로그를 선언해준다. 개발자는 편리하게 log 라고 사용하면 된다

 

참고1. @Conroller 의 사용 가능한 파라미터 목록은 다음 공식 메뉴얼에서 확인할 수 있다.   https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-annarguments >

참고2. @Conroller 의 사용 가능한 응답 값 목록은 다음 공식 메뉴얼에서 확인할 수 있다.

  https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-annreturn-types

스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 18

Spring 정리 2022. 4. 3. 16:09

인프런 강의 35일차.

 - 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 1 (김영한 강사님)

 - 서블릿, JSP, MVC 패턴

 - 서블릿으로 1차 구현

   -> 서블릿으로 구현했을 때 불편한 점 개선을 위해 JSP로 2차 구현

   -> JSP로도 불편한 점을 개선하기 위해 MVC 패턴으로 3차 구현

 

3. 스프링 MVC - 구조 이해

 3.8 스프링 mvc 시작하기

  > 스프링이 제공하는 컨트롤러는 애노테이션 기반으로 동작해서, 매우 유연하고 실용적이다. 

  > @RequestMapping 스프링은 애노테이션을 활용한 매우 유연하고, 실용적인 컨트롤러를 만들었는데 이것이 바로 @RequestMapping 애노테이션을 사용하는 컨트롤러이다. 과거에는 스프링 프레임워크가 MVC 부분이 약해서 스프링을 사용하더라도 MVC 웹 기술은 스트럿츠 같은 다른 프레임워크를 사용했었다.

  > 가장 우선순위가 높은 핸들러 매핑과 핸들러 어댑터는 RequestMappingHandlerMapping, RequestMappingHandlerAdapter 이다.

   - RequestMappingHandlerMapping

   - RequestMappingHandlerAdapter

  > 지금 스프링에서 주로 사용하는 애노테이션 기반의 컨트롤러를 지원하는 핸들러 매핑과 어댑터이다.

 

package hello.servlet.web.springmvc.v1;

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

@Controller
public class SpringMemberFormControllerV1 {

    @RequestMapping("/springmvc/v1/members/new-form")
    public ModelAndView process() {
        return new ModelAndView("new-form");
    }
}

  - web.springmvc.v1.SpringMemberFormControllerV1

  - @Controller : 스프링이 자동으로 스프링 빈으로 등록한다. (내부에 @Component로 정의되어있음)

  - @RequestMapping : 요청 정보를 매핑한다. 해당 URL이 호출되면 이 메서드가 호출된다. 애노테이션을 기반으로 동작하기 때문에, 메서드의 이름은 임의로 지으면 된다.

  - ModelAndView : 모델과 뷰 정보를 담아서 반환하면 된다

  - @Controller 대신 @Component + @RequestMapping으로 직접 컴포넌트 스캔을 타도록 지정해도 된다.

 

package hello.servlet.web.springmvc.v1;

import hello.servlet.domain.member.Member;
import hello.servlet.domain.member.MemberRepository;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;


public class SpringMemberSaveControllerV1 {

    private MemberRepository memberRepository = MemberRepository.getInstance();

    @RequestMapping("/springmvc/v1/members/save")
    public ModelAndView process(HttpServletRequest request, HttpServletResponse response) {
        String username = request.getParameter("username");
        int age = Integer.parseInt(request.getParameter("age"));

        Member member = new Member(username, age);
        memberRepository.save(member);


        ModelAndView mv = new ModelAndView("save-result");
        mv.addObject("member", member);     //mv.getModel().put("member", member);    //멤버 모델에 저장

        return mv;
    }
}

  - web.springmvc.v1.SpringMemberSaveControllerV1

  - 기본적인 패턴은 동일하나 mv.getModel.put이 아닌 addObject로 간편하게 모델에 추가가 가능하다.

 

package hello.servlet.web.springmvc.v1;

import hello.servlet.domain.member.Member;
import hello.servlet.domain.member.MemberRepository;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

import java.util.List;
import java.util.Map;

public class SpringMemberListControllerV1 {

    private MemberRepository memberRepository = MemberRepository.getInstance();

    @RequestMapping("/springmvc/v1/members/save")
    public ModelAndView process(Map<String, String> paramMap) {
        List<Member> members = memberRepository.findAll();
        ModelAndView mv = new ModelAndView("members");
        mv.addObject("members", members);   //mv.getModel().put("members", members);      //members 리스트로 저장
        return mv;
    }
}

  - web.springmvc.v1.SpringMemberListControllerV1

  - Save와 동일하게 addObject로 멤버를 추가한다.

 > 위 로직은 뷰 리졸버 동작방식으로 동작한다.

 

스프링 부트가 자동 등록하는 뷰 리졸버 동작방식