일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- 컬렉션프레임워크
- oracle
- exception
- 추상메서드
- 자바
- 메소드오버로딩
- 다형성
- GRANT VIEW
- EnhancedFor
- 사용자예외클래스생성
- cursor문
- 오라클
- 참조형변수
- 대덕인재개발원
- 컬렉션 타입
- 자동차수리시스템
- 예외미루기
- 생성자오버로드
- 어윈 사용법
- 한국건설관리시스템
- 객체 비교
- 제네릭
- abstract
- Java
- 인터페이스
- 정수형타입
- NestedFor
- 예외처리
- 집합_SET
- 환경설정
- Today
- Total
거니의 velog
231208_SPRING 2 (13-2) 본문
* 인터셉트(intercept)
- 클라이언트에서 서버로 요청을 보낼 때, DispatcherServlet이 맨 먼저 받는다. 각각의 핸들러, 리퀘스트 매핑 정보 등을 조합하여 응답에 대한 뷰를 만들어서 디스패처가 내보낸다.
- 여기서, 필터라고 하는 녀석을 이용해서 인코딩 설정, 로그 기록, 세션 관리 등을 진행했다.
- 문제 : 클라이언트와 서버 라고 하는 전반적인 영역 안에서 필터의 위치가 어딜까?
- 필터가 스프링 자원을 가용하기 위한 옵션 설정이 매우 어렵다. 그래서 AOP라는 녀석을 활용했다.
- 서비스 타겟 실행 이전에 AOP가 실행. 중간에 프록시가 동작. 위치는 어디? 서버 안에 타겟이 들어 있다. 그래서 스프링 자원을 충분히 가용 가능.
- 단, AOP는 대규모 시스템에서는 사용이 가능하나, 소규모 시스템은 부적절할 수 있다.
- 세션, 로깅 처리를 좀 더 보편적으로 만들어내기 적합한 것은 인터셉터. 디스패처 서블릿과 컨트롤러 사이에 있는 것.
- web.xml 에 이 인터셉터들을 설정해 주면, 특정 URL에 해당하는 규칙들이 정해짐. 이게 동작하는 규칙이 설정되면 인터셉터가 가동됨.
- 총 3 가지 동작 시점이 있다.
package kr.or.ddit.controller.intercept;
public class InterceptorController {
/*
* [ 17장: 인터셉터 ]
*
* - 인터셉터는 웹 애플리케이션 내에서 특정한 URI 호출을 가로채는 역할을 한다.
*
* 1. 인터셉터 설명
*
* 필터와 인터셉터
* - 서블릿 기술의 필터와 스프링 MVC의 인터셉터는 특정 URI에 접근할 때 제어하는 용도로 사용된다는 공통점이 있다.
* 하지만 실행 시점에 속하는 영역(Context)에 차이점이 있다.
* 인터셉터의 경우 스프링에서 관리하기 때문에 스프링 내의 모든 객체에 접근이 가능하지만 필터는 웹 애플리케이션 영역 내의
* 자원들을 활용할 수 있지만 스프링 내의 객체에는 접근이 불가능하다.
*
* 스프링 AOP와 인터셉터
* - 특정 객체 동작의 사전 혹은 사후 처리는 AOP 기능을 활용할 수 있지만, 컨트롤러의 처리는 인터셉터를 활용하는 경우가 많다.
* AOP의 어드바이스와 인터셉터의 가장 큰 차이는 파라미터의 차이라고 할 수 있다.
* 어드바이스의 경우 JoinPoint나 ProceedingJoinPoint 등을 활용해서 호출 대상이 되는 메소드의 파라미터 등을
* 처리하는 방식이다. 인터셉터는 필터와 유사하게 HttpServletRequest, HttpServletResponse를 파라미터로
* 받는 구조이다.
*
* HandlerInterceptorAdaptor 클래스
* - HandlerInterceptorAdaptor 는 HandlerInterceptor를 쉽게 사용하기 위해서 인터페이스의 메소드를 미리 구현한 클래스이다.
*
* HandlerInterceptor의 메소드는 아래와 같다.
* - preHandle
* > 지정된 컨트롤러의 동적 이전에 가로채는 역할을 한다.
* - postHandle
* > 지정된 컨트롤러의 동작 이후에 처리, DispatcherServlet이 화면을 처리하기 전에 동작한다.
* - afterCompletion
* > DispatcherServlet의 화면 처리가 완료된 상태에서 처리한다.
*
* 2. 인터셉터 구현
*
* 클라이언트의 요청을 처리하다 보면 요청 경로마다 접근 제어를 다르게 하거나, 특정 URL에 대한 접근 내역을 기록하고 싶을 떄가
* 있다. 이런 기능은 특정 컨트롤러에 종속되기 보다는 여러 컨트롤러에서 공통적으로 적용되는 기능들이라 하겠다.
* 이런 기능을 각 컨트롤러에서 개별적으로 구현하면 중복 코드가 발생하므로, 코드 중복 없이 기존의 컨트롤러에 수정을 가하지 않고
* 적용할 수 있는 방법이 필요하다. 지금까지 배운 내용들을 떠올려보면 이를 해결할 수 있는 방식 2가지가 존재하는데...!
* (작성하면서 어떤 방법이 있는지 머릿 속에 떠오르길 바란다!)
*
* 그 첫 번째로 Filter라는 서블릿 스펙에 따른 객체를 사용하는 방법이 있다. 필터를 통해 DispatcherServlet이나
* 컨트롤러에게 요청이 위임되기 전/후에 공통적인 어떤 기능을 수행하도록 하면, 기존 컨트롤러나 DispatcherServlet에
* 어떤 수정 사항을 가하거나 코드 중복 없이 이슈를 해결할 수 있을 것이다. 그러나 이 방법은 한 가지 단점이 있다.
* 바로 Filter가 DispatcherServlet 보다 먼저 객체가 생성되고 스프링 컨테이너 밖에 존재하기 때문에 컨테이너를 통한
* DI를 받을 수 없다는 점이다. 물론 아예 불가능 하지는 않다. 설정이나 이런 부분이 복잡하고 귀찮을 뿐이다...
* DelegatingFilterProxy 필터를 사용하면 필터링 작업을 스프링 컨테이너에 존재하는 빈에게 위임할 수도 있다.
* 해당 DelegatingFilterProxy라는 필터는 원래 필터 기반의 보안 처리를 지원하는 Spring Security 프레임워크에서
* 제공되었던 필터인데 하도 널리 쓰이기 시작하면서 아예 스프링 코어 웹 모듈로 편입된 타입이다. 그런데 이 필터를 이용해서
* 스프링 빈에게 필터링을 위임하는 방법도 몇 가지 불편한 점들이 있다.
* 반드시 위임을 받을 빈을 Filter를 구현하고 있어야 하고, root-context.xml에서 관리되는 빈이어야만 한다는 것이다.
*
* 두 번째로 AOP방법론에 따른 공통 기능을 정의한 Advice를 구현하고, pointcut을 통해 적절한 target 컨트롤러를 골라낸 다음
* 두 설정으로 Aspect를 생성해야 런타임에 컨트롤러와 위빙하도록 하는 방법을 생각해 볼 수 있다. 실제 보안 프레임워크들에서도
* AOP 방법론에 따라 위빙을 위한 어노테이션을 활용하고 있기는 하다.
*
* 그렇지만, 첫번째/두번째 모두 우리가 처리하고 싶은 기능을 구현하는 데 제약이 따른다.
* 우리가 지금 처리하고 싶은 기능은 특정 URL에 대한 접근 내역을 기록하거나 특정 경로에 대한 접근 제어를 하는 등 웹이라는
* 공간과 환경에 제한된 공통 기능을 처리하고 싶은 것이다. AOP는 너무 범용적인 방법이라 할 수 있고, Filter 방식은
* 제약이 많다. (사용할 자원의 환경이 다르기 때문이다)
*
* 이러한 경우에 사용하기 위한 전략으로 스프링은 HandlerInterceptor라는 추상화를 제공하고 있으며, 이를 사용하면
* Spring MVC에 맞게 공통 기능을 다수의 URL에 적용할 수 있게 된다.
* HandlerInterceptor 인터페이스를 사용하면 아래와 같은 시점에 대해 공통 기능을 넣을 수 있다.
* > 컨트롤러 실행 전(preHandle)
* > 컨트롤러 실행 후, 아직 뷰를 실행하기 전 단계이다. (postHandle)
* > 뷰를 실행한 이후(afterCompletion)
*
* preHandle() 메소드는 컨트롤러 객체를 실행하기 전에 필요한 기능을 구현할 때 사용되며, handler 파라미터는 웹 요청을
* 처리할 컨트롤러 객체이다. 이 메소드를 사용하면 컨트롤러를 실행하기 전에 컨트롤러에서 필요로 하는 정보를 생성하거나
* 접근 권한이 없는 경우, 리턴값을 false를 반환하여 컨트롤러가 실행되지 않도록 하는 작업이 가능하다.
* postHandle() 메소드는 컨트롤러가 정상적으로 실행된 이후에 추가 기능을 구현할 때 사용되는데, 만약 컨트롤러에서
* 예외가 발생했다면 postHandle() 메소드는 실행되지 않는다.
* afterCompletion() 메소드는 클라이언트의 뷰를 전송한 뒤에 실행되며, 만약 컨트롤러를 실행하는 관점에서 예외가
* 발생했다면, 이 메소드의 네 번째 파라미터로 전달된다.
* 예외가 발생하는 경우는 null 값이 들어올 것이다. 따라서 컨트롤러 실행 이후 예기치 않은 예외에 대해 로깅을 한다거나
* 실행 시간을 기록하는 등의 후처리를 하기에 적합한 메소드이다.
*
* 정리하자면 HandlerInterceptor는 세 가지 메소드를 통해 AOP 방법론 시점에 따른 여러 Advice들의 역할을
* 하나의 HandlerInterceptor 객체가 전담할 수 있는 구조를 가지고 있다!!!
*
* 3. 인터셉터 설정
*
* - 인터셉터 클래스를 정의하고 스프링 웹 설정 파일에 인터셉터를 지정한다.
*
* 인터셉터 지정
* - servlet-context.xml에서 설정
* > loginInterceptor 아이디로 빈 등록
* > interceptor 태그 설정
*/
}
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!-- DispatcherServlet Context: defines this servlet's request-processing infrastructure -->
<!-- Enables the Spring MVC @Controller programming model -->
<annotation-driven />
<!-- Handles HTTP GET requests for /resources/** by efficiently serving up static resources in the ${webappRoot}/resources directory -->
<resources mapping="/resources/**" location="/resources/" />
<!-- Resolves views selected for rendering by @Controllers to .jsp resources in the /WEB-INF/views directory -->
<beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<beans:property name="prefix" value="/WEB-INF/views/" />
<beans:property name="suffix" value=".jsp" />
<beans:property name="order" value="2" />
</beans:bean>
<!-- Tiles 설정을 위한 Bean 등록 시작 -->
<beans:bean id="tilesViewResolver" class="org.springframework.web.servlet.view.UrlBasedViewResolver">
<beans:property name="viewClass" value="org.springframework.web.servlet.view.tiles3.TilesView" />
<beans:property name="order" value="1" />
</beans:bean>
<beans:bean id="tilesConfigurer" class="org.springframework.web.servlet.view.tiles3.TilesConfigurer">
<beans:property name="definitions">
<beans:list>
<beans:value>/WEB-INF/spring/tiles-config.xml</beans:value>
</beans:list>
</beans:property>
</beans:bean>
<!-- Tiles 설정을 위한 Bean 등록 끝 -->
<context:component-scan base-package="kr.or.ddit.controller" />
<!--
서블릿 표준용 MultipartResolver 를 스프링 빈으로 정의
- StandardServletMultipartResolver 사용 시 설정
> Servlet 3.0의 Part를 이용한 MultipartFile 데이터 처리
-->
<!-- <beans:bean id="multipartResolver" class="org.springframework.web.multipart.support.StandardServletMultipartResolver">
</beans:bean> -->
<!--
인터셉터 설정
- loginInterceptor 클래스를 빈으로 정의한다.
> 설정한 클래스는 해당 위치에 존재해야 함(설정에 맞는 위치에 있어야 한다)
-->
<beans:bean id="loginInterceptor" class="kr.or.ddit.controller.intercept.LoginInterceptor" />
<interceptors>
<interceptor>
<mapping path="/login1" />
<beans:ref bean="loginInterceptor" />
</interceptor>
</interceptors>
</beans:beans>
package kr.or.ddit.controller.intercept;
import java.lang.reflect.Method;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.ui.ModelMap;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class LoginInterceptor extends HandlerInterceptorAdapter {
/*
* LoginInterceptor 상황 시나리오
*
* 'http://localhost/login1' 을 요청해 로그인 페이지를 요청한다.
* 이 때, 해당 컨트롤러 메소드가 실행되기 전에 LoginInterceptor의 preHandle이 동작하고, 'userInfo' 세션명을
* 가진 세션 정보를 조회한다. 조회된 정보가 존재한다면, 세션을 삭제처리한다.
* 그리고 타겟을 거쳐 다시 인터셉터로 넘어올 때 데이터 전달자가 전달해준 회원 정보가 존재한다면 'userInfo' 키로
* 회원 정보를 세션에 등록하고 '/'로 리다이렉트한다. 그렇지 않은 경우에는 해당 타겟을 정상 실행 후 리턴하는 결과 페이지로 이동한다.
*/
private static final String USER_INFO = "userInfo";
// 지정된 컨트롤러의 동작 이전에 가로채는 역할로 사용한다.
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
log.info("preHandle() 실행...!");
String requestURL = request.getRequestURL().toString(); // http://localhost/login1
String requestURI = request.getRequestURI().toString(); // login1
log.info("requestURL : " + requestURL);
log.info("requestURI : " + requestURI);
HandlerMethod method = (HandlerMethod) handler;
Method methodObj = method.getMethod();
// kr.or.ddir.controller.login.LoginController@312r2dta
log.info("Bean : " + method.getBean());
// public java.lang.String.kr.or.ddir.controller.login.LoginController.loginForm()
log.info("method : " + methodObj);
HttpSession session = request.getSession();
if(session.getAttribute(USER_INFO) != null) {
session.removeAttribute(USER_INFO);
}
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
log.info("postHandle() 실행...!");
String requestURL = request.getRequestURL().toString(); // http://localhost/login1
String requestURI = request.getRequestURI().toString(); // login1
log.info("requestURL : " + requestURL);
log.info("requestURI : " + requestURI);
HandlerMethod method = (HandlerMethod) handler;
Method methodObj = method.getMethod();
// kr.or.ddir.controller.login.LoginController@312r2dta
log.info("Bean : " + method.getBean());
// public java.lang.String.kr.or.ddir.controller.login.LoginController.loginForm()
log.info("method : " + methodObj);
HttpSession session = request.getSession();
// 컨트롤러 메소드를 거쳤다가 postHandle 로 넘어오면서 전달된 user라는 키에 value로 member가 담긴 값이
// Model에 담겨져 있다. 그 중에 'user'로 넘긴 값이 로그인 후 인증된 회원 1명의 정보가 담긴 MemberVO 자바빈즈 객체가 되고
// 객체가 null이 아닌 경우 메인 화면을 리다이렉트 처리한다.
ModelMap modelMap = modelAndView.getModelMap();
Object member = modelMap.get("user");
if(member != null) {
log.info("member : " + member);
log.info("member != null");
session.setAttribute(USER_INFO, member);
response.sendRedirect("/");
}
}
// DispatcherServlet의 화면 처리가 완료된 상태에서 처리한다.
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
log.info("afterCompletion() 실행...!");
String requestURL = request.getRequestURL().toString(); // http://localhost/login1
String requestURI = request.getRequestURI().toString(); // login1
log.info("requestURL : " + requestURL);
log.info("requestURI : " + requestURI);
HandlerMethod method = (HandlerMethod) handler;
Method methodObj = method.getMethod();
// kr.or.ddir.controller.login.LoginController@312r2dta
log.info("Bean : " + method.getBean());
// public java.lang.String.kr.or.ddir.controller.login.LoginController.loginForm()
log.info("method : " + methodObj);
}
}
package kr.or.ddit.controller.intercept;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import kr.or.ddit.vo.crud.CrudMember;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Controller
public class LoginController {
@RequestMapping(value = "/login1", method = RequestMethod.GET)
public String loginForm() {
return "login/loginForm";
}
@RequestMapping(value = "/login1", method = RequestMethod.POST)
public String login(String userId, String userPw, Model model) {
CrudMember member = new CrudMember();
member.setUserId(userId);
member.setUserPw(userPw);
member.setUserName("홍길동");
model.addAttribute("user", member);
return "login/success";
}
}
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Login Form</title>
</head>
<body>
<h2>Login Form</h2>
<form action="/login1" method="post">
아이디 : <input type="text" name="userId" /><br />
비밀번호 : <input type="text" name="userPw" /><br />
<button type="submit">전송</button>
</form>
</body>
</html>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>SUCCESS</title>
</head>
<body>
<h1>로그인 되었습니다!</h1>
</body>
</html>
- http://localhost/login1
@Slf4j
@Controller
public class HomeController {
private static final Logger logger = LoggerFactory.getLogger(HomeController.class);
@RequestMapping(value = "/", method = RequestMethod.GET)
public String home(Locale locale, Model model) {
logger.info("Welcome home! The client locale is {}.", locale);
Date date = new Date();
DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, locale);
String formattedDate = dateFormat.format(date);
model.addAttribute("serverTime", formattedDate );
return "home";
}
* 정상적으로 동작하고 있지 않다. 왜? 현재 어느 위치에 있는가? ddit 아래. 이 녀석 빈 등록되었나? AOP 도입하면서 basepackage 구조가 바뀌었다. 즉, controller 패키지 안에 들어가야 한다.
[servlet-context.xml]
<!--
인터셉터 설정
- loginInterceptor 클래스를 빈으로 정의한다.
> 설정한 클래스는 해당 위치에 존재해야 함(설정에 맞는 위치에 있어야 한다)
-->
<beans:bean id="loginInterceptor" class="kr.or.ddit.controller.intercept.LoginInterceptor" />
<interceptors>
<interceptor>
<mapping path="/login1" />
<beans:ref bean="loginInterceptor" />
</interceptor>
</interceptors>
<beans:bean id="accessLoggingInterceptor" class="kr.or.ddit.controller.intercept.AccessLoggingInterceptor" />
<interceptors>
<interceptor>
<mapping path="/**" />
<exclude-mapping path="/resources/**"/>
<beans:ref bean="accessLoggingInterceptor" />
</interceptor>
</interceptors>
package kr.or.ddit.controller.intercept;
import java.io.File;
import java.io.FileWriter;
import java.io.PrintWriter;
import java.lang.reflect.Method;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class AccessLoggingInterceptor extends HandlerInterceptorAdapter {
PrintWriter writer;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
log.info("preHandle() 실행...!");
File file = new File("C:/logs/ddit-logging.log");
writer = new PrintWriter(new FileWriter(file, true), true);
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
log.info("postHandle() 실행...!");
String requestURI = request.getRequestURI();
log.info("requestURI : " + requestURI);
HandlerMethod handlerMethod = (HandlerMethod) handler;
Method method = handlerMethod.getMethod();
// class kr.or.ddit.controller.BoardController 와 같은 녀석
Class clazz = method.getDeclaringClass();
// kr.or.ddit.controller.BoardController 와 같은 녀석
String className = clazz.getName();
// BoardController 와 같은 녀석
String classSimpleName = clazz.getSimpleName();
// boardList와 같은 메서드
String methodName = method.getName();
writer.printf("현재일시 : %s %n", getCurrentTime());
writer.printf("Access Controller : %s %n", className + "." + methodName);
writer.println("==========================================");
}
// 현재 일시 가져오기
private String getCurrentTime() {
DateFormat formatter = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
Calendar cal = Calendar.getInstance();
cal.setTimeInMillis(System.currentTimeMillis());
return formatter.format(cal.getTime());
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
log.info("afterCompletion() 실행...!");
super.afterCompletion(request, response, handler, ex);
}
}
- http://localhost/board/tag/list.do
'대덕인재개발원 > 대덕인재개발원_웹기반 애플리케이션' 카테고리의 다른 글
231212_SPRING 2 (15-1) (0) | 2023.12.11 |
---|---|
231211_SPRING 2 (14-1) (0) | 2023.12.11 |
231208_SPRING 2 (13-1) (0) | 2023.12.08 |
231207_SPRING 2 (12-2) (1) | 2023.12.07 |
231207_SPRING 2 (12-1) (1) | 2023.12.07 |