스프링MVC - AOP [2]

600g (Kim Dong Geun)·2020년 8월 22일
0

본 글은 스프링 MVC에 대해 지식을 정리하고 나중에 헷갈릴 때 다시 보기 위한 글입니다 👀

본 게시글은 Spring MVC Quick Start를 참조하여 정리한 글입니다. 📖 👀

본 게시글은 Spring MVC Documentation를 참조하여 정리한 글입니다 📚 👀

본 게시글은 이전글과 이어집니다. 👈👈👈

어드 바이스 동작 시점

어드바이스는 각 조인포인트에 삽입되어 동작할 횡단 관심에 해당하는 공통 기능(메소드)이며, 스프링에서 제공하는 동작 시점은 총 5가지가 존재한다.

동작시점설명
Before비즈니스 메소드 실행 전 동작
After비즈니스 메소드가 실행된 후 무조건 실행
After Returning비즈니스 메소드가 성공적으로 리턴되면 동작
After Throwing비즈니스 메소드 실행 중 예외가 발생하면 동작
AroundAround는 메소드 호출 자체를 가로채 비즈니스 메소드 실행 전후에 처리할 로직을 삽입할 수 있음

Before 어드바이스

Before 어드바이스는 포인트 컷으로 지정된 메소드 호출 시, 메소드가 실행되기 전에 어드바이스를 실행시킨다.

예를들어,

  • BeforeAdvice.java
public class BeforeAdvice{
    public void beforeLog(){
        System.out.println("[사전처리] 비즈니스 로직 수행 전 동작");
    }
}
  • applicationContext.xml
<bean id="before" class="com.x.y.z.BeforeAdvice"/>

<aop:config>
  <aop:pointcut id="allPointCut" expression="execution(* com.x.y.z..impl.*(..))"/>
  <aop:aspect ref="before">
    <aop:before pointcut-ref="allPointCut" method="beforeLog"/>
  </aop:aspect>
</aop:config>

요롬코롬 적용하게 되는 것이다.

그렇다면 서비스에 내용된 모든 메소드가 실행되기 전에 BeforeAdvice 클래스 내에 있는 beforeLog 메소드가 실행되게 되는 것이다.

After Retunring 어드바이스

After Returning 어드바이스는 포인트컷으로 지정된 메소드가 정상적으로 실행되고 나서 메소드 수행 결과로 생성된 데이터를 리턴하는 시점에 동작한다.

자바 클래스는 알아서 만들어져있다 생각하고 생략하겠다

  • applicationContext.xml
<bean id="afterReturning" class="com.x.y.z.AfterRetunringAdvice"/>

<aop:config>
  <aop:pointcut id="getPointcut"
                expression="execution(* com.x.y.z..*Impl.get*(..))"/>
  
  <aop:aspect ref="afterReturning">
    <aop:after-returning pointcut-ref="getPointcut" method="afterLog"/>
  </aop:aspect>
</aop:config>    

그렇다 Impl 내에 존재하는 모든 클래스의 get으로 시작하는 메소드가 실행된 뒤에 AfterReturningAdvice클래스의 afterLog() 메소드가 실행될 것이다.

After Throwing 어드바이스

After Throwing 어드바이스는 포인트컷으로 지정한 메소드가 실행되다가 예외가 발생하는 시점에 동작한다. 따라서 예외 처리 어드바이스를 설정할 때 사용한다.

  • applicationContext.xml
<bean id="afterThrowing" class="com.x.y.z.AfterThrowingAdvice"/>

<aop:config>
  <aop:pointcut id="getPointcut"
                expression="execution(* com.x.y.z..*Impl.get*(..))"/>
  
  <aop:aspect ref="afterThrowing">
    <aop:after-throwing pointcut-ref="getPointcut" method="exceptionLog"/>
  </aop:aspect>
</aop:config>    

예외가 발생할 시점에 exceptionLog() 메소드를 실행시키게 된다.

After 어드바이스

try-catch-finally 구문에서 finally 구문 처럼 예외 발생 여부에 상관없이 무조건 수행되는 어드바이스를 등록할 때 After Advice를 사용한다.

  • applicationContext.xml
<bean id="afterThrowing" class="com.x.y.z.AfterThrowingAdvice"/>
<bean id="after" class="com.x.y.z.AfterAdvice"/>

<aop:config>
  <aop:pointcut id="getPointcut"
                expression="execution(* com.x.y.z..*Impl.get*(..))"/>
  
  <aop:aspect ref="afterThrowing">
    <aop:after-throwing pointcut-ref="getPointcut" method="exceptionLog"/>
  </aop:aspect>
  
  <aop:aspect ref="after">
    <aop:after pointcut-ref="getPointcut" method="finallyLog"/>
  </aop:aspect>
</aop:config>    

만약 이 두개가 설정된다면 어떤 메소드가 실행이 될 것인가?
답은 AfterLog()메소드가 먼저 실행되고 exceptionLog() 가 실행된다.

즉 실행순서는 after -> after-throwing 이라 볼 수 있다.

Around 어드바이스

Around 어드바이스는 클라이언트의 메소드 호출을 가로챈다. 그래서 호출할 비즈니스 메소드가 실행되기 전이나 후 모두 처리를 할 수 있는 것이다.

  • AroundAdvice.java
public class AroundAdvice{
    public Object aroundLog(ProceedingJoinPoint pjp) throws Throwable{
        System.out.println("before : 사전처리");
        Object returnObj = pjp.proceed();
        System.out,println("after: 사후처리");
        return returnObj;
    }
}

여기서 알아둬야 할 점이 ProceedingJoinPoint 클래스이다. 후에 서술하겠지만, 각각의 어드바이스들은 Joinpoint들을 매개변수로 가질 수 있는데, 특별히 Around만이 ProceedingJoinPoint를 매개변수로 가진다.

ProceedingJoinPoint의 객체 pjp가 proceed() 메소드를 호출하기전 까지의 처리를 사전 처리라 할 수 있으며 proceed() 메소드를 호출한 이후를 사후 처리라 할 수 있다.

따라서 ProceedingJoinPoint 클래스를 통해서 Around는 메소드 호출 시점을 정할 수 있다.

다음은 xml 처리이다.
xml 처리는 다른 어드바이스들과 비슷하다.

<bean id="around" class="com.x.y.z.AroundAdvice"/>

<aop:config>
  <aop:pointcut id="allPointcut"
                exepression="execution (* com.x.y..*Impl.*(..))"/>
  
  <aop:aspect ref="around">
    <aop:around pointcut-ref="allPointcut" method="aroundLog"/>
  </aop:aspect>
</aop:config>

JoinPoint와 바인드 변수

횡단 관심에 해당하는 어드바이스 메소드를 의미있게 구현하려면 클라이언트가 호출할 비즈니스 메소드의 정보가 필요하다.

아까 Around 어드바이스를 구현할 때 ProceedingJoinPoint를 이용하여 메소드 호출을 직접 관리하곤 했다.

그것 말고도 스프링에서는 이런 다양한 정보들을 이용할 수 있도록 JoinPoint 인터페이스를 제공한다.

JoinPoint 메소드

메소드설명
Signature getSignature()클라이언트가 호출한 메소드의 시그니처(리턴타입, 이름, 매개변수) 정보가 저장된 Signature 객체 리턴
Object getTarget()클라이언트가 호출한 비즈니스 메소드를 포함하는 비즈니스 객체 리턴
Object[] getArgs()클라이언트가 메솓르ㅡㄹ 호출할 때 넘겨준 인자 목록을 Object 배열롤 리턴

실제로 JoinPoint를 제대로 활용하면 현재 실행되고 있는 메소드가 무엇인지, 어떤 매개변수를 가지며 어떤 객체를 반환하는지에 대한 정보도 살펴볼 수 있다.

실제로 ProceedingJoinPoint도 위 JoinPoint 인터페이스를 상속받아 위의 기능을 모두 사용할 수 있다.

각설하고 당연히 나머지 Advice에서도 (Before나, After...등) JoinPoint를 사용할 수 있다는 점이다.

여기서 주의할점은 Around만 ProceedingJoinPoint ,나머지는 그냥 JoinPoint를 사용한다.

또한 JoinPoint가 제공하는 메소드중 getSignature() 메소드를 사용하면 호출되는 메소드에 대한 정보를 얻을 수 있다.

메소드 명설명
String getName()클라이언트가 호출한 메소드 이름
String toLongString()클라이언트가 호출한 메소드의 리턴타입, 이름, 매개변수를 패키지 경로까지 포함하여 리턴
Stirng toShortString()클라이언트가 호출한 메소드 시그니처를 축약한 문자열로 리턴.

다음은 각 Advice에 대해 가져올 수 있는 객체에 대해서 설명하도록 하겠다.

Before Advice

public class BeforeAdvice{

    public void beforeLog(JoinPoint jp){
        String method = jp.getSignature().getName();
        Object[] args = jp.getArgs();
        
        System.out.println("[사전 처리]: + method + "() 메소드 ARGS 정보" + args[0].toString());
    }
}

나 정도면 위 코드는 다음에 봤을 때, 믿어 의심치 않고 이해할 수 있을꺼라 생각하고 생략

After Retuning Advice

After Returning 어드바이스는 비즈니스 메소드가 수행되고 나서, 결과 데이터를 리턴할 때 동작하는 어드바이스다. 따라서 어떤 메소드가 어떤 값을 리턴했는지 알아야 다양한 사후 처리기능을 구현할 수 있을 것이다.

public class AfterReturningAdvice {

    public void afterLog(JoinPoint jp, Object returnObj){
        String method = jp.getSignature().getName();
        if(returnObj instanceof UserVO){
            UserVo user = (UserVO) returnObj;
            System.out.println("[사용자 로그인] : "+user.getName());
        }
    }
}

자세히 봐야할 것은 returnObj이다.

또한 xml또한 손을 봐줘야한다.

  • applicationContext.xml
<bean id="afterReturning" class="com.x.y.z.AfterRetunringAdvice"/>

<aop:config>
  <aop:pointcut id="getPointcut"
                expression="execution(* com.x.y.z..*Impl.get*(..))"/>
  
  <aop:aspect ref="afterReturning">
    <aop:after-returning pointcut-ref="getPointcut" method="afterLog"
                         returnObj="returnObj"/>
  </aop:aspect>
</aop:config>    

전과 다른 점이라면 Aspect 내에 returnObj를 명시하는 부분이 추가 되었다는 점

After Throwing 어드바이스

After Throwing은 비즈니스 메소드가 수행되다가 예외가 발생했을 때 동작하는 어드바이스다. 따라서 `어떤 메소드에서 어떤 예외가 발생했는지 알아야 한다.

  • AfterThrowingAdvice.java
public class AfterThrowingAdvice {
    public void exceptionLog(JoinPoint jp, Exception exceptObj){
        String method = jp.getSignature().getName();
        
        System.out.println("[예외처리] " + method +
            "() 메소드 수행 중 발생된 예외 메시지" + exceptObj.getMessage());
        }
    }
  • applicationContext.xml
<bean id="afterThrowing" class="com.x.y.z.AfterThrowingAdvice"/>
<bean id="after" class="com.x.y.z.AfterAdvice"/>

<aop:config>
  <aop:pointcut id="getPointcut"
                expression="execution(* com.x.y.z..*Impl.get*(..))"/>
  
  <aop:aspect ref="afterThrowing">
    <aop:after-throwing pointcut-ref="getPointcut" method="exceptionLog"
                        throwing="exceptObj"/>
  </aop:aspect>
  
  <aop:aspect ref="after">
    <aop:after pointcut-ref="getPointcut" method="finallyLog"/>
  </aop:aspect>
</aop:config>    

After Returning과 비슷하게 aspect에 throwing 부분을 넣어준다.

Around Advice

Around의 경우 위 부분에 서술 했으므로 생략하겠다
(힘들다..)

어노테이션 기반 AOP

XML 지겹다. 어노테이션으로 넘어가즈아!!!

어노테이션 사용을 위한 스프링 설정

AOP기능을 어노테이션으로 설정하려면 가장 먼저 스프링 설정 파일에 <aop:aspectj-autoproxy>를 선언해야 한다.

  • applicationContext.xml
<beans>
  <aop:aspectj-autoproxy/>
</beans>

또한 어드바이스 클래스에 선언된 어노테이션들을 처리하기 위해서는 반드시 어드바이스 객체가 생성되어있어야한다.

빈으로 등록되어있어야 사용이 가능하단 말이다.

따라서 @Service@Component등으로 해당 컨테이너가 객체들을 알 수 있게끔 해야 한다.

포인트 컷 설정

  • LogAdvice.java

@Service
public class LogAdvice {
    @Pointcut("execution(* com.x.y.z..*Impl.*(..)")
    public void allPointCut(){}
    
    @Pointcut("execution(* com.x.y.z..*Imp.get(..)")
    public void getPointCut(){}
}

위 소스에서 알 수 있듯이 포인트 컷을 선언할 때는 @Pointcut을 사용한다.
또한 포인트 컷을 사용하기 위한 메소드는 참조 메소드를 사용하는데,
참조 메소드란 메소드 몸체가 비어있는 구현로직이 없는 메소드를 말한다.

메소드의 이름이 포인트 컷의 id가 된다.

어드바이스 설정

어드바이스 클래스에는 횡단 관심에 해당하는 어드바이스 메소드가 구현되어 있다.
이 어드바이스 메소드가 언제 동작할지 결정하여 관련된 어노테이션을 메소드 위에 설정하면 된다.

  • LogAdvice.java

@Service
public class LogAdvice {
    @Pointcut("execution(* com.x.y.z..*Impl.*(..)")
    public void allPointCut(){}
    
    @Pointcut("execution(* com.x.y.z..*Imp.get(..)")
    public void getPointCut(){}

    @Before("allPointCut()")
    public void printLog(){
        System.out.println("[비즈니스 로그 수행전 동작]");
    }
}

처럼 말이다.

어드바이스로 설정할 수 있는 어노테이션은 총 5가지가 존재한다.

어노테이션설명
@Before비즈니스 메소드 실행전에 동작
@AfterRetunring비즈니스 메소드가 성공적으로 리턴되면 동작
@AfterThrowing비즈니스 메소드 실행 중 예외가 발생하면 동작
@After비즈니스 메소드가 실행된 후 무조건 실행
@Around호출 자체를 가로채 비즈니스 메소드 실행 전후에 처리할 로직을 삽입할 수 있음

애스팩트 설정

AOP 설정에서 가장 중요한 애스팩트는 @Aspect를 이용하여 설정한다. Aspect는 용어 정리에서 봤듯이 포인트 컷과 어드바이스의 결합이다. 따라서 @Aspect가 설정된 애스팩트 객체에는 반드시 포인트 컷과 어드바이스를 결합해야 한다.

  • LogAdvice.java

@Service
@Aspect // Aspect = Pointcut + Advice
public class LogAdvice {
    @Pointcut("execution(* com.x.y.z..*Impl.*(..)")
    public void allPointCut(){}
    
    @Pointcut("execution(* com.x.y.z..*Imp.get(..)")
    public void getPointCut(){}

    @Before("allPointCut()")
    public void printLog(){
        System.out.println("[비즈니스 로그 수행전 동작]");
    }
}

어드바이스 동작 시점

Before Advice

@Before
public void beforeLog(JoinPoint jp){
	...
}

After Returning Advice

@AfterRetunring(pointcut="getPointcut()", returning="returnObj")
public void afterLog(JoinPoint jp, Object returnObj){
    ...
}

After Throwing Advice

@AfterThrowing(pointcut="allPointcut()", throwing="exceptObj")
public void exceptionLog(JoinPoint jp, Exception exceptObj){
    ...
}

After Advice

@After("allPointcut()")
public void finallyLog(){
    ...
}

Around 어드바이스 설정

@Around
public Object aroundLog(ProceedingJoinPoint pjp) throws Throwable{
   ...
   Object obj = pjp.proceed();
   ...
   return obj;
}

힘들다. ㅠㅠㅠ

내일은 스프링 JDBC에 대해 적을 예정!

profile
수동적인 과신과 행운이 아닌, 능동적인 노력과 치열함

0개의 댓글