레이블이 AOP인 게시물을 표시합니다. 모든 게시물 표시
레이블이 AOP인 게시물을 표시합니다. 모든 게시물 표시

2011년 2월 21일 월요일

CXF 서비스 호출 시 발생하는 예외 처리 - Spring AOP Annotation

Spring에서 Annotation기반 AOP를 이용하려면 스프링 설정을 아래와 같이 지정해야 한다.

<?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"
  xsi:schemaLocation="http://www.springframework.org/schema/beans
  http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
    http://www.springframework.org/schema/aop
    http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">

  <aop:aspectj-autoproxy />
  
</beans>

아래의 소스 코드는 CXF의 서비스 호출 시 에러 처리에 관련된 Aspect 소스 코드다.
아래의 소스 코드에서 @Service나 @Component 스테레오 타입이 지정되도록 해야한다.

package com.archnal.sample.cxf;

import javax.ws.rs.core.Response;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Service;

import com.archnal.sample.exception.BaseException;
import com.archnal.sample.exception.StatusCode;

@Aspect()
@Service("restfulServiceAspect")
public class RestfulServiceAspect {

  @Around("execution(public * com.archnal.sample.jaxrs.*ResponseServiceImpl.*(..))")
//  @Pointcut("execution(public * com.archnal.sample.jaxrs.*RestfulServiceImpl.*(..))")
  public Object handleRestfulService(ProceedingJoinPoint joinpoint) {
    System.out.println("around: " + joinpoint.getSignature().getName());
    
    Response response = null;
    try {
      Object[] args = joinpoint.getArgs();
      Object entity = joinpoint.proceed(args);
      
      return entity;
//      response = Response.ok(entity).build();
//      response = (Response)joinpoint.proceed(args);
      
    } catch(BaseException ex) {
      System.out.println("baseException: " + ex);
      response = Response.serverError().entity(create(ex)).build();
    } catch(Throwable ex) {
      System.out.println("throwable: " + ex);
      response = Response.serverError().entity(create(ex)).build();
    }
    return response;
  }
  
  @AfterThrowing(pointcut="execution(public * com.archnal.sample.jaxrs.*ResponseServiceImpl.*(..))", 
      throwing="e")
  public Object handleException(JoinPoint joinPoint, Throwable e) {
    System.out.println("afterThrowing: " + joinPoint.getSignature().getName());
    System.out.println("e: " + e);
    return Response.serverError().entity(create(e));
  }
  
  @Before("execution(public * com.archnal.sample.jaxrs.*ResponseServiceImpl.*(..))")
  public void before(JoinPoint joinPoint) {
    System.out.println("before: " + joinPoint.getSignature().getName());
  }
  
  @After("execution(public * com.archnal.sample.jaxrs.*ResponseServiceImpl.*(..))") 
  public void after(JoinPoint joinPoint) {
    System.out.println("after: " + joinPoint.getSignature().getName());
  }
  
  @AfterReturning(pointcut="execution(public * com.archnal.sample.jaxrs.*ResponseServiceImpl.*(..))", returning="result") 
  public void afterReturning(JoinPoint joinPoint, Object result) {
    System.out.println("afterReturning: " + joinPoint.getSignature().getName());
    System.out.println("result: " + result);
  }
  
  private ResponseResult create(BaseException ex) {
    ResponseResult header = new ResponseResult();
    header.setSuccess(false);
    header.setStatusCode(ex.getStatusCode().getCode());
    header.setMessage(ex.getMessage());
    
    return header;
  }
  
  private ResponseResult create(Throwable th) {
    if(th.getClass().isAssignableFrom(BaseException.class)) {
      return create((BaseException) th);
    }
    
    ResponseResult header = new ResponseResult();
    header.setSuccess(false);
    header.setStatusCode(StatusCode.UNKNOWN.getCode());
    header.setMessage(th.getMessage());
    
    return header;
    
  }
  

}


위에 메서드를 호출하면 에러가 발생하는 경우에 @AfterThrowing 으로 처리가 가능할까 싶어서 테스트를 해 보았으나,
예외가 발생하는 경우에 @AfterThrowing이 지정된 메서드를 호출하지만 메서드가 호출 된 이후에 Aspect는 여전히 원래 발생한 Throwable을 잡아서 제거하지 않고 던져 버린다. 따라서 서비스 Invoker에서는 여전히 에러가 발생한 것으로 인식하여 장황한 stack trace를 화면에 출력한다.
위의 예제에서는 에러가 발생하는 경우 잡아채서 클라이언트에게 serverError(500) 의 상태코드로 전달하면서 에러상태 코드를 전달하려는 의도로 작성된 코드다.
이런 요구사항에 @AterThrowing은 에러를 여전히 던지기 때문에 적합하지 않다. 실제로 @AfterThrowing에서 리턴하는 Response는 아무런 역할을 하지 못한다. 위의 소스 코드에서 처럼 @Around 에서 원래 메서드를 호출하고 에러가 발생하는 경우에 Response를 생성하여 리턴하도록 해야한다.
하지만 이런 방식에서도 문제가 하나 있다. Restful서비스의 원래 메서드가 Response를 리턴하도록 되어 있어야 한다. 일반 Pojo 클래스를 리턴하는 경우에는 ClassCastException이 발생한다. 이처럼 에러를 일괄적으로 처리하기 위해서는 꼭 Response가 아니더라도 ResponseMessage 같은 일관된 메시지 형태의 객체를 리턴해야만 ClassCastException이 발생하지 않는다.

위의 Aspect가 적용된 상태에서 서비스를 호출하였을 때 정상적인 경우에 아래와 같이 화면에 출력된다.
before: get
around: get
after: get
afterReturning: get
result: org.apache.cxf.jaxrs.impl.ResponseImpl@170a5a3

에러가 발생하는 경우에는 아래와 같이 화면에 출력된다.

before: get
around: get
after: get
afterThrowing: get
e: com.archnal.sample.exception.BaseException: category: BIZ_LOGIC_ERROR, status: USER_NOT_FOUND, code: E_102
afterReturning: get
result: org.apache.cxf.jaxrs.impl.ResponseImpl@15f8a0a

뭔 얘기를 하는건지 나만 알 수 있는 말을 적은 거 같다. 아는 사람은 알겠지 뭐~~

2010년 4월 13일 화요일

AOP 핵심 용어 정리

AOP 프로그램을 하면서도 가끔 용어들이 익숙치 않아서 작아 질 때가 있다.

조인포인트(joinpoint)
조인포인트는 애플리케이션 실행 중의 특정한 지점을 의미한다. 전형적인 조인포인트의 예로는 메서드 호출, 메서드 실행 자체, 클래스 초기화, 객체 생성시점 등이 있다. 조인포인트는 AOP 핵심 개념이며 애플리케이션의 어떤 지점에 AOP를 사용하여 추가적인 로직을 삽입할 지 정의한다.

어드바이스(advice)
특정 조인포인트에 실행하는 코드를 어드바이스라고 한다. 조인포인트 이전에 실행하는 Before 어드바이스와 이후에 실행하는 After 어드바이스를 비롯한 여러 종류의 어드바이스가 있다.

포인트컷(pointcut)
하나의 포인트컷은 여러 조인포인트의 집합체로 언제 어드바이스를 실행할 지 정의할 때 사용한다. 포인트컷을 만들어서, 애플리케이션 구성 요소에 어드바이스를 어떻게 적용할 지 상세하게 제어할 수 있다. 앞서 언급했듯이 가장 일반적으로 사용하는 조인포인트는 메서드 호출이다. 가장 일반적인 포인트컷은 특정 클래스에 있는 모든 메서드 호출로 구성된다. 종종, 여러분은 어드바이스 실행 지점을 좀 더 다양하게 제어할 필요가 있을 때 복잡한 형태로 포인트컷을 구성할 수도 있다.

애스팩트(aspect)
애스팩트는 어드바이스와 포인트컷을 조합한 것이다. 즉 이 조합물은 애플리케이션이 가지고 있어야 할 로직과 그것을 실행해야 하는 지점을 정의한 것이라고 할 수 있다.

위빙(weaving)
이것은 애플리케이션 코드 해당 지점에 애스팩트를 실제로 주입하는 과정을 말한다. 당연히, 컴파일 시점 AOP 솔루션은 이 작업을 컴파일 시점에 하여, 빌드 과정 중에 별도의 과정을 거친다. 마찬가지로, 실행 시점 AOP 설로션은 위빙 과정이 실행 중에 동적으로 일어난다.

타겟(target)
자신의 실행 흐름이 어떠한 AOP 처리로 인해 수정되는 객체를 타겟 객체라고 한다. 타겟 객체를 어드바이스가 적용된 객체라고도 한다.

인트로덕션(introduction)
인트로덕션은 객체 구조를 수정하여 새로운 메서드나 필드를 그 안에 추가할 수 있는 처리를 뜻한다. 여러분은 인트로덕션을 사용해서 어떤 객체가 특정 인터페이스를 명시적으로 구현하지 않고도 구현한 것으로 만들수 있다.

출처: Pro Spring 2.5