Altiora Petamus

[Spring] AOP 활용하기 본문

Java/Spring Framework

[Spring] AOP 활용하기

Haril Song 2021. 5. 20. 21:19

 

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. 시간 측정이 필요한 곳에 1번의 어노테이션을 작성해준다.
  1. 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는 아주 중요한 개념 중 하나이니 열심히 연습해놓자.

 

 

'Java > Spring Framework' 카테고리의 다른 글

다양한 HTTP Mapping 1  (0) 2021.07.28
in-memory DB 구현  (0) 2021.06.30
Spring Security 사용하기 1  (0) 2021.06.26
Spring Security란?  (0) 2021.06.19
[Spring] ApplicationEventPublisher 를 사용한 Event 처리  (0) 2021.05.17