AOP?
관점 지향 프로그래밍 (Aspect Oriented Programing)의 약자로, 메인 비즈니스 로직에서 공통된 기능을 분리하여 별도로 관리할 수 있게 한다.
사용해보기
요구사항
REST API 개발 시 컨트롤러나 특정 메소드가 동작하는 시간을 측정하여 로그로 남겨야한다.
과정
간단한 api를 만들어주고
@RestController
@RequestMapping("/api")
public class RestApiController {
@GetMapping("/get/{id}")
public String get(@PathVariable Long id, @RequestParam String name) {
return id + " " + name;
}
@PostMapping("/post")
public User post(@RequestBody User user) {
return user;
}
}
테스트 코드를 작성해서 동작을 확인해봤다.
@DisplayName("1. GET")
@Test
void test_1() throws Exception {
mockMvc.perform(get(URL + "/get/{id}", "100")
.queryParam("name", "steve"))
.andExpect(status().isOk())
.andExpect(content().string("100 steve"))
.andDo(print());
}
이상없이 동작한다는 것을 확인했으니 aop를 사용하기 위한 준비는 끝났다.
전체 테스트 코드
package com.example.aop.controller; import com.example.aop.dto.User; import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureWebMvc; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; /** * @author songkg7 * @version 1.0.0 * @since 2021/05/20 4:50 오후 */ @WebMvcTest(RestApiController.class) @AutoConfigureWebMvc class RestApiControllerTest { final String URL = "http://localhost:8080/api"; @Autowired private ObjectMapper objectMapper; @Autowired private MockMvc mockMvc; @DisplayName("1. GET") @Test void test_1() throws Exception { mockMvc.perform(get(URL + "/get/{id}", "100") .queryParam("name", "steve")) .andExpect(status().isOk()) .andExpect(content().string("100 steve")) .andDo(print()); } @DisplayName("2. POST") @Test void test_2() throws Exception { User user = new User(); user.setId("testId"); user.setPassword("1111"); user.setEmail("steve@gmail.com"); String json = objectMapper.writeValueAsString(user); mockMvc.perform(post(URL + "/post") .content(json) .contentType(MediaType.APPLICATION_JSON) .accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andExpect(content().contentType(MediaType.APPLICATION_JSON)) .andExpect(jsonPath("$.id").value("testId")) .andDo(print()); } }
AOP 를 사용하기 위해서 bulild.gradle에 aop 의존성을 추가한다.
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-aop'
...
}
AOP 를 사용하여 시간을 측정하기 위한 방법은 다음과 같다.
- 커스텀 어노테이션을 작성한다.
- 시간 측정이 필요한 곳에 1번의 어노테이션을 작성해준다.
- 1번의 어노테이션이 작성된 메서드들이 실행될 때 시간측정 로직이 실행되게끔 한다.
- Timer 어노테이션 작성
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Timer {
}
2. RestApiController 에 아래 메서드를 추가해준다.
@Timer
@DeleteMapping("/delete")
public void delete() throws InterruptedException {
// db logic
Thread.sleep(1000 * 2);
}
아무런 로직을 작성하지 않으면 너무 짧은 시간이 측정되기 때문에 Thread.sleep()
을 약 2초간 주어서 2초 이상의 시간이 측정되게 한다.
3.
@Aspect
@Component
public class TimerAop {
@Pointcut("execution(* com.example.aop.controller..*.*(..))")
private void cut() {
}
@Pointcut("@annotation(com.example.aop.annotation.Timer)")
private void enableTimer() {
}
@Around("cut() && enableTimer()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
Object result = joinPoint.proceed();
stopWatch.stop();
System.out.println("total time = " + stopWatch.getTotalTimeSeconds());
return result;
}
}
@Aspect
: Aspect 역할을 할 클래스를 선언하기 위해 선언
@Pointcut
: 속성에다가 어느 부분까지 공통 기능을 사용할지 명시해준다.
@Around
: 메서드가 실행되기 전과 후 적용되는 방식이고 메서드의 실행시간을 측정해주기 위해선 around 방식을 사용해야한다.
테스트
postman 을 사용하여 delete 요청을 보내보자.
로그에 정상적으로 실행시간이 기록되는 것을 볼 수 있다.
정리
만약 AOP 를 사용하지 못한다면 시간 측정 같은 단순한 로직을 모든 메서드에 작성해야할 수 있고, 그로 인해 비즈니스 로직하고는 관련이 없는 코드가 추가되어 코드가 복잡해지고 유지보수면에서 불리해지게 될 것이다.
스프링을 사용한다면 AOP는 아주 중요한 개념 중 하나이니 열심히 연습해놓자.