AOP pointcut 에 1개 이상의 조건을 사용 하는 경우 AND&&, OR|| 를 사용 한다. Java Annotation 방식으로 사용 하지 않고, XML 에서 사용 하는 경우 예외가 발생 한다.


XML은 기본적으로 <, >, & 등 사용 되고 있으므로, 이스케이프 문자로 변환 해야 한다.


  • &lt; <
  • &gt; >
  • &amp; &


아래와 같이 &amp; 로 수정해서 사용 하자.


<aop:pointcut id="test" expression="execution(public * *..*Sample.*(..)) &amp;&amp; !execution(public * *..*Sample.exclude(..))"/>


참고 사이트


Spring MVC 기반으로 개발 하는 경우, URL 기준이 아닌, Method 기준 으로 AOP 를 사용할 때가 있다. 스타일 차이라고 생각 한다.


보통 Interceptor 를 사용 하면, HttpServletRequest, HttpServletResponse 객체를 기본적으로 사용할 수 있다. Tomcat Filter 의 기능과 비슷하기 때문이다. 처리 방법에 따라 다르겠지만, 시점이 Controller 호출 전 이기 때문에, @ModelAttribute 를 사용하여 개발 한다면, Http 요청 시 사용되는 파라미터가 Model 로 값이 들어가기 전이기 때문에, 원시 적인 방법인 request.getParameter(String key) 함수를 사용해야 한다. 더 나은 방법은 AOP 기법을 활용 하는 것이다.


AOP 는 Method 기준으로 사용되기 때문에 원하는 시점을 선택하여 적용이 가능 하다. Controller 를 호출 하기전에 끼어드는 방식으로 Around 방식을 사용하면 Model 에 값이 들어간 후 이기 때문에, 데이터의 가공을 편하게 할 수 있다. Model은 joinPoint.getArgs() 함수를 사용하여 값 확인이 가능 하다. 아래는 예제 소스 이다.


/**
 * CustomAspect
 *
 * @author whitelife
 * @since 2014.10.05
 * @version 0.1
 */
public class CustomAspect {

    /**
     * Custom Aspect
     *
     * @param joinPoint Proxy Method Info
     * @return joinPoint
     * @throws Throwable
     */
    public Object customAspect(ProceedingJoinPoint joinPoint) throws Throwable {
        Object result = null;

        // (Before) doing...        

        for (Object obj : joinPoint.getArgs()) {
            if (obj instanceof HttpServletRequest || obj instanceof MultipartHttpServletRequest) {
                HttpServletRequest request = (HttpServletRequest) obj;

                // Doing...

            }
        }

        try {
            result = joinPoint.proceed();
        } catch (Throwable e) {
            throw e;
        }

        // (After) doing...     

        return result;
    }
}


ProceedingJoinPoint 클래스는 Target Class, Target Method 정보 를 담고 있다. proceed 메소드 호출 시 Target Method 를 실행 한다. 보통 joinPoint.getArgs() 함수 를 사용하여 Target Method 의 인자 값을 확인하여 사용하지만, 매번 Controller Method 에 인자로 HttpServletRequest 를 넣어줘야 하는 불편함이 있다.


HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();


위 방법을 사용하면, AOP 에서도 HttpServletRequest 객체에 접근이 가능하다. Controller 개발 시 인자로 HttpServletRequest 를 받을 필요가 없다. 편리하게 사용할 수 있겠다.


ServletRequestAttributes 는 HttpRequest 가 오는 경우, RequestContextListener.requestInitialized(ServletRequestEvent requestEvent) 함수에 의해 값이 전달 되기 때문에 값을 받을 수 있는 것이다. RequestContextListener 의 일부 이다. 아래와 같이 구현 되어 있다.


public void requestInitialized(ServletRequestEvent requestEvent) {
    if (!(requestEvent.getServletRequest() instanceof HttpServletRequest)) {
        throw new IllegalArgumentException(
                "Request is not an HttpServletRequest: "
                        + requestEvent.getServletRequest());
    }

    HttpServletRequest request = (HttpServletRequest) requestEvent
            .getServletRequest();
    ServletRequestAttributes attributes = new ServletRequestAttributes(
            request);
    request.setAttribute(REQUEST_ATTRIBUTES_ATTRIBUTE, attributes);
    LocaleContextHolder.setLocale(request.getLocale());
    RequestContextHolder.setRequestAttributes(attributes);
}


ArgumentResolver 를 사용하여 구현도 가능 하지만, 해당 기능은 공통적으로 필요한 값을 추가 하는 용도라고 판단이 된다.


무조건 정답이라고 할 수는 없다. 적합한 방법을 사용하여 작업 하면 된다고 판단 된다.


참고 사이트


Spring MVC 사용 시 SimpleMappingExceptionResolver 설정을 해놓았다. Controller 에서 공통으로 필요한 부분을 AOP 로 만들어서 사용 하는대, Exception 발생 시 AOP Proxy 에서 예외가 발생 했다. 정작 필요한 에러 메시지는 볼 수 없었다.


일반적으로 AOP 설정을 하는 경우 아래와 같이 선언을 했었다.


<aop:config>    
    // Doing...
</aop:config>


<aop:config> 에 아무런 설정 값을 주지 않은 경우 기본적으로 JDK Proxy 로 동작 한다. Spring 에서 제공 하는 InvocableHandlerMethod.invoke(Object[] arg) 메소드를 사용 하여 실제 targetClass를 호출 한다. Java Reflection 기반 이라고 생각 하면 된다.


에러 메시지의 일부 이다. invoke 호출 시 예외가 발생 한다. targetClass 가 Interface 를 구현하고 있는 경우 instance 를 인식하지 못해 예외가 발생 하는 것 같다.


java.lang.IllegalStateException: The mapped controller method class 'com.xxx.xxx' is not an instance of the actual controller bean instance '$Proxy22'. If the controller requires proxying (e.g. due to @Transactional), please use class-based proxying.
HandlerMethod details: 
Controller [$Proxy22]
Method [public org.springframework.web.servlet.ModelAndView com.xxx.xxx.xxxxxx() throws java.lang.Exception]
// 생략...


문제 해결을 위해서는 Class 기반 proxy 사용을 해야 한다.
please use class-based proxying. 이 메시지가 친절하게 방법을 알려 준다.


CGLIB Proxy 를 사용하려면 아래와 같이 설정 값을 변경 한다.


<aop:config proxy-target-class="true">
    // Doing...
</aop:config>


테스트를 해보자. 정상적으로 동작 할 것 이다.


참고 사이트


+ Recent posts