2022.09.19

Jimin·2022년 9월 19일
0

비트캠프

목록 보기
44/60
post-thumbnail
  • board-app 프로젝트 수행
      1. 애노테이션과 리플렉션 API를 활용하여 객체 자동 생성하기(계속)
      1. 웹서버와 애플리케이션 서버 기능 분리하기
      1. 웹 애플리케이션 서버를 자바 표준 웹 기술로 대체하기: Servlet/JSP

055. annotation과 reflection API를 활용하여 객체 자동 생성하기

reflection 객체를 사용하면 객체를 자동생성되게 할 수 있다.
패키지 지정시, 그 패키지부터 하위 패키지까지 검색한다.
이제 Mini

public class MiniWebServer {

  public static void main2(String[] args) throws Exception {
    // 클래스를 찾아주는 도구를 준비
    Reflections reflections = new Reflections("com.bitcamp.board");

    /*
    // 지정된 패키지에서 @WebServlet 애노테이션이 붙은 클래스를 모두 찾는다.
    // 검색필터 1) WebServlet 애노테이션이 붙어 있는 클래스의 이름들을 모두 찾아라!
    QueryFunction<Store,String> 검색필터1 = TypesAnnotated.with(WebServlet.class);

    // 검색필터 2) 찾은 클래스 이름을 가지고 클래스를 Method Area 영역에 로딩하여
    //             Class 객체 목록을 리턴하라!
    QueryFunction<Store,Class<?>> 검색필터2 = 검색필터1.asClass();

    // 위의 두 검색 조건으로 클래스를 찾는다.
    Set<Class<?>> 서블릿클래스들 = reflections.get(검색필터2);

    for (Class<?> 서블릿클래스정보 : 서블릿클래스들) {
      System.out.println(서블릿클래스정보.getName());
    }
     */

    Set<Class<?>> servletClassInfoList = reflections.get(TypesAnnotated.with(WebServlet.class).asClass());
    for (Class<?> servletClassInfo : servletClassInfoList) {
      WebServlet anno = servletClassInfo.getAnnotation(WebServlet.class);
      System.out.printf("%s ---> %s\n", anno.value(), servletClassInfo.getName());
    }

  }

  public static void main(String[] args) throws Exception {
    Connection con = DriverManager.getConnection(
        "jdbc:mariadb://localhost:3306/studydb","study","1111");

    // 객체(DAO, 서블릿)를 보관할 맵을 준비
    Map<String,Object> objMap = new HashMap<>();

    // DAO 객체를 찾아 맵에 보관한다.
    Reflections reflections = new Reflections("com.bitcamp.board");
    Set<Class<?>> classes = reflections.get(TypesAnnotated.with(Repository.class).asClass());
    for (Class<?> clazz : classes) {
      String objName = clazz.getAnnotation(Repository.class).value();
      Constructor<?> constructor = clazz.getConstructor(Connection.class);
      objMap.put(objName, constructor.newInstance(con));
    }

    // WebServlet 애노테이션이 붙은 클래스를 찾아 객체를 생성한 후 맵에 저장한다.
    // 맵에 저장할 때 사용할 key는 WebServlet 애노테이션에 설정된 값이다.
    //
    Set<Class<?>> servlets = reflections.get(TypesAnnotated.with(WebServlet.class).asClass());
    for (Class<?> servlet : servlets) {
      // 서블릿 클래스의 붙은 WebServlet 애노테이션으로부터 path 를 꺼낸다.
      String servletPath = servlet.getAnnotation(WebServlet.class).value();

      // 생성자의 파라미터의 타입을 알아내, 해당 객체를 주입한다.
      Constructor<?> constructor = servlet.getConstructors()[0];
      Parameter[] params = constructor.getParameters();

      if (params.length == 0) { // 생성자의 파라미터가 없다면, 즉 기본 생성자라면 
        objMap.put(servletPath, constructor.newInstance());

      } else { // 생성자의 파라미터가 있다면, 
        // 그 파라미터 타입과 일치하는 객체를 찾는다.
        Object argument = findObject(objMap, params[0].getType());
        if (argument != null) { // 생성자의 파라미터 타입과 일치하는 객체를 찾았다면 
          // 그 객체를 가지고 생성자를 호출하여 인스턴스를 생성한다.
          objMap.put(servletPath, constructor.newInstance(argument));
        }
      }
    }

    ErrorHandler errorHandler = new ErrorHandler();

    class MyHttpHandler implements HttpHandler {
      @Override
      public void handle(HttpExchange exchange) throws IOException {
        System.out.println("클라이언트가 요청함!");

        URI requestUri = exchange.getRequestURI();
        String path = requestUri.getPath();
        // String query = requestUri.getQuery(); // 디코딩을 제대로 수행하지 못한다!
        String query = requestUri.getRawQuery(); // 디코딩 없이 query string을 그대로 리턴 받기!
        byte[] bytes = null;

        try (StringWriter stringWriter = new StringWriter();
            PrintWriter printWriter = new PrintWriter(stringWriter)) {

          Map<String,String> paramMap = new HashMap<>();
          if (query != null && query.length() > 0) { // 예) no=1&title=aaaa&content=bbb
            String[] entries = query.split("&");
            for (String entry : entries) { // 예) no=1
              String[] kv = entry.split("=");
              // 웹브라우저가 보낸 파라미터 값은 저장하기 전에 URL 디코딩 한다.
              paramMap.put(kv[0], URLDecoder.decode(kv[1], "UTF-8"));
            }
          }
          System.out.println(paramMap);

          Servlet servlet = (Servlet) objMap.get(path);

          if (servlet != null) {
            servlet.service(paramMap, printWriter);
          } else {
            errorHandler.service(paramMap, printWriter);
          }

          bytes = stringWriter.toString().getBytes("UTF-8");

        } catch (Exception e) {
          bytes = "요청 처리 중 오류 발생!".getBytes("UTF-8");
          e.printStackTrace(); // 서버 콘솔 창에 오류에 대한 자세한 내용을 출력한다.
        }

        // 보내는 콘텐트의 MIME 타입이 무엇인지 응답 헤더에 추가한다.
        Headers responseHeaders = exchange.getResponseHeaders();
        responseHeaders.add("Content-Type", "text/html; charset=UTF-8");

        exchange.sendResponseHeaders(200, bytes.length);

        OutputStream out = exchange.getResponseBody();
        out.write(bytes);
        out.close();
      }
    }

    HttpServer server = HttpServer.create(new InetSocketAddress(8888), 0);
    server.createContext("/", new MyHttpHandler()); 
    server.setExecutor(null); 
    server.start();

    System.out.println("서버 시작!");
  }

  private static Object findObject(Map<String, Object> objMap, Class<?> type) {

    // 맵에 들어 있는 객체를 모두 꺼낸다.
    Collection<Object> values = objMap.values();

    // 꺼낸 객체들 중에 해당 타입의 인스턴스가 있는지 알아 본다.
    for (Object value : values) {
      if (type.isInstance(value)) { // 주어진 타입과 일치하는 객체를 찾았다면 
        return value; // 그 객체를 리턴한다.
      }
    }

    return null; // 못찾았으면 null을 리턴한다.
  }

}

056. Web Server와 Application Container 기능 분리하기

분리하는 이유? Web Server를 교체하기 쉽도록하기 위해서
웹 서버의 기능을 별도의 클래스로 분리한다.

1단계 - 웹서버에서 어플리케이션 객체 관리 기능을 분리한다.

  • com.bitcamp.board.ApplicationContainer 클래스 추가

  • com.bitcamp.board.MiniWebServer 클래스 변경

  • ApplicationContainer class

public class ApplicationContatiner {
  Map<String,Object> objMap = new HashMap<>();
  Reflections reflections;
  ErrorHandler errorHandler = new ErrorHandler();

  public ApplicationContatiner(String pakageName) throws Exception{
    reflections = new Reflections(pakageName);

    Connection con = DriverManager.getConnection(
        "jdbc:mariadb://localhost:3306/studydb","study","1111");

    Set<Class<?>> classes = reflections.get(TypesAnnotated.with(Repository.class).asClass());
    for (Class<?> clazz : classes) {
      String objName = clazz.getAnnotation(Repository.class).value();
      Constructor<?> constructor = clazz.getConstructor(Connection.class);
      objMap.put(objName, constructor.newInstance(con));
    }

    Set<Class<?>> servlets = reflections.get(TypesAnnotated.with(WebServlet.class).asClass());
    for (Class<?> servlet : servlets) {
      // 서블릿 클래스의 붙은 WebServlet 애노테이션으로부터 path 를 꺼낸다.
      String servletPath = servlet.getAnnotation(WebServlet.class).value();

      // 생성자의 파라미터의 타입을 알아내, 해당 객체를 주입한다.
      Constructor<?> constructor = servlet.getConstructors()[0];
      Parameter[] params = constructor.getParameters();

      if (params.length == 0) { // 생성자의 파라미터가 없다면, 즉 기본 생성자라면 
        objMap.put(servletPath, constructor.newInstance());

      } else { // 생성자의 파라미터가 있다면, 
        // 그 파라미터 타입과 일치하는 객체를 찾는다.
        Object argument = findObject(objMap, params[0].getType());
        if (argument != null) { // 생성자의 파라미터 타입과 일치하는 객체를 찾았다면 
          // 그 객체를 가지고 생성자를 호출하여 인스턴스를 생성한다.
          objMap.put(servletPath, constructor.newInstance(argument));
        }
      }
    }
  } // ApplicationServer()

  public void execute(String path, String query, PrintWriter out) throws Exception {
    // Query String을 분석하여 파라미터 값을 맵에 저장한다.
    Map<String,String> paramMap = new HashMap<>();
    if (query != null && query.length() > 0) { // 예) no=1&title=aaaa&content=bbb
      String[] entries = query.split("&");
      for (String entry : entries) { // 예) no=1
        String[] kv = entry.split("=");
        // 웹브라우저가 보낸 파라미터 값은 저장하기 전에 URL 디코딩 한다.
        paramMap.put(kv[0], URLDecoder.decode(kv[1], "UTF-8"));
      }
    }
    //System.out.println(paramMap);

    // 경로에 해당하는 서블릿 객체를 보관한다.
    Servlet servlet = (Servlet) objMap.get(path);
    if (servlet != null) {
      servlet.service(paramMap, out);
    } else {
      errorHandler.service(paramMap, out);
    }
  } // execute()

  private static Object findObject(Map<String, Object> objMap, Class<?> type) {
    Collection<Object> values = objMap.values();
    for (Object value : values) {
      if (type.isInstance(value)) {  
        return value; 
      }
    }
    return null; 
  } // findObject()
}
  • MiniWebServer class
public class MiniWebServer {

  public static void main(String[] args) throws Exception {

    // 어플리케이션 컨테이너 객체를 준비한다.
    ApplicationContatiner appContainer = new ApplicationContatiner("com.bitcamp.board");

    class MyHttpHandler implements HttpHandler {
      @Override
      public void handle(HttpExchange exchange) throws IOException { // Client가 요청할 때마다 호출된다.
        System.out.println("클라이언트가 요청함!");

        URI requestUri = exchange.getRequestURI();
        String path = requestUri.getPath();
        String query = requestUri.getRawQuery(); 
        byte[] bytes = null;

        try (StringWriter stringWriter = new StringWriter();
            PrintWriter printWriter = new PrintWriter(stringWriter)) {

          // application을 찾아 실행하는 것을 ApplicationContainer에게 위임한다.
          appContainer.execute(path, query, printWriter);

          bytes = stringWriter.toString().getBytes("UTF-8");

        } catch (Exception e) {
          bytes = "요청 처리 중 오류 발생!".getBytes("UTF-8");
          e.printStackTrace(); // 서버 콘솔 창에 오류에 대한 자세한 내용을 출력한다.
        }

        // 보내는 콘텐트의 MIME 타입이 무엇인지 응답 헤더에 추가한다.
        Headers responseHeaders = exchange.getResponseHeaders();
        responseHeaders.add("Content-Type", "text/html; charset=UTF-8");

        exchange.sendResponseHeaders(200, bytes.length);

        OutputStream out = exchange.getResponseBody();
        out.write(bytes);
        out.close();
      }
    } // MyHttpHandler {}

    HttpServer server = HttpServer.create(new InetSocketAddress(8000), 0);
    server.createContext("/", new MyHttpHandler()); 
    server.setExecutor(null); 
    server.start();

    System.out.println("서버 시작!");
  } // main()
} // MiniWebServer {}

057. Web Application Server를 자바 표준 웹 기술로 대체하기: Servlet/JSP

자바 표준 웹 애플리케이션 기술의 사용법: Servlet/JSP

1단계 - 톰캣 서버를 다운로드 하여 설치한다.

  • tomcat.apache.org 사이트에서 다운로드한다.
  • 로컬에 압축을 푼다.

2단계 - 톰캣 서버를 이클립스 IDE에 등록한다.

  • Window > Preferences > Server > Runtime Environments > Add

3단계 - 프로젝트를 배포할 떄 사용할 서버 작업 환경을 준비한다.

  • Servers 뷰 > 새 서버 등록

4단계 - Tomcat Server에 배포할 수 있는 Web application으로 전환한다.

  • build.gradle 변경
    • id eclipse-wtp 플러그인 추가: web application 관련 설정 파일을 추가로 생성한다.
    • id war 플러그인 추가: .war 배포 파일을 생성한다.
    • Web Application 관련 설정 추가
    • gradle cleanEclipse 실행 : 이클립스 설정 파일 삭제
    • gradle eclipse 실행 : 이클립스 설정 파일 재생성

5단계 - Web Application을 서버에 배포한다.

  • Servers 뷰 > 서버 실행 환경 선택 > 웹 프로젝트 추가

6단계 - Web Application 개발에 필요한 자바표준 웹라이브러리를 프로젝트에 추가한다.

  • search.maven.org에서 servlet-api 라이브러리를 찾아 추가한다.
    • build.gradle 변경
    • gradle cleanEclipse 실행 : 이클립스 설정 파일 삭제
    • gradle eclipse 실행 : 이클립스 설정 파일 재생성
    • 프로젝트 갱신

7단계 - WelcomeHandler 클래스를 자바 표준 웹 기술에 맞추어 변경한다.

  • com.bitcamp.board.servlet.WelcomeServlet 클래스 추가
    • WelcomeHandler 클래스를 복사해 온다.
    • Servlet 규칙에 따라 변경한다.

Eclipse IDE와 Tomcat Server

  • startup.bat/startup.sh → Tomcat Server → 웹 어플리케이션을 실행
  • EclipseIDE → Tomcat Server →(실행할 때마다 배포, 복사된다.) Eclipse IDE에서 설정한 서버작업환경
profile
https://github.com/Dingadung

0개의 댓글