memostack
article thumbnail
블로그를 이전하였습니다. 2023년 11월부터 https://bluemiv.tistory.com/에서 블로그를 운영하려고 합니다. 앞으로 해당 블로그의 댓글은 읽지 못할 수 도 있으니 양해바랍니다.
반응형

AOP 구현

AOP 개념은 아래 글 참고

2021.03.11 - [Spring] - Spring의 AOP 개념 (Aspect Oriented Programming)

 

Spring의 AOP (Aspect Oriented Programming)

AOP AOP는 Aspect Oriented Programming 의 약자로, 번역하면 관점 지향 프로그래밍이다. AOP는 주 비지니스 로직 앞, 뒤로 부가적인 기능을 추가하고 싶을때 사용하는데 예를들어, 로그처리, 보안처리, DB

memostack.tistory.com

 

의존성 추가

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

 

서비스(비즈니스 로직) 구현

테스트를 위한 비즈니스 로직을 구현

  • AOP 구현에 집중하기 위해, 단순히 2개 정수로 사칙연산하는 메소드를 생성
public interface CalcService {
    int sum(int x, int y);
    int subtract(int x, int y);
    int multiply(int x, int y);
    int divide(int x, int y);
}
public class CalcServiceImpl implements CalcService{
    @Override
    public int sum(int x, int y) {
        return x + y;
    }

    @Override
    public int subtract(int x, int y) {
        return x - y;
    }

    @Override
    public int multiply(int x, int y) {
        return x * y;
    }

    @Override
    public int divide(int x, int y) {
        return x / y;
    }
}

 

Aspect 구현

로그를 남기는 Aspect를 구현한다.

  • @Around 로 앞, 뒤로 실행하는 Advice를 구현
@Aspect
@Component
public class LogAspect {  // Aspect : 부가 기능 구현체들을 포함하고 있는 모듈

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    // PointCut : 적용할 지점 또는 범위 선택
    @Pointcut("execution(public * com.example.demo.service..*(..))")
    private void publicTarget() { }

    // Advice : 실제 부가기능 구현부
    @Around("publicTarget()")
    public Object calcPerformanceAdvice(ProceedingJoinPoint pjp) throws Throwable {
        logger.info("성능 측정을 시작합니다.");
        StopWatch sw = new StopWatch();
        sw.start();

        // 비즈니스 로직 (메인 로직)
        Object result = pjp.proceed();

        sw.stop();
        logger.info("성능 측정이 끝났습니다.");
        logger.info("걸린시간: {} ms", sw.getLastTaskTimeMillis());
        return result;
    }
}

 

실행

메소드 앞뒤로 실행 됨

@SpringBootTest
public class AopTest {

    @Autowired
    private CalcService calcService;

    @Test
    @DisplayName("AOP - Around 테스트")
    void aopAroundTest() {
        calcService.sum(3, 5);
    }
}
2021-03-11 11:03:36.237  INFO 19720 --- [main] com.example.demo.LogAspect : 성능 측정을 시작합니다.
2021-03-11 11:03:36.246  INFO 19720 --- [main] com.example.demo.LogAspect : 성능 측정이 끝났습니다.
2021-03-11 11:03:36.246  INFO 19720 --- [main] com.example.demo.LogAspect : 걸린시간: 9 ms

 

Advice는 5가지 종류가 있음

  • Before Advice: Target 메소드 호출 전에 적용 (@Before 사용)
  • After returning: Target 메소드 호출 후 적용  (@AfterReturnning 사용)
  • After throwing: Target에서 예외 발생 후 적용 (@AfterThrowing 사용)
  • After: Target 메소드 호출 후 예외 발생에 상관없이 적용  (@After 사용)
  • Around: Target 메소드 호출 전/후 적용 (@Around 사용)

실행순서

 

@Before 예시

subtract() 빼기 연산 메소드 전에 로그를 찍어주는 Advice 추가


@Aspect
@Component
public class LogAspect {

    // ...

    @Pointcut("execution(public * com.example.demo.service.CalcService.subtract(..))")
    private void subtractTarget() { }

    @Before("subtractTarget()")
    public void subtractBeforeAdvice() {
        logger.info("빼기 연산을 수행합니다.");
    }
}

 

그리고, 실행

@Test
@DisplayName("AOP - Before 테스트")
void aopBeforeTest() {
    calcService.subtract(100, 20);
}
2021-03-11 11:17:51.976  INFO 11668 --- [main] com.example.demo.LogAspect: 성능 측정을 시작합니다.
2021-03-11 11:17:51.976  INFO 11668 --- [main] com.example.demo.LogAspect: 빼기 연산을 수행합니다.
2021-03-11 11:17:52.020  INFO 11668 --- [main] com.example.demo.LogAspect: 성능 측정이 끝났습니다.
2021-03-11 11:17:52.021  INFO 11668 --- [main] com.example.demo.LogAspect: 걸린시간: 44 ms

 

@After 예시

multiply() 메소드 실행 후에 로그를 찍어주는 Advice 추가

@Aspect
@Component
public class LogAspect {

    // ...
    
    @Pointcut("execution(public * com.example.demo.service.CalcService.multiply(..))")
    private void multiplyTarget() { }

    @After("multiplyTarget()")
    public void multiplyAfterAdvice() {
        logger.info("곱하기 연산이 끝났습니다");
    }
}

 

그리고, 실행

@Test
@DisplayName("AOP - After 테스트")
void aopAfterTest() {
    calcService.multiply(3, 10);
}
2021-03-11 11:21:47.952  INFO 13624 --- [main] com.example.demo.LogAspect: 성능 측정을 시작합니다.
2021-03-11 11:21:47.953  INFO 13624 --- [main] com.example.demo.LogAspect: 곱하기 연산이 끝났습니다
2021-03-11 11:21:47.963  INFO 13624 --- [main] com.example.demo.LogAspect: 성능 측정이 끝났습니다.
2021-03-11 11:21:47.963  INFO 13624 --- [main] com.example.demo.LogAspect: 걸린시간: 10 ms

 

@AfterRenturnning 예제

메소드 실행하고 return까지 반환한 뒤 실행

@Aspect
@Component
public class LogAspect {

    // ...
    
    @Pointcut("execution(public * com.example.demo.service.CalcService.sum(..))")
    private void sumTarget() { }

    @AfterReturning(value = "sumTarget()", returning = "returnValue")
    public void sumAfterReturningAdvice(Object returnValue) {
        logger.info("연산 값은 {} 입니다.", returnValue);
    }
}

 

그리고, 실행

@Test
@DisplayName("AOP - AfterReturning 테스트")
void aopAfterReturningTest() {
    calcService.sum(10, 2);
}
2021-03-11 11:29:23.073  INFO 20512 --- [main] com.example.demo.LogAspect: 성능 측정을 시작합니다.
2021-03-11 11:29:23.082  INFO 20512 --- [main] com.example.demo.LogAspect: 연산 값은 12 입니다.
2021-03-11 11:29:23.083  INFO 20512 --- [main] com.example.demo.LogAspect: 성능 측정이 끝났습니다.
2021-03-11 11:29:23.083  INFO 20512 --- [main] com.example.demo.LogAspect: 걸린시간: 10 ms

 

@AfterThrowing 예제

0으로 나누는것이 불가능하기 때문에 Exception 발생, 그때 실행

@Aspect
@Component
public class LogAspect {

    // ...
    
    @Pointcut("execution(public * com.example.demo.service.CalcService.divide(..))")
    private void divideTarget() { }

    @AfterThrowing(value = "divideTarget()", throwing = "exception")
    public void sumAfterReturningAdvice(Exception exception) {
        logger.info("나누기 연산 도주에 ERROR({}) 발생", exception.getMessage());
    }
}

 

그리고 실행

@Test
@DisplayName("AOP - AfterReturning 테스트")
void aopAfterThrowingTest() {
    calcService.divide(10, 0);
}
2021-03-11 11:33:00.408  INFO 18032 --- [main] com.example.demo.LogAspect: 성능 측정을 시작합니다.
2021-03-11 11:33:00.419  INFO 18032 --- [main] com.example.demo.LogAspect: 나누기 연산 도주에 ERROR(/ by zero) 발생

java.lang.ArithmeticException: / by zero

	at com.example.demo.service.CalcServiceImpl.divide(CalcServiceImpl.java:24)
	at com.example.demo.service.CalcServiceImpl$$FastClassBySpringCGLIB$$ef1e0ecd.invoke(<generated>)
    ...
반응형
블로그를 이전하였습니다. 2023년 11월부터 https://bluemiv.tistory.com/에서 블로그를 운영하려고 합니다. 앞으로 해당 블로그의 댓글은 읽지 못할 수 도 있으니 양해바랍니다.
profile

memostack

@bluemiv_mm

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!