http://closer27.github.io/backend/2017/08/03/spring-aop/
Spring AOP · 어느 개발자의 한적한 공간
Spring AOP 2017.08.03 Day 2 - 스프링 AOP(Aspect Oriented Programming) 개요 낮은 결합도 높은 응집도는 기본, DI는 낮은 결합도를 위한 것이라면 AOP는 높은 응집도를 위한 것 엔터프라이즈 애플리케이션들은 보통 핵심 비지니스 로직은 몇 줄 안되고 주로 로깅이나 예외, 트랜잭션 처리 같은 부가 코드가 대부분이다. -> 비지니스 메소드 복잡도는 증가 -> 비지니스 메소드들마다 매번 반복해야한다는 것이 중요 해
closer27.github.io
Day 2 - 스프링 AOP(Aspect Oriented Programming)
개요
낮은 결합도 높은 응집도는 기본, DI는 낮은 결합도를 위한 것이라면 AOP는 높은 응집도를 위한 것
엔터프라이즈 애플리케이션들은 보통 핵심 비지니스 로직은 몇 줄 안되고 주로 로깅이나 예외, 트랜잭션 처리 같은 부가 코드가 대부분이다.-> 비지니스 메소드 복잡도는 증가-> 비지니스 메소드들마다 매번 반복해야한다는 것이 중요
해결 방법
- 단순 복사 붙여넣기 => 코드 중복이 많아져 코드 분석과 유지보수를 어렵게 만듬.
- AOP를 통해 부가적인 공통 코드들을 효율적으로 관리
AOP에서 중요한 것
관심 분리(Separation of Concerns)
- 로깅, 예외, 트랜잭션 처리 같은 코드들은 횡단 관심(Crosscutting Concerns)
- 핵심 비지니스 로직은 핵심 관심(Core Concerns)
LogAdvice 클래스를 변경하거나 printLog 메서드의 시그니처가 변경되는 상황에서는 유연하게 대처 불가능AOP는 핵심 관심과 횡단 관심을 완벽하게 분리할 수 없는 OOP의 한계를 극복하도록 도와준다.소스상의 결합은 발생하지 않는다. => AOP를 사용하는 목적
용어 및 기본 설정
조인포인트(JoinPoint)
: 클라이언트가 호출하는 모든 비즈니스 메소드, 모든 메소드를 조인포인트로 생각하면 된다. -> 이 중에서 포인트컷이 결정 됨.
포인트컷(Pointcut)
: 포인트컷은 필터링된 조인포인트를 의미한다.
포인트컷으로 메소드 필터링
포인트컷을 위해 클래스와 패키지는 물론이고 메소드 시그니처까지 정확하게 지정할 수 있다.
app.xml
<bean id="log" class="io.icednut.spring.exercise.common.Log4jAdvice"/> <aop:config> <aop:pointcut id="allPointcut" expression="execution(* io.icednut.spring.exercise..*Impl.*(..))"/> <aop:aspect ref="log"> <aop:before pointcut-ref="allPointcut" method="printLog"/> </aop:aspect> </aop:config>
allPointcut이라는 id의 포인트컷은 Impl로 끝나는 모든 클래스의 모든 메서드를 포인트컷으로 설정했다.
포인트컷은 <aop:pointcut> 엘리먼트로 선언 id 속성으로 식별 expression 속성으로 필터링되는 메소드를 지정할 수 있다.
어드바이스(Advice)
: 어드바이스는 횡단 관심에 해당하는 공통 기능의 코드를 의미하며 독립된 클래스의 메서드로 작성된다. 언제 동작할지는 스프링 설정에서 결정.
- before
- after
- after-returning
- after-throwing
- around
위빙(Weaving)
: 횡단 관심 메서드가 삽입되는 과정을 의미한다. 이 위빙을 통해 비지니스 메소드를 수정하지 않고 횡단 관심에 해당하는 기능을 추가하거나 변경 가능.
애스팩트(Aspect) 또는 어드바이저(Advisor)
: 애스펙트는 포인트컷과 어드바이스의 결합 => 어떤 포인트컷 메서드에 어떤 어드바이스 메소드를 실행할지 결정한다.
AOP 엘리먼트
<aop:config> 엘리먼트
스프링 설정 파일 내 여러번 사용 가능,
<beans xmlns="http://www.springframework.org/schema/beans" ...> <aop:config> <aop:pointcut …/> </aop:aspect …></aop:aspect> </aop:config> </beans>
<aop:pointcut> 엘리먼트
포인트컷을 지정하기 위해 사용 <aop:config>이나 <aop:aspect>의 자식 엘리먼트로 사용 <aop:aspect> 하위에 설정된 포인트컷은 해당 <aop:aspect>에서만 사용할 수 있다. 여러개 정의 가능
<aop:config> <aop:pointcut id="allPointcut" expression="execution(* io.icednut.spring.exercise..*Impl.*(..))"/> <aop:aspect ref="log"> <aop:before pointcut-ref="allPointcut" method="printLog"/> </aop:aspect> </aop:config>
“allPointcut” 이라는 포인트컷은 io.icednut.spring.exercise 패키지로 시작하는 클래스 중 Impl로 끝나는 클래스의 모든 메소드를 포인트컷으로 설정, 애스펙트 설정에서 app:before 엘리먼트의 pointcut-ref 속성으로 포인트컷을 참조
<aop:aspect> 엘리먼트
해당 관심에 해당하는 포인트컷 메소드와 횡단 관심에 해당하는 어드바이스 메소드를 결합하기 위해 사용. 어떻게 설정하냐에 따라 위빙 결과가 달라지므로 AOP에서 가장 중요한 설정
<aop:advisor> 엘리먼트
aspect와 같은 기능, 트랜잭션 설정 같은 몇몇 특수한 경우는 어드바이저를 사용해야 함. AOP에서 애스펙트로 설정하기 위해서는 어드바이스의 아이디와 메소드 이름을 알아야하지만 이를 모를 경우 사용할 수 없다.
<!-- Transaction 설정 --> <bean id="txManager" class="org.springframework.orm.jap.JpaTransactionManager"> <property name="entityManagerFactory" ref="entityManagerFactory"></property> </bean> <tx:advice id="txAdvice" transaction-manager="txManager"> <tx:attributes> <tx:method name="get*" read-only="true"/> <tx:method name="*"/> </tx:attributes> </tx:advice> <aop:config> <aop:pointcut id="allPointcut" expression="execution(* io.icednut.spring.exercise..*Impl.*(..))"/> <aop:advisor pointcut-ref="allPointcut" advice-ref="txAdvice"/> ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ </aop:config>
스프링 컨테이너는 <tx:advice> 엘리먼트를 해석하여 트랜잭션 관리 기능의 어드바이스 객체를 생성한다. txAdvice 설정 방법에 따라 동작은 달라짐. => 문제는 어드바이스의 아이디는 확인되지만 메소드 이름은 확인할 방법이 없다. 이럴 때 <aop:advisor>를 써야함.
포인트컷 표현식
execution(*io.icednut.spring.exercise..*Impl.get*(..))
execution(*io.icdenut.spring.exercise..*Impl.get*(..))
리턴타입 | 패키지경로 | 클래스명 | 메소드명 매개변수 |
리턴 타입
타입설명
* | 모든 리턴타입허용 |
void | 리턴타입 void인 메소드 선택 |
!void | void가 아닌 메소드 선택 |
패키지 경로
패키지경로설명
io.icednut.spring.exercise | 정확하게 패키지 선택 |
io.icednut.spring.exercise.. | 패키지로 시작하는 모든 패키지 선택 |
io.icednut.spring..impl | io.icednut.spring으로 시작하면서 마지막 패키지 이름이 Impl로 끝나는 패키지 선택 |
클래스명
클래스명설명
BoardServiceImpl | 정확한 클래스 선택 |
*Impl | 이름이 Impl로 끝나는 클래스 선택 |
BoardService+ | 해당 클래스로 파생된 모든 자식 클래스 선택, 인터페이스 구현 된 모든 클래스 선택 |
메소드명, 매개변수
메소드명, 매개변수설명
* | 모든 메소드 선택 |
get*(..) | 이름이 get으로 시작하는 모든 메소드 선택 |
어드바이스 동작 시점
<aop:aspect> 아래 삽입
- Before : 비지니스 메소드 실행 전 동작 ; <aop:before>
- After
- After Returning : 비지니스 메소드가 성공적으로 리턴되면 동작 ; <aop:after-returning>
- After Throwing : 비지니스 메소드 실행 중 예외가 발생하면 동작(try~catch 블록에서 catch 블록에 해당) ; <aop:after-throwing>
- After : 실행 된 후 무조건 실행(try~catch~finally 블록에서 finally 블록에 해당) ; <aop:after>
- Around : 비지니스 메소드 실행 전후에 처리할 로직을 삽입할 수 있음 <aop::after-around>
Before 어드바이스
<bean id="before" class="io.icednut.spring.exercise.beforeAdvice"/> <aop:config> <aop:pointcut id="allPointcut" expression="execution(* io.icednut.spring.exercise..*Impl.*(..))"/> <aop:aspect ref="before"> <aop:before pointcut-ref="allPointcut" method="beforeLog"/> </aop:aspect> </aop:config>
After Returning 어드바이스
<bean id="afterReturning" class="io.icednut.spring.exercise.AfterRetruningAdvice"/> <aop:config> <aop:pointcut id="getPointcut" expression="execution(* io.icednut.spring.exercise..*Impl.get*(..))"/> <aop:aspect ref="before"> <aop:after-returning pointcut-ref="getPointcut" method="afterLog"/> </aop:aspect> </aop:config>
After Throwing 어드바이스
<bean id="afterThrowing" class="io.icednut.spring.exercise.AfterThrowingAdvice"/> <aop:config> <aop:pointcut id="allPointcut" expression="execution(* io.icednut.spring.exercise..*Impl.*(..))"/> <aop:aspect ref="afterThrowing"> <aop:after-throwing pointcut-ref="allPointcut" method="exceptionLog"/> </aop:aspect> </aop:config>
After 어드바이스
<bean id="after" class="io.icednut.spring.exercise.AfterAdvice"/> <aop:config> <aop:pointcut id="allPointcut" expression="execution(* io.icednut.spring.exercise..*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>
동시에 지정하게 되면 예외가 발생한 상황에서도 <aop:after>로 설정한 finallyLog가 먼저 실행되고 exceptionLog()가 실행되는것을 확인할 수 있다.
Around 어드바이스
<bean id="after" class="io.icednut.spring.exercise.AfterAdvice"/> <aop:config> <aop:pointcut id="allPointcut" expression="execution(* io.icednut.spring.exercise..*Impl.*(..))"/> <aop:aspect ref="after"> <aop:around pointcut-ref="allPointcut" method="aroundLog"/> </aop:aspect> </aop:config>
JoinPoint와 바인드 변수
예외가 발생한 비즈니스 메소드 이름이 무엇인지, 그 메소드가 속한 클래스와 패키지 정보는 무엇인지 알아야 정확한 예외 처리 로직을 구현할 수 있다. => JoinPoint 인터페이스 제공
Signature getSignature() : 클라이언트가 호출한 메소드의 시그니처(리턴타입, 이름, 매개변수) 정보 리턴
- String getName() : 메서드 이름 리턴
- String toLongString() : 메서드 리턴 타임, 이름, 매개변수 패키지경로까지 포함해서 리턴
- String toShorString() : 메[소드 시그니처를 축약한 문자열로 리턴 Object getTarget() : 클라이언트가 호출한 비지니스 메소드를 포함하는 비즈니스 객체 리턴 Object[] getArgs() : 클라이언트가 메소드를 호출할 때 넘겨준 인자 목록 리턴
사용하려면 어드바이스 메소드 매개변수로 JoinPoint를 선언하면 된다. => 스프링 컨테이너에서 JoinPoint 객체를 생성
실습 보기
어노테이션 기반 AOP
<aop:aspectj-autoproxy/> 를 스프링 설정 파일에 입력하면 알아서 처리해준다.
이후 @Service 어노테이션 설정으로 bean등록을 대신한다
@Pointcut("execution(* io.icednut.spring.exercise..*Impl.*(..))") // 포인트컷 public void allPointcut() {} // 참조 메서드
@Before("allPointcut()") // 어드바이스 설정 public void printLogging() { System.out.println("[공통 로그-Log4j 비지니스 로직 수행 전 동작"); }
애스펙트 설정 (포인트컷과 어드바이스 결합)
@Aspect가 설정된 애스펙트 객체에는 반드시 포인트컷과 어드바이스를 결합하는 설정이 있어야한다.
package io.icednut.spring.exercise.common; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Service; import java.sql.SQLException; @Service @Aspect public class Log4jAdvice { @Pointcut("execution(* io.icednut.spring.exercise..*Impl.*(..))") public void allPointcut() {} @Pointcut("execution(* io.icednut.spring.exercise..*Impl.get*(..))") public void getPointcut() {} @Before("allPointcut()") public void printLogging() { System.out.println("[공통 로그-Log4j 비지니스 로직 수행 전 동작"); } @AfterThrowing(pointcut = "allPointcut()", throwing = "exceptObj") public void exceptionLogging(JoinPoint jp, Exception exceptObj) { String method = jp.getSignature().getName(); System.out.println(method + "() 메소드 수행 중 예외 발생!"); if (exceptObj instanceof SQLException) { System.out.println("SQL 수행 중 예외 발생"); } else { System.out.println("기타 예외 발생"); } } }
'낙서장 > Spring' 카테고리의 다른 글
스프링 라이브러리 다운로드 mvnrepository.com (0) | 2019.09.24 |
---|---|
스프링 개념 이해하기 (GenericXmlApplicationContext , XML) (0) | 2019.09.24 |
comment