Spring AOP로 Controller 파라미터 자동 주입하기
Spring AOP를 활용하면, Controller 파라미터에 특정 값을 자동으로 주입하거나 메서드 실행 전·후에 공통 로직을 삽입하는 흐름을 직접 구현할 수 있습니다.
이번 글에서는 @CurrentUserId 라는 커스텀 어노테이션을 만들어, Controller 메서드에서 아래처럼 userId를 직접 받지 않아도 되도록 구성해보겠습니다.
@GetMapping("/recommendations")
public ResponseEntity<Map<String, Object>> getRecommendations(@CurrentUserId Long userId)
이 userId 값은 우리가 만든 AOP @Around 어드바이스가 자동으로 주입하게 됩니다.
프로젝트 초기

Spring Initializr에서 기본 Dependencies를 추가한 뒤 프로젝트를 생성한 후, pom.xml에 아래 의존성을 추가합니다.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<version>4.0.0-M2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
전체 흐름 (AOP Flow)
HTTP 요청
→ DispatcherServlet
→ Controller 메서드 호출 시도
→ Spring AOP 프록시 개입(@Around)
→ @CurrentUserId 파라미터 탐색
→ userId 추출 + 검증
→ 원래 Controller 메서드 실행
핵심은 Controller 메서드가 호출되기 전에 AOP가 먼저 실행되어 파라미터 검증과 값 주입을 먼저 처리한다는 점입니다.
Controller (Mock API)
우리가 만들어볼 AOP flow는 다음과 같습니다.
데이터베이스 없이 바로 테스트 가능한 간단한 Mock API를 구성했습니다.
@RestController
@RequestMapping("/api/v1/mock")
public class AopMockController {
private final List<User> mockList;
public AopMockController() {
this.mockList = createMockData();
}
@GetMapping("/recommendations")
public ResponseEntity<Map<String, Object>> getRecommendations(@CurrentUserId Long userId) {
Map<Long, String> userPreferredRegion = Map.of(
1L, "강남구",
2L, "홍대"
);
String preferredCity = userPreferredRegion.getOrDefault(userId, "강남구");
List<User> recommended = mockList.stream()
.filter(r -> r.getCity().equals(preferredCity))
.limit(3)
.toList();
Map<String, Object> response = new HashMap<>();
response.put("userId", userId);
response.put("recommendedCity", preferredCity);
return ResponseEntity.ok(response);
}
// Mock 데이터 생성
private List<User> createMockData() {
List<User> mockList = new ArrayList<>();
mockList.add(new User(1L, "강남구"));
mockList.add(new User(2L, "홍대"));
mockList.add(new User(3L, "이태원"));
mockList.add(new User(4L, "부산"));
mockList.add(new User(5L, "대구"));
return mockList;
}
}
@CurrentUserId 어노테이션 정의
메서드 파라미터에 붙일 전용 어노테이션입니다.
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface CurrentUserId {
}
Domain (User)
간단한 Mock 도메인 객체입니다.
@Getter
@Setter
@AllArgsConstructor
public class User {
private Long id;
private String city;
}
AOP @Around — 파라미터 자동 주입 핵심 로직
Controller 호출 전에 실행되는 AOP 구성입니다.
@Around("execution(* aop.test.aop.demo..controller.*.*(..))") 로 pointcut 은 Controller 패키지의 모든 메서드에서 적용됩니다.
@CurrentUserId 발견하면 userId 를 추출합니다. 그리고 userId 가 1L인지 검증하는 로직을 수행합니다.
@Aspect
@Component
@Order(1)
public class CurrentUserAspect {
@Around("execution(* aop.test.aop.demo..controller.*.*(..))")
public Object injectCurrentUserId(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
Parameter[] parameters = method.getParameters();
Object[] args = joinPoint.getArgs();
for (int i = 0; i < parameters.length; i++) {
if (parameters[i].isAnnotationPresent(CurrentUserId.class)) {
Long userId = extractUserIdFromRequest();
validateUserId(userId);
args[i] = userId;
System.out.println("[AOP] @CurrentUserId → " + userId + " 주입됨");
}
}
return joinPoint.proceed(args);
}
요청에서 userId 추출하기
private Long extractUserIdFromRequest() {
ServletRequestAttributes attributes =
(ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (attributes == null) return null;
HttpServletRequest request = attributes.getRequest();
String userIdParam = request.getParameter("userId");
if (userIdParam != null) {
return Long.parseLong(userIdParam);
}
return null;
}
userId 검증
private void validateUserId(Long userId) {
final Long VALID_USER_ID = 1L;
if (userId == null) {
throw new IllegalArgumentException("userId는 필수입니다. (?userId=1)");
}
if (!userId.equals(VALID_USER_ID)) {
throw new IllegalArgumentException("유효하지 않은 userId입니다.");
}
}
실행 결과
정상 케이스
GET /api/v1/mock/recommendations?userId=1
AOP가 userId를 인식 → 검증 성공 → Controller 실행

실패 케이스 (userId 2)
GET /api/v1/mock/recommendations?userId=2

구성
/src
├─ controller/
├─ aspect/
├─ annotation/
├─ domain/
└─ DemoApplication.java
pom.xml
README.md
'IT' 카테고리의 다른 글
| 바이브 코딩 잘 활용하기 (0) | 2025.12.25 |
|---|---|
| MongoDB Covered Index 이해하기 (0) | 2025.11.23 |
| [대규모 시스템 설계 기초] 10장. 알림 시스템 설계 (0) | 2025.11.09 |
| [대규모 시스템 설계 기초] 4장. 처리율 제한 장치의설계 - 3 (0) | 2025.11.02 |
| [대규모 시스템 설계 기초] 4장. 처리율 제한 장치의설계 - 2 (0) | 2025.10.19 |
댓글
이 글 공유하기
다른 글
-
바이브 코딩 잘 활용하기
바이브 코딩 잘 활용하기
2025.12.25 -
MongoDB Covered Index 이해하기
MongoDB Covered Index 이해하기
2025.11.23 -
[대규모 시스템 설계 기초] 10장. 알림 시스템 설계
[대규모 시스템 설계 기초] 10장. 알림 시스템 설계
2025.11.09 -
[대규모 시스템 설계 기초] 4장. 처리율 제한 장치의설계 - 3
[대규모 시스템 설계 기초] 4장. 처리율 제한 장치의설계 - 3
2025.11.02