You are using an outdated browser. Please upgrade your browser to improve your experience.

11/19 수업

애플리케이션 서버 아키텍처로 전환하기

java - Project 36-b + 42 = 43-a

전통적인 Client/Server Architecture의 문제점

image

Application Server Architecture

DBMS외부에서 직접 DBMS접근 불가 -> 보안이 강화된다

-> 기능이 업그레이드 때마다 재설치가 필요 없다.

익명이너클래스가 호출하는 생성자는 수퍼클래스 생성자이다.

윈도우 터미널 클라이언트 -> 서버접속 명령

java "-Dfile.encoding=utf-8" -cp bin/main com.eomcs.pms.ClientApp localhost 8888

image

11/20 수업 이어서

java Project36-b + 42 = 43-a

out, in 입출력을 맵객체에 담아놓으면 한번에 여러 커맨드가 들어와서 요청할 경우를 처리할 수 없다. 그래서 Request 클래스에 커맨드 객체가 클라이언트의 데이터를 읽고 쓸 때 사용할 스트림 객체를 보관한다.

Request는 뭐하는 앤가? 바구니이다. 내가 필요한 도구를 담아둠.

Map : 주머니, commandpath : 이름, out, in : 도구

객체는 별거 아니다. 이름 붙인 그릇이라고 생각하면 됨!

현재 시스템의 가장 치명적 문제점 : 로컬에서 접속 + 원격에서 접속, 로그인을 시키면 context에 보관되어서 다른사람의 정보가 보여짐. 다른사람을 로그아웃시킬수도 있음.

각각 클라이언트 전용 보관소(전용상자)가 필요한 상태!!

43-b 세션 다루기

클라이언트와 서버가 접속하는 유저를 식별할 수 있도록 코드를 바꾼다.

서버에 getSession메서드 추가

  private static String prepareSession(String sessionInfo) {

    String[] values = sessionInfo.split("=");

    if(values.length == 2) { // 클라이언트에서 자신의 세션 아이디를 보내왔다면,
      // 기존에 서버에서 발급한 세션 아이디를 그대로 리턴한다..
      return values[1];
    }

    // 클라이언트에게 새 세션 아이디를 부여한다.
    String sessionId = UUID.randomUUID().toString();

    // 새 세션을 위한 보관소를 생성한다.
    HashMap<String, Object> sessionMap = new HashMap<>();

    // 필터나 커맨드가 사용할 수 있도록 context맵에 저장한다.
    context.put(sessionId, sessionMap);

    return sessionId;
  }

Command가 필요한 객체가 늘 때마다 파라미터에 추가하고 커맨드를 수정하는 것이 매우 번거로우므로 Command 인터페이스를 Request객체를 받는 것으로 바꾼다. 대신 이제 필요한 기능은 Request클래스에 추가한다.

43-c 필터 및 커맨드 객체 생성 자동화

리플렉션 API를 사용하여 커맨드 객체를 자동으로 찾아 인스턴스를 생성한다.

package com.eomcs.pms.listener;

import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.Parameter;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import org.apache.ibatis.io.Resources;
import com.eomcs.context.ApplicationContextListener;
import com.eomcs.pms.handler.Command;
import com.eomcs.pms.handler.CommandAnno;

// 클라이언트 요청을 처리할 커맨드 객체를 준비한다.
public class RequestMappingListener implements ApplicationContextListener {

  Map<String, Object> context;

  @Override
  public void contextInitialized(Map<String,Object> context) {
    try {
      // 다른 메서드에서 context 맵 객체를 사용할 수 있도록 인스턴스 필드에 저장한다.
      this.context = context;

      // 커맨드 클래스가 있는 패키지의 파일 경로를 알아내기
      // => Mybatis에서 제공하는 클래스의 도움을 받는다.
      File commandPackagePath = Resources.getResourceAsFile("com/eomcs/pms/handler");
      System.out.println(commandPackagePath.getCanonicalPath());


      // 해당 패키지안에 있는 커맨드 클래스를 찾아 인스턴스를 생성한다.
      createCommands(commandPackagePath, "com.eomcs.pms.handler");

      // Command 구현체 생성 및 commandMap 객체 준비
      Map<String,Command> commandMap = new HashMap<>();

      // command객체만 모아놓은 상자를 큰 상자(context)에 보관한다.
      context.put("commandMap", commandMap);

      // 테스트 용 로그인 사용자 정보 가져오기
      //Member member = memberService.get("aaa@test.com", "1111");
      //context.put("loginUser", member);

    } catch (Exception e) {
      System.out.println("서비스 객체를 준비하는 중에 오류 발생!");
      e.printStackTrace();
    }
  }

  private Map<String,Object> createCommands(File packagePath, String packageName) {
    HashMap<String,Object> commandMap = new HashMap<>();

    File[] files = packagePath.listFiles((dir, name) -> name.endsWith(".class"));

    for(File f : files) {
      // 파일 정보를 가지고 클래스 이름을 알아낸다.
      String className = String.format("%s.%s", packageName, f.getName().replace(".class", ""));

      try {
        // 클래스 이름(패키지명 포함)을 사용하여 .class파일을 로딩한다.
        Class<?> clazz = Class.forName(className);

        // 패키지에서 찾은 클래스가 Command인터페이스를 구현한 클래스가 아니라면,
        // 생성자를 찾지 말고 다음 클래스로 이동한다.
        Class<?>[] interfaces = clazz.getInterfaces();
        boolean isCommand = false;
        for(Class<?> c : interfaces) {
          if(c == Command.class) {
            isCommand = true;
            break;
          }
        }

        if(!isCommand) continue; // 이 클래스는 Command구현체가 아니다.

        // 커맨드 클래스에 붙여 놓은 @CommandAnno 애노테이션 정보를 가져온다.
        CommandAnno commandAnno = clazz.getAnnotation(CommandAnno.class);

        // @CommandAnno 애노테이션이 클래스에 붙어 있지 않다면,
        // 해당 커맨드를 저장할 수 없기 때문에 객체를 생성하지 않는다!
        if(commandAnno == null) continue;

        // 클래스의 생성자 정보를 알아낸다.
        Constructor<?> constructor = clazz.getConstructors()[0];

        // 생성자의 파라미터 정보를 알아낸다.
        Parameter[] params = constructor.getParameters();

        // 생성자를 호출할 떄 넘겨 줄 파라미터 값을 담을 배열을 준비한다.
        Object[] args = new Object[params.length];

        int i = 0;
        for(Parameter param : params) {
          args[i++] = findDependency(param.getType());
        }

        Object command = constructor.newInstance(args);
        System.out.println(command.getClass().getName() + " 객체 생성 성공..?");


        // @CommandAnno 애노테이션에 지정한 커맨드 객체의 이름을 가져와서,
        // 커맨드 객체를 저장할 때 key로 사용한다!!
        commandMap.put(commandAnno.value(), command);

      } catch (Exception e) {
        System.out.println(className + " 로딩 중 오류 발생!");
      }
    }
    return commandMap;
  }

  private Object findDependency(Class<?> type) {
    // context맵에서 해당 타입의 객체를 찾는다.

    // 1) context맵에 보관된 모든 객체를 꺼낸다.
    Collection<?> objs = context.values();

    // 2) 각 객체가 파라미터로 받은 타입의 인스턴스인지 확인한다.
    for(Object obj : objs) {
      if(type.isInstance(obj)) {
        return obj;
      }
    }
    return null;
  }

  @Override
  public void contextDestroyed(Map<String,Object> context) {
  }
}

command의 이름을 자동으로 알 수 있도록 우리만의 주석 : annotaion을 생성한다.

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

// 커맨드 객체에 붙일 특별한 주석!
// => 커맨드의 이름을 설정하는 용도이다.
// => JVM에서 객체를 생성할 때 사용할 것이다.
@Retention(RetentionPolicy.RUNTIME)
public @interface CommandAnno {
  String value();
}

command클래스에 애노테이션을 등록한다.

@CommandAnno("/board/add")
public class BoardAddCommand implements Command {}

이제 클래스 이름만 알면 인스턴스를 자동으로 생성할 수 있게 되었다!

안타깝게도 실무에서는 리플렉션 API를 만들일이 없다. 그러나 혼자서 이런걸 공부하게 된다면 실력이 쑥쑥 늘 것이다..

API란?

“API*(Application Programming Interface, 응용 프로그램 프로그래밍 인터페이스)는 응용 프로그램에서 사용할 수 있도록, 운영체제나 프로그래밍 언어가 제공하는 기능을 제어할 수 있게 만든 인터페이스를 뜻한다.”

노마드 코더에 의하면 API는 키보드같은거라고 한다. 코드와 코드가 소통하기 위해서 입력체제가 필요한데 키보드 역할을 하는 것이 API인것임.

예를들어 Daum이 지도 데이터를 공개해도 대부분의 사람들은 그 데이터를 가지고 자신에게 유용하게 사용하기가 어려울 것입니다. 호환성의 문제라던가 너무나 정보가 방대해서 다루기 어렵다든가 등이 그 이유가 되겠죠. 생각해보세요. 홈버튼과 터치가 없이 스마트폰을 조작하라고 한다면 너무나 막막하겠죠? 그래서 설계자들은 사용자들에게 스마트폰과 교감을 할 수 있도록 터치 기능과 홈버튼을 집어 넣었습니다. 같은 맥락으로 Daum에선 자사 데이터를 활용하여 사용할 수 있게끔 ‘다음지도API’라는 일종의 ‘홈버튼’을 사용자들에게 공개한 것이죠.

API란 간단하게 이해하면 내가 만든 프로그램이 개인 개발자, 기업 기관이 제공하는 기능, 프로그램 등을 활용할 수 있게끔 도와주는 중간 매개체라는 것.

앞으로 우리가 써야할 외부 로그인기능도 구글 api, 깃허브 api를 사용한다고 생각하면 된다.

44 - 웹 애플리케이션 서버(web application server : WAS) : 서블릿 기술 도입

톰캣서버 사용자 홈디렉토리/server/ 이곳에 압축푼다.

Servelet = 서버 어플리케이션 조각

톰캣 서버 위치를 이클립스에 등록하기

그레이들 플러그인 추가

오늘 수업에서 이해해야할 것

클래스에 애노테이션을 붙이면 클래스 정보를 추출할 수 있구나!

그럼 우린 객체를 생성해서 자동으로 관리할 수 있구나!

코드 한줄한줄 이해하는 것 보다 전체적인 그림을 보기

주말에 진짜 공부 열심히하고싶다 -> html공부하세요