00. 화면 준비 myPage.jsp
▼ jsp에서는 항상 아래의 라이브러리가 있으면 유용하다.
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>
<c:set var="path" value="${ pageContext.request.contextPath }" />
01. MemberController.java
- 일단 myPage로 이동하는 URL에 대한 Get 처리(해당하는 URL이 ("/member/myPage") 이고, 메소드가 get 일 때 처리할 메소드를 만든다.)
- 디스패처 서블릿이 뷰 리졸버를 통해서 return하는 이름을 가진 view로 포워딩 시킨다.
@GetMapping("/member/myPage")
public String myPage() {
return "/member/myPage";
}
01-1. 로그인한 사람한테만 마이페이지 정보수정이 가능하도록 처리
- 로그인하고 나서야, 정보수정 하는 페이지로 이동시킬 수 있도록, 버튼이나 <a> 활용해서 연결하기
- home.jsp에 <c:if> 태그 활용
<c:if test="${ !empty loginMember }">
<a href="${ path }/member/myPage">
${ loginMember.name }
</a>님, 안녕하세요.
<form action="${ path }/logout" method="get">
<button type="submit">로그아웃</button>
</form>
</c:if>
01-2. 로그아웃하고나서 URL 접근이 안되게 처리
Spring에서 제공해주는 Interceptor를 사용해보자.
https://goddaehee.tistory.com/154
filter와 Interceptor의 차이
-> spring 자원에 접근 가능 여부
▷ filter : wep.xml에 설정한 것, 빈 자원을 사용할 수 없이 스프링 차원에 접근할 수 없다. (사용자 요청이 서블릿으로 들어가기 전에 처리하는 것)
▷ Interceptor : 빈 자원을 사용하기 위해 스프링 차원에 접근할 수 있다. (컨트롤러로 넘어가기 전에 요청을 가로채는 것)
▶ Interceptor
- 디스패처 서블릿과 컨트롤러 사이에서 요청을 가로채서 컨트롤러 전에 처리해야 할 작업을 처리하는역할
▷ preHandle()
- 컨트롤러가 실행되기 전에 필요한 작업을 할 수 있는 메소드
- 반환 값이 false일 경우 컨트롤러를 실행하지 않고, 이 단계에서 요청을 끝낸다.
▷ postHandle()
- 컨트롤러가 실행된 후에 필요한 작업을 할 수 있는 메소드
▷ afterCompletion()
- 컨트롤러의 처리가 끝나고 화면(View) 처리(랜더링)까지 모두 완료되면 실행되는 메소드
▷ afterConcurrentHandlingStarted()
- 비동기 요청(서블릿 3.0 이상) 시 postHandle, afterCompletion이 수행되지 않고 afterConcurrentHandlingStarted 메소드가 실행된다.
- preHandle() -> afterConcurrentHandlingStarted() 실행
02. LoginCheckInterceptor.java
- com > kh > mvc > common > interceptor 밑에 클래스 생성
- pulic class LoginCheckInterceptor extends HandlerInterceptorAdapter를 상속하도록 한다.
Alt + Shift + S 단축키 눌러서
다음처럼 체크하고 Finish해서 메소드 생성
- 필터를 만든다고 해서 모든 요청에 대해서 서블릿 필터를 태우지 않고, filter 클래스를 만들고 xml에 filter mapping 해주거나 어노테이션을 통해서 어떤 요청, 어떤 서블릿을 수행할 때 필터를 태울지 xml 파일에 지정한 것처럼
- 인터셉터도 요청 사이에서 어떤 작업을 가로채서 필요한 여러 작업들을 할 때 하기 위해서 사용하게 되고, 모든 요청에 대해서 적용할 필요 없기 때문에 어떤 요청에 대해서 해당 인터셉터를 태울 것인지 지정해야 한다. 즉, 인터셉터가 적용될 요청을 지정한다.
03. servlet-context.xml
▶ 인터셉터 설정
- 인터셉터가 웹 관련 설정(in 스프링 영역)이기 때문에 root-context.xml이 아닌 servlet.context.xml에 작성한다.
<interceptors>
<interceptor>
<!-- 인터셉터를 적용시킬 요청(컨트롤러) 선택 -->
<mapping path="/member/myPage"/>
<!-- 인터셉터 등록 -->
<beans:bean id="LoginCheckInterceptor" class="com.kh.mvc.common.interceptor.LoginCheckInterceptor"/>
</interceptor>
</interceptors>
04. LoginCheckInterceptor.java
- Dispatcher Servlet에서 myPage(Controller)쪽으로 가기전에 Interceptor에서 요청을 가로채서 로그인 했는지 안했는지를 체크할 것이다.
- 즉, 컨트롤러를 태윅 전에 로그인 체크가 먼저여야 하는 것
- 로그인 체크하는 것이기 때문에 preHandle()에 작성
- 세션 영역에서 loginMember를 가져와서,
- loginMember가 null이면 컨트롤러를 태우지 않고 msg쪽으로 직접 포워딩 시킨다.
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
// 컨트롤러가 실행되기 전에 필요한 작업을 할 수 있는 메소드
// 반환 값이 false일 경우 컨트롤러를 실행하지 않는다.
log.info("preHandle() call..");
// attribute에서 리턴하는 데이터 타입은 Object이기 때문에 형변환 필수
Member loginMember = (Member) request.getSession().getAttribute("loginMember");
if(loginMember == null) {
// 컨트롤러를 태우는게 아니라 msg로 포워딩 시키기
request.setAttribute("msg", "로그인 후 이용이 가능합니다.");
request.setAttribute("location", "/");
request.getRequestDispatcher("/WEB-INF/views/common/msg.jsp").forward(request, response);
// 컨트롤러 실행되지 않도록 한다.
return false;
}
return super.preHandle(request, response, handler);
}
[ 여기까지 정리 ]
▶ 인터셉터
- 컨트롤러에 들어오는 요청(HttpRequest)과 응답(HttpResponse)을 가로채는 역할을 한다.
- 인터셉터를 구현하기 위해서는 HandlerInterceptorAdapter 클래스를 상속하는 방법으로 구현해야 한다.
- 인터셉터는 DispatcherServlet 수행 후에 컨트롤러에 요청을 넘기기 전에 실행된다.
- 스프링 자원을 이용할 수 있다.
- Servlet-context.xml에 설정한다.
▶ 필터
- 필터는 Servlet 수행 전에 실행된다.
- 스프링 자원을 이용할 수 없다.
- web.xml에 설정한다.
※ 언제 필터, 인터셉터, AOP를 사용하나요?
- 필터 : 요청이 넘어오기 전에 처리해야 할 사항들(인코딩, 보안 등)
- 인터셉터 : 요청이 처리되기 전에 수행하고, 스프링 자원을 사용해야할 경우
-> 어떤 요청에 대해서 처리해야 할 것이 있을 때,
- AOP : 비즈니스 로직을 수행하기 전, 후에 코드가 삽입되어야 할 경우
-> 어떤 비즈니스 로직에 대해서 적용되어야 할 부분이 있을 경우(Service 클래스 앞 뒤로 감싸는 역할)
여기까지 구현 되었으면, 이제 실제로 회원 정보의 값을 변경해서 완료 버튼을 누르면 /member/update로 요청을 보내고, 요청을 받아서 회원 정보를 수정하는 로직을 짜보자.
05. MemberController.java
@PostMapping("/member/update")
public String update(
@ModelAttribute Member member) {
log.info(member.toString());
return "member/myPage";
}
- 하나의 객체로 사용자가 post 날리는 정보를 받아서 잘 받아오도록 하려면 @ModelAttribute 어노테이션 사용한다. 그러면 스프링에서 자동으로 객체를 만들고, setter를 통해서 값을 주입한다.
- 이 때, 조건은 실제 만드는 객체의 필드의 필드명과, 사용자가 보내는 데이터의 name 속성의 값이 동일해야 한다. (동일하지 않다면 같도록 바꾼다.)
- 정보를 수정하는 것도, 로그인 되어있는 회원만이 가능하기 때문에 인터셉터를 적용할 수 있도록 만들자.
<interceptors>
<interceptor>
<!-- 인터셉터를 적용시킬 요청(컨트롤러) 선택 -->
<mapping path="/member/myPage"/>
<mapping path="/member/update"/>
<!-- 인터셉터 등록 -->
<beans:bean id="LoginCheckInterceptor" class="com.kh.mvc.common.interceptor.LoginCheckInterceptor"/>
</interceptor>
</interceptors>
06. MemberServiceImpl.java
- update 로직을 수행하려면 member 객체의 PK인 No가 0이 아니여야 한다.
- 그러니까 사용자로부터 값을 가져올 때, no 값도 가져와야 한다.
[ 처리 방법 ]
1) myPage.jsp에서 hidden으로 no 값을 포함시켜서 보낸다.
<div id="view-container">
<form id="memberFrm" action"${ path }/member/update" method="post">
<input type="hidden" name="no" value="${ loginMember.no }">
이하 생략
07. MemberController.java
2) session 영역의 loginMember를 가져와서 member의 no를 찾아오기
@PostMapping("/member/update")
public ModelAndView update(
ModelAndView model,
@SessionAttribute(name="loginMember") Member loginMember,
@ModelAttribute Member member) {
int result = 0;
member.setNo(loginMember.getNo());
result = service.save(member);
if(result > 0) {
// ▼ 세션을 갱신하는 작업
model.addObject("loginMember", service.findMemberById(loginMember.getId()));
model.addObject("msg", "회원 정보 수정을 완료했습니다.");
model.addObject("location", "/member/myPage");
} else {
model.addObject("msg", "회원 정보 수정이 실패했습니다.");
model.addObject("location", "/member/myPage");
}
model.setViewName("common/msg");
return model;
}
- @SessionAttribute 어노테이션 활용
- ModelAndView를 매개 값으로 받기(컨트롤러에서 view로 전달해줄 데이터 + view에 대한 정보를 저장한 객체 -> 그 정보들을 디스패처 서블릿한테 전달해준다.)
- result 결과에 따라서 업데이트 성공 여부를 판단한다. 그에 따라서 msg를 세팅한다.
- model에서 포워딩할 view의 이름을 지정한다. by setViewName, 그리고 리턴
- 업데이트 된 정보는 어디에 담아줄까요? => Model에
- WHY? loginMember(대신 이름이 이거여야 함)가 model에 들어가도 scope 는 session으로 확장되어 있기 때문에 session 객체를 만들 필요가 없다.
08. MemberServiceImpl.java
- mapper에 실행하라고 할 메소드 이름은 mapper.xml에 등록한 해당 쿼리문의 id와 같아야 한다.
@Override
@Transactional
public int save(Member member) {
int result = 0;
if(member.getNo() != 0) {
// update
result = mapper.updateMember(member);
} else {
// insert
member.setPassword(passwordEncoder.encode(member.getPassword()));
result = mapper.insertMember(member);
}
// if(true) {
// throw new RuntimeException();
// }
return result;
}
09. MemberServiceImpl.java
- 리팩토링 해보자.
- 적용해야 할 URL이 굉장히 많은 경우, 일일히 적용하기 보다는 적용시키지 않을 요청을 빼서 적는게 더 효율적인 경우에 <exclude-mapping> 태그를 활용한다.
<interceptors>
<interceptor>
<!-- 인터셉터를 적용시킬 요청(컨트롤러) 선택 -->
<mapping path="/member/**"/>
<!-- 인터셉터를 제외시킬 요청(컨트롤러) 선택 -->
<exclude-mapping path="/member/enroll"/>
<exclude-mapping path="/member/idCheck"/>
<!-- 인터셉터 등록 -->
<beans:bean id="LoginCheckInterceptor" class="com.kh.mvc.common.interceptor.LoginCheckInterceptor"/>
</interceptor>
</interceptors>
[ 와일드카드 /* , /** 의 차이점 ]
/member/*
- /member/insert (O)
- /member/update (O)
- /member/insert/10 (X)
- /member/update/user (X)
/member/**
- /member/insert (O)
- /member/update (O)
- /member/insert/10 (O)
- /member/update/user (O)
'Programming > Framework' 카테고리의 다른 글
DAY 160. Spring MVC 게시판 페이징 (0) | 2022.01.26 |
---|---|
DAY 159. Spring 회원 탈퇴 (0) | 2022.01.25 |
DAY 157. Spring ID 중복 검사 (0) | 2022.01.23 |
DAY 156. Spring 회원 가입 기능 (0) | 2022.01.22 |
DAY 155. Spring 로그인 기능 (0) | 2022.01.21 |