스프링 IoC와 같이 스프링 AOP도 어노테이션 설정을 지원한다.
AOP를 어노테이션으로 설정하려면 가장 먼저 스프링 설정 파일에
<aop:aspectj-autoproxy> 엘리먼트를 선언해야 한다.
( 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"
xmlns:tx="http://www.springframework.org/schema/tx"
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
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.2.xsd">
<context:component-scan base-package="com.heidi.biz"></context:component-scan>
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
<aop:aspectj-autoproxy> 엘리먼트를 위와 같이 선언해주면,
스프링 컨테이너는 AOP 관련 어노테이션들을 자동으로 인식하고 용도에 맞게 처리해준다.
AOP 관련 어노테이션들은 어드바이스 클래스 내부에 설정해주면 된다.
이 때, 어드바이스 클래스에 선언된 어노테이션들을 스프링 컨테이너가 처리하게 해주려면
어드바이스 객체가 생성된 상태여야 한다 !
따라서 어드바이스 클래스는 반드시 스프링설정파일에서 <bean> 태그로 등록하거나,
@Service 어노테이션을 사용해서 컴포넌트가 검색될 수 있게 해줘야한다.
( 나는 @Service 어노테이션으로 객체 생성을 해줄꺼임..! )
1. Pointcut (포인트컷) 설정
기존에는 포인트컷 설정을 xml 파일에서 <aop:pointcut> 태그를 사용했는데,
xml 설정 파일에서 말고 어노테이션 설정으로 포인트컷을 설정해줄 수 있다!
어노테이션 설정으로 포인트컷을 설정할 땐, @Pointcut 어노테이션을 사용한다.
하나의 어드바이스 클래스 내에 여러개의 Pointcut을 선언할 수 있는데,
이런 경우 여러개의 포인트컷을 식별하기 위한 식별자가 필요하며 이 때 참조 메소드를 사용한다.
참조메소드란 메소드 코드 내부가 비어있는, 구현 로직이 없는 메소드인데
구현 로직이 없기 때문에 어떠한 기능을 처리하는걸 목적으로 하는게 아니라 단순히 포인트컷 식별용으로만 사용된다.
( LogAdvice.java )
package com.heidi.biz.board.common;
import org.aspectj.lang.annotation.Pointcut;
public class LogAdvice {
public LogAdvice() {
}
@Pointcut("execution(* com.heidi.biz..*Impl.*(..))")
public void allPointCut() {}
@Pointcut("execution(* com.heidi.biz..*Impl.get*(..))")
public void getPointCut() {}
public void printLog() {
System.out.println("[공통 로그] 비즈니스 로직 수행 전 동작");
}
}
위의 LogAdvice 라는 하나의 어드바이스 클래스에 있는 allPointcut(), getPointcut() 메소드를
각각 @Pointcut 어노테이션을 이용해서 포인트컷 설정을 해준거다.
현재 여러개의 포인트컷이 있기 때문에 구별하기 위해서 식별자를 붙여주었고,
앞서 말한 것 처럼 식별자로는 참조메소드를 사용했다.
( 내부가 텅 빈 메소드들.. )
2. Advice (어드바이스) 설정
동작 시점을 설정해주는 어드바이스 열시 어노테이션으로 설정이 가능하고,
어드바이스 메소드가 결합될 포인트컷을 반드시 참조해야 한다 !
포인트컷을 참조할땐 어드바이스 어노테이션 뒤에 ( ) 내에 포인트컷 참조 메소드를 지정하면 된다.
어드바이스 동작 시점과 관련된 어노테이션은 아래와 같다.
@Before
@AfterReturning
@AfterThrowing
@After
@Around
( LogAdvice.java )
package com.heidi.biz.board.common;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
public class LogAdvice {
public LogAdvice() {
}
@Pointcut("execution(* com.heidi.biz..*Impl.*(..))")
public void allPointCut() {}
@Pointcut("execution(* com.heidi.biz..*Impl.get*(..))")
public void getPointCut() {}
@Before("allPointcut()")
public void printLog() {
System.out.println("[공통 로그] 비즈니스 로직 수행 전 동작");
}
}
3. Aspect (애스팩트) 설정
@Aspect 어노테이션을 이용해서 설정하며,
@Aspect 가 설정된 객체에는 반드시 포인트컷과 어드바이스를 결합해주는 설정이 필요하다 !
package com.heidi.biz.board.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 // Aspect = Pointcut + Advice
public class LogAdvice {
public LogAdvice() {
}
@Pointcut("execution(* com.heidi.biz..*Impl.*(..))")
public void allPointCut() {}
@Pointcut("execution(* com.heidi.biz..*Impl.get*(..))")
public void getPointCut() {}
@Before("allPointcut()")
public void printLog() {
System.out.println("[공통 로그] 비즈니스 로직 수행 전 동작");
}
}
지금부터는 이전에 XML 기반으로 설정했던 각 어드바이스들을 어노테이션으로 변경하자...
1. Before 어드바이스
( BeforeAdvice.java )
package com.heidi.biz.board.common;
import org.aspectj.lang.JoinPoint;
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 BeforeAdvice {
public BeforeAdvice() {
}
@Pointcut("execution(* com.heidi.biz..*Impl.*(..))")
public void allPointcut(){}
@Before("allPointcut()")
public void beforeLog(JoinPoint jp){
String method=jp.getSignature().getName();
Object[] args=jp.getArgs();
System.out.println("[공통 로그] "+method+"() 메소드 ARGS 정보 : "+args[0].toString());
}
}
( 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:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:tx="http://www.springframework.org/schema/tx"
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
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.2.xsd">
<context:component-scan base-package="com.heidi.biz"></context:component-scan>
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
( UserServiceClient.java )
package com.heidi.biz.user;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;
public class UserServiceClient {
public static void main(String[] args) {
// 1. Spring 컨테이너를 구동한다.
AbstractApplicationContext container = new GenericXmlApplicationContext("applicationContext.xml");
// 2. Spring 컨테이너로부터 userService 객체를 Lookup 한다.
UserService userService = (UserService) container.getBean("userService");
// 3. 로그인 기능 테스트
UserVO vo = new UserVO();
vo.setId("Admin");
vo.setPassword("admin");
UserVO user = userService.getUser(vo);
if (user != null) {
System.out.println(user.getName() + "님 환영합니다.");
} else {
System.out.println("로그인 실패");
}
// 4. 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 [Mon Oct 19 17:19:56 KST 2020]; root of context hierarchy
INFO : org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor - JSR-330 'javax.inject.Inject' annotation found and supported for autowiring
[공통 로그] getUser() 메소드 ARGS 정보 : UserVO [id=Admin, password=admin, name=null, role=null]
==> JDBC로 getUser() 기능 처리
administrator님 환영합니다.
INFO : org.springframework.context.support.GenericXmlApplicationContext - Closing org.springframework.context.support.GenericXmlApplicationContext@7dc5e7b4: startup date [Mon Oct 19 17:19:56 KST 2020]; root of context hierarchy
2. After Returning 어드바이스
( AfterReturningAdvice.java )
package com.heidi.biz.board.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.heidi.biz.user.UserVO;
@Service
@Aspect
public class AfterReturningAdvice {
public AfterReturningAdvice() {
}
@Pointcut("execution(* com.heidi.biz..*Impl.get*(..))")
public void getPointcut(){}
@AfterReturning(pointcut="getPointcut()", returning="returnObj")
public void afterLog(JoinPoint jp, Object returnObj){
String method=jp.getSignature().getName();
if(returnObj instanceof UserVO){
UserVO user=(UserVO) returnObj;
if(user.getRole().equals("Admin")){
System.out.println(user.getName()+" 로그인(Admin)");
}
}
System.out.println("[사후 처리] " + method +"() 메소드 리턴값 : "+returnObj.toString());
}
}
( applicationContext.xml )
동일
( UserServiceClient.java )
동일
실행 결과
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 [Mon Oct 19 17:27:05 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로 getUser() 기능 처리
administrator 로그인(Admin)
[사후 처리] getUser() 메소드 리턴값 : UserVO [id=Admin, password=admin, name=administrator, role=Admin]
administrator님 환영합니다.
INFO : org.springframework.context.support.GenericXmlApplicationContext - Closing org.springframework.context.support.GenericXmlApplicationContext@7dc5e7b4: startup date [Mon Oct 19 17:27:05 KST 2020]; root of context hierarchy
3. After Throwing 어드바이스
( AfterThrowingAdvice.java )
package com.heidi.biz.board.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 AfterThrowingAdvice {
public AfterThrowingAdvice() {
}
@Pointcut("execution(* com.heidi.biz..*Impl.*(..))")
public void allPointCut(){}
@AfterThrowing(pointcut="allPointcut()", throwing="exceptObj")
public void exceptionLog(JoinPoint jp, Exception exceptObj){
String method=jp.getSignature().getName();
System.out.println("[사후 처리] " + method +"() 메소드 수행 중 예외 발생! ");
if(exceptObj instanceof IllegalArgumentException){
System.out.println("부적합한 값이 입력되었습니다.");
}else if(exceptObj instanceof NumberFormatException){
System.out.println("숫자 형식의 값이 아닙니다.");
}else if (exceptObj instanceof Exception) {
System.out.println("문제가 발생했습니다.");
}
}
}
( BoardServiceImpl.java )
insertBoar() 메소드 내부 if문 살려놓기
@Override
public void insertBoard(BoardVO vo) {
if (vo.getSeq() == 0) {
throw new IllegalArgumentException("0번 글은 등록할 수 없습니다.");
}
this.boardDAO.insertBoard(vo);
}
( 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 컨테이너로부터 boardService 객체를 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 [Mon Oct 19 17:40:57 KST 2020]; root of context hierarchy
INFO : org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor - JSR-330 'javax.inject.Inject' annotation found and supported for autowiring
[사후 처리] insertBoard() 메소드 수행 중 예외 발생!
부적합한 값이 입력되었습니다.
Exception in thread "main" java.lang.IllegalArgumentException: 0번 글은 등록할 수 없습니다.
4. After 어드바이스
( AfterAdvice.java )
package com.heidi.biz.board.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 AfterAdvice {
public AfterAdvice() {
}
@Pointcut("execution(* com.heidi.biz..*Impl.*(..))")
public void allPointcut(){}
@After("allPointcut()")
public void finallyLog(){
System.out.println("[사후 처리] 비즈니스 로직 수행 후 무조건 동작");
}
}
( BoardServiceClient.java )
실행 결과
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 [Mon Oct 19 17:44: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
[사후 처리] insertBoard() 메소드 수행 중 예외 발생!
부적합한 값이 입력되었습니다.
[사후 처리] 비즈니스 로직 수행 후 무조건 동작
Exception in thread "main" java.lang.IllegalArgumentException: 0번 글은 등록할 수 없습니다.
5. Around 어드바이스
( AroundAdvice.java )
package com.heidi.biz.board.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 {
public AroundAdvice() {
}
@Pointcut("execution(* com.heidi.biz..*Impl.*(..))")
public void allPointcut(){}
@Around("allPointcut()")
public Object aroundLog(ProceedingJoinPoint pjp) throws Throwable{
String method=pjp.getSignature().getName();
StopWatch stopWatch = new StopWatch();
stopWatch.start();
System.out.println("[BEFORE] : ...");
Object obj = pjp.proceed();
System.out.println("[AFTER] : ...");
stopWatch.stop();
System.out.println(method + "() 메소드 수행에 걸린 시간 : "+ stopWatch.getTotalTimeMillis()+"(ms)초");
return obj;
}
}
( BoardServiceImpl.java )
insertBoard() 내부 if문 주석처리하기
( UserServiceClient.java )
동일
실행 결과
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 [Mon Oct 19 17:49:27 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로 getUser() 기능 처리
[AFTER] : ...
getUser() 메소드 수행에 걸린 시간 : 738(ms)초
[사후 처리] 비즈니스 로직 수행 후 무조건 동작
administrator님 환영합니다.
INFO : org.springframework.context.support.GenericXmlApplicationContext - Closing org.springframework.context.support.GenericXmlApplicationContext@7dc5e7b4: startup date [Mon Oct 19 17:49:27 KST 2020]; root of context hierarchy
" 외부 Pointcut 참조 "
이 전까지 XML 설정을 통해 포인트컷을 설정해줄때는 여러개의 포인트컷을 등록할 수 있었는데,
어노테이션 기반으로 포인트컷을 성정하게 되면서는 각 어드바이스 클래스마다 포인트컷 설정을 해줘야했고,
비슷하거나 동일한 이름의 포인트컷이 반복설정되는 문제가 있었다.
스프링에서는 이런 문제점을 피하기 위해서,
포인트컷을 모두 모아서 외부에 독립된 클래스에 따로 설정해준다 !
( PointCutCommon.java )
package com.heidi.biz.board.common;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Service;
@Service
@Aspect
public class PointCutCommon {
public PointCutCommon() {
}
@Pointcut("execution(* com.heidi.biz..*Impl.*(..))")
public void allPointcut(){}
@Pointcut("execution(* com.heidi.biz..*Impl.get*(..))")
public void getPointcut(){}
}
이렇게 외부 클래스에 포인트컷을 정의해놓은 뒤,
해당 포인트컷들을 사용하려면 어드바이스 클래스 내부 코드를 수정해야한다.
( BeforeAdvice.java )
package com.heidi.biz.board.common;
import org.aspectj.lang.JoinPoint;
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 BeforeAdvice {
public BeforeAdvice() {
}
@Pointcut("execution(* com.heidi.biz..*Impl.*(..))")
public void allPointcut(){}
@Before("allPointcut()")
public void beforeLog(JoinPoint jp){
String method=jp.getSignature().getName();
Object[] args=jp.getArgs();
System.out.println("[공통 로그] " + method+"() 메소드 ARGS 정보 : " + args[0].toString());
}
}
위와 같이 기존의 코드에서 @Pointcut, @Before 어노테이션 설정을 해준 부분에서,
package com.heidi.biz.board.common;
import org.aspectj.lang.JoinPoint;
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 BeforeAdvice {
public BeforeAdvice() {
}
@Before("PointCutCommon.allPointcut()")
public void beforeLog(JoinPoint jp){
String method=jp.getSignature().getName();
Object[] args=jp.getArgs();
System.out.println("[공통 로그] " + method+"() 메소드 ARGS 정보 : " + args[0].toString());
}
}
이렇게 바꿔줘야한다 !
또한 바인드변수가 존재하는 경우에는 아래와 같다.
( AfterReturningAdvice.java )
package com.heidi.biz.board.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.heidi.biz.user.UserVO;
@Service
@Aspect
public class AfterReturningAdvice {
public AfterReturningAdvice() {
}
@AfterReturning(pointcut="PointCutCommon.getPointcut()", returning="returnObj")
public void afterLog(JoinPoint jp, Object returnObj){
String method=jp.getSignature().getName();
if(returnObj instanceof UserVO){
UserVO user=(UserVO) returnObj;
if(user.getRole().equals("Admin")){
System.out.println(user.getName()+" 로그인(Admin)");
}
}
System.out.println("[사후 처리] " + method +"() 메소드 리턴값 : "+returnObj.toString());
}
}
'Java . Spring . Web . SQL' 카테고리의 다른 글
스프링 JDBC (0) | 2020.10.20 |
---|---|
어노테이션 기반 MVC 개발 (0) | 2020.10.20 |
[AOP] JoinPoint 와 바인드 변수 (0) | 2020.10.18 |
[AOP] Advice(어드바이스) 동작 시점 (0) | 2020.10.16 |
div 태그 float 속성 + 가운데정렬 (0) | 2020.10.15 |
댓글