Spring, Spring Framework AOP
AOP
- 로깅, 예외, 트랜잭션 처리 같은 부가적인 코드(횡단관심)로 인해서 비즈니스 메소드가 복잡해질때가 있다
- 핵심관심 : 핵심 비즈니스 로직
- AOP를 이용하여 관심 분리를 하므로써 클래스의 응집도를 높일 수 있다
예제
- LogAdvice.java
package com.springbook.biz.common;
public class LogAdvice {
public void printLog() {
System.out.println("[공통 로그] 비즈니스 로직 수행 전 동작");
}
}
- 스프링컨테이너 설정파일
- 현재 진행중인 메소드가 expresion의 설정범위에 있으면 method를 실행시킨다
<bean id="log" class="com.springbook.biz.common.LogAdvice"></bean>
<aop:config>
<aop:pointcut id="allPointcut" expression="execution(* com.springbook.biz..*Impl.*(..))"/>
<aop:aspect ref="log">
<aop:before pointcut-ref="allPointcut" method="printLog"/>
</aop:aspect>
</aop:config>
- BoardServiceClient.java
- 해당클래스를 실행시키면 BoardServiceImpl클래스의 insertBoard()메서드가 실생된다.
- aop:pointcut 태그의 expression에 의해서 aop:aspect에 설정된 printLog()가 실행된다
public class BoardServiceClient {
public static void main(String[] args) {
//... 생략
// 3. 글 등록 기능 테스트
BoardVO vo = new BoardVO();
vo.setTitle("제목 테스트얌");
vo.setWriter("이혜미");
vo.setContent("임시 내용이라능 spring boot 뿌셔버리자능~~");
boardService.insertBoard(vo);
//... 생략
}
}
- 결과
의미
- 핵심 관심 메소드(insertBoard())와 횡단 관심 메소드(printLog())사이에서 소스상의 결합은 발생하지 않았다
AOP용어 정리
조인트 포인트(JoinPoint)
- client가 호출하는 모든 비즈니스 메소드
- ex) BoardServiceImpl, UserServiceImpl 클래스의 모든 메소드
포인트컷(Pointcut)
- 필터링된 조인트 포인트
- 비즈니스 메소드들 중에서 우리가 원하는 특정메소드
- 이 메소드에서 횡단관심이 발생한다
<aop:pointcut id="allPointcut" expression="execution(* com.springbook.biz..*Impl.*(..))"/>
- ‘*’ : 리턴타입
- com.springbook.biz. . : 패키지정보
- ‘*Impl’ : 클래스명
- *(..) : 메소드명 및 매개 변수
어드바이스(Advice)
- 횡당 관심에 해당하는 공통 기능의 코드
- 독립된 클래스 메소드로 작성한다
- 동작 시점 : before, after, after-returning, after-throwing, around
<aop:before pointcut-ref="allPointcut" method="printLog"/>
위빙(Weaving)
- 핵심 관심 메소드(포인트컷)가 호출될 때, 어드바이스에 해당하는 횡단관심 메소드가 삽입되는 과정을 의미한다.
- 위빙을 통해서 비즈니스 메소드를 수정하지 않고도 횡단 관심의 기능을 추가,변경할 수 있다.
애스팩트(Aspect) 또는 어드바이저(Advisor)
- 포인트컷 + 어드바이스 를 이어준다
- 어떤 포인트컷 메소드에 대해서 어떤 어드바이스 메소드를 실행할지 결정한다
<bean id="log" class="com.springbook.biz.common.LogAdvice"></bean>
<aop:config>
<aop:pointcut id="allPointcut" expression="execution(* com.springbook.biz..*Impl.*(..))"/>
<aop:aspect ref="log">
<aop:before pointcut-ref="allPointcut" method="printLog"/>
</aop:aspect>
</aop:config>
- 어디서 ? execution에 해당하는 메소드를 실행하는 곳에서
- 무엇을 ? printLog()메소드를
- 언제 ? before, 포인트컷 메소드가 실행되기 전에 실행한다
엘리먼트
- aspect와 같은 기능을한다
- 트랜잭션 설정 같은 특수한 경우는 advisor를 사용
- advice 객체의 아이디를 모르거나 메소드 이름을 확인할 수 없는 경우 사용한다
어드바이스 동작 시점
|동작시점| 설명
|–|–|
|Before| 비즈니스 메소드 실행 전 동작
|After| - After Returning : 비즈니스 메소드가 성공적으로 리턴되면 동작
- After Throwing : 비즈니스 메소드가 실행 중 예외가 발생하면 동작 (try~catch 블록에서 catch블록에 해당)
-
After : 비즈니스 메소드가 실행된 후, 무조건 실행(try~catch~finally에서 finally에 블록에 해당) |Around| 비즈니스 메소드 실행 전후에 처리할 로직을 삽입할 수 있음
- 예제
<bean id="afterThrowing" class="com.springbook.biz.common.AfterThrowingAdvice"></bean>
<bean id="after" class="com.springbook.biz.common.AfterAdvice"></bean>
<aop:config>
<aop:pointcut id="allPointcut" expression="execution(* com.springbook.biz..*Impl.*(..))"/>
<aop:aspect ref="afterThrowing">
<aop:after-throwing pointcut-ref="allPointcut" method="exceptionLog"/>
</aop:aspect>
<aop:aspect ref="after">
<aop:after pointcut-ref="allPointcut" method="finallyLog"/>
</aop:aspect>
</aop:config>
- Around 예제
- ProceedingJoinPoint객체의 proceed()를 통해 비즈니스 메소드를 호출할 수 있다.
import org.aspectj.lang.ProceedingJoinPoint;
public class AroundAdvice {
public Object aroundLog(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("[Before] : 비즈니스 메소드 수행 전에 처리내용...");
Object returnObj = pjp.proceed();
System.out.println("[After] : 비즈니스 메소드 수행 후에 처리내용...");
return returnObj;
}
}
애노테이션으로 AOP 설정
- @Service : 횡단관심의 기능을 가진 클래스의 객체를 생성시킨다(어드바이스)
- @Aspect : 어드바이스 + 포인트컷
- @Pointcut : 횡단관심 발생 메소드 위치
- @Before, @After, @AfterReturing, @AfterThrowing, @Around : 횡단관심 발생시점
예제
- xml 설정
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
Before
@Service // 해당 클래스의 객체가 생성되어야 사용할 수 있으므로 @Service를 이용해 생성해준다.
@Aspect // LogAdvice 어드바이스(공통횡단관심의 기능) + allPointcut 포인트컷(어디서 발생시킬지) 둘을 연결시켜준다
public class LogAdvice {
@Pointcut("execution(* com.springbook.biz..*Impl.*(..))")
public void allPointcut() {}
@Before("allPointcut()")
public void printLog() {
System.out.println("[공통 로그] 비즈니스 로직 수행 전 동작");
}
}
After
- @AfterReturing (메소드 성공적 수행 후)
- returning을 이용하여 메소드의 결과값을 받아 올 수 있다
- @AfterThrowing
- throwing을 이용하여 예외값 받아올 수 있다
- @AfterThrowing(pointcut=”afterAllPointCut()”, throwing=”exceptObj”)
- 예제
@Service
@Aspect
public class AfterThrowingAdvice {
@Pointcut("execution(* com.springbook.biz..*Impl.*(..))")
public void afterAllPointCut() {}
// 포인트컷 설정 및 (예외)결과값 받아오기
@AfterThrowing(pointcut="afterAllPointCut()", 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("문제가 발생했습니다");
}
}
}
Around
@Service
@Aspect
public class AroundAdvice {
// ProceedingJoinPoint객체의 proceed()를 통해 비즈니스 메소드를 호출할 수 있다.
@Pointcut("execution(* com.springbook.biz..*Impl.*(..))")
public void aroundAllPointCut() {}
@Around("aroundAllPointCut()")
public Object aroundLog(ProceedingJoinPoint pjp) throws Throwable {
String method = pjp.getSignature().getName();
StopWatch stopWatch = new StopWatch();
stopWatch.start();
System.out.println("[Before] : 비즈니스 메소드 수행 전에 처리내용...");
Object returnObj = pjp.proceed();
stopWatch.stop();
System.out.println(method + "() 메소드 수행 시간 : "+ stopWatch.getTotalTimeMillis()+"(ms)초");
return returnObj;
}
}
Reference
- 스프링 퀵 스타트-채규태 지음