08_Spring_240521(화)_86일차(0) - AOP / 240625 수정

soowagger·2024년 5월 22일

8_Spring

목록 보기
36/38

240625(화) 프로젝트 배포 전 로그 설정 시 확인 사항

1. Spring AOP

관심사 : Aspect

Aspect Test

@Component // Bean 등록
@Aspect // 공통 관심사가 작성된 클래스임을 명시 (AOP 동작용 클래스)
@Slf4j // log를 찍을 수 있는 객체(Logger) 생성 코드를 추가 (Lombok 제공)
public class TestAspect {
	
	// advice : 키워 넣을 코드(메서드)
	// Pointcut : 실제로 Advice를 적용할 JoinPoint 지점
	
	// <Pointcut 작성 방법>
	// execution( [접근제한자] 리턴타입 클래스명 메서드명 ([파라미터]) )
	// * 클래스명은 패키지명부터 모두 작성
	
	// 주요 어노테이션
	// - @Aspect : Aspect를 정의하는데 사용되는 어노테이션으로, 클래스 상단에 작성함.
	// - @Before : 대상 메서드 실행 전에 Advice를 실행함.
	// - @After  : 대상 메서드 실행 후에 Advice를 실행함.
	// - @Around : 대상 메서드 실행 전/후로 Advice를 실행함 (@Before + @After)
	
	// "execution(* edu.kh.project..*Controller*.*(..))"
	
	// execution == 메서드의 실행 지점을 가리키는 키워드
	// * : 모든 리턴 타입을 나타냄
	// edu.kh.project : 패키지명을 나타냄. edu.kh.project 패키지와 하위 패키지에 속하는 것을 대사으로함 =
	// .. : 0개 이상의 하위 패키지를 나타냄
	// *Controller* : 이름에 "Controller"라는 문자열을 포함하는 모든 클래스를 대상으로 함
	// .* : 모든 메서드를 나타냄
	// (..) : 0개 이상의 파라미터를 나타냄
	
	//@Before(포인트컷)
	@Before("execution(* edu.kh.project..*Controller*.*(..))")
	public void testAdvice() {
		log.info("----------- testAdvice 수행됨 -------------");
		
	}
	
	@After("execution(* edu.kh.project..*Controller*.*(..))")
	public void controllerEnd(JoinPoint jp) {
		// JoinPoint : AOP 기능이 적용된 대상
		
		// AOP가 적용된 클래스 이름 얻어오기
		String className = jp.getTarget().getClass().getSimpleName(); // ex) MainController
		
		// 실행된 컨트롤러 메서드 이름을 얻어오기
		String methodName = jp.getSignature().getName(); // ex) mainPage 메서드
		
		log.info("-------- {}.{} 수행 완료 ---------", className, methodName);
		
	}
}

PointcutBundle Class

LoggingAspect Class

@Component
@Aspect
@Slf4j
public class LoggingAspect {
	
	/** 컨트롤러 수행 전 로그 출력 (클래스/메서드/ip..)
	 * @param jp
	 */
	@Before("PointcutBundle.controllerPointCut()")
	public void beforeController(JoinPoint jp) {
		
		// AOP가 적용된 클래스 이름 얻어오기
		String className = jp.getTarget().getClass().getSimpleName();
		
		// 실행된 컨트롤러 메서드 이름을 얻어오기
		String methodName = jp.getSignature().getName() + "()";
		
		// 요청한 클라이언트의 HttpServletRequest 객체 얻어오기
		HttpServletRequest req = 
				((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
		
		// 클라이언트 ip 얻어오기
		String ip = getRemoteAddr(req);
		
		StringBuilder sb = new StringBuilder();
		
		sb.append(String.format("[%s,%s] 요청 / ip : %s", className, methodName, ip));
		
		// 로그인 상태인 경우
		if(req.getSession().getAttribute("loginMember") != null) {
			
			String memberEmail = ( (Member)req.getSession().getAttribute("loginMember") ).getMemberEmail();
			
			sb.append(String.format(", 요청 회원 : %s", memberEmail));
			
		}
		
		log.info(sb.toString());
		
	}
	
	// ---------------------------------------------------------------------------------
	
	// ProceedingJoinPoint
	// - JoinPoint 상속한 자식 객체
	// - @Around에서 사용 가능
	// - proceed() 메서드 제공
	// -> proceed() 메서드 호출 전/후로
	//    Before/After가 구분되어짐
	
	// * 주의할 점 *
	// 1) @Around 사용 시 반환형 Object
	// 2) @Around 메서드 종료 시 proceed() 반환 값을 return 해야 한다.
	
	
	/** 서비스 수행 전/후로 동작하는 코드 (advice)
	 * @return
	 * @throws Throwable 
	 */
	@Around("PointcutBundle.serviceImplPointCut()")
	public Object aroundServiceImpl(ProceedingJoinPoint pjp) throws Throwable {
		// Throwable - 예외 처리의 최상위 클래스
		// Exception(예외) / Error(오류) 의 부모가 Throwable
		
		// @Before 부분
		
		// 클래스명
		String className = pjp.getTarget().getClass().getSimpleName();
		
		// 메서드명
		String methodName = pjp.getSignature().getName() + "()";
		
		log.info("========== {}.{} 서비스 호출 ==========", className, methodName);
		
		// 파라미터
		log.info("Parameter : {}", Arrays.toString(pjp.getArgs()));
		
		// 서비스 코드 실행 시 시간 기록
		long startMs = System.currentTimeMillis();
		
		// -- 
		
		Object obj = pjp.proceed(); // 전/후를 나누는 기준점
		
		// --
		
		// @After 부분
		long endMs = System.currentTimeMillis();
		
		log.info("Running Time : {}ms", endMs - startMs);
		
		log.info("======================================================================");
		
		
		return obj;
		
	}
	
	
	// ---------------------------------------------------------------------------------
	
	/** 접속자 IP 얻어오는 메서드
	 * @param request
	 * @return ip
	 */
	private String getRemoteAddr(HttpServletRequest request) {
		
		String ip = null;
		
		ip = request.getHeader("X-Forwarded-For");
		
		if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
			ip = request.getHeader("Proxy-Client-IP");
		}
		
		if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
			ip = request.getHeader("WL-Proxy-Client-IP");
		}
		
		if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
			ip = request.getHeader("HTTP_CLIENT_IP");
		}
		
		if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
			ip = request.getHeader("HTTP_X_FORWARDED_FOR");
		}
		
		if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
			ip = request.getHeader("X-Real-IP");
		}
		
		if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
			ip = request.getHeader("X-RealIP");
		}
		
		if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
			ip = request.getHeader("REMOTE_ADDR");
		}
		
		if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
			ip = request.getRemoteAddr();
		}
		
		return ip;
	}

}

컨트롤러, 메서드, 접근 ip 확인

@Around 활용한 수행 시간 확인

📌 예외 발생 후 수행되는 코드

LoggingAspect Class

	// @AfterThrowing : 메소드가 예외를 던진 후에 실행되는 Advice를 정의
	// @Transactional 어노테이션이 붙어있는 곳에서 예외 발생 시 코드 수행(서비스단) 
	// 예외 발생 후 수행되는 코드
	@AfterThrowing(pointcut = "@annotation(org.springframework.transaction.annotation.Transactional)",
			   throwing = "ex")
	public void transactionRollback(JoinPoint jp, Throwable ex) {
		log.info("**** 트랜잭션이 롤백됨 {} ****", jp.getSignature().getName());
		log.error("[롤백 원인] : {}", ex.getMessage());
	}
profile

0개의 댓글