끈기JK 2023. 2. 28. 20:18

com.eomcs.ioc

 

 

예제 소스 정리

 

 

ioc

 

 

com.eomcs.ioc.ex01

 

 

IoC 컨테이너 만들기 - 1) 특정 패키지의 파일 목록 알아내기
package com.eomcs.ioc.ex01;

import java.io.File;

// => 그 패키지에 있는 파일 및 디렉토리 정보만 알아낸다.
public class Exam01 {

  public static void main(String[] args) {
    // 1) 패키지 경로 준비
    File packageDir = new File("/Users/eomjinyoung/git/java106/java106-java-basic/bin/main/step19/ex1");

    // 2) 해당 디렉토리에서 파일 목록을 가져오기
    File[] files = packageDir.listFiles();
    for (File f : files) {
      if (f.isDirectory()) {
        System.out.printf("d %s\n", f.getName());
      } else {
        System.out.printf("- %s\n", f.getName());
      }
    }
  }

}

 

 

package com.eomcs.ioc.ex01;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

// => 파일 및 디렉토리 정보를 알아내는 코드를 별도의 메서드로 분리한다.
public class Exam02 {

  public static void main(String[] args) {
    // 1) 패키지 경로 준비
    File packageDir = new File("/Users/eomjinyoung/git/java106/java106-java-basic/bin/main/step19/ex1");

    // 2) 해당 디렉토리에서 파일 목록을 가져오기
    List<File> files = findFiles(packageDir);
    for (File f : files) {
      if (f.isDirectory()) {
        System.out.printf("d %s\n", f.getName());
      } else {
        System.out.printf("- %s\n", f.getName());
      }
    }
  }

  static List<File> findFiles(File dir) {
    // 배열을 사용하는 것 보다 ArrayList를 사용하는 것이 편하기 때문에
    // 배열에 들어있는 File 객체들을 ArrayList로 복사한다.
    ArrayList<File> list = new ArrayList<>();
    File[] files = dir.listFiles();
    for (File f : files) {
      list.add(f);
    }
    return list;
  }

}

 

 

package com.eomcs.ioc.ex01;

import java.io.File;
import java.util.List;

// => 디렉토리의 파일 목록을 다루는 메서드를 별도의 클래스로 분리한다.
public class Exam03 {

  public static void main(String[] args) {
    // 1) 패키지 경로 준비
    File packageDir = new File("/Users/eomjinyoung/git/java106/java106-java-basic/bin/main/step19/ex1");

    // 2) 해당 디렉토리에서 파일 목록을 가져오기
    ApplicationContext appContext = new ApplicationContext(packageDir);
    List<File> files = appContext.getFiles();
    for (File f : files) {
      if (f.isDirectory()) {
        System.out.printf("d %s\n", f.getName());
      } else {
        System.out.printf("- %s\n", f.getName());
      }
    }
  }
}

 

// 지정된 폴더의 파일 목록만 가져온다.
package com.eomcs.ioc.ex01;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

public class ApplicationContext {
  private ArrayList<File> list = new ArrayList<>();

  public ApplicationContext(File dir) {
    findFiles(dir);
  }

  private void findFiles(File dir) {
    File[] files = dir.listFiles();
    for (File f : files) {
      this.list.add(f);
    }
  }

  public List<File> getFiles() {
    return this.list;
  }
}

 

 

package com.eomcs.ioc.ex01;

import java.io.File;
import java.util.List;

// => 지정된 디렉토리 뿐만아니라 하위 디렉토리도 검색하여 파일 목록을 알아낸다.
public class Exam04 {

  public static void main(String[] args) throws Exception {
    // 1) 패키지 경로 준비
    File packageDir = new File("/Users/eomjinyoung/git/java106/java106-java-basic/bin/main/step19");

    // 2) 해당 디렉토리에서 파일 목록을 가져오기
    ApplicationContext2 appContext = new ApplicationContext2(packageDir);
    List<File> files = appContext.getFiles();
    for (File f : files) {
      System.out.println(f.getCanonicalPath());
    }
  }
}

 

// 하위 폴더의 파일을 가져온다.
package com.eomcs.ioc.ex01;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

public class ApplicationContext2 {
  private ArrayList<File> list = new ArrayList<>();

  public ApplicationContext2(File dir) {
    findFiles(dir);
  }

  private void findFiles(File dir) {
    File[] files = dir.listFiles();
    for (File f : files) {
      if (f.isDirectory()) {
        findFiles(f);
      } else {
        this.list.add(f);
      }
    }
  }

  public List<File> getFiles() {
    return this.list;
  }
}

 

 

com.eomcs.ioc.ex02

 

 

IoC 컨테이너 만들기 - 2) 특정 패키지의 파일 목록 알아내기
package com.eomcs.ioc.ex02;

import java.io.File;
import java.util.List;

// => ClassLoader로 자바 classpath를 검색하여 해당 패키지를 찾는다. 
public class Exam05 {

  public static void main(String[] args) throws Exception {
    // 1) 패키지 경로를 따로 준비하지 않는다.
    // 소스 코드에 직접 전체 디렉토리 경로를 지정하게 되면,
    // 만약 해당 폴더가 옮겨졌을 때 디렉토리를 찾지 못하는 문제가 있다.
    // 찾으려면 소스 코드를 변경해야 한다.
    // 소스 코드를 변경하지 않고 계속 step18/ex6 가 존재하는 디렉토리를 찾는 방법은?
    // => step18/ex6를 자바 CLASSPATH 에 둔다.
    //         즉 jvm을 실행할 때 -classpath로 클래스 파일이 있는 경로를 지정하는데
    //         그 경로 안에 두는 것을 말한다.
    // => classpath에 두면 ClassLoader를 이용하여 해당 폴더가 
    //         실제 어느 디렉토리 밑에 있는지 쉽게 찾을 수 있다.

    // ClassLoader의 도움을 받지 않는다면,
    // 우리는 다음과 같이 코드에 직접 그 패키지가 있는 경로를 지정해야 한다.
    //File packageDir = new File("/Users/eomjinyoung/git/java106/java106-java-basic/bin/main/step19");

    // 2) 해당 패키지의 파일 목록을 가져오기
    ApplicationContext3 appContext = new ApplicationContext3("step19.ex1");
    List<File> files = appContext.getFiles();
    for (File f : files) {
      System.out.println(f.getCanonicalPath());
    }
  }
}

 

// 디렉토리 경로 대신 패키지 이름을 입력 받아 
// 해당 패키지의 파일 목록을 알아내기
package com.eomcs.ioc.ex02;

import java.io.File;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;

public class ApplicationContext3 {
  private ArrayList<File> list = new ArrayList<>();

  public ApplicationContext3(String packageName) {
    // 1) 패키지 이름에 포함된 .을 파일 경로의 /로 변경한다.
    String path = packageName.replace(".", "/");

    // 2) 해당 경로의 디렉토리를 classpath에서 찾아 실제 경로를 알아낸다.
    // 프로그램과 관련된 파일을 일반 경로에 두는 것 보다
    // JVM이 알고 있는 경로(CLASSPATH)에 두면 해당 파일을 보다 쉽게 찾을 수 있다.
    // 왜냐하면,
    // JVM이 모르는 특정 경로에 파일을 두면, 
    // 그 파일을 찾기 위해서 소스 코드에 그 특정 경로를 지정해야 한다.
    // 만약 프로그램을 다른 OS에 배포하거나 경로가 달라진다면
    // 또 소스 코드를 변경해야 하는 번거로움이 발생한다.
    // 그래서 실무에서는 이런 문제를 해결하기 위해 
    // 프로그램과 관련된 파일은 주로 CLASSPATH에 둔다.

    // 다음은 CLASSPATH 에 파일을 두었을 때
    // 그 파일의 실제 경로를 알아내는 방법이다.
    // 
    // CLASSPATH 를 뒤져 파일을 찾아줄 객체를 얻는다.
    // => ClassLoader
    ClassLoader classLoader = ClassLoader.getSystemClassLoader();

    // 클래스 로더를 이용하여 특정 패키지나 클래스의 절대 경로를 알아낸다.
    //   => 클래스 로더는 디렉토리나 파일을 찾을 때 JVM 에 지정된 CLASSPATH에서 찾는다.
    //      따라서 프로그램을 작성할 때 해당 패키지나 파일이 있는 경로를 
    //      코드에 지정할 필요가 없어 편리하다.
    //   => 디렉토리 경로를 지정할 때 / 를 사용하라!
    //   => getResource() 가 리턴하는 것은 찾은 자원의 경로 정보이다.
    //   => 같은 경로의 자원이 여러 디렉토리에 중복해서 있을 때는
    //      getResources()를 호출하여 여러 개의 경로 정보를 받아라!
    //   => 자원의 경로 정보를 담고 있는 URL 객체를 리턴한다.
    //  
    URL url = classLoader.getResource(path);

    // 3) URL에서 실제 경로를 뽑아 File 객체를 생성한다.
    File dir = new File(url.getPath());

    // 4) 해당 파일의 경로가 디렉토리를 가리키고 있다면,
    //    그 디렉토리의 파일 목록을 알아낸다.
    if (!dir.isDirectory())
      return;

    findFiles(dir);
  }

  private void findFiles(File dir) {
    File[] files = dir.listFiles();
    for (File f : files) {
      if (f.isDirectory()) {
        findFiles(f);
      } else {
        this.list.add(f);
      }
    }
  }

  public List<File> getFiles() {
    return this.list;
  }
}

 

 

com.eomcs.ioc.ex03

 

 

IoC 컨테이너 만들기 - 3) .class 파일만 추출하기. 중첩클래스 제외.
package com.eomcs.ioc.ex03;

import java.io.File;
import java.util.List;

// => FileFilter를 사용하여 .class 파일만 추출한다. 
public class Exam06 {

  public static void main(String[] args) throws Exception {
    ApplicationContext4 appContext = new ApplicationContext4("step19.ex1");
    List<File> files = appContext.getFiles();
    for (File f : files) {
      System.out.println(f.getCanonicalPath());
    }
  }
}

 

// 중첩 클래스를 제외한 .class 파일만 추출한다.
package com.eomcs.ioc.ex03;

import java.io.File;
import java.io.FileFilter;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;

public class ApplicationContext4 {
  private ArrayList<File> list = new ArrayList<>();

  public ApplicationContext4(String packageName) {
    String path = packageName.replace(".", "/");
    ClassLoader classLoader = ClassLoader.getSystemClassLoader();
    URL url = classLoader.getResource(path);
    File dir = new File(url.getPath());
    if (!dir.isDirectory())
      return;
    findFiles(dir);
  }

  private void findFiles(File dir) {
    File[] files = dir.listFiles(new FileFilter() {
      public boolean accept(File pathname) {
        if (pathname.isDirectory() || // 디렉토리 이거나
            (pathname.getName().endsWith(".class") && // .class 파일인 경우 
                !pathname.getName().contains("$"))) { // 단 중첩클래스는 제외
          return true;
        }
        return false;
      }
    });
    for (File f : files) {
      if (f.isDirectory()) {
        findFiles(f);
      } else {
        this.list.add(f);
      }
    }
  }

  public List<File> getFiles() {
    return this.list;
  }
}

 

 

com.eomcs.ioc.ex04

 

 

IoC 컨테이너 만들기 - 4) 파일 경로 대신 패키지 이름을 포함한 클래스 이름을 추출한다.
package com.eomcs.ioc.ex04;

import java.io.File;
import java.util.List;

// => 전체 클래스 이름 출력
public class Exam07 {

  public static void main(String[] args) throws Exception {
    ApplicationContext5 appContext = new ApplicationContext5("step19.ex1");
    List<String> classnames = appContext.getClassname();
    for (String name : classnames) {
      System.out.println(name);
    }
  }
}

 

// 중첩 클래스를 제외한 .class 파일만 추출한다.
package com.eomcs.ioc.ex04;

import java.io.File;
import java.io.FileFilter;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;

public class ApplicationContext5 {
  private ArrayList<String> list = new ArrayList<>();

  public ApplicationContext5(String packageName) {
    String path = packageName.replace(".", "/");
    ClassLoader classLoader = ClassLoader.getSystemClassLoader();
    URL url = classLoader.getResource(path);
    File dir = new File(url.getPath());
    if (!dir.isDirectory())
      return;
    findFiles(dir, packageName);
  }

  // 파일 경로에서 패키지명과 클래스명을 추출할 수 없다.
  // 그래서 파일을 찾을 때 패키지 이름을 따로 관리해야 한다.
  private void findFiles(File dir, String packageName) {
    File[] files = dir.listFiles(new FileFilter() {
      public boolean accept(File pathname) {
        if (pathname.isDirectory() || // 디렉토리 이거나
            (pathname.getName().endsWith(".class") && // .class 파일인 경우 
                !pathname.getName().contains("$"))) { // 단 중첩클래스는 제외
          return true;
        }
        return false;
      }
    });
    for (File f : files) {
      if (f.isDirectory()) {
        findFiles(f, packageName + "." + f.getName());
      } else {
        String classname = f.getName();                
        this.list.add(packageName + "." + 
            classname.substring(0, classname.length() - 6));
      }
    }
  }

  public List<String> getClassname() {
    return this.list;
  }
}

 

 

com.eomcs.ioc.ex05

 

 

IoC 컨테이너 만들기 - 5) 클래스 이름으로 Class 객체를 만든다.
package com.eomcs.ioc.ex05;

import java.io.File;
import java.util.List;

// => 클래스 이름으로 해당 클래스를 로딩한 후 Class 객체를 가져온다.
//    왜? Class 객체가 있으면 언제라도 인스턴스를 생성할 수 있기 때문이다.
public class Exam08 {

  public static void main(String[] args) throws Exception {
    ApplicationContext6 appContext = new ApplicationContext6("step19.ex1");
    List<Class> classes = appContext.getClasses();
    for (Class clazz : classes) {
      System.out.println(clazz.getName());
    }
  }
}

 

// 클래스 이름으로 클래스를 로딩하여 Class 객체를 저장한다.
package com.eomcs.ioc.ex05;

import java.io.File;
import java.io.FileFilter;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;

public class ApplicationContext6 {
  private ArrayList<Class> list = new ArrayList<>();

  public ApplicationContext6(String packageName) throws Exception {
    String path = packageName.replace(".", "/");
    ClassLoader classLoader = ClassLoader.getSystemClassLoader();
    URL url = classLoader.getResource(path);
    File dir = new File(url.getPath());
    if (!dir.isDirectory())
      return;
    findClasses(dir, packageName);
  }

  private void findClasses(File dir, String packageName) throws Exception {
    File[] files = dir.listFiles(new FileFilter() {
      public boolean accept(File pathname) {
        if (pathname.isDirectory() || // 디렉토리 이거나
            (pathname.getName().endsWith(".class") && // .class 파일인 경우 
                !pathname.getName().contains("$"))) { // 단 중첩클래스는 제외
          return true;
        }
        return false;
      }
    });
    for (File f : files) {
      if (f.isDirectory()) {
        findClasses(f, packageName + "." + f.getName());
      } else {
        String classname = f.getName();                
        this.list.add(Class.forName(packageName + "." + 
            classname.substring(0, classname.length() - 6)));
      }
    }
  }

  public List<Class> getClasses() {
    return this.list;
  }
}

 

 

com.eomcs.ioc.ex06

 

 

IoC 컨테이너 만들기 - 6) Class 객체를 이용하여 인스턴스를 만들어 저장한다.
package com.eomcs.ioc.ex06;

import java.util.List;

// => 인스턴스를 꺼내 그 클래스 이름을 출력한다.
public class Exam09 {

  public static void main(String[] args) throws Exception {
    ApplicationContext7 appContext = new ApplicationContext7("step19.ex1");
    List<Object> objects = appContext.getObjects();
    for (Object obj : objects) {
      System.out.println(obj.getClass().getName());
    }
  }
}

 

// 해당 패키지의 클래스를 알아내 인스턴스를 생성한다.
package com.eomcs.ioc.ex06;

import java.io.File;
import java.io.FileFilter;
import java.lang.reflect.Constructor;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;

public class ApplicationContext7 {
  private ArrayList<Object> list = new ArrayList<>();

  public ApplicationContext7(String packageName) throws Exception {
    String path = packageName.replace(".", "/");
    ClassLoader classLoader = ClassLoader.getSystemClassLoader();
    URL url = classLoader.getResource(path);
    File dir = new File(url.getPath());
    if (!dir.isDirectory())
      return;
    findClasses(dir, packageName);
  }

  private void findClasses(File dir, String packageName) throws Exception {
    File[] files = dir.listFiles(new FileFilter() {
      public boolean accept(File pathname) {
        if (pathname.isDirectory() || // 디렉토리 이거나
            (pathname.getName().endsWith(".class") && // .class 파일인 경우 
                !pathname.getName().contains("$"))) { // 단 중첩클래스는 제외
          return true;
        }
        return false;
      }
    });
    for (File f : files) {
      if (f.isDirectory()) {
        findClasses(f, packageName + "." + f.getName());
        continue;
      } 

      String classname = f.getName();    
      Class clazz = Class.forName(packageName + "." + 
          classname.substring(0, classname.length() - 6));
      Object obj = createObject(clazz);
      if (obj != null)
        this.list.add(obj);
    }
  }

  private Object createObject(Class clazz) {
    try {
      // 파라미터가 없는 기본 생성자를 찾는다.
      clazz.getConstructor();
      return clazz.newInstance(); // 기본 생성자를 호출하여 객체를 생성한다.
    } catch (Exception e) {
      return null;
    }
  }

  public List<Object> getObjects() {
    return this.list;
  }
}

 

 

com.eomcs.ioc.ex07

 

 

IoC 컨테이너 만들기 - 객체를 저장할 때 클래스 이름으로 저장하기, 클래스 이름으로 객체 꺼내기
package com.eomcs.ioc.ex07;

public class Exam10 {
  int value;

  public Exam10() {
    this.value = 200;
  }

  public void print() {
    System.out.printf("value=%d\n", this.value);
  }

  public static void main(String[] args) throws Exception {
    ApplicationContext8 appContext = new ApplicationContext8("step19.ex7");
    Exam10 obj = (Exam10) appContext.getBean("step19.ex7.Exam10");
    obj.print();
  }
}

 빈(bean)?
 => 인스턴스(instance), 객체(object)와 같은 의미로 사용한다.
 => 다만 자바 문법에서 정한 나름의 규칙에 따라 만든 클래스의 인스턴스를 얘기한다.
 => 그러나 현업에서는 인스턴스 또는 객체와 같은 의미로 사용한다.
 => bean = instance = object
 
 빈 컨테이너(bean container)?
 => 위의 ApplicationContext8 과 같이 인스턴스를 생성하고 보관하고 
    필요할 때 리턴해주는 그런 역할을 하는 객체를 말한다.
 => 컨테이너는 객체의 '생성-실행-소멸' 즉 객체의 생명주기(lifecycle)를 관리한다.
 => 대표적인 라이브러리가 "스프링 IoC 컨테이너" 이다.
 
 IoC 컨테이너(Inversion of Control 컨테이너)?
 => IoC 컨테이너 = 빈 컨테이너 + 의존 객체 자동 주입 

 

// 객체를 저장할 때 클래스 이름으로 저장하고 클래스 이름으로 꺼낼 수 있다.
package com.eomcs.ioc.ex07;

import java.io.File;
import java.io.FileFilter;
import java.net.URL;
import java.util.HashMap;
import java.util.List;

public class ApplicationContext8 {
  // 클래스 이름으로 객체를 저장할 수 있도록 Map 을 사용한다.
  private HashMap<String,Object> objPool = new HashMap<>();

  public ApplicationContext8(String packageName) throws Exception {
    String path = packageName.replace(".", "/");
    ClassLoader classLoader = ClassLoader.getSystemClassLoader();
    URL url = classLoader.getResource(path);
    File dir = new File(url.getPath());
    if (!dir.isDirectory())
      return;
    findClasses(dir, packageName);
  }

  private void findClasses(File dir, String packageName) throws Exception {
    File[] files = dir.listFiles(new FileFilter() {
      public boolean accept(File pathname) {
        if (pathname.isDirectory() || // 디렉토리 이거나
            (pathname.getName().endsWith(".class") && // .class 파일인 경우 
                !pathname.getName().contains("$"))) { // 단 중첩클래스는 제외
          return true;
        }
        return false;
      }
    });
    for (File f : files) {
      if (f.isDirectory()) {
        findClasses(f, packageName + "." + f.getName());
        continue;
      } 

      String classname = f.getName();    
      Class clazz = Class.forName(packageName + "." + 
          classname.substring(0, classname.length() - 6));
      Object obj = createObject(clazz);
      if (obj != null) {
        this.objPool.put(clazz.getName(), obj);
      }
    }
  }

  private Object createObject(Class clazz) {
    try {
      // 파라미터가 없는 기본 생성자를 찾는다.
      clazz.getConstructor();
      return clazz.newInstance(); // 기본 생성자를 호출하여 객체를 생성한다.
    } catch (Exception e) {
      return null;
    }
  }

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

 

 

com.eomcs.ioc.ex08

 

 

IoC 컨테이너 만들기 - 의존 객체의 사용
package com.eomcs.ioc.ex08;

// 의존 객체(dependancy object)
// => 작업을 수행하기 위해 사용해야만 하는 객체 
// => Car 클래스의 경우 Engine 클래스가 Car의 의존 객체이다.
// => Car의 move()를 실행하려면 Engine 객체가 있어야 한다.
// 
// 의존 객체 준비
// => 일반적으로 의존 객체는 그 객체를 사용하는 측에서 생성한다.
// => 즉 Car 클래스에서 Engine 클래스를 사용하니까, 
//    Car이 생성자에서 Engine 객체를 생성하였다.
// 
public class Exam11 {
  public Exam11() {
    System.out.println("===> Exam11()");
  }

  public static void main(String[] args) throws Exception {
    Car car = new Car();
    car.move();
  }
}

 

package com.eomcs.ioc.ex08;

public class Car {
  Engine engine;

  public Car() {
    System.out.println("===> Car()");
    this.engine = new Engine();
  }

  public void move() {
    this.engine.run();
  }
}
package com.eomcs.ioc.ex08;

public class Engine {

  public Engine() {
    System.out.println("===> Engine()");
  }

  public void run() {
    System.out.println("엔진을 가동한다.");
  }
}

 

 

IoC 컨테이너 만들기 - 의존 객체의 주입
package com.eomcs.ioc.ex08;

// 의존 객체 주입(dependancies injection; DI)
// => 작업하는 데 필요한 의존객체를 자체적으로 만들지 않고 외부에서 주입해주는 것을 말한다.
// => 이점?
//    다양한 객체를 주입할 수 있다.
//    즉 나중에 Engine의 새로운 서브 클래스가 등장했을 때 그 객체도 주입할 수 있다.
// => Car 클래스는 경우 생성자에서 Engine 객체를 생성하기 때문에 
//    새로운 Engine의 서브 클래스가 만들어지더라도 사용할 수 없다.
// 
public class Exam12 {
  public Exam12() {
    System.out.println("===> Exam12()");
  }

  public static void main(String[] args) throws Exception {
    Engine engine = new Engine();
    Car2 car = new Car2(engine);
    car.move();
  }
}

 

// 의존 객체 Engine을 자체적으로 만들지 않고 외부에서 주입 받는다.
package com.eomcs.ioc.ex08;

public class Car2 {
  Engine engine;

  public Car2(Engine engine) {
    System.out.println("===> Car2(Engine)");
    this.engine = engine;
  }

  public void move() {
    this.engine.run();
  }
}

 

 

IoC 컨테이너 만들기 - 의존 객체의 주입
package com.eomcs.ioc.ex08;

public class Exam13 {
  public Exam13() {
    System.out.println("===> Exam13()");
  }


  public static void main(String[] args) throws Exception {
    // 새 Engine을 준비한다.
    UltraEngine engine = new UltraEngine();

    // Car 클래스는 Engine을 자체적으로 생성하여 사용하기 때문에 
    // 새로 만든 엔진을 꼽을 수가 없다.
    Car car = new Car(); // 기존의 Engine 객체 사용!
    car.move();
    System.out.println("---------------------------");

    // Car2 클래스는 외부에서 Engine 객체를 주입받도록 설계했기 때문에
    // 새 UltraEngine을 장착할 수 있다.
    Car2 car2 = new Car2(engine);
    car2.move();
  }
}

// DI는 IoC의 한 예이다.
//
// IoC(Inversion of Control)?
// => 제어의 역행.
// => 일반적인 흐름에서 벗어난 동작을 말한다.
// => 예:
//    1) Dependancy Injection(DI)
//       => 자신이 사용할 의존 객체를 자신이 만들지 않고 외부에서 주입 받는 것.
//    2) Event
//       => 실행 순서에 상관없이 특정 사건이 발생하면 지정된 코드가 실행되는 것.

 

package com.eomcs.ioc.ex08;

public class UltraEngine extends Engine {
  public UltraEngine() {
    System.out.println("===> UltraEngine()");
  }

  @Override
  public void run() {
    super.run();
    System.out.println("쌩쌩...달린다....");
  }
}

 

 

IoC 컨테이너 만들기 - 의존 객체 자동 주입
package com.eomcs.ioc.ex08;

public class Exam14 {
  public Exam14() {
    System.out.println("===> Exam14()");
  }

  public static void main(String[] args) throws Exception {
    ApplicationContext9 iocContainer = new ApplicationContext9("step19.ex8");
    Car2 car2 = (Car2) iocContainer.getBean("step19.ex8.Car2");
    car2.move();
  }
}

 

// 객체 생성할 때 의존 객체가 필요하다면 의존 객체를 생성하여 자동 주입시킨다.
package com.eomcs.ioc.ex08;

import java.io.File;
import java.io.FileFilter;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Set;

public class ApplicationContext9 {

  private HashMap<String,Object> objPool = new HashMap<>();

  public ApplicationContext9(String packageName) throws Exception {
    ClassLoader classLoader = ClassLoader.getSystemClassLoader();

    File dir = new File(classLoader.getResource(
        packageName.replace(".", "/")).getPath());
    if (!dir.isDirectory())
      return;

    findAndInstantiateClasses(dir, packageName);
  }

  private void findAndInstantiateClasses(File dir, String packageName) throws Exception {
    File[] files = dir.listFiles(new FileFilter() {
      public boolean accept(File pathname) {
        if (pathname.isDirectory() || // 디렉토리 이거나
            (pathname.getName().endsWith(".class") && // .class 파일인 경우 
                !pathname.getName().contains("$"))) { // 단 중첩클래스는 제외
          return true;
        }
        return false;
      }
    });
    for (File f : files) {
      if (f.isDirectory()) {
        findAndInstantiateClasses(f, packageName + "." + f.getName());
        continue;
      } 

      String classname = f.getName();    
      Class clazz = Class.forName(packageName + "." + 
          classname.substring(0, classname.length() - 6));

      // 이미 해당 타입의 객체가 생성되어 있다면 다시 생성하지 않는다.
      if (objPool.get(clazz.getName()) != null) 
        continue;

      Object obj = createObject(clazz);
      if (obj != null) {
        this.objPool.put(clazz.getName(), obj);
      }
    }
  }

  private Object createObject(Class clazz) throws Exception {
    try {
      // 파라미터가 없는 기본 생성자를 찾는다.
      clazz.getConstructor();
      return clazz.newInstance(); // 기본 생성자를 호출하여 객체를 생성한다.
    } catch (Exception e) {
      Constructor[] constructors = clazz.getConstructors();
      for (Constructor constructor : constructors) {
        Object obj = callConstructor(constructor);
        if (obj != null)
          return obj;
      }
      return null;
    }
  }

  private Object callConstructor(Constructor constructor) throws Exception {
    if (containsDefaultType(constructor))
      return null;

    // 생성자의 파라미터 타입을 알아낸다.
    Class[] paramTypes = constructor.getParameterTypes();

    // 파라미터 타입에 해당하는 값을 보관할 저장소
    ArrayList<Object> paramValues = new ArrayList<>();
    for (Class paramType : paramTypes) {
      paramValues.add(findObject(paramType)); // 파라미터 값을 준비
    }

    // 파라미터 값이 준비되었기 때문에 
    // 준비한 파라미터 값을 가지고 생성자를 호출하여 인스턴스를 만들어 리턴한다.
    return constructor.newInstance(paramValues.toArray());
  }

  private Object findObject(Class clazz) throws Exception {
    Object obj = objPool.get(clazz.getName());
    if (obj != null) { // 그 클래스 타입과 일치하는 객체가 있다면 그 객체를 리턴,
      return obj;
    } 

    // 만약 objPool에 그런 타입의 객체가 없다면 새로 만들어 리턴,
    obj = clazz.newInstance();
    objPool.put(clazz.getName(), obj);
    return obj;
  }

  private boolean containsDefaultType(Constructor constructor) {
    Class[] defaultTypes = {
        byte.class, short.class, int.class, long.class,
        float.class, double.class, boolean.class, char.class,
        Byte.class, Short.class, Integer.class, Long.class,
        Float.class, Double.class, Boolean.class, Character.class,
        String.class
    };

    //1) 생성자의 파라미터 정보를 꺼낸다.
Class[] paramTypes = constructor.getParameterTypes();

//2) 생성자의 파라미터 타입이 primitive 타입이거나 Wrapper, String 일 경우
//   이 생성자를 호출할 때 해당 값을 줘서 호출해야 한다.
//   문제는 어떤 값을 줘야 하는지, 예를 들어 int를 원한다면 
//   int 값으로 얼마를 줘야 하는지 여기 결정할 수 없다.
//   그래서 이런 생성자로는 객체를 생성할 수 없다.
//   이런 생성자인지 검사한다. 
for (Class paramType : paramTypes) {
  for (Class defaultType : defaultTypes) {
    if (paramType == defaultType)
      return true;
  }
}
return false;
  }

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

 

 

IoC 컨테이너 만들기 - 애노테이션을 사용하여 객체의 이름 지정하기
package com.eomcs.ioc.ex09;

public class Exam15 {
  public Exam15() {
    System.out.println("===> Exam15()");
  }

  public static void main(String[] args) throws Exception {
    ApplicationContext10 iocContainer = new ApplicationContext10("step19.ex9");
    Car car = (Car) iocContainer.getBean("car");
    car.move();
  }
}

 

package com.eomcs.ioc.ex09;

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

@Retention(RetentionPolicy.RUNTIME)
public @interface Component {
  String value() default "";
}

 

// 의존 객체 Engine을 자체적으로 만들지 않고 외부에서 주입 받는다.
package com.eomcs.ioc.ex09;

@Component(value="car")
public class Car {
  Engine engine;

  public Car(Engine engine) {
    System.out.println("===> Car(Engine)");
    this.engine = engine;
  }

  public void move() {
    this.engine.run();
  }
}

 

package com.eomcs.ioc.ex09;

@Component
public class Engine {

  public Engine() {
    System.out.println("===> Engine()");
  }

  public void run() {
    System.out.println("엔진을 가동한다.");
  }
}

 

package com.eomcs.ioc.ex09;

public class UltraEngine extends Engine {
  public UltraEngine() {
    System.out.println("===> UltraEngine()");
  }

  @Override
  public void run() {
    super.run();
    System.out.println("쌩쌩...달린다....");
  }
}

 

// @Component 애노테이션이 붙은 클래스만 객체를 생성한다.
package com.eomcs.ioc.ex09;

import java.io.File;
import java.io.FileFilter;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Set;

public class ApplicationContext10 {

  private HashMap<String,Object> objPool = new HashMap<>();

  public ApplicationContext10(String packageName) throws Exception {
    ClassLoader classLoader = ClassLoader.getSystemClassLoader();

    File dir = new File(classLoader.getResource(
        packageName.replace(".", "/")).getPath());
    if (!dir.isDirectory())
      return;

    findAndInstantiateClasses(dir, packageName);
  }

  private void findAndInstantiateClasses(File dir, String packageName) throws Exception {
    File[] files = dir.listFiles(new FileFilter() {
      public boolean accept(File pathname) {
        if (pathname.isDirectory() || // 디렉토리 이거나
            (pathname.getName().endsWith(".class") && // .class 파일인 경우 
                !pathname.getName().contains("$"))) { // 단 중첩클래스는 제외
          return true;
        }
        return false;
      }
    });
    for (File f : files) {
      if (f.isDirectory()) {
        findAndInstantiateClasses(f, packageName + "." + f.getName());
        continue;
      } 

      String classname = f.getName();    
      Class clazz = Class.forName(packageName + "." + 
          classname.substring(0, classname.length() - 6));

      // 이미 해당 타입의 객체가 생성되어 있다면 다시 생성하지 않는다.
      if (objPool.get(clazz.getName()) != null) 
        continue;

      Object obj = createObject(clazz);
      if (obj != null) {
        this.objPool.put(getComponentName(clazz), obj);
      }
    }
  }

  private String getComponentName(Class clazz) throws Exception {
    Component anno = (Component) clazz.getAnnotation(Component.class);
    String label = anno.value();
    if (label.length() == 0)
      return clazz.getName();
    return label;
  }

  private Object createObject(Class clazz) throws Exception {

    if (!isComponent(clazz))
      return null;

    try {
      // 파라미터가 없는 기본 생성자를 찾는다.
      clazz.getConstructor();
      return clazz.newInstance(); // 기본 생성자를 호출하여 객체를 생성한다.
    } catch (Exception e) {
      Constructor[] constructors = clazz.getConstructors();
      for (Constructor constructor : constructors) {
        Object obj = callConstructor(constructor);
        if (obj != null)
          return obj;
      }
      return null;
    }
  }

  private boolean isComponent(Class clazz) throws Exception {
    // 애노테이션의 타입을 지정하여 해당 클래스에서 @Component 애노테이션 정보를 추출한다.
    Component anno = (Component) clazz.getAnnotation(Component.class);
    if (anno == null)
      return false;
    return true;
  }

  private Object callConstructor(Constructor constructor) throws Exception {
    if (containsDefaultType(constructor))
      return null;

    // 생성자의 파라미터 타입을 알아낸다.
    Class[] paramTypes = constructor.getParameterTypes();

    // 파라미터 타입에 해당하는 값을 보관할 저장소
    ArrayList<Object> paramValues = new ArrayList<>();
    for (Class paramType : paramTypes) {
      paramValues.add(findObject(paramType)); // 파라미터 값을 준비
    }

    // 파라미터 값이 준비되었기 때문에 
    // 준비한 파라미터 값을 가지고 생성자를 호출하여 인스턴스를 만들어 리턴한다.
    return constructor.newInstance(paramValues.toArray());
  }

  private Object findObject(Class clazz) throws Exception {
    Object obj = objPool.get(clazz.getName());
    if (obj != null) { // 그 클래스 타입과 일치하는 객체가 있다면 그 객체를 리턴,
      return obj;
    } 

    // 만약 objPool에 그런 타입의 객체가 없다면 새로 만들어 리턴,
    obj = clazz.newInstance();
    objPool.put(clazz.getName(), obj);
    return obj;
  }

  private boolean containsDefaultType(Constructor constructor) {
    Class[] defaultTypes = {
        byte.class, short.class, int.class, long.class,
        float.class, double.class, boolean.class, char.class,
        Byte.class, Short.class, Integer.class, Long.class,
        Float.class, Double.class, Boolean.class, Character.class,
        String.class
    };

    //1) 생성자의 파라미터 정보를 꺼낸다.
Class[] paramTypes = constructor.getParameterTypes();

//2) 생성자의 파라미터 타입이 primitive 타입이거나 Wrapper, String 일 경우
//   이 생성자를 호출할 때 해당 값을 줘서 호출해야 한다.
//   문제는 어떤 값을 줘야 하는지, 예를 들어 int를 원한다면 
//   int 값으로 얼마를 줘야 하는지 여기 결정할 수 없다.
//   그래서 이런 생성자로는 객체를 생성할 수 없다.
//   이런 생성자인지 검사한다. 
for (Class paramType : paramTypes) {
  for (Class defaultType : defaultTypes) {
    if (paramType == defaultType)
      return true;
  }
}
return false;
  }

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