[SpringBoot] AOP(Aspect Oriented Programming) & Logging

애이용·2021년 1월 5일
0

springboot

목록 보기
5/20
post-thumbnail

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();
    }
}
  • @Component : 개발자가 직접 정의한 클래스를 Bean으로 등록
    (@Bean : 개발자가 제어할 수 없는 외부 라이브러리를 빈으로 등록할 때)
  • @Aspect : AOP 기능을 하는 클래스의 클래스 레벨에 지정
  • @Around : Advice의 종류 중 하나(총 5가지)
    • Advice 종류
      @Before : Target 메서드 호출 이전에 적용
      @AfterRunning : Target 메서드가 성공적으로 실행되고, 결과값 리턴한 뒤 적용
      @AfterThrowing : Target 메서드에서 예외 발생 이후 적용(try/catch의 catch)
      @After : Target 메서드에서 예외 발생에 관계없이 적용
      @Around : Target 메서드 호출 이전과 이후 모두 적용(메서드의 호출 자체를 제어할 수 있어 가장 강력)

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 돼서 구글링 했드아

profile
로그를 남기자 〰️

0개의 댓글