Java EE 정리 25
02 Apr 2019
Reading time ~9 minutes
Java EE 정리 25 - Model2(2)
Model 2 작성 방법
-
진입점을 하나로 설정
- 통제의 편의성
- Servlet(Main Controller) 하나 생성
-
DD(web.xml)에 security constraint 설정
- JSP를 직접 요청하지 못하도록 막음
- 객체다형성(inheritance)
- 페이지 이동
Model 2 작성 방법 - JSP 요청 막기
- 통제의 편의성
- secufrity constraint(보안제약) 설정
<!-- DD(web.xml) -->
...
<security-constraint>
<web-resource-collection>
<web-resource-name></web-resource-name>
<url-pattern>*.jsp</url-pattern>
<http-method>GET</http-method>
<http-method>POST</http-method>
</web-resource-collection>
<auth-constraint>
<role-name></role-name>
</auth-constraint>
</security-constraint>
</web-app>
-
JSP를 바로 접근 시 에러페이지 처리
- 에러 이미지를 보여주는 JSP페이지로 이동시킴
- 서비스할 때만 사용, 개발할 땐 막지 않는다.
- 에러 코드에 따라 다른 에러 페이지를 보여줄 수 있다.
<!-- err_403.jsp -->
...
<img src="http://localhost:8080/model2_prj/common/error/1.PNG"/>
...
<!-- web.xml -->
...
</welcome-file-list>
<error-page>
<error-code>403</error-code>
<location>/common/error/err_403.jsp</location>
</error-page>
<security-constraint>
...
Model 2 작성 방법 - 진입점을 하나로 만들기
- 요청을 처리한다는 의미로 ‘do’가 붙음
- 어떤 do가 불리든 Main Controller로 가서 진입점을 하나로 만듦
<!-- view.jsp -->
...
요청
<a href="main.do">main</a>
<a href="a.do">a</a>
<a href="b.do">b</a>
<a href="yana.do">yanado</a>
...
public class MainController extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 진입점을 하나로 만들기 위해
// get 요청이 와도 post으로 처리됨
doPost(request,response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
RequestDispatcher rd = request.getRequestDispatcher("date0402/view.jsp");
rd.forward(request, response);
}
}
<!-- DD, web.xml -->
...
<servlet>
<description></description>
<display-name>MainController</display-name>
<servlet-name>MainController</servlet-name>
<servlet-class>date0401.MainController</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>MainController</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
</web-app>
-
*.do가 url로 표기 되므로 index.do가 표기되도록 변경처리
- welcome-file로 index.html을 만들고 페이지 이동처리
<!-- index.html -->
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<script type="text/javascript">
location.href="index.do";
</script>
</head>
<body>
</body>
</html>
Model 2 작성 방법 - 페이지 이동
-
Servlet(MainController)에서 JSP로 이동할 땐 forward를 사용한다.
- redirect로 페이지 이동하면 JSP 직접요청으로 브라우저가 이동을 알게되고 에러페이지로 이동해버린다.
public class MainController extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 진입점을 하나로 만들기 위해
// get 요청이 와도 post으로 처리됨
doPost(request,response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
RequestDispatcher rd = request.getRequestDispatcher("date0401/hello.jsp");
rd.forward(request, response);
}
}
<!-- hello.jsp -->
...
안녕하세요?<br/>
<img src="../common/images/img.png"/>
...
-
forward로 페이지 이동 시 url이 변경되지 않음
- 이미지의 소스는 상대 경로가 아닌 절대 경로로 잡아야 한다.
- 어디에 있든 서비스가 되게 하려면 url로 설정해야 한다.
<img src="common/images/img.png"/>
Model 2 작성 방법 - 객체 다형성
- 클래스가 다르지만 같은 메소드를 갖고 있는 경우
public class SeonUi {
public String execute() {
return "어~~";
}
}
public class JaeHyun {
public String execute() {
return "저요";
}
}
public class TeackSung {
public String execute() {
return "한공기 추가요";
}
}
- key에 따라 다른 객체를 갖기 때문에 다른 객체를 캐스팅하면 에러가 발생
Map<String, Object> map = new HashMap<String, Object>();
map.put("gong", new SeonUi());
map.put("lee", new JaeHyun());
map.put("jung", new TeackSung());
String key = "lee";
SeonUi su = (SeonUi)map.get(key);
System.out.println(su.execute());
-
에러가 발생하지 않기 위해선 if로 물어봐야 함
- 사람 수가 늘어날 수록 코드가 많아진다.
if ("gong".equals(key)) {
SeonUi su = (SeonUi)map.get(key);
su.execute();
} else if ("lee".equals(key)) {
JaeHyun jh = (JaeHyun)map.get(key);
jh.execute();
} else if ("jung".equals(key)) {
TeackSung ts = (TeackSung)map.get(key);
ts.execute();
}
- inheritance를 사용(객체다형성)하면 코드를 줄일 수 있다.
// is a, 객체다형성을 이용하면 코드의 길이가 줄어든다.
Controller c = (Controller)map.get(key);
c.execute(); // Override된 메소드 호출
- 객체다형성 사용 예
public interface Person { // 인터페이스!
public String execute();
}
public class SeonUi implements Person{
public String execute() {
return "어~~";
}
}
public class JaeHyun implements Person {
public String execute() {
return "저요";
}
}
public class TeackSung implements Person {
public String execute() {
return "한공기 추가요";
}
}
- 이때 사용되는 key를 Model2에선 Command(입력하는 값)라고 한다.
Map<String, Person> map = new HashMap<String, Person>();
map.put("gong", new SeonUi());
map.put("lee", new JaeHyun());
map.put("jung", new TeackSung());
String key = "gong";
if (map.containsKey(key)) { // NullPointerException 예외처리
// 객체 다형성을 사용하여 부모에 얻어낸 자식을 저장
Person person = map.get(key);
System.out.println(person.execute());
}
// 사람이 더 늘어도 4줄로 다 처리가 가능하다.
Model2 예제
- 주석으로 클래스다이어그램에 보여줄 JSP를 명시할 수 있다.
- 컨트롤러에서 직접 DAO를 연결하지 않는다.
-
Command
- D001 : DeptController
- E001 : EmpController
-
MainController 만들기
-
진입점(Spring의 DispatcherServlet)
- 모든 요청을 받고 요청을 처리할 Controller를 찾아 실행을 함
- View로 이동
-
진입점(Spring의 DispatcherServlet)
public class MainController extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request,response); // GET방식의 요청이 있어도 doPost로 처리
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// Controller 구현 후 추가 예정
}
}
-
welcome-file을 index.html만 남기고 삭제, JSP를 직접 요청하지 못하도록 설정
- DD 변경(web.xml)
...
<welcome-file-list>
<welcome-file>index.html</welcome-file>
</welcome-file-list>
...
<security-constraint>
<web-resource-collection>
<web-resource-name></web-resource-name>
<url-pattern>*.jsp</url-pattern>
<http-method>GET</http-method>
<http-method>POST</http-method>
</web-resource-collection>
<auth-constraint>
<role-name></role-name>
</auth-constraint>
</security-constraint>
</web-app>
- MainController로 요청이 가도록 welcome-file 생성
<!-- index.html -->
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<script type="text/javascript">
location.href="index.do";
</script>
</head>
<body>
</body>
</html>
- Controller 인터페이스 생성
public interface Controller {
/**
* 요청, 응답 처리, 관계유지 객체(Session, Cookie)의 사용
* @param request 웹 파라미터 요청처리
* @param response 응답처리
* @throws ServletException
* @throws IOException
*/
public void execute(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException;
/**
* 처리한 결과를 보여줄 JSP명을 반환
* @return
*/
public String moveURL();
/**
* 처리한 결과를 보여줄 JSP, HTML로 이동하는 이동방식 선정
* @return
*/
public boolean isForward();
}
- VO 생성
public class DeptVO {
private int deptno;
private String dname, loc;
// 생성자, getter, setter 생성
...
public class EmpVO {
private int empno, sal;
private String name, job, hiredate;
// 생성자, getter, setter 생성
...
- DAO 생성, Server.xml에 DBCP 설정
public class Model2DAO {
private static Model2DAO j_dao;
private Model2DAO() {}
public static Model2DAO getInstance() {
if (j_dao == null) {
j_dao = new Model2DAO();
}
return j_dao;
}
public Connection getConn() throws SQLException {
Connection con = null;
try {
// DBCP
Context ctx = new InitialContext();
DataSource ds = (DataSource)ctx.lookup("java:comp/env/jdbc/model2_dbcp");
con = ds.getConnection();
} catch (NamingException e) {
e.printStackTrace();
}
return con;
}
public List<EmpVO> selectEmpList(int deptno) throws SQLException {
List<EmpVO> list = new ArrayList<EmpVO>();
Connection con = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
con = getConn();
StringBuilder selectEmp = new StringBuilder();
selectEmp
.append(" select empno, ename, job, to_char(hiredate,'yyyy-MM-dd') hiredate, sal ")
.append(" from emp ")
.append(" where deptno = ? ")
.append(" order by sal ");
pstmt = con.prepareStatement(selectEmp.toString());
pstmt.setInt(1, deptno);
rs = pstmt.executeQuery();
EmpVO evo = null;
while(rs.next()) {
evo = new EmpVO(rs.getInt("empno"), rs.getInt("sal"),
rs.getString("ename"), rs.getString("job"),
rs.getString("hiredate"));
list.add(evo);
}
} finally {
if (rs != null) { rs.close(); }
if (pstmt != null) { pstmt.close(); }
if (con != null) { con.close(); }
}
return list;
}
public List<DeptVO> selectDeptList() throws SQLException {
List<DeptVO> list = new ArrayList<DeptVO>();
Connection con = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
con = getConn();
StringBuilder selectDept = new StringBuilder();
selectDept
.append(" select deptno, dname, loc ")
.append(" from dept ");
pstmt = con.prepareStatement(selectDept.toString());
rs = pstmt.executeQuery();
DeptVO dvo = null;
while(rs.next()) {
dvo = new DeptVO(rs.getInt("deptno"),
rs.getString("dname"), rs.getString("loc"));
list.add(dvo);
}
} finally {
if (rs != null) { rs.close(); }
if (pstmt != null) { pstmt.close(); }
if (con != null) { con.close(); }
}
return list;
}
}
<!-- server.xml -->
...
<Context docBase="model2_emp" path="/model2_emp" reloadable="true" source="org.eclipse.jst.jee.server:model2_emp">
<Resource auth="Container" driverClassName="oracle.jdbc.OracleDriver" maxIdle="10" maxTotal="20" maxWaitMillis="-1" name="jdbc/model2_dbcp" password="tiger" type="javax.sql.DataSource" url="jdbc:oracle:thin:@localhost:1521:orcl" username="scott"/>
</Context>
...
-
Service 생성
- Business Logic을 처리하는 클래스, DAO 단의 클래스를 사용
public class DeptService {
public List<DeptVO> searchAllDept() {
List<DeptVO> list = null;
Model2DAO m_dao = Model2DAO.getInstance();
try {
list = m_dao.selectDeptList();
} catch (SQLException e) {
e.printStackTrace();
}
return list;
}
}
public class EmpService {
public List<EmpVO> searchEmp(int deptno) {
List<EmpVO> list = null;
Model2DAO m_dao = Model2DAO.getInstance();
try {
list = m_dao.selectEmpList(deptno);
for(EmpVO ev : list) { // 사원명에 '님'을 붙인다.
ev.setName(ev.getName()+"님");
}
} catch (SQLException e) {
e.printStackTrace();
}
return list;
}
}
- 요청 하나당 클래스 하나가 처리한다.(올드한 방식)
- Spring에선 한 클래스에서 요청마다 다른 메서드로 처리한다.
public class DeptController implements Controller {
private String url;
private boolean forwardFlag;
@Override
public void execute(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
DeptService ds = new DeptService();
List<DeptVO> deptList = ds.searchAllDept();
// 처리된 데이터를 JSP에서 보여주기 위해 Scope객체에 설정한다.
request.setAttribute("deptList", deptList);
url="dept/dept.jsp";
forwardFlag = true;
}
@Override
public String moveURL() {
return url;
}
@Override
public boolean isForward() {
return forwardFlag;
}
}
public class EmpController implements Controller {
private String url;
private boolean forwardFlag;
@Override
public void execute(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String deptno = request.getParameter("deptno");
int intDeptno = Integer.parseInt(deptno);
EmpService es = new EmpService();
List<EmpVO> empList = es.searchEmp(intDeptno);
request.setAttribute("empList", empList);
url="emp/emp.jsp";
forwardFlag=true;
}
@Override
public String moveURL() {
return url;
}
@Override
public boolean isForward() {
return forwardFlag;
}
}
-
MainController
- static 영역으로 JVM이 실행되면서 미리 Command를 저장한 Map 변수를 만듦
public class MainController extends HttpServlet {
private static Map<String, Controller> distMap;
static { // static영역
distMap = new HashMap<String, Controller>();
distMap.put("D001", new DeptController()); // 부서조회 업무
distMap.put("E001", new EmpController()); // 사원조회 업무
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request,response); // GET방식의 요청이 있어도 doPost로 처리
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("UTF-8"); // POST 한글처리
String cmd = request.getParameter("cmd");
if (cmd == null || "".equals("")) { // cmd가 없을 때
cmd = "D001";
}
Controller controller = distMap.get(cmd);
if (controller == null) { // cmd를 외부에서 임의로 변경했을 때
// 메인을 제공한다.
controller = distMap.get("D001");
}
// 웹 파라미터 처리, 관계유지, 업무처리 결과를 Scope객체에 설정하는 일을 수행
controller.execute(request, response);
// 이동할 페이지의 URL받기
String url = controller.moveURL();
// 이동방식 받기
boolean forwardFlag = controller.isForward();
pageMove(request, response, url, forwardFlag);
}
public void pageMove(HttpServletRequest request, HttpServletResponse response, String url, boolean forwardFlag)
throws ServletException, IOException {
if (forwardFlag) { // forward 이동
RequestDispatcher rd = request.getRequestDispatcher(url);
rd.forward(request, response);
} else { // redirect 이동
response.sendRedirect(url);
}
}
}
-
View 생성 * Model2에서 JSP의 역할은 보여주는 역할만 한다.
- 전달되는 deptList가 어디서 전달되는건지 확인하기가 어렵다.
- 부서번호를 클릭했을 때 emp정보를 보는 페이지로 이동할 것
- “emp.do” 처럼 do 앞은 뭐가 와도 상관없고 중요한건 Web Parameter
<!-- dept.jsp -->
...
<table>
<tr>
<th width="40">번호</th>
<th width="80">부서번호</th>
<th width="100">부서명</th>
<th width="100">위치</th>
</tr>
<c:if test="${ empty deptList }">
<tr>
<td colspan="4" align="center">부서정보가 존재하지 않습니다.</td>
</tr>
</c:if>
<c:forEach var="dept" items="${ deptList }" >
<c:set var="i" value="${ i+1 }"/>
<tr>
<td><c:out value="${ i }"/></td>
<td><a href="emp.do?cmd=E001&deptno=${ dept.deptno }"><c:out value="${ dept.deptno }"/></a></td>
<td><c:out value="${ dept.dname }"/></td>
<td><c:out value="${ dept.loc }"/></td>
</tr>
</c:forEach>
</table>
...
<!-- emp.jsp -->
...
<strong>${ param.deptno }</strong>번 부서조회<br/>
<a href="dept.do?cmd=D001">뒤로</a>
<table border="1">
<tr>
<th width="40">번호</th>
<th width="80">사원번호</th>
<th width="100">사원명</th>
<th width="80">연봉</th>
<th width="80">직무</th>
<th width="150">입사일</th>
</tr>
<c:if test="${ empty empList }">
<tr>
<td colspan="6" align="center">
해당 부서는 사원이 존재하지 않거나, 부서가 존재하지 않을 수 있습니다.
</td>
</tr>
</c:if>
<c:forEach var="emp" items="${ empList }">
<c:set var="i" value="${ i+1 }"></c:set>
<tr>
<td>${ i }</td>
<td><c:out value="${ emp.empno }"/></td>
<td><c:out value="${ emp.name }"/></td>
<td><c:out value="${ emp.sal }"/></td>
<td><c:out value="${ emp.job }"/></td>
<td><c:out value="${ emp.hiredate }" escapeXml="false"/></td>
</tr>
</c:forEach>
</table>
...