framework/spring

2. spring mvc1

wooweee 2023. 5. 24. 00:19
728x90

1. WAS 작동 큰 그림

 

  • 서블릿이라는게 http message의 실제 필요한 부분만 logic에 사용할 수 있도록 반복 작업을 다 수행해준다.
  • request, response 라는 것을 이용해서 요청온 정보중 원하는 부분만 가지고 올 수 있고 내가 보내고 싶은 정보를 보낼 수 있다.

 

2. 서블릿

 

2.1. 기본

  1. 임시저장소
    • request.setAttribute(name, value)
    • request.getAttribute(name)
  2. 세션관리
    • request.getSession(create: true)

 

  • 요청
    1. queryParams, html
    2. api
      1. text
      2. json

 

2.2. 요청

  • queary params, html form 조회 메서드
// http://localhost:8080/request-param?username=hello&age=20&username=bye
// key-value 형식

// 1. 단일 파라미터 조회 
String username = request.getParameter("username"); // 중복된 key있을 시 1st 꺼냄
String username = request.getParameter("age");

// 2. 파라미터 이름들 모두 조회(중복 key는 1st만 꺼냄)
Enumeration<String> parameterNames = request.getParameterNames(); //username,age 다꺼냄
request.getParameterNames()
	.asIterator().forEachRemaining(paramName->sout(paramName)) // 전체 params 각각 꺼냄

// 3. 파라미터를 Map 으로 조회
Map<String, String[]> parameterMap = request.getParameterMap();

// 4. 복수 파라미터 조회
String[] usernames = request.getParameterValues("username"); // 중복된 key가 있을때만 사용가능

 

  • HTTP message body  -  API 메시지 바디_단순 텍스트
    1. getInputStream()
    2. StreamUtils.copyToString()
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        
        ServletInputStream inputStream = request.getInputStream();
        // http message body의 내용을 bytetypecode로 읽어온다.
        String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
        // bytecode를 string으로 변환, 변환시 항상 encoding 정보를 알려줘야함
        
        System.out.println("messageBody = " + messageBody);
	}

 

  • HTTP message body  -  API 메시지 바디_JSON
    • JSON -> Object로 변경해서 읽어야한다.
      = JSON도 JS의 Object nation(?)으로 JS의 객체이기 때문
    • 방법
      1. json 객체를 모방한 java의 객체

      2. new ObjectMapper() - json이랑 java 객체 mapping 용도
      3. getInputStream()
      4. StreamUtils.copyToString()
      5. objectMapper.readValue
// json 형태의 객체를 받아올 java의 객체를 미리 준비
@Getter @Setter
public class HelloData {
    private String username;
    private int age;
}
@WebServlet(name = "requestBodyJsonServlet", urlPatterns = "/request-body-json")
public class RequestBodyJsonServlet extends HttpServlet {
    // 1. json data를 파싱해서 사용할 수 있는 java object로 변환하기 위한 Jackson library, ObjectMapper 제공
    // json 변환 library: Jackson, Gson
    private ObjectMapper objectMapper = new ObjectMapper();
    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 2. http meassage body에서 data 꺼내는 작업
        ServletInputStream inputStream = request.getInputStream();
        String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
        System.out.println("messageBody = " + messageBody);

        // 3. 꺼낸 data(=json)을 java 객체에 담아서 파싱하기
        HelloData helloData = objectMapper.readValue(messageBody, HelloData.class);
        
        // object로 변환된 data 출력
        System.out.println("helloData.getUsername() = " + helloData.getUsername());
        System.out.println("helloData.getAge() = " + helloData.getAge());

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

 

2.3. 응답

  • 응답
    1. query, html
    2. http api
      1. text
      2. json

 

  • 기본 정보
@WebServlet(name = "responseHeaderServlet", urlPatterns = "/response-header")
public class ResponseHeaderServlet extends HttpServlet {
    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // status-line
        response.setStatus(HttpServletResponse.SC_OK); //http 응답코드

        // response-headers
        response.setHeader("Content-Type ", "text/plain;charset=utf-8");
        response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
        response.setHeader("Pragma", "no-cache");
        response.setHeader("my-header", "woowee"); // 내가 만든 임의의 header

        // header 편의 method
        content(response);
        cookie(response);
        redirect(response);

        PrintWriter writer = response.getWriter();
        writer.println("hello");
    }
    // Content 편의 method
    private void content(HttpServletResponse response) {
        response.setContentType("text/plain");
        response.setCharacterEncoding("utf-8");
        // response.setContentLength(2); // 생략시 자동 생성
    }
    // Cookie 편의 method
    private void cookie(HttpServletResponse response) {
        Cookie cookie = new Cookie("myCookie", "good");
        cookie.setMaxAge(600);
        response.addCookie(cookie);
    }
    // redirect 편의 method
    private void redirect(HttpServletResponse response) throws IOException {
        //Status Code 302
        // Location: /basic/hello-form.html
        
//        response.setStatus(HttpServletResponse.SC_FOUND); // 302
//        response.setHeader("Location", "/basic/hello-form.html");
        response.sendRedirect("/basic/hello-form.html");
    }
}

 

  • query, html
PrintWriter writer = response.getWriter();
writer.println("hello");
@WebServlet(name = "responseHtmlServlet",urlPatterns = "/response-html")
public class ResponseHtmlServlet extends HttpServlet {
    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response ) throws ServletException, IOException {
        response.setContentType("text/html");
        response.setCharacterEncoding("utf-8");
        
        PrintWriter writer = response.getWriter();
        writer.println("<html>");
        writer.println("<body>");
        writer.println("안녕");
        writer.println("</body>");
        writer.println("</html>");
        // 그냥 writer.println("안녕")만 해도 html로 나타남. ContentType에서 html이라고 알려주기 때문
    }
}

 

  • http api - json 응답
    1. java 객체 생성 -> 이 객체를 json 객체로 변경할 것이다.

    2. new ObjectMapper()
    3. objectMapper.writeValueAsString(java 객체)
    4. getWriter에 넣기

 

  • json 요청 받을 때 비교해보기
    1. new ObjectMapper() - json이랑 java 객체 mapping 용도
    2. getInputStream()
    3. StreamUtils.copyToString()
    4. objectMapper.readValue
@WebServlet(name = "responseJsonServlet", urlPatterns = "/response-json")
public class ResponseJsonServlet extends HttpServlet {
    // 1. objectMapper 생성
    private ObjectMapper objectMapper = new ObjectMapper();

    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("application/json");
        response.setCharacterEncoding("utf-8");
        // java class 객체에 http 응답 메시지 body에 넣을 data 담기 
        HelloData data = new HelloData();
        data.setUsername("woowee");
        data.setAge(26);
        // 2. java 객체를 json 형태의 string으로 담기
        String result = objectMapper.writeValueAsString(data);
        // http message body에 넣기
        response.getWriter().write(result);
    }
}

 

3. mvc 프레임워크 만들기

 

4. 스프링 mvc 구조 이해

 

 

  • ViewResolver
    • (1순위) BeanNameViewResolver            : 빈 이름으로 뷰를 찾아서 반환한다. (예: 엑셀 파일 생성 기능에 사용)
    • (2순위) InternalResourceViewResolver : JSP를 처리할 수 있는 뷰를 반환한다.

 

  • view 반환
    1. String : view 이름만 반환
    2. ModelAndView : ("view이름", model값)
      • mv.addObject("member", member);
@Controller
public class SpringMemberSaveControllerV1 {
    private MemberRepository memberRepository = MemberRepository.getInstance();

    @RequestMapping("springmvc/v1/members/save")
    public ModelAndView process(HttpServletRequest request, HttpServletResponse response) {
    // dispatcher servlet 작동 때 받아온 req, res를 params로 가지고 온 것
        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.getModel().put("member", member);
        mv.addObject("member", member); // 더 깔끔한 방법
        return mv;
    }
}

 

 

3.1. mvc 기본 기능

  • 2가지 방식
    1. ModelAndView 반환 방식
      • modelAndView 생성하면서 초기화시, 논리 path 넣기
      • mv.addOject() 으로 view에 전달할 리소스 넣기
      • mv.getModel().put("member", member); 이것도 가능한데 위의 방법이 좋음

    2. String 논리url 반환 방식
      • dispatcherServlet에서 이미 만든 Model 객체를 이용(model.addAttribute())해서 view로 전달할 리소스 넣기
      • String으로 논리 url만 반환
    3. request params를 직접 받는 방법 : @RequestParam()
@Controller
@RequestMapping("/springmvc/v3/members")
public class SpringMemberControllerV3 {
    private MemberRepository memberRepository = MemberRepository.getInstance();

    @RequestMapping(value = "/new-form" , method = RequestMethod.GET)
    public String newForm(){
        return "new-form";
    }
    @PostMapping("/save")
    public String save(@RequestParam("username") String username, @RequestParam("age") int age, Model model) {

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

        model.addAttribute("member", member);
        return "save-result";
    }
    // model에 직접 저장
    @GetMapping
    public String members(Model model) {
        List<Member> members = memberRepository.findAll();

        model.addAttribute("members", members);
        return "members";
    }
}

 

4. mvc 기본 기능 - 핵심

4.1. 로그

  • slf4j 로그
# application.properties

# 전체 로그 레벌 설정(default 값으로 info)
logging.level.root=info

#hello.springmvc 패키지와 하의 로그 레벨 설정
logging.level.hello.springmvc=debug # debug부터 high level log 모두 출력됨. trace 출력 안됨

 

로그 단계:  TRACE > DEBUG > INFO > WARN > ERROR

 

@Slf4j
@RestController
public class LogTestController {
    @RequestMapping("/log-test")
    public String logTest(){
        log.trace("trace log={}", name);
        log.debug("debug log={}", name); // 개발 입장
        log.info("info log={}", name);   // 중요 정보
        log.warn("warn log={}", name);
        log.error("error log={}", name);

        return "ok";
    }
}

 

4.2. @requestMapping

@RestController
public class MappingController {
    /**
     * 편리한 축약 애노테이션 (코드보기)
     * @GetMapping
     * @PostMapping
     * @PutMapping
     * @DeleteMapping
     * @PatchMapping
     */
    @GetMapping(value = "/mapping-get-v2")
    public String mappingGetV2() {
        log.info("mapping-get-v2");
        return "ok";
    }

    /**
     * PathVariable 사용
     * 변수명이 같으면 생략 가능
     * @PathVariable("userId") String userId -> @PathVariable userId  * 이름과 params가 같으면 생략가능
     */
    @GetMapping("/mapping/{userId}")
    public String mappingPath(@PathVariable("userId") String data) {
        log.info("mappingPath userId={}", data);
        return "ok";
    }

    /**
     * PathVariable 사용 다중, 변수명이 같아서 생략
     */
    @GetMapping("/mapping/users/{userId}/orders/{orderId}")
    public String mappingPath(@PathVariable String userId, @PathVariable Long
            orderId) {
        log.info("mappingPath userId={}, orderId={}", userId, orderId);
        return "ok";
    }

 

4.3. http 요청 - 기본, 헤더 조회

  • multivalueMap: 동일 key에 여러 value를 배열로 받는 다.
MultiValueMap<String, String> map = new LinkedMultiValueMap();
map.add("keyA", "value1");
map.add("keyA", "value2");

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

 

  • header 정보 - cookie를 핵심으로 보기
@Slf4j
@RestController
public class RequestHeaderController {
    @RequestMapping("/headers")
    public String header(
         HttpServletRequest request,
         HttpServletResponse response,
         HttpMethod httpMethod,
         Locale locale, // 언어
         @RequestHeader MultiValueMap<String, String> headerMap,  // 모든 헤더 다 받을 때
         @RequestHeader("host") String host,  // 필수 헤더 받을 때, 헤더 key 값을 넣는다.
         @CookieValue(value = "myCookie", required = false) String cookie){
    }
}

 

4.4. 요청 params - query, Html form, http api

  • query, form
    1. @RequestParam : 기본형인 경우
    2. @ModelAttribute : 참조변수인 경우

 

  • @RequestParam
    • 어지간 하면 생략 말기
    • default랑 required 사용 경우 존재
    • params 여러개 받을 때 Map || MultiMap으로 받을 수 있다.
@ResponseBody
    @RequestMapping("/request-param-required")
    public String requestParamRequired(
            // default가 true : 해당 request params가 꼭 들어와야함
             @RequestParam(required = true) String username,
            @RequestParam(required = false) Integer age){ // int는 안됨. false인 경우 null을 반환하기 때문. 그래서 객체타입으로 null 받아야함.

        log.info("username={}, age={}", username, age);
        return "ok";
    }

    @ResponseBody
    @RequestMapping("/request-param-default")
    public String requestParamDefault(
            // false에서 값이 없거나 true에서 값이 안들어오면 default가 넘어감, null 뿐만이 아니라 빈문자열도 defaultValue 적용한다.
            @RequestParam(required = true, defaultValue = "guest") String username,
            @RequestParam(required = false, defaultValue = "-1") int age){

        log.info("username={}, age={}", username, age);
        return "ok";
    }

    @ResponseBody
    @RequestMapping("/request-param-map")
    // 해당  requestParams를 통채로 Map으로 가져온다. multiMap도 있음
    public String requestParamMap(@RequestParam Map<String, Object> paramMap){

        log.info("username={}, age={}", paramMap.get("username"),paramMap.get("age"));
        return "ok";
    }

 

 

@Slf4j
@Controller
public class RequestParamController {
    @RequestMapping("/model-attribute-v1")
    public String modelAttributeV1(@ModelAttribute HelloData helloData){

        log.info("username={}, age={}", helloData.getUsername(),helloData.getAge());
        log.info("helloData={}",helloData);
        return "ok";
    }
    @RequestMapping("/model-attribute-v2")
    public String modelAttributeV2(HelloData helloData){ // 생략가능

        log.info("username={}, age={}", helloData.getUsername(),helloData.getAge());
        log.info("helloData={}",helloData);
        return "ok";
    }
}

 

  • http message body : json, text

  • Http message body 조회 기능
    1. HttpEntity<>
      generics 사용 안할 시, object로 반환

    2. @RequestBody  -> 생략 하면 안됨
      spring이 요청 params 조회 애노테이션 생략으로 판단

 

@Slf4j
@Controller
public class RequestBodyStringController {    
    @PostMapping("/request-body-string-v3")
    public HttpEntity<String> requestBodyStringV3(HttpEntity<String> httpEntity) throws IOException{
        String body = httpEntity.getBody();
        return new HttpEntity<>("ok");
    }
    /*
    public HttpEntity<String> requestBodyStringV3(RequestEntity<String> httpEntity) throws IOException{
        String body = httpEntity.getBody();
        return new ResponseEntity<>("ok", HttpStatus.CREATED);
    }
     */
     
    @ResponseBody
    @PostMapping("/request-body-string-v4")
    public String requestBodyStringV4(@RequestBody String messageBody) {
        return "ok";
    }
}

 

  • json
    • @RequestBody
    • HttpEntity<HelloData> data
@ResponseBody
    @PostMapping("/request-body-json-v3")
    public String requestBodyJsonV3(@RequestBody HelloData helloData) {
        log.info("messageBody={}", helloData);
        log.info("username={}, age={}", helloData.getUsername(), helloData.getAge());
        return "ok";
    }

    @ResponseBody
    @PostMapping("/request-body-json-v4")
    public String requestBodyJsonV4(HttpEntity<HelloData> data) {
        HelloData helloData = data.getBody();
        log.info("messageBody={}", helloData);
        log.info("username={}, age={}", helloData.getUsername(), helloData.getAge());
        return "ok";
    }

    @ResponseBody
    @PostMapping("/request-body-json-v5")
    public HelloData requestBodyJsonV5(@RequestBody HelloData helloData) {
        log.info("username={}, age={}", helloData.getUsername(), helloData.getAge());
        return helloData; // http message converter를 통해서 바뀜
    }

 

  • query, form data를 view로 보내는건 쉬우니깐 생략
  • body - json, text 
  • 방법
    1. ResponseEntity<>("보낼 data", 상태코드)
    2. @ResponseBody
      @ResponseStatus
package hello.springmvc.basic.response;

@Slf4j
@Controller
public class ResponseBodyController {

    // 1. 문자열 처리
    @GetMapping("/response-body-string-v1")
    public void responseBodyV1(HttpServletResponse response) throws IOException{
        response.getWriter().write("ok");
    }

    @GetMapping("/response-body-string-v2")
    public ResponseEntity<String> responseBodyV2(){
        return new ResponseEntity<>("ok", HttpStatus.OK);
    }

    @ResponseBody
    @GetMapping("/response-body-string-v3")
    public String responseBodyV3() {
        return "ok";
    }

    // 2. json 처리
    @GetMapping("/response-body-json-v1")
    public ResponseEntity<HelloData> responseBodyJsonV1(){
        HelloData helloData = new HelloData();
        helloData.setUsername("userA");
        helloData.setAge(20);
        return new ResponseEntity<>(helloData, HttpStatus.OK);
    }

    @ResponseStatus(HttpStatus.OK) // responseEntity와 달리 상태코드 설정 못하기 때문
    @ResponseBody
    @GetMapping("/response-body-json-v2")
    public HelloData responseBodyJsonV2(){
        HelloData helloData = new HelloData();
        helloData.setUsername("userA");
        helloData.setAge(20);
        return helloData;
    }
}

 

 

5. redirect , PRG

스프링 MVC 6 - thymeleaf, @ModelAttribute, PRG, Redirection

 

스프링 MVC 6 - thymeleaf, @ModelAttribute, PRG, Redirection

0. 부트스트랩 html form 이쁘게 보이도록 하려고 사용 https://getbootstrap.com 동작 https://getbootstrap.com/docs/5.0/getting-started/download/ Compiled CSS and JS 항목을 다운로드 압축을 풀고 bootstrap.min.css 를 복사 폴더

code-is-me.tistory.com

 

  • modelAttribute
 @PostMapping("/add")
    public String addItemV4(Item item){
        itemRepository.save(item);
        return "basic/item"; // 해당 url로도 상세화면이 가는 이유는 @ModelAttribute가 basic/item에 직접 값을 넣어주기 때문
    }

 

  • PRG
package hello.itemservice.web.basic;

@Controller
@RequestMapping("/basic/items") // 공통부분
@RequiredArgsConstructor // 생성자 주입 자동으로 해주는 lombok
public class BasicItemController {
    private final ItemRepository itemRepository;

    @PostMapping("/add")
    public String addItemV6(Item item, RedirectAttributes redirectAttributes){
        Item savedItem = itemRepository.save(item);
        redirectAttributes.addAttribute("itemId", savedItem.getId());
        redirectAttributes.addAttribute("status", true); 
        // 추가기능, url 마지막에 query 형식으로 ?status=true 추가됨
        return "redirect:/basic/items/{itemId}";
    }
}
  • RedirectAttribute 기능 - data가 view로 넘어가는 것이 아니고 Url redirection 정보로 사용된다.
    1. url 인코딩 기능 수행
      redirectAttributes.addAttribute("itemId", savedItem.getId());
       
    2. 추가 기능까지 수행
      redirectAttributes.addAttribute("status", true); 

  • addFlashAttribute
    • 그냥 Model로 data 넘겨도 되는데 회원가입 완료 알림창 같은 경우 model에 담게되면 이동시마다 계속 나타나게 된다.
    • 위의 메서드 사용시, data가 session에 담긴후, view에서 한번만 수행된다.
    • 회원가입 완료 알림창 같은 경우 사용된다.