관점 지향 프로그래밍
Web Layer
Business Layer
Data Layer
AOP 사용하려면 build.gradle에 dependencies 추가 필요
implementation 'org.springframework.boot:spring-boot-starter-aop'
포트 변경하고 싶으면 [ resources ] - [ application.properties ] 에 포트 등록
server.port=9090
dto 패키지 내에 User 클래스 작성
controller 패키지 내에 RestApiController 클래스 작성
@RestController
@RequestMapping("/api")
public class RestApiController { ... }
controller 패키지의 RestApiController 클래스
@RestController
@RequestMapping("/api")
public class RestApiController {
@GetMapping("/get/{id}")
public String get(@PathVariable Long id, @RequestParam String name) {
return id + " " + name;
}
@PostMapping("/post")
public User post(@RequestBody User user) {
return user;
}
}
aop 패키지의 ParameterAOP 클래스
package com.example.AOP.aop;
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.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
@Aspect // AOP 클래스
@Component // Spring에서 관리
public class ParameterAop {
// controller 하위의 모든 메소드에 적용
@Pointcut("execution(* com.example.AOP.controller..*.*(..))") // ()안에는 어디다가 적용할 지 작성
private void cut() {
// input(before)과 output(after)을 확인
}
// 메소드 실행 전에 넘어가는 argument가 무엇인지 확인하는 메소드
@Before("cut()") // cut이 실행되는 지점의 before에 해당 메소드 실행
public void before(JoinPoint joinPoint) { // JoinPoint : 들어가는 지점에 대한 정보를 가진 객체
System.out.println("--- ParameterAop 클래스의 before 실행 ---");
// 메소드 이름 가져오기
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") // cut이 실행된 after에 해당 메소드 실행
// returning과 함수의 매개변수 이름 matching 되어야 함
public void afterReturn(JoinPoint joinPoint, Object returnObj) {
System.out.println("--- ParameterAop 클래스의 afterReturn 실행 ---");
System.out.println("return obj : " + returnObj);
}
}
Talend API에서
GET | http://localhost:9090/api/get/100?name=steve |
POST | http://localhost:9090/api/post |
BODY | { "id" : "steve", "pw" : "1234", "email" : "steve@gamil.com"} |
실행 결과
Annotation 정의하고, 해당 Annotation이 설정된 메소드만 기록할 수 있도록 실습
@Timer
@DeleteMapping("/delete")
public void delete() throws InterruptedException {
Thread.sleep(1000 * 2);
}
annotation 패키지의 Timer 인터페이스
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 클래스
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 // AOP 클래스
@Component // Spring에서 관리
public class TimerAop {
// controller 하위의 모든 메소드에 적용
@Pointcut("execution(* com.example.AOP.controller..*.*(..))")
private void cut() { }
// @Timer가 작성된 메소드에만 로깅
@Pointcut("@annotation(com.example.AOP.annotation.Timer)")
private void enableTimer() { }
// 시간을 잴 것이기 때문에 전/후가 필요함
// @Before, @After로 하면 time을 공유할 수 없기 때문에 @Around 사용
@Around("cut() && enableTimer()") // 두 가지 조건을 같이 쓰겠다는 것
public void around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("--- TimerAop 클래스의 around 실행 ---");
// 실행 전
StopWatch stopWatch = new StopWatch();
stopWatch.start();
Object result = joinPoint.proceed(); // 실질적인 메소드 실행
// 실행 후
stopWatch.stop();
System.out.println("total time : " + stopWatch.getTotalTimeSeconds());
}
}
Talend API에서
DELETE | http://localhost:9090/api/delete |
실행 결과
값의 변환 실습 (암호화, 복호화)
프로젝트의 시작 부분에서 이메일 Decoding/Encoding 된 값 확인
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("--- main에서 실행 ---");
System.out.print("steve@gmail.com -> ");
System.out.print(Base64.getEncoder().encodeToString("steve@gmail.com".getBytes()));
}
}
controller 패키지의 RestApiController 클래스
@Decode
@PutMapping("/put")
public User put(@RequestBody User user) {
return user;
}
annotation 패키지의 Decode 인터페이스
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 { }
aop 패키지의 DecodeAop 클래스
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.util.Base64;
@Aspect // AOP 클래스
@Component // Spring에서 관리
public class DecodeAop {
@Pointcut("execution(* com.example.AOP.controller..*.*(..))")
private void cut() { }
@Pointcut("@annotation(com.example.AOP.annotation.Decode)")
private void enableDecode() { }
// 전 : Decoding 해서 내보냄
@Before("cut() && enableDecode()")
public void before(JoinPoint joinPoint) throws UnsupportedEncodingException {
System.out.println("--- DecodeAop 클래스의 before 실행 ---");
Object [] args = joinPoint.getArgs();
for(Object arg : args) {
if(arg instanceof User) { // 내가 원하는 User라는 클래스가 매칭이 된다면
User user = User.class.cast(arg); // User라는 클래스로 형 변환
String base64Email = user.getEmail(); // 기존에 Encoding된 이메일 꺼내고
String email = new String(Base64.getDecoder().decode(base64Email), "UTF-8"); // Decoding 하고
user.setEmail(email);// Decoding 된 이메일 넣어주기
System.out.println(user.getEmail());
// 실질적인 controller 코드에서는 User를 Decode 하는 코드가 필요 없음
}
}
}
// 후 : Encoding 해서 내보냄
@AfterReturning(value = "cut() && enableDecode()", returning = "returnObj")
public void afterReturn(JoinPoint joinPoint, Object returnObj) {
System.out.println("--- DecodeAop 클래스의 afterReturn 실행 ---");
if(returnObj instanceof User) {
User user = User.class.cast(returnObj);
String email = user.getEmail();
String base64Email = Base64.getEncoder().encodeToString(email.getBytes());
user.setEmail(base64Email);
System.out.println(user.getEmail());
}
}
}
Talend API에서
PUT | http://localhost:9090/api/put |
BODY | { "id" : "steve", "pw" : "1234", "email" : "c3RldmVAZ21haWwuY29t" } |
실행 결과
관련 주요 Annotation
@Aspect | 자바에서 널리 사용하는 AOP 프레임워크에 해당, AOP를 정의하는 Class에 할당 |
@Pointcut | 기능을 어디에 적용시킬지(ex. 메소드, Annotation 등) 지점을 설정 |
@Before | 메소드 실행하기 이전 |
@After | 메소드 성공적으로 실행 후, 예외가 발생 되어도 실행 |
@AfterReturning | 메소드 호출 성공 실행 시 (Not Throws) |
@AfterThrowing | 메소드 호출 실패 예외 발생 (Throws) |
@Around | Before / After 모두 제어 (ex. Time 공유 필요 시) |