AOP

Minseo Kang·2023년 2월 13일
0

Spring Boot

목록 보기
13/27
post-thumbnail

01. AOP(Aspect Oriented Programming) 개념


관점 지향 프로그래밍

  • Spring Application은 대부분 MVC
  • 웹 어플리케이션에서는 Web Layer / Business Layer / Data Layer
  • AOP는 메소드나 특정 구역에서 반복되는 로직들을 한곳에 몰아서 코딩할 수 있도록 함

Web Layer

  • REST API 제공, Client 중심 로직 적용
  • ex) Response 내려주기, HTTP status 바꾸기

Business Layer

  • 내부 정책에 따른 Logic 개발, 주로 해당 부분 개발
  • ex) Service 제공

Data Layer

  • 데이터 베이스 및 외부와의 연동 처리
  • ex) Repository와 연결



02. AOP 실습 준비


AOP 사용하려면 build.gradle에 dependencies 추가 필요

implementation 'org.springframework.boot:spring-boot-starter-aop'

포트 변경하고 싶으면 [ resources ] - [ application.properties ] 에 포트 등록

server.port=9090

dto 패키지 내에 User 클래스 작성

  • id, pw, email 멤버 변수 생성
  • Getter, Setter 작성 및 toString 오버라이딩

controller 패키지 내에 RestApiController 클래스 작성

@RestController
@RequestMapping("/api")
public class RestApiController { ... }



03. AOP 실습 정리(1)


controller 패키지의 RestApiController 클래스

  • GET 메소드 작성
  • POST 메소드 작성
@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에서

GEThttp://localhost:9090/api/get/100?name=steve

POSThttp://localhost:9090/api/post
BODY{ "id" : "steve", "pw" : "1234", "email" : "steve@gamil.com"}

실행 결과




04. AOP 실습 정리(2)


Annotation 정의하고, 해당 Annotation이 설정된 메소드만 기록할 수 있도록 실습


controller 패키지의 RestApiController 클래스
  • DELETE 메소드 작성
@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에서

DELETEhttp://localhost:9090/api/delete

실행 결과



05. AOP 실습 정리(3)


값의 변환 실습 (암호화, 복호화)


프로젝트의 시작 부분에서 이메일 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 클래스

  • PUT 메소드 작성
@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에서

PUThttp://localhost:9090/api/put
BODY{ "id" : "steve", "pw" : "1234", "email" : "c3RldmVAZ21haWwuY29t" }

실행 결과


06. 정리


관련 주요 Annotation

@Aspect자바에서 널리 사용하는 AOP 프레임워크에 해당, AOP를 정의하는 Class에 할당
@Pointcut기능을 어디에 적용시킬지(ex. 메소드, Annotation 등) 지점을 설정
@Before메소드 실행하기 이전
@After메소드 성공적으로 실행 후, 예외가 발생 되어도 실행
@AfterReturning메소드 호출 성공 실행 시 (Not Throws)
@AfterThrowing메소드 호출 실패 예외 발생 (Throws)
@AroundBefore / After 모두 제어 (ex. Time 공유 필요 시)

0개의 댓글