본문 바로가기
Java . Spring . Web . SQL

[AOP] Advice(어드바이스) 동작 시점

by heidish 2020. 10. 16.
반응형

 

 

 

 

 

Advice  (어드바이스)

 

-  before, after, after-returning, after-throwing, around  5가지의 동작 시점이 존재

 

어드바이스는 횡단 관심에 해당하는 공통 기능의 코드를 의미하며, 독립된 클래스의 메소드로 작성된다.

그리고 어드바이스로 구현된 메소드가 언제 동작할지를 스프링설정파일을 통해 지정할 수 있다.

 

 

 


 

어드바이스는 각 Joinpoint에 삽입되어서 동작할 횡단 관심에 해당하는 공통 기능이며,

동작 시점은 각 AOP 기술마다 다르다.

 

스프링에는 위에 써있는 것과 같이 5개의 동작 시점이 제공된다.

 

 

Before

-   비즈니스 메소드 실행 전 동작

 

After Returning

-   비즈니스 메소드가 성공적으로 리턴되면 동작

     (단, 메소드 내부에 리턴값이 존재하는 경우)

 

After Throwing

-   비즈니스 메소드 실행 중, 예외가 발생하면 동작

     (예를들면 try~catch 블록의 catch 부분에 해당!)

 

After

-   비즈니스 메소드가 실행된 후,  !무조건!  실행

     (예를들면 try~catch 블록에서 finally 부분에 해당!)

 

Around

-   메소드 호출 자체를 가로채서 비즈니스 메소드 실행 전&후 모두에 처리할 로직을 삽입할 수 있음

 

 

 

어드바이스 메소드의 동작 시점은 <aop:aspect> 엘리먼트 하위에 각각

<aop:before> , <aop:after-returning> , <aop:after-throwing> , <aop:after> , <after-around> 엘리먼트를

이용해서 지정해줄 수 있다.

 

 

 

 

 

 

 


 

 

아래는 각 어드바이스 동작 시점을 테스트하기 위한 예제들임

( BoardWeb_DI_AOP 라는 프로젝트의 src/main/java 내부의 com.heidi.biz.board.common 패키지 내부에

모든 어드바이스 클래스들을 작성할 예정임 )

 

 

또한 전체적인 세세한 설명은 가장 첫번째인 Before, Around 어드바이스 부분에서만 할 예정임!

나머지 어드바이스들은 비슷한 원리이므로 설명 생략할꺼임..!

 

 


 

 

1.  Before 어드바이스

 

비즈니스 메소드 실행 전 동작

 

 

 


 

Before 어드바이스를 테스트하기 위해서,

비즈니스 메소드가 실행되기 전에 수행될 기능을 어드바이스 클래스로 구현하자.

 

 

( BeforeAdvice.java )

package com.heidi.biz.board.common;

import org.aspectj.lang.JoinPoint;

public class BeforeAdvice {

	public BeforeAdvice() {
	}
	
	public void beforeLog() {
		System.out.println("[사전 처리] 비즈니스 로직 수행 전 동작");
	}
}

 

이 BeforeAdvice 클래스에 선언한 beforeLog() 메소드가 Before 형태로 동작하도록,

비즈니스 메소드가 실행하기 전에 beforeLog() 메소드가 동작할 수 있도록 하기 위해서

스프링설정파일(applicationContext.xml) 을 수정하자.

 

 


 

( 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.heidi.biz"></context:component-scan>
	
	<bean id="before" class="com.heidi.biz.board.common.BeforeAdvice"></bean>
	
	<aop:config>
		<aop:pointcut expression="execution(* com.heidi.biz..*Impl.*(..))" id="allPointcut"/>
		<aop:aspect ref="before">
			<aop:before method="beforeLog" pointcut-ref="allPointcut" />
		</aop:aspect>
	</aop:config>

</beans>

<context:component-scan ~> 태그에 의해서 @Component 어노테이션이 붙은 클래스들은

자동으로 객체 생성이 된다.

 

 

이 태그가 기억 안나면 빨리 위에 사진 클릭해서 다시 공부하고 오기...........

 

 

그리고 BeforeAdvice 클래스의 경우에도 클래스선언부 위에 @Component 어노테이션을 붙여줘서

<context:component-scan> 태그에 의해 자동으로 객체생성이 되게 만들어도 되지만,

우리는 BeforeAdvice 객체는 지금만 쓰고 사용하지 않을 예정이기 때문에

( 각 어드바이스 동작 시점마다 당장 필요한 어드바이스 객체만 생성해줄꺼임. 테스트니까..! )

객체를 자동생성 되게 한게 아니라 위와 같이 <bean> 태그에 의해 객체 생성을 해준거다.

 

 

즉, BeforeAdvice 타입의 before 라는 이름의 객체를 생성한것!

 

 

그리고 모든 AOP 전체설정을 감싸주는 <aop:config> 태그가 있고,

그 안에 자식태그인 <aop:pointcut> 과 <aop:aspect> 태그가 있다.

( <aop:config> 태그에 대해서도 기억 안나면 아래 사진 눌러서 얼른 한번 더 읽고오기...... )

 

 

<aop:pointcut> 태그를 이용해서 Impl 로 끝나는 이름을 가진 모든 클래스의, 모든 메소드들을

allPointcut 이라는 이름의 포인트컷으로 설정해주었다.

 

이때 <aop:pointcut> 태그는 <aop:aspect>가 아니라 <aop:config> 태그의 하위 엘리먼트로 선언되었기 때문에

모든 aspect 에서 사용 가능하다는건 참고로 알고 넘어가자.

 

 

 

<aop:config> 태그 내의 또다른 태그인 <aop:aspect> 태그는 애스팩트를 설정하는 태그로,

포인트컷과 어드바이스를 결합시키는 역할을 한다.

( 이것도 기억 안나면 다시읽고오기.... )

 

 

즉, pointcut-ref 속성값으로 써준 allPointcut 으로 설정한 메소드가 호출될 때,

before 라는 이름의 Before어드바이스 객체가 가지고 있는

beforeLog 라는 메소드가 실행되고,

이때 beforeLog() 메소드의 동작 시점은 <aop:before> 태그에 선언한 것 처럼 비즈니스 메소드가 실행되기 전에 동작하겠다 ~ 는 의미가 된다.

 

 

 


이제 BoardServiceClient.java 프로그램을 실행시켜 보자.

 

( 마우스 우클릭 > Run As.. > Java Application 선택해서 실행하기 )

 

 

 

( BoardServiceClient.java )

package com.hedidi.biz.board;

import java.util.List;

import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;

import com.heidi.biz.board.BoardService;
import com.heidi.biz.board.BoardVO;

public class BoardServiceClient {

	public static void main(String[] args) {
		// 1. Spring 컨테이너를 구동한다.
		AbstractApplicationContext container = new GenericXmlApplicationContext("applicationContext.xml");
		
		// 2. Spring 컨테이너로부터 BoardServiceImpl 객체를 Lookup 한다.
		BoardService boardService = (BoardService) container.getBean("boardService");
		
		// 3. 글 등록 기능 테스트
		BoardVO vo = new BoardVO();
		vo.setTitle("임시 제목");
		vo.setWriter("heidi");
		vo.setContent("임시내용");
		boardService.insertBoard(vo);
	
		// 4. 글 목록 검색 기능 테스트
		List<BoardVO> boardList = boardService.getBoardList(vo);
		for (BoardVO board : boardList) {
			System.out.println("==> " + board.toString());
		}
		
		// 5. Spring 컨테이너 종료
		container.close();
		
	}
}

실행할 파일의 코드는 위와 같다.

 

 

 

실행 결과는 아래처럼 나온다.

INFO : org.springframework.beans.factory.xml.XmlBeanDefinitionReader - Loading XML bean definitions from class path resource [applicationContext.xml]
INFO : org.springframework.context.support.GenericXmlApplicationContext - Refreshing org.springframework.context.support.GenericXmlApplicationContext@7dc5e7b4: startup date [Fri Oct 16 16:32:58 KST 2020]; root of context hierarchy
INFO : org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor - JSR-330 'javax.inject.Inject' annotation found and supported for autowiring
[사전 처리] 비즈니스 로직 수행 전 동작
==> JDBC로 insertBoard() 기능 처리
[사전 처리] 비즈니스 로직 수행 전 동작
==> JDBC로 getBoardList() 기능 처리
==> BoardVO [seq=1, title=임시 제목, writer=heidi, content=임시내용, regDate=2020-10-16, cnt=0]
INFO : org.springframework.context.support.GenericXmlApplicationContext - Closing org.springframework.context.support.GenericXmlApplicationContext@7dc5e7b4: startup date [Fri Oct 16 16:32:58 KST 2020]; root of context hierarchy

 

근데 보기 힘드니까 지금 봐야되는 부분만 저 콘솔창 결과에서 뽑아보면,

[사전 처리] 비즈니스 로직 수행 전 동작
==> JDBC로 insertBoard() 기능 처리
[사전 처리] 비즈니스 로직 수행 전 동작
==> JDBC로 getBoardList() 기능 처리
==> BoardVO [seq=1, title=임시 제목, writer=heidi, content=임시내용, regDate=2020-10-16, cnt=0]

이부분이다.

 

 

 

실행한 BoardServiceClient.java 의 main() {  } 내부를 보면,

 

가장 먼저 Spring container가 구동된다.

이때 가장 먼저 하는 일이 applicationContext.xml 문서를 읽어내는건데,

스프링 설정 파일(applicationContext.xml)을 읽는 순서는 다음과 같다.

 

<context:component-scan> 에 의해서 @Component 어노테이션이 붙은 객체들이 가장 먼저 자동으로 객체생성되고

이어서 BeforeAdvice 타입의 before 객체도 생성된다.

 

그리고 pointcut 으로 선언된 Impl로 끝나는 클래스들의 모든 메소드들이 수행되기 전,

before 객체가 가지는 beforeLog 메소드가 실행되도록,

포인트컷과 어드바이스를 결합시켜 주었다.

 

다시 메인인 BoardServiceClient.java 로 돌아오면,

2번으로 주석처리한 줄의 바로 아랫줄을 보자.

 

좀 전에 컨테이너가 어플리케이션 운용에 필요한 객체를 생성해줬고, 클라이언트는 컨테이너가 생성한 객체를 검색(Lookup) 해서 사용하게 된다.

 

따라서 "boardService" 라는 이름의 객체를 검색해서 (우변)

BoardService 타입의 boardService 라는 객체명에 저장해준것임! (좌변)

( 정확히 이게 맞는지 잘 모르겠는데 아마 맞는듯 )

 

 

다음으로 3번 주석 부분을 보면,

글 등록을 해주는 insertBoard() 메소드 기능을 테스트하기 위한 코드들인데

가장 먼저 BoardVO 객체를 생성해주고 제목, 글쓴이, 내용을 각각 "임시 제목", "heidi", "임시내용"으로 직접 입력해줬다.

입력한건 set~() 메소드를 통해 각각 BoardVO 에 저장해줬고,

boardService 객체가 갖고있는 insertBoard() 메소드를 이용해서 값을 DB에 insert 해줬다.

 

근데 이 insert 기능의 메소드인 insertBoard() 메소드가 실행되기 전에

당연하게 beforeLog() 메소드가 실행된거에 주의하자!!

이 Before 어드바이스가 우리가 설정한 동작 시점에 잘 실행되는지 확인하는게 이 예제의 목적임..!

 

다음으로 4번 주석 부분은

현재 board_spring 이라는 테이블에 존재하는 데이터들을 가져오는 메소드이다.

boardService 객체가 갖는 getBoardList() 메소드를 이용해서 List 타입으로 된 글들을 꺼내와서 List 타입으로 다시 받아준다. 이 받은 데이터는 boardList 라는 이름으로 저장해줬고,

for 문에 의해서 데이터를 한줄 한줄 꺼내서 콘솔창에 출력하게 해줬다.

 

마찬가지로 getBoardList() 메소드 실행 전, beforeLog() 메소드가 실행된게 포인트임!

 

모든 작업이 끝나면 Spring 컨테이너를 종료해야 하므로 5번 주석처럼 close() 메소드로 컨테이너를 종료해줬다.

 

 

 


 

 

2.  After Returning 어드바이스

 

비즈니스 메소드가 성공적으로 리턴되면 동작

 

 

 


 

( AfterReturningAdvice.java )

package com.heidi.biz.board.common;

import org.aspectj.lang.JoinPoint;

import com.heidi.biz.user.UserVO;

public class AfterReturningAdvice {

	public AfterReturningAdvice() {
	}

	public void afterLog(JoinPoint jp, Object returnObj) {
		System.out.println("[사후 처리] 비즈니스 로직 수행 후 동작");
	}
}

 

 

( 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.heidi.biz"></context:component-scan>
	
	<bean id="afterReturning" class="com.heidi.biz.board.common.AfterReturningAdvice"></bean>
	
	<aop:config>
		<aop:pointcut expression="execution(* com.heidi.biz..*Impl.get*(..))" id="getPointcut"/>
		<aop:aspect ref="afterReturning">
			<aop:after-returning method="afterLog" pointcut-ref="getPointcut" />
		</aop:aspect>
	</aop:config>

</beans>

 

 

실행 결과

==> JDBC로 insertBoard() 기능 처리
==> JDBC로 getBoardList() 기능 처리
[사후 처리] 비즈니스 로직 수행 후 동작
==> BoardVO [seq=2, title=임시 제목, writer=heidi, content=임시내용, regDate=2020-10-16, cnt=0]
==> BoardVO [seq=1, title=임시 제목, writer=heidi, content=임시내용, regDate=2020-10-16, cnt=0]

 

 

get~ 으로 시작하는 모든 메소드에 대해서, 비즈니스 메소드 실행 후에 afterLog() 메소드가 실행되도록 설정했기 때문에

위와 같이 insert~ 메소드가 실행된 후에는 afterLog() 메소드가 실행되지 않지만

get~ 메소드가 실행된 후에는 afterLog() 메소드가 실행되는걸 볼 수 있다.

 

그리고 getBoardList() 메소드로 아까 입력한 값과 지금 입력한 값, 총 두개의 값이 꺼내지는걸 볼 수 있다.

 

 

 

 


 

3.  After Throwing 어드바이스

 

 

포인트컷으로 지정한 비즈니스 메소드 실행 중, 예외가 발생하는 시점에 동작

 

 

 


 

 

( AfterThrowingAdvice.java )

package com.heidi.biz.board.common;

public class AfterThrowingAdvice {

	public AfterThrowingAdvice() {
	}
	
	public void exceptionLog() {
		System.out.println("[예외 처리] 비즈니스 로직 수행 중 예외 발생!");
	}
}

 

 

( 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.heidi.biz"></context:component-scan>
	
	<bean id="afterThrowing" class="com.heidi.biz.board.common.AfterThrowingAdvice"></bean>
	
	<aop:config>
		<aop:pointcut expression="execution(* com.heidi.biz..*Impl.*(..))" id="allPointcut"/>
		<aop:aspect ref="afterThrowing">
			<aop:after method="exceptionLog" pointcut-ref="allPointcut" />
		</aop:aspect>
	</aop:config>

</beans>

 

 

( BoardServiceImpl.java )

package com.heidi.biz.board.impl;

import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.heidi.biz.board.BoardService;
import com.heidi.biz.board.BoardVO;

@Service("boardService")
public class BoardServiceImpl implements BoardService {
	@Autowired
	private BoardDAO boardDAO;

	@Override
	public void insertBoard(BoardVO vo) {
		if (vo.getSeq() == 0) {
			throw new IllegalArgumentException("0번 글은 등록하실 수 없습니다.");
		}
		this.boardDAO.insertBoard(vo);
	}

	@Override
	public void updateBoard(BoardVO vo) {
		this.boardDAO.updateBoard(vo);
	}
	@Override
	public void deleteBoard(BoardVO vo) {
		this.boardDAO.deleteBoard(vo);		
	}
	@Override
	public BoardVO getBoard(BoardVO vo) {
		return boardDAO.getBoard(vo);
	}
	@Override
	public List<BoardVO> getBoardList(BoardVO vo) {
		return boardDAO.getBoardList(vo);
	}
}

기존의 BoardServiceImpl.java 클래스의 내부 메소드들 중, insertBoard() 메소드는

	@Override
	public void insertBoard(BoardVO vo) {
		this.boardDAO.insertBoard(vo);
	}

이런 내부 코드를 갖고 있었는데, 여기에 if 조건물을 추가해줘야 예외 발생이 된다.

 

 

실행 결과

INFO : org.springframework.beans.factory.xml.XmlBeanDefinitionReader - Loading XML bean definitions from class path resource [applicationContext.xml]
INFO : org.springframework.context.support.GenericXmlApplicationContext - Refreshing org.springframework.context.support.GenericXmlApplicationContext@7dc5e7b4: startup date [Sun Oct 18 16:55:31 KST 2020]; root of context hierarchy
INFO : org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor - JSR-330 'javax.inject.Inject' annotation found and supported for autowiring
[예외 처리] 비즈니스 로직 수행 중 예외 발생!
Exception in thread "main" java.lang.IllegalArgumentException: 0번 글은 등록하실 수 없습니다.
	at com.heidi.biz.board.impl.BoardServiceImpl.insertBoard(BoardServiceImpl.java:20)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
	at java.lang.reflect.Method.invoke(Unknown Source)
	at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:302)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:190)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)
	at org.springframework.aop.aspectj.AspectJAfterThrowingAdvice.invoke(AspectJAfterThrowingAdvice.java:58)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
	at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
	at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:208)
	at com.sun.proxy.$Proxy10.insertBoard(Unknown Source)
	at com.hedidi.biz.board.BoardServiceClient.main(BoardServiceClient.java:25)

실행 결과, 포인트컷으로 지정한 BoardServiceImpl 클래스의 insertBoard() 메소드가 실행될 때 예외가 발생하므로AfterThrowingAdvice 클래스가 보유한 exceptionLog() 메서드가 작동해서 "[예외처리] ~~~" 라고 콘솔창에 찍히고

그 아래로 Exception 에러코드가 쭈루룩 나오게 되었다.

 

 

 

왜 vo.getSeq() == 0 이라는 조건이 성립해서 예외발생이 되는지 자세히 들여다보면 다음과 같다.

 

가장 먼저, BoardServiceClient.java 프로그램을 실행하면,

(1) 스프링 컨테이너가 구동된다.

applicationContext.xml 파일을 가장 1순위로 읽게 되고,

applicationContext.xml 내의 코드가 차례로 실행되면서,

<context:component-scan> 코드에 의해서 com.heidi.biz 로 시작하는 모든 클래스에서

@Component 혹은 @Service 어노테이션이 붙은 클래스의 객체 생성을 자동으로 해준다.

 

그리고 Impl 로 끝나는 모든 클래스들이 갖고있는 모든 매개변수 타입을 받아주는 모든 메소드들이 allPointcut 이라는 id 속성을 갖도록 포인트컷으로 만들어지고,

 

allPointcut 으로 설정된 모든 포인트컷이 실행될 때, 예외가 발생하는 시점에 exceptionLog 라는 메소드가 동작할 수 있도록 하기 위해서 포인트컷과 어드바이스를 결합시켜 주었다.

 

그리고 다시 실행 프로그램인 BoardServiceClient.java 의 코드가 계속해서 실행된다.

스프링 컨테이너 메모리 내에 생성된 BoardServiceImpl 타입의 boardSerice 라는 이름의 객체를 가져와서 BoardSerivce 타입으로 형변환시켜주고 다시 한번 boardService 라는 객체명으로 받아서 저장해준다.

( 왜냐면 이 아래의 코드에서 써야하니까...! 메모리에 있는 객체를 선언한것이라고 보면 될듯..! ) 

 

그리고 계속해서 아래 코드가 실행될 차례인데,

BoardVO 객체인 vo 에다가 제목, 작성자, 내용을 각각 위와 같이 직접 지정해서 set 시켜주었다.

그리고 이 vo를 이용해서 boardService 객체가 갖고있는 insertBoard() 라는 메소드를 실행시켜 주는데,

 

insertBoard() 메소드의 경우 매개변수로 BoardVO 타입의 vo 객체가 들어가게 된다.

메소드 내부 코드의 if 문의 조건절에서 vo 객체에 저장된 seq 값이 0 과 같으면 { } 블럭 내부를 실행하라고 했는데,

우리는 제목, 작성자, 내용만 vo에 값을 지정해주었고 seq 의 경우 값을 지정해서 넣어주지 않았기 때문에

현재 int 타입의 기본값인 0이 seq 값인 형태라고 보면 된다.

 

따라서 insertBoard() 메소드에서 if문 내부로 들어가서 IllegalArgumentException 을 강제로 발생시켜서 예외처리 어드바이스가 동작하게 된다.

 

 

 

 


 

 

4.  After 어드바이스

 

비즈니스 메소드가 실행된 후,  !무조건!  실행

 

 

 


 

 

( AfterAdvice.java )

package com.heidi.biz.board.common;

public class AfterAdvice {

	public AfterAdvice() {
	}

	public void finallyLog() {
		System.out.println("[사후 처리] 비즈니스 로직 수행 후 !무조건! 동작");
	}
}

 

 

( 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.heidi.biz"></context:component-scan>
	
	<bean id="afterThrowing" class="com.heidi.biz.board.common.AfterThrowingAdvice"></bean>
	<bean id="after" class="com.heidi.biz.board.common.AfterAdvice"></bean>
	
	<aop:config>
		<aop:pointcut expression="execution(* com.heidi.biz..*Impl.*(..))" id="allPointcut"/>
		<aop:aspect ref="afterThrowing">
			<aop:after method="exceptionLog" pointcut-ref="allPointcut" />
		</aop:aspect>
		
		<aop:aspect ref="after">
			<aop:after method="finallyLog" pointcut-ref="allPointcut" />
		</aop:aspect>
	</aop:config>

</beans>

After 어드바이스는 예외발생과는 무관하게 진짜 말그대로 무조건 실행되어야 하기 때문에,

일부러 After-throwing 어드바이스는 그대로 두고 위와 같이 after 엘리먼트를 추가해서 실행해볼꺼다.

 

 

 

실행 결과

INFO : org.springframework.beans.factory.xml.XmlBeanDefinitionReader - Loading XML bean definitions from class path resource [applicationContext.xml]
INFO : org.springframework.context.support.GenericXmlApplicationContext - Refreshing org.springframework.context.support.GenericXmlApplicationContext@7dc5e7b4: startup date [Sun Oct 18 17:21:54 KST 2020]; root of context hierarchy
INFO : org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor - JSR-330 'javax.inject.Inject' annotation found and supported for autowiring
[사후 처리] 비즈니스 로직 수행 후 !무조건! 동작
[예외 처리] 비즈니스 로직 수행 중 예외 발생!
Exception in thread "main" java.lang.IllegalArgumentException: 0번 글은 등록하실 수 없습니다.
	at com.heidi.biz.board.impl.BoardServiceImpl.insertBoard(BoardServiceImpl.java:20)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
	at java.lang.reflect.Method.invoke(Unknown Source)
	at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:302)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:190)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)
	at org.springframework.aop.aspectj.AspectJAfterAdvice.invoke(AspectJAfterAdvice.java:43)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
	at org.springframework.aop.aspectj.AspectJAfterAdvice.invoke(AspectJAfterAdvice.java:43)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
	at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
	at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:208)
	at com.sun.proxy.$Proxy10.insertBoard(Unknown Source)
	at com.hedidi.biz.board.BoardServiceClient.main(BoardServiceClient.java:25)

 

실행 결과 위와 같이 비즈니스 메소드가 실행된 후 after 어드바이스가 실행되기 때문에

after 어드바이스의 메소드인 finallyLog() 메소드 실행 후,

after-throwing 어드바이스가 실행되어 예외가 발생한걸 볼 수 있다.

 

 

 

 


 

 

5.  Around 어드바이스

 

 

메소드 호출 자체를 가로채서 비즈니스 메소드 실행 전&후 모두에 처리할 로직을 삽입할 수 있음

 

 

 


 

( AroundAdvice.java )

package com.heidi.biz.board.common;

import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.util.StopWatch;

public class AroundAdvice {

	public AroundAdvice() {
	}

	public Object aroundLog(ProceedingJoinPoint pjp) throws Throwable {
		String method = pjp.getSignature().getName();
		
		StopWatch stopWatch = new StopWatch();
		stopWatch.start();
		
		Object obj = pjp.proceed();
		
		stopWatch.stop();
		System.out.println(method + "() 메소드 수행에 걸린 시간 : " + stopWatch.getTotalTimeMillis() + "(ms)초");
		
		return obj;
	}
}

Around 어드바이스는 클라이언트의 메소드 호출을 가로채서,

클라이언트가 호출한 비즈니스 메소드가 실행되기 전에 사전 처리 로직을 수행할 수 있게 되고,

또 비즈니스 메소드가 실행된 후 사후 처리 로직을 수행할 수 있게 된다.

 

이 때, 클라이언트의 요청을 가로챈 어드바이스는 클라이언트가 호출한 비즈니스 메소드를 호출하기 위해서

ProProceedingJoinPoint 객체를 매개변수로 받아야 한다.

ProceedingJoinPoint에 관한 내용은 아직 배우지 않았는데, 여기서는 그냥 ProceedingJoinPoint 객체의 proceed() 메소드를 이용해서 비즈니스 메소드를 호출할 수 있다고만 알아놓자 !

 

여기서 Object obj = pjp.proceed(); 코드 바로 전에 쓰인 System.out.print(); 코드는 Before 어드바이스처럼,

후에 쓰인 System.out.print(); 코드는 After 어드바이스처럼 사용된다고 생각하자.

 

 

 

( 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.heidi.biz"></context:component-scan>
	
	<bean id="around" class="com.heidi.biz.board.common.AroundAdvice"></bean>
	
	<aop:config>
		<aop:pointcut expression="execution(* com.heidi.biz..*Impl.*(..))" id="allPointcut"/>
		<aop:aspect ref="around">
			<aop:around method="aroundLog" pointcut-ref="allPointcut" />
		</aop:aspect>
	</aop:config>

</beans>

 

실행하기 전, 

BoardServiceImpl.java 의 insertBoard() 메소드 내부의 if문은 주석처리하거나 지워야한다!

 

 

 

실행 결과

INFO : org.springframework.beans.factory.xml.XmlBeanDefinitionReader - Loading XML bean definitions from class path resource [applicationContext.xml]
INFO : org.springframework.context.support.GenericXmlApplicationContext - Refreshing org.springframework.context.support.GenericXmlApplicationContext@7dc5e7b4: startup date [Sun Oct 18 17:34:15 KST 2020]; root of context hierarchy
INFO : org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor - JSR-330 'javax.inject.Inject' annotation found and supported for autowiring
[BEFORE] : 비즈니스 메소드 수행 전에 처리할 내용...
==> JDBC로 insertBoard() 기능 처리
[AFTER] : 비즈니스 메소드 수행 후에 처리할 내용...
[BEFORE] : 비즈니스 메소드 수행 전에 처리할 내용...
==> JDBC로 getBoardList() 기능 처리
[AFTER] : 비즈니스 메소드 수행 후에 처리할 내용...
==> BoardVO [seq=4, title=임시 제목, writer=heidi, content=임시내용, regDate=2020-10-18, cnt=0]
==> BoardVO [seq=3, title=임시 제목, writer=heidi, content=임시내용, regDate=2020-10-18, cnt=0]
==> BoardVO [seq=2, title=임시 제목, writer=heidi, content=임시내용, regDate=2020-10-17, cnt=0]
==> BoardVO [seq=1, title=임시 제목, writer=heidi, content=임시내용, regDate=2020-10-17, cnt=0]
INFO : org.springframework.context.support.GenericXmlApplicationContext - Closing org.springframework.context.support.GenericXmlApplicationContext@7dc5e7b4: startup date [Sun Oct 18 17:34:15 KST 2020]; root of context hierarchy

 

 

 

반응형

댓글