본문 바로가기
Spring 프레임워크/이론

AOP (관점 지향 프로그래밍) 관심분리 (@ 어노테이션 설정 모듈화)

by Hwanii_ 2023. 8. 11.
728x90

https://hwanii96.tistory.com/334

 

AOP : 관점 지향 프로그래밍 (@ 어노테이션 방식으로 로그 찍기)

 

hwanii96.tistory.com

 

위의 글에서, @ 어노테이션 방식으로,

Xxx 포인트컷과 로그 설정 어드바이스를 어스펙트 하는 설정을 했었다.

 

만약, 포인트컷과 어드바이스를 어스펙트 했는데, 위빙이 되지 않는다 ?

>> 오타일 가능성이 매우 높다.

>> 필요한 객체들의 객체화가 안됬을 수도 있다.

 

1.

 

package com.spring.biz.common;

import org.aspectj.lang.annotation.Pointcut;

public class PointcutCommon {	//	PointCut 통합 클래스. (@ 어노테이션을 사용)

	@Pointcut("execution(* com.spring.biz..*Impl.*(..))")
	public void aPointcut() {}

	@Pointcut("execution(* com.spring.biz..*Impl.select*(..))")
	public void bPointcut() {}

	@Pointcut("execution(* com.spring.biz..*Impl.*(..))")
	public void cPointcut() {}

	@Pointcut("execution(* com.spring.biz..*Impl.*(..))")
	public void dPointcut() {}

	@Pointcut("execution(* com.spring.biz..*Impl.insert*(..))")
	public void ePointcut() {}

}	//	PointCutCommon

 

 

위는, XxxPointcut 에 대해 모듈화 해놓은 PointCutCommon 클래스 이다.

 

포인트컷을 어떻게 설정할것인지를 생각해서,

@Pointcut() 어노테이션의 소괄호 내부를 작성해주면 된다.

 

모든 Pointcut을 이 클래스에서 관리하므로,

유지보수가 용이하고, 가독성이 올라간다.

 

 

 

2.

 

package com.spring.biz.common;

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.springframework.stereotype.Service;
import org.springframework.util.StopWatch;

import com.spring.biz.member.MemberVO;

@Service
@Aspect
public class LogAdvice {	//	LogAdvice 통합 클래스. (@ 어노테이션을 사용)
	
	@Before("PointcutCommon.aPointcut()")
	public void beforeLog() {

		System.out.println("log : LogAdvice : beforeLog() : ");
		System.out.println("모든 비즈니스 메서드를 대상으로, 비즈니스 메서드 '수행 전'에 로그 호출.");
		System.out.println();
	}

	@After("PointcutCommon.bPointcut()")
	public void afterLog() {

		System.out.println("log : LogAdvice : afterLog() : ");
		System.out.println("R 비즈니스 메서드를 대상으로, 비즈니스 메서드 '수행 후'에 로그 호출.");
		System.out.println();
	}

	@AfterReturning(pointcut = "PointcutCommon.cPointcut()", returning = "returnObj")
	public void afterReturningLog(JoinPoint jp, Object returnObj) {	//	CRUD 메서드가 결과값을 반환 후에 호출 되는 어드바이스 (횡단 관심).

		String methodName = jp.getSignature().getName();

		String getSignature = jp.getSignature().toString();	//	MemberVO com.spring.biz.member.MemberService.selectOne(MemberVO)

		int index = getSignature.indexOf("biz.") + 4;		//	"biz." 다음 인덱스

		if(index != -1) {
			String log = getSignature.substring(index);
			System.out.println("log : " + log);
			System.out.println();
		}

		System.out.println("log : LogAdvice : afterReturningLog() : ");
		System.out.println("methodName : " + methodName + " 의 반환 이후의 로그 호출.");
		System.out.println();

		if(returnObj instanceof MemberVO) {	

			MemberVO mVO = (MemberVO)returnObj;

			if(mVO.getRole().equals("ADMIN")) {
				System.out.println("[관리자 입장]");
				System.out.println();
			}
			else {
				System.out.println("[사용자 입장]");
				System.out.println();
			}
		}
	}
	
	@AfterThrowing(pointcut = "PointcutCommon.dPointcut()", throwing = "exceptObj")
	public void afterThrowingLog(JoinPoint jp, Exception exceptObj) {	//	예외 발생 시 뜨는 로그.

		String methodName = jp.getSignature().getName();

		System.out.println("log : LogAdvice : afterThrowingLog() : ");
		System.out.println("methodName : " + methodName + " 의 반환 이후의 로그 호출.");
		System.out.println("exceptObj.getMessage() : " + exceptObj.getMessage());
	}
	
	@Around("PointcutCommon.ePointcut()")
	public Object aroundLog(ProceedingJoinPoint pjp) throws Throwable {	
		
		String methodName = pjp.getSignature().getName();	

		System.out.println("log : LogAdvice : aroundLog() : ");
		System.out.println("C 비즈니스 메서드를 대상으로, 비즈니스 메서드 '수행 전' 에 로그 호출.");
		System.out.println();
		
		StopWatch sw = new StopWatch();	//	스프링에서 제공 해주는 클래스 이다.
		
		sw.start();

		//	외부의 비즈니스 메서드를 호출 한다.

		Object obj = pjp.proceed();
		
		sw.stop();

		System.out.println("log : LogAdvice : aroundLog() : ");
		System.out.println("methodName : " + methodName + " 의 반환 이후의 로그 호출.");
		System.out.println(sw.getTotalTimeMillis() + " 밀리초의 시간이 걸렸습니다.");
		System.out.println(sw.getTotalTimeSeconds() + " 초의 시간이 걸렸습니다.");
		System.out.println();

		return obj;
	}

}	//	LogAdvice

 

모든, 로그 설정에 관련된 로그 어드바이스들을 한곳에 모듈화한 LogAdvice 클래스 이다.

 

 

위와 같이,

LogAdvice 라는 클래스 안에 모든 어드바이스 메서드들이 있기 때문에,

LogAdvice 객체화에 필요한 @Service 어노테이션은 딱 한번만 명시 해 주면 된다.

@Aspect 어노테이션도 마찬 가지 이다.

 

 

만약, 로그 설정 어드바이스를 사용 하고 싶지 않다면 ?

 

 

위와 같이 주석 처리를 하면 될듯 하다.

 

3.

[ 정리 ]

 

@Aspect 어노테이션은 스프링 AOP 에서 사용되는 어드바이스 클래스를 정의 할 때 사용 된다.

 

해당 클래스가 AOP의 관점 (aspect) 역할을 수행함을 나타낸다.

 

@Aspect 어노테이션을 사용하면,

LogAdvice 클래스가,

AOP의 관점 역할을 한다는 것을 명시적으로 나타내고,

스프링 컨테이너가 LogAdvice 클래스를 AOP의 일부로 인식 하게 된다.

 

@Aspect 어노테이션을 사용한 이유와 장점은 다음과 같다.

 

1) AOP 관심사 분리

LogAdvice 클래스는 다양한 로그 설정 메서드를 포함 한다.

하지만, 이 로그 설정 메서드 들은 비즈니스 로직과는 별개이다.

(== 별개의 관심사 이다.)

 

@Aspect 어노테이션을 사용하면, 해당 클래스를 AOP 관점으로 정의 하게 되고,

그렇게 되면,

비즈니스 로직과, 어드바이스 로직을 분리하게 된다.

이는 유지 보수가 용이해짐을 의미 한다.

 

2) 모듈화 및 재사용

@Aspect 어노테이션을 사용해서, AOP 관점을 정의하면,

이 어드바이스 클래스는 다른곳에서도 재사용이 가능해진다.

그냥 LogAdvice 클래스를 그대로 재사용 하면 된다.

 

3) 명시성

@Aspect 어노테이션은 스프링 AOP와 함께 사용되는 개념이다.

스프링은 @Aspect 어노테이션을 인식하고,

자동으로 AOP 설정을 생성하고 실행한다.

이는 코드의 가독성을 높이고,

개발자들도 코드를 이해하기 쉽게 되는 장점을 가진다.

 

4) 스프링 AOP 통합

@Aspect 어노테이션은 스프링 AOP와 함께 사용되므로,

어드바이스 메서드가 적용될 지점과 관련된 부분을

XML 설정이 아닌, 자바에서 정의 할 수 있다는 장점을 가지게 된다.

 

 

반응형