SpringBoot - AOP (2)
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에
대해서 알아보겠다. 다음시간에 보아요
