Part3 - CH5. 스프링 더 알아보기(2) AOP
AOP
-
관점 지향 프로그래밍 특별한 경우를 제외한 스프링 MVC 웹 어플리케이션에서는 Web Layer, Buisness Layer, Data Layer로 정의.
- Web Layer : REST API를 제공하며, Client중심 로직 적용
- Business Layer : 내부 정책에 따른 로직개발
- Data Layer : 데이터베이스 및 외부와 연동을 처리
주요 어노테이션
Annotation | 의미 |
---|---|
@Aspect | 자바에서 널리 사용하는 AOP프레임워크에 포함되며, AOP를 정의하는 Class에 할당 |
@Pointcut | AOP기능을 적용시킬 지점을 설정 |
@Before | 메소드 실행 이전 |
@After | 메소드 실행 후, 예외무시 |
@AfterRunning | 메소드 호출 성공 실행 시 |
@AfterThrowing | 메소드 호출 실패 예외 발생 |
@Around | Before/After모두 제어 |
AOP 실무사례(1)
스프링 이니셜라이저로 스프링 웹 프로젝트를 하나 만들어준다.
build.gradle의 dependency에다가
implementation 'org.springframework.boot:spring-boot-starter-aop'
를 추가하고 refresh해준다.
일단 RestController를 만들어준다.
@GetMapping("/get/{id}")
public String get(@PathVariable Long id, @RequestParam String name) {
System.out.println("get method : " + id + ", " + name);
return id + " " + name;
}
@PostMapping("/post")
public Account post(@RequestBody Account account) {
System.out.println("post method : " + account);
return account;
}
이제 각 메소드가 실행되는 곳에서 로그를 찍도록 AOP를 작성해보자.
@Aspect //AOP할당
@Component
public class ParameterAop {
@Pointcut("execution(* com.example.helloboot.controller..*.*(..))") //AOP기능 적용지점 설정
private void cut(){}
@Before("cut()") //cut메소드 실행 이전에 실행
public void before(JoinPoint joinPoint) {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method method = methodSignature.getMethod();
System.out.println(method.getName());
Object[] args = joinPoint.getArgs(); //메소드에 들어가는 매개변수들
for(Object obj : args){
System.out.println("type : " + obj.getClass().getSimpleName());
System.out.println("value : " + obj);
}
}
@AfterReturning(value = "cut()", returning = "returnObj")
public void after(JoinPoint joinPoint, Object returnObj) {
System.out.println("returnObj : " + returnObj);
}
}
@Before에서는 어떤 메소드가 실행되었고, 어떤 파라미터가 들어왔는지, @AfterRunning 에서는 어떤 응답 이 return되었는지 알 수 있다.
AOP 실무사례(2)
커스텀 어노테이션을 하나 만들고,이를 활용하여 서버의 상태를 AOP를 이용해서 로깅해보자.
일단 커스텀 어노테이션을 하나 만들자.
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Timer {}
그리고 컨트롤러에 delete메소드가 2초가 걸린다고 가정하고 메소드를 하나 만든다.
@Timer
@DeleteMapping("/delete")
public void delete() throws InterruptedException {
// db login
Thread.sleep(1000*2);
}
마지막으로 우리가 붙인 @Timer어노테이션이 붙은 클래스 하위메소드에 대해 적용시키기 위해 AOP를 다음과 같 이 작성한다.
@Aspect
@Component
public class TimerAop {
@Pointcut("execution(* com.example.helloboot.controller..*.*(..))")
private void cut(){}
@Pointcut("@annotation(com.example.helloboot.annotation.Timer)")
private void enableTimer(){
}
@Around("cut() && enableTimer()")
public void arround(ProceedingJoinPoint joinPoint) throws Throwable {
StopWatch stopWatch = new StopWatch();
//before
stopWatch.start();
// joinPorint.preceed(); 할때 cut(), enableTimer()가 실행되는 시점이라고 보면됨
Object result = joinPoint.proceed();
//after
stopWatch.stop();
System.out.println("total time : " + stopWatch.getTotalTimeSeconds());
}
}
AOP에서 암호화된 정보 복호화를 먼저 진행시키고 실제 서비스로직에 넘겨줄 수 있다. 또한 서비스로직이 끝났을 때 암호화를 다시 해서 return시킬 수 있다.
일단 DecodeAop를 먼저 작성해보자.
@Aspect
@Component
public class DecodeAop {
@Pointcut("execution(* com.example.helloboot.controller..*.*(..))")
private void cut(){}
@Pointcut("@annotation(com.example.helloboot.annotation.Decode)")
private void enableDecode(){
}
@Before("cut() && enableDecode()")
public void before(JoinPoint joinPoint){
Object[] args = joinPoint.getArgs();
for(Object arg: args){
if(arg instanceof Account){
Account account = Account.class.cast(arg);
String base64Email = account.getEmail();
String email = new String(Base64.getDecoder().decode(base64Email), StandardCharsets.UTF_8);
account.setEmail(email);
}
}
}
@AfterReturning(value= "cut() && enableDecode()", returning = "returnObj")
public void afterReturn(JoinPoint joinPoint, Object returnObj){
if(returnObj instanceof Account){
Account account = Account.class.cast(returnObj);
String email = account.getEmail();
String base64Email = Base64.getEncoder().encodeToString(email.getBytes(StandardCharsets.UTF_8));
account.setEmail(base64Email);
}
}
}
이제 DecodeAop가 작동할 수 있도록 joinPoint걸린 Decode어노테이션이 붙은 api를 하나 만들어주자.
@Timer
@Decode
@DeleteMapping("/put")
public Account put(@RequestBody Account account) {
System.out.println("put user : " + account);
return account;
}
요청 바디에다가 base64로 암호화된 데이터값을 보내게 되면 알아서 복호화해서 로그로 찍고, 다시 암호화해서 에코하는 AOP를 적용시켰다.