본문 바로가기
개발/개념 설명

(Spring) AOP (Aspect Oriented Programming)

by kakk789 2022. 6. 23.

Spring에서 AOP (Aspect Oriented Programming) 

여러 곳에서 사용되는 Method를 한 곳에서 Spring이 유지보수 가능하도록 도와줌

AOP는 메소드에 대한 이야기 (공통 관심사항'과 '핵심 관심사항'의 Bind를 스프링이 해줌)
  • 공통 관심사항, 핵심 관심사항 개념을 알아야함
    - 공통 관심사항(cross-cutting concern) - 공통으로 처리되는 사항 (포괄적 개념)
    - 핵심 관심사항(core concern) - 핵심으로 처리되는 사항 
  • 대표적인 사용 용도
    1. 패키지 내 메소드 Logging
    2. 패키지 내 메소드 로직 수행 시간 확인

주요 용어 설명

Aspect
- 여러 곳에서 사용되는  '공통 관심 사항' 들을(cross-cutting concern) 모듈화
- @Aspect 
Target
- Aspect 가 적용되는 곳
- 어떤 대상에 부가 기능을 부여할 것인가

Advice
- Aspect 에서 실질적인 기능에 대한 구현체
- 어떤 부가 가능 인지 (Before, AfterReturning, AfterThrowing, After, Around)

Joint point
- Advice 가 Target 에 적용되는 시점메서드 진입할 때, 생성자 호출할 때, 필드에서 값을 꺼낼 때 등등
- 어디에 적용할 것인가? 메서드, 필드, 객체, 생성자 등
Pointcut
- Joint point 의 상세 스펙을 정의한 것
- 실제 Advice가 적용될 지점, Spring AOP에서는 advice가 적용될 메서드를 선정
- @Pointcut

Aspect

  • 여러 곳에서 사용되는  '공통 관심 사항' 들을(cross-cutting concern)를 모듈화

Advice 

  • '공통 관심 사항'을 위해 만들어 놓은 클래스
before
핵심 관심사항 처리 전  '공통 관심사항' 동작
- @Before

After Returning
- 핵심 관심사항이 성공적으로 마무리 되었을 경우 '공통 관심사항' 동작
- @AfterReturning
- 대상 객체가 리턴한 값을 사용하고자 할 때는 returning="ret" 에 담아서 사용

After Throwing
- '핵심 관심사항' 실패했을 때  '공통 관심사항' 동작 (예외)
- @AfterThrowing
- 예외에 접근하기 위해 throwing="ex" 에 담아서 사용

After 
- 핵심 관심사항 성공여부 관계 없이 무조건 공통 관심사항 동작

Around
- 핵심 관심사항이 처리되기 전 후 or 예외 발생 시 공통 관심사항 동작
- JProceedingJoinPoint 타입의 joinPoint를 반드시 매개변수로 가져야함

JoinPoint

  • '공통 관심 사항'이 호출되는 지점
  • 필요한 메소드가 호출되는 부분을 jointPoint 라고 부름
  • 대상 객체 및 호출되는 메서드의 정보를 담고 있음.
joinpoint.getSignature()
  • 호출되는 메서드의 정보를 담고 있음
@After("test()")
	public void afterError(JoinPoint joinPoint) {
		//toShortString() 메소드 이름 반환
		String methodName = joinPoint.getSignature().toShortString();
		System.out.println(methodName + "메소드가 완료되었습다.");
	}
Obejct getTarget()
  • 대상 객체를 반환
Object[] getArgs()
  • 파라미터 목록을 반환 (아래에 추가 설명 있음)

JoinPoint의 getArgs() 메소드

  • 언제 사용하냐면, 해당 서비스를 요청한 클라이언트의 정보를 받아서 사용할 수 있음
getArgs()는 타겟 메소드들의 매개변수들을 배열로 알려줌

PointCut

  • JoinPoint를 여러 개 묶어 놓은 것
  • 어디에 적용할 것 인지를 정한다(excution ..... )
  • PointCut이 적용된 메소드의 리턴 값을 void이여야 한다.
	@Pointcut("execution(public * com.example.demo.controller..*(..))")

속성1) execution

  • Advice를 적용할 메서드를 명시할 때 사용
execution([수식어] 리턴타입 [클래스이름].이름(파라미터)

execution
(* aaa.bbb.ccc.*.*())
- aaa.bbb.ccc 패키지의 파라미터가 없는 모든 메서드 호출

execution(* aaa.bbb.ccc..*.*(..))
- aaa.bbb.ccc패키지 및 하위 패키지에 있는 파라미터가 0개 이상인 메서드

execution(Integer aaa.bbb.ccc..WriteArticleService.write(..))
- 리턴 타입이 Integer인 WriteArticleService 인터페이스의 write 메서드 호출

execution(* get*(*))
- 이름이 get으로 시작하고 1개의 파라미터를 갖는 메서드 호출

execution(* get*(*,*))
- 이름이 get으로 시작하고 2개의 파라미터를 갖는 메서드 호출

execution(* read*(Integer, ..))
- 메서드 이름이 read로 시작하고 첫 번째 파라미터 타입이 Integer이며 1개 이상의 파라미터를 갖는 메서드 호출

 

속성2) within

  • 메서드가 아닌 특정 타입에 속하는 메서드를 Pointcut으로 설정 할 때 사용
within(aaa.bbb.ccc.board.service.WriteArticleService)
- WriteArticleService 인터페이스의 모든 메서드 호출

within(aaa.bbb.ccc.board.service.*)
- aaa.bbb.ccc.board.service 패키지에 있는 모드 메서드호출

within(aaa.bbb.ccc.board..*)
- aaa.bbb.ccc.board 패키지 및 하위 패키지에 있는 모든 메서드 호출

 

속성3) bean

bean 명시자

bean(beanname)
 - 이름이 beanname인 빈의 모든 메서드
 
bean(bean*)
 - 빈의 이름이 bean으로 시작하는 빈의 모든 메서드

Weaving

  • join point에 Advice를 적용하는 방법 (3가지가 있는데 스프링 에서는 프록시 방식을 사용한다)

Spring AOP Dependency  설정

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
    <version>2.7.0</version>
</dependency>

코드설명

더보기
package com.example.demo.advice;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Aspect // AOP 환경설정한다는 의미
@Component // Autoscan을 위해
public class LoggingAdvice {
	
	//Advice가 동작할 타겟을 설정합니다.
	//해당 패키지 하위의 모든 클래스들의 모든 메소드들을 타겟으로 설정
	//-----------핵심 관심사항-----------
	@Pointcut("execution(public * com.example.demo.controller..*(..))")
	public void test() {
		// 해당 타겟을 대표하는 ID 정도로 생각
		// 바디 값에는 값을 적어도 의미가 없음 
		// 무조건 void 타입 이어야함 
	} 
	
	//정상, 비정상일 때 모두 동작
	//test() 아이디를 갖는 pointcut에 설정된 메소드들이 동작한 후에 다음의 메소드가 동작
	//-----------공통 관심사항-----------
	@After("test()")
	public void afterError(JoinPoint joinPoint) {
		String methodName = joinPoint.getSignature().toShortString();
		System.out.println(methodName + "메소드가 완료되었습다.");
	}

	//정상일 때 동작
	@AfterReturning("test()")
	public void AfterReturning() {
		System.out.println("타겟 메소드가 정상적으로 완료되었습니다. ");
	}
	//비정상일때 동작
	@AfterThrowing("test()")
	public void AfterThrowing(JoinPoint joinPoint) {
		String methodName = joinPoint.getSignature().toShortString();
		System.out.println(methodName + " 타겟 메소드가 비정상적 . ");
	}
	@Before("test()")
	public void before(JoinPoint joinPoint) {
		String methodName = joinPoint.getSignature().toShortString();
		System.out.println(methodName + " 타깃 메소드 동작하기 전 입니다.");
	}
	@Around("test()")
	public Object pro(ProceedingJoinPoint joinPoint) {
		Object re = null;
		String methodName = joinPoint.getSignature().toShortString();
		long start  = System.currentTimeMillis();
		System.out.println(methodName + "이 동작하기 전입니다.");
		try {
			
			//이 문장을 전 후로//
			//위에는 타겟 실행 전
			//아래는 타겟 실행 후
			re = joinPoint.proceed(); //타깃 메소드를 동작

		}catch (Throwable e) {
			
		}
		
		long end = System.currentTimeMillis();
		System.out.println(methodName + "이 동작한 후입니다.");
		System.out.println("걸린시간:"  + (end-start));
		return re;
	}
}

참고 블로그

https://chung-develop.tistory.com/67

 

반응형

댓글