。゚(*´□`)゚。

코딩의 즐거움과 도전, 그리고 일상의 소소한 순간들이 어우러진 블로그

[네이버클라우드] 클라우드 기반의 개발자 과정 7기/웹프로그래밍

[NC7기-93일차(9월7일)] - 웹프로그래밍 74일차

quarrrter 2023. 9. 7. 18:11

에이젝스? 

80. Spring WebMVC 활용하기 

 

 

 

*Spring WebMVC 아키텍처

프론트컨트롤러 (DispatcherServlet) // 서블릿으로 만들어짐 

앞에서 모든 요청도 주고 응답도함. 

 

return 전 , 6 JSP 를 include 함 (JSP가 view 임)

return 응답 

 

페이지 컨트롤러(Controller) // 일반 자바객체

call: 실제 일을 함 , 요청을 처리 (1. 요청할 때 넘어온 데이터 가공, 2. 가공한 데이터로 서비스 객체 실행,3. 서비스 객체가 뱉어낸 결과(응답 데이터를 가공   4. 어느 뷰 컨포넌트를 사용할지 지정하고, 뷰컨포넌트를 알려줌)

 

return : view 컴포넌트 정보 / ex JSP URL

 

서비스 객체  // 일반 자바객체

비지니스 로직 수행 (컨트롤러가 넘겨준 데이터를 가지고  )

트랜젝션 제어 

 

DAO 객체  // 일반 자바객체

데이터 처리

 

*서비스객체 & DAO 객체 : Model 이라고 부름 

 

 

MVC 종류 

1. standalone  - GUI App

- pc 설치형 

2. Web App

 

 

*Page Controller

@Controller : 페이지 컨트롤러 임을 표시 

 

*DispatcherServlet <<servlet>>

페이지 컨트롤러는 무조건 Dispatcher 거쳐서 가야됨. 그래서 프론트컨트롤러 url추가 한 주소로 가야함

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
                      http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
  version="4.0" metadata-complete="false">

  <description>
    스프링 Web MVC 프레임워크 예제 테스트
  </description>

  <display-name>java-spring-webmvc</display-name>

  <!-- Spring Web MVC의 프론트 컨트롤러 역할을 수행할 서블릿을 지정한다. -->
  
  <!-- DispatcherServlet 배치하기 : 방법1-->
  
  <!-- => DispatcherServlet은 자체적으로 IoC 컨테이너(기본: XmlWebApplicationContext)를 보유하고 있다.
       => 파라미터를 사용하여 IoC 컨테이너의 설정 파일을 지정해야 한다.
          초기화 파라미터명: contextConfigLocation
          초기화 파라미터값: 예) /WEB-INF/app-servlet.xml
       => 설정하고 싶지 않다면 init-value를 비워둬라.
  --> 
  <servlet>
    <servlet-name>app</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>/WEB-INF/app-servlet.xml</param-value>
    </init-param>
    <!-- 서블릿을 요청하지 않아도 웹 애플리케이션을 시작시킬 때 자동 생성되어 
         IoC 컨테이너를 준비할 수 있도록 
         다음 옵션을 붙인다. -->
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>app</servlet-name>
    <url-pattern>/app/*</url-pattern>
  </servlet-mapping>
  
  <welcome-file-list>
    <welcome-file>index.html</welcome-file>
    <welcome-file>index.htm</welcome-file>
    <welcome-file>default.htm</welcome-file>
  </welcome-file-list>

</web-app>

=> web.xml에서 ㄷ디스패처 안에 잇는 ioc 컨테이너의 위치를 설정하지 않으면 자동으로 /WEB-INF 밑에서 디스패처서블릿이름으로 설정된 *servlet.xml 을 찾는다 

  <!-- => contextConfigLocation 초기화 변수가 없으면
          다음 규칙에 따라 작성된 IoC 설정 파일을 자동으로 찾는다.
          /WEB-INF/서블릿이름-servlet.xml
          해당 파일을 찾지 못하면 예외가 발생한다.
       => contextConfigLocation 초기화 변수가 있다면,
          - 지정한 설정 파일을 로딩하여 객체를 준비한다.
          - 만약 변수의 값이 비어 있다면, 아무런 객체를 생성하지 않는다.
            변수가 없을 때와 달리 예외가 발생하지 않는다.
       => 즉 contextConfigLocation 변수를 생략하는 것과
          변수의 값을 비워두는 것은 다르다.
 
          생략하면 기본 위치에서 뒤지는거고, 주석처리(안쓰면) 객체를 안 만듬
 
   <!--  
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value></param-value>
    </init-param>
    -->
 
따라서,
  <!-- => DispatcherServlet의 IoC 컨테이너를 사용하고 싶지 않다면,
          contextConfigLocation 초기화 파라미터의 값을 빈 채로 두면 된다.
          초기화 파라미터 변수가 없으면 안된다.
          왜?
          없으면 /WEB-INF/서블릿이름-servlet.xml 파일을 자동으로 찾기 때문이다.
  -->

bitcamp 밑에 @component 붙은 거 다 찾아서 생성하는거임 !!
이렇게 해도 돌아는 감. 이렇게 하면 안되고 제 위치에 두기 왜냐면,,,
. WEB-INF 폴더에 두면 노출이 안 되는드ㅔ 밖에 두면&nbsp;ㅇㅣ렇게 노출됨

// 프론트 컨트롤러(DispatcherServlet)가 실행할 페이지 컨트롤러는
// 다음과 같이 @Controller 애노테이션을 붙여야 한다.
@Controller
public class HelloController {
  // 클라이언트 요청이 들어왔을 때 호출될 메서드(request handler)를
  // 표시하려면 다음과 같이 @RequestMapping 애노테이션을 붙여야 한다.
  //    @RequestMapping(요청URL)
  // => 예)
  //    @RequestMapping(value="/hello")
  //    @RequestMapping("/hello")
  //    @RequestMapping(path="/hello")
  @RequestMapping({"/hello", "/hello2", "/okok"})
  // 리턴하는 String 값이 뷰 컴포넌트(예: JSP)의 URL이 아닌 경우
  // 애노테이션으로 표시한다.
  // => @ResponseBody : 리턴하는 문자열이 클라이언트에게 보낼 콘텐트임을 표시한다.
  // => 이 애노테이션이 붙어 있으면,
  //    프론트 컨트롤러는 리턴 값을 클라이언트에게 그대로 전송한다.
  @ResponseBody
  public String hello() throws Exception {
    return "

 

* ContextLoaderListener // ServletContextListener임:

ServletContextListener : 서버(웹앱)가 실행될 때 실행되는 객체  = contextinitialaized 실행됨. , contextdestroyed는 웹앱이 종료될때 실행됨. 

 

 

루트에서 쓸 xml 파일 위치 설정

<!--
  ContextLoaderListener?
      - DispatcherServlet 클래스와 달리
        ContextLoaderListener의 IoC 컨테이너는
        웹 관련 애노테이션(예: @RequestMapping)을 처리하지 못한다.
      - 즉 @Component, @Controller, @Service 등의 애노테이션 붙은  
        클래스에 대해 객체를 생성해 주지만,
        @RequestMapping, @ResponseBody 등과 같은
        웹 관련 애노테이션은 인식하지 못한다.
      - 따라서 페이지 컨트롤러의 요청 핸들러를 관리하는 일을 하지 못한다.
  해결책?
      - 웹관련 애노테이션을 처리할 도우미 객체를 등록해야 한다.
      - 즉 다음과 같이 WebMVC 관련 애노테이션을 처리할 도우미 객체를 등록하라.
      - 주목!
        DispatcherServlet의 IoC 컨테이너는 이런 일을 기본으로 하기 때문에 다음과 같은 태그를 선언하지 않아도 페이지
컨트롤러를 처리한다.
           
-->

  <mvc:annotation-driven/>  -> 관련된 빈들을 알아서 설정한다. ? 

 

* 여러개의 DispatcherServlet 

만약 servlet.xml을 config 안에 두게 되면 initialparamdmf을 설정해야함. 그래서 그냥 WEB-INF밑에 바로두는걸 추천

 

* DispatcherServlet의 기본 IoC 컨테이너 교체하기 

기본 IoC 컨테이너: xmlWebApplicationContext 내장되어 있음 

 

교체하기1 -그냥 서블릿에 잇는 서블릿컨텍스트 리스너 구현체를 만들어서 이니셜라이저가 호출될때 서블릿 등록하는 방법

addServlet: 서블릿 등록 

Dynamic: 부가 정보 설정 

 

startup의 기능 

이 객체를 만들어서 startup을 호출할때 

 

톰캣이 4. 스프링이니셜라이져 객체를 만들어서 찾은다음에 온스타트업에 넣어서 호출. 

 

// 페이지 컨트롤러 만드는 방법
@Controller // 이 애노테이션을 붙인다.
@RequestMapping("/c01_1") // 컨트롤러에 URL을 매핑한다.
public class Controller01_1 {

  //@RequestMapping // 이 애노테이션을 붙여서 요청이 들어왔을 때 호출될 메서드임을 표시한다.
  @ResponseBody // 메서드의 리턴 값이 클라이언트에게 출력할 내용임을 표시한다.
  public String handler() {
    return "c01_1 -> handler()";
  }

  // URL 한 개 당 한 개의 핸들러만 연결할 수 있다.
  // 같은 URL에 대해 다른 메서드를 또 정의하면 실행 오류가 발생한다.
  @RequestMapping       <===== 요거 붙은게 적용됨 
  @ResponseBody
  public String handler2() {
    return "c01_1 -> handler2()";
  }
}

=> 클래스에 매핑 붙이고 메서드에 매핑붙이면 됨. 

 

// 페이지 컨트롤러 만드는 방법 - 기본 URL과 상세 URL을 분리하여 설정하기
package bitcamp.app1;

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

@Controller
@RequestMapping("/c01_3") // 핸들러에 적용될 기본 URL을 지정한다.
public class Controller01_3 {

  @RequestMapping("h1") // 기본 URL에 뒤에 붙는 상세 URL. 예) /c01_3/h1
  @ResponseBody
  public String handler() {
    return "h1";
  }

  @RequestMapping("/h2") // 앞에 /를 붙여도 되고 생략해도 된다. 예) /c01_3/h2
  @ResponseBody
  public String handler2() {
    return "h2";
  }

  @RequestMapping("h3")
  @ResponseBody
  public String handler3() {
    return "h3";
  }

  @RequestMapping("h4")
  @ResponseBody
  public String handler4() {
    return "h4";
  }

  @RequestMapping({"h5", "h6", "h7"})
  @ResponseBody
  public String handler5() {
    return "h5,h6,h7";
  }
}
// GET, POST 구분하기
package bitcamp.app1;

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

@Controller
@RequestMapping("/c02_1")
public class Controller02_1 {

  // 테스트 방법:
  // => http://localhost:9999/eomcs-spring-webmvc/html/app1/c02_1.html

  @RequestMapping(method = RequestMethod.GET) // GET 요청일 때만 호출된다.
  @ResponseBody
  public String handler1() {
    return "get";
  }

  @RequestMapping(method = RequestMethod.POST) // POST 요청일 때만 호출된다.
  @ResponseBody
  public String handler2() {
    return "post";
  }
}
// GET, POST 구분하기 II
package bitcamp.app1;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
@RequestMapping("/c02_2")
public class Controller02_2 {

  // 테스트 방법:
  // => http://localhost:9999/eomcs-spring-webmvc/html/app1/c02_2.html

  @GetMapping // GET 요청일 때만 호출된다.
  @ResponseBody
  public String handler1() {
    return "get";
  }

  @PostMapping // POST 요청일 때만 호출된다.
  @ResponseBody
  public String handler2() {
    return "post";
  }
}
// request handler를 구분하는 방법 - 파라미터 이름으로 구분하기
package bitcamp.app1;

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.ResponseBody;

@Controller
@RequestMapping("/c03_1")
public class Controller03_1 {

  // 테스트 방법:
  // => http://localhost:9999/eomcs-spring-webmvc/app1/c03_1?name=kim
  @GetMapping(params = "name")
  @ResponseBody
  public String handler1() {
    return "handler1";
  }

  // 테스트 방법:
  // => http://localhost:9999/eomcs-spring-webmvc/app1/c03_1?age=20
  @GetMapping(params = "age")
  @ResponseBody
  public String handler2() {
    return "handler2";
  }

  // 테스트 방법:
  // => http://localhost:9999/eomcs-spring-webmvc/app1/c03_1?name=kim&age=20
  @GetMapping(params = {"age", "name"})
  @ResponseBody
  public String handler3() {
    return "handler3";
  }

  // 테스트 방법:
  // => http://localhost:9999/eomcs-spring-webmvc/app1/c03_1
  @GetMapping
  @ResponseBody
  public String handler4() {
    return "handler4";
  }
}
// request handler를 구분하는 방법 - Accept 요청 헤더의 값에 따라 구분하기
package bitcamp.app1;

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.ResponseBody;

@Controller
@RequestMapping("/c03_3")
public class Controller03_3 {

  // 테스트 방법:
  // => http://localhost:9999/eomcs-spring-webmvc/html/app1/c03_3.html
  // => 요청 헤더 중에서 Accept의 값에 따라 구분할 때 사용한다.
  //
  // Accept 헤더?
  // => HTTP 클라이언트(웹 브라우저)에서 서버에 요청할 때
  //    받고자 하는 콘텐트의 타입을 알려준다.

  @GetMapping(produces = "text/plain")
  @ResponseBody
  public String handler1() {
    return "handler1";
  }

  @GetMapping(produces = "text/html")
  @ResponseBody
  public String handler2() {
    return "handler2";
  }

  @GetMapping(produces = "application/json")
  @ResponseBody
  public String handler3() {
    return "handler3";
  }

  @GetMapping
  @ResponseBody
  public String handler4() {
    return "handler4";
  }
}

 

 

// request handler를 구분하는 방법 - Content-Type 헤더의 값에 따라 구분하기
package bitcamp.app1;

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

@Controller
@RequestMapping("/c03_4")
public class Controller03_4 {

  // Content-Type 요청 헤더
  // => HTTP 클라이언트가 보내는 데이터의 콘텐트 타입이다.
  // => 프론트 컨트롤러는 보내는 데이터의 타입에 따라 처리를 구분할 수 있다.

  // 테스트 방법:
  // => http://localhost:9999/eomcs-spring-webmvc/html/app1/c03_4.html
  // => 클라이언트가 POST 요청으로 데이터를 보낼 때 기본 형식은 다음과 같다.
  //      application/x-www-form-urlencoded
  // => <form> 태그에서 enctype 속성에 "mulpart/form-data"를 지정하면
  //    해당 형식으로 서버에 값을 보낸다.
  // => 자바스크립트를 사용하여 개발자가 임의의 형식으로 값을 보낼 수 있다.
  //
  // 클라이언트가 POST로 요청할 때 보내는 데이터의 유형에 따라 호출될 메서드를 구분할 때 사용한다.

  // 다음 메서드는 application/x-www-form-urlencoded 형식의 데이터를 소비한다.
  // => 즉 클라이언트의 HTTP 요청에서 Content-Type 헤더의 값이 위와 같을 때
  //    이 메서드를 호출하라는 의미다.
  @PostMapping(consumes = "application/x-www-form-urlencoded")
  @ResponseBody
  public String handler1() {
    return "handler1";
  }

  // 다음 메서드는 multipart/form-data 형식의 데이터를 소비한다.
  @PostMapping(consumes = "multipart/form-data")
  @ResponseBody
  public String handler2() {
    return "handler2";
  }

  // 다음 메서드는 text/csv 형식의 데이터를 소비한다.
  @PostMapping(consumes = "text/csv")
  @ResponseBody
  public String handler3() {
    return "handler3";
  }

  // 다음 메서드는 application/json 형식의 데이터를 소비한다.
  @PostMapping(consumes = "application/json")
  @ResponseBody
  public String handler4() {
    return "handler4";
  }

  // 다음 메서드는 Content-Type 헤더가 없을 때 호출된다.
  @RequestMapping
  @ResponseBody
  public String handler5() {
    return "handler5";
  }
}

raw에&nbsp;{"name":"aaa",&nbsp;"Age"=20,"working"=true,"tel"="1111-2222"}

// 요청 핸들러의 아규먼트 - @RequestParam
@Controller
@RequestMapping("/c04_2")
public class Controller04_2 {

  // 클라이언트가 보낸 파라미터 값을 바로 받을 수 있다.

  // => 요청 핸들러의 파라미터로 선언하면 된다.
  //    단 파라미터 앞에 @RequestParam 애노테이션을 붙인다.
  //    그리고 클라이언트가 보낸 파라미터 이름을 지정한다.
  // 테스트:
  // => http://localhost:9999/eomcs-spring-webmvc/app1/c04_2/h1?name=kim
  @GetMapping("h1")
  @ResponseBody
  public void handler1(
      PrintWriter out,
      ServletRequest request,
      @RequestParam(value = "name") String name1,
      @RequestParam(name = "name") String name2, // value와 name은 같은 일을 한다.
      @RequestParam("name") String name3, // value 이름을 생략할 수 있다.
      /* @RequestParam("name") */ String name // 요청 파라미터 이름과 메서드 파라미터(아규먼트)의 이름이 같다면
      // 애노테이션을 생략해도 된다.
      ) {

    out.printf("name=%s\n", request.getParameter("name"));
    out.printf("name=%s\n", name1);
    out.printf("name=%s\n", name2);
    out.printf("name=%s\n", name3);
    out.printf("name=%s\n", name);
  }

// 테스트:
// http://.../app1/c04_2/h2?name1=kim&name2=park
@GetMapping("h2")
@ResponseBody
public void handler2(
    PrintWriter out,

    @RequestParam("name1") String name1, // 애노테이션을 붙이면 필수 항목으로 간주한다.
    // 따라서 파라미터 값이 없으면 예외가 발생한다.

    String name2, // 애노테이션을 붙이지 않으면 선택 항목으로 간주한다.
    // 따라서 파라미터 값이 없으면 null을 받는다.

    @RequestParam(value = "name3", required = false) String name3,
    // required 프로퍼티를 false로 설정하면 선택 항목으로 간주한다.

    @RequestParam(value = "name4", defaultValue = "ohora") String name4
    // 기본 값을 지정하면 파라미터 값이 없어도 된다.
    ) {

  out.printf("name1=%s\n", name1);
  out.printf("name2=%s\n", name2);
  out.printf("name3=%s\n", name3);
  out.printf("name4=%s\n", name4);
}

// 요청 핸들러의 아규먼트 - 도메인 객체(값 객체; Value Object)로 요청 파라미터 값 받기
// 요청 핸들러의 아규먼트 - 도메인 객체(값 객체; Value Object)로 요청 파라미터 값 받기
package bitcamp.app1;

import java.io.PrintWriter;
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.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
@RequestMapping("/c04_3")
public class Controller04_3 {

  // 클라이언트가 보낸 요청 파라미터 값을 값 객체에 받을 수 있다.

  // => 요청 핸들러의 아규먼트가 값 객체라면,
  //    프론트 컨트롤러는 메서드를 호출할 때 값 객체의 인스턴스를 생성한 후
  //    요청 파라미터와 일치하는 프로퍼티에 대해 값을 저장한다.
  //    그리고 호출할 때 넘겨준다.
  //
  // 테스트:
  // => http://.../c04_3/h1?model=sonata&maker=hyundai&capacity=5&auto=true&engine.model=ok&engine.cc=1980&engine.valve=16
  @GetMapping("h1")
  @ResponseBody
  public void handler1(
      PrintWriter out,

      String model,

      String maker,

      /*@RequestParam(defaultValue = "100")*/ int capacity, // 프론트 컨트롤러가 String 값을 int로 변환해 준다.
      // 단 변환할 수 없을 경우 예외가 발생한다.

      boolean auto,
      // 프론트 컨트롤러가 String 값을 boolean으로 변환해 준다.
      // 단 변환할 수 없을 경우 예외가 발생한다.
      // "true", "false"는 대소문자 구분없이 true, false로 변환해 준다.
      // 1 ==> true, 0 ==> false 로 변환해 준다. 그 외 숫자는 예외 발생!

      Car car
      // 아규먼트가 값 객체이면 요청 파라미터 중에서 값 객체의 프로퍼티 이름과 일치하는
      // 항목에 대해 값을 넣어준다.
      // 값 객체 안에 또 값 객체가 있을 때는 OGNL 방식으로 요청 파라미터 값을
      // 지정하면 된다.
      // 예) ...&engine.model=ok&engine.cc=1980&engine.valve=16
      ) {

    out.printf("model=%s\n", model);
    out.printf("maker=%s\n", maker);
    out.printf("capacity=%s\n", capacity);
    out.printf("auto=%s\n", auto);
    out.printf("car=%s\n", car);
  }

}


애노테이션 살림 . 디폴트 5 설정

객체안에 객체에 값을 넣고 싶을 때 

car, engine 이면 ,, 

engine.model="ㅁㅁㅁ"이렇게 파라미터 넣으면 됨