Home AOP 사용해보기
Post
Cancel

AOP 사용해보기

Dependency

  • AOP를 사용하기 위해서는 dependency를 추가해 줘야 한다.
  • build.gradle의dependencies에 AOP를 추가해준다.
1
2
3
4
5
dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-aop'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

Method별 Log남기기

dto.User

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.example.aop.dto;

@Setter
@Getter
public class User {

    private String id;
    private String pw;
    private String email;

    @Override
    public String toString() {
        return "User{" +
                "id='" + id + '\'' +
                ", pw='" + pw + '\'' +
                ", email='" + email + '\'' +
                '}';
    }
}

controller.RestApiController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.example.aop.controller;

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");
        return id + " " + name;
    }

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

aop.ParameteAop

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Aspect   //Aop
@Component  //Spring에서 관리
public class ParameterAop {

    // rule설정, example.aop.controller패키지 하위의 모든것을 AOP로 보겠다.
    @Pointcut("execution(* com.example.aop.controller..*.*(..))")
    private void cut(){}

    //cut()이 실행되는 지점에 before때 해당 메서드 실행
    @Before("cut()")
    public void before(JoinPoint joinPoint){
        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 afterReturn(JoinPoint joinPoint, Object returnObj){
        System.out.println("return obj");
        System.out.println(returnObj);
    }
}
  • ParameterAop 클래스를 Annotation을 작성해 AOP로 정의하고, Spring component로 등록한다.
  • 이후 Aop를 적용시킬 지점을 Pointcut을 사용해 지정하는데 위의 연산은 example.aop.controller패키지 하위의 모든것을 AOP로 보겠다는 뜻이다.
  • AfterReturning의 returning 이름은 메서드의 파라미터명과 일치해야한다.
  • GET Test

    Aop1

  • POST Test

    Aop2

  • 위와 같은 방식으로 한 곳에서 몰아서 로그를 남길 수 있다. 하지만 위와 같이 하면 어떤 메서드에서 로그가 찍혔는지 확인 할 수 없다. MethodSignature를 사용해 메서드의 이름을 함께 출력 할 수 있다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    
      @Before("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);
          }
      }
    

    Aop3


Timer

  • 특정 메서드의 실행 시간을 알고싶다면 아래의 코드를 실행 시간을 알고싶은 메서드 내부에 추가해 주면된다.
1
2
3
4
5
6
7
StopWatch stopWatch = new StopWatch();
stopWatch.start();
/*
실행...
*/
stopWatch.stop();
System.out.println("total time : " + stopWatch.getTotalTimeSeconds());
  • 하나의 메서드에 대해서만 알고 싶다면 상관 없지만 여러개의 메서드에서 각각의 실행 시간을 보고 싶다면 위와 같은 코드를 모두 붙여 넣기 해야 한다. 하지만 AOP를 활용하면 간단하게 원하는 메서드들의 실행 시간을 볼 수 있다.

annotation.Timer

  • Timer Annotation추가
1
2
3
4
5
6
7
8
9
10
11
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 {
}

aop.TimerAop

  • Timer AOP추가
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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();
        //before
        Object result = joinPoint.proceed();      //여기에서 실행
        //after
        stopWatch.stop();
        System.out.println("total time : " + stopWatch.getTotalTimeSeconds());
    }
}
  • 직접 작성한 Timer Annotation이 작성 된 곳을 Pointcut으로 지정한다.
  • Before, After를 사용할 경우 시간 변수를 공유할 수 없으므로 around를 사용한다.
  • Around의 경우 ProceedingJoinPoint 객체를 받게 되는데 직접 해당 객체를 proceed시켜주어야 한다.
  • 실행 시간을 측정하고 싶은 메서드에 Timer Annotation만 작성해주면 실행 시간이 출력된다.

Data 변화하기

  • 외부에서 암호화 된 파일, 필드가 들어오면 복호화를 코드에서 진행하지 않고 AOP에서 복호화가 완료된 상태로 들어오게 할 수 있고, 데이터를 내보낼 때 내부 코드에서는 일반적으로 코딩하지만 특정 타겟에 보내게 될 때 AOP에서 데이터를 변환해서 보내지게 할 수 있다.

annotation.Decode

  • Decode Annotation추가
1
2
3
4
package com.example.aop.annotation;

public @interface Decode {
}

aop.DecodeAop

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package com.example.aop.aop;

import com.example.aop.dto.User;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
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 obj : args){
            if(obj instanceof User){
                User user = User.class.cast(obj);
                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);
        }
    }

}
  • Input Email이 Base64 Encoding되어 있다고 가정.
  • Before 동작
    • 객체들을 가져와 해당 객체가 User의 객체라면 User로 형 변환 시킨 뒤 Email을 decoding한다.
  • After 동작
    • 리턴 하려는 객체가 User의 객체라면 해당 User의 email을 Encoding한다.

Controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
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");
        return id + " " + name;
    }

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

    @Timer
    @DeleteMapping("/delete")
    public void delete() throws InterruptedException {
        //db logic 가정
        Thread.sleep(2000);
    }

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

}
  • “kms@naver.com”을 Base64 Encoding하면 a21zQG5hdmVyLmNvbQ==가 나오는데 이를 가지고 TEST를 진행해보면 결과는 아래와 같다.

Aop4

Aop5

This post is licensed under CC BY 4.0 by the author.