본문 바로가기
👨‍💻 2. 웹개발_Back end/2-4 JSP & Servlet

[JSP & Servlet] 6장 미니 MVC 프레임워크 만들기 (6) 애노테이션을 이용한 객체 관리

by 달님🌙 2021. 10. 19.
반응형

 

6.6절 애노테이션을 이용한 객체 관리

 

1. 기존 방식 -> 프로퍼티를 이용한 방식

 

 

2. 기존 방식의 문제점

- DAO 또는 페이지 컨트롤러를 추가할 때 마다 프로퍼티 파일을 변경해야 함.

 

3. 해결 방안

 

- 애노테이션을 이용하여 클래스 파일에 객체 정보를 담는다.

- DAO 객체 이름을 클래스 파일에 보관

- 페이지 컨트롤러의 서블릿 경로 정보를 클래스 파일에 보관

- DAO 객체나 페이지 컨트롤러를 추가할 때도 단지 애노테이션만 클래스에 선언해 주면 된다.

 

4. 실습 시나리오

 

1단계. 객체 이름을 보관할 애노테이션 정의

 

2단계. DAO 및 페이지 컨트롤러에 애노테이션 적용

 

 

3단계. 프로퍼티 파일변경

 

 

4단계. ApplicationContext 클래스 변경

 

- 애노테이션 정보 추출 : 페이지 컨트롤러는 클래스 파일에서 애노테이션 정보를 추출한다.

- 애노테이션 정보를 바탕으로 객체를 생성한다.

 

5. 참고할것

- 클래스 파일에서 애노테이션 정보를 추출할 때 Reflections 오픈 소스 라이브러리 사용

 

6. 애노테이션 실습 ( _30_MVC_Annotation )

 

1) lib 폴더에 라이브러리 추가

 

2) annotation 패키지 생성

 

3) component 클래스 생성

package spms.annotation;

import java.lang.annotation.*;

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

 

4) 모든 컨트롤러에 아래 코드 추가 : 컴포넌트 어노테이션으로 명시한다.

import spms.annotation.Component;

@Component("/member/add.do")

- 그렇게 되면 memberDAO와 pageController를 따로 만들어줄 필요가 없게 됨

 

5) application-context.properties #2, #3 코드 지우기 (4번에서 컴포넌트 어노테이션으로 명시했기때문)

#1) dataSource
jndi.dataSource=java:comp/env/jdbc/studydb
#2) MemberDAO
memberDAO=spms.dao.MySqlMemberDAO
#3) pageController
/auth/login.do=spms.controller.LoginController 
/auth/logout.do=spms.controller.LogoutController
/member/list.do=spms.controller.MemberListController
/member/add.do=spms.controller.MemberAddController
/member/delete.do=spms.controller.MemberDeleteController
/member/update.do=spms.controller.MemberUpdateController

지운 후..

#1) dataSource
jndi.dataSource=java:comp/env/jdbc/studydb

 

6) ApplicationContext.java

package spms.context;

import java.io.FileReader;
import java.lang.reflect.Method;
import java.util.Hashtable;
import java.util.Properties;
import java.util.Set;

import javax.naming.Context;
import javax.naming.InitialContext;

import org.reflections.Reflections;

import spms.annotation.Component;

public class ApplicationContext {
	//application-context.properties 파일을 읽어서 key값과 value을 담아줄 Hashtable 생성
	// key : jndi.dataSource, memberDAO, /auth/login.do .....
	// value : java:comp/env/jdbc/studydb, spms.dao.MemberDAO, spms.controller.LoginCotroller...
	Hashtable<String, Object> objTable = new Hashtable<String, Object>();
	
	// key값에 해당하는 객체 반환하는 메소드
	public Object getBean(String key) {
		return objTable.get(key);
	}
	
	//생성자는 파일경로(application-context.properties)를 읽어서 프로퍼티를 생성해주는 역할
	public ApplicationContext(String propertiesPath) throws Exception {
		Properties props = new Properties();
		props.load(new FileReader(propertiesPath));
		
		//프로퍼티 파일을 읽어서 객체를 만들어 주는 메소드
		prepareObject(props);
		//어노테이션으로 선언된 객체 생성
		prepareAnnotationObject();
		//의존성 주입 메소드
		injectDependency();
	}
	
	public void prepareObject(Properties props) throws Exception {
		Context ctx = new InitialContext();
		String key = null;
		String value = null;
		
		//props.keySet : jndi.dataSource, memberDAO, /auth/login.do .....
		for(Object item : props.keySet()) {
			key = (String)item;
			//props.getProperty : java:comp/env/jdbc/studydb, spms.dao.MemberDAO, spms.controller.LoginCotroller...
			value = props.getProperty(key);
			
			if(key.startsWith("jndi.")) {
				//key 값이 dataSource면 lookup
				objTable.put(key, ctx.lookup(value));
			} else {
				//dataSource 외에 객체들은 직접 객체 생성
				objTable.put(key, Class.forName(value).newInstance());
			}
		}
	}
	
	public void prepareAnnotationObject() throws Exception {
		//리플렉션 라이브러리 : 리플렉션API보다 더 쉽게 클래스나 메소드를 찾을 수 있다.
		//new Reflections("패키지명") : 해당 패키지의 하위를 모두 찾는다.
		//new Reflections("spms") : spms 패키지의 하위를 모두 찾는다.
		//new Reflections("spms.controller") : spms.comtroller 패키지의 하위를 모두 찾는다.
		//new Reflections("") : classpath 하위를 모두 찾는다(컴파일된 클래스를 모두 검사)
		Reflections reflector = new Reflections("");
		
		//@Component 어노테이션을 가진 type 추출하도록 설정
		Set<Class<?>> list = reflector.getTypesAnnotatedWith(Component.class);
		String key = null;
		for(Class<?> clazz : list) {
			key = clazz.getAnnotation(Component.class).value();
			objTable.put(key, clazz.newInstance());
		}
	}
	
	public void injectDependency() throws Exception {
		for(String key : objTable.keySet()) {
			//dataSource가 아니면 setter메소드를 찾아서 DAO를 객체를 주입
			if(!key.startsWith("jndi.")) {
				//매개변수로는 key값에 해당하는 객체를 보내줌
				callSetter(objTable.get(key));
			}
		}
	}
	
	//setter메소드를 찾아서 실행해줌
	private void callSetter(Object obj) throws Exception {
		Object dependency = null;
		//객체에 있는 모든 메소드들을 호출해서 그 중에 setter메소드를 찾음
		for(Method m : obj.getClass().getMethods()) {
			if(m.getName().startsWith("set")) {
				//setter메소드를 찾아서 setter 메소드의 매개변수 타입으로 객체를 찾음
				//dependeny = MySqlMemberDAO
				dependency = findObjectByType(m.getParameterTypes()[0]);
				if(dependency != null) {
					m.invoke(obj, dependency);
				}
			}
		}
	}
	
	private Object findObjectByType(Class<?> type) {
		for(Object obj : objTable.values()) {
			if(type.isInstance(obj)) {
				return obj;
			}
		}
		return null;
	}
}

 

7) 실행 화면

- 로그인, 로그아웃, 회원등록, 회원수정, 회원삭제 잘 되는지 확인

 

반응형

댓글