클래스 맴버 필드(변수)에 어노테이션(Annotation)를 적용하여 외부에서 클래스 맴버 필드 타입에 해당되는 클래스를 찾고 생성된 인스턴스를 주입하는 방법(디펜던시 인젝션)에 대해 알아보겠습니다.
의존성 주입(Dependency Injection)을 위해서 클래스 맴버 필드 타입은 클래스가 아닌 인터페이스(interface)로 합니다. 그 이유는 인터페이스(interface)로 구현(implements)한 다양한 클래스들을 외부에서 생성하여 주입할 수 있기 때문입니다. 의존성 주입으로 클래스 간의 의존 관계를 낮출 수 있습니다.
의존성 주입(Dependency Injection)을 위해 클래스 맴버 필드와 인터페이스(interface)로 구현된 클래스에 어노테이션(Annotation)를 생성하여 적용합니다.
"SpringFramework"에서 의존성 주입을 위해 클래스 맴버 필드에 "@Autowired"어노테이션, 서비스 클래스에는 "@Service"어노테이션을 적용하여 처리합니다.
클래스 맴버 필드에 의존성 주입을 위해 어노테이션(Annotation)으로 생성합니다.
1. "test2" 프로젝트의 "Java Resources"에서 "src/main/java"의 "com.hom.project.test2.annotation"에서 오른쪽 버튼을 클릭하여 컨텍스트 메뉴 [New > Annotation]를 클릭합니다.
"New Annotation Type"창에서 "Name"에 "TestAnnAutowired"를 입력하고 "@Retention"은 "Runtime", "@Target"은 "Field"를 선택하고 "Finish"버튼을 클릭합니다.
"@TestAnnAutowired"어노테이션은 클래스 맴버 필드가 의존성 주입 대상으로 인식하기 위한 것으로 추가 정보인 엘리먼트(element)가 필요하지 않습니다.
인터페이스(interface)로 구현(implements)된 클래스를 찾아 인스턴스를 생성하기 위해 어노테이션(Annotation)으로 생성합니다.
2. "test2" 프로젝트의 "Java Resources"에서 "src/main/java"의 "com.hom.project.test2.annotation"에서 오른쪽 버튼을 클릭하여 컨텍스트 메뉴 [New > Annotation]를 클릭합니다.
"New Annotation Type"창에서 "Name"에 "TestAnnAutowired"를 입력하고 "@Retention"은 "Runtime", "@Target"은 "Type"를 선택하고 "Finish"버튼을 클릭합니다.
"@TestAnnService"어노테이션은 인터페이스(interface)로 구현(implements)된 클래스로 인식하기 위한 것으로 추가 정보인 엘리먼트(element)가 필요하지 않습니다.
클래스 맴버 필드에 어노테이션(Annotation)을 추가합니다.
3. "TestRootController.java"의 클래스 맴버 필드 위에 "@TestAnnAutowired"어노테이션을 추가합니다.
/**
* ITestService1 인터페이스 객체
*/
@TestAnnAutowired
ITestService1 testService = null;
의존성 주입을 위해 생성자에 있는 "TestService1"의 인스턴스 생성과 형 변환을 주석처리합니다.
/**
* TestRootController 생성자
*/
public TestRootController() {
//TestService1 testService1 = new TestService1();
//testService = (ITestService1)testService1;
}
인터페이스(interface)로 구현(implements)된 클래스를 찾아 인스턴스를 생성하기 위해 어노테이션(Annotation)을 추가합니다.
4. "TestService1.java"클래스 위에 "@TestAnnService"어노테이션을 추가합니다.
컨트롤러(Controller) 클래스보다 의존성 주입을 위해 "@TestAnnService"어노테이션이 적용된 서비스 클래스를 먼저 스캔하여 인스턴스를 생성시킵니다.
5. "TestServletContextListener.java"의 "contextInitialized"에 의존성 주입을 위해 서비스매핑맵(serviceMappingMap)을 추가합니다.
// Service-Class 맵
Map<String, Object> serviceMappingMap = new HashMap<String, Object>();
"@TestAnnService"어노테이션이 적용된 서비스 클래스를 스캔하여 인스턴스를 생성하고 서비스 클래스의 인터페이스(interface)와 생성된 인스턴스를 서비스매핑맵(serviceMappingMap)에 추가합니다.
for (Class<?> findClass : classes) {
TestAnnService annoService = findClass.getAnnotation(TestAnnService.class);
if (null != annoService) {
Object serviceInstance = null;
try {
serviceInstance = findClass.newInstance();
Class<?>[] interfaces = findClass.getInterfaces();
for (int i = 0; i < interfaces.length; ++i) {
serviceMappingMap.put(interfaces[i].getName(), serviceInstance);
System.out.println("[Service] " + findClass.getName() + "서비스 인스턴스를 생성함");
}
} catch (InstantiationException | IllegalAccessException e) {
//e.printStackTrace();
System.out.println(findClass.getName() + "의 서비스 인스턴스를 생성하지 못함");
}
}
}
인터페이스(interface)로 구현(implements)된 클래스는 반드시 하나만 있어야 합니다. 구현된 클래스가 여러개 있을 경우가 어떤 클래스로 의존성 주입을 할지 알 수 없기 때문입니다. "SpringFramework"에서도 구현된 클래스는 하나로 제한되어 있습니다.
6. "Servers"탭에서 "tomcat8"를 선택하고 "start"버튼(start the server)을 클릭합니다.
"Console"탭을 클릭하면 "@TestAnnService"어노테이션이 적용된 클래스를 찾아 인스턴스를 생성한 것을 확인할 수 있습니다.
[Console]
[Service] com.home.project.test2.service.TestService1서비스 인스턴스를 생성함
7. "TestServletContextListener.java"의 "contextInitialized"에 클래스에서 선언된 맴버 필드 리스트를 가져와서 "@TestAnnAutowired"어노테이션이 있는지 확인합니다.
Field[] Fields = findClass.getDeclaredFields();
for (Field field : Fields) {
TestAnnAutowired testAnnAutowired = field.getAnnotation(TestAnnAutowired.class);
if (null != testAnnAutowired) {
}
}
맴버 필드의 타입으로 서비스매핑맵(serviceMappingMap)에서 동일한 타입(인터페이스)로 구현(implements)된 클래스를 찾습니다.
Object objectImplement = (Object) serviceMappingMap.get(field.getType().getName());
if (null != objectImplement) {
}
맴버 필드에 접근할 수 있게 설정하고 "set"메소드를 통해 값을 주입(인젝션)합니다.
field.setAccessible(true);
try {
field.set(controllerInstance, objectImplement);
} catch (IllegalArgumentException | IllegalAccessException e) {
e.printStackTrace();
}
전체 소스입니다.
Field[] Fields = findClass.getDeclaredFields();
for (Field field : Fields) {
TestAnnAutowired testAnnAutowired = field.getAnnotation(TestAnnAutowired.class);
if (null != testAnnAutowired) {
System.out.println("[Field] " + field.getType().getName() + " 타입의 " + field.getName() + " 맴버 필드을 찾음");
Object objectImplement = (Object) serviceMappingMap.get(field.getType().getName());
if (null != objectImplement) {
field.setAccessible(true);
try {
field.set(controllerInstance, objectImplement);
System.out.println("[Field] " + field.getName() + "에 의존성 주입을 처리함");
} catch (IllegalArgumentException | IllegalAccessException e) {
e.printStackTrace();
System.out.println("[Field] " + field.getName() + "에 의존성 주입을 처리하지 못함");
}
}
}
}
8. "Servers"탭에서 "tomcat8"를 선택하고 "start"버튼(start the server)을 클릭합니다.
"Console"탭을 클릭하면 "@TestAnnAutowired"어노테이션이 적용된 클래스 맴버 필드를 찾고 서비스매핑맵(serviceMappingMap)에서 찾아 주입한 것을 확인할 수 있습니다.
[Console]
[Field] com.home.project.test2.service.ITestService1 타입의 testService 맴버 필드을 찾음
[Field] testService에 의존성 주입을 처리함
9. 웹 브라우저에서 "http://localhost:8080/test2/test1.do"를 입력합니다.
이처럼 어노테이션을 이용하여 클래스 맴버 필드를 외부에서 주입(디펜던시 인젝션)하여 처리하게되어 클래스 간의 의존 관계를 낮출 수 있었습니다.