728x90

회사 업무 중 스프링 레거시 프로젝트를 스프링 부트로 변환하는 업무가 있었다. 업무 내용중 xml로 관리되는 빈들을 자바클래스로 변환하는 업무도 있었는데 이 때, aop:config 로 선언된 문구를 변환하면서 어려움을 겪었다. 빈은 클래스를 생성하고 @Bean 어노테이션을 통해 등록했는데 aop는 뭔지 하나도 아는게 없었기 때문이다. 그래서 이번에 aop에 관한 공부를 하고 스프링 부트에서는 어떻게 적용가능한지 포스팅해보려 한다.


 

GitHub - N1ghtsky0/playground: 개인적으로 흥미가 생긴 기술들을 구현하면서 생각을 정리한 놀이터입니

개인적으로 흥미가 생긴 기술들을 구현하면서 생각을 정리한 놀이터입니다. Contribute to N1ghtsky0/playground development by creating an account on GitHub.

github.com


  • 스프링부트 3.2.1
  • 자바 17

build.gradle

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-web'
	implementation 'org.springframework.boot:spring-boot-starter-mustache'
	implementation 'org.springframework.boot:spring-boot-starter-aop'
	compileOnly 'org.projectlombok:lombok'
	annotationProcessor 'org.projectlombok:lombok'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}

프로젝트는 단순하게 구현할 예정이다.

컨트롤러가 호출될 때마다 어떤 path가 호출되었는지를 로깅하는 기능을 구현할 것이다.

디렉토리 구조

특별한 기능을 만들지 않을거기 때문에 그냥 A서비스, B서비스와 같이 호출되는 서비들과 컨트롤러를 생성해주었다.

// controller
import com.jiwook.playground.service.AService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
@RequiredArgsConstructor
public class AController {
    private final AService aService;

    @GetMapping("/a")
    public String a() {
        aService.serviceMethod();
        return "index";
    }
}

// service
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

@Slf4j
@Service
public class AService {
    public void serviceMethod() {
        log.info("A serviceMethod");
    }
}

AOP를 이용한 로깅은 컨트롤러에만 적용할 것이기에 서비스 호출 로깅은 직접 매서드 내에 작성해주었다.

// aspect
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Slf4j
@Component
@Aspect
public class LoggingAspect {
    private final HttpServletRequest request;

    public LoggingAspect(HttpServletRequest request) {
        this.request = request;
    }

    @Before("execution(* com.jiwook.playground.controller.*.*(..))")
    public void beforeController() {
        log.info("Before controller");
        log.info(request.getRequestURI());
    }

    @After("within(com.jiwook.playground.controller.*)")
    public void afterController() {
        log.info("After controller");
    }
}
<!-- index -->
<!DOCTYPE html>
<html lang="ko">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body>
<h2>메인 페이지</h2>
<a href="/a">A 서비스 호출하기</a><br />
<a href="/b">B 서비스 호출하기</a>
</body>
</html>

프로젝트 실행 후 http://localhost:8080/a 로 접속하면

로그

와 같이 컨트롤러가 호출된 순간 Aspect가 동작하여 로그가 남은것을 확인할 수 있다.


 사실 단순한 로그기록을 위해서라면 CommonController 클래스를 만든 뒤 모든 컨트롤러에 상속해서 컨트롤러가 호출 될때마다 로그를 남기는 방법도 있다. 하지만 이 경우 새로운 컨트롤러를 만들었을 때 개발자의 실수로 상속하는 것을 잊어버린다면 로그가 작성되지 않을 수 있다.

 이번에는 예시 때문에 로깅 관련 기능을 구현했지만, 추적하는 대상을 어드민 기능 관련 컨트롤러로 축소하여 접속 기록을 DB에 저장하고 관리하는 등의 기능을 구현할 때 유용할 것 같다.

+ Recent posts