Controller에서 공통되는 작업이나 부가기능을 모아 관리하기 위해 aop 를 사용한다고 배웠었다
➡️aop
와 비슷하게 공통 프로세스를 처리하는Interceptor
사용해보기
Interceptor
를 지원하는@Component
클래스를 생성한다HandlerInterceptor
interface를 구현해야 한다
(또는 HandlerInterceptorAdapter 상속)
➡️ HandlerInterceptor 를 CustomHandlerInterceptor 에서 구현Interceptor
를 사용하기 위한 3가지 추상 메서드를 구현한다.
preHandle()
: 컨트롤러에 요청을 보내기 전 작업을 수행시 사용
이 메서드가 클라이언트에게 응답을 반환하려면, true를 리턴해야함postHandle()
: 클라이언트에 응답을 보내기 전 작업을 수행시 사용afterCompletion()
: 요청 및 응답 완료 후 작업을 수행시 사용
@Configuration
= 서버를 구동할때 미리 자동으로 수행된다
➡️ application.properties(=환경설정)에 등록된것과 같다
WebMvcConfigurer 인터페이스를 상속받아 오버라이드 메서드
addInterceptors
를 사용
1. 인터셉터 클래스를 등록, 필터할 url주소를 설정, 제거, 추가 하기 위해HandlerInterceptor
type의Interceptor
객체가 필요하다
registry.addInterceptor(직접 생성한 인터셉터 객체)
➡️ 생성된CustomHandlerInterceptor
를@Autowired
하여 사용
addPathPatterns
= 사용할 페이지
excludePathPatterns
= 필터(제외)할 페이지
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
// 생성한 handler 사용
@Autowired
CustomHandlerInterceptor handlerInterceptor;
// 인터셉터 클래스를 등록, 필터할 url주소를 설정, 제거, 추가
@Override
public void addInterceptors(InterceptorRegistry registry) {
// registry.addInterceptor(직접 생성한 인터셉터 객체)
registry.addInterceptor( handlerInterceptor )
.addPathPatterns("/**") // 전체 페이지 사용(기록된다)
// 필터할 주소 설정 (사용하지 않을 페이지 설정)
.excludePathPatterns("/seller/**", "/admin/**", "/member/**", "/item_image/**" );
// item_image도 주소로 취급되기 때문에 지워준다
}
}
생성후 어노테이션 =
@Component
명시
CustomLoginSuccessHandler.java / CustomLogoutSuccessHandler.java
➡️@Component
명시해줘야 한다
postHandle
은 파라미터에 ModelAndView
가 있으니
외부에서 들어오는 데이터를 저장/공유 가능하다는것을 예측할 수 있다
= Controller 진입 후 view 표시전에 수행
preHandle
은 model이 없어 데이터를 저장/공유할 수는 없지만
Controller에서 이루어지는 공통작업들을 Controller보다 먼저 수행하도록 한다
@Component
@Slf4j
public class CustomHandlerInterceptor implements HandlerInterceptor {
final String format = "INTERCEPTOR => {}";
// 컨트롤러 진입 후 view 표시전에 수행
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
log.info(format, "postHandle");
}
// 컨트롤러 진입 전에 수행
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
// 1. 세션 가져오기
HttpSession httpSession = request.getSession();
if( request.getQueryString() == null ){
httpSession.setAttribute("CURRENT_URL", request.getContextPath() );
}
else {
httpSession.setAttribute("CURRENT_URL", request.getContextPath() + request.getServletPath() + "?" + request.getQueryString());
}
log.info(format, "preHandle");
// 하위 3개 내용으로 url판별 가능
// http://127.0.0.1:8080/BOOT1/
// InterceptorConfig.java에서 제외한 기록은 출력되지 않는다
log.info(format, request.getContextPath()); //출력확인 결과 => /BOOT1
log.info(format, request.getServletPath()); //출력확인 결과 => /
log.info(format, request.getQueryString()); //출력확인 결과 => null *?r가 없으면 null, ?가 있으면 no=14등 해당 번호출력
return HandlerInterceptor.super.preHandle(request, response, handler);
}
}
Application.java
@ComponentScan
에 handler추가
➡️"com.example.handler"
💡 @Autowired
사용하려면 application에 등록을 해야된다
기본적으로는 @Autowired
나 @Bean
으로 객체 생성후
Application에 등록하고 사용하는게 올바른 방법이다
SecurityConfig.java에서
customLoginSuccessHandler/CustomLogoutSuccessHandler 사용하려면
@Autowired
해서 사용해야 하는데
@Autowired
CustomLoginSuccessHandler customLoginSuccessHandler;
@Autowired
CustomLogoutSuccessHandler customLogoutSuccessHandler;
CustomLoginSuccessHandler.java / CustomLogoutSuccessHandler.java 에서
@Autowired
사용을 위해 어노테이션 = @Component
를 명시해 주어야 한다
@Component
명시해주어야 @Autowired
사용 가능하다
서버 실행시
preHandle
⇒postHandle
핸들 순서로 실행되는것을 터미널에서 확인할 수 있다
⇒ 메인페이지 접속시 터미널 출력결과
excludePathPatterns
를 사용하여 제외했던 페이지인seller
페이지에 들어가면
Interceptor
가 필터되어 터미널창에 출력되지 않는것을 확인할 수 있다
⇒ 판매자 페이지 로그인 후 접속시 터미널 출력결과
고객용 물품조회 목록 생성
public List<ItemDTO> selectItemList();
<!-- 고객용 물품목록, 12개, 가격낮은순 -->
<select id="selectItemList" resultType="com.example.dto.ITEMDTO">
SELECT * FROM(
SELECT I.*, ROW_NUMBER() OVER(ORDER BY PRICE ASC) ROWN FROM ITEMTBL I
) WHERE ROWN BETWEEN 1 AND 12 ORDER BY ROWN ASC;
</select>
List<itemDTO> = imapper.selectitemList();
model.addAttribute("list", list);
반복문 실행하여 가져온 물품데이터를 화면에 출력한다
<tr th:each="obj, idx : ${list}">
<td th:text="${obj.name}"></td>
<td th:text="${obj.no}"></td>
<td th:text="${obj.content}"></td>
<td th:text="${obj.price}"></td>
...
물품에 등록된 여러개의 이미지 중 대표이미지만 조회
- ItemImageMapper 사용하여 이미지 불러오기
- HomeController 에서 물품번호
obj.no
를 보내줬어야하고,
가져올 아이템 이미지 번호를 받아와야한다- 아이템 이미지 주소url 생성시
⇒ 해당 번호가 있는경우는 url 생성, 번호가 없는경우 생성하지 않는다
이미지 클릭시 해당 물품번호 보내주기 ➡️
obj.no
home.html에서 이미지 url 출력하기
<img th:src="@{ ${obj.image} }" style="width: 50px; height:50px" /></td>
반복문 이용하여 이미지 url넣기
- 아이템에 이미지가 첨부된 경우 = 해당이미지 출력
- 이미지가 첨부되지 않은 경우 = 기본이미지 출력
// 아이템 이미지 불러오기
// th:src="@{/item_image(no=${obj.no})}"
@GetMapping(value = "/item_image")
public ResponseEntity<byte[]> imageGET(
@RequestParam(name = "no") Long no) throws IOException {
// 아이템 이미지 번호가 존재하는 경우
if (no > 0L) {
ItemImageDTO item = iimapper.selectImageOne(no);
if (item.getImagesize() > 0L) { // 이미지 파일이 존재하는 경우
// 타입설정 png인지 jpg인지 gif인지
HttpHeaders headers = new HttpHeaders();
headers.setContentType(
MediaType.parseMediaType(item.getImagetype()));
// 실제이미지데이터, 타입이포함된 header, status 200
ResponseEntity<byte[]> response = new ResponseEntity<>(
item.getImagedata(), headers, HttpStatus.OK);
return response;
} else { // 이미지 파일이 존재하지 않는경우 = default이미지 설정
InputStream is = resourceLoader.getResource("classpath:/static/image/noimage.jpg")
.getInputStream();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.IMAGE_JPEG);
// 실제이미지데이터, 타입이포함된 header, status 200
ResponseEntity<byte[]> response = new ResponseEntity<>(
is.readAllBytes(), headers, HttpStatus.OK);
return response;
}
} else { //아이템 이미지 번호가 존재하지 않는경우 = default이미지 설정
InputStream is = resourceLoader.getResource("classpath:/static/image/noimage.jpg")
.getInputStream();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.IMAGE_JPEG);
// 실제이미지데이터, 타입이포함된 header, status 200
ResponseEntity<byte[]> response = new ResponseEntity<>(
is.readAllBytes(), headers, HttpStatus.OK);
return response;
}
}
물품번호 전달시 대표이미지 1개를 이미지 번호를 반환
public ItemImageDTO selectImageNoOne(Long itemno);
ItemDTO
에 임시변수 생성 = 대표이미지 url을 보관할 임시변수
DTO는 entity와 다르기 때문에 테이블내에 존재하지 않는 컬럼을 필드로 가질 수 있다
= DTO는 데이터 전달용 객체일 뿐
private String image;
// set + Image=변수명(String) or get + Image=변수명()
홈화면 로드시 아이템 이미지 불러오기
- 물품번호 전송하기 ➡️
iimapper.selectImageNoOne(item.getNo())
- 가져온 여러개의
ItemDTO
를 반복문 이용하여 itemimage url 생성 ➡️for(ItemDTO item : list)
Controller
에서html
= view 넘어가는 과정에서 view에 넘겨줄 값을 model에 담아 전달한다
➡️model.addAttribute("문자로 되어있는 전달할 임의의 키값", 실제 전달될 값);
//홈페이지 로드시
@GetMapping(value = { "/", "/home", "/home.do" })
// UserDetails에서 반환된 리턴값 User
public String homeGET(@AuthenticationPrincipal User user, Model model) {
List<ItemDTO> list = imapper.selectItemList();
// 넘어온 데이터 확인용
System.out.println(list.toString());
// 수동으로 이미지URL을 포함시킴
for (ItemDTO item : list) {
// 물품번호 전송하면 itemimage가 온다
ItemImageDTO obj = iimapper.selectImageNoOne(item.getNo());
item.setImage("/item_image?no=" + obj.getNo());
}
model.addAttribute("user", user);
model.addAttribute("list", list);
return "home";
}
이미지 클릭하면 주문페이지로 이동
로그인하지 않은 상태에서 이동해야하니HomeController
에 생성해준다
<td>
<a th:href="@{/content.do( item.no=${obj.no} )}">
<img th:src="@{ ${obj.image} }" style="width: 50px; height:50px" />
</a>
</td>
- 물품번호를 이용해서 클릭한 물품의 정보만 가져오기
- 해당 물품에 등록된 이미지 전부 가져오기
return content
= 주문페이지 상세화면 만들기
@GetMapping(value = {"/content.do"})
public String contentGET(
Model model,
@RequestParam(name = "itemno") Long no){
// 1. 물품번호를 이용해서 정보 가져오기
ItemDTO item = imapper.selectItemOne(no);
// 2. 물품관련 이미지 전체 가져오기
List<ItemImageDTO> list = iimapper.selectImageList(no);
System.out.println("item => " + item.toString());
System.out.println("list => " + list.toString());
model.addAttribute("item", item);
model.addAttribute("list", list);
return "content";
}
물품 주문페이지 생성
- HomeController 에서
item
으로 넘겨준 물품정보는 1개- 1개의 물품정보를 입력하니 반복문 사용하지 않음
- HomeController 에서
list
로 이미지는 1개 이상이니 입력시 반복문 사용- 이미지가 있는경우/비어있는경우 2가지 조회결과로 나누어 이미지 url생성
<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title></title>
</head>
<body>
<h3>물품상세</h3>
<!-- homecontroller 에서 item으로 넘겨준 정보 입력! 1개의 정보이니 반복문사용X -->
<form th:action="@{/customer/order.do}" method="post">
<p th:text="${item.no}"></p>
<p th:text="${item.price}"></p>
<p th:text="${item.content}"></p>
<p th:text="|${item.price} 원|"></p>
<!-- 이미지 여러개 반복문으로 입력 -->
<!-- 이미지가 비어있지 않은경우 -->
<div th:if="${not #lists.isEmpty(list)}">
<th:block th:each="item, idx : ${list}">
<img th:src="@{/item_image(no=${item.no})}" style="width:200px;" />
</th:block>
</div>
<!-- 이미지가 비어있는경우 -->
<div th:if="${#lists.isEmpty(list)}">
<img th:src="@{/item_image(no=0)}" style="width:200px;" />
</div>
<select>
<th:block th:each="tmp, idx : ${#numbers.sequence(1,100)}">
<option th:text="${tmp}" th:value="${tmp}"></option>
</th:block>
</select>
<input type="submit" value="주문하기" />
</form>
</body>
</html>
주문하기 버튼 클릭시 고객 권한을 가진 사용자만 주문이 가능하도록 하고
고객 권한을 확인하여 고객 권한이 없으면 로그인 페이지로 이동한다
로그인 페이지로 이동하여 로그인 후 원래 있던 물품 상세페이지로 이동하게 한다
로그인
http://127.0.0.1:8080/BOOT1/member/login.do
후에
원래 있었던 물품 상세페이지로 돌아가야하니excludePathPatterns
제외할 주소에/member/**
추가
쿼리있는경우 없는경우 구분하여 주소 생성
if( request.getQueryString() == null ){ httpSession.setAttribute("CURRENT_URL", request.getContextPath() ); } else { httpSession.setAttribute("CURRENT_URL", request.getContextPath() + request.getServletPath() + "?" + request.getQueryString()); }
preHandle
에서 url 판별을 위해 log를 기록해준다
InterceptorConfig.java에서excludePathPatterns
사용하여 제외한 기록은 출력되지 않는다
url에서 ?뒤의 주소가 없으면 null, ?뒤의 추가 주소가 있으면 no=14등 해당 번호(쿼리)출력
// 하위 3개 내용으로 url판별 가능
// ex. url => http://127.0.0.1:8080/BOOT1/
log.info(format, request.getContextPath()); //출력확인 결과 => /BOOT1
log.info(format, request.getServletPath()); //출력확인 결과 => /
log.info(format, request.getQueryString()); //출력확인 결과 => null
getRequestURL()
메소드는 쿼리문자열을 잘라버리고 출력함
아래에 굵게 표현된 쿼리문자열 부분은getRequestURL()
메소드로는 가져올 수 없다
➡️ http://localhost/community/board.jsp?bid=free&page=1getQueryString()
메소드는 Get방식으로 넘어온 쿼리문자열을 구하기 위한 request 객체의 메소드, 쿼리문자열이 없을때는null
을 리턴- 현재 페이지의 URL과 쿼리문자열까지 모두 구하기 위해서는
getRequestURL()
+getQueryString()
두개의 메소드로 구해진 결과 값을 더해주면 된다
// 예시
String url = request.getRequestURL().toString();
if ( request.getQueryString() != null )
url = url + "?" + request.getQueryString();
out.println(url);
로그인 세션 확인
고객의 세션정보를 확인하기 위해 세션을 가져온다 httpSession 이용
...
if(role.equals("CUSTOMER")){
// 이동하고자 하는 위치 작성시 getContextPath 사용
// request.getContextPath() = /BOOT1
// 세션에서 꺼냄
String currentUrl = (String) httpSession.getAttribute("CURRENT_URL");
if(currentUrl == null){ //세션정보 없음
response.sendRedirect(
request.getContextPath() + "/customer/home.do" );
}
else { // 세션정보가 있다면 가장 마지막 세션기록 URL로 이동
response.sendRedirect(currentUrl);
}
...
주문은 로그인 처리가 되어 customer
권한을 확보해야 가능하다
content.html
에서
<form th:action="@{/customer/order.do}" method="post">
에서 /customer/order.do
로 이동시 권한 확보 여부에 따라 이동한다
/customer/order.do
는 InterceptorConfig.java
에서excludePathPatterns
(=제외된 주소)에 포함되어있지 않으니 바로 CustomHandlerInterceptor.java
로 이동CustomHandlerInterceptor.java
에서 컨트롤러 진입전에 수행되는 preHandle
실행됨 HttpSession httpSession = request.getSession();
으로 세션 가져와서 확인CustomLoginSuccessHandler.java
에서 로그인 세션의 권한 확인currentUrl
, 없는경우 /customer/home.do로 이동)/customer/order.do
이기 때문에 고객권한만 접근 가능