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

AOP (관점 지향 프로그래밍) 관심분리 (.xml 파일 설정)

by Hwanii_ 2023. 8. 11.
728x90

https://hwanii96.tistory.com/332

 

AOP (Aspect Oriented Programming) (관점 지향 프로그래밍)

[ 참고 ] 객체 지향 프로그래밍 이란 ? Object Oriented Programming. 즉, OOP 라고 부른다. 객체 지향 프로그래밍은 응집도가 낮고, 응집도가 낮다는것은, 관련 없는 다양한 기능이 하나의 클래스에 섞여

hwanii96.tistory.com

 
위의 글에서 이미 정리 했었다.
 
몇부분 내용을 추가 하고 복습 하기 위해서 다시 작성 한다.
 
AOP의 주요 구성중에 횡단 관심을 구현한 코드인 Advice 코드 (클래스) 를 확인해보자.
 
[ 참고 ]
1) Advice (어드바이스)
횡단 관심사의 실제 동작을 구현한 코드 이다.
횡단 관심 == 공통 로직
비즈니스 메서드를 기준으로, 전 / 후 / 함께 / .. 언제든지 동작 시점을 설정 할 수 있다.
 
before : 비즈니스 메서드 이전에 어드바이스가 호출 된다.
after : 비즈니스 메서드 이후에 어드바이스가 호출 된다.
after-returning : 비즈니스 메서드가 메서드 수행 이후에 수행 결과를 반환 하면, 어드바이스가 호출 된다.
after-throwing : 비즈니스 메서드가 메서드 수행 이후에 에러가 발생 하게 되면, 어드바이스가 호출 된다.
★ around ★
비즈니스 메서드를 수행 하기 전과 수행 하기 후에 모두 호출 된다.
around가 진행 되는 동안에 비즈니스 메서드가 around 내부에서 호출 되는 것이다.
 
여기서 before, after 방식의 동작 시점 설정은 패스하고,
 
after-returning,
after-throwing
around
방식의 동작 시점 설정을 아래에서 다시 정리해보겠다.
 
1.
after-returning 

after-returning 어드바이스는,
CRUD 메서드가 결과값을 반환 하고 나서,
호출되는 어드바이스 이다.
 
이때,
반환된 결과값을 자바에서 구현해놓은 메서드 내부에서, 직접적으로 사용 하기 위해서,
그 결과값을 메서드의 인자로 받아야지만, 사용 할 수 있으니,
메서드의 인자로 매개변수 타입과 매개변수명을 작성해 준다.
 

 
위의 이미지를 확인하면, 메서드의 인자로 작성된,
Object 타입의 returnObj 라는 매개변수명을 확인 할 수 있다.
 
[ 개념 ]
 

바인딩 기법 이라 한다.

 
CRUD의 결과값으로,
C, U, D는 boolean 타입이 리턴 타입 이므로,
true of false가 반환될것이고,
R의 selectAll()은 List 타입이 리턴 타입이고,
R의 selectOne()은 XxxVO 타입이 리턴 타입 이다.
 
이렇게 비즈니스 메서드 수행 이후, 결과값으로 어떤게 올지 알 수 없으므로,
최상위 자료형인 Object 타입으로 파라미터명을 작성해주는것이다.
이러한 파라미터명을 바인딩 변수 라고 부른다.
 
이는 다형성의 개념을 활용하는 개념이다.
 

반환된 결과값을 가지고 메서드 내부에서 활용하는 예시

 

 
returnObj라는 파라미터로 데이터가 들어오면,
해당 데이터를 MemberVO 자료형으로 다운캐스팅을 하여,
데이터를 MemberVO 자료형의 mVO 참조변수에 저장 한다.
 
그리고,
mVO에 있는 role 프로퍼티가 ADMIN 이면,
[관리자 입장] 으로 로그를 찍게 되고,
mVO에 있는 role 프로퍼티가 ADMIN 이 아니면,
[사용자 입장] 으로 로그를 찍게 된다.
 
이때,
returnObj 로 들어온 데이터를,
MemberVO 자료형으로 다운캐스팅을 할 수 있으려면,
실제로 들어온 데이터가 MemberVO 자료형 이어야만 하기 때문에,
 
instanceof 연산자를 사용해서, 캐스팅이 가능한지를 확인 하는 로직을 사용 했다.
 
위와 같이 비즈니스 메서드의 결과값을 받기 위해서, .xml 파일에서 설정을 한가지 추가 해줘야 한다.
 

<aop:aspect ref = "returningAdvice">
	<aop:after-returning method = "returningPrintLog" pointcut-ref = "ePointcut" returning = "returnObj" />
</aop:aspect>

 

 
파라미터명으로 returnObj로 작성해뒀기 때문에,
applicationContext.xml 에서 returning 속성의 값을 returnObj로 작성해주면 된다.
 
참고로,
equals 메서드를 Object 클래스 로부터, 오버라이딩 했을 때는,
equals 메서드 인자로 Object obj 라고 되어 있는데,
이렇게 작성된 파라미터명은 어감상 일회성으로 사용되고 끝이구나 ~
라는 느낌을 주고,
 
위의 이미지와 같이, 메서드의 인자로 Object returnObj 라고 되어 있으면,
즉, Object XxxObj 라고 되어 있으면,
메서드 내부에서 해당 파라미터명으로 받는 데이터를,
두번 이상 활용 한다는 것을 알 수 있는 변수명 표기 방법 이라고 할 수 있겠다.
 
 
 
메서드의 인자로, JoinPoint jp 도 인자로 작성된 것을 확인 할 수 있는데,
JoinPoint 클래스의 참조변수로 getSignature() 라는 메서드를 사용 할 수 있고,
getSignature() 메서드는, 메서드의 시그니쳐 자체를 반환하는 기능을 가진다.
 
그래서, 인자로 JoinPoint jp 를 작성한 이유는,
비즈니스 메서드의 정보를 알고 싶어서 사용 했다고 생각 할 수 있겠다.
 

 
만약에, 비즈니스 메서드로 MemberService, 즉, MemberDAO 를 사용 했다면,

 
콘솔창에 위의 이미지와 같이 메서드 시그니쳐 자체를 확인 할 수 있다.
 
getSignature() 메서드는 그 자체로 toString() 이 되기 때문에,
따로 toString() 오버라이딩을 하지 않아도 위의 이미지와 같이 콘솔창에 나오게 된다.
 

 
jp.getSignature().getName() 메서드는,
메서드 시그니쳐 에서, 메서드의 이름을 보여 주는 기능을 한다.
 
암튼, 위와 같이 비즈니스 메서드의 정보를 사용 하기 위해서 사용 한다고 보면 된다.
 
메서드 내부 전체 코드는 아래와 같다.
 

public class ReturningAdvice {

	public void returningPrintLog(JoinPoint jp, Object returnObj) {
    
    	String methodName = jp.getSignature().getName();
        
        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

 
코드가 이것저것 작성 되어 있는데, 참고만 하자.
 
2.
after-throwing
 
after-throwing 어드바이스는,
비즈니스 메서드를 호출 했을 때, 발생하는 예외 자체를 바인딩 변수로 가져 올 수 있다.
 
코드를 확인해보자.
 

 
발생한 예외 자체를 바인딩 변수로 가져 올 수 있다.
 
Exception exceptObj 에서, Exception은 예외의 최상위 클래스 이다.
 
세상에 모든 예외를 exceptObj 라는 파라미터명으로 되어있는 인자로 가져오겠다는 의미 이다.

 

보통, after-throwing 은,

서비스 하기 전까지 발생하는 예외 또는 에러를 확인 하려는 용도로 사용 한다.

만약,

이미 try-catch 처리가 되어있으면 호출이 되지 않는다.

또한,

Service 레이어 에서 발생한 경우에만 어드바이스와 결합 된다.

즉,

Impl 클래스 내부에서 발생한 예외인 경우에 결합되어 로그가 호출되고,

DAO, SQL, .. 에서는 로그가 호출 되지 않는다.


[ 참고 ]

 

자바에서 예외 클래스는 java.lang.Exception 클래스를 상속하는 구조로 되어있다.

Exception 클래스를 직접 상속하여 예외 클래스를 작성하거나,

또는 Exception 클래스의 하위 클래스를 상속하여 구체적인 예외 클래스를 작성 할 수 있다.

 

예외 클래스는 크게 두 가지로 나뉜다.

체크 예외 (Checked Exception) 과,

언체크 예외 (Unchecked Exception) 이다.

 

체크 예외 :

Exception 클래스를 상속하며, RuntimeException 을 제외한 모든 예외 클래스가 해당 된다.

 

컴파일러가 체크하므로 반드시 예외 처리 코드가 필요 하다.

즉, throws 또는 try-catch 블록을 통해 처리 해야만 한다.

 

파일 입출력, 네트워크 통신, 데이터베이스 연결 등과 같이 프로그램 외부 요인에 의해,

발생 할 수 있는 예외들이 체크 예외에 속한다.

 

체크 예외의 종류로는,

IOException,

SQLException,

..

등이 있다.

 

언체크 예외 :

RuntimeException 클래스를 상속한 예외 클래스 이다.

 

컴파일러가 체크하지 않는다.

개발자의 주의에 의존 한다.

 

주로 프로그램 코드의 버그나, 잘못된 사용으로 인해 발생되는,

예외들이 언체크 예외에 속한다.

 

언체크 예외의 종류로는,

NullPointerException,

ArithmeticException,

ArrayIndexOutOfBoundsException,

IllegalArgumentException,

..

등이 있다.

 
3.
around
 
around 어드바이스는,
비즈니스 메서드를 수행 하기 전과 수행 하기 후에 모두 호출 된다.
around가 진행 되는 동안에 비즈니스 메서드가 비즈니스 메서드가 around 내부에서 호출 되는 것이다.
 
코드를 확인해보자.
 
ProceedingJoinPoint는 JoinPoint 인터페이스를 상속받은 인터페이스 이다.
around 어드바이스는 반드시, ProceedingJoinPoint 인터페이스를 인자로 받아야만 한다.
왜 ? 외부의 비즈니스 메서드를 호출 해야만 하기 때문에 !
 

 
StopWatch 클래스는, 스프링에서 기본 제공 해주는 클래스 이다.

StopWatch 클래스를 사용해서,
sw.start() 메서드를 호출 하고,

비즈니스 메서드가 호출되고,
비즈니스 메서드의 기능 수행이 끝나면,

sw.stop() 메서드를 호출 하는데,
이때, 걸렸던 시간을 표현해주는 메서드를 사용할 수 있다.
 
getTotalTimeMillis() 메서드와,
getTotalTimeSeconds() 메서드가 있다.
 

 

4.

applicationContext.xml 내부에 작성된 기능 설정 확인 하기.

 

<?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"/>
      
    <bean id = "logAdvice" class ="com.spring.biz.common.LogAdvice" />
    <bean id = "logAdvice02" class ="com.spring.biz.common.LogAdvice02" />
    <bean id = "aroundAdvice" class = "com.spring.biz.common.AroundAdvice" />
    <bean id = "throwingAdvice" class = "com.spring.biz.common.ThrowingAdvice" />
    <bean id = "returningAdvice" class = "com.spring.biz.common.ReturningAdvice" />

    <aop:config>
    
    	<aop:pointcut expression = "execution(* com.spring.biz..*Impl.*(..))" id = "aPointcut"/>
		<aop:pointcut expression = "execution(* com.spring.biz..*Impl.select*(..))" id = "bPointcut"/>
   		<aop:pointcut expression = "execution(* com.spring.biz..*Impl.insert*(..))" id = "cPointcut"/>
   		<aop:pointcut expression = "execution(* com.spring.biz..*Impl.*(..))" id = "dPointcut"/>
   		<aop:pointcut expression = "execution(* com.spring.biz..*Impl.*(..))" id = "ePointcut"/>
        
        <aop:aspect ref = "logAdvice">
        	<aop:before method = "printLog" pointcut-ref = "aPointcut" />
      	</aop:aspect>
		
		<aop:aspect ref = "logAdvice02">
        	<aop:after method = "printLog02" pointcut-ref = "bPointcut" />
      	</aop:aspect>
      	
		<aop:aspect ref = "aroundAdvice">
			<aop:around method = "aroundPrintLog" pointcut-ref = "cPointcut" />
		</aop:aspect>
        
        <aop:aspect ref = "throwingAdvice">
			<aop:after-throwing method = "throwingPrintLog" pointcut-ref = "dPointcut" throwing = "exceptObj"/>
		</aop:aspect>
        
        <aop:aspect ref = "returningAdvice">
			<aop:after-returning method = "returningPrintLog" pointcut-ref = "ePointcut" returning = "returnObj" />
		</aop:aspect>
		
	</aop:config>

 

5.

위에서는,

관점 지향 프로그래밍인, AOP 개념을 이용해서,

비즈니스 메서드와 횡단 관심을 상황에 맞게, aspect 하여,

실제로 사용 하는 모습을 확인 했었는데,

설정을 위해서 사용한 방법으로는 .xml 이였다.

.xml 파일에서 기능 설정을 하니, 가독성 측면에서 불리하고, 복잡해 지는 문제점이 생긴다.

따라서, 위의 모든 과정을 @ 어노테이션 방식으로 변경 해 보려고 한다.

 

https://hwanii96.tistory.com/334

 

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

 

hwanii96.tistory.com

 

 

 

반응형