Servlet

이동규·2023년 4월 17일
  • 자바를 기반으로 웹 애플리케이션을 개발하고, 동적인 웹 콘텐츠를 생성하며, 클라이언트의 요청을 처리하는 중요한 컴포넌트

여기서 잠깐!

HTTP, API, URL, MVC 를 짚고 넘어가자

HTTP

  • HTTP를 알기 앞서 알아야 하는 것
    • IP 주소 : 사용의 요청이 서버에 도달하기 위해 필요한 주소
    • DNS : IP주소에 별칭을 준 것
  • DNS 속 HTTP → 사용자의 요청을 받고 요청 받은 정보를 전달하기 위한 통신
  • HTTP 메서드
    • GET
    • POST
    • PUT
    • DELETE
  • HTTP 상태 코드
    • 1XX → Informational
    • 2XX → Seccessful
    • 3XX → Redirection
    • 4XX → Client Error
    • 5XX → Server Error

API

  • Client 와 Server 가 HTTP 를 통해 서로 통신을 하며 기능을 동작 시키는데 이때 서로 간의 정해진 통신 규칙

MVC

→ Model, View, Controller 디자인 패턴

  • Model : 데이터와 비즈니스 로직을 담당 → 데이터베이스와 연동하여 데이터를 저장하고 불러오는 등의 작업을 수행
  • View : 사용자 인터페이스를 담당 → 사용자가 보는 화면과 버튼, 폼 등을 디자인하고 구현
  • Controller : Model과 View 사이의 상호작용을 조정하고 제어 → 사용자의 입력을 받아 Model에 전달하고, Model의 결과를 바탕으로 View를 업데이트
  • Client에게 HTML 파일을 내려주는 상황에서
    • 정적 웹 페이지를 주는 경우
      1. Client의 요청을 Model(개인 정보들)로 받아서 처리
      2. View(정적 웹 페이지, HTML)를 내려줌
    • 동적 웹 페이지를 주는 경우
      1. Controller에서 Client의 요청을 Model(개인 정보들)로 받아서 처리
      2. Template engine에게 View, Model 전달
      3. Template engine은 View에 Model을 적용 → 동적 웹 페이지 생성
        • Template engine의 종류 : ThymeLeaf, Groovy, FreeMarket 등등

Spring MVC

  • Servlet

    • 클라이언트로부터 요청을 받아서 처리하고, 응답을 생성하여 클라이언트에게 전송 → HTTP 요청과 응답에 대한 처리를 담당
    • 웹 애플리케이션에서 동적인 컨텐츠를 생성하거나, 데이터베이스와 연동하여 데이터를 처리하는 등의 작업을 수행
    • 일반적으로 웹 애플리케이션 서버에서 실행되며, 템플릿 엔진과 함께 웹 애플리케이션을 개발하는 경우가 많음
  • Tomcat

    • Servlet 컨테이너 → Servlet을 실행하고 관리하기 위한 환경을 제공
      • Servlet 컨테이너는 클라이언트의 요청을 받아서 적절한 Servlet을 실행하고, Servlet에서 생성된 응답을 클라이언트에게 전송
    • HTTP 요청을 처리하기 위해 HTTP 서버와 연결된 방식으로 동작
    • 동작 방식
      • Client 요청 → Tomcat [HTTP 서버에서 요청을 받음] → Tomcat[요청을 처리할 Servlet을 찾음] → Servlet[요청 처리] → Tomcat[생성한 응답을 HTTP로 전송] → HTTP 서버[응답을 Client 전송]
    • Servlet을 실행하기 위해 JVM을 사용
    • Servlet을 실행할 때, JVM의 인스턴스를 생성하고 Servlet을 로드
    • JVM은 Servlet 코드를 실행하고, Servlet이 요청을 처리하고 생성한 응답을 Tomcat에게 반환
  • Servlet & MVC

    • Servlet을 이용하면 Controller 역할을 수행할 수 있음 → Servlet은 클라이언트의 요청을 처리하고, 요청에 따른 적절한 데이터를 Model에서 가져와서 View에게 전달하는 역할을 함
    • MVC 패턴을 이용하면 Servlet을 이용하여 Controller 역할을 수행하고, 템플릿 엔진을 이용하여 View 역할을 수행할 수 있음

Servlet을 통해 Spring MVC를 구현해보자!

  • 예시 코드(수업)[url : /course/body] →

    • `@WebServlet(name = "courseServletBody", urlPatterns = "/course/body")`

      • 서블릿을 등록하기 위한 어노테이션
      • 위 코드 해석 : courseServletBody이라는 서블릿의 이름과 /course/body라는 URL 패턴을 매핑
        • 아래 코드를 확인해 보면 service 메서드가 있는데 이를 통해 위의 url 패턴으로 요청이 들어올 경우에 호출되며, HttpServlet 클래스를 상속받아 service() 메서드를 재정의하여 요청에 대한 처리 로직을 구현
    • 코드

      @WebServlet(name = "courseServletBody", urlPatterns = "/course/body")
      public class CourseServletBody extends HttpServlet {
      
          @Override
          protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
    • service 메서드 : HttpServlet의 기본 메서드

      • 클라이언트의 요청에 따라 doGet(), doPost(), doPut(), doDelete() 등의 메서드를 자동으로 호출하여 요청을 처리
      • 위 코드에서는 요청 메서드에 상관없이 service() 메서드가 호출되어 요청에 대한 처리를 수행
    • HttpServletRequest와 HttpServletResponse 객체는 Java Servlet 스펙에 정의되어 있는 인터페이스

      • HttpServletRequest : Client에서 서버로의 HTTP 요청에 대한 정보를 담고 있는 객체 → 클라이언트가 서버로 전송한 데이터, URL, HTTP 메소드, 요청 헤더, 쿠키, 세션 등의 정보 접근 → 사용자의 입력을 받아 처리할 수 있는 기능 제공
      • HttpServletResponse : 서버에서 Client로의 HTTP 응답에 대한 정보를 담고 있는 객체 → 서버에서 클라이언트로의 HTTP 응답에 대한 정보를 담고 있는 객체를 전송 → 동적인 웹 페이지를 생성하고 전송하는 기능 제공
    • 코드

      ServletInputStream inputStream = req.getInputStream();
      // HTTP Body에서 가져온 데이터를 바이트코드로 변환
      String body = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
      // 바이트코드를 String 으로 변환
    • req.getInputStream() : HttpServletRequest 객체에서 클라이언트로부터 전송된 HTTP 요청 본문의 데이터를 읽어오기 위한 메서드

      • 이 메소드를 사용하여 요청 본문에 포함된 데이터를 서블릿에서 처리할 수 있음
    • ServletInputStream : Servlet 환경에서 클라이언트로부터 전송된 요청 본문의 이진 데이터를 읽기 위한 메서드를 제공

    • 즉, HttpServletRequest req를 통해 Client에서 받아온 정보를 req.getInputStream()을 읽어오는 메서드

    • StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8): Spring Framework에서 제공하는 유틸리티 클래스인 StreamUtils를 사용하여 ServletInputStream에서 읽어온 데이터를 문자열로 변환하는 코드

      StandardCharsets.UTF_8: 문자열의 인코딩 방식을 지정하는 상수

    • 코드

      // JSON To Object
      Course course = new ObjectMapper().readValue(body, Course.class);
      System.out.println("course.getTitle() = " + course.getTitle());
      System.out.println("course.getInstructor() = " + course.getInstructor());
      System.out.println("course.getCost() = " + course.getCost());
      
      // 가격 수정
      course.setCost(9999999);
    • new ObjectMapper().readValue(body, Course.class)
      : Jackson 라이브러리를 사용하여 요청 본문의 문자열 데이터를 Java 객체로 변환하는 코드
      - ObjectMapper
      : Jackson 라이브러리에서 제공하는 JSON 데이터를 Java 객체로 변환하거나, Java 객체를 JSON 데이터로 변환하기 위한 클래스
      - readValue(): 메소드는 JSON 문자열을 Java 객체로 변환하는 메소드

          → 즉, 첫 번째 인자로는 JSON 문자열 데이터가 들어있는 문자열(**`body`**변수)을 전달하고, 두 번째 인자로는 변환하고자 하는 Java 객체의 클래스(**`Course.class`**변수)를 전달
          
          (Course.class는 뒤에 나온다)
          
      - 결론 : **`body`**에 들어있는 JSON 문자열 데이터가 **`Course`**클래스의 객체로 변환되어 **`course`**변수에 할당
      - 그렇게 위 코드를 보면 Course 클래스의 필드 값을 호출하거나 변경하고 있다.
    • 코드

      // 요청에 대한 결과 반환
      res.setContentType("application/json");
      res.setCharacterEncoding("utf-8");
      res.setStatus(HttpStatus.CREATED.value());
    • res.setContentType("application/json"): HTTP 응답의 Content-Type 헤더를 설정

      application/json: JSON 형식의 데이터를 의미하며, 이를 설정함으로써 클라이언트에게 전송되는 데이터가 JSON 형식임을 명시

    • res.setCharacterEncoding("utf-8"): HTTP 응답의 문자 인코딩을 설정하는 부분

    • res.setStatus(HttpStatus.CREATED.value()): HTTP 응답의 상태 코드를 설정하는 부분

      HttpStatus.CREATED: HTTP 상태 코드 201(Created)을 의미하며, 이를 설정함으로써 클라이언트에게 요청이 성공적으로 처리되어 새로운 리소스가 생성되었음을 알림

      value()메소드는 HttpStatus.CREATED상태 코드의 정수값인 201을 반환

    • 위 코드를 사용해서 HTTP 응답의 Content-Type, 문자 인코딩, 상태 코드를 설정하여 클라이언트에게 적절한 응답을 반환할 수 있음

    • 코드

      // Object To JSON
      res.getWriter().write(new ObjectMapper().writeValueAsString(course));
    • new ObjectMapper().writeValueAsString(course): course객체를 Jackson 라이브러리를 사용하여 JSON 형식의 문자열로 변환하는 부분

      → 위에서 설명한 readValue()와 반대로 writeValueAsString()메소드는 Java 객체를 JSON 형식의 문자열로 변환하여 반환

    • res.getWriter().write(...): 이 코드는 HTTP 응답의 body에 JSON 형식의 문자열을 작성하는 부분

      res.getWriter()

      : ServletResponse인터페이스에서 제공하는 메소드로, HTTP 응답의 출력 스트림을 반환

      write(...)메소드는 문자열을 출력 스트림에 작성하여 클라이언트로 전송

    • 즉, 위 코드는 course객체를 JSON 형식의 문자열로 변환하여 HTTP 응답의 body에 작성하게 된다. → 클라이언트는 이 JSON 형식의 문자열을 받아 응답 데이터를 처리할 수 있다.

    • 위 코드들을 모두 합쳐보면 HttpServletRequest 객체를 통해 HTTP Body에 있는 데이터를 읽어와서 String 형태로 변환하고, 이를 Jackson 라이브러리를 사용하여 Course 클래스 객체로 변환 그리고 Course 객체의 내용을 수정하고, HttpServletResponse 객체를 통해 JSON 형태로 변환하여 클라이언트로 응답을 보내는 로직이 구현!!

    • 코드

      private void printHttpInfo(HttpServletRequest req) {
              System.out.println("req.getRequestURI() = " + req.getRequestURI());
              System.out.println("req.getRequestURL() = " + req.getRequestURL());
              System.out.println("req.getServerPort() = " + req.getServerPort());
              System.out.println("req.getServletPath() = " + req.getServletPath());
              System.out.println("req.getMethod() = " + req.getMethod());
      }
    • req에는 위에 수행했던 코드들을 통해 변경된 내용이 req에 들어가게 된다.

    • printHttpInfo()메소드 : req객체를 사용하여 HTTP 요청의 다양한 정보를 출력

  • 예시 코드(수업)[url : /course] →

    • courseServletBody 코드와 비슷하지만 courseServlet 코드는 단순히 req 정보들을 받아 확인 후 res를 통해 Client에게 전달해보는 코드이다.

    • 코드

      // 데이터 확인
      String title = req.getParameter("title");
      String instructor = req.getParameter("instructor");
      double cost = Double.parseDouble(req.getParameter("cost"));
    • req.getParameter(): 클라이언트가 전송한 HTTP 요청의 파라미터 값을 문자열 형태로 반환

      → 예를 들어 url 이 아래와 같다고 치면,

      http://example.com/course?title=Java&instructor=John&cost=99.99

      title은 Java, instructor은 John, cost는 문자열을 double형으로 변환하여 99.99 로 출력

    • 코드

      // Object To JSON
      Course course = new Course(title, instructor, cost);
      res.getWriter().write(new ObjectMapper().writeValueAsString(course));
    • Course객체를 생성한 후, 해당 객체를 JSON 형식의 문자열로 변환하여 HTTP 응답에 작성하는 부분

    • Course클래스의 객체인 coursetitle, instructor, cost라는 세 개의 파라미터를 가지고 있는 Course클래스의 생성자를 호출하여 객체를 생성 → 생성자 호출!!!

      course는 각각의 필드에 전달된 값을 갖게 됨

    • 다음은 앞서 설명한 것과 같이 res에 course객체를 JSON 형식의 문자열로 변환

    • 마지막 코드는 JSON 형태의 문자열로 변환하는 과정으로 앞에 설명한 것과 같다.

    • CourseServlet 결론

      • courseServlet클래스의 service()메소드에서 req.getParameter()메소드를 사용하여 요청(request)의 파라미터 값을 추출 후 Course클래스의 생성자에 title, instructor
        , cost값을 전달하여 새로운 Course객체를 생성 이 사이에 Object To Json 과정은 클라이언트에게 전송되는 응답의 형식과 문자 인코딩을 지정하는 것으로, 클라이언트가 서버에서 받는 응답의 형태와 인코딩을 제대로 인식할 수 있도록 도와줌
    • CourseServletBody 결론

      • courseServlet클래스를 통해 course 객체의 필드 값이 들어갔다면, courseServletbody클래스를 통해 필드 값 변경을 해보는 것이다.
  • 전체 코드

    • CourseServlet

      @WebServlet(name = "courseServlet", urlPatterns = "/course")
      public class CourseServlet extends HttpServlet {
          @Override
          protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
              //printHttpInfo(req);
      
              // 데이터 확인
              String title = req.getParameter("title");
              String instructor = req.getParameter("instructor");
              double cost = Double.parseDouble(req.getParameter("cost"));
      
              // 요청에 대한 결과 반환
              res.setContentType("application/json");
              res.setCharacterEncoding("utf-8");
      
              // Object To JSON
              Course course = new Course(title, instructor, cost);
              res.getWriter().write(new ObjectMapper().writeValueAsString(course));
      
          }
      
          private void printHttpInfo(HttpServletRequest req) {
              System.out.println("req.getRequestURI() = " + req.getRequestURI());
              System.out.println("req.getRequestURL() = " + req.getRequestURL());
              System.out.println("req.getServerPort() = " + req.getServerPort());
              System.out.println("req.getServletPath() = " + req.getServletPath());
              System.out.println("req.getMethod() = " + req.getMethod());
          }
      }
    • CourseServletBody

      @WebServlet(name = "courseServletBody", urlPatterns = "/course/body")
      public class CourseServletBody extends HttpServlet {
      
          @Override
          protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
              // printHttpInfo(req);
      
              // HTTP Body 정보 가져오기
              ServletInputStream inputStream = req.getInputStream(); // HTTP Body에서 가져온 데이터를 바이트코드로 변환
              String body = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8); // 바이트코드를 String 으로 변환
              System.out.println("body = " + body);
      
              // JSON To Object
              Course course = new ObjectMapper().readValue(body, Course.class);
              System.out.println("course.getTitle() = " + course.getTitle());
              System.out.println("course.getInstructor() = " + course.getInstructor());
              System.out.println("course.getCost() = " + course.getCost());
      
              // 가격 수정
              course.setCost(9999999);
      
              // 요청에 대한 결과 반환
              res.setContentType("application/json");
              res.setCharacterEncoding("utf-8");
              res.setStatus(HttpStatus.CREATED.value());
      
             // Object To JSON
              res.getWriter().write(new ObjectMapper().writeValueAsString(course));
          }
      
          private void printHttpInfo(HttpServletRequest req) {
              System.out.println("req.getRequestURI() = " + req.getRequestURI());
              System.out.println("req.getRequestURL() = " + req.getRequestURL());
              System.out.println("req.getServerPort() = " + req.getServerPort());
              System.out.println("req.getServletPath() = " + req.getServletPath());
              System.out.println("req.getMethod() = " + req.getMethod());
          }
      }
  • Controller
    • 코드

      @RestController
      @RequestMapping("/controller/course")
      public class CourseController {
      
          @PostMapping("/body")
          public ResponseEntity<?> courseBody(@RequestBody Course course) {
              System.out.println("course.toString() = " + course.toString());
              course.setCost(123456);
              return ResponseEntity.ok(course);
          }
    • 위의 CourseServlet과 겹치지 않게 /controller/course경로로 들어오는 요청들은 CourseController클래스의 메소드들 중에서 해당 경로와 매핑되는 메소드로 처리

    • ResponseEntity<?>: 스프링 프레임워크에서 사용되는 HTTP 응답을 나타내는 클래스

      <?>부분은 모든 타입을 나타내며, 원하는 타입으로 대체

      ResponseEntity<?>를 사용하면 컨트롤러에서 HTTP 응답을 더욱 세밀하게 제어 가능

    • @RequestBody: 이 어노테이션은 POST 요청에서의 Request Body에 포함된 데이터를 객체로 변환하기 위해 사용

    • POST 요청의 Request Body에 포함된 데이터를 Course객체로 변환하여 처리하고, 이를 ResponseEntity에 담아 응답으로 반환

    • ok(): HTTP 상태 코드를 200 OK로 설정하고, 본문을 설정하지 않음

      • 그 외
        • notFound(): HTTP 상태 코드를 404 Not Found로 설정하고, 본문을 설정 X
        • badRequest(): HTTP 상태 코드를 400 Bad Request로 설정하고, 본문을 설정 X
    • “cost” = 99000 값을 넣어주었다면 setCost(12345)메소드를 호출 12345로 변경이 된다.

      • 하지만, 이는 메모리 상에서만 해당 객체의 cost값을 변경한 것이고, HTTP 요청의 바디에 있는 데이터와는 무관하다.

      • 만약, @RequestBody로 받아온 객체를 변경하여 HTTP 요청의 바디에 있는 데이터를 변경하고자 할 경우, 아래의 코드로 입력하면 된다.

        Course updatedCourse = new Course(course.getTitle(), course.getInstructor(), 123456)
    • 코드

      @GetMapping("/query")
          public ResponseEntity<?> courseQuery(@PathParam("title") String title, @PathParam("instructor") String instructor, @PathParam("cost") double cost) {
              System.out.println("title = " + title);
              System.out.println("instructor = " + instructor);
              System.out.println("cost = " + cost);
              return ResponseEntity.ok(new Course(title, instructor, cost));
          }
    • @PathParam: URL 경로(Path)에 포함된 데이터를 파라미터로 받아오기 위해 사용

    • 위의 코드는 입력받은 파라미터를 출력해 보고 Course 클래스에 해당 인자를 받는 로직

    • 질문!!! → @PathParam 은 SpringMVC에서는 사용 안됨??Java API for RESTful Web Services 에서 사용됨(@PathVariable을 사용해야 함 → url : "/query/{title}/{instructor}/{cost}”) 근데 Controller는 Spring MVC???!!!!

    • 코드

      @GetMapping("/model-attribute")
          public ResponseEntity<?> courseModelAttribute (@ModelAttribute Course course) {
              System.out.println("course.toString() = " + course.toString());
              return ResponseEntity.ok(course);
          }
    • @ModelAttribute: 요청 파라미터들을 객체로 변환하기 위해 사용

      /model-attribute엔드포인트로 들어오는 HTTP GET 요청의 요청 파라미터들이 Course
      객체로 변환되어 처리됨 (이것도 질문!!!)

    • 여기서 “/query”를 이용할 경우

      http://localhost:8080/query?title={title}&instructor={instructor}&cost={cost}
    • 위의 url로 명시한 후 사용을 한다.

    • “model-attribute”를 이용할 경우

      http://localhost:8080/model-attribute?title={title}&instructor={instructor}&cost={cost}
    • 위의 url을 사용하는데 query와 model-attribute의 url 명시가 비슷해 보인다.

      • 하지만, 두개의 차이점은 → @GetMapping("/query") 핸들러 메소드는 @PathParam 어노테이션을 사용하여 URL 경로에서 추출한 경로 변수를 매개변수로 받아온다. → @GetMapping("/model-attribute") 핸들러 메소드는 @ModelAttribute 어노테이션을 사용하여 HTTP 요청 파라미터를 객체로 바인딩하여 매개변수로 받아온다.
        • 즉, 경로 변수인가, 객체를 바인딩한 것인가의 차이이다.

여기서 ResponseEntity.ok(course); 와 ResponseEntity.ok(new Course(title, instructor, cost));의 차이

  • ResponseEntity.ok(course): 이미 생성된 Course객체 course를 HTTP 응답 본문으로 설정하여 200 OK 상태 코드를 가지는 ResponseEntity객체를 생성하는 것 → course객체는 이미 생성되어 있는 객체를 그대로 사용하는 것이기 때문에, 해당 객체에 이미 값이 설정되어 있어야 함
  • ResponseEntity.ok(new Course(title, instructor, cost)) : 새로운 Course객체를 생성하여 해당 객체를 HTTP 응답 본문으로 설정하고, 200 OK 상태 코드를 가지는 ResponseEntity객체를 생성하는 것 → title, instructor, cost와 같은 파라미터를 받아서 새로운 Course객체를 생성하는 것이기 때문에, 해당 파라미터를 인자로 받아서 객체를 생성해야 함

즉!! ResponseEntity.ok(course)는 이미 생성된 course객체를 사용하는 것이고, ResponseEntity.ok(new Course(title, instructor, cost))는 새로운 Course객체를 생성하여 사용하는 것


  • Controller 전체 코드 & Course 클래스 코드
@RestController
@RequestMapping("/controller/course")
public class CourseController {

    @PostMapping("/body")
    public ResponseEntity<?> courseBody(@RequestBody Course course) {
        System.out.println("course.toString() = " + course.toString());
        course.setCost(123456);
        return ResponseEntity.ok(course);
    }

    @GetMapping("/query")
    public ResponseEntity<?> courseQuery(@PathParam("title") String title, @PathParam("instructor") String instructor, @PathParam("cost") double cost) {
        System.out.println("title = " + title);
        System.out.println("instructor = " + instructor);
        System.out.println("cost = " + cost);
        return ResponseEntity.ok(new Course(title, instructor, cost));
    }

    @GetMapping("/model-attribute")
    public ResponseEntity<?> courseModelAttribute (@ModelAttribute Course course) {
        System.out.println("course.toString() = " + course.toString());
        return ResponseEntity.ok(course);
    }

    @GetMapping
    public ResponseEntity<?> course(Course course) {
        System.out.println("course.toString() = " + course.toString());
        return ResponseEntity.ok(course);
    }
}
@Getter
@ToString
@Setter
//@AllArgsConstructor
@NoArgsConstructor
public class Course {
    private String title;
    private String instructor;
    private double cost;

    public void setCost(double cost) {
        this.cost = cost;
    }

    public Course(String title, String instructor, double cost) {
        this.title = title;
        this.instructor = instructor;
        this.cost = cost;
    }
}

결론

  • courseServletHttpServlet을 상속한 서블릿으로, Java Servlet 기술을 사용하여 HTTP 요청을 처리

    • web.xml파일이나 @WebServlet어노테이션 등을 통해 서블릿 매핑을 설정하고, 해당 서블릿이 담당할 URL 패턴을 지정하여 사용
    • CourseServletHttpServletRequestHttpServletResponse객체를 통해 클라이언트의 요청을 처리하고 응답을 생성
  • CourseController는 Spring MVC의 컨트롤러로, Spring 프레임워크가 제공하는 기능을 사용하여 HTTP 요청을 처리

    • @RequestMapping, @GetMapping, @PostMapping등을 사용하여 URL 패턴과 HTTP 메소드에 대한 처리를 설정하고, 메소드 파라미터와 리턴 타입을 통해 요청과 응답 데이터를 처리
    • ResponseEntity를 사용하여 응답을 생성하고 반환
  • 일반적으로는 Spring MVC의 @Controller을 사용하는 CourseController를 선호

  • 그리고 만약, 파라미터 값을 안넣게 된다면 null 값을 호출하게 된다.

    • 즉, 위에 나와있는 모든 Get 메서드의 url 뒤에는 파라미터를 넣어주어야 한다.
profile
진짜 개발자가 되고 싶다

0개의 댓글