。゚(*´□`)゚。

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

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

[NC7기-68일차(7월31일)] - 웹프로그래밍 49일차

quarrrter 2023. 7. 31. 19:08

53. Facade 패턴(퍼사드)적용  (front와 비슷)

- GoF의 Facade 패턴의 동작원리 이해와 적용하는 방법 

- ActionListener 실행에 Facade 객체를 사용하기 

1. 기존 구조 

 - client는 여러 개의 객체에 의존한다. => 의존 객체 변경의 영향을 자주 받는다.  => 개선 방법: "Low Coupling" 유지 (다른 객체와의 관계를 줄이는 것)

* GRASP(일반적인 책임할당 소프트웨어 패턴)의 Low Coupling

 

2. 개선된 구조 

Facade: 앞 단에서 Client의 모든 요청을 받아 객체에 전달 & - 여러 객체에서 수행하는 공통 작업을 담당 

(=Front Controller)

Client는 Facade 역할의 객체만 사용. 뒷 단의 객체를 사용하는 것은  Facade 뿐 ! => 뒷 단의 객체에 변화가 발생하더라도 클라이언트는 영향을 받지 않는다. 

 

 

--------

프로젝트 적용

Server util에 Dispatcher 클래스 추가 

Serverapp 수정

menu 수정 

Dispatcher 수정

menugroup add 메서드 오버로딩 하고 서버앱 수정

 

 

54. Ioc 컨테이너 적용하기 

IoC(Inversion of Control) 제어의 역전 = 역제어 

1. Dependency Injection (의존 객체 주입 = 의존성 주입)

일반: 필요한 객체를 만들어 쓴다.

역제어: 필요한 객체를 외부에서 꽂아준다.  => 교체가 쉽다. 큰(복잡한) 시스템에 적합. 목업(mockup 가짜)개체를 주입하여 단위 테스트를 쉽게 할 수 있다. 

 

2. Event Listener 

일반: 위 -> 아래 순차적으로 코드를 실행

역제어: 특정 이벤트가 발생했을때 등록된 리스너가 실행된다. 

" 실행 흐름이 역행한다." 

 

IoC 컨테이너  = Bean Container + Dependency Injection 

Bean : Object(객체)의 애칭.// java(coffee) Object(bean)

 

Dispatcher Listener 하는 일

1. Listener 객체 준비

2. Listener의 Facade 역할 

 

프로젝트 적용

util- ApplicationContext class 준비 

config 패키지 및 appconfig 클래스 준비 

Application Context 수정 

 

---

주석

애노테이션 일반 문장빼고 다 앞에 붙일 수 있음

애노테이션 정의 - > 적용 - > 추출

적용: 클래스 선언, 필드선언, 메서드 선언, 파라미터 선언, 로컬변수 선언에 적용 가능

추출: java Reflection API를 사용하여 추출 

 

* 애노테이션 활용  - 소스코드 자동생성 

Lombok

* 애노테이션 활용  -  컴파일러에게 요청

ex) @Override 

* 애노테이션 활용  -  실행 중(Runtime)에 정보 추출 

 

 

 

 

 

 

default 값을 정의하지 않으면

 

 

배열; 

 

 

 

 

 

 

 

클래스나 인터페이스 선언에만 붙일 수 있다 .
스태틱이든 인스턴스든 필드에만 붙일 수 있음.
중복사용


프로젝트 

util - ComponentScan annotation 추가하기 

 

Hashset은 중복되는거 자동제거임. array list는 아님 

 

1. annotation 문법알기 

2. 패키지 이름을 알기만 하면 그 안에 클래스들이 무엇인지 알아낼수있다.

 

public class ApplicationContext {
  // 객체 보관소
  Map<String,Object> beanContainer = new HashMap<>();

  public ApplicationContext(Class<?> configClass) throws Exception {

    processBeanAnnotation(configClass);

    ComponentScan componentScan = configClass.getAnnotation(ComponentScan.class);
    if (componentScan != null) {
      processComponentScanAnnotation(componentScan);
    }
  }

  private void processBeanAnnotation(Class<?> configClass) throws Exception {
    // 클래스의 기본 생성자를 알아낸다.
    Constructor<?> constructor = configClass.getConstructor();

    // 기본 생성자를 이용하여 객체를 생성한다.
    Object obj = constructor.newInstance();

    // 해당 클래스에 정의된 메서드 목록만 가져온다.
    Method[] methods = configClass.getDeclaredMethods();
    for (Method m : methods) {

      // 메서드의 리턴 타입이 없다면 무시한다.
      if (m.getReturnType() == void.class) {
        continue;
      }

      // @Bean 애노테이션이 붙지 않은 메서드라면 무시한다.
      Bean beanAnnotation = m.getAnnotation(Bean.class);
      if (beanAnnotation == null) {
        continue;
      }

      // 메서드 중에서 리턴 값이 있는 메서드를 호출한다.
      // 즉 오직 값을 리턴하는 메서드만 호출한다.
      Object returnValue = m.invoke(obj);

      // 메서드가 리턴한 값을 컨테이너에 저장한다.
      if (beanAnnotation.value().length() > 0) {
        // 애노테이션에 객체 이름이 지정되어 있다면 그 이름으로 객체를 저장한다.
        beanContainer.put(beanAnnotation.value(), returnValue);
      } else {
        // 애노테이션에 설정된 이름이 없다면 메서드 이름을 사용하여 객체를 저장한다.
        beanContainer.put(m.getName(), returnValue);
      }
    }
  }

  private void processComponentScanAnnotation(ComponentScan componentScan) throws Exception {
    for (String basePackage : componentScan.basePackages()) {
      System.out.println(basePackage + "---------------------------------");
      createBeans(basePackage);
    }
  }

  private void createBeans(String basePackage) throws Exception {
    // 패키지 이름에 해당하는 디렉토리 정보를 알아낸다.
    // 1) 패키지 이름을 파일 경로로 변환한다(. --> /)
    String packagePath = basePackage.replaceAll("[.]", "/");

    // 2) 클래스 경로(CLASSPATH)를 관리하는 객체를 준비한다.
    ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();

    // 3) 클래스로더에게 패키지 경로에 해당하는 디렉토리 정보를 읽을 수 있는 도구를 달라고 요구한다.
    InputStream dirInputStream = systemClassLoader.getResourceAsStream(packagePath);
    if (dirInputStream == null) {
      return;
    }

    // 4) 패키지 디렉토리 안에 들어있는 파일 이름이나 하위 디렉토리 이름을 읽을 도구를 준비한다.
    BufferedReader dirReader = new BufferedReader(new InputStreamReader(dirInputStream));

    // 5) 디렉토리 리더를 통해 해당 디렉토리에 들어 있는 하위 디렉토리 또는 파일 이름을 알아낸다.
    // ==> 전통적인 컬렉션 데이터 가공 방식
    //    - 한 줄씩 읽으면 된다.
    //    Set<Class<?>> classes = new HashSet<>();
    //    String filename = null;
    //    while ((filename = dirReader.readLine()) != null) {
    //
    //      // 파일 확장자가 .class 로 끝나는 파일만 처리한다.
    //      if (filename.endsWith(".class")) {
    //
    //        // 패키지 이름과 클래스 이름(.class 확장자를 뺀 이름)을 합쳐서
    //        // Fully-Qualified class name을 만든 다음에
    //        // Class.forName() 을 사용하여 클래스를 메모리(Method Area)에 로딩한다.
    //        Class<?> clazz = Class.forName(basePackage + "." + filename.replace(".class", ""));
    //
    //        // 로딩한 클래스 정보를 Set 컬렉션에 담는다.
    //        classes.add(clazz);
    //      }
    //    }

    // ==> 스트림 프로그래밍 기법을 이용하여 컬렉션 데이터를 가공하는 방식
    //    Set<Class<?>> classes = dirReader
    //        .lines() // 오리지널 문자 단위 스트림을 줄 단위로 문자열로 나열한 스트림으로 변환한다.
    //        .filter(new Predicate<String>() { // 스트림에서 처리할 대상을 선택하는 필터를 꼽는다.
    //          @Override
    //          public boolean test(String filename) {
    //            // 이 필터는 파일의 확장자가 .class 로 끝나는 경우에만 처리한다.
    //            return filename.endsWith(".class");
    //          }})
    //        .map(new Function<String, Class<?>>() { // 파일 이름을 받아서 Class 정보를 리턴하는 플러그인 장착한다.
    //          @Override
    //          public Class<?> apply(String filename) {
    //            try {
    //              return Class.forName(basePackage + "." + filename.replace(".class", ""));
    //            } catch (Exception e) {
    //              // 클래스 로딩하다가 예외가 발생하면 무시한다.
    //            }
    //            return null;
    //          }})
    //        .collect(Collectors.toSet()); // 스트림을 수행하여 최종 결과물은 Set 컬렉션에 담아서 리턴하라고 명령한다.

    // ==> 스트림 프로그래밍 방식( + 람다)
    Set<Class<?>> classes = dirReader
        .lines()
        .filter(filename -> filename.endsWith(".class"))
        .map(filename -> {
          try {
            return Class.forName(basePackage + "." + filename.replace(".class", ""));
          } catch (Exception e) {
            return null;
          }
        })
        .collect(Collectors.toSet());

    for (Class<?> clazz : classes) {
      System.out.println("##### " + clazz.getName());
    }
  }

  public Object getBean(String name) {
    return beanContainer.get(name);
  }

  public String[] getBeanNames() {
    return beanContainer.keySet().toArray(new String[0]);
  }

  public static void main(String[] args) throws Exception {
    ApplicationContext applicationContext = new ApplicationContext(AppConfig.class);
    for (String name : applicationContext.getBeanNames()) {
      System.out.println(name);
    }
  }
}