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
POST Test
위와 같은 방식으로 한 곳에서 몰아서 로그를 남길 수 있다. 하지만 위와 같이 하면 어떤 메서드에서 로그가 찍혔는지 확인 할 수 없다. 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); } }
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를 진행해보면 결과는 아래와 같다.