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

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

by Hwanii_ 2023. 8. 11.
728x90

Spring을 사용하여, AOP (관점 지향 프로그래밍) 을 할 때,

비즈니스 메서드와 횡단 관심을 설정하고,

사용 하는 방법을 공부했었다.

이제까지의 기능 설정 방법으로는 applicationContext.xml 파일에서 

필요한 태그들을 사용해서 했었지만,


이번 글에서는 @ 어노테이션 방식으로 설정 해보려고 한다.

 

1.

applicationContext.xml 파일에서,

AOP를 어노테이션을 사용하겠다고, 코드를 작성 하기. 

 

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xmlns:context="http://www.springframework.org/schema/context"
   xmlns:aop="http://www.springframework.org/schema/aop"
   xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
      http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd
      http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd">
      
    	<context:component-scan base-package="com.spring.biz"/>
      
      	<!-- AOP를 어노테이션으로 하겠다는 의미 이다. -->
    	<aop:aspectj-autoproxy />

</beans>

 

<aop:aspectj-autoproxt /> 코드를 작성 하면 된다.

 

2.

자바에서 사용할 @ 어노테이션은 다음과 같다.

 

1) @Service

2) @Aspect

3) @Pointcut()

4) @Before()

5) @After()

6) @AfterReturning()

7) @AfterThrowing()

8) @Around

 

3.

@ 어노테이션으로 작성된, 어드바이스를 확인 하기.

 

package com.spring.biz.common;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Service;

@Service
@Aspect
public class LogAdvice {
	
	//	@Component 를 사용 하지 않고, @Service를 사용함으로써,
	//	Spring이 메모리 관리를 더 효율적으로 할 수 있도록 한다.
	
	@Pointcut("execution(* com.spring.biz..*Impl.*(..))")
	public void aPointcut() {}
	
	@Before("aPointcut()")
	public void printLog() {
		
		System.out.println("log : LogAdvice : printLog() : ");
		System.out.println("모든 비즈니스 메서드를 대상으로, 비즈니스 메서드 '수행 전'에 로그 호출.");
		System.out.println();
	}

}	//	LogAdvice

 

 

@Service :

@Component 어노테이션을 상속받은 어노테이션 이다.

Spring이 메모리 관리를 더 효율적으로 할 수 있도록 한다.

 

@Aspect :

Pointcut 과 (== 비즈니스 메서드)

어드바이스 == 공통 로직 == 횡단 관심 == 로그 관련 설정

를 결합하기 위해 필요한 어노테이션 이다.

 

@Pointcut() :

비즈니스 메서드에 대한 설정을 하기 위한 어노테이션 이다.

 

@Before() :

비즈니스 메서드 호출 전에 횡단 관심 == 로그 를

호출 하겠다는 의미의 어노테이션 이다.

 

4.

위의 이미지를 확인하면,

어디 경로에 있는,

어떤 비즈니스 메서드를 대상으로,

Pointcut 설정을 할건지를 설정하는,
@Pointcut 어노테이션을 확인 할 수 있다.

 

비즈니스 메서드를 사용 하기 위한 주체는 XxxDAO 이다.

하지만,

레이어 개념을 사용해서, XxxDAO를 직접 사용 하지 않고,

XxxService를 주체로 해서 비즈니스 메서드를 호출 하고 있었기에,

그러한 개념을 생각하면서,

execution() 괄호안의 경로를 작성 하면 된다.

 

 

com.spring.biz..*Impl.*(..) 은

biz 폴더 안에,

끝자리가 Impl 로 되는 클래스 내부의

모든 비즈니스 메서드를 대상으로 하겠다는 의미 이다.

 

5.

나머지도 어드바이스도 확인해보자.

 

package com.spring.biz.common;

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Service;

@Service
@Aspect
public class LogAdvice02 {
	
	@Pointcut("execution(* com.spring.biz..*Impl.select*(..))")
	public void bPointcut() {}
	
	@After("bPointcut()")
	public void printLog02() {

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

}	//	LogAdvice02

 

package com.spring.biz.common;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Service;

import com.spring.biz.member.MemberVO;

@Service
@Aspect
public class ReturningAdvice {	//	after-returning	

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

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

		String methodName = jp.getSignature().getName();	//	getSignature()은 시그니쳐 자체를 의미 한다.

		System.out.println(jp.getSignature());	//	MemberVO com.spring.biz.member.MemberService.selectOne(MemberVO)

		System.out.println();

		String getSignature = jp.getSignature().toString();

		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 : ReturningAdvice : returningPrintLog() : ");
		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();
			}
		}
	}

}	//	ReturningAdvice

 

package com.spring.biz.common;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Service;

//	서비스 하기 전에 예외 및 에러를 확인 하려는 용도로 사용 한다.

@Service
@Aspect
public class ThrowingAdvice {	//	after-throwing

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

	@AfterThrowing(pointcut = "dPointcut()", throwing = "exceptObj")
	public void throwingPrintLog(JoinPoint jp, Exception exceptObj) {	//	예외 발생 시 뜨는 로그.

		String methodName = jp.getSignature().getName();	//	getSignature()은 시그니쳐 자체를 의미 한다.

		System.out.println("log : ThrowingAdvice : throwingPrintLog() : ");
		System.out.println("methodName : " + methodName + " 의 반환 이후의 로그 호출.");
		System.out.println("exceptObj.getMessage() : " + exceptObj.getMessage());
	}

}	//	ThrowingAdvice

 

 

package com.spring.biz.common;

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

@Service
@Aspect
public class AroundAdvice {
	
	@Pointcut("execution(* com.spring.biz..*Impl.insert*(..))")
	public void cPointcut() {}
	
	@Around("cPointcut()")
	public Object aroundPrintLog(ProceedingJoinPoint pjp) throws Throwable {	
		
		//	외부의 비즈니스 메서드를 호출 하기 위해 ProceedingJoinPoint pjp를 인자로 삽입.
		//	around 는 메서드 시그니쳐 프레임이 독특하게도 아래와 같이 작성 하게 된다.
		
		String methodName = pjp.getSignature().getName();	//	getSignature()은 시그니쳐 자체를 의미 한다.

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

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

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

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

		return obj;
	}

}	//	AroundAdvice

 

6.

[ 정리 ]

 

1)

어드바이스 를 사용 하기 위해서는 객체화가 필요 하다.

== @Service 어노테이션

 

2)

@Pointcut 어노테이션으로, 비즈니스 메서드를 설정 한다.

 

3)

(횡단관심 == 공통로직) 의 실제 동작을 구현한 코드 == 어드바이스

 

@Around 어노테이션으로,

횡단관심 == 공통로직 == 로그 기능 메서드

은,

비즈니스 메서드 == 포인트컷 하고 결합하기위해 설정 한다.

 

4)

@Aspect 어노테이션으로 2) 와 3)을 결합 시키는 기능을 하는 어노테이션 이다.

 

 

 

7.

@ 어노테이션을 사용함으로써, 자바에 직접 작성 하게 되었고,

자바에 직접 작성하게 되어서,

공통되는 또는 반복되는 (유사한) 코드를 모듈화 할 수 있다는 생각을 할 수 있게 된다.

 

그래서,

다음 게시글 에서, 위의 작성된 모든 코드들을 모듈화 하는것을 정리 할 예정 이다.

 

링크 : https://hwanii96.tistory.com/335

 

AOP : 관점 지향 프로그래밍 (@ 어노테이션 방식 모듈화_최종)

 

hwanii96.tistory.com

 

반응형