framework/spring AOP(관점지향)

[springAOP] 5. 스키마기반의 AOP 만들어 사용하기 | Advice클래스의 매개변수

jeri 2024. 7. 31. 11:58
반응형
  • 일반적으로 특별한 경우가 아니면, 매개변수는 잘 만들지 않음
  • 핵심관심모듈이 가진 데이타에 대한 처리의 부가적인 기능을 하고 싶을 때 매개변수 사용함

 

 

01. [핵심관심모듈] DAO클래스&Service클래스

JoinPointBean
  • 핵심관심모듈이 가진 메소드들은 개발자들에 의해 호출하므로 아무렇게나 작성 가능
package xyz.itwill07.aop;

//핵심관심모듈
public class JoinPointBean {
	public void add() {
		System.out.println("### JoinPointBean 클래스의 add() 메소드 호출 ###");
	}

	public void modify(int num, String name) {
		System.out.println("### JoinPointBean 클래스의 modify(int num, String name) 메소드 호출 ###");
	}

	public void remove(int num) {
		System.out.println("### JoinPointBean 클래스의 remove(int num) 메소드 호출 ###");
	}

	public String getName() {
		System.out.println("### JoinPointBean 클래스의 getName() 메소드 호출 ###");
		return "홍길동";
	}

	public void calc(int num1, int num2) {
		System.out.println("### JoinPointBean 클래스의 calc(int num1, int num2) 메소드 호출 ###");
		System.out.println("몫 = "+(num1/num2));
	}
}

 

 

02. [횡단관심모듈] : Advice클래스

  • 횡단관심모듈이 가진 메소드는 aspectj에 의해 핵심관심모듈이 실행되기 전에 전 , 후 , 전후 에 호출되기 위해 사용되므로 우리 마음대로 만들 수는 없음
  • 횡단관심모듈의 소드명은 상관없음 xml 파일에서 설정할 수 있음
  • 그러나 매개변수는 딱 정해져있는 매개변수만을 받을 수 있음 (아무렇게 설정 불가능)
JoinPointAdvice
package xyz.itwill07.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;

//횡단관심모듈 - Advice 클래스
public class JoinPointAdvice {

	// [Around Advice 메소드]를 제외한 나머지 Advice 메소드 :
  	// => 반환형 : 일반적으로 [반환형을 void]로 작성 - 반환해봤자 얻어서 사용할 수 있는 곳이 없으믐로 그냥 void 하는 것임
	// => 매개변수: 매개변수를 작성하지 않거나, 매개변수를 작성한다면 JoinPoint 인터페이스로 선언된 매개변수만을 작성 가능    
	// => Advice 메소드를 작성 규칙에 맞지 않게 작성할 경우 IllegalArgumentException 발생 (매개변수를 잘못 썼어요! 예외)
    
	//[JoinPoint 객체] : 타겟 메소드(핵심관심모듈이 가진 메소드) 관련 정보가 저장된 객체
	// => 스프링 컨테이너가 Advice 클래스의 메소드를 호출할 때 타겟메소드 관련 정보를 JoinPoint 객체에 저장하여 매개변수에 전달
	// => Advice 클래스의 메소드에서 타겟메소드 관련 정보가 필요한 경우 매개변수를 선언하여 사용
    
    
    //1.
    //Before Advice 메소드 - 전 삽입
	//사용가능한 매개변수 : 없음 or JoinPoint
	public void beforeDisplay(JoinPoint joinPoint) {
		//System.out.println("[before] 핵심관심코드 실행 전에 삽입되어 실행될 횡단관심코드");

		//JoinPoint.getTarget() : 타겟메소드를 호출한 객체(핵심관심모듈)를 반환하는 메소드 - Object 타입으로 객체 반환
		//Object.getClass() : 객체를 생성한 클래스의 Class 객체(Clazz)를 반환하는 메소드
    	//리플랙션 기술을 이용할 수도 있으나, 리플랙션은 접근지정자에 상관없이 필드에 모두 접근하므로 위험함 , 잘못만드면 무한루프에 빠지거나 오버플로우 발생하여 메모리 어마무시하게 차지할 수도 있음
        
    	//=> Class 객체(클래즈) : 클래스 정보를 저장하고 있는 객체 - ex) 클래스명(패키지명 포함) , 클래스는어디의메모리에 저장되고 있는지
    	//=> Class 객체(클래즈 - Clazz) 만드는 방법 3가지 : class.forName | class.Class | class.getClass()
        
		//Class.getName() : Class 객체에 저장된 클래스(패키지포함)의 이름을 문자열로 반환하는 메소드
		//System.out.println(joinPoint.getTarget().getClass().getName());
    	//=> xyz.itwill07.aop.JoinPointBean - 타겟메소드가 포함된 클래스가 누구인가?

		//Class.getSimpleName() : Class 객체에 저장된 클래스(패키지미포함)의 이름을 문자열로 반환하는 메소드
		//System.out.println(joinPoint.getTarget().getClass().getSimpleName());
    	//=> JoinPointBean

		//JoinPoint.getSignature() : 타켓메소드의 정보가 저장된 Signature 객체를 반환하는 메소드
		//Signature.getName() : 타켓메소드의 이름을 문자열로 반환하는 메소드
		//System.out.println(joinPoint.getSignature().getName());
    	//=> add , modify, remove, getName, calc

		//JoinPoint.getArgs() : 타켓메소드의 매개변수에 저장된 모든 값(객체)을 제공받아 Object
		//객체 배열로 반환하는 메소드
		//System.out.println(joinPoint.getArgs());
    	//=> [Ljava.lang.Object;@1de5f0ef : Object 객체 배열

		String className = joinPoint.getTarget().getClass().getSimpleName();
		String methodName = joinPoint.getSignature().getName();
		Object[] params = joinPoint.getArgs();

		System.out.print("[before]"+className+" 클래스의 "+methodName+"(");
		for(int i=0; i<params.length; i++) {
			System.out.print(params[i]);
			if(i<params.length-1) {
				System.out.print(", ");
			}
		}
		System.out.println(") 메소드 호출");
	}
    
    
    //2. 
    //After Advice 메소드 - 무조건 실행 후 삽입
	//사용가능한 매개변수 : 없음 or JoinPoint
	//After Advice 메소드
	public void displayMessage(JoinPoint joinPoint) {
		//System.out.println("[after]핵심관심코드 실행 후에 무조건 삽입되어 실행될 횡단관심코드");

		Object[] params=joinPoint.getArgs();
		System.out.println("[after]학번이 "+params[0]+"인 학생정보를 삭제 하였습니다.");
	}


	//3.
    //After Returning Advice 메소드 - 정상 실행 후 삽입
	//xml 파일에 반드시 [returning 속성값] 작성
	//사용가능한 매개변수 : 없음 or JoinPoint or Object (반환값이 무조건 정해져있다면? String , int..가능)   
    // => After Returning Advice 메소드에는 JoinPoint 인터페이스의 매개변수 외에 Object 클래스의 매개변수 선언 가능
	// => 스프링 컨테이너는 Object 클래스의 매개변수에 타겟메소드의 반환값이 저장되도록 전달
	// => 타겟메소드에서 반환되는 값(객체)의 자료형이 고정되어 있는 경우 Object 클래스 대신 반환되는 값(객체)의 자료형으로 매개변수 작성 가능
	// => Spring Bean Configuration File의 AOP 설정에서 after-returning 엘리먼트에 반드시 returning 속성을 사용하여 반환값을 저장할 매개변수의 이름을 속성값으로 지정
	// => after-returning 엘리먼트에 returning 속성이 없거나 속성값이 잘못 설정된 경우 IllegalArgumentException 발생
	public void displayName(Object object) {
		//System.out.println("[after-returning]핵심관심코드가 정상적으로 실행된 후에 삽입되어 실행될 횡단관심코드");

		//반환값이 Object 타입이라 형변환 필요 - 자식이 누구인지 명확하게 구분하기 위해!!
        //instanceof 연산자를 사용하여 매개변수에 저장된 객체의 자료형을 구분하여 처리
		if(object instanceof String) {
			String name=(String)object;//명시적 객체 형변환
			System.out.println("[after-returning]"+name+"님, 안녕하세요.");
		}
	}
    
    
    //4.
    //After Throwing Advice 메소드 - 예외발생된 경우 삽입
	//xml 파일에 반드시 [throwing 속성값] 작성
	//사용가능한 매개변수 : 없음 or JoinPoint or Exception (예외가 무조건 정해져있다면? ex)NullPointerExcpetion 가능
	//하지만 프로그램에서 발생할 수 있는 예외는 다양하므로 그냥 부모객체인 Exception으로 작성하는 것이 대부분
    // => After Throwing Advice 메소드에는 JoinPoint 인터페이스의 매개변수 외에 Exception 클래스의 매개변수 선언 가능
	// => 스프링 컨테이너는 Exception 클래스의 매개변수에 타켓메소드의 명령 실행 시 발생된 예외(Exception 객체)가 저장되도록 전달
	// => 타겟메소드에서 발생되는 예외가 고정되어 있는 경우 Exception 클래스 대신 자식클래스로 선언된 매개변수 작성 가능
	// => Spring Bean Configuration File의 AOP 설정에서 after-returning 엘리먼트에 반드시 throwing 속성을 사용하여 Exception 객체를 저장할 매개변수의 이름을 속성값으로 지정
	// => after-throwing 엘리먼트에 throwing 속성이 없거나 속성값이 잘못 설정된 경우 IllegalArgumentException 발생
	public void exceptionHandle(JoinPoint joinPoint, Exception exception) {
		System.out.println("[after-throwing]핵심관심코드 실행 시 예외가 발생된 경우 삽입되어 실행될 횡단관심코드");

		String className = joinPoint.getTarget().getClass().getSimpleName();
		String methodName = joinPoint.getSignature().getName();

		System.out.println("[after-throwing]"+className+" 클래스의 "+methodName
				+" 메소드에서 발생된 예외 = "+exception.getMessage());
	}
    
    
    //5.
    //Around Advice 메소드 - 전,후 삽입
	//만약 타겟메소드의 반환형이 없다면 굳이 반환형을 쓸 필요는 없지만, 일반적으로 Object클 반환하는 경우가 많긴함
	//사용가능한 매개변수 : 없음 or ProceedingJoinPoint
	//Throwable 예외가 발생하므로 **예외처리하거나 예외전달 필수 **
    // => Around Advice 메소드는 반환형을 Object 클래스로 작성하고, 매개변수에 자료형은 ProceedingJoinPoint 인터페이스로 작성
	// => 타켓메소드의 반환값을 제공받아 반환하기 위해 Object 클래스를 반환형으로 작성
	// => 타겟메소드 관련 정보를 ProceedingJoinPoint 인터페이스의 매개변수로 제공받아 Around Advice 메소드에서 사용
	// => ProceedingJoinPoint : 타겟메소드 관련 정보를 저장하기 위한 객체
	// => JoinPoint 객체와 다른 점은 타겟메소드를 직접 호출하기 위한 메소드를 제공함
    // => 그래야지 타겟메소드 호출 전에 실행이 가능헐 것임!!
	public Object disply(ProceedingJoinPoint joinPoint) throws Throwable {
		System.out.println("[around]핵심관심코드 실행 전 삽입되어 실행될 횡단관심코드");
		//ProceedingJoinPoint.proceed() : 타겟메소드를 호출하는 메소드 - 핵심관심코드 실행
		// => 타겟메소드를 호출하여 반환되는 결과값을 제공받아 저장
		// => Throwable(Error 클래스와 Exception 클래스의 부모클래스) 객체(예외)가 발생되므로
		//예외를 처리하거나 예외를 전달
		Object object=joinPoint.proceed();
		System.out.println("[around]핵심관심코드 실행 후 삽입되어 실행될 횡단관심코드");
		return object;//타겟메소드를 호출하여 반환된 결과값을 메소드를 호출한 명령으로 반환
	}
    
    
    
    
}

 

 

03. 💖Spring Bean Configuration File

[07-2_param.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: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/aop> <http://www.springframework.org/schema/aop/spring-aop.xsd>">



	<!-- [핵심관심모듈]로 선언된 클래스를 Spring Bean으로 등록 -->
	<bean class="xyz.itwill07.aop.JoinPointBean" id="joinPointBean"/>
	<!-- [횡단관심모듈]로 선언된 클래스를 Spring Bean으로 등록 -->
	<bean class="xyz.itwill07.aop.JoinPointAdvice" id="joinPointAdvice"/>
    
    
    
	<aop:config>
		<aop:aspect ref="joinPointAdvice">
          
            <!-- => [JoinPoint]: 타겟메소드 실행전 beforeDisplay 삽입, [pointCut]: 타겟메소드 = 모든 메소드 -->
			<aop:before method="beforeDisplay" pointcut="execution(* *(..))"/>

            <!-- => [JoinPoint]: 타겟메소드 무조건 실행후 displayMessage 삽입, [pointCut]: 타겟메소드 = remove 메소드 -->
			<aop:after method="displayMessage" pointcut="execution(void remove(int))"/>
          
            <!-- => [JoinPoint]: 타겟메소드 정상 실행후 displayName 삽입, [pointCut]: 타겟메소드 = getName 메소드, [returning]: 반환값 Object객체는 object 매개변수에 저장해주세요! -->
			<!-- returning 속성 : 타겟메소드의 반환값을 전달받아 저장할 매개변수의 이름을 속성값으로 설정 -->
			<aop:after-returning method="displayName" pointcut="execution(java.lang.String getName())" returning="object"/>
          
          	<!-- throwing 속성 : 타겟메소드의 Exception 객체를 전달받아 저장할 매개변수의 이름을 속성값으로 설정 -->
            <!-- => [JoinPoint]: 타겟메소드에서 예외가 발생된 경우 exceptionHandle 삽입, [pointCut]: 타겟메소드 = calc 메소드 , [throwing]: 반환값 Exception객체는 exception 매개변수에 저장해주세요! -->
			<aop:after-throwing method="exceptionHandle" pointcut="execution(void calc(int, int))" throwing="exception"/>
          
			<aop:around method="disply" pointcut="execution(* modify(..))"/>
		</aop:aspect>
	</aop:config>


</beans>

 

 

04. 실행프로그램

JoinPointApp
package xyz.itwill07.aop;

import org.springframework.cglib.beans.BeanGenerator;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class JoinPointApp {
	public static void main(String[] args) {
		ApplicationContext context = new ClassPathXmlApplicationContext("07-2_param.xml");
		JoinPointBean bean = context.getBean("joinPointBean",JoinPointBean.class);
		System.out.println("======================================================");
		bean.add();
		System.out.println("======================================================");
		bean.modify(1000, "홍길동");
		System.out.println("======================================================");
		bean.remove(2000);
		System.out.println("======================================================");
		bean.getName();
		System.out.println("======================================================");
		//bean.calc(20, 10);
		bean.calc(20, 0); //어떤 수를 0으로 나누면 ArithmeticException 예외 발생
		System.out.println("======================================================");
		((ClassPathXmlApplicationContext)context).close();
	}
}

 

반응형