개발공부/Spring

[Spring] AOP

환타몬 2022. 2. 14. 19:54

AOP ( Aspect Oriented Programming , 관점지향 프로그램 ) 

 

스프링 어플리케이션은 대부분 특별한 경우를 제외하고는 MVC 웹 어플리케이션에서는 Web Layer, Business Layer, Data Layer로 정의된다.

 

  • Web Layer : REST API를 제공하고, Client 중심의 로직 적용
  • Business Layer : 내부 정책에 따른 logic을 개발하며, 주로 해당 부분을 개발
  • Data Layer : 데이터 베이스 및 외부와의 연동을 처리

 

횡단 관심

 

주요 Annotation

 

AOP를 사용하기에 앞서

 

AOP를 사용하기 위해서는 Dependencies를 추가해야함.

build.gradle -> Dependencies -> implementation 'org.springframework.boot:spring-boot-starter-aop' 추가 후 Gradle 탭 새로고침

 

 - RestApiController.java

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.util.StopWatch;
import org.springframework.web.bind.annotation.*;

@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){
        System.out.println("post method" + user);

        return user;
    }


    @Timer()
    @DeleteMapping("/delete")
    public void delete() throws InterruptedException {

        // db logic
        Thread.sleep(1000*2);
    }

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

        return user;
    }
}

 

 

- TimerAop.java

package com.example.aop.aop;

import com.example.aop.annotation.Timer;
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;
import org.springframework.web.bind.annotation.DeleteMapping;

@Aspect
@Component //bean과 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();

        Object result = joinPoint.proceed();

        stopWatch.stop();
        //

        System.out.println("total time : "+stopWatch.getTotalTimeSeconds());
    }

}
  • @Bean 과 @Component의 차이?
  • @Bean은 클래스에 붙일 수 없음.
  • @Component를 통해서 클래스 단위로 빈을 등록
  • @Bean 같은 경우 메서드에서 빈 어노테이션 가능.
  • @Configration: 하나의 클래스에 여러가지 빈이 등록가능

- ParameterAop.java

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 
@Component
public class ParameterAop {

    @Pointcut("execution(* com.example.aop.controller..*.*(..))")
    private void cut(){

    }

    @Before("cut()") // cut이 실행되는 지점의 before에서 실행됨.
    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 afterReturn(JoinPoint joinPoint, Object returnObj){
        System.out.println("return obj");
        System.out.println(returnObj);
    }
}

 

- DecodeAop.java

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
@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 arg : args){
            if(arg instanceof User) {
                User user = User.class.cast(arg);
                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);
        }
    }
}

 

 - timer.annotation

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 {
}

 

- Decode.annotation

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 {
}

 

 

 

 

정리

  • @Aspact -> AOP 동작하기 위해서 적어야함
  • @Component -> 컴포넌트로 스프링에서 관리하기 위함
  • @PointCut(execution(*com.example.aop.controller..*.*(..))") /execution의 수식들은 굉장히 다양한데, 이는 따로 기술 
  • - 포인트컷 메소드임을 알리는 anntation.
    - 포인트컷은 보통 패키지나 클래스 하위등에 사용함
  • @Before("cut()") // 포인트컷이 실행되는 지점의 before에서 실행됨. 안의 내용은 포인트컷 메서드의 이름.
  •  @AfterReturning(value = "cut()", returning = "returnObj") // 반환값을 컷이라는 곳에서 함.
        public void afterReturn(JoinPoint joinPoint, Object returnObj){

        }
  • AfterReturning Annotation은 반환값도 존재하기 때문에 returning = "returnObj"를 추가하고, 파라미터의 이름과 매칭이 되어야함.
  • @Bean 과 @Component의 차이?
    @Bean은 클래스에 붙일 수 없음.
    @Component를 통해서 클래스 단위로 빈을 등록
    @Bean 같은 경우 메서드에서 빈 어노테이션 가능.
    @Configration: 하나의 클래스에 여러가지 빈이 등록가능
  • 스프링 - StopWatch 가 존재. 
    StopWatch stopwatch = new Stopwatch(); 로 생성
    이러한 stopwatch로 디버깅을 하거나 서비스의 개선을 이루려 할 때, 메서드에 stopwatch 기능을 넣는 것임. 
    하지만 비지니스 로직과는 전혀 상관없는 것StopWatch에 대한 코드가 모든 메서드에 들어가 있음. 
    이것을 횡단관점에서 바깥으로 빼는 작업임.