Spring

AOP

영카이브 2024. 2. 28. 23:19

AOP(Aspect Oriented Programing)란?

AOP(Aspect Oriented Programming)는 여러 관점(Aspect)을 기반으로 프로그래밍하는 개념이다. 여기서 "관점"은 소프트웨어에서의 횡단 관심사(cross-cutting concern)를 의미한다. 횡단 관심사란 주요 기능이 아니지만 여러 부분에서 반복적으로 나타나는 기능을 말한다. 예를 들어, 로그처리, 트랜잭션처리, 보안처리 등이 있다.

사용자가 요구했던 업무로직이 아니라 만들다 보니 개발자나 운영자가 프로그램을 구현 또는 테스트하기위해 필요한 코드가 있다. 이러한 코드는 주 업무 로직은 아니기 때문에 사용자는 모른다. 따라서 사용자, 개발자, 운영자의 관점에 따라 분리해서 만들게 된다. 이것을 관점지향 프로그래밍 즉 AOP라고 하며 객체지향 프로그래밍의 보완적인 개념이라 볼 수 있다. 

 

Primary Concern과 Cross-cutting Concern

 

Primary Concern(주요 관심사) : 소프트웨어에서의 핵심 비즈니스 로직이나 주요 기능 

ex) 고객정보 추가, 수정, 삭제 

Cross-cutting Concern(횡단 관심사) : 소프트웨어에서의 주요 기능과 관련이 있지만 여러 모듈 또는 계층에 걸쳐 반복적으로 나타나는 기능, 보통 주 업무 로직의 앞이나 뒤에 있는데 Core Concen에 두는 것이 아니라 따로 분리해 두고 필요할 때 쓴다. (그림 참조)

ex) 로그처리, 트랜잭션처리, 보안처리

 

프록시(Proxy)란?

프록시(Proxy)는 다른 객체에 대한 인터페이스를 제공하여 해당 객체에 대한 접근을 제어하거나 추가적인 기능을 제공하는 객체 이다. 클라이언트가 실제 객체에 직접 접근하는 대신 프록시 객체를 통해 상호 작용한다.

  • 원격 서비스에 대한 엑세스 제어
  • 리소스 지연 로딩
  • 보안 및 권한 부여

프록시는 AOP(Aspect-Oriented Programming)와 함께 사용될 때 횡단 관심사(cross-cutting concern)를 구현하는 데에도 활용된다. 

 

AOP 구현

NewlecExam.java의 total()과 avg()에서 로직실행 시간계산을  cross-cutting concern으로 빼려고한다.

 

기존 코드

Program.java

public class Program {

	public static void main(String[] args) {
		Exam exam =  new NewlecExam(1, 1, 1, 1);
		System.out.printf("total is %d\n", exam.total());
	}

}

 

NewlecExam.java

public class NewlecExam implements Exam {
	
	private int kor; 
	private int eng;
	private int math;
	private int com; 

	public NewlecExam() {
		// TODO Auto-generated constructor stub
	}

	public NewlecExam(int kor, int eng, int math, int com) {
		this.kor = kor;
		this.eng = eng;
		this.math = math;
		this.com = com;
	}

	public int getKor() {
		return kor;
	}

	public void setKor(int kor) {
		this.kor = kor;
	}

	public int getEng() {
		return eng;
	}

	public void setEng(int eng) {
		this.eng = eng;
	}

	public int getMath() {
		return math;
	}

	public void setMath(int math) {
		this.math = math;
	}

	public int getCom() {
		return com;
	}

	public void setCom(int com) {
		this.com = com;
	}

	@Override
	public int total() {
		long start = System.currentTimeMillis(); 
		int result = kor+eng+math+com; // 실질적 주 업무로직 
		try {
			Thread.sleep(200);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		long end = System.currentTimeMillis();
		String message = (end-start) + "ms 시간이 걸렸습니다.";
		System.out.println(message);
		return result;
	}

	@Override
	public float avg() {
		float result = total()/4.0f; 
		return result; 
	}

		
	@Override
	public String toString() {
		return "NewlecExam [kor=" + kor + ", eng=" + eng + ", math=" + math + ", com=" + com + "]";
	}


}

 

 

프록시 적용하여 순수자바로  AOP구현

Proxy객체 생성을 위해 Proxy.newProxyInstance() 메서드가 사용된다.

이 메서드에 전달되는 세 개의 매개변수가 있다. 

  1. 클래스 로더(Class Loader): 프록시 클래스의 로딩을 담당, 필요한 객체 클래스이름.class.getClassLoader()와 같이 실제 클래스의 클래스 로더를 사용한다.
  2. 인터페이스 배열: 프록시 객체가 구현해야 하는 인터페이스를 나타낸다. 코드에서는 Exam 인터페이스를 구현해야 한다.
  3. InvocationHandler 구현체: InvocationHandler는 프록시 객체의 메서드 호출을 처리하는 인터페이스, 코드에서는 익명 클래스를 사용하여 InvocationHandler를 구현하였다. InvocationHandler의 invoke() 메서드는 프록시 객체가 호출된 메서드와 메서드의 매개변수를 받아 실제 객체의 메서드를 호출하고 부가적인 작업(예: 실행 시간 측정)을 수행한 후 결과를 반환한다.

 아래 코드는 프록시 객체를 생성하여 Exam 인터페이스를 구현하고, InvocationHandler를 사용하여 프록시 객체의 메서드 호출을 가로채어 부가적인 작업을 수행하는 로직이다.

 

Program.java

public class Program {

	public static void main(String[] args) {
		// Real 객체 생성
		Exam exam =  new NewlecExam(1, 1, 1, 1);
		
		// Proxy 객체 생성
		Exam proxyExam = (Exam)Proxy.newProxyInstance(
			NewlecExam.class.getClassLoader(), // 클래스 로더
			new Class[] {Exam.class}, // 인터페이스
			new InvocationHandler() { // InvocationHandler 구현

				@Override
				public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
					// 메서드 호출 전 시간 측정
					long start = System.currentTimeMillis(); 
					// 실제 업무 로직 호출
					Object result = method.invoke(exam, args);
					// 메서드 호출 후 시간 측정 및 출력
					long end = System.currentTimeMillis();
					String message = (end - start) + "ms 시간이 걸렸습니다.";
					System.out.println(message);
					return result;
				}
			});
		
		// Real 객체의 주요 기능 호출
		System.out.printf("total is %d\n", exam.total());
		System.out.printf("avg is %f\n", exam.avg());
		
        	System.out.println("-----------------");
        
		// Proxy 객체를 통해 주요 기능 및 부가적 기능 실행 시간 출력
		System.out.printf("total is %d\n", proxyExam.total());
		System.out.printf("avg is %f\n", proxyExam.avg());

 

NewlecExam.java

public class NewlecExam implements Exam {
	
	private int kor; 
	private int eng;
	private int math;
	private int com; 

	public NewlecExam() {
		// TODO Auto-generated constructor stub
	}

	public NewlecExam(int kor, int eng, int math, int com) {
		this.kor = kor;
		this.eng = eng;
		this.math = math;
		this.com = com;
	}

	public int getKor() {
		return kor;
	}

	public void setKor(int kor) {
		this.kor = kor;
	}

	public int getEng() {
		return eng;
	}

	public void setEng(int eng) {
		this.eng = eng;
	}

	public int getMath() {
		return math;
	}

	public void setMath(int math) {
		this.math = math;
	}

	public int getCom() {
		return com;
	}

	public void setCom(int com) {
		this.com = com;
	}

	@Override
	public int total() {
		
		int result = kor+eng+math+com; // 실질적 주 업무로직 
		try {
			Thread.sleep(200);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		return result;
	}

	@Override
	public float avg() {
		float result = total()/4.0f; 
		return result; 
	}

		
	@Override
	public String toString() {
		return "NewlecExam [kor=" + kor + ", eng=" + eng + ", math=" + math + ", com=" + com + "]";
	}
}

 

출력

total is 4
avg is 1.000000
-----------------
214ms 시간이 걸렸습니다.
total is 4
215ms 시간이 걸렸습니다.
avg is 1.000000

 

 

프록시 적용하여 스프링으로 AOP구현

  1. XML 설정 파일(setting.xml):
    • 먼저, NewlecExam 객체를 생성하여 빈으로 등록한다. 이때 시험 과목의 점수를 설정한다.
    • 다음으로, 로그를 기록하는 LogAroundAdvice 클래스를 빈으로 등록한다.
    • 마지막으로, 프록시 객체를 생성하는 ProxyFactoryBean 클래스를 빈으로 등록한다. 이때, 프록시의 대상이 되는 객체를 설정하고, 부가적인 기능을 담당할 핸들러를 지정한다.
    • <property name="target" ref="target" /> 부분에서 target은 실제 대상이 되는 객체를 가리키는 것이며, ref="target"은 빈으로 등록된 NewlecExam 객체를 참조하라는 의미다.
    • 이렇게 설정된 프록시 객체는 대상 객체로 지정된 NewlecExam 객체에 대한 호출을 가로채어 부가적인 기능을 적용할 수 있다.
  2. 메인 클래스(Program):
    • 프로그램이 시작되면 ApplicationContext를 생성하여 설정 파일을 로드한다.
    • 설정 파일에서 생성한 프록시 객체를 가져와서 exam 객체로 사용한다.
    • exam 객체의 total()과 avg() 메서드를 호출하여 시험 점수의 총점과 평균을 출력한다.
  3. 부가적인 기능(LogAroundAdvice):
    • LogAroundAdvice 클래스는 MethodInterceptor 인터페이스를 구현하여 부가적인 로깅 기능을 제공합니다.
      • LogAroundAdvice의 활용
        1. 성능 모니터링: 메서드 실행 시간을 측정하여 성능 통계를 수집하거나, 메서드 호출이 발생한 시간과 관련된 정보를 로깅한다.
        2. 트랜잭션 관리: 메서드 호출 전 후에 트랜잭션의 시작과 종료를 로깅하거나, 트랜잭션 경계 내에서의 예외 처리를 추가한다.
        3. 보안 강화: 메서드 호출 전에 권한 확인을 수행하고, 호출 후에 사용자의 활동을 로깅하여 보안 문제를 탐지하고 예방한다.
        4. 캐싱: 메서드 호출 결과를 캐싱하거나, 캐시에서 값을 읽어오는 로직을 추가한다.
    • invoke() 메서드에서는 메서드 호출 전과 후에 시간을 측정하여 실행 시간을 출력하고, 실제로 주요 로직이 수행되도록 한다.
  4. 프록시 생성 및 적용:
    • 프록시 객체는 ProxyFactoryBean을 통해 생성됩니다. 이때, 프록시의 대상이 되는 객체로 target 빈을 설정하고, 부가적인 기능을 담당할 핸들러로 logAroundAdvice 빈을 지정한다.
    • 이렇게 설정된 프록시 객체는 설정 파일에 정의된 대로 실제 대상 객체의 메서드 호출을 가로채어 부가적인 기능을 수행한 뒤에 실제 메서드를 호출한다.

setting.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	   xmlns:context="http://www.springframework.org/schema/context"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:util="http://www.springframework.org/schema/util"
       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.xsd">

    <!-- 이름이 target인 NewlecExam 빈 등록 -->
    <bean id="target" class="spring.aop.entity.NewlecExam" p:kor="1" p:eng="1" p:math="1" p:com="1"/>
    <!-- LogAroundAdvice 빈 등록 : 로그 기록을 담당하는 어드바이스, 핸들러이다.-->
    <bean id="logAroundAdvice" class="spring.aop.advice.LogAroundAdvice" />
    <!-- 이름이 exam인 ProxyFactoryBean 빈 등록 -> proxy 생성 -->
    <bean id="exam" class="org.springframework.aop.framework.ProxyFactoryBean">
    	<!-- proxy는 2가지를 설정해야 한다. -->
    	<!-- 1.실제 NewlecExam객체 즉 타겟에 해당되는 클래스로더 -->
    	<!-- name="target"은 setTarget() 호출, set은 빼고 t는 소문자로 바꾼 형식을 사용한다.-->
    	<property name="target" ref="target" />
    	<!-- 2.핸들러 -->
    	<property name="interceptorNames">
    		<!-- interceptorNames:복수형 답게 리스트를 사용해 여러개 넣어줄 수 있다 -->
    		<list>
    			<!-- value에 참조하고 있는 핸들러의 이름을 넣어준다. -->
    			<value>logAroundAdvice</value>
    		</list>
    	</property>
    </bean>
</beans>

 

Program.java

public class Program {

	public static void main(String[] args) {
		// 애플리케이션 컨텍스트 생성
		ApplicationContext context = 
				new ClassPathXmlApplicationContext("spring/aop/setting.xml");
		Exam exam = (Exam)context.getBean("exam"); 
		// proxy를 이용해 업무 로직 실행 시간 계산하는 부수적 기능까지 껴 놓음 
		System.out.printf("total is %d\n", exam.total());
		System.out.printf("avg is %f\n", exam.avg());
	}

}

 

NewlecExam.java

package spring.aop.entity;

public class NewlecExam implements Exam {
	
	private int kor; 
	private int eng;
	private int math;
	private int com; 

	public NewlecExam() {
		// TODO Auto-generated constructor stub
	}

	public NewlecExam(int kor, int eng, int math, int com) {
		this.kor = kor;
		this.eng = eng;
		this.math = math;
		this.com = com;
	}

	public int getKor() {
		return kor;
	}

	public void setKor(int kor) {
		this.kor = kor;
	}

	public int getEng() {
		return eng;
	}

	public void setEng(int eng) {
		this.eng = eng;
	}

	public int getMath() {
		return math;
	}

	public void setMath(int math) {
		this.math = math;
	}

	public int getCom() {
		return com;
	}

	public void setCom(int com) {
		this.com = com;
	}

	@Override
	public int total() {
		
		int result = kor+eng+math+com; // 실질적 주 업무로직 
		try {
			Thread.sleep(200);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		return result;
	}

	@Override
	public float avg() {
		float result = total()/4.0f; 
		return result; 
	}

		
	@Override
	public String toString() {
		return "NewlecExam [kor=" + kor + ", eng=" + eng + ", math=" + math + ", com=" + com + "]";
	}
}

 

LogAroundAdvice.java

package spring.aop.advice;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

public class LogAroundAdvice implements MethodInterceptor{

	@Override
	public Object invoke(MethodInvocation invocation) throws Throwable {
		long start = System.currentTimeMillis(); 
		// 주 업무 로직 호출 (스프링없이는 Object result = method.invoke(exam, args);)
		Object result= invocation.proceed();
		
		long end = System.currentTimeMillis();
		String message = (end-start) + "ms 시간이 걸렸습니다.";
		System.out.println(message);
		return result;
	}

}

 

출력

209ms 시간이 걸렸습니다.
total is 4
208ms 시간이 걸렸습니다.
avg is 1.000000

 

 

부가적인 기능 없이 주 업무 기능만 출력하고 싶을 경우!

 

setting.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	   xmlns:context="http://www.springframework.org/schema/context"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:util="http://www.springframework.org/schema/util"
       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.xsd">

    <!-- 이름이 target인 NewlecExam 빈 등록 -->
    <bean id="exam" class="spring.aop.entity.NewlecExam" p:kor="1" p:eng="1" p:math="1" p:com="1"/>
</beans>

 

출력

total is 4
avg is 1.000000

 

Proxy객체를 사용유무는 Prgoram.java의 코드를 보고서는 알 수 가 없고 수정할 필요도 없다.

오로지 setting.xml에서 수정한다. 따라서 코드 수정이 용이해진다.

 

 

BeforeAdvice란?

BeforeAdvice 는 핵심 기능이 실행되기 전에 실행되는 코드 블록을 정의한다.

간단히 "앞에서 실행될 로직"이라는 문장이 핵심 기능들과 다른 부수적 기능들이 실행되기전에 실행되도록 적용하였다.

 

BeforeAdvice 적용

setting.xml
<bean id="logBeforeAdvice" class="spring.aop.advice.LogBeforeAdvice" />
<value>logBeforeAdvice</value> 

추가

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	   xmlns:context="http://www.springframework.org/schema/context"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:util="http://www.springframework.org/schema/util"
       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.xsd">

    <!-- 이름이 target인 NewlecExam 빈 등록 -->
    <bean id="target" class="spring.aop.entity.NewlecExam" p:kor="1" p:eng="1" p:math="1" p:com="1"/>
    <!-- LogAroundAdvice 빈 등록 : 로그 기록을 담당하는 어드바이스, 핸들러이다.-->
    <bean id="logAroundAdvice" class="spring.aop.advice.LogAroundAdvice" />
    <bean id="logBeforeAdvice" class="spring.aop.advice.LogBeforeAdvice" />
    <!-- 이름이 exam인 ProxyFactoryBean 빈 등록 -> proxy 생성 -->
    <bean id="exam" class="org.springframework.aop.framework.ProxyFactoryBean">
    	<!-- proxy는 2가지를 설정해야 한다. -->
    	<!-- 1.실제 NewlecExam객체 즉 타겟에 해당되는 클래스로더 -->
    	<!-- name="target"은 setTarget() 호출, set은 빼고 t는 소문자로 바꾼 형식을 사용한다.-->
    	<property name="target" ref="target" />
    	<!-- 2.핸들러 -->
    	<property name="interceptorNames">
    		<!-- interceptorNames:복수형 답게 리스트를 사용해 여러개 넣어줄 수 있다 -->
    		<list>
    			<!-- value에 참조하고 있는 핸들러의 이름을 넣어준다. -->
    			<value>logAroundAdvice</value>
    			<value>logBeforeAdvice</value>
    		</list>
    	</property>
    </bean>
</beans>

 

LogBeforeAdvice.java 추가

package spring.aop.advice;

import java.lang.reflect.Method;

import org.springframework.aop.MethodBeforeAdvice;

public class LogBeforeAdvice implements MethodBeforeAdvice{

	@Override
	public void before(Method method, Object[] args, Object target) throws Throwable {
		System.out.println("앞에서 실행될 로직");
	}

}

 

나머지는 앞과 동일하다. 

 

출력

앞에서 실행될 로직
209ms 시간이 걸렸습니다.
total is 4
앞에서 실행될 로직
211ms 시간이 걸렸습니다.
avg is 1.000000

 

 

After Returning Advice란?

핵심 기능이 성공적으로 실행된 후에 실행되는 코드 블록을 정의한다. 주로 핵심 기능 실행 후에 추가 작업을 수행하거나 결과를 처리하는 데 사용된다.

 

After Returning Advice 적용

setting.xml

<bean id="logAfterRerturningAdvice" class="spring.aop.advice.LogAfterRerturningAdvice" /><value>logAfterRerturningAdvice</value>

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	   xmlns:context="http://www.springframework.org/schema/context"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:util="http://www.springframework.org/schema/util"
       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.xsd">

    <!-- 이름이 target인 NewlecExam 빈 등록 -->
    <bean id="target" class="spring.aop.entity.NewlecExam" p:kor="1" p:eng="1" p:math="1" p:com="1"/>
    <!-- 핸들러이다.-->
    <bean id="logAroundAdvice" class="spring.aop.advice.LogAroundAdvice" />
    <bean id="logBeforeAdvice" class="spring.aop.advice.LogBeforeAdvice" />
    <bean id="logAfterRerturningAdvice" class="spring.aop.advice.LogAfterRerturningAdvice" />
    <!-- 이름이 exam인 ProxyFactoryBean 빈 등록 -> proxy 생성 -->
    <bean id="exam" class="org.springframework.aop.framework.ProxyFactoryBean">
    	<!-- proxy는 2가지를 설정해야 한다. -->
    	<!-- 1.실제 NewlecExam객체 즉 타겟에 해당되는 클래스로더 -->
    	<!-- name="target"은 setTarget() 호출, set은 빼고 t는 소문자로 바꾼 형식을 사용한다.-->
    	<property name="target" ref="target" />
    	<!-- 2.핸들러 -->
    	<property name="interceptorNames">
    		<!-- interceptorNames:복수형 답게 리스트를 사용해 여러개 넣어줄 수 있다 -->
    		<list>
    			<!-- value에 참조하고 있는 핸들러의 이름을 넣어준다. -->
    			<value>logAroundAdvice</value>
    			<value>logBeforeAdvice</value>
    			<value>logAfterRerturningAdvice</value>
    		</list>
    	</property>
    </bean>
</beans>

 

LogAfterReturningAdvice.java

public class LogAfterRerturningAdvice implements AfterReturningAdvice{

	@Override
	public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
		System.out.println("returnValue : " + returnValue + ", method: " + method.getName());
	}

}

 

출력

앞에서 실행될 로직
returnValue : 4, method: total
208ms 시간이 걸렸습니다.
total is 4
앞에서 실행될 로직
returnValue : 1.0, method: avg
214ms 시간이 걸렸습니다.
avg is 1.000000

 

 

AfterThrowing Advice란?

이 어드바이스는 핵심 기능이 예외를 던져서 호출 스택을 탈출할 때 실행되는 코드 블록을 정의한다.

 

AfterThrowing Advice 적용

setting.xml

<bean id="logAfterThrowingAdvice" class="spring.aop.advice.LogAfterThrowingAdvice" /><value>logAfterThrowingAdvice</value>

추가

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	   xmlns:context="http://www.springframework.org/schema/context"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:util="http://www.springframework.org/schema/util"
       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.xsd">

    <!-- 이름이 target인 NewlecExam 빈 등록 -->
    <bean id="target" class="spring.aop.entity.NewlecExam" p:kor="1" p:eng="1" p:math="1" p:com="1"/>
   	<!-- LogAroundAdvice 빈 등록 : 로그 기록을 담당하는 어드바이스, 핸들러이다.-->
   	<bean id="logAroundAdvice" class="spring.aop.advice.LogAroundAdvice" />
   	<bean id="logBeforeAdvice" class="spring.aop.advice.LogBeforeAdvice" />
   	<bean id="logAfterRerturningAdvice" class="spring.aop.advice.LogAfterRerturningAdvice" />
   	<bean id="logAfterThrowingAdvice" class="spring.aop.advice.LogAfterThrowingAdvice" />
    <!-- 이름이 exam인 ProxyFactoryBean 빈 등록 -> proxy 생성 -->
    <bean id="exam" class="org.springframework.aop.framework.ProxyFactoryBean">
    	<!-- proxy는 2가지를 설정해야 한다. -->
    	<!-- 1.실제 NewlecExam객체 즉 타겟에 해당되는 클래스로더 -->
    	<!-- name="target"은 setTarget() 호출, set은 빼고 t는 소문자로 바꾼 형식을 사용한다.-->
    	<property name="target" ref="target" />
    	<!-- 2.핸들러 -->
    	<property name="interceptorNames">
    		<!-- interceptorNames:복수형 답게 리스트를 사용해 여러개 넣어줄 수 있다 -->
    		<list>
    			<!-- value에 참조하고 있는 핸들러의 이름을 넣어준다. -->
    			<value>logAroundAdvice</value>
    			<value>logBeforeAdvice</value>
    			<value>logAfterRerturningAdvice</value>
    		</list>
    	</property>
    </bean>
</beans>

 

LogThrowingAdvice.java

public class LogAfterThrowingAdvice implements ThrowsAdvice {
	public void afterThrowing(IllegalArgumentException e) throws Throwable{
		System.out.println("예외가 발생하였습니다.: " + e.getMessage());
	}
}

 

출력

정상적으로 에러없이 실행되었으므로 LogThrowingAdvice를 적용하기 전과 같다.

앞에서 실행될 로직
returnValue : 4, method: total
207ms 시간이 걸렸습니다.
total is 4
앞에서 실행될 로직
returnValue : 1.0, method: avg
203ms 시간이 걸렸습니다.
avg is 1.000000

 

 

p:kor="101"이라고 설정하고 

NewlecExam.java의 total()을 아래와 같이 예외가 발생할 경우의 로직을 작성하였다.

@Override
	public int total() {
		
		int result = kor+eng+math+com; // 실질적 주 업무로직 
		if(kor>100)
			throw new IllegalArgumentException("유효하지 않은 국어점수");
		try {
			Thread.sleep(200);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		return result;
	}

 

출력

앞에서 실행될 로직
예외가 발생하였습니다.: 유효하지 않은 국어점수
Exception in thread "main" java.lang.IllegalArgumentException: 유효하지 않은 국어점수
	at spring.aop.entity.NewlecExam.total(NewlecExam.java:58)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ..

 

 

 

 

 

참고 자료 출처 : (18~25)

https://www.youtube.com/watch?v=y2JkXjOocZ4&list=PLq8wAnVUcTFUHYMzoV2RoFoY2HDTKru3T&index=18