Spring 프록시 기반 AOP
Spring 프록시 기반 AOP
백기선님의 강의인 Spring 프레임워크 핵심 기술 편의 AOP
를 학습한 내용을 정리한 글
스프링 AOP 특징
Spring AOP는 프록시 기반의 AOP 구현체를 사용하고 있다.
또한 Spring Bean에만 AOP를 적용할 수 있다.
모든 AOP 기능을 제공하는 것이 아닌 Spring IoC와 연동하여 엔터프라이즈 애플리케이션에서 가장 흔한 문제에 대한 해결책을 제공하는 것이 목적
Spring은 Proxy 패턴으로 AOP를 구현하고 있다.
Proxy 객체는 Target 객체를 갖고 있다.
왜 이 패턴을 썼을까?
접근 제어와 부가 기능 추가를 위함이다.
직접 코드를 보면서 이해를 해보자
public interface EventService{
void createEvent();
void publishEvent();
}
@Service
public class SimpleEventService implements EventService{
@Overrride
public void createEvent(){
try{
Thread.sleep(1000);
} catch(InterruptedException e){
e.printStackTrace();
}
System.out.println("Created an event");
}
@Overrride
public void publishEvent(){
try{
Thread.sleep(2000);
} catch(InterruptedException e){
e.printStackTrace();
}
System.out.println("Published an event");
}
}
//클라이언트 코드
@Component
public class AppRunner implements ApplicationRunner{
@Autowired
EventService eventService;
@Override
public void run(Application args) throws Exception{
eventService.createEvent();
eventService.publishedEvent();
}
}
위 코드는 createEvent 메소드 호출하고 나서 1초 후에 Created an event라는 메시지를 출력하고
publishedEvent 메소드를 호출하고 나서 2초 뒤에 Publised an event를 호출하게 될 것이다.
이때 위 메소드들의 수행시간을 계산하는 기능을 추가한다고 해보자.
@Service
public class SimpleEventService implements EventService{
@Overrride
public void createEvent(){
long begin = System.currentTimeMillis(); // 추가된 코드
try{
Thread.sleep(1000);
} catch(InterruptedException e){
e.printStackTrace();
}
System.out.println("Created an event");
System.out.println(System.currentTimeMillis() - begin); // 추가된 코드
}
@Overrride
public void publishEvent(){
long begin = System.currentTimeMillis(); // 추가된 코드
try{
Thread.sleep(2000);
} catch(InterruptedException e){
e.printStackTrace();
}
System.out.println("Published an event");
System.out.println(System.currentTimeMillis() - begin); // 추가된 코드
}
}
위의 추가된 코드라는 주석이 달린 코드들이 바로 Crosscutting Concerns
이다.
만약 여러개의 클래스에 매번 시간을 측정해야 한다면 일일이 저 추가된 코드들을 작성해주어야 한다.
꽤나 번거롭다.
또한 매번 측정할때 마다 기존의 코드에 시간을 측정하는 코드를 추가해주어야 한다.
저 추가된 코드를 넣지 않으면서 시간을 측정할 수 없을까?
이러한 문제를 해결하기 위해 AOP가 필요한 것이며 Spring AOP는 Proxy 패턴을 사용하여 문제를 해결하였다.
문제 해결을 위해 Proxy 객체를 만들자.
우선 시간 측정하는 코드를 모두 지워준다.
@Service
public class SimpleEventService implements EventService{
@Overrride
public void createEvent(){
// 삭제
try{
Thread.sleep(1000);
} catch(InterruptedException e){
e.printStackTrace();
}
System.out.println("Created an event");
// 삭제
}
@Overrride
public void publishEvent(){
// 삭제
try{
Thread.sleep(2000);
} catch(InterruptedException e){
e.printStackTrace();
}
System.out.println("Published an event");
// 삭제
}
}
@Primary // 동일 이름이 있다면 이 객체를 가장 우선해서 빈으로 만든다.
@Service
public class ProxySimpleEventService implements EventService{
@Autowired
SimpleEventService simpleEventService;
@Override
public void createEvent(){
long begin = System.currentTimeMillis();
simpleEventService.createEvent(); // 실제 기능은 위임
System.out.println(System.currentTimeMillis()-begin);
}
@Override
public void publishEvent(){
long begin = System.currentTimeMillis();
simpleEventService.publishedEvent(); // 실제 기능은 Real Subject에게 위임
System.out.println(System.currentTimeMillis()-begin);
}
}
Proxy가 Real Subject에게 실제 기능을 위임하고 반복되는 기능을 감쌈으로써 AOP를 구현하였다.
이 Proxy 클래스를 만듦으로써 문제가 깔끔하게 해결된 것 처럼 보인다. 그렇지않은가?
하지만 사실 그렇지않다.
Proxy 객체에 여전히 중복된 코드들이 들어가게 되며 Proxy 클래스를 만들어야하는 수고와 비용이 들게 된다.
만약 다른 클래스에도 AOP를 적용하려면 Proxy 클래스를 또 만들고 메소드를 만들고.. 이렇게 하는 것이 상당히 번거롭다고 느낄 것이다.
우리는 여기서 직접 Proxy 객체를 만들었지만 동적으로 프록시 객체를 만드는 방법이 존재한다.
여기서 말하는 동적이란 런타임에 생성된다는 것을 의미하고, 이러한 기술을 Dynamic Proxy
라고 부르며 Spring AOP는 이 방법을 사용한다.
이를 구현하는 방법은 AbstractAutoProxyCreator
클래스인데 이 클래스는 BeanPostProcessor
의 구현체이다.
AbstractAutoProxyCreator
는 특정 클래스가 빈으로 등록될 때 해당 Bean에 AOP를 적용하여 가공 후 빈으로 등록해준다.
AbstractAutoProxyCreator
를 통해 런타임에 Proxy 패턴을 구현할 수 있게 되었으며 Spring AOP는 이 방법을 사용하고 있다.
Reference
인프런 백기선님의 스프링 프레임워크 핵심 기술 강좌