Spring | AOP(Aspect Oriented Programming)

brightbell·2023년 10월 11일

Spring

목록 보기
10/12

AOP, Aspect Oriented Programming

관점 지향 프로그래밍

  • 문제를 바라보는 관점을 기준으로 프로그래밍하는 패러다임
  • 각 문제를 해결하는 핵심로직과 모든 문제에 적용되는 공통로직을 기준으로 프로그래밍을 분리하는 것

AOP 도입 이유

  • 애플리케이션 개발에 필요한 다양한 공통 모듈 로그 , 보안 , 트랜잭션 처리 등 이 사용됨
  • 대부분의 비즈니스로직에서 필요하기 때문에 반복적으로 유사한 코드를 매번 작성해야 함
  • 공통 모듈을 비즈니스로직마다 작성해야 하는 문제를 개선하기 위해서 AOP 가 도입
  1. AOP 관련 핵심 용어 3가지
    1) 조인포인트(JoinPoint) : AOP를 적용시킬 수 있는 메소드 전체 - 예시) 목록, 상세, 삽입, 수정, 삭제
    2) 포인트컷(PointCut) : 조인포인트 중에서 AOP를 동작시킬 메소드 - 예시) 삽입, 수정, 삭제
    3) 어드바이스(Advice) : 포인트컷에 동작시킬 AOP 동작 자체 - 예시) 트랜잭션
  1. 어드바이스 동작 시점
             |  의미                                              | 포인트컷 반환 타입
  -----------|---------------------------------------------------|------------------------------------
  1) @Before | 포인트컷 동작 이전에 수행(인터셉터와 동일한 시점) 		 | void
  2) @After  | 포인트컷 동작 이후에 수행                         	 | void
  3) @Around | 포인트컷 동작 이전/이후에 모두 수행               	 | Object (포인트컷의 실행 결과를 반환)
  1. 어드바이스 동작 순서
    @Around -> @Before -> @After
  1. 표현식(Expression) 작성 방법

    1) 형식
    execution(반환타입 패키지.클래스.메소드(매개변수))

    2) 의미
    (1) 반환타입
    * : 모든 반환타입
    ② void : void 반환타입
    ③ !void : void를 제외한 반환타입
    (2) 매개변수
    ① .. : 모든 매개변수
    ② * : 1개의 모든 매개변수

BeforeAop

package com.gdu.app10.aop;

import java.util.Arrays;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import lombok.extern.slf4j.Slf4j;

@Slf4j // private static final Logger log = LoggerFactory.getLogger(BeforeAop.class);
@Aspect
@Component
public class BeforeAop {
  
  // 포인트컷 : 언제 동작하는가?
  @Pointcut("execution(* com.gdu.app10.controller.*Controller.*(..))")
  public void setPointCut() { }  // 이름만 제공하는 메소드(이름은 마음대로 본문도 필요 없다.)
  
  // 어드바이스 : 무슨 동작을 하는가?
  
  @Before("setPointCut()")
  public void beforeAdvice(JoinPoint joinPoint) {
    /*
     * Before 어드바이스
     * 1. 반환타입 : void
     * 2. 메소드명 : 마음대로
     * 3. 매개변수 : JoinPoint
     */
    
    /* ContactController의 모든 메소드가 동작하기 전에 요청(방식/주소/파라미터) 출력하기 */
    
    // 1. HttpServletRequest
    ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
    HttpServletRequest request = servletRequestAttributes.getRequest();
    
    // 2. 요청 파라미터 -> Map 변환
    Map<String, String[]> map = request.getParameterMap();
    
    // 3. 요청 파라미터 출력 형태 만들기
    String params = "";
    if(map.isEmpty()) {
      params += "No Parameter";
    } else {
      for(Map.Entry<String, String[]> entry : map.entrySet()) {
        params += entry.getKey() + ":" + Arrays.toString(entry.getValue()) + " ";
      }
    }
    // 4. 로그 찍기 (치환 문자 {} 활용)
    log.info("{} {}", request.getMethod(), request.getRequestURI());  // 방식, 주소
    log.info("{}", params);   // 요청 파라미터
  }
}

AfterAop

package com.gdu.app10.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.interceptor.TransactionInterceptor;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@Aspect
@Component
public class AfterAop {

  @Autowired
  private TransactionInterceptor transactionInterceptor;
  

  // 포인트컷 : 언제 동작하는가?
  @Pointcut("execution(* com.gdu.app10.controller.*Controller.*(..))")
  public void setPointCut() { }
  
  // 어드바이스 : 무슨 동작을 하는가?
  @After("setPointCut()")
  public void afterAdvice(JoinPoint joinPoint) {
    
    /*
     * After 어드바이스
     * 1. 반환타입 : void
     * 2. 메소드명 : 마음대로
     * 3. 매개변수 : JoinPoint
     */
    
    // 로그 찍기
    log.info("==================================================================");
  }
}

AroundAop

package com.gdu.app10.aop;

import java.text.SimpleDateFormat;
import java.util.Date;

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 lombok.extern.slf4j.Slf4j;

@Slf4j
@Aspect
@Component
public class AroundAop {

  // 포인트컷 : 언제 동작하는가?
  @Pointcut("execution(* com.gdu.app10.controller.*Controller.*(..))")
  public void setPointCut() {}
  
  // 어드바이스 : 무슨 동작을 하는가?
  @Around("setPointCut()")
  public Object aroundAdvice(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
    /*
     * Around 어드바이스
     * 1. 반환타입 : Object
     * 2. 메소드명 : 마음대로
     * 3. 매개변수 : ProceedingJoinPoint
     */
    log.info("================================================================");       // 포인트컷 실행 이전에 실행할 코드(@Before 이전에 동작)
    Object obj = proceedingJoinPoint.proceed();                                         // 포인트컷이 실행되는 시점
    log.info("{}", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date())); // 포인트컷 실행 이후에 실행(@After 이전에 동작)
    
    return obj;
  }
}

AppConfig

  /* AOP를 이용한 트랜잭션 처리를 위해 필요한 Bean */
  
  // TransactionInterceptor : 트랜잭션 처리를 위해 언제 rollback 할 것인지 정의하는 스프링 클래스
  @Bean
  public TransactionInterceptor transactionInterceptor() {
    
    // 규칙
    RuleBasedTransactionAttribute ruleBasedTransactionAttribute = new RuleBasedTransactionAttribute();
    ruleBasedTransactionAttribute.setRollbackRules(Collections.singletonList(new RollbackRuleAttribute(Exception.class)));
    
    MatchAlwaysTransactionAttributeSource matchAlwaysTransactionAttributeSource = new MatchAlwaysTransactionAttributeSource();
    matchAlwaysTransactionAttributeSource.setTransactionAttribute(ruleBasedTransactionAttribute);
    
    // 반환
    return new TransactionInterceptor(transactionManager(), matchAlwaysTransactionAttributeSource);
      
  }

0개의 댓글