AOP (Aspect Oriented Programming)
AOP = Advice + PointCut
- 자바와 같은 객체 지향 프로그래밍(OOP)를 OOP답게 사용할 수 있도록 도와줌.
- 주 목적 : 다수의 모듈에 공통적으로 나타나는 부분을 제거하는 것
기능을 핵심 비즈니스 로직과 공통 모듈로 구분!
AOP 는 여러 개의 핵심 비즈니스 로직 외에 공통으로 처리되어야 하는 로그 출력, 보안 처리, 예외 처리와 같은 코드를 별도로 분리해 하나의 단위로 묶는 모듈화의 개념으로 볼 수 있다.
코드 밖에서 설정된다는 것이 핵심- DI가 객체 간 의존성을 주입한 거라면, AOP는 Logic 주입이라 할 수 있다
- 반복적인 Logic을 공통 Logic으로 분리하면, 응집도를 높게 가져갈 수 있음
- xml과 Annotation을 이용해 설정 가능
용어
- Aspect(관점)
공통적으로 적용될 기능
부가적인 기능을 정의한 코드인 Advice와 Advice를 어느 곳에 적용할지 결정하는 PointCut의 조합으로 만들어짐- Advice : 실제로 부가적인 기능을 구현한 객체를 의미(Aspect 기능 자체)
- JoinPoint(조인포인트)
Advice를 적용할 위치
ex) ExampleService의 메서드 중 원하는 메서드를 골라 Advice를 적용할 수 있는데, 이때 모든 메서드들은 Join Point가 됨- PointCut(포인트컷)
Advice를 적용할 JoinPoint를 선별하는 과정이나, 그 기능을 정의한 모듈
정규표현식 or AspectJ 를 사용해서 어떤 JoinPoint를 사용할지 결정- Target(타켓)
실제 비즈니스 로직을 수행하는 객체(Advice를 적용할 대상)- Proxy(프록시) : Advice가 적용됐을 때, 생성되는 객체
- Introduction
Target엔 없는 새로운 메서드나 인스턴스 변수를 추가하는 기능- Weaving(위빙)
PointCut에 의해 결정된 Target의 JoinPoint에 Advice를 적용하는 것(핵심기능에 Advice 적용하는 행위)
AOP를 통해 원하는 시점에 로깅을 찍어보자
build.gradle
implementation 'org.springframework.boot:spring-boot-starter-aop
추가
@Aspect
@Component
public class LoggingAspect {
private final Logger logger = LoggerFactory.getLogger(this.getClass()); // logger.debug 안 찍힘
// controller 패키지에서 *Controller 클래스에 있는 메서드 중 파라미터 0개인 메서드만
// service 패키지에서 *Service 클래스에 있는 메서드 중 파라미터 0개인 메서드만
@Around("execution(* com.map..controller.*Controller.*()) || execution(* com.map..service.*Service.*())")
public Object printLog(ProceedingJoinPoint joinPoint) throws Throwable {
logger.debug("This - '" + joinPoint.getThis() + "'"); // Advice를 행하는 객체
logger.debug("Kind - '" + joinPoint.getKind() + "'"); // 해당 Advice 의 타입
logger.debug("Target - '" + joinPoint.getTarget() + "'"); // Target 객체
String type = "";
String name = joinPoint.getSignature().getDeclaringTypeName();
// getSignature() : 실행되는 대상 객체의 메서드에 대한 정보를 가지고 옴
if (name.contains("Controller")) {
type = "Controller - '";
} else if (name.contains("Service")) {
type = "Service - '";
}
logger.debug(type + name + "." + joinPoint.getSignature().getName() + "()'");
// getName - 메서드 이름
return joinPoint.proceed();
}
}
execution
@Around 안에서 exception으로 시작하는 구문은 포인트컷을 지정하는 문법
접근 제어자, 리턴 타입, 타입 패턴, 메서드, 파라미터 타입, 예외 타입 등을 조합해서 포인트컷을 생성할 수 있음예시
execution(Example get*(..))
리턴 타입이 Example이고, 메서드의 이름이 get으로 시작하고, 파라미터가 0개 이상인 모든 메서드가 호출될 때
execution(* com.may.aop.controller.*())
해당 패키지 밑에 파라미터가 없는 모든 메서드가 호출될 때
execution(* com.may.aop.controller.*(..))
해당 패키지 밑에 파라미터가 0개 이상인 모든 메서드가 호출될 때
execution(* com.may.aop..get(*))
com.may.aop 패키지의 모든 하위 패키지에 존재하는 get으로 시작하고, 파라미터가 한 개인 모든 메서드가 호출될 때
execution(* com.may.aop..get(*, *))
com.may.aop 패키지의 모든 하위 패키지에 존재하는 get으로 시작하고, 파라미터가 두 개인 모든 메서드가 호출될 때
ProceedingJoinPoint
public Object printLog(ProceedingJoinPoint joinPoint) throws Throwable { String name = joinPoint.getSignature().getDeclaringTypeName(); ... }
ProceedingJoinPoint 인터페이스가 상속받는 클래스가 포함하는 메서드
Object[] getArgs()
전달되는 모든 파라미터들을 Object 타입의 배열로 가지고 옴
String getKind()
해당 Advice의 타입을 가지고 옴
Signature getSignature()
실행되는 대상 객체의 메서드에 대한 정보를 가지고 옴
Object getTarget()
Target 객체를 가지고 옴
Object getThis()
Advice를 행하는 객체를 가지고 옴2021-01-05 19:16:52.904 DEBUG 16120 --- [nio-8080-exec-1] com.map.aop.aop.LoggingAspect : This - 'com.map.aop.controller.MainController@1bdf37a6' 2021-01-05 19:16:52.905 DEBUG 16120 --- [nio-8080-exec-1] com.map.aop.aop.LoggingAspect : Kind - 'method-execution' 2021-01-05 19:16:52.905 DEBUG 16120 --- [nio-8080-exec-1] com.map.aop.aop.LoggingAspect : Target - 'com.map.aop.controller.MainController@1bdf37a6' 2021-01-05 19:16:52.908 DEBUG 16120 --- [nio-8080-exec-1] com.map.aop.aop.LoggingAspect : Controller - 'com.map.aop.controller.MainController.main()' 2021-01-05 19:16:52.934 DEBUG 16120 --- [nio-8080-exec-1] com.map.aop.aop.LoggingAspect : This - 'com.map.aop.service.MainService@1f89f825' 2021-01-05 19:16:52.934 DEBUG 16120 --- [nio-8080-exec-1] com.map.aop.aop.LoggingAspect : Kind - 'method-execution' 2021-01-05 19:16:52.934 DEBUG 16120 --- [nio-8080-exec-1] com.map.aop.aop.LoggingAspect : Target - 'com.map.aop.service.MainService@1f89f825' 2021-01-05 19:16:52.935 DEBUG 16120 --- [nio-8080-exec-1] com.map.aop.aop.LoggingAspect : Service - 'com.map.aop.service.MainService.getData()'
- 이때, ' '(작은 따옴표)를 사용하여 공백체크까지 할 수 있게!
스프링부트 logger.debug 사용방법
application.properties
에서debug = true
설정
전부 디버그가 아니라 특정 패키지에만 지정할 때
logging.level.*(패키지명) = DEBUG (로그레벨 설정)# 패키지마다 로그레벨 생성 logging.level.com.map.aop.aop=DEBUG
log4j 로그레벨
TRACE > DEBUG > INFO > WARN > ERROR > FATAL
TRACE : DEBUG보다 좀 더 상세한 정보를 나타냄
DEBUG : 프로그램을 디버깅하기 위한 정보 지정
INFO : 상태 변경과 같은 정보성 메시지를 나타냄
ERROR : 요청을 처리하는 중, 문제가 발생한 경우
FATAL : 아주 심각한 에러가 발생한 상태(시스템적으로 심각한 문제가 발생해서 어플리케이션 작동 불가능한 경우)
cf) slf4j를 이용하기 위해 lombok 을 dependency에 추가해야 함
로그파일 설정
application.properties
logging.file.path=logs
logs 디렉토리 생성되고, spring.log라는 파일이 생김
기본적으로 10M를 저장하고 아카이빙 해주지만, 설정 변경 가능
ps. 내이름 따서 com.may 패키지 쓴 줄 알고,, execution안에 계속 com.may 썼다가 안돼서 매우 헤맸ㄷ....ㅏ 글고 logger.debug
는 안되고, logger.info
돼서 구글링 했드아