코딩기록 저장소 🐕/spring(JPA)🌱

Spring AOP

kimkongmom 2023. 10. 11. 12:09

 

 

AOP

 

Spring의 핵심 개념 중 하나인 DI가 애플리케이션 모듈들 간의 결합도를 낮춘다면, 

AOP(Aspect-Oriented Programming)는 애플리케이션 전체에 걸쳐 사용되는 기능을 재사용하도록 지원하는 것이다. Aspect-Oriented Programming이란 단어를 번역하면 관점(관심) 지향 프로그래밍 이 된다.

프로젝트 구조를 바라보는 관점을 바꿔보자는 의미이다.

 

각각의 Service의 핵심기능에서 바라보았을 때 User과 Order는 공통된 요소가 없다. 

하지만 부가기능 관점에서 바라보면 이야기가 달라진다.

 

부가기능 관점에서 바라보면 각각의 Service의 getXX 메서드를 호출하는 전후에 before과 after라는 

메서드가 공통되는 것을 확인할 수 있습니다.
기존에 OOP에서 바라보던 관점을 다르게 하여 부가기능적인 측면에서 보았을때 공통된 요소를 추출하자는 것입니다. 

이때 가로(횡단) 영역의 공통된 부분을 잘라냈다고 하여, AOP를 크로스 컷팅(Cross-Cutting) 이라고 부르기도 합니다.

OOP : 비즈니스 로직의 모듈화
          모듈화의 핵심 단위는 비즈니스 로직
AOP : 인프라 혹은 부가기능의 모듈화
          대표적인 예 : 로깅, 트랜잭션, 보안 등
          각각의 모듈들의 주 목적 외에 필요한 부가적인 기능들

 

1. AOP 관련 핵심 용어 3가지
  1) 조인포인트(JoinPoint) : AOP를 적용시킬 수 있는 메소드 전체      - 예시) 목록, 상세, 삽입, 수정, 삭제
  2) 포인트컷(PointCut)    : 조인포인트 중에서 AOP를 동작시킬 메소드 - 예시) 삽입, 수정, 삭제
  3) 어드바이스(Advice)    : 포인트컷에 동작시킬 AOP 동작 자체       - 예시) 트랜잭션


2. 어드바이스 동작 시점
                        |  의미                                             | 포인트컷 반환 타입
         -----------|---------------------------------------------------|------------------------------------
  1) @Before | 포인트컷 동작 이전에 수행(인터셉터와 동일한 시점) | void
  2) @After  | 포인트컷 동작 이후에 수행                                           | void
  3) @Around | 포인트컷 동작 이전/이후에 모두 수행                        | Object (포인트컷의 실행 결과를 반환)


3. 어드바이스 동작 순서
  @Before -> @Around -> @After


4. 표현식(Expression) 작성 방법

  1) 형식
    execution(반환타입 패키지.클래스.메소드(매개변수))
  
  2) 의미
    (1) 반환타입
      ① *     : 모든 반환타입
      ② void  : void 반환타입
      ③ !void : void를 제외한 반환타입
    (2) 매개변수
      ① ..  : 모든 매개변수
      ② *   : 1개의 모든 매개변수

 

 

 

 

 

 

 

 

▶ BeforeAop

@Slf4j ->  SLF4J (Simple Logging Facade for Java) 로깅을 위한 코드를 자동으로 생성합니다.(Lombok)
@Aspect -> 클래스가 Aspect로 사용됨을 나타냅니다. Aspect는 횡단 관심사(cross-cutting concerns)를 정의, 적용

 

beforeAdvice(JoinPoint joinPoint) -> 

Before 어드바이스로, 설정한 Pointcut에 해당하는 메서드들이 실행되기 전에 동작합니다. 

이 메소드는 다음과 같은 역할을 수행합니다:

HTTP 요청 정보를 추출하기 위해 HttpServletRequest를 사용합니다. 

이를 통해 요청 방식 (GET, POST 등), 요청 주소 등을 얻을 수 있습니다.

 

① 요청 파라미터를 Map으로 변환하여 요청 파라미터 정보를 획득합니다.
② 요청 파라미터를 출력할 형식을 만들고 로깅합니다.

 

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
49
50
51
52
53
54
55
56
57
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;
@Component
@Slf4j
@Aspect
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<StringString[]> map = request.getParameterMap();
    
    // 3. 요청 파라미터 출력 형태 만들기
    String params = "";
    if(map.isEmpty()) {
      params += "No Parameter";
    } else {
      for(Map.Entry<StringString[]> entry : map.entrySet()) {
        params += entry.getKey() + ":" + Arrays.toString(entry.getValue()) + " ";
        }
    }
    
    // 4. 로그 찍기 (치환 문자 {} 활용)
    log.info("{} {}", request.getMethod(), request.getRequestURI());  // 요청 방식, 요청 주소
    log.info("{}", params);                                           // 요청 파라미터
  }
}
 
cs

 

 

▶ AfterAop

스프링 AOP (Aspect-Oriented Programming)를 사용하여 로깅을 수행하는 클래스인 AfterAop입니다. 

AOP는 관점 지향 프로그래밍으로, 애플리케이션의 흩어진 관심사(로깅, 보안, 트랜잭션 관리 등)를 

모듈화하고 중앙화된 방식으로 처리할 수 있도록 도와줍니다.

 

setPointCut() -> spect의 Pointcut을 정의하는 메소드입니다. Pointcut은 

어떤 메서드가 언제 실행될 것인지를 지정합니다. 

여기에서는 com.gdu.app10.controller 패키지 내의 모든 컨트롤러 클래스의 모든 메서드에 해당하는 Pointcut을 정의한다.

 

afterAdvice(JoinPoint joinPoint) ->  @After 어드바이스로, 설정한 Pointcut에 해당하는 메서드들이 실행된 이후에 동작

로그를 출력하여 메소드 실행 후에 추가 정보를 기록합니다.

 

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.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;
@Component
@Aspect
@Slf4j
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("======================================================"); 
  }
}
 
cs

 

 

 

▶ AroundAop

aroundAdvice(ProceedingJoinPoint proceedingJoinPoint) ->

@Around 어드바이스로, 설정한 포인트컷에 해당하는 메서드들이 실행되기 전과 후에 동작합니다. 

 

① 로그를 출력하여 메서드 실행 전과 후에 추가 정보를 기록합니다.
② proceedingJoinPoint.proceed()를 호출하여 실제 메서드를 실행합니다.

   이것은 메서드 실행을 계속하도록 하는데, 이 메소드 실행 지점에서 실행됩니다.
③ 실행 후에는 시간 정보를 로그에 추가로 기록합니다.

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
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;
@Component
@Aspect
@Slf4j
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;
  }
}
 
cs

 

 

 

 

 

 

 

'코딩기록 저장소 🐕 > spring(JPA)🌱' 카테고리의 다른 글

트랜잭션  (0) 2023.10.12
SpringMyBatis  (0) 2023.10.12
Springjdbc  (0) 2023.10.11
SpringLogger  (0) 2023.10.10
jdbcJunitTest  (0) 2023.10.10