Spring MVC AOP 어노테이션 기반 Aspect 설정 및 IllegalArgumentException: formal unbound in pointcut 오류 해결

Maven 의존성 설정

<!-- 스프링 AOP 핵심 라이브러리 -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    <version>4.0.2.RELEASE</version>
</dependency>

<!-- AspectJ 어노테이션 방식 지원 라이브러리 -->
<dependency>  
    <groupId>org.aspectj</groupId>  
    <artifactId>aspectjrt</artifactId>  
    <version>1.8.9</version>  
</dependency>  
<dependency>  
    <groupId>org.aspectj</groupId>  
    <artifactId>aspectjweaver</artifactId>  
    <version>1.8.9</version>  
</dependency>

주의사항: 버전은 사용 중인 JDK와 반드시 호환되어야 합니다. 호환되지 않으면 error at ::0 can't find referenced pointcut pointCutName 오류가 발생할 수 있습니다.

Spring MVC XML 설정

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                        http://www.springframework.org/schema/beans/spring-beans-4.0.xsd  
                        http://www.springframework.org/schema/context  
                        http://www.springframework.org/schema/context/spring-context-4.0.xsd  
                        http://www.springframework.org/schema/mvc  
                        http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
                        http://www.springframework.org/schema/aop 
                        http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- AspectJ 자동 프록시 활성화 -->  
    <aop:aspectj-autoproxy proxy-target-class="true" />
</beans>

필수 설정: <aop:aspectj-autoproxy> 태그는 반드시 포함되어야 합니다.

AOP 어노테이션 기반 Aspect 클래스

import org.apache.log4j.Logger;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class LogAopAction {

    /**
     * execution: 메서드 실행 조인 포인트 매칭
     * - 예: @Pointcut("execution(* com.example.service..*.*(..))")
     *   첫 번째 *: 모든 반환 타입, ..: 0개 이상, service..: 서비스 패키지 및 하위 패키지
     * - @Pointcut("within(com.example.service.*)")
     *   within: 특정 패키지 내 조인 포인트 제한
     * - @Pointcut("this(com.example.service.UserService)")
     *   this: AOP 프록시가 특정 타입의 인스턴스인 경우
     * - @Pointcut("bean(userService)")
     *   bean: IoC 컨테이너 내 특정 빈 이름 지정
     * - @within(org.springframework.stereotype.Service): @Service 어노테이션이 있는 클래스의 모든 메서드
     * - @annotation(org.springframework.web.bind.annotation.RequestMapping): @RequestMapping 어노테이션이 있는 메서드
     */
    @Pointcut("execution(* com.cn.xxx..*.*(..))") // 지정된 패키지의 모든 메서드 매칭
    private void pointCut() {}

    /**
     * 메서드 실행 전
     */
    @Before("pointCut()")
    public void doBefore(JoinPoint joinPoint) {
        Class<?> clazz = joinPoint.getTarget().getClass();
        String className = clazz.getSimpleName();
        String methodName = joinPoint.getSignature().getName();
        Logger logger = Logger.getLogger(clazz);
        Object[] params = joinPoint.getArgs();
        
        StringBuilder logMessage = new StringBuilder(
            "메서드 시작: " + clazz.getPackage().getName() + "." + className + "." + methodName + ", 인자: ");
        for (Object param : params) {
            logMessage.append(param).append(", ");
        }
        logger.info(logMessage.toString());
    }

    /**
     * 메서드 정상 종료 후
     */
    @AfterReturning(pointcut = "pointCut()", returning = "result")
    public void doAfter(JoinPoint joinPoint, Object result) {
        Class<?> clazz = joinPoint.getTarget().getClass();
        String className = clazz.getSimpleName();
        String methodName = joinPoint.getSignature().getName();
        Logger logger = Logger.getLogger(clazz);
        logger.info("메서드 종료: " + className + "." + methodName + ", 결과: " + result);
    }

    /**
     * 메서드 예외 발생 시
     */
    @AfterThrowing(pointcut = "pointCut()", throwing = "ex")
    public void doAfterThrow(JoinPoint joinPoint, Throwable ex) {
        Class<?> clazz = joinPoint.getTarget().getClass();
        String className = clazz.getSimpleName();
        String methodName = joinPoint.getSignature().getName();
        Logger logger = Logger.getLogger(clazz);
        logger.error("메서드 예외: " + className + "." + methodName + ", 예외 메시지: ", ex);
    }

    /**
     * 메서드 전후 처리 (Around)
     */
    @Around("pointCut()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        try {
            return pjp.proceed();
        } catch (Exception e) {
            e.printStackTrace();
            throw e;
        }
    }
}

주의사항: 어노테이션 내 returningthrowing 속성 값은 메서드 파라미터 이름과 반드시 일치해야 합니다. 일치하지 않으면 IllegalArgumentException: error at ::0 formal unbound in pointcut 오류가 발생합니다. 예를 들어, 위 코드에서 returning = "result"는 파라미터 Object result와, throwing = "ex"는 파라미터 Throwable ex와 일치해야 합니다.

태그: Spring AOP aspectj 어노테이션 Pointcut IllegalArgumentException

7월 15일 16:25에 게시됨