고래씌
[Spring] 13. Aop 본문
1. Aop
▶ proxy-target-class : 프록시 객체를 생성할 때 interface로 혹은, class로 생성할지를 결정하는 구문
- 기본값 : true → 프록시객체 생성시 class로 생성
false → 다이나믹 프록시 인터페이스로 생성함
▶ aop : 내가만든 공통 코드를 모듈로 관리하고자 할 때. 메소드단위로 작동시켜야함. 코드가 있을 때, 일반적으로 interceptor로 관리하지 못한 경우 사용한다.
aop : 주로 트랜잭션처리(@Transactional), 로깅처리, 로그인처리 등 비즈니스단에 공통적으로 필요한 메소드를 좀 더 세밀하게 조정할 때 사용한다.
▶ interceptor : 디스패처서블릿이 컨트롤러로 호출하기 전/후로 끼어들어 실행하기 때문에, controller에게 전달하기 전에 request, response의 데이터를 처리하기에 좋다.
▶ 스프링 요청 / 응답 흐름
=> 메이븐 레파지토리에서 코드 복사
▶ pom.xml
- 다음과 같이 추가한 후, 수정
<!-- Aop 의존성 -->
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>${org.aspectj-version}</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>${org.aspectj-version}</version>
</dependency>
▶ servelt.context.xml
=> aop 추가(namespaces)
▶ Aop.java
※ @Pointcut(advice가 끼어들어서 수행될 클래스나 메소드의 위치)
- joinpoint : advice가 적용될 수 있는 후보지들. 클라이언트가 호출하는 모든 비즈니스 메소드가 여기에 포함된다.
- pointcut : joinpoint들 중에서 실제로 advice가 실행될 지점
☞ Pointcut 작성방법 @Pointcut("execution([접근제한자] 반환형 풀클래스명.메소드명([매개변수]))") ☞ Pointcut 표현식 * : 모든 | 아무값 .. : 하위 | 아래 (하위패키지) | 매개변수에 들어가면 0개 이상의 매개변수를 뜻함 |
package com.kh.spring.common.aop;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import lombok.extern.slf4j.Slf4j;
@Component
@Slf4j
@Aspect // 공통관심사가 작성된 클래스임을 명시
// 실제로 실행할 코드(advice), 실행시킬 위치(pointcut) 두개가 반드시 정의되어 있어야함
public class Aop {
// com.kh.spring.board 아래에서 클래스들 중 Impl로 끝나는 클래스 내부에 존재하는 모든 메소드가 호출될때로 pointcut 지정
// 모든 클래스들 중 클래스 가 Impl로 끝나는 0개의상의 메소드
@Before("testPointcut()") // 특정메소드가 실행되기 이전에 이코드가 중간에 끼어들어서 실행될 것임
public void start() {
// 특정 메소드가 실행되기 전에 항상 실행되는 advice
log.info("================ service start ================");
}
@After("testPointcut()")
public void end() {
log.info("================ service end ================");
}
// @Pointcut : pointcut을 정의해두는 역할만 함. 어노테이션 안에 작성한 패턴을 저장하는 역할을 수행
@Pointcut("execution(* com.kh.spring.board..*Impl.*(..))")
public void testPointcut() {}
}
=> BoardServiceImpl.java 파일 확인한 결과, 끼어들어 실행되는 것을 확인할 수 있다!
2. 게시판 서비스용 / 모든 서비스용 포인트 컷
▶ Aop.java에서 작성하였던 @Before, @After 어노테이션 주석처리
=> BeforeTest.java, CommonPointcut.java, AfterTest모두 class 파일 생성해줌
▶ CommonPointcut.java
package com.kh.spring.common.aop;
import org.aspectj.lang.annotation.Pointcut;
// pointcut들을 모아둔 클래스
public class CommonPointcut {
// 게시판 서비스용 포인트컷
@Pointcut("execution(* com.kh.spring.board..*Impl.*(..))")
public void boardPointcut() {
}
// 모든 서비스용 포인트컷
@Pointcut("execution(* com.kh.spring..*Impl.*(..))")
public void implPointcut() {
}
}
▶ BeforeTest.java
package com.kh.spring.common.aop;
import java.util.Arrays;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
import lombok.extern.slf4j.Slf4j;
@Component
@Aspect
@Slf4j
public class BeforeTest {
@Before("CommonPointcut.implPointcut()")
public void beforeService(JoinPoint jp) {
StringBuilder sb = new StringBuilder();
sb.append("==============================================\n");
// JoinPoint : advice가 적용될 수 있는 모든 후보 메소드
// JoinPoint : advice가 실제로 적용되는 Target Object에 접근할 수 있는 메소드를 제공하는 인터페이스
// TargetObject의 클래스정보, 전달되는 매개변수, 메소드명, 반환값, 예외정보 등을 얻어올 수 있다.
// 모든 advice메소드의 첫번째 매개변수는 무조건 JoinPoint로 작성해야함
sb.append("start : " + jp.getTarget().getClass().getSimpleName()+" - ");
sb.append(jp.getSignature().getName()); // 메소드명
sb.append("(" + Arrays.toString(jp.getArgs()) + ")");
log.info(sb.toString());
}
}
▶ AfterTest.java
package com.kh.spring.common.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import lombok.extern.slf4j.Slf4j;
@Component
@Aspect
@Slf4j
@Order(1) // after관련된 어노테이션들 중 첫번째로 실행하도록 설정
public class AfterTest {
@After("CommonPointcut.implPointcut()")
public void serviceEnd(JoinPoint jp) {
log.info("\n=============== service End ==================");
}
}
=> 반환받은 이후(메소드 리턴문 이후)
▶ AfterReturningTest.java
- @AfterReturning : 메소드 실행 이후에 "반환값"을 얻어오는 기능의 어노테이션
- returning : 반환값을 저장할 "매개변수명"을 지정하는 속성
package com.kh.spring.common.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import com.kh.spring.member.model.vo.Member;
import lombok.extern.slf4j.Slf4j;
@Component
@Aspect
@Slf4j
@Order(5)
public class AfterReturningTest {
// return되는 변수명지정(returnObj) 반환되는 값이 여기에 담길 것
@AfterReturning(pointcut="CommonPointcut.implPointcut()", returning="returnObj")
public void returnValue(JoinPoint jp, Object returnObj) { // 모든 메소드에는 반드시 JoinPoint가 있어야한다
if(returnObj instanceof Member) {
Member m = (Member) returnObj;
m.setUserName("하고래"); // 이런식으로 값의 조정이 가능하다! 모든 사용자의 이름이 "하고래"로 바뀔것
}
log.info("return value : {}", returnObj);
}
}
3. 게시글 수정 막기
▶ AfterThrowingTest.java
- @AfterThrowing : 메소드 실행 이후에 발생하는 예외를 얻어오는 어노테이션
- throwing : 반환된 예외값을 저장할 매개변수명 지정하는 속성
package com.kh.spring.common.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import lombok.extern.slf4j.Slf4j;
@Component
@Aspect
@Slf4j
public class AfterThrowingTest {
// @AfterThrowing : 메소드 실행 이후에 발생하는 예외를 얻어오는 어노테이션
// throwing : 반환된 예외값을 저장할 매개변수명 지정하는 속성
@AfterThrowing(pointcut="CommonPointcut.implPointcut()", throwing="exceptionObj")
public void serviceReturnException(JoinPoint jp, Exception exceptionObj) {
StringBuilder sb = new StringBuilder("Exception : " + exceptionObj.getStackTrace()[0]);
sb.append("에러 메세지 : " + exceptionObj.getMessage()+"\n");
log.error(sb.toString());
}
}
▶ BoardService.java
// 게시판 수정
int updateBoard(Board board, String deleteList, MultipartFile upfile, List<MultipartFile> upfiles) throws Exception;
▶ BoardServiceImpl.java
throw new Exception("dd"); 추가
// 게시판 수정
@Transactional(rollbackFor = {Exception.class}) // 어떠한 에외가 발생한다면 롤백처리하겠다! 트랜잭션 처리를 함
@Override
public int updateBoard(Board board, String deleteList, MultipartFile upfile, List<MultipartFile> upfiles) throws Exception{
board.setBoardContent(Utils.XSSHandling(board.getBoardContent()));
board.setBoardContent(Utils.newLineHandling(board.getBoardContent()));
board.setBoardTitle(Utils.XSSHandling(board.getBoardTitle()));
// 게시글 업데이트
int result = boardDao.updateBoard(board);
// 이미지 및 첨부파일 등록
String webPath = "/resources/images/board/" + board.getBoardCd() + "/";
String serverFolderPath = application.getRealPath(webPath);
// 게시글 등록이 성공적으로 완료되었을 때
if(result > 0) {
// 업로드된 이미지를 분류작업
List<BoardImg> imgList = new ArrayList();
if(upfiles != null) { // 일반게시판, 자유게시판에서는 upfiles가 null임
for(int i=0; i<upfiles.size(); i++) {
if(!upfiles.get(i).isEmpty()) {
String changeName = Utils.saveFile(upfiles.get(i), serverFolderPath);
// BoardImg객체 생성후, 필요한 값을 추가해서 imgList에 추가
BoardImg bi = new BoardImg();
bi.setChangeName(changeName);
bi.setOriginName(upfiles.get(i).getOriginalFilename());
bi.setRefBno(board.getBoardNo());
bi.setImgLevel(i);
imgList.add(bi);
}
}
}
// x버튼을 눌러서 이미지를 삭제하고자 하는 경우
if(deleteList != null && !deleteList.equals("")) {
// 삭제하기 위해서는 board_img_no가 필요함
result = boardDao.deleteBoardImg(deleteList);
}
// db에서 삭제처리 완료됐거나 혹은 게시판 업데이트 성공시
if(result > 0) {
// BoardImg객체 하나하나 업데이트
for(BoardImg bi : imgList) {
result = boardDao.updateBoardImg(bi);
// result값은 1혹은 0으로 반환
// result == 0 ? 실패 → 기존에 이미지가 없던 경우
// result == 1 ? 성공 → 기존에 이미지가 있던 경우
if(result == 0) { // 기존에 이미지가 없으면 insert문 호출
result = boardDao.insertBoardImg(bi);
// 기존에 있던 boardMapper 이용. changeName, OriginName, refBno, imgLevel 값이 다 있어서 문제없음
}
}
}
}
throw new Exception("dd");
// return result;
}
▶ BoardController.java
=> try/catch문 추가
▶ AroundTest.java
- @Around : before, after 합쳐놓은 어노테이션
- ProceedingJoinPoint : before/after처리 관련된 기능을 제공.
- 값을 얻어올수 있는 메소드도 함께 제공
package com.kh.spring.common.aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import lombok.extern.slf4j.Slf4j;
@Component
@Aspect
@Order(4)
@Slf4j
public class AroundTest {
// @Around : before, after 합쳐놓은 어노테이션
@Around("CommonPointcut.implPointcut()")
public Object checkRunningTiem(ProceedingJoinPoint jp) throws Throwable{
// ProceedingJoinPoint : before/after처리 관련된 기능을 제공
// 값을 얻어올수 있는 메소드도 함께 제공
// jp.proceed() 메소드 호출전 : before의 역할수행
long start = System.currentTimeMillis();
Object obj = jp.proceed(); // 메소드 실행. 결과값 반환
// jp.proceed()호출 이후 : after의 역할 수행
long end = System.currentTimeMillis();
log.info("Running Time : {} ms", (end-start));
return obj;
}
}
'Server > Spring' 카테고리의 다른 글
[Spring] 12-2. DB에는 존재하지 않는 파일인데 images 안에만 존재하는 파일 삭제하는 스케쥴러 (0) | 2024.01.31 |
---|---|
[Spring] 12-1. 스케쥴러, Quartz를 이용한 회원 비밀번호 변경 알람(ex. 변경한지 3개월 지난경우 알람) (0) | 2024.01.31 |
[Spring] 11-3. 로그인 여부 체크하는 인터셉터 (0) | 2024.01.30 |
[Spring] 11-2. 인터셉터 이용해서 로깅 처리 (0) | 2024.01.30 |
[Spring] 11-1. 로그 처리 (0) | 2024.01.30 |