Spring Boot/AOP

SpringBoot - AOP (2)

운동하는 주니어개발자 2023. 5. 1. 19:31

AOP (1)에 이어서 두번째 AOP시간이다. 우선 오늘 처음으로 공부한 내용은 메소드의 실행시간을 가지고 서버의 부하, 

서버의 현재 상태를 로깅으로 남길 수 있기 때문에 로깅을 남기는 실습을 먼저 진행하겠다.

하지만 이번엔 단순하게 AOP만 만드는 것이 아니라 커스텀 된 annotation을 하나 만들어서 해당 annotation을 가지고 annotation이 설정된 메소드만 기록을 할 수 있도록 실습을 진행하였다. @Around를 활용해 반복되는 logic을 AOP로 따로 빼네서 반복하지 않고 관리하고 실행할 수 있도록 하는 실습을 진행하였다.

 

우선 aop패키지에 TimerAop.java파일을 생성해 준다. 이어서 annotation패키지를 생성해 주고 Timer.java파일로

Timer annotation을 생성해준다.

TimerAop.java

package com.example.aop.aop;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;

@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 void around(ProceedingJoinPoint joinPoint) throws Throwable {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();

        Object result = joinPoint.proceed();

        stopWatch.stop();

        System.out.println("total time : " + stopWatch.getTotalTimeSeconds());
    }

}

이어서 controller 패키지에 있는 RestApiController.java 파일에 Delete 메소드를 생성해 준다.

RestApiController.java

package com.example.aop.controller;


import com.example.aop.annotation.Timer;
import com.example.aop.dto.User;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api")
public class RestApiController {

    @GetMapping("/get/{id}")
    public String get(@PathVariable Long id, @RequestParam String name) {
        System.out.println("get method");
        System.out.println("get method : " + id);
        System.out.println("get method : " + name);
        return id + " " + name;
    }

    @PostMapping("/post")
    public User post(@RequestBody User user) {
        System.out.println("post method : " + user);
        return user;
    }

    @Timer
    @DeleteMapping("/delete")
    public void delete() throws InterruptedException {
        // db logic
        Thread.sleep(1000 * 2);
    }
}

이어서 annotation패키지에 있는 Timer annotation에 코드를 다음과 같이 작성해 준다.

package com.example.aop.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Timer {
}

이어서 서버를 실행시키고  Delete방식으로 TalendApiTester홈페이지에서 데이터를 전송하면 다음과 같이 결과가 나온다

다음과 같이 메소드의 시간을 출력해 주면 복잡한 로직이 있거나 데이터베이스를 사용을 한다거나 외부 기관하고 통신을 한다했을 때 여기에 대해서 시간이 얼마나 걸렸는지 측정을 하고 싶을 때 @Timer라는 annotation만 붙이면 AOP를 이용해서 원하는 결과를 DB에 저장을 한다던지 모니터링을 할수있는 곳에 push를 해줄 수 있다. 지금까지는 @Around를 사용해서 진행을 해봤고 이어서 메소드가 실행되기 전에 특정한 값을 바꾸는 값을 변환하는 예제에 대해 실습을 해보겠다.

 

우선 annotation 패키지에 Decode annotation 을 생성하고 aop패키지에 DecodeAop.java파일을 생성해 준다.

이어서 controller패키지에 RestApiController.java파일에 put메소드를 생성해 주고 진행하겠다.

RestApiController.java

package com.example.aop.controller;


import com.example.aop.annotation.Decode;
import com.example.aop.annotation.Timer;
import com.example.aop.dto.User;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api")
public class RestApiController {

    @GetMapping("/get/{id}")
    public String get(@PathVariable Long id, @RequestParam String name) {
        System.out.println("get method");
        System.out.println("get method : " + id);
        System.out.println("get method : " + name);
        return id + " " + name;
    }

    @PostMapping("/post")
    public User post(@RequestBody User user) {
        System.out.println("post method : " + user);
        return user;
    }

    @Timer
    @DeleteMapping("/delete")
    public void delete() throws InterruptedException {
        // db logic
        Thread.sleep(1000 * 2);
    }

    @Decode
    @PutMapping("/put")
    public User put(@RequestBody User user) {
        System.out.println("put");
        System.out.println(user);
        return user;
    }
}

Decode.java annotation 

package com.example.aop.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Decode {
}

DecodeAop.java

package com.example.aop.aop;

import com.example.aop.dto.User;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

import java.io.UnsupportedEncodingException;
import java.util.Base64;

@Aspect
@Component
public class DecodeAop {

    @Pointcut("execution(* com.example.aop.controller..*.*(..))")
    private void cut() {}

    @Pointcut("@annotation(com.example.aop.annotation.Decode)")
    private void enableDecode(){}

    @Before("cut() && enableDecode()")
    public void before(JoinPoint joinPoint) throws UnsupportedEncodingException {

        Object[] args = joinPoint.getArgs();

        for(Object arg : args) {
            if(arg instanceof User) {
                User user = User.class.cast(arg);
                String base64Email = user.getEmail();
                String email = new String(Base64.getDecoder().decode(base64Email), "UTF-8");
                user.setEmail(email);
            }
        }
    }

    @AfterReturning(value = "cut() && enableDecode()", returning = "returnObj")
    public void afterReturn(JoinPoint joinPoint, Object returnObj) {
        if(returnObj instanceof User) {
            User user = User.class.cast(returnObj);
            String email = user.getEmail();
            String base64Email = Base64.getEncoder().encodeToString(email.getBytes());
            user.setEmail(base64Email);
        }
    }

}

이어서 AopApplication.java파일에 출력 코드를 다음과 같이 작성해준 후 결과를 확인해보면 된다.

AopApplication.java

package com.example.aop;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import java.util.Base64;

@SpringBootApplication
public class AopApplication {

    public static void main(String[] args) {
        SpringApplication.run(AopApplication.class, args);
        System.out.println(Base64.getEncoder().encodeToString("Kim@naver.com".getBytes()));
    }

}

서버를 실행시키면 Base64형태로 문자 하나가 출력이 된다. 그 문자를 복사한 후 TalendApiTester홈페이지에서

다음 사진과 같이 붙여주고 send를 해주면 Base64형태의 문자가 Decoding이 되어 원하는 값이 출력되는걸 볼 수 있다.

이렇게 aop에 대해 여러가지 실습을 해보았다. 다음시간에는 ObjectMapper에 대해서 실습하고 여러가지 Annotation에

대해서 알아보겠다. 다음시간에 보아요