[Spring] Spring DI 흉내내기

Jeini·2023년 5월 25일
0

🍃  Spring

목록 보기
15/33
post-thumbnail

💡 다형성 | factory method


✔️ 우리는 어떻게 하면 프로그램의 변경을 최소화 할까를 고민해야 한다.
➡️ 변경이 유리하게 만들기 위해 = OOP

✏️ 1. 변경에 유리한 코드

class Car {}
class SportCar extends Car {}
class Truck extnds Car {}

  • 오른쪽 예제의 메서드는 기능을 담당하는 메서드이고 car를 정의하는 식은 사용하는 코드이다. 저 메서드가 getCar()SportCar() 가 바껴도 Car car = get Car(); 에는 영향을 받지 않는다.

  • 기능을 제공하는 코드는 하나만 있으면 되는데, 사용하는 코드(car 정의)는 여러개일 수가 있으며 여러 군데에서 사용된다. 그래서 왼쪽 식을 보면, 사용하는 코드를 계속 바꿔줘야 해서 번거롭다. 반대로 오른쪽 식은 별도의 메서드를 통해서 객체를 반환하게 바꾸면, 수많은 getCar() 를 넣은 car 는 고치지 않아도 된다.

💡 Properties 활용


✏️ 2. 변경에 유리한 코드

  • Properties 클래스를 이용해서 config.txt 를 읽어온다. 그 내용이 있는 객체를 생성해서 반환한다.

  • Properties ➡️ key:values
    : Properties를 사용하는 이유는 데이터를 가져오거나 저장할 때 편리하게 되어있다. (p.load)

✏️ config.txt

car = kr.ac.jipark09.Truck
engine = kr.ac.jipark09.TurboEngine

  • 코드를 변경하지 않고 프로퍼티만 변경하면 된다.
    : 컴파일을 다시 하지 않아도 되고 테스트도 하지 않아도 된다.

  • spring도 다 이런식으로 되어있다.
    : 텍스트 파일대신 XML or Java 코드를 이용해서 정보를 제공하면 프레임이 정보를 이용해서 동작하게 되어있다. ➡️ 변경 포인트를 최대한 분리

❗️ 변하는 것과 변하지 않는 것 분리 ➡️ 지금하고 있는 것!
❗️ 관심사의 분리
❗️ 중복 코드 분리 = AOP

✏️ Properties를 활용하여 객체로 만들고 Map에 담아서 출력

package kr.ac.jipark09.diCopy1;

import java.io.FileNotFoundException;
import java.io.FileReader;
import java.util.HashMap;
import java.util.Properties;
import java.util.Set;

class Car {}
class SportsCar extends Car {}
class Truck extends Car {}
class Engine {}

public class Main1 {
    public static HashMap<String, Object> cmds;

    public static void main(String[] args) throws Exception {
        getMap();
        System.out.println(cmds);
//        Car car = (Car)getObject("car");
//        Engine engine = (Engine) getObject("engine");
//        System.out.println("car=" + car);
//        System.out.println("engine=" + engine);
    }

    static Object getObject(String key) throws Exception {
        // properties를 불러와서 value를 객체를 만든다.
        Properties properties = new Properties();
        properties.load(new FileReader("config.txt"));

        Class clazz = Class.forName(properties.getProperty(key));
        return clazz.newInstance();
    }

    static void getMap() throws Exception {
        cmds = new HashMap<>();
        Properties properties = new Properties();
        properties.load(new FileReader("config.txt"));
        Set<Object> keys = properties.keySet();

        for(Object key : keys) {
            String strKey = key.toString();
            Class clazz = Class.forName(properties.getProperty(strKey));
            cmds.put(strKey, clazz.newInstance());
        }
    }


}
[결과값]

{car=class kr.ac.jipark09.diCopy1.Truck, engine=class kr.ac.jipark09.diCopy1.Engine}

💡 객체 컨테이너(ApplicationContext) 만들기


✔️ ApplicationContext = 객체 저장소

✏️ 바꾸기 전

✏️ 바꾼 후


  • 클래스 안에 Map을 만들어 준다.

  • getObject ➡️ getBean
    : javaBean은 규칙을 지켜야 하는 제약이 있다.

  • 직접 new SportsCar() 를 넣어주면 하드코딩이 되어버린다. 즉, 변하는 부분이 생기면 직접적으로 계속 바꿔줘야 된다.
    ➡️ Properties를 이용하여 유연하게 처리

✏️ AppContext()의 메서드 부분을 분리시킨다.

  • Properties를 이용해서 분리를 하였다.

✏️ ApplicationContext에 map을 만들어서 저장

package kr.ac.jipark09.diCopy2;

import java.io.FileReader;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

class Car {}
class SportsCar extends Car {}
class Truck extends Car {}
class Engine {}

class AppContext {
    Map map;

    public AppContext() throws Exception {
        Properties properties = new Properties();
        properties.load(new FileReader("config.txt"));
        map = new HashMap(properties);

        for(Object key : map.keySet()) {
            Class clazz = Class.forName((String) map.get(key));
            map.put(key, clazz.newInstance());
        }
    }

    public Object getBean(String key) {
        return map.get(key);
    }
}

public class Main2 {

    public static void main(String[] args) throws Exception {
        AppContext appContext = new AppContext();
        Car car = (Car)appContext.getBean("car");
        Engine engine = (Engine)appContext.getBean("engine");
        System.out.println("car=" + car);
        System.out.println("engine=" + engine);
    }

}
[결과값]

car=kr.ac.jipark09.diCopy2.Truck@6574b225
engine=kr.ac.jipark09.diCopy2.Engine@2669b199

💡 Component Scanning | 자동 객체 등록하기


✔️ @Component 어노테이션을 이용해서 객체 저장소에 저장한 빈들을 지정
: guava 라이브러리

❗️ guava 라이브러리
: 자바 reflaction을 좀 더 쉽게 사용할 수 있도록 도와주는 라이브러리

  • classInfo.getSimpleName() 으로 클래스의 대문자를 소문자로 변환시켜준다.
    ➡️ key로 사용

  • config.txt 처럼 외부 파일을 이용해서 객체를 등록하기도 하지만 클래스 앞에 어노테이션을 이용해서 객체를 등록하는 방법도 있다.
    : 외부 파일을 이용하면 새로운 객체를 등록할 때마다 고쳐야 되는데 여러사람이 편집할 때에는 쉽지않다. 그래서 공통으로 사용 할 객체를 적어놓고 어노테이션을 사용하면 편집하기 쉬워진다.

  • 2가지 방법을 적절히 실행

⚙️ guava 라이브러리 가져오기

<!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>31.1-jre</version>
</dependency>

⚒️ config.xml: <context:component-scan>

   <context:component-scan base-package="kr.ac.jipark09">
      <context:exclude-filter type="regex" expression="kr.ac.jipark09.diCopy*.*"/> // 해당하는 패키지만 가져오기
   </context:component-scan>

✏️ Component Scanning 실습

✏️ pom.xml에다 넣어주기

  • 넣은 다음 pom.xml 우클릭 Maven ➡️ Reload Project 해 주기

  • 라이브러리에 잘 들어간 것을 확인할 수 있음

✏️ @Component 어노테이션을 이용해서 객체 저장소에 저장한 빈들을 지정

package kr.ac.jipark09.diCopy3;

import com.google.common.reflect.ClassPath;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.io.FileReader;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

@Component class Car {}
@Component class SportsCar extends Car {}
@Component class Truck extends Car {}
@Component class Engine {}

class AppContext {
    Map map;

    public AppContext() throws Exception {
        map = new HashMap();
        doComponentScan();
    }

    private void doComponentScan() throws Exception {
        // 1. 패키지내의 클래스 목록을 가져온다.
        // 2. 반복문으로 클래스를 하나씩 읽어와서 @Component가 붙어 있는지 확인
        // 3. @Component가 붙어있으면 객체를 생성해서 map에 저장

        ClassLoader classLoader = AppContext.class.getClassLoader();
        ClassPath classPath = ClassPath.from(classLoader);

        Set<ClassPath.ClassInfo> set = classPath.getTopLevelClasses("kr.ac.jipark09.diCopy3");

        for(ClassPath.ClassInfo classInfo : set) {
            Class clazz = classInfo.load();
            Component component =  (Component) clazz.getAnnotation(Component.class);

            if(component != null) {
                String id = StringUtils.uncapitalize(classInfo.getSimpleName());
                map.put(id, clazz.newInstance());
            }
        }
    }

    public Object getBean(String key) {
        return map.get(key);
    }
}

public class Main3 {

    public static void main(String[] args) throws Exception {
        AppContext appContext = new AppContext();
        Car car = (Car)appContext.getBean("car");
        Engine engine = (Engine)appContext.getBean("engine");
        System.out.println("car=" + car);
        System.out.println("engine=" + engine);
    }

}

✏️ 자바빈으로 등록된 클래스들

✏️ 결과 값

car=kr.ac.jipark09.diCopy3.Car@74ad1f1f
engine=kr.ac.jipark09.diCopy3.Engine@6a1aab78
  • Engine에 @Component를 제거하고 실행해 보면!
car=kr.ac.jipark09.diCopy3.Car@491cc5c9
engine=null
  • null 값이 뜬 것을 확인할 수 있다.

💡 by Name, by Type | 객체 찾기


  • 타입으로 찾을 때 instanceof 를 통해 Car 타입이 맞는지 아닌지 알아 볼 수 있다.
    : value를 찾겠다는 말
    : 참이면 객체 반환

✏️ by Name, by Type 예제

 // ByName으로 찾기
    public Object getBean(String key) {
        return map.get(key);
    }
    // ByType으로 찾기
    public Object getBean(Class clazz) {
        for (Object obj : map.values()) {
            // obj가 클래스의 객체거나 자손이면 true 반환
            if(clazz.isInstance(obj)) {
                return obj;
            }
        }
        return null;
    }
    
// 결과 보기    
public class Main3 {

    public static void main(String[] args) throws Exception {
        AppContext appContext = new AppContext();
        Car car = (Car)appContext.getBean("car");
        Car car1 = (Car)appContext.getBean(Car.class);
        Engine engine = (Engine)appContext.getBean("engine");
        System.out.println("car=" + car);
        System.out.println("car1=" + car1);
        System.out.println("engine=" + engine);
    }
}

💡 @Autowired: 객체 자동 연결 1


✔️ 스프링이 관리하는 빈(Bean)을 자동으로 주입해주는 어노테이션1
✔️ By Type으로 값을 찾아서 연결

✏️ @Autowired 사용하기 전: 수동으로 객체 참조 연결

  • 참조변수를 수동으로 연결 함
    ➡️ 이렇게 안하고 자동으로 할 수 있는 방법이 있다.

✏️ @Autowired 사용하기

  • 참조변수를 자동으로 연결 함
    : map에 있는 객체들 중에 찾아서 자동 연결 해준다.
    : ByType으로 value를 뒤져서 해당 타입에 맞는 값을 찾는다. 찾으면 참조변수에다가 대입해 준다.
    : instanceof 로 찾음

✏️ @Autowired를 사용하여 객체 자동 연결

package kr.ac.jipark09.diCopy4;

import com.google.common.reflect.ClassPath;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

@Component class Car {
    @Autowired Engine engine;
    @Autowired Door door;

    @Override
    public String toString() {
        return "Car{" +
                "engine=" + engine +
                ", door=" + door +
                '}';
    }
}
@Component class SportsCar extends Car {}
@Component class Truck extends Car {}
@Component class Engine {}
@Component class Door {}

class AppContext {
    Map map;

    public AppContext() throws Exception {
        map = new HashMap();
        doComponentScan();
        doAutowired();
    }

    private void doAutowired() {
        // map에 저장된 객체의 iv중에 @Autowired가 붙어 있으면
        // map에서 iv의 타입에 맞는 객체를 찾아서 연결(객체의 주소를 iv에 저장)
        try {
            for(Object bean : map.values()) {
                for(Field field : bean.getClass().getDeclaredFields()) {
                    if(field.getAnnotation(Autowired.class) != null) {
                        field.set(bean, getBean(field.getType())); // car.engine = obj; 타입을 찾아서 연결

                    }
                }
            }
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }

    private void doComponentScan() throws Exception {
        // 1. 패키지내의 클래스 목록을 가져온다.
        // 2. 반복문으로 클래스를 하나씩 읽어와서 @Component가 붙어 있는지 확인
        // 3. @Component가 붙어있으면 객체를 생성해서 map에 저장

        ClassLoader classLoader = AppContext.class.getClassLoader();
        ClassPath classPath = ClassPath.from(classLoader);

        Set<ClassPath.ClassInfo> set = classPath.getTopLevelClasses("kr.ac.jipark09.diCopy4");

        for(ClassPath.ClassInfo classInfo : set) {
            Class clazz = classInfo.load();
            Component component =  (Component) clazz.getAnnotation(Component.class);

            if(component != null) {
                String id = StringUtils.uncapitalize(classInfo.getSimpleName());
                map.put(id, clazz.newInstance());
            }
        }
    }

    // ByName으로 찾기
    public Object getBean(String key) {
        return map.get(key);
    }
    // ByType으로 찾기
    public Object getBean(Class clazz) {
        for (Object obj : map.values()) {
            // obj가 클래스의 객체거나 자손이면 true 반환해서 obj에 대입
            if(clazz.isInstance(obj)) {
                return obj;
            }
        }
        return null;
    }
}

public class Main4 {

    public static void main(String[] args) throws Exception {
        AppContext appContext = new AppContext();
        Car car = (Car)appContext.getBean("car");
        Door door = (Door) appContext.getBean(Door.class);
        Engine engine = (Engine)appContext.getBean("engine");


        // 수동으로 객체 연결
//        car.engine = engine;
//        car.door = door;

        System.out.println("car=" + car);
        System.out.println("engine=" + engine);
        System.out.println("door=" + door);

    }
}

💡 @Resource: 객체 자동 연결 2


✔️ 스프링이 관리하는 빈(Bean)을 자동으로 주입해주는 어노테이션 2
✔️ By Name으로 값을 찾아서 연결

  • ByName으로 key를 뒤져서 해당 타입에 맞는 값을 찾는다.

  • @Resource 이름을 우리가 직접 지정해 줄 수도 있다.

  • 직접 지정하기 전에는 앞글자를 대문자에서 소문자로 바꾼 name을 사용한다.

✏️ @Resource를 사용하여 객체 자동 연결

package kr.ac.jipark09.diCopy4;

import com.google.common.reflect.ClassPath;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

@Component class Car {
    @Resource
    Engine engine;
    @Autowired Door door;

    @Override
    public String toString() {
        return "Car{" +
                "engine=" + engine +
                ", door=" + door +
                '}';
    }
}
@Component class SportsCar extends Car {}
@Component class Truck extends Car {}
@Component class Engine {}
@Component class Door {}

class AppContext {
    Map map;

    public AppContext() throws Exception {
        map = new HashMap();
        doComponentScan();
        doResource();
    }

    private void doResource() {
        // map에 저장된 객체의 iv중에 @Resource가 붙어 있으면
        // map에서 iv의 이름에 맞는 객체를 찾아서 연결(객체의 주소를 iv에 저장)
        try {
            for(Object bean : map.values()) {
                for(Field field : bean.getClass().getDeclaredFields()) {
                    if(field.getAnnotation(Resource.class) != null) {
                        field.set(bean, getBean(field.getName())); // car.engine = obj; 타입을 찾아서 연결

                    }
                }
            }
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }

    private void doComponentScan() throws Exception {
        // 1. 패키지내의 클래스 목록을 가져온다.
        // 2. 반복문으로 클래스를 하나씩 읽어와서 @Component가 붙어 있는지 확인
        // 3. @Component가 붙어있으면 객체를 생성해서 map에 저장

        ClassLoader classLoader = AppContext.class.getClassLoader();
        ClassPath classPath = ClassPath.from(classLoader);

        Set<ClassPath.ClassInfo> set = classPath.getTopLevelClasses("kr.ac.jipark09.diCopy4");

        for(ClassPath.ClassInfo classInfo : set) {
            Class clazz = classInfo.load();
            Component component =  (Component) clazz.getAnnotation(Component.class);

            if(component != null) {
                String id = StringUtils.uncapitalize(classInfo.getSimpleName());
                map.put(id, clazz.newInstance());
            }
        }
    }

    // ByName으로 찾기
    public Object getBean(String key) {
        return map.get(key);
    }
    // ByType으로 찾기
    public Object getBean(Class clazz) {
        for (Object obj : map.values()) {
            // obj가 클래스의 객체거나 자손이면 true 반환해서 obj에 대입
            if(clazz.isInstance(obj)) {
                return obj;
            }
        }
        return null;
    }
}

public class Main4 {

    public static void main(String[] args) throws Exception {
        AppContext appContext = new AppContext();
        Car car = (Car)appContext.getBean("car");
        Door door = (Door) appContext.getBean(Door.class);
        Engine engine = (Engine)appContext.getBean("engine");


        // 수동으로 객체 연결
//        car.engine = engine;
//        car.door = door;

        System.out.println("car=" + car);
        System.out.println("engine=" + engine);
        System.out.println("door=" + door);

    }
}

Reference
: https://fastcampus.co.kr/dev_academy_nks

profile
Fill in my own colorful colors🎨

0개의 댓글