51일차(1)/Spring(8) : Interceptor
- Interceptor 의 기능, 사용법 익히기, redirect 이동시키기
[ Spring Framework 의 특징 ]
1. 스프링 프레임워크가 객체의 생성과 관리를 대신 해준다.
- 이것을 '제어의 역전' 이라고 부른다. (Inversion Of Control, IOC)
- 어떤 객체를 생성하고 관리할지 기준이 되는 문서가 servlet-context.xml 문서이다.
(beans란 스프링이 생성하고 관리하는 객체이다. 이 객체를 관리하는 문서!)
- 스프링이 관리하는 bean이 되는 또다른 방법은 component scan을 이용하는 것이다.
- bean을 생성할 클래스가 존재하는 패키지에 component scan이 일어나야 하고,
또한 해당 클래스는 적절한(용도에 맞는) 어노테이션(@)이 작성되어 있어야 한다.
2. 필요한 핵심 의존객체를 직접 생성하지 않고 주입 받아서 사용한다.
- 이것을 'Dependency Injection (DI)' 이라고 한다.
- 주입받을 때 주의할 사항은 interface type으로 받아서 사용해서 한다는 점!
- 이처럼 IOC, DI형태로 개발하면 의존관계가 느슨해져서 유지보수가 쉬워진다.
참고) AOP라는 것도 있다! AOP까지가 스프링의 3대 핵심 개념.
- sqlSessionTemplate 객체에서 만들어진 객체가 dao의 session으로 들어가는 것이다.(주입받는다)
(sqlSessionTemplate 는 SqlSession 인터페이스를 구현했기 때문에 타입이 맞아서 주입받을 수 있다)
- @Autowired 해두면 타입이 맞으면 자동으로 주입된다.(spring에서 해당 객체를 관리하고 있을 경우!)
- dao의 입장에서는 주입받아서 사용할 뿐, 이 객체가 어떻게 들어오는 것인지는 고민하지 않아도 된다.
- 명시된 1)spring03 의 하위 패키지에 있으면서 2)적절한 어노테이션@이 붙어있으면 bean이 될 수 있다.
- bean이 되면 핵심의존객체를 쉽게 주입받을 수 있고,
그 bean 자체도 container에서 관리되어 필요시 쉽게 조립될 수 있다.
- 의존관계가 이렇게 엮여있으면, 만약 C를 수정한다고 하면 C만 수정하면 되지만
B를 수정한다고 할 경우, 갑자기 A에 에러가 발생한다. 그렇다고 A를 수정하면 C에 에러 발생..
- 의존관계에 있으면 이렇게 에러가 전파된다.
- 그러나 위의 1,2의 원칙을 지키면 이 에러가 퍼지는 것을 막을 수 있다.
- 의존관계가 너무 견고하면 이런 부분이 힘들다..
ex) 편의점에서 김밥을 직접 말아서 팔지 않고 납품받아서 판매하듯이!
편의점은 어떤 과정을 통해서 납품되는지를 고민하지 않는다. 특정 제조사를 고집할 필요도 없다. 납품받기만 하면 된다.
특정 회사를 정해놓으면 그 회사가 망하면 김밥을 팔수 없게 된다...
또는 직접 김밥을 말아서 판매하면 너무 힘들다....
원활하게 김밥을 납품받아 판매하기 위해서 이런 느슨한 의존관계를 유지하는 것!
- dao에서 메소드의 모양을 미리 정해놓았으므로, 구현해서 클래스를 정의해서 bean으로 만들면 된다.(DaoImpl)
- dao의 안쪽 코딩을 어떻게 하든 일단 구현해서 사용하면 된다.
- 이 dao를 받아서 사용하는 서비스에도 autowired 되어 있다. 이 객체를 주입받아서 사용!
- 서비스도 인터페이스 타입으로 만들어서 쓴다. 이렇게 하면 Impl 클래스를 삭제해도 에러가 발생하지 않는다.
- 인터페이스 타입을 사용하면 오류가 나지않고, 다른객체로 교체도 가능하다. 의존관계를 느슨하게 만들 수 있다!
- service는 컨트롤러에서 사용하는 유틸리티 같은 것
- 복잡한 로직을 controller에서 직접 하지 않고 service를 통해서 사용한다.
- 코딩양이 늘어나서 번거로울 수 있지만, 편한 유지보수를 위해서 넣어서 사용한다.
- 주입받아서 사용하는데 어노테이션이 없으면?
- bean이 되지 못해서 객체를 주입받지 못한다. 에러발생!
- 이 경우 @Autowired 가 동작하지 않았다. Bean Creation에 실패했다고 나온다.
- dao가 bean이 되지 못했다면 service로 들어가지도 않고, controller에 service도 주입되지 않는다.
- DAO : 데이터의 저장소라는 의미에서 @Repository (데이터의 저장소)
- 서비스 : @Service
- 컨트롤러 : @Controller
- 각각의 문서들은 이 정해진 어노테이션을 사용하고 있다. 이 조건들이 다 맞아야만 프레임워크가 동작한다.
Spring03_Interceptor 새 MVC프로젝트 생성.
- com.sy.spring03 패키지
- 이 Spring03이 webapp의 역할을 한다.(context path)
- Interceptor : 중간에 끼어드는 자라는 의미
* in jsp : Filter
- 클라이언트가 서버에 요청을 할때, servlet 또는 jsp로 요청하면
중간에 있는 필터가 요청을 가로채서 어떤 일을 했다.
- 이 필터는 요청이 들어가기 이전에만 어떤 개입을 할 수 있다.
* in Spring Framework
- 클라이언트가 요청하면 Dispatcher Servlet이 요청을 받는다.
이 사이에도 필터를 달 수 있다!
- Controller를 거쳐서 처리를 하고 view페이지에서 응답
- jsp 필터 : 우리가 아는 Filter는 처음(request)에만 개입할 수 있지 중간에는 개입할 수 없다.
- 필터는 servlet/jsp로 가기 전까지만 개입할 수 있다.
- 하지만 Dispatcher Servlet → controller → viewpage 이 사이에 개입을 하고싶을 경우?
- 이 경우에 개입을 할수있는 것이 interceptor이다.
- 필터와 비슷하지만 spring framework가 동작하는 도중에 개입할 수 있다.
중간에 끼어드는 것! (로그인여부 검사 - 검사해서 redirect 시킬 수 있다)
- 좀 더 기능이좋은 필터라는 느낌!
- Interceptor를 이 위치에서 관여시켜볼 것이다.
- Dispatcher Servlet → Controller 의 사이!
home.jsp (최종)
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>/home.jsp</title>
</head>
<body>
<div class="container">
<h1>인덱스 페이지입니다.</h1>
<ul>
<li><a href="play">놀러가기</a></li>
<li><a href="users/loginform">로그인</a></li>
<li><a href="users/logout">로그아웃</a></li>
<li><a href="users/info">개인정보(로그인 필요)</a></li>
</ul>
<h3>공지사항</h3>
<ul>
<c:forEach var="tmp" items="${noticeList }">
<li>${tmp }</li>
</c:forEach>
</ul>
</div>
</body>
</html>
play.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>/views/play.jsp</title>
</head>
<body>
<div class="container">
<p>신나게 놀아요!</p>
<a href="/spring03/">인덱스로</a>
</div>
</body>
</html>
com.sy.spring03.interceptor 새 패키지
MyInterceptor
package com.sy.spring03.interceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
/*
* [ 인터셉터 만들기 ]
*
* 1. HandlerInterceptor 인터페이스를 구현해서 만든다.
* 2. servlet-context.xml에 bean 설정을 하고 interceptor 목록에 등록을 하고 맵핑을 해준다.
*/
public class MyInterceptor implements HandlerInterceptor{
//Controller 실행 이전에 호출되는 메소드
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
System.out.println("이전!");
/*
* true를 리턴하면 계속 호출을 이어가고 false를 리턴하면 이어가지 않는다.
*/
return true;
}
//Controller 실행 이후에 호출되는 메소드
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
System.out.println("이후!");
}
//응답한 이후에 호출되는 메소드
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
System.out.println("응답이후!");
}
}
- 특별한 일을 하는 클래스이므로 분명 상속받거나 구현해서 만들 것!
→ 인터페이스를 구현해서 만든다.
- HandlerInterceptor를 구현하고, 추상메소드를 override한다.
preHandle / postHandle / afterCompletion
- 3개의 메소드. 테스트용으로 위와같이 작성
- play에 개입시키기
- spring이 지금 MyInterceptor를 모르므로 인식하도록 설정시켜야 한다. → servlet-context.xml
<!-- 인터셉터 목록 -->
<interceptors>
<!-- myInterceptor가 /play 요청에 대해 끼어들도록 설정한다. -->
<interceptor>
<mapping path="/play"/>
<beans:ref bean="myInterceptor" />
</interceptor>
<!-- /users/하위의 모든 요청에 대해 loginInterceptor가 끼어들도록 설정한다. -->
<interceptor>
<mapping path="/users/*" />
<exclude-mapping path="/users/loginform"/>
<exclude-mapping path="/users/login"/>
<beans:ref bean="loginInterceptor"/>
</interceptor>
</interceptors>
<beans:bean class="com.sy.spring03.interceptor.MyInterceptor" />
- 이 코드가 있으면 myInterceptor 객체가 생성된다. 이 객체를 생성시키고 관리하라는 뜻!
- 전부 자동완성으로 작성할 수 있다. hard coding 할 필요가 없다.
- 이렇게 전체를 자동완성으로 작성해주기.
- play.jsp 페이지를 실행하면 콘솔창에 이렇게 작성된다.
- MyInterceptor를 bean으로 만든 것. 참조값으로 읽어온다.
- 어떤 요청에 대해서 끼어들 것인지를 mapping path=" " 에 등록한다.
- 인터페이스를 구현해서 만들고 / 메소드 override 하기
- 세 메소드 중에서 이전(preHandle)에서 제일 많이 작업햔다.
[ 인터셉터 만들기 ]
1. HandlerInterceptor 인터페이스를 구현해서 만든다.
2. servlet-context.xml 에 bean 설정을 하고 interceptor 목록에 등록을 하고 맵핑을 해준다.
- return false를 하면? 응답되는 요청이 없다(소스보기 한 결과)
- true를 리턴하면 계속 호출을 이어가고 false를 리턴하면 이어가지 않는다.
- 로그인 여부에 따라 intercept 하는 기능을 추가할 예정
LoginInterceptor
package com.sy.spring03.interceptor;
import java.net.URLEncoder;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
//로그인된 사용자인지 검사할 인터셉터
public class LoginInterceptor implements HandlerInterceptor {
//Controller 메소드 수행 직전에 로그인된 사용자인지 검증을 해서
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
//세션 객체의 참조값을 얻어와서
HttpSession session=request.getSession();
String id=(String)session.getAttribute("id");
//만일 로그인을 하지 않았다면
if(id==null) {
//로그인 페이지로 리다일렉트 이동시키고 false를 리턴한다.
//원래 가려던 url 정보 읽어오기
String url=request.getRequestURI();
//GET 방식 전송 파라미터를 query 문자열로 읽어오기 ( a=xxx&b=xxx&c=xxx )
String query=request.getQueryString();
//특수 문자는 인코딩을 해야한다.
String encodedUrl=null;
if(query==null) {//전송 파라미터가 없다면
encodedUrl=URLEncoder.encode(url);
}else {
// 원래 목적지가 /test/xxx.jsp 라고 가정하면 아래와 같은 형식의 문자열을 만든다.
// "/test/xxx.jsp?a=xxx&b=xxx ..."
encodedUrl=URLEncoder.encode(url+"?"+query);
}
//3. 로그인을 하지 않았다면 /users/loginform.do 페이지로 리다일렉트 이동 시킨다. (HttpServletResponse)
String cPath=request.getContextPath();
response.sendRedirect(cPath+"/users/loginform?url="+encodedUrl);
return false;
}
//로그인을 했다면 흐름을 이어간다.
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
// TODO Auto-generated method stub
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
// TODO Auto-generated method stub
}
}
- request 값이 전달되므로 여기서 session값을 읽어온다.
- 위와 같은 구조로 작성해주면 된다. 필터와 비슷하다.
- 요청 파라미터를 잃어버리지 않도록 작업하면 된다.
- sevlet-context 가서 interceptor 설정
- 이렇게 여러개도 등록 가능하다.
- /users/하위의 모든 요청에 대해 loginInterceptor가 끼어들도록 설정한 것.
- 로그인하지 않은 사용자는 redirect 이동시키고 interceptor로 진행을 멈춘다.
- Controller 추가
com.sy.spring03.interceptor.users.controller 패키지생성
UsersController
package com.sy.spring03.interceptor.users.controller;
import javax.servlet.http.HttpSession;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class UsersController {
//로그인폼 요청 처리
@RequestMapping("users/loginform")
public String loginform() {
return "users/loginform";
}
//로그인 요청 처리
@RequestMapping("/users/login")
public String login(String id, HttpSession session){
session.setAttribute("id", id);
return "users/login";
}
//로그아웃 요청 처리
@RequestMapping("users/logout")
public String logout(HttpSession session) {
session.invalidate();
return "users/logout";
}
//개인정보 보기 요청처리
@RequestMapping("users/info")
public String info() {
return "users/info";
}
}
- 이렇게 4개의 페이지에 대한 컨트롤러를 만들어줌
- 지금은 " /users/* " 로 입력해서 하위 page를 전부 걸리도록 해놨는데, 배제할 페이지를 추가할 것.
- 로그인하기 전은 intercept할 대상에 해당되지 않으므로 login / loginform을 인터셉터에서 배제해둔다.
home에 로그인, 로그아웃, 회원정보 보기 정보 링크 추가.
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>/home.jsp</title>
</head>
<body>
<div class="container">
<h1>인덱스 페이지입니다.</h1>
<ul>
<li><a href="play">놀러가기</a></li>
<li><a href="users/loginform">로그인</a></li>
<li><a href="users/logout">로그아웃</a></li>
<li><a href="users/info">개인정보(로그인 필요)</a></li>
</ul>
<ul>
<c:forEach var="tmp" items="${noticeList }">
<li>${tmp }</li>
</c:forEach>
</ul>
</div>
</body>
</html>
뷰페이지 만들기
loginform.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>/views/users/loginform.jsp</title>
</head>
<body>
<div class="container">
<h3>로그인 폼</h3>
<form action="${pageContext.request.contextPath}/users/login" method="post">
<input type="text" name="id" placeholder="아이디 입력..." />
<button type="submit">로그인</button>
</form>
</div>
</body>
</html>
login.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>/views/users/login.jsp</title>
</head>
<body>
<div class="container">
<p>
<strong>${sessionScope.id}</strong> 님 로그인 되었습니다.
<a href="${pageContext.request.contextPath}/">인덱스로</a>
</p>
</div>
</body>
</html>
logout.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>logout.jsp</title>
</head>
<body>
<div class="container">
<p>로그아웃 되었습니다.</p>
<a href="${pageContext.request.contextPath}/">인덱스로 가기</a>
</div>
</body>
</html>
info.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>/views/users/info.jsp</title>
</head>
<body>
<div class="container">
<h3>개인정보</h3>
<p>아아디는 <strong>${id }</strong>입니다. 어쩌구 저쩌구...</p>
</div>
</body>
</html>
- 개인정보 보기에 들어가면 로그인 폼으로 자동 redirect 된다.
'국비교육(22-23)' 카테고리의 다른 글
52일차(1)/Spring(10) : 회원가입, 로그인, 로그아웃, 회원정보 보기 기능 구현 (1) | 2022.12.20 |
---|---|
51일차(2)/Spring(9) : 파일 업로드 기능 구현 / SmartEditor 적용 (0) | 2022.12.19 |
50일차(4)/Spring(7) : Service 메소드 / Todo 테이블로 실습 (1) | 2022.12.18 |
50일차(3)/Spring(6) : MyBatis / 회원정보 수정 기능 구현 (getData, update) (0) | 2022.12.18 |
50일차(2)/Spring(5) : MyBatis / 회원 추가, 삭제 기능 구현 (insert, delete) (0) | 2022.12.17 |