관심 지향 / 관점 지향 프로그래밍
Aspect Oriented Programming
OOP 를 효과적으로 사용하는 것
대부분 MVC 웹 어플리케이션에서 Web Layer, Business Layer, Data Layer 로 정의
특정 구역에 반복되는 로직들을 한곳에 모아서 해결
외부 클래스.메서드()
로 사용)비즈니스 클래스 (XXXImpl 클래스
)
비즈니스 로직을 구현하는 메서드에 로킹 처리, 예외 처리, 트랜잭션 처리 등
핵심 비즈니스 로직 외 부가적인 코드들(횡단 관심 부분)이 많이 첨부 된다.
⇒ 배보다 배꼽이 더 크다
ex. 각 회사마다 각 메서드는 20줄을 넘기지 않는다는 기준도 있다
위와 같은 문제점을 해결하기 위해?
👉 메서드가 여러 기능을 하지 않도록 분리시켜, 재사용성 + 가독성 증가❗
👉 = 관심 분리 = 핵심 관심과 횡단 관심을 분리❗
XXXServiceImpl
클래스👉 비즈니스 클래스에는 비즈니스 메서드가 구현되어 있으며, 비즈니스 메서드가 가진 코드가 핵심 관심 코드다
XXXAdvice
클래스👉 횡단 관심 클래스에는 비즈니스 메서드마다 실행될 공통의 로직이 들어있으며, 공통 로직이 횡단 관심 코드다
즉, 비즈니스 메서드에 비즈니스 로직만 구현할 수 있다.
컨트롤러와 디자인을 분리시키는 것과 같은 개념으로 이해하자.
클라이언트가 호출하는 모든 비즈니스 메서드
XXXServiceImpl
클래스의 모든 메서드
수많은 메서드 중에 내가 원하는 메서드 출력
필터링된 조인포인트(=필터링된 비즈니스 메서드)
사전처리와 사후처리 가능
트랜잭션 처리
commit
, 반영안한다면 rollback
select
에서는 동작하지 않음필터링 하는 이유?
리턴 타입 필터링
*
: 모든 타입을 필터링void
: 리턴 타입이 void인 메서드만 필터링!void
: 리턴 타입이 void가 아닌 메서드만 필터링패키지 경로 필터링
com.ssamz.biz.board
: 정확하게 com.ssamz.biz.board인 패키지 필터링com.ssamz.biz..
: com.ssamz.biz로 시작하는 모든 패키지 필터링com.ssamz.biz..board
: com.ssamz로 시작하는 모든 패키지 중에서 패키지 마지막이 board인 패키지를 필터링클래스명 필터링
BoardServiceImpl
: BoardServiceImpl 클래스를 필터링*Impl
: 클래스 이름이 Impl로 끝나는 모든 클래스(XXXImpl)를 필터링메서드명 필터링
getBoardList
: 이름이 정확하게 getBoardList
인 메서드 필터링get*
: 이름이 get 으로 시작하는 모든 메서드(get()
) 포함매개 변수 필터링
(..)
: 모든 경우의 매개변수의 개수와 타입을 허용<aop:pointcut id="allPointcut" expression="execution(* com.ssamz.biz..*Impl.*(..))"/>
get으로 시작하는 메서드만(특정 메서드만) 사전처리
<aop:config>
<aop:pointcut id="allPointcut" expression="execution(* com.ssamz.biz..*Impl.*(..))"/>
<aop:pointcut id="getPointcut" expression="execution(* com.ssamz.biz..*Impl.get*(..))"/>
<aop:aspect ref="log">
<aop:before pointcut-ref="getPointcut" method="printLog"/>
</aop:aspect>
</aop:config>
===> BoardDAO 생성
===> BoardServiceImpl 생성
<사전 처리> 핵심 비즈니스 로직 수행 전 동작
===> getBoardList 사전처리
모든 메서드 사전처리
===> BoardDAO 생성
===> BoardServiceImpl 생성
<사전 처리> 핵심 비즈니스 로직 수행 전 동작
===> insertBoard 사전처리
<사전 처리> 핵심 비즈니스 로직 수행 전 동작
===> getBoardList 사전처리
횡단 관심에 해당하는 공통 기능의 코드
어드바이스 동작 시점을 5가지로 지정
getUser
메서드 실행 후에 로직 수행 ===> BoardDAO 생성
===> BoardServiceImpl 생성
===> getUser 사전처리
<사전 처리> 핵심 비즈니스 로직 수행 전 동작
<!-- 횡단관심에 해당하는 Advice 클래스 등록 -->
<bean id="log" class="com.ssamz.biz.common.LogAdvice"/>
<bean id="afterReturning" class="com.ssamz.biz.common.AfterReturningAdvice"/>
<!-- AOP 설정 -->
<aop:config>
<aop:pointcut id="allPointcut" expression="execution(* com.ssamz.biz..*Impl.*(..))"/>
<aop:pointcut id="getPointcut" expression="execution(* com.ssamz.biz..*Impl.get*(..))"/>
. . .
<aop:aspect ref="afterReturning">
<aop:after-returning pointcut-ref="getPointcut" method="afterLog"/>
</aop:aspect>
</aop:config>
<사전 처리> 핵심 비즈니스 로직 수행 전 동작
===> getUser 사전처리
<사후 처리> 핵심 비즈니스 로직 수행 후 동작
pointcut-ref="getPointcut"
에 해당하는 메서드 실행 후
뭔가 리턴된 후에(<aop:after-returning . . .>
)
해당 id(afterReturning
)가 가지고 있는
해당 메서드(afterLog
)를 실행
📌실습📌
public class AfterReturningAdvice {
public void afterLog(Object returnObj) {
System.out.println("<사후 처리> 비즈니스 메서드가 리턴한 값 : " + returnObj.toString());
// 비즈니스 메서드가 리턴한 데이터를 이용해서 관리자가 로그인했는 지 여부를 확인
if(returnObj instanceof UserVO) {
UserVO user = (UserVO) returnObj;
if(user.getRole().equals("ADMIN")) {
System.out.println(user.getName() + "님 관리자 전용 페이지로 이동합니다...");
}
}
}
}
===> BoardDAO 생성
===> BoardServiceImpl 생성
<사전 처리> 핵심 비즈니스 로직 수행 전 동작
===> getUser 사전처리
<사후 처리> 비즈니스 메서드가 리턴한 값 : UserVO(id=test, password=test, name=yeppi, role=ADMIN)
yeppi님 관리자 전용 페이지로 이동합니다...
yeppi님 환영합니다.
비즈니스 메서드 실행 중 예외가 발생하면 동작
AfterThrowingAdvice
public class AfterThrowingAdvice {
public void exceptionLog() {
System.out.println("<예외 처리> 비즈니스 메서드 수행 중 예외 발생");
}
}
business-layer.xml
<!-- 횡단관심에 해당하는 Advice 클래스 등록 -->
<bean id="log" class="com.ssamz.biz.common.LogAdvice"/>
<bean id="afterReturning" class="com.ssamz.biz.common.AfterReturningAdvice"/>
<bean id="afterThrowing" class="com.ssamz.biz.common.AfterThrowingAdvice"/>
<!-- AOP 설정 -->
<aop:config>
<aop:pointcut id="allPointcut" expression="execution(* com.ssamz.biz..*Impl.*(..))"/>
<aop:pointcut id="getPointcut" expression="execution(* com.ssamz.biz..*Impl.get*(..))"/>
. . .
<aop:aspect ref="afterThrowing">
<aop:after-throwing pointcut-ref="allPointcut" method="exceptionLog"/>
</aop:aspect>
</aop:config>
예외 발생 후에(뭔가 리턴된 후에)
컨테이너는 afterThrowing
이 가지고 있는 exceptionLog
를 모든 메서드(allPointcut
)에서 실행
<사전 처리> 핵심 비즈니스 로직 수행 전 동작
<예외 처리> 비즈니스 메서드 수행 중 예외 발생
Exception in thread "main" java.lang.IllegalArgumentException
메서드 호출 가로채서 비즈니스 메서드 실행 전후에 특정 로직 삽입
AroundAdvice.java
public class AroundAdvice {
public Object aroundLog(ProceedingJoinPoint jp) throws Throwable{ // Throwable 은 exception 의 부모
Object returnObj = null;
System.out.println("--<Before Logic>--");
returnObj = jp.proceed(); // proceed() -> return 타입 Object, 예외 throws 는 Throwable
System.out.println("--<After Logic>--");
return returnObj;
}
}
xml
<bean id="around" class="com.ssamz.biz.common.AroundAdvice"/>
<!-- AOP 설정 -->
<aop:config>
<aop:pointcut id="allPointcut" expression="execution(* com.ssamz.biz..*Impl.*(..))"/>
<aop:pointcut id="getPointcut" expression="execution(* com.ssamz.biz..*Impl.get*(..))"/>
. . .
<aop:aspect ref="around">
<aop:around pointcut-ref="allPointcut" method="aroundLog"/>
</aop:aspect>
</aop:config>
<사전 처리> 핵심 비즈니스 로직 수행 전 동작
--<Before Logic>--
===> getUser 사전처리
--<After Logic>--
<사후 처리> 비즈니스 메서드가 리턴한 값 : UserVO(id=test, password=test, name=yeppi, role=ADMIN)
yeppi님 관리자 전용 페이지로 이동합니다...
yeppi님 환영합니다.
getUser
메서드 호출 요청jp.proceed()
가 getUser()
메서드 호출returnObj
값 받아오고after
수행 시작👉 필터와 유사한 동작
ProceedingJoinPoint
는 JoinPoint
를 상속 받음ProceedingJoinPoint
는 확장된 상태ProceedingJoinPoint
에만 proceed()
메서드가 추가되어 있음Proceed
를 매개변수로 사용하고after-returning
<aop:aspect ref="afterReturning">
<aop:after-returning pointcut-ref="getPointcut" method="afterLog" returning="returnObj"/>
</aop:aspect>
after 로 등록한 메서드는 return 값을 받지 못함
👉 반환 여부에 따라
👉 아예 용도가 다름
CGLIB
)를 생성하는 과정필터링된 비즈니스 메서드(Pointcut) + 횡단관심에 해당하는 공통 기능의 메서드(Advice)
⇒ 이 둘을 적절히 연결하는 것
핵심 관심 + 횡단 관심 ⭐
<!-- 횡단관심에 해당하는 Advice 클래스 등록 -->
<bean id="log" class="com.ssamz.biz.common.LogAdvice"/>
<!-- AOP 설정 -->
<aop:config>
<aop:pointcut id="allPointcut" expression="execution(* com.ssamz.biz..*Impl.*(..))"/>
<aop:pointcut id="getPointcut" expression="execution(* com.ssamz.biz..*Impl.get*(..))"/>
<aop:aspect ref="log">
<aop:before pointcut-ref="allPointcut" method="printLog"/>
</aop:aspect>
</aop:config>
핵심 관심(AOP의 P) pointcut-ref="allPointcut"
횡단 관심(AOP의 A) method="printLog"
이 둘을 연결(AOP의 O)
xml 파일은 컨테이너를 위한 파일(컨테이너에 지시/설정)
동작 흐름
pointcut-ref="allPointcut"
모든 비즈니스 메서드가 실행되기 전에(before
)<bean id="log" class"">
) 클래스들은 method="printLog"
)를 실행하라고 하는 것Pointcut 👉 핵심 관심, 필터링 기능
Advice 👉 횡단 관심, 필터링 동작 시점
Ascpect 👉 Pointcut 과 Advice 의 연결 고리
AOP를 적용하면? 핵심 비즈니스 로직을 분리하여 유지보수가 편리해진다.