🔷 특정 이벤트가 발생하기를 기다리다가 실행되는 객체
💡 이벤트(event)
특정한 사건 발생(버튼 클릭, 키보드입력, 컨테이너 빌드 완료, HTTP 요청 수신 등), 이벤트 소스는 이벤트가 발생한 근원지(객체)를 뜻한다.
1. Annotation
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
/**
* Application Lifecycle Listener implementation class MyListener
*
*/
@WebListener
public class MyListener implements ServletContextListener {
/**
* Default constructor.
*/
public MyListener() {
// TODO Auto-generated constructor stub
}
/**
* @see ServletContextListener#contextDestroyed(ServletContextEvent)
*/
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("웹어플리케이션이 종료가 될때 호출 될 친구");
}
/**
* @see ServletContextListener#contextInitialized(ServletContextEvent)
*/
public void contextInitialized(ServletContextEvent sce) {
System.out.println("웹어플리케이션이 시작될때 호출 될 친구");
}
}
2. Web.xml에 추가하기
<listener>
<listener-class>com.ssafy.mvc.MyListener2</listener-class>
</listener>
<context-param>
<param-name>welcome</param-name>
<param-value>Bzeromo</param-value>
</context-param>
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
public class MyListener2 implements ServletContextListener {
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("웹어플리케이션이 종료가 될때 호출 될 친구2");
}
public void contextInitialized(ServletContextEvent sce) {
System.out.println("웹어플리케이션이 시작될때 호출 될 친구2");
ServletContext context = sce.getServletContext();
System.out.println(context.getInitParameter("welcome"));
}
}
🖨 welcome이라는 이름을 가진 파라미터의 값(Bzeromo)을 가져와 콘솔창에 출력한다.
🔷 요청과 응답 데이터를 필터링하여 제어, 변경하는 역할
FilterChain
을 통해 연쇄적으로 동작 가능💡 FilterChain은
chain.doFilter(request, response)
의 호출을 통해 다음 필터로 계속해서 이어질 수 있으며 필터체인이 끝날 때 서블릿을 호출할 수 있게끔 한다.
💡 실습 코드에서는 간단하게 인코딩만 다루겠지만 필터 체인으로 다양한 필터를 다룰 수 있다.
<filter>
<filter-name>MyFilter</filter-name>
<filter-class>com.bzeromo.mvc.MyFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>MyFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>MyFilter2</filter-name>
<filter-class>com.bzeromo.mvc.MyFilter2</filter-class>
</filter>
<filter-mapping>
<filter-name>MyFilter2</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
💡
url-pattern
에서/*
은 모든 경로의 요청이 들어올 때 필터로 이동할 것을 명한다.
🖥 MyFilter
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
public class MyFilter implements Filter {
public FilterConfig filterConfig;
public MyFilter() {
}
//필터 초기화
public void init(FilterConfig fConfig) throws ServletException {
filterConfig = fConfig;
}
//필터 종료
public void destroy() {
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
//코드를 작성하면
System.out.println("서블릿 동작 이전에 할것");
String encoding = this.filterConfig.getInitParameter("encoding");
request.setCharacterEncoding(encoding); //encoding 부분에 직접 "UTF-8"을 넣어도 된다. 다만 유연성이 떨어진다.
chain.doFilter(request, response); //다음 필터로 전달 , 서블릿 호출
System.out.println("서블릿 동작 이후에 할것");
}
}
🖥 MyFilter2
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
public class MyFilter2 implements Filter {
public MyFilter2() {
}
public void init(FilterConfig fConfig) throws ServletException {
}
//필터 종료
public void destroy() {
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
//코드를 작성하면
System.out.println("서블릿 동작 이전에 할것2");
chain.doFilter(request, response); //다음 필터로 전달 , 서블릿 호출
//코드를 작성하면
System.out.println("서블릿 동작 이후에 할것2");
}
}
🖥 MyServlet
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Servlet implementation class MyServlet
*/
@WebServlet("/MyServlet")
public class MyServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
System.out.println(request.getCharacterEncoding());
response.getWriter().append("Served at: ").append(request.getContextPath());
}
/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
*/
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
doGet(request, response);
}
}
🖨 요청이 들어오면 필터 1 -> 필터 2로 넘어가며 서블릿 동작 이전에 할 것 1, 2가 이루어진다. 모든 필터를 거치면 서블릿으로 넘어가 현재 인코딩 방식을 출력하고 서블릿 동작 이후에 할 것 1, 2가 이루어지며 종료된다.
💡 필터의 순서는 Web.xml에 작성한 순서대로 돌아간다. 애너테이션으로는 순서 지정이 굉장히 힘들어지기 때문에 필터는 보통 Web.xml을 활용한다.
🔷 HandlerInterceptor
를 구현한 것(또는 HandlerInterceptorAdapter
를 상속한 것)
요청을 처리하는 과정에서 요청을 가로채서 처리
접근 제어(Auth), 로그(Log) 등 비즈니스 로직과 구분되는 반복적이고 부수적인 로직 처리
주요 메서드
1) preHandle()
: boolean 타입, Controller 실행 이전에 호출되며 false를 반환하면 요청을 종료한다.
2) postHandle()
: Controller 실행 후 호출, 정상 실행 후 추가 기능 구현 시 사용, Controller에서 예외 발생 시 해당 메서드는 실행되지 않는다.
3) afterCompletion()
: 뷰가 클라이언트에게 응답을 전송한 뒤 실행, Controller에서 예외 발생 시, 네번째 파라미터로 전달이 되며 디폴트는 null이다. Controller에서 발생한 예외 혹은 실행 시간 같은 것들을 기록하는 등 후처리 시 주로 사용.
❗ 이번 실습부터는 bean과 필터 등이 등록되어 있는 web.xml이나 servlet-context.xml 등은 굳이 이곳에 올리지 않는다. 새 프로젝트를 만들더라도 계속해서 사용하던 것을 import해서 제작하기 때문에 그 부분은 변함이 없다. (물론 새로운 bean이나 Interceptor 등록은 이곳에 명시하지 않더라도 이루어져 있어야 한다.)
🖥 AInterceptor
package com.bzeromo.mvc.interceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
//인터셉터로 만들기 위해서
//1. 구현 : implements HandlerInterceptor (추천)
//2. 상속 : extends HandlerInterceptorAdapter (deprecated)
public class AInterceptor implements HandlerInterceptor{
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
System.out.println("A : preHandle");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
System.out.println("A : postHandle");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
System.out.println("A : afterCompletion");
}
}
🖨 pre -> post -> after 순으로 콘솔에 출력된다.
💡
BInterceptor
와CInterceptor
를 xml에 순서대로 등록하여 실행시킨다면 순서는 다음과 같다.
Apre -> Bpre -> Cpre -> Cpost -> Bpost -> Apost -> Cafter -> Bafter -> Aafter
🖥 HomeController
package com.bzeromo.mvc.controller;
import java.text.DateFormat;
import java.util.Date;
import java.util.Locale;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import org.springframework.http.HttpRequest;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
/**
* Handles requests for the application home page.
*/
@Controller
public class HomeController {
@RequestMapping(value = "/", method = RequestMethod.GET)
public String home(Locale locale, Model model) {
Date date = new Date();
DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, locale);
String formattedDate = dateFormat.format(date);
model.addAttribute("serverTime", formattedDate );
return "home";
}
//regist 라고 하는 Get요청이 들어왔을 때
@GetMapping("regist")
public String registForm() {
//로그인 유무를 파악을 하고 했으면 진행 안됐으면 돌아간다
return "regist";
}
@PostMapping("regist")
public String regist() {
//예시
//서비스를 호출해서 등록을 한다
return "index";
}
@GetMapping("login")
public String loginForm() {
return "login";
}
@PostMapping("login")
public String login(HttpSession session, String id, String pw) {
//user 관련 service를 호출해서 직접 내 사용자가 맞는지 check
if(id.equals("bzeromo") && pw.equals("1234")) {
session.setAttribute("id", id);
return "redirect:/";
}
//아니면 로그인페이지로 돌려보내기
return "redirect:/login";
}
@GetMapping("logout")
public String logout(HttpSession session) {
session.removeAttribute("id");
// session.invalidate();
return "redirect:/";
}
}
🖥 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"%>
<html>
<head>
<title>Home</title>
</head>
<body>
<c:choose>
<c:when test="${empty id}">
<a href="login">로그인 페이지</a>
</c:when>
<c:otherwise>
${id} 님 반갑습니다. <a href="logout">로그아웃</a>
</c:otherwise>
</c:choose>
<h1>Hello world!</h1>
<P>The time on the server is ${serverTime}.</P>
<!-- regist.jsp (게시글 등록) 로 보내는 링크 -->
<a href="regist">게시글 등록</a>
</body>
</html>
🖥 login.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>로그인 페이지</title>
</head>
<body>
<h2>로그인 페이지입니다.</h2>
<form action="login" method="POST">
<input type="text" name="id"><br>
<input type="password" name="pw"><br>
<input type="submit"><br>
</form>
</body>
</html>
🖥 regist.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>게시글 등록</title>
</head>
<body>
<h2>게시글 등록 화면</h2>
<form>
</form>
</body>
</html>
🖥 LoginInterceptor
package com.ssafy.mvc.interceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.web.servlet.HandlerInterceptor;
public class LoginInterceptor implements HandlerInterceptor{
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
HttpSession session = request.getSession();
if(session.getAttribute("id") == null) {
response.sendRedirect("login");
return false; //멈추고 돌려보낸다
}
return true; //컨트롤러로 요청을 보낸다
}
}
🖨 인터셉터가 작동하면 로그인이 되어있지 않을 때 어떤 페이지로 이동하려 하든 로그인 페이지로 튕긴다.
추석 이후 예비군까지
개발 감각 찾기가 쉽지가 않다!