개발기록
Spring AOP 내부 호출 문제 해결 방법 본문
Spring AOP(Aspect-Oriented Programming)는 횡단 관심사를 효과적으로 분리할 수 있는 강력한 기능을 제공합니다. 하지만 내부 메서드 호출(self-invocation) 시 AOP가 적용되지 않는 문제가 있습니다. 이 글에서는 이 문제의 원인과 다양한 해결 방법을 예제 코드와 함께 상세히 살펴보겠습니다.
## 문제 상황
먼저, 문제가 발생하는 상황을 코드로 살펴보겠습니다.
```java
@Service
public class UserService {
@Transactional
public void createUser(String username) {
System.out.println("Creating user: " + username);
// 사용자 생성 로직
}
public void registerUser(String username) {
System.out.println("Registering user");
this.createUser(username); // 내부 호출
}
}
@Aspect
@Component
public class LoggingAspect {
@Before("execution(* com.example.UserService.*(..))")
public void logMethodCall(JoinPoint joinPoint) {
System.out.println("Before method: " + joinPoint.getSignature().getName());
}
}
```
이 경우, `createUser` 메서드를 직접 호출할 때는 AOP가 적용되어 로깅이 수행되지만, `registerUser` 메서드 내에서 `createUser`를 호출할 때는 AOP가 적용되지 않습니다.
## 해결 방법
### 1. 자기 자신을 주입받아 사용
Spring의 `@Autowired` 어노테이션을 사용하여 자기 자신을 주입받아 사용하는 방법입니다.
```java
@Service
public class UserService {
@Autowired
private UserService self;
@Transactional
public void createUser(String username) {
System.out.println("Creating user: " + username);
}
public void registerUser(String username) {
System.out.println("Registering user");
self.createUser(username); // 프록시를 통한 호출
}
}
```
이 방법은 간단하지만, 순환 참조 문제가 발생할 수 있으므로 주의가 필요합니다.
### 2. 별도의 내부 클래스로 분리
관련 메서드를 별도의 내부 클래스로 분리하고, 이를 빈으로 등록하여 사용하는 방법입니다.
```java
@Service
public class UserService {
@Autowired
private UserCreator userCreator;
public void registerUser(String username) {
System.out.println("Registering user");
userCreator.createUser(username);
}
@Component
public static class UserCreator {
@Transactional
public void createUser(String username) {
System.out.println("Creating user: " + username);
}
}
}
```
이 방법은 코드를 더 모듈화할 수 있지만, 클래스 구조가 복잡해질 수 있습니다.
### 3. ApplicationContext를 이용해 프록시 객체 가져오기
Spring의 `ApplicationContext`를 사용하여 현재 객체의 프록시를 가져와 사용하는 방법입니다.
```java
@Service
public class UserService implements ApplicationContextAware {
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
@Transactional
public void createUser(String username) {
System.out.println("Creating user: " + username);
}
public void registerUser(String username) {
System.out.println("Registering user");
UserService proxy = applicationContext.getBean(UserService.class);
proxy.createUser(username);
}
}
```
이 방법은 `ApplicationContext`에 대한 의존성이 생기지만, 프록시를 직접 가져와 사용할 수 있습니다.
### 4. AspectJ를 사용한 컴파일 타임 위빙
AspectJ를 사용하여 컴파일 시점에 AOP를 적용하는 방법입니다. 이 방법을 사용하려면 프로젝트 설정을 변경해야 합니다.
```java
@Service
@Aspect
public class UserService {
@Transactional
public void createUser(String username) {
System.out.println("Creating user: " + username);
}
public void registerUser(String username) {
System.out.println("Registering user");
this.createUser(username); // AspectJ를 사용하면 이 호출에도 AOP가 적용됩니다.
}
@Before("execution(* com.example.UserService.*(..))")
public void logMethodCall(JoinPoint joinPoint) {
System.out.println("Before method: " + joinPoint.getSignature().getName());
}
}
```
AspectJ를 사용하려면 `build.gradle` 또는 `pom.xml`에 AspectJ 관련 설정을 추가해야 합니다.
## 결론
Spring AOP의 내부 호출 문제는 프록시 기반 AOP의 한계로 인해 발생합니다. 위에서 소개한 방법들은 각각 장단점이 있으므로, 프로젝트의 요구사항과 구조에 맞는 방법을 선택하여 사용하는 것이 좋습니다.
어떤 방법을 선택하든, AOP의 동작 원리를 이해하고 적절히 사용하는 것이 중요합니다. 또한, 단위 테스트를 통해 AOP가 의도대로 동작하는지 반드시 확인해야 합니다.
'Spring' 카테고리의 다른 글
| N+1 문제와 해결 방법: Kotlin과 Spring Data JPA (0) | 2024.09.13 |
|---|---|
| @Configuration은 어떻게 싱글톤 빈을 보장하는가? (0) | 2024.09.10 |
| Spring의 프록시 기반 트랜잭션 관리 (0) | 2024.09.09 |
| JPQL 벌크 연산을 통한 더티 체킹 우회 (0) | 2024.09.09 |
| JPA 더티 체킹의 성능 이슈와 최적화 전략 (0) | 2024.09.09 |