Spring에서 지원하는 주요 개념인 DI(Dependency Injection)는 의존성 주입의 약어로 인스턴스를 만들지 않더라도 스프링 컨텍스트에 등록된 빈의 인스턴스를 생성해주는 것으로 잘 알려져있다.
실제 DI가 어떤 식으로 구현되어 있고 동작하는지 백기선님의 Java강의를 참고하여 DI를 만들어보며 이해해보자.
우선 Java의 리플렉션 API를 활용하여 개발을 해보고자 한다. 우선 특정 클래스의 인스턴스를 만들고자 한다. 이 인스턴스를 생성할 때 해당 클래스의 필드 중 특정 어노테이션이 달려 있는 경우에 해당 필드의 인스턴스까지 함께 생성하고자 한다.
스프링에서는 @Autowired 어노테이션으로 필드에 의존성을 주입한다. 나는 @Inject라는 어노테이션을 정의하여 이 어노테이션으로 의존성을 주입하고자 한다. 우선 Inject 어노테이션 파일을 만든다.
package study.jaeseok;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface Inject {
}
컨테이너 서비스에서는 인스턴스를 생성해서 반환하는 createInstance() 메소드를 가진다. 이 때 리플렉션 api를 활용하여 기본 생성자를 활용하여 생성한다.
그리고 생성한 인스턴스에 Inject 어노테이션이 붙어있는 필드가 있는지 검사해서 해당 필드의 인스턴스도 생성하는 로직을 구성한다.
package study.jaeseok;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
public class ContainerService {
public static <T> T getObject(Class<T> classType) {
T instance = createInstance(classType);
Arrays.stream(classType.getDeclaredFields())
.forEach(field -> {
Annotation annotation = field.getAnnotation(Inject.class);
if (annotation != null) {
try {
field.setAccessible(true);
field.set(instance, createInstance(field.getType()));
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
});
return instance;
}
private static <T> T createInstance(Class<T> classType) {
try {
return classType.getConstructor(null).newInstance();
} catch (InstantiationException | IllegalAccessException |
InvocationTargetException | NoSuchMethodException e) {
e.printStackTrace();
}
return null;
}
}
그렇다면 의존성 주입이 제대로 이루어졌는지 확인하는지 테스트 코드로 확인해보자. 아래와 같이 코드를 작성하고 BookService에 BookRepository 타입의 필드에 @Inject 어노테이션을 달아놓은 상태이다.
package study.jaeseok;
import org.junit.Assert;
import org.junit.Test;
public class ContainerServiceTest {
@Test
public void getBookService() {
BookService bookService = ContainerService.getObject(BookService.class);
Assert.assertNotNull(bookService);
Assert.assertNotNull(bookService.bookRepository);
}
@Test
public void getBookRepository() {
BookRepository bookRepository = ContainerService.getObject(BookRepository.class);
Assert.assertNotNull(bookRepository);
}
}
테스트 코드 실행 결과 아래와 같이 Tests passed 결과를 확인할 수 있다.