。゚(*´□`)゚。

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

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

[NC7기-94일차(9월8일)] - 웹프로그래밍 75일차

quarrrter 2023. 9. 8. 18:22

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

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

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

  // => 요청 핸들러의 아규먼트가 값 객체라면,
  //    프론트 컨트롤러는 메서드를 호출할 때 값 객체의 인스턴스를 생성한 후
  //    요청 파라미터와 일치하는 프로퍼티에 대해 값을 저장한다.
  //    그리고 호출할 때 넘겨준다.
  
  @GetMapping("h1")
  @ResponseBody
  public void handler1(
      PrintWriter out,

      String model,

      String maker,

      @RequestParam(defaultValue = "5") 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);
  }

}

 

일치하는 이름이 있으면 자동으로 꼽아준다 

보통은 request param 안 넣는데 

숫자가 넘어올수도 잇고 안 넘어올수도 있는 상황에선  리퀘스트 파람 넣어야함 

만약 안 넘어오거나 다른 프러머티브 타입이 넘어와서 에러 떠도 되는 상황엔 리퀘스트  파람 안 넣어도 됨. 

이상한게 넘어왓을때 넣을 디폴트 값을 넣으려면 리퀘스트 파람 디폴트 설정하면 됨. 

 

Car car : 프론트컨트롤러가  Car 객체를 생성해서 프로퍼티(셋터메서드)에 잇는 값들에 넣음 

 

 

Property editor : Convertor 

클라이언트가 보낸 요청 파라미터 값(String 타입)을
request handler의 아규먼트 타입(String, int, boolean 등)의 값으로 바꿀 때
primitive type에 대해서만 자동으로 변환해 준다.
그 외의 타입에 대해서는 프로퍼티 에디터(타입 변환기)가 없으면 예외를 발생시킨다.
 페이지 컨트롤러에서 사용할 프로퍼티 에디터 설정하는 방법
 
   => 프론트 컨트롤러는 request handler를 호출하기 전에
      그 메서드가 원하는 아규먼트 값을 준비해야 한다.
      각 아규먼트 값을 준비할 때
      @InitBinder가 표시된 메서드(request handler를 실행할 때 사용할 도구를 준비하는 메서드)
      를 호출하여 프로퍼티 에디터(변환기)를 준비시킨다.
      그리고 이 준비된 값 변환기(프로퍼티 에디터)를 이용하여 파라미터 값을
      request handler의 아규먼트가 원하는 타입의 값을 바꾼다.
      request handler의 아규먼트 개수 만큼 이 메서드를 호출한다.
   => 따라서 프로퍼티 에디터를 적용하기에
      @InitBinder가 표시된 메서드가 적절한 지점이다.
      즉 이 메서드에 프로퍼티 에디터를 등록하는 코드를 둔다.

 

---ioc컨테이너때는 이렇게 설정함 

Web mvc에서는  프로퍼티 에디터를 사용할 때마다 매번 호출한다. 

메모리아낀다고 인스턴스 변수에 넣어놓지 말고 쓰던 안 쓰던 매번 호출되게 등록해라.

인스턴스 변수에 넣어서 공유하게 되면 충돌관련 오류가 일어남; 

 

model, capacity, auto에서 안 쓰고 created에서만 사용하지만 4번 모두 호출됨.
4번

 
@InitBinder
// => 메서드 이름은 마음대로.
// => 작업하는데 필요한 값이 있다면 파라미터로 선언하라.
public void initBinder(WebDataBinder binder) {
  System.out.println("Controller04_4.initBinder()...");
  // 프로퍼티 에디터를 등록하려면 그 일을 수행할 객체(WebDataBinder)가 필요하다.
  // request handler 처럼 아규먼트를 선언하여
  // 프론트 컨트롤러에게 달라고 요청하라.
  // binder: 등록기

  //String ===> java.util.Date 프로퍼티 에디터 준비
  DatePropertyEditor propEditor = new DatePropertyEditor();

  // WebDataBinder에 프로퍼티 에디터 등록하기
  binder.registerCustomEditor(
      Date.class, // String을 Date 타입으로 바꾸는 에디터임을 지정한다.
      propEditor // 바꿔주는 일을 하는 프로퍼티 에디터를 등록한다.
      );


  // WebDataBinder에 프로퍼티 에디터 등록하기
  binder.registerCustomEditor(
      Car.class, // String을 Car 타입으로 바꾸는 에디터임을 지정한다.
      new CarPropertyEditor() // 바꿔주는 일을 하는 프로퍼티 에디터를 등록한다.
      );

  // WebDataBinder에 프로퍼티 에디터 등록하기
  binder.registerCustomEditor(Engine.class, // String을 Engine 타입으로 바꾸는 에디터임을 지정한다.
      new EnginePropertyEditor() // 바꿔주는 일을 하는 프로퍼티 에디터를 등록한다.
      );
}

// PropertyEditor 만들기
// => 문자열을 특정 타입의 프로퍼터의 값으로 변환시킬 때 사용하는 에디터이다.
// => java.beans.PropertyEditor 인터페이스를 구현해야 한다.
// => PropertyEditor를 직접 구현하면 너무 많은 메서드를 오버라이딩 해야 하기 때문에
//    자바에서는 도우미 클래스인 PropertyEditorSupport 클래스를 제공한다.
//    이 클래스는 PropertyEditor를 미리 구현하였다.
//    따라서 이 클래스를 상속 받은 것 더 낫다.
class DatePropertyEditor extends PropertyEditorSupport {

  @Override
  public void setAsText(String text) throws IllegalArgumentException {
    System.out.println("DatePropertyEditor.setAsText()");
    // 프로퍼티 에디터를 사용하는 측(예: 프론트 컨트롤러)에서
    // 문자열을 Date 객체로 바꾸기 위해 이 메서드를 호출할 것이다.
    // 그러면 이 메서드에서 문자열을 프로퍼티가 원하는 타입으로 변환한 후 저장하면 된다.
    try {

      // 1) String ==> java.util.Date
      // SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
      // Date date = format.parse(text); // String ===> java.util.Date
      // setValue(date); // 내부에 저장

      // 2) String ==> java.sql.Date
      setValue(java.sql.Date.valueOf(text));

    } catch (Exception e) {
      throw new IllegalArgumentException(e);
    }
  }

  @Override
  public Object getValue() {
    System.out.println("DatePropertyEditor.getValue()");
    // 이 메서드는 프로퍼티 에디터를 사용하는 측(예: 프론트 컨트롤러)에서
    // 변환된 값을 꺼낼 때 호출된다.
    // 이 메서드를 오버라이딩 하는 이유는 이 메서드가 호출된 것을
    // 확인하기 위함이다. 원래는 오버라이딩 해야 할 이유가 없다.
    return super.getValue();
  }
}
변환기는 변환만 해서 인스턴스 변수에 넣어놓고
리턴하진 않음

 

요청 핸들러의 아규먼트 - @RequestHeader

@Controller
@RequestMapping("/c04_6")
public class Controller04_6 {

  // 클라이언트의 HTTP 요청 헤더를 받고 싶으면
  // request handler의 아규먼트 앞에 @RequestHeader(헤더명) 애노테이션을 붙여라!


  @GetMapping("h1")
  @ResponseBody
  public void handler1(
      PrintWriter out,
      @RequestHeader("Accept") String accept,
      @RequestHeader("User-Agent") String userAgent) {

    out.printf("Accept=%s\n", accept);
    out.printf("User-Agent=%s\n", userAgent);

    if (userAgent.matches(".*Whale.*")) {
      out.println("Whale");
    } else if (userAgent.matches(".*Edg.*")) {
      out.println("Edge");
    } else if (userAgent.matches(".*Chrome.*")) {
      out.println("chrome");
    } else if (userAgent.matches(".*Safari.*")) {
      out.println("safari");
    } else if (userAgent.matches(".*Firefox.*")) {
      out.println("firefox");
    } else {
      out.println("etc");
    }
  }

--

원래는 url 인코딩 넣고, 디코딩 해서 출력해야함 , 근데 지금 최신장비들은 한글을 다 지원해서 문제없음 

---

 

요청 핸들러의 아규먼트 - @Cookie

@Controller
@RequestMapping("/c04_7")
public class Controller04_7 {

  // 클라이언트가 보낸 쿠키 꺼내기
  // => @CookieValue(쿠키명) 애노테이션을 request handler의 아규먼트 앞에 붙인다.


  @GetMapping("h1")
  @ResponseBody
  public void handler1(
      PrintWriter out,
      HttpServletResponse response
      ) {
    // 이 메서드에서 쿠키를 클라이언트로 보낸다.
    try {
      // 쿠키의 값이 ASCII가 아니라면 URL 인코딩 해야만 데이터가 깨지지 않는다.
      // URL 인코딩을 하지 않으면 ? 문자로 변환된다.
      response.addCookie(new Cookie("name1", "AB가각"));
      response.addCookie(new Cookie("name2", URLEncoder.encode("AB가각", "UTF-8")));
      response.addCookie(new Cookie("name3", "HongKildong"));
      response.addCookie(new Cookie("age", "30"));

    } catch (Exception e) {
      e.printStackTrace();
    }

    out.println("send cookie!");
  }


  @GetMapping(value = "h2", produces = "text/plain;charset=UTF-8")
  @ResponseBody
  public String handler2(
      @CookieValue(value = "name1", required = false) String name1,
      @CookieValue(value = "name2", defaultValue = "") String name2,
      @CookieValue(value = "name3", defaultValue = "홍길동") String name3,
      @CookieValue(value = "age", defaultValue = "0") int age // String ===> int 자동 변환
      ) throws Exception {

    //
    // 1) URLEncoder.encode("AB가각", "UTF-8")
    // ==> JVM 문자열은 UTF-16 바이트 배열이다.
    //     0041 0042 ac00 ac01
    // ==> UTF-8 바이트로 변환한다.
    //     41 42 ea b0 80 ea b0 81
    // ==> 8비트 데이터가 짤리지 않도록 URL 인코딩으로 7비트화 시킨다.
    //     "AB%EA%B0%80%EA%B0%81"
    //     41 42 25 45 41 25 42 30 25 38 30 25 45 41 25 42 30 25 38 31
    // ==> 웹 브라우저에서는 받은 값을 그대로 저장
    //
    // 2) 쿠키를 다시 서버로 보내기
    // ==> 웹 브라우저는 저장된 값을 그대로 전송
    //     "AB%EA%B0%80%EA%B0%81"
    //     41 42 25 45 41 25 42 30 25 38 30 25 45 41 25 42 30 25 38 31
    // ==> 프론트 컨트롤러가 쿠키 값을 꺼낼 때 자동으로 URL 디코딩을 수행한다.
    //     즉 7비트 문자화된 코드를 값을 원래의 8비트 코드로 복원한다.
    //     41 42 ea b0 80 ea b0 81
    // ==> 디코딩 하여 나온 바이트 배열을 UTF-16으로 만든다.
    //     문제는 바이트 배열을 ISO-8859-1로 간주한다는 것이다.
    //     그래서 UTF-16으로 만들 때 무조건 앞에 00 1바이트를 붙인다.
    //     0041 0042 00ea 00b0 0080 00ea 00b0 0081
    //     그래서 한글이 깨진 것이다.
    //
    // 해결책:
    // => UTF-16을 ISO-8859-1 바이트 배열로 변경한다.
    //    41 42 ea b0 80 ea b0 81
    byte[] originBytes = name2.getBytes("ISO-8859-1");

    // => 다시 바이트 배열을 UTF-16으로 바꾼다.
    //    이때 바이트 배열이 UTF-8로 인코딩된 값임을 알려줘야 한다.
    //    0041 0042 ac00 ac01
    String namex = new String(originBytes, "UTF-8");

    return String.format(//
        "name1=%s\n name2=%s\n name2=%s\n name3=%s\n age=%d\n", //
        name1, name2, namex, name3, age);
  }


}

 

---------

요청 핸들러의 아규먼트 - multipart/form-data 형식의 파라미터 값 받기
@Controller
@RequestMapping("/c04_8")
public class Controller04_8 {

  // ServletContext는 메서드의 아규먼트로 받을 수 없다.
  // 의존 객체로 주입 받아야 한다.
  @Autowired
  ServletContext sc;

  // 클라이언트가 멀티파트 형식으로 전송한 데이터를 꺼내기
  // => Servlet API에서 제공하는 Part를 사용하거나
  //    또는 Spring에서 제공하는 MultipartFile 타입의 아규먼트를 선언하면 된다.
  //
  // 주의!
  // => DispatcherServlet을 web.xml을 통해 배치했다면,
  //    <multipart-config/> 태그를 추가해야 한다.
  // => WebApplicationInitializer를 통해 DispatcherServlet을 배치했다면,
  //    App1WebApplicationInitializer 클래스를 참고하라!
  //


  @PostMapping(value = "h1", produces = "text/html;charset=UTF-8")
  @ResponseBody
  public String handler1(
      String name,
      int age,
      Part photo // Servlet API의 객체
      ) throws Exception {

    System.out.println(name);
    System.out.println(age);
    System.out.println(photo);

    String filename = null;
    if (photo.getSize() > 0) {
      filename = UUID.randomUUID().toString();
      String path = sc.getRealPath("/html/app1/" + filename);
      photo.write(path);
    }

    return "<html><head><title>c04_8/h1</title></head><body>" + "<h1>업로드 결과</h1>" + "<p>이름:" + name
        + "</p>" + "<p>나이:" + age + "</p>" +
        // 현재 URL이 다음과 같기 때문에 업로드 이미지의 URL을 이 경로를 기준으로 계산해야 한다.
        // http://localhost:8080/java-spring-webmvc/app1/c04_8/h1
        //
        (filename != null ? "<p><img src='../../html/app1/" + filename + "'></p>" : "")
        + "</body></html>";
  }

  // MultipartFile로 멀티파트 데이터를 받으려면,
  // Spring WebMVC 설정에서 MultipartResolver 객체를 등록해야 한다.


  @PostMapping(value = "h2", produces = "text/html;charset=UTF-8")
  @ResponseBody
  public String handler2(//
      String name, //
      @RequestParam(defaultValue = "0") int age, //
      MultipartFile photo // Spring API의 객체
      ) throws Exception {

    String filename = null;
    if (!photo.isEmpty()) {
      filename = UUID.randomUUID().toString();
      String path = sc.getRealPath("/html/app1/" + filename);
      photo.transferTo(new File(path));
    }

    return "<html><head><title>c04_8/h2</title></head><body>" + "<h1>업로드 결과</h1>" + "<p>이름:" + name
        + "</p>" + "<p>나이:" + age + "</p>" +
        // 현재 URL이 다음과 같기 때문에 업로드 이미지의 URL을 이 경로를 기준으로 계산해야 한다.
        // http://localhost:8080/java-spring-webmvc/app1/c04_8/h2
        //
        (filename != null ? "<p><img src='../../html/app1/" + filename + "'></p>" : "")
        + "</body></html>";
  }



  @PostMapping(value = "h3", produces = "text/html;charset=UTF-8")
  @ResponseBody
  public String handler3(//
      String name, //
      int age, //
      // 같은 이름으로 전송된 여러 개의 파일은 배열로 받으면 된다.
      MultipartFile[] photo //
      ) throws Exception {

    StringWriter out0 = new StringWriter();
    PrintWriter out = new PrintWriter(out0);
    out.println("<html><head><title>c04_8/h3</title></head><body>");
    out.println("<h1>업로드 결과</h1>");
    out.printf("<p>이름:%s</p>\n", name);
    out.printf("<p>나이:%s</p>\n", age);

    for (MultipartFile f : photo) {
      if (!f.isEmpty()) {
        String filename = UUID.randomUUID().toString();
        String path = sc.getRealPath("/html/app1/" + filename);
        f.transferTo(new File(path));
        out.printf("<p><img src='../../html/app1/%s'></p>\n", filename);
      }
    }
    out.println("</body></html>");

    return out0.toString();
  }



  @PostMapping(value = "h4", produces = "text/html;charset=UTF-8")
  @ResponseBody
  public String handler4(
      String name,
      int age,
      // 같은 이름으로 전송된 여러 개의 파일은 배열로 받으면 된다.
      MultipartFile[] photo
      ) throws Exception {

    StringWriter out0 = new StringWriter();
    PrintWriter out = new PrintWriter(out0);
    out.println("<html><head><title>c04_8/h3</title></head><body>");
    out.println("<h1>업로드 결과</h1>");
    out.printf("<p>이름:%s</p>\n", name);
    out.printf("<p>나이:%s</p>\n", age);

    for (MultipartFile f : photo) {
      String filename = UUID.randomUUID().toString();
      String path = sc.getRealPath("/html/app1/" + filename);
      f.transferTo(new File(path));
      out.printf("<p><img src='../../html/app1/%s'></p>\n", filename);
    }
    out.println("</body></html>");

    return out0.toString();
  }
}

파트를 쓰면 서블렛API
multipartfile은 mvc 

----

요청 핸들러의 아규먼트 - @RequestBody : 클라이언트가 보낸 데이터를 한 덩어리로 받기

클라이언트가 보낸 데이터를 통째로 받기
 => request handler의 아규먼트 앞에 @RequestBody를 붙이면 된다.

@Controller 
@RequestMapping("/c04_9")
public class Controller04_9 {

  @PostMapping(value="h1", produces="text/html;charset=UTF-8") 
  @ResponseBody 
  public String handler1(
      String name,
      int age,

      @RequestBody String data
      ) throws Exception {
    
    StringWriter out0 = new StringWriter();
    PrintWriter out = new PrintWriter(out0);
    out.println("<html><head><title>c04_9/h1</title></head><body>");
    out.println("<h1>결과</h1>");
    out.printf("<p>이름:%s</p>\n", name);
    out.printf("<p>나이:%s</p>\n", age);
    out.printf("<p>통데이터:%s</p>\n", data);
    out.println("</body></html>");
    return out0.toString();
  }
}

---car 객체에 담을수도있음

  @PostMapping(value="h2", produces="text/html;charset=UTF-8")
  @ResponseBody
  public String handler2(

          @RequestBody String jsonData
  ) throws Exception {

    StringWriter out0 = new StringWriter();
    PrintWriter out = new PrintWriter(out0);
    out.println("<html><head><title>c04_9/h2</title></head><body>");
    out.println("<h1>결과</h1>");
    out.printf("<p>통데이터:%s</p>\n", jsonData);

    Car car = new Gson().fromJson(jsonData, Car.class);
    out.printf("<p>%s</p>\n", car.toString());

    out.println("</body></html>");
    return out0.toString();
  }

------

// 요청 핸들러의 리턴 값 - 콘텐트를 직접 리턴하기

@Controller
@RequestMapping("/c05_1")
public class Controller05_1 {

 
  @GetMapping("h1")
  @ResponseBody
  public String handler1() {
    // 리턴 값이 클라이언트에게 보내는 콘텐트라면
    // 메서드 선언부에 @ResponseBody를 붙인다.
    // => 붙이지 않으면 프론트 컨트롤러는 view URL로 인식한다.
    // => 출력 콘텐트는 브라우저에서 기본으로 HTML로 간주한다.
    //    단 한글은 ISO-8859-1 문자표에 정의된 코드가 아니기 때문에
    //    클라이언트로 보낼 때 '?' 문자로 바꿔 보낸다.
    return "<html><body><h1>abc가각간</h1></body></html>";
  }

  
  // => 리턴되는 콘텐트의 MIME 타입과 charset을 지정하고 싶다면
  //    애노테이션의 produces 프로퍼티에 설정하라.
  @GetMapping(value = "h2", produces = "text/html;charset=UTF-8")
  @ResponseBody
  public String handler2() {
    return "<html><body><h1>abc가각간<h1></body></html>";
  }


  @GetMapping("h3")
  @ResponseBody
  public String handler3(HttpServletResponse response) {

    // HttpServletResponse에 대해 다음과 같이 콘텐트 타입을 설정해봐야 소용없다.
    response.setContentType("text/html;charset=UTF-8");
// => 셋컨텐트타입은 겟라이터 호출 전에 적용해야되는데 
 // 리스판스바디(프로트컨트롤러)는 미리ㅣ 겟라이터를 호출한 상태이기때문에 먹히지 않음 
   
   return "<html><body><h1>abc가각간<h1></body></html>";
  }


  @GetMapping("h4")
  public HttpEntity<String> handler4(HttpServletResponse response) {
    // HttpEntity 객체에 콘텐트를 담아 리턴할 수 있다.
    // 이 경우에는 리턴 타입으로 콘텐트임을 알 수 있기 때문에
    // @ResponseBody 애노테이션을 붙이지 않아도 된다.

    HttpEntity<String> entity = new HttpEntity<>(
        "<html><body><h1>abc가각간<h1></body></html>");

    // 이 경우에는 출력할 때 ISO-8859-1 문자표의 코드로 변환하여 출력한다.
    // 그래서 한글은 ? 문자로 변환된다.

    return entity;
  }


  @GetMapping(value = "h5", produces = "text/html;charset=UTF-8")
  public HttpEntity<String> handler5(HttpServletResponse response) {
    // 한글을 제대로 출력하고 싶으면 위 애노테이션의 produces 속성에 콘텐트 타입을 지정한다.
    HttpEntity<String> entity = new HttpEntity<>(
        "<html><body><h1>abc가각간<h1></body></html>");

    return entity;
  }


  @GetMapping("h6")
  public HttpEntity<String> handler6(HttpServletResponse response) {
    // 한글을 제대로 출력하고 싶으면,
    // 응답 헤더에 직접 Content-Type을 설정할 수 있다.

    HttpHeaders headers = new HttpHeaders();
    headers.add("Content-Type", "text/html;charset=UTF-8");

    HttpEntity<String> entity = new HttpEntity<>(
        "<html><body><h1>abc가각간<h1></body></html>",
        headers);

    return entity;
  }

httpeneity와 reponseentity의 차이 
http 밑에 response가 있음 (좀 더 전문적인 것임)

  @GetMapping("h7")
  public ResponseEntity<String> handler7(HttpServletResponse response) {
    // HttpEntity 대신에 ResponseEntity 객체를 리턴 할 수 있다.
    // 이 클래스의 경우 응답 상태 코드를 추가하기 편하다.

    HttpHeaders headers = new HttpHeaders();
    headers.add("Content-Type", "text/html;charset=UTF-8");

    // 이렇게 응답 헤더를 따로 설정하는 방법이 존재하는 이유는
    // 다음과 같이 임의의 응답 헤더를 추가하는 경우가 있기 때문이다.
    headers.add("BIT-OK", "ohora");

    ResponseEntity<String> entity = new ResponseEntity<>(
        "<html><body><h1>abc가각간<h1></body></html>",
        headers,
        HttpStatus.OK // 응답 상태 코드를 설정할 수 있다.
        );

    return entity;
  }
}

CDN 서비스

우리나라 사람이 올린 유튜브를 미국에서 보려면 미국사람이 바다 건너 우리나라 광서버 접속해야됨

그래서,,! 밤에 우리나라 스토리지 자체를 미국에 전달해버림. 그 지역에서 꺼내서 봄 

자동복제가 일어남. 스토리지금액이 올라감 

 

 

--

// 요청 핸들러의 리턴 값 - view URL 리턴하기, 리다이렉트, forward/include

@Controller
@RequestMapping("/c05_2")
public class Controller05_2 {

  @GetMapping("h1")
  public String handler1() {
    // 메서드 선언부에 @ResponseBody를 붙이지 않으면
    // 프론트 컨트롤러는 view URL로 간주한다.
    // => 리턴 URL의 '/'는 웹 애플리케이션 루트를 의미한다.
    return "/jsp/c05_2.jsp";
  }
@Controller
@RequestMapping("/c05_2")
public class Controller05_2 {


  @GetMapping("h2")
  public String handler2() {
    // MVC 패턴에서는 항상 Controller에 의해 View가 통제되어야 한다.
    // Controller를 경유하지 않고 View를 실행하게 해서는 안된다.
    // 그래야 View에 대해 일관성 있는 제어가 가능하다.
    // 문제는 jsp 파일을 웹 애플리케이션의 일반 폴더에 두게 되면
    // 다음과 같이 클라이언트에서 직접 실행을 요청할 수 있다.
    // http://localhost:9999/eomcs-spring-webmvc/jsp/c05_2.jsp
    //
    // 이것을 막으려면, 다음과 같이 WEB-INF 폴더 밑에 JSP 파일을 두어라.
    // /WEB-INF 폴더에 있는 파일은 클라이언트에서 직접 실행을 요청할 수 없다.
    return "/WEB-INF/jsp/c05_2.jsp";
  }


  @GetMapping("h3")
  public View handler3() {
    return new JstlView("/WEB-INF/jsp/c05_2.jsp");
  }


  @GetMapping("h4")
  public ModelAndView handler4() {
    System.out.println("===> /app1/c05_2/h4");
    ModelAndView mv = new ModelAndView();
    mv.setViewName("/WEB-INF/jsp/c05_2.jsp");
    mv.addObject("name", "홍길동");// 프론트 컨트롤러가 ServeltRequest 보관소로 옮긴다.
    mv.addObject("age",20); // 프론트 컨트롤러가 ServeltRequest 보관소로 옮긴다.
    return mv;


  @GetMapping("h5")
  public String handler5() {
    // 리다이렉트를 지정할 때는 URL 앞에 "redirect:" 접두어를 붙인다.
    // 즉 HTTP 응답이 다음과 같다.
    // HTTP/1.1 302
    // Location: h4
    // Content-Language: ko-KR
    // Content-Length: 0
    // Date: Fri, 19 Apr 2019 07:57:00 GMT

    return "redirect:h4";
  }


  @GetMapping("h6")
  public String handler6() {
    // 포워드를 지정할 때는 URL 앞에 "forward:" 접두어를 붙인다.
    // 인클루드를 지정할 때는 URL 앞에 "include:" 접두어를 붙인다.
    return "forward:h4";
  }
}

 

요청 핸들러에서 view 컴포넌트(JSP) 쪽에 데이터 전달하기

// 요청 핸들러에서 view 컴포넌트(JSP) 쪽에 데이터 전달하기

@Controller
@RequestMapping("/c05_3")
public class Controller05_3 {


  @GetMapping("h1")
  public String handler1(
      ServletRequest request) {

    // JSP가 꺼내 쓸 수 있도록 ServletRequest 객체에 직접 담는다.
    request.setAttribute("name", "홍길동");
    request.setAttribute("age", 20); // auto-boxing: int ===> Integer 객체
    request.setAttribute("working", true); // auto-boxing: boolean ===> Boolean 객체

    return "/WEB-INF/jsp/c05_3.jsp";
  }


  @GetMapping("h2")
  public String handler2(Map<String,Object> map) {

    // 아규먼트에 Map 타입의 변수를 선언하면
    // 프론트 컨트롤러는 빈 맵 객체를 만들어 넘겨준다.
    // 이 맵 객체의 용도는 JSP에 전달할 값을 담는 용이다.
    // 맵 객체에 값을 담아 놓으면 프론트 컨트롤러가 JSP를 실행하기 전에
    // ServletRequest로 복사한다.
    // 따라서 ServletRequest에 값을 담는 것과 같다.
    //
    map.put("name", "홍길동");
    map.put("age", 20); // auto-boxing
    map.put("working", true); // auto-boxing

    return "/WEB-INF/jsp/c05_3.jsp";
  }

***많이 쓰는 방법 
  @GetMapping("h3")
  public String handler3(Model model) {

    // 아규먼트에 Model 타입의 변수를 선언하면
    // 프론트 컨트롤러는 모델 객체를 만들어 넘겨준다.
    // 이 객체의 용도는 Map 객체와 같다.
    //
    model.addAttribute("name", "홍길동");
    model.addAttribute("age", 20); // auto-boxing
    model.addAttribute("working", true); // auto-boxing

    return "/WEB-INF/jsp/c05_3.jsp";
  }


  @GetMapping("h4")
  public ModelAndView handler4() {

    // request handler에서 ModelAndView 객체를 만들어 리턴한다.
    // => 이 객체의 용도는 Model과 view URL을 함께 리턴하는 것이다.
    //
    ModelAndView mv = new ModelAndView();

    // JSP가 사용할 데이터를 담고
    mv.addObject("name", "홍길동");
    mv.addObject("age", 20); // auto-boxing
    mv.addObject("working", true); // auto-boxing

    // JSP 주소도 담는다.
    mv.setViewName("/WEB-INF/jsp/c05_3.jsp");

    return mv;
  }
}

-----------------

뷰:

 뷰(View)란 무엇인가? 

1. 뷰는 사용자에게 접근이 허용된 자료만을 제한적으로 보여주기 위해 하나 이상의 기본 테이블로부터 유도된, 이름을 가지는 가상 테이블이다.

2. 뷰는 저장장치 내에 물리적으로 존재하지 않지만 사용자에게 있는 것처럼 간주된다.

3. 뷰는 데이터 보정작업, 처리과정 시험 등 임시적인 작업을 위한 용도로 활용된다.

4. 뷰는 조인문의 사용 최소화로 사용상의 편의성을 최대화 한다.

 

 

뷰리졸버란..? : 스프링 백엔드에서 데이터를 처리하거나 가지고 왔다면, 이 데이터를 View의 영역으로 전달을 해야 한다. 이때 View를 어떤 것을 사용할지 자유롭게 설정을 할 수 있는데 이 설정 역할을 하는 것이 View Resolver라고 생각하면 된다

뷰리졸버: 종류가 많음 ;

리턴타입: view url

 

리턴타입: url을 가리키는 view 이름

url내용 을 바꾸더라도 이름을 넘긴거라 페이지컨트롤러는 바꿀 필요가 없음 

 

 

기본 View Resolver 사용하기

@Controller
@RequestMapping("/c01_1")
public class Controller01_1 {


  @GetMapping("h1")
  // @ResponseBody // 뷰 이름을 리턴 할 때는 이 애노테이션을 붙이면 안된다.
  public String handler1(Model model) {

    model.addAttribute("name", "홍길동");
    model.addAttribute("age", 20);

    return "/jsp/c01_1.jsp";
    // 기본 ViewResolver는 리턴 값으로 URL을 받아
    // 웹 애플리케이션 디렉토리에서 JSP를 찾는다.
    // 웹 애프리케이션이 경로가 /eomcs-spring-webmvc 라면,
    // JSP 경로는 다음과 같다.
    // ==> /eomcs-spring-webmvc/jsp/c01_1.jsp
    //
    // InternalResourceViewResolver로 교체한 다음의 JSP URL은?
    // => /WEB-INF/jsp2//jsp/c01_1.jsp.jsp
  }


  @GetMapping("h2")
  public void handler2(Model model) {
    model.addAttribute("name", "홍길동2");
    model.addAttribute("age", 30);

    // 기본 ViewRosolver를 사용할 때는
    // 뷰 이름을 리턴하지 않으면 오류 발생!
    //
    // InternalResourceViewResolver로 교체한 다음은?
    // => 리턴 값이 없으면 요청 URL(/c01_1/h2)을 리턴 값으로 사용한다.
    // => 따라서 ViewResolver가 계산한 최종 URL은
    // /WEB-INF/jsp2/c01_1/h2.jsp
    //

  }


  @GetMapping("h3")
  public String handler3(Map<String, Object> map) {

    map.put("name", "홍길동3");
    map.put("age", 40);

    return "/WEB-INF/jsp/c01_1.jsp";
    // MVC 모델에서는 JSP는 뷰 콤포넌트로서 출력이라는 역할을 담당한다.
    // 출력할 데이터를 준비하는 일은 페이지 컨트롤러가 담당한다.
    // 그래서 JSP를 실행할 때는 항상 페이지 컨트롤러를 통해 실행해야 한다.
    // 페이지 컨트롤러가 하는 일이 없어도 프로그래밍의 일관성을 위해
    // 직접 JSP을 요청하지 않고, 페이지 컨트롤러를 통해 요청해야 한다.
    //
    // 그런데 웹 디렉토리에 JSP를 두면 클라이언트에서 JSP를 직접 요청할 수 있다.
    // 페이지 컨트롤러를 경유하지 않은 상태에서 실행해봐야 소용없지만,
    // 그래도 요청은 할 수 있다.
    // 이런 의미 없는 요청을 막는 방법으로,
    // JSP 파일을 /WEB-INF 폴더 아래에 두는 것을 권장한다.
    //
    // 웹 브라우저에서 다음 URL의 JSP를 요청해보라!
    // 1) http://localhost:8888/bitcamp-java-spring-webmvc/jsp/c01_1.jsp
    // => 클라이언트가 요청할 수 있다.
    // 2) http://localhost:8888/bitcamp-java-spring-webmvc/WEB-INF/jsp/c01_1.jsp
    // => 클라이언트가 요청할 수 없다.
    // => /WEB-INF 폴더에 있는 자원들은 클라이언트에서 직접 요청할 수 없다.
    // => 그래서 잘못된 요청을 막을 수 있다.
    // 실무에서는 이 방법을 사용한다.
  }

}

페이지 컨트롤러로 접두사 접미사를 뺀 url만 리턴한다.
등록하는 법

URL 에서 값 추출하기 - @PathVariable

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

  // 테스트:  http://localhost:9999/eomcs-spring-webmvc/app2/c02_1?name=kim&age=20
  @GetMapping
  @ResponseBody
  public String handler1(String name, int age) {
    // 클라이언트로부터 값을 받는 일반적인 방법
    // => Query String 으로 받는다.
    // => 즉 URL 다음에 "?변수=값&변수=값" 형태로 값을 받는다.
    // => Query String의 값을 request handler에서 받으려면
    //    아규먼트를 선언하면 된다.
    //    아규먼트 앞에 @RequestParam을 붙여도 되고
    //    아규먼트이 이름이 요청 파라미터의 이름과 같다면 @RequestParam을 생략해도 된다.
    return String.format("name=%s, age=%d", name, age);
  }

  // 테스트: http://localhost:9999/eomcs-spring-webmvc/app2/c02_1/kim/20
  @GetMapping("{name}/{age}")
  @ResponseBody
  public String handler2(
      /*
      @PathVariable("name") String name,
      @PathVariable("age") int age
       */
      // URL의 변수 이름을 생략하면 아규먼트 이름을 사용한다.
      @PathVariable String name,
      @PathVariable int age
      ) {
    // URL path에 값을 포함하여 전달할 수 있고, 그 값을 아규먼트로 받을 수 있다.
    // URL path에 포함된 값을 받으려면 request handler의 URL을 설정할 때
    // 다음의 문법으로 선언해야 한다.
    // => .../{변수명}/{변수명}
    // 이렇게 선언된 변수 값을 받으려면 다음과 같이 아규먼트를 선언해야 한다.
    // => @PathVariable(변수명) String 아규먼트
    // 변수명과 아규먼트의 이름이 같다면, 다음과 같이 변수명을 생략할 수 있다.
    // => @PathVariable String 아규먼트
    //
    return String.format("name=%s, age=%d", name, age);
  }

  // 테스트:  http://localhost:9999/eomcs-spring-webmvc/app2/c02_1/kim_20
  @GetMapping("{name}_{age}")
  @ResponseBody
  public String handler3(
      @PathVariable String name,
      @PathVariable int age
      ) {
    return String.format("name=%s, age=%d", name, age);
  }

}

URL 에서 값 추출하기 - @MatrixVariable

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

  // http://.../app2/c02_2?name=kim&age=20
  @GetMapping
  @ResponseBody
  public String handler1(String name, int age) {
    // Query String 으로 값 받기
    return String.format("name=%s, age=%d", name, age);
  }



** 세미콜론 나오면 매트릭스 임 . 
메트릭스는 기본으로 처리할 수 없기 때문에 enable MVc 시키고, configurePathMatche를 오버라이딩 해야한다. 
  @GetMapping(value = "{value}", produces = "text/plain;charset=UTF-8")
  @ResponseBody
  public String handler2(//
      @PathVariable("value") String value,
      // value 값 중에서 name 항목의 값을 받고 싶을 때 @MatrixVariable 을 사용한다.
      // 단 value의 형식은 "이름=값;이름=값;이름=값" 형태여야 한다.
      // @MatrixVariable("name") String name,
      // @MatrixVariable("age") int age

      // 매트릭스 변수명을 생략하면 아규먼트의 이름을 사용한다.
      @MatrixVariable String name,
      @MatrixVariable int age
      ) {

    // @MatrixVariable 애노테이션을 사용하려면
    // IoC 컨테이너에서 이 애노테이션을 활성화시키는 설정을 추가해야 한다.
    // 1) XML 설정
    //    => <mvc:annotation-driven enable-matrix-variables="true"/>
    // 2) Java Config 설정
    //    => @EnableWebMvc 애노테이션을 활성화시킨다.
    //    => WebMvcConfigurer 구현체를 정의한다.
    //    => UrlPathHelper 객체의 removeSemicolonContent 프로퍼티 값을 false로 설정한다.

    // 테스트1
    // http://.../app2/c02_2/name=kim;age=20
    // => @PathVariable("value") : name=kim <== 첫 번째 세미콜론의 값만 가져온다.
    // => @MatrixVariable("name") : kim
    // => @MatrixVariable("age") : 20
    //
    // 테스트2
    // http://.../app2/c02_2/user;name=kim;age=20
    // => @PathVariable("value") : user <== 첫 번째 세미콜론의 값만 가져온다.
    // => @MatrixVariable("name") : kim
    // => @MatrixVariable("age") : 20
    return String.format("value:%s \n name:%s, age:%d", value, name, age);
  }

  // 테스트:
  // http://.../app2/c02_2/name=teamA;qty=5/title=work1;state=1
  @GetMapping(value = "{team}/{task}", produces = "text/plain;charset=UTF-8")
  @ResponseBody
  public String handler3(
      // 여러 개의 패스 변수가 있을 때 값을 꺼내는 방법
      @MatrixVariable String name,
      @MatrixVariable int qty,
      @MatrixVariable String title,
      @MatrixVariable int state
      ) {

    return String.format("team: %s(%d)\n task: %s, %d", name, qty, title, state);
  }

  // http://.../app2/c02_2/h4/name=teamA;qty=5/name=work1;qty=1
  @GetMapping(value = "h4/{team}/{task}", produces = "text/plain;charset=UTF-8")
  @ResponseBody
  public String handler4(
      // 여러 개의 패스 변수가 있을 때 값을 꺼내는 방법
      // => 만약 항목의 이름이 같다면?
      @MatrixVariable(name = "name", pathVar = "team") String name1,
      @MatrixVariable(name = "qty", pathVar = "team") int qty1,
      @MatrixVariable(name = "name", pathVar = "task") String name2,
      @MatrixVariable(name = "qty", pathVar = "task") int qty2) {

    return String.format("team: %s(%d)\n task: %s, %d", name1, qty1, name2, qty2);
  }
}

매트릭스 추가 설정

// URL 에서 값 추출하기 - 정규표현식으로 URL 다루기 

@Controller 
@RequestMapping("/c02_3")
public class Controller02_3 {


  @GetMapping(
      value="h1/{name}/{tel}/{gender}", 
      produces="text/plain;charset=UTF-8")
  @ResponseBody
  public String handler1(
      @PathVariable String name, 
      @PathVariable String tel,
      @PathVariable String gender) {
    
    return String.format("name: %s\n tel: %s \n gender: %s", 
        name, tel, gender);
  }
  

  @GetMapping(
      value="h2/{name:[a-zA-Z0-9]+}/{tel:[0-9]+-[0-9]+-[0-9]+}/{gender:man|woman}", 
      produces="text/plain;charset=UTF-8")
  @ResponseBody
  public String handler2(
      @PathVariable String name, 
      @PathVariable String tel,
      @PathVariable String gender) {
    
    // 패스 변수를 사용할 때,
    // 패스 변수의 값 규칙을 정규표현식으로 정의할 수 있다.
    // 정규 표현식에 어긋간 URL의 경우 예외가 발생할 것이다.
    //
    return String.format("name: %s\n tel: %s \n gender: %s", 
        name, tel, gender);
  }

}