[Spring] 게시판 예제를 통한 Spring 구조 정리
스프링의 기본적인 실행 구조를 간단한 게시판 서비스를 구성하며 순차적으로 알아보자.
1.
우선 프로젝트를 만들고 나서 요청에 따른 controller를 자동 호출하기 위한 DispatcherServlet을 web.xml 문서에 추가해줘야 한다.
◎WEB-INF.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" version="3.1">
<display-name>spring_simple_board</display-name>
<!-- DispatcherServlet 등록 -->
<servlet>
<servlet-name>board</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>board</servlet-name>
<url-pattern>*.do</url-pattern> <!-- 모든 요청사항 뒤에 .do가 붙으면 DispatcherServlet을 사용해! -->
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
이때, servlet의 이름은 dispatcher가 자동으로 찾기 때문에 위와 같이 이름을 작성했다면, servlet.xml은 반드시
board-servlet.xml 과 같이 생성해야 한다.
2.
board-servlet.xml 파일을 생성하고, 해당 문서가 HandlerMapping과 ViewResolver 기능을 수행할 수 있도록 각각 등록해줘야 한다.
◎WEB-INF.board-servlet.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 요청에 따른 컨트롤러 반환 담당 : /list.do -->
<bean id="defaultHandlerMapping" class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>
<!-- viewResolver(위치, 이동할 페이지 지정) : "list" -->
<bean id="viewResolover" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.InternalResourceView"/>
<property name="prefix" value="/"/> <!-- "/list" -->
<property name="suffix" value=".jsp"/> <!-- "/list.jsp" -->
</bean>
</beans>
3.
JNDI를 이용해서 DB를 연동해야 하기 때문에, 해당 DB의 정보를 DAO에서 사용할 수 있도록 Context.xml 파일을 작성해야 한다.
◎META-INF.Context.xml
<?xml version="1.0" encoding="UTF-8"?>
<Context>
<Resource name="jdbc/orcl"
auth="container"
type="javax.sql.DataSource"
username="SPRING_EX"
password="1234"
driverClassName="oracle.jdbc.driver.OracleDriver"
factory="org.apache.commons.dbcp.BasicDataSourceFactory"
url="jdbc:oracle:thin:@localhost:1521:XE"
maxActive="20"
maxIdle="10">
</Resource>
</Context>
4.
이제 생성한 Context.xml 파일을 이용하여 Connection 객체를 생성하여 이용하기 위한 DAO를 작성한다.
전체적인 흐름을 알아보는 예제이기 때문에 상세 메서드 & DTO 는 기술하지 않겠다.
중요한 점은 DB를 제어하는 각각의 메서드에서 Connection 객체를 생성자에서 선언한 ds 맴버 변수를 이용하여 생성한다는 점이다.
◎BoardDAO.java
package com.choonham.dao;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import javax.naming.InitialContext; // InitialContext implements Context
import javax.naming.NamingException;
import javax.sql.DataSource;
import com.choonham.command.BoardCommand;
import com.choonham.dto.BoardDTO;
public class BoardDAO {
DataSource ds;
//DataSource 객체는 getConnection() 과 동일한 역할을 담당한다.
public BoardDAO() {
// Context 객체 얻기 : Context.xml 문서에서 name = "jdbc/orcl" 속성 값을 추출
// 방법 : InitialContext 객체를 이용(해당 객체가 가지고 있는 lookUp() 을 통해 name 속성 값을 추출할 수 있다.
// 단, jdbc/orcl을 찾기 위해서는 java:comp/evn/jdbc/orcl 와 같이 적어야 한다.
try {
InitialContext ctv = new InitialContext();
ds = (DataSource)ctv.lookup("java:comp/env/jdbc/orcl");
} catch (NamingException e) {
e.printStackTrace();
}
}
/** 글 목록 조회 **/
public ArrayList<BoardDTO> list() {
//생략
}
/** 새 글에 대한 글 번호를 위한 메서드 **/
public int getNewNum() {
//생략
}
/** 글 저장 메서드 **/
public void write(BoardCommand data) {
//생략
}
}
5.
작성한 DAO를 board-servlet.xml 에 등록해야지만 사용할 수 있기 때문에 등록해준다.
◎WEB-INF.board-servlet.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 요청에 따른 컨트롤러 반환 담당 : /list.do -->
<bean id="defaultHandlerMapping" class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>
<!-- DB 접속 : JNDI 방식 -->
<bean id = "boardDAO" class = "com.choonham.dao.BoardDAO"></bean>
<!-- viewResolver(위치, 이동할 페이지 지정) : "list" -->
<bean id="viewResolover" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.InternalResourceView"/>
<property name="prefix" value="/"/> <!-- "/list" -->
<property name="suffix" value=".jsp"/> <!-- "/list.jsp" -->
</bean>
</beans>
6.
이후 실제 기능을 구현할 컨트롤러를 작성해줘야 한다.
Spring 프레임워크에서 컨트롤러가 요청을 받아 자동적으로 어떤 메서드를 수행하게 하기 위해서는 반드시
ModelAndView() 메서드를 사용해야하는데, 이는 org.springframework.web.servlet.mvc.Controller를 구현 받거나
spring프레임워크에서 제공하는 다른 컨트롤러를 상속받아야 한다.
◎ListActionController.java 는 아래와 같이 따로 인터페이스를 구현받아 작성했다.
◎ListActionController.java
package com.choonham.controller;
import java.util.ArrayList;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;
import com.choonham.dao.BoardDAO;
import com.choonham.dto.BoardDTO;
public class ListActionController implements Controller {
private BoardDAO dao;
public void setDao(BoardDAO dao) {
this.dao = dao;
System.out.println("setDao() 호출 : " + dao);
}
public ListActionController() {
// TODO Auto-generated constructor stub
}
/** 요청사항이 전달되면 자동 호출되는 메서드 **/
@Override
public ModelAndView handleRequest(HttpServletRequest arg0, HttpServletResponse arg1) throws Exception {
System.out.println("ListActionController의 handleRequest() 자동호출 ");
ArrayList<BoardDTO> list = dao.list();
ModelAndView mav = new ModelAndView();
mav.setViewName("list"); // list.jsp 를 의미
mav.addObject("list", list); // request.setAttribute("list", list); 개념
return mav;
}
}
또한 글 쓰기 기능을 담당하는 ◎WriteActionController.java 의 경우, 아래와 같이 AbstractCommandController 를 상속 받았다.
◎WriteActionController.java
package com.choonham.controller;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.validation.BindException;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.AbstractCommandController;
import com.choonham.command.BoardCommand;
import com.choonham.dao.BoardDAO;
/** AbstractCommondController를 상속 받은 이유
* 사용자가 입력한 값을 외부로부터 자동으로 전달받기 위해
* 요청시 자동으로 호출되는 handle()의 매개 변수의 역할
* HttpServletRequest : request 객체 (요청 객체)
* HttpServletResponse : response 객체 (응답 객체)
* Object : 입력받은 값을 저장하는 객체
* BindException : 전달 받을 값에 대한 오류를 예외처리
*
* 따라서 이 컨트롤러 등록시, 입력 받은 값을 저장할 수 있는 객체가 필요하고,
* 객체는 commandClass 변수를 통해 자동 전달한다.
* **/
public class WriteActionController extends AbstractCommandController {
private BoardDAO dao;
public void setDao(BoardDAO dao) {
this.dao = dao;
System.out.print("dao 호출" + dao);
}
@Override
protected ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object command, BindException error)
throws Exception {
BoardCommand data = (BoardCommand) command;
// BoardDAO의 write() 메서드에게 data 객체를 전달
dao.write(data);
// 기존 방식
// String t = request.getParameter("t");
// Spring 방식 : ModelAndView객체 사용
ModelAndView mav = new ModelAndView();
mav.setViewName("redirect:/list.do");
return mav;
}
}
7.
마지막으로 생성한 각각의 컨트롤러들을 board-servlet.xml에 등록해주면 해당 요청이 들어왔을 때, 자동으로 파라미터를 맵핑하고, 기능을 수행하는 구조가 완성이 된다.
◎WEB-INF.board-servlet.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 요청에 따른 컨트롤러 반환 담당 : /list.do -->
<bean id="defaultHandlerMapping" class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>
<!-- DB 접속 : JNDI 방식 -->
<bean id = "boardDAO" class = "com.choonham.dao.BoardDAO"></bean>
<!-- 글 목록 보기 컨트롤러 등록 : private dao => setDao(boardDAO) -->
<bean name = "/list.do" class = "com.choonham.controller.ListActionController">
<property name = "dao">
<ref bean = "boardDAO" />
</property>
</bean>
<!-- 글 쓰기 화면 처리 컨트롤러 등록 : 스프링이 제공 -->
<bean name = "/writeui.do" class="org.springframework.web.servlet.mvc.ParameterizableViewController"> <!-- 파라미터 없이 뷰를 내보내주는 spring 클래스 -->
<property name = "viewName" value = "write"></property>
</bean>
<!-- 글 저장 처리 컨트롤러 등록 -->
<bean name = "/write.do" class = "com.choonham.controller.WriteActionController">
<property name = "dao">
<ref bean = "boardDAO"/>
</property>
<property name = "commandClass" value = "com.choonham.command.BoardCommand"></property>
</bean>
<!-- viewResolver(위치, 이동할 페이지 지정) : "list" -->
<bean id="viewResolover" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.InternalResourceView"/>
<property name="prefix" value="/"/> <!-- "/list" -->
<property name="suffix" value=".jsp"/> <!-- "/list.jsp" -->
</bean>
</beans>
화면 구성은 다음과 같이 간단하게 구성했다.
◎index.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%
response.sendRedirect("http://localhost:9000/spring_simple_board/list.do");
%>
◎list.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ page import = "java.util.ArrayList, com.choonham.dto.BoardDTO" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>list</title>
</head>
<body>
<table border = "1">
<tr>
<td colspan = "5">게시판 목록
<a href = "writeui.do">글 쓰기</a>
</td>
</tr>
<tr>
<th>번호</th>
<th>제목</th>
<th>작성자</th>
<th>작성일</th>
<th>조회수</th>
</tr>
<!-- DispatcherServlet은 Controller 로부터 ModelAndView 객체를 받고
그 중 model 에 해당하는 객체를 list.jsp의 request에게 전달
따라서 list.jsp 는 request의 attribute 영역에서 해당 객체를 추출하여 사용 가능
-->
<%
ArrayList<BoardDTO> list = (ArrayList)request.getAttribute("list");
if(list != null) {
for(BoardDTO dto:list){
int num = dto.getNum();
String title = dto.getTitle();
String author = dto.getAuthor();
String writeday = dto.getDate();
int readcnt = dto.getReadcnt();
%>
<tr>
<td><%= num %></td>
<td><a href = "retrieve.do?num=<%= num %>"><%= title %></a></td>
<td><%= author %></td>
<td><%= writeday.substring(0, 10) %></td>
<td><%= readcnt %></td>
</tr>
<%
}
}
%>
</table>
</body>
</html>
◎write.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>write.jsp</title>
</head>
<body>
<form action = "write.do">
<table>
<tr>
<td>제목</td>
<td>
<input type = "text" name = "title" />
</td>
</tr>
<tr>
<td>작성자</td>
<td>
<input type = "text" name = "author" />
</td>
</tr>
<tr>
<td>내용</td>
<td>
<textarea rows = "5" cols = "30" name = "content"></textarea>
</td>
</tr>
<tr>
<td colspan = "2">
<input type = "submit" value = "저장" />
</td>
</tr>
</table>
</form>
</body>
</html>
결과 페이지는 다음과 같이 정상적으로 작동하는 걸 확인할 수 있다.
데이터의 흐름을 눈으로 따라가기가 지금은 좀 힘들지만, 대부분의 업무를 자동으로 맵핑하여 처리해주는 점이 익숙해지면 굉장히 편리할 거 같다.
... 일단 디버깅이 굉장히 어려워진 거 같으니, 최대한 집중해서 코드를 작성해야할 거 같다.