4. ContextLoaderListener 변경
ApplicationContext 클래스를 만든 이유는 페이지 컨트롤러나 DAO가 추가되더라도 ContextLoaderListener 클래스를 변경하지 않기 위함이다. 정말 그것이 가능한지 다음 실습을 통해 확인한다.
spms.listeners.ContextLoaderListener 클래스를 다음과 같이 변경한다.
package spms.listeners;
import javax.naming.InitialContext;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
//import javax.servlet.annotation.WebListener;
import javax.servlet.annotation.WebListener;
import javax.sql.DataSource;
import org.apache.commons.dbcp2.BasicDataSource;
import spms.context.ApplicationContext;
import spms.controls.LogInController;
import spms.controls.LogOutController;
import spms.controls.MemberAddController;
import spms.controls.MemberDeleteController;
import spms.controls.MemberListController;
import spms.controls.MemberUpdateController;
import spms.dao.MySqlMemberDao;
//프로퍼티 파일 적용 : ApplicationContext 사용
@WebListener
public class ContextLoaderListener implements ServletContextListener {
static ApplicationContext applicationContext;
//ApplicationContext 객체 얻을 때 사용
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
@Override
public void contextInitialized(ServletContextEvent event) {
try {
//DB 커넥션 객체 준비
ServletContext sc = event.getServletContext();
//InitialContext 객체 생성 - 톰캣 서버에서 자원 찾기 위함
//InitialContext initialContext = new InitialContext();
//lookup() 메서드로 JNDI 이름으로 등록된 서버 자원 찾을 수 있다.
//lookup() 메서드 매개변수는 서버 자원의 JNDI 이름
//DataSource ds = (DataSource)initialContext.lookup("java:comp/env/jdbc/studydb");
//MemberDao 객체 준비하여 ServletContext에 보관
//MemberDao memberDao = new MemberDao();
//MySqlMemberDao memberDao = new MySqlMemberDao();
//memberDao.setDataSource(ds); //DAO에 DataSource 객체 주입
//sc.setAttribute("memberDao", memberDao); //memberDao 객체를 별도로 꺼내서 사용할 일 없기 때문에 ServletContext에 저장하지 않는다.
//페이지 컨트롤러 객체를 ServletContext에 저장시, 서블릿 요청 URL을 키값으로 하여 저장
/*sc.setAttribute("/auth/login.do", new LogInController().setMemberDao(memberDao));
sc.setAttribute("/auth/logout.do", new LogOutController());
sc.setAttribute("/member/list.do", new MemberListController().setMemberDao(memberDao));
sc.setAttribute("/member/add.do", new MemberAddController().setMemberDao(memberDao));
sc.setAttribute("/member/update.do", new MemberUpdateController().setMemberDao(memberDao));
sc.setAttribute("/member/delete.do", new MemberDeleteController().setMemberDao(memberDao));*/
//web.xml 파일로부터 프로퍼티 파일 이름과 경로 정보 읽어옴
String propertiesPath = sc.getRealPath(sc.getInitParameter("contextConfigLocation"));
applicationContext = new ApplicationContext(propertiesPath);
} catch(Throwable e) {
e.printStackTrace();
}
}
@Override
public void contextDestroyed(ServletContextEvent event) {}
}
이전 소스와 비교하면 매우 간결하다. 그리고 정말 중요한 것은 이제 더 이상 이 클래스를 변경할 필요가 없다는 것이다. 페이지 컨트롤러나 DAO 등을 추가할 때는 프로퍼티 파일에 그 클래스에 대한 정보를 한 줄 추가하면 자동으로 그 객체가 생성된다.
- 프로퍼티 파일의 경로
프로퍼티 파일의 이름과 경로 정보도 web.xml 파일로부터 읽어오게 처리하였다. ServletContext 객체의 getInitParameter() 메서드를 호출하여 web.xml에 설정된 매개변수 정보를 가져온다.
//web.xml 파일로부터 프로퍼티 파일 이름과 경로 정보 읽어옴
String propertiesPath = sc.getRealPath(sc.getInitParameter("contextConfigLocation"));
그리고 ApplicationContext 객체를 생성할 때 생성자의 매개변수로 넘겨준다.
applicationContext = new ApplicationContext(propertiesPath);
이렇게 생성한 ApplicationContext 객체는 프런트 컨트롤러에서 사용할 수 있게 ContextLoaderListener 클래스의 클래스 변수 'applicationContext'에 저장된다.
- getApplicationContext() 클래스 메서드
이 메서드는 ContextLoaderListener 클래스에서 만든 ApplicationContext 객체를 얻을 때 사용한다. 당장 프런트 컨트롤러에서 사용해야 한다. 클래스 이름만으로 호출할 수 있게 static으로 선언하였다.
//ApplicationContext 객체 얻을 때 사용
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
5. web.xml 파일에 프로퍼티 경로 정보 설정
ContextLoaderListener 클래스가 프로퍼티 파일을 찾을 수 있도록 web.xml 파일에 프로퍼티에 대한 파일 경로 정보를 설정한다.
다음과 같이 web.xml 파일에 컨텍스트 매개변수를 추가한다.
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
id="WebApp_ID"
version="3.1">
<display-name>web06</display-name>
<!-- 컨텍스트 초기화 파라미터 -->
<!-- 프로퍼티 경로 정보 설정 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/application-context.properties</param-value>
</context-param>
<!-- 웹 애플리케이션에서 톰캣 서버의 자원 사용 -->
<resource-ref>
<res-ref-name>jdbc/studydb</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<res-auth>Container</res-auth>
</resource-ref>
<!-- 컨텍스트 초기화 파라미터 -->
<!--
<context-param>
<param-name>driver</param-name>
<param-value>com.mysql.jdbc.Driver</param-value>
</context-param>
<context-param>
<param-name>url</param-name>
<param-value>jdbc:mysql://localhost/studydb</param-value>
</context-param>
<context-param>
<param-name>username</param-name>
<param-value>study</param-value>
</context-param>
<context-param>
<param-name>password</param-name>
<param-value>study</param-value>
</context-param>
-->
<!-- 필터 선언 -->
<!-- 필터 URL 매핑 -->
<!-- 리스너 선언 -->
<!--
<listener>
<listener-class>spms.listeners.ContextLoaderListener</listener-class>
</listener>
-->
<!-- 서블릿 선언 -->
<!--
<servlet>
<servlet-name>AppInitServlet</servlet-name>
<servlet-class>spms.servlets.AppInitServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
-->
<!-- 서블릿을 URL과 연결 -->
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.jsp</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>default.html</welcome-file>
<welcome-file>default.jsp</welcome-file>
<welcome-file>default.htm</welcome-file>
</welcome-file-list>
</web-app>
6. DispatcherServlet 변경
이제 마지막으로 프런트 컨트롤러를 변경하는 것만 남았다.
spms.servlets.DispatcherServlet 클래스를 다음과 같이 변경한다.
package spms.servlets;
import java.io.IOException;
import java.util.HashMap;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import spms.bind.DataBinding;
import spms.bind.ServletRequestDataBinder;
import spms.context.ApplicationContext;
import spms.controls.Controller;
import spms.controls.LogInController;
import spms.controls.LogOutController;
import spms.controls.MemberAddController;
import spms.controls.MemberDeleteController;
import spms.controls.MemberListController;
import spms.controls.MemberUpdateController;
import spms.listeners.ContextLoaderListener;
import spms.vo.Member;
@SuppressWarnings("serial")
@WebServlet("*.do") //프런트 컨트롤러의 배치
public class DispatcherServlet extends HttpServlet {//서블릿이기 때문에 HttpServlet 상속
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html; charset=UTF-8");
request.setCharacterEncoding("UTF-8");
String servletPath = request.getServletPath(); //서블릿 경로 추출
try {
//ServletContext 객체 준비
//ServletContext sc = this.getServletContext();
//ApplicationContext 객체 준비
ApplicationContext ctx = ContextLoaderListener.getApplicationContext();
//페이지 컨트롤러에게 전달할 Map 객체 준비
HashMap<String,Object> model = new HashMap<String,Object>();
//model.put("memberDao", sc.getAttribute("memberDao")); //memberDao 객체는 더이상 Map 객체에 담을 필요 없음.
model.put("session", request.getSession()); //로그인 및 로그아웃 페이지 컨트롤러에서 사용할 세션 객체
//String pageControllerPath = null;
//인터페이스 타입의 참조 변수 선언 - Controller의 구현체 클래스들 객체 주소 저장 위함
//Controller pageController = null;
//ServletContext 객체에서 페이지 컨트롤러 꺼낼 때 서블릿 URL 사용
//Controller pageController = (Controller)sc.getAttribute(servletPath);
//ApplicationContext 객체에서 페이지 컨트롤러 꺼낼 때 서블릿 URL 사용
Controller pageController = (Controller)ctx.getBean(servletPath);
//찾지 못한다면 오류 발생시킴
if(pageController == null) {
throw new Exception("요청한 서비스를 찾을 수 없습니다.");
}
//DataBinding 구현 여부 검사하여, 해당 인터페이스를 구현한 경우에만 호출
if(pageController instanceof DataBinding) {
prepareRequestData(request, model, (DataBinding)pageController);
}
...
}
}
이전에는 페이지 컨트롤러가 ServletContext 객체에 저장되었기 때문에 이 객체를 준비해야 했지만, ApplicationContext 객체를 도입하면서 필요가 없어졌다. 그래서 제거하였다.
//ServletContext 객체 준비
//ServletContext sc = this.getServletContext();
//ApplicationContext 객체 준비
ApplicationContext ctx = ContextLoaderListener.getApplicationContext();
대신 ContextLoaderListener 클래스의 getApplicationContext() 메서드를 호출하여 ApplicationContext 객체를 꺼낸다.
페이지 컨트롤러를 찾을 때도 ServletContext 객체에서 찾지 않기 때문에 해당 코드를 제거했다.
//ServletContext 객체에서 페이지 컨트롤러 꺼낼 때 서블릿 URL 사용
//Controller pageController = (Controller)sc.getAttribute(servletPath);
//ApplicationContext 객체에서 페이지 컨트롤러 꺼낼 때 서블릿 URL 사용
Controller pageController = (Controller)ctx.getBean(servletPath);
대신 ApplicationContext 객체의 getBean() 메서드를 호출하여 페이지 컨트롤러를 찾는다. 만약 찾지 못한다면 오류를 발생시킨다.
//찾지 못한다면 오류 발생시킴
if(pageController == null) {
throw new Exception("요청한 서비스를 찾을 수 없습니다.");
}
앞에서 생각한 시나리오대로 잘 동작하는지 테스트한다. 톰캣 서버를 재시작하고, 모든 기능을 테스트한다. 이전과 동일하게 잘 동작한다.
만약 오류가 있다면, 누구에게 물어보지 말고 자신이 해결한다. 아무리 해도 안 되겠으면 처음부터 다시 코딩한다. 그것이 프로그래밍 실력을 가장 빠르게 향상시키는 방법이다. 그리고 절대로 눈 코딩하지 않는다. 그냥 쳐다본다고 실력이 늘지 않는다. 자신이 타이핑한 라인 수만큼 실력이 늘 것이다.
참고도서 : https://freelec.co.kr/book/1674/
[열혈강의] 자바 웹 개발 워크북
[열혈강의] 자바 웹 개발 워크북
freelec.co.kr
'교재 실습 > 자바 웹 개발 워크북' 카테고리의 다른 글
93. 어노테이션을 이용한 객체 관리 (2) (0) | 2022.08.14 |
---|---|
92. 어노테이션을 이용한 객체 관리 (1) (0) | 2022.08.13 |
90. 프로퍼티를 이용한 객체 관리 (2) (1) | 2022.08.10 |
89. 프로퍼티를 이용한 객체 관리 (1) (0) | 2022.08.08 |
88. 리플랙션 API를 이용하여 프런트 컨트롤러 개선하기 (4) (1) | 2022.08.06 |
댓글