AOP와 내부 메소드 이슈

qpwoeiru·2024년 11월 23일
0
post-thumbnail

그룹에서 멤버가 탈퇴하면 랭킹 변동 사항을 반영하기 위해 AOP로 이를 처리하고자 했다.
내가 의도한 메소드 순서는 아래와 같다.

이제 보니 이름 참 구리게 지은 것 같다

deleteGroup()deleteMember() 메소드가 공통적으로 deleteMemberFromStudyGroup() 를 사용하고, 이 두 메소드가 종료되면 랭킹을 업데이트 해야한다. 그래서 deleteMemberFromStudyGroup() 이 완전히 종료 됐을 때 AOP로 updateRanking() 을 수행하고자 했다.

하지만, 생각처럼 AOP가 수행되지 않았다. 로그를 찍어보니 deleteMemberFromStudyGroup() 메소드가 완료 되고도 AOP 수행 자체가 되지 않았다. 원인이 도저히 뭔지 모르겠어서.. 찾아보니 내부 메소드 호출이 문제였다.


문제 원인

프록시 객체는 원래 객체를 감싸고 있는 객체이다. 보통 접근을 제어하고 싶거나, 부가 기능을 추가하고 싶을 때 사용한다.

이번 이슈의 발생 원인은 프록시 객체의 내부 메서드를 호출하는 것이다.

AOP를 작동하려면 프록시를 무조건 거쳐야 한다.

AOP를 적용하게 되면 클라이언트프록시메서드 순서로 접근하는데, 이 때 메서드에서 다른 내부 메서드를 호출하게 되면 클라이언트프록시외부 메서드내부 메서드 순서가 된다. 그래서 AOP의 pointcut을 내부 메서드로 지정하면 프록시가 이를 감지할 수 없게 되는 것이다. 외부 메서드에서 내부 메서드 호출은 this.inner() 로 호출되기 때문이다. this 를 사용한 호출이 발생하는 경우, 프록시가 적용되지 않은 내부 객체를 호출하게 되는 것이다.


해결책

1. 자기자신 주입

@Service
public class Service{
	private final Service service;

	public void setService(Service service1){
		this.service = service;
	}

	public void deleteMember(){ // 외부 메서드
		// 내부 메서드 호출
		service.deleteMemberFromStudyGroup();
	}

	public void deleteMemberFromStudyGroup(){ // 내부 메서드
		...
	}
}

자기 자신을 주입해서 내부 메서드를 호출하는 방식이다.

하지만 현재 사용중인 스프링 3.X 버전에서는 자기 자신을 생성함과 동시에 주입하면 순환 참조 에러가 발생한다. spring.main.allow-circular-references=true로 설정하면 이를 허용할 수 있다는데, 이 기능 하나 때문에 순환 참조에 대한 위험성을 얹어가기엔 과도 하다고 생각했다. 그래서 사용하지 않았다.

2. 지연 조회

ObjectProvider 또는 ApplicationContext 를 사용해 조회하는 방법이다. 둘 다 Bean을 조회 해 가져오는 원리는 동일하다.

ObjectProvider 는 스프링 컨테이너에 등록된 Bean을 직접 조회하고 조회된 해당 Bean을 통해 메서드를 호출하는 원리이다.

@Service
public class Service{
	private final ObjectProvider<Service> serviceProvider;

	public void setService(ObjectProvider<Service> serviceProvider){
		this.serviceProvider = serviceProvider;
	}

	public void deleteMember(){ // 외부 메서드
			// ObjectProvider 사용한 내부 메서드 호출
			serviceProvider.getObject().deleteMemberFromStudyGroup();
	}

	public void deleteMemberFromStudyGroup(){ // 내부 메서드
			...
	}
}

deleteMember() 이 호출되는 시점에 Bean을 조회하고, Bean에서 찾은 Service 의 메서드를 호출하는 것이다. 근본적인 해결 원리는 1번과 비슷하다.

ApplicationContext를 사용한 예시는 아래와 같다.

@Service
public class Service{
	private final ApplicationContext applicationContext;

	public void deleteMember(){ // 외부 메서드
		// ApplicationContext를 사용한 Bean 조회 후 호출
		Service service = applicationContext.getBean(Service.class);
		service.deleteMemberFromStudyGroup();
	}

	public void deleteMemberFromStudyGroup(){ // 내부 메서드
		...
	}
}

사실상 가장 좋은 방법은 AOP를 정상적으로 적용할 수 있도록 deleteMemberFromStudyGroup() 메소드를 다른 클래스로 옮기는 것이다. 프록시 객체를 사용할 수 있도록 구조를 변경하는 것이 가장 좋은 방법인데, 현재 나의 케이스에서는.. 저 메소드 하나를 위해 클래스를 분리하기엔 오버 엔지니어링이라고 생각이 들어 2번 지연 조회 방법을 채택해서 사용했다.


원인을 찾는게 꽤 걸렸는데 AOP 원리를 제대로 알지 못했던 것 같다. AOP를 완벽히 이해하지 못하면 한 번쯤 겪어볼만 한 이슈일 것 같다..


참고
https://harrislee.tistory.com/100

0개의 댓글

관련 채용 정보