1부에서 설명했듯이, Spring AOP는 runtime weaving으로 AOP를 구현하기 때문에 프록시를 만들어서 AOP를 구현한다.
스프링 빈을 생성할때 프록시도 생성하기 때문에 스프링 빈으로 등록이 되어 있어야지만 AOP를 쓸수 있음
그럼 프록시 패던은 써서 AOP를 구현하는 이유는?
기존 코드를 변경하지 않고 프록시에다가 코드를 추가함으로써 AOP를 구현 할 수 있기 때문에
여기서 잠깐 프록시 패턴 좀 보구 가실게여~
그림에서 보듯이 프록시 패턴은 interface를 상속받은 진짜 객체와 프록시 객체가 있는데, 진짜 객체는 로직을 구현하고 프록시 객체는 진짜 객체를 불러와서 사용한다. 음... 프록시 패턴으로 구현된 AOP를 한번 봅세당~
//인터페이스
public interface EventService {
void createEvent();
void publishEvent();
void deleteEvent();
}
//진짜 객체 - 실제 메서드의 로직이 구현된 객체임
@Component
public class SimpleEventService implements EventService{
@Override
public void createEvent() {
try{
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Event created");
}
@Override
public void publishEvent() {
try{
Thread.sleep(2000);
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.println("published an event");
}
@Override
public void deleteEvent(){
System.out.println("event deleted");
}
}
//프록시 객체 - 실제 로직은 진짜객체에서 불러와서 사용하고 부가적인 부분을 넣어줌
@Primary//빈주입할때 프록시 객체가 주입되도록 @primary어노테이션을 붙여줌
@Component
public class proxyEventService 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.publishEvent(); //진짜 객체에서 로직을 불러와서 사용
System.out.println(System.currentTimeMillis() - begin);//부가적인 기능 - 메서드 실행 기산을 측정
}
@Override
public void deleteEvent() {
simpleEventService.deleteEvent();
}
}
//클라이언트에서 사용할때
@Component
public class AppRunner implements ApplicationRunner {
@Autowired //이때 주입되는 객체는 프록시 객체이다
EventService eventService;
@Override
public void run(ApplicationArguments args) throws Exception {
eventService.createEvent();
eventService.publishEvent();
eventService.deleteEvent();
}
}
코드에서 보듯이 구현은 진자 객체에서 한다. 프록시는 부가적인 기능을 추가해서 AOP를 구현한다. 코드에서는 진짜 객체에 수정이 없이도 부가적인 기능을 추가 했다.
그건 바로 BeanPostProcessor를 상속받은 AbstractAutoProxyCreator로 프록시를 만들어서 구현한다. 이를 동적 프록시(Dynamic proxy)라구 한다.
Advice는 join point시에 적용할 동작을 정의하는 것임. Advice를 만들기 위해서는
1. 동작을 적용할 클래스를 만듬
2. 해당 클래스는 빈으로 등록되어 있어야 함
3. @Aspect어노테이션을 클래스에 붙여줌
4. Advice로 정의할 메서드에 어노테이션(e.g @Around, @Before 등등)
코드로 한번 살펴봅세당~
@Component
@Aspect
public class PerfAspect {
//@Around("execution(* com.example.*.EventService.*(..))")
//@Around("bean(simpleEventService)") //해당 빈의 모든 클레스에 적용 (bean id)
@Around(" @annotation(PerfLoggin)")
public Object logPerf(ProceedingJoinPoint pip) throws Throwable {
Long begin = System.currentTimeMillis();
Object retVal = pip.proceed();
System.out.println(System.currentTimeMillis() - begin);
return retVal;
}
}
@Component - 컴포넌트 스캔으로 빈으로 등록
@Aspect - 어노테이션을 클래스에 붙여줌
@Around()/@Before()/@After() - 등등 advice에 join point 를 지정할때 사용하는 어노테이션
3가지 방법이 있는데 보편적으로 가장 많이 쓰는 방법은 어노테이션을 이용해서 적용하는 방법이다
어노테이션 사용해서 적용하기
패키지 경로를 이용해서 정의하기
bean id를 이용해서 정의하기
1. 어노테이션으로 적용하기
1.1 어노테이션 만들기
1.2 어노테이션 적용하기
1.3 advice의 어노테이션에다가 사용할 어노테이션 정의하기 e.g. @Around("@Annotation(PerfLoggin)")
어노테이션 만들기
//예를들어 Runtime시에도 가지고 있고 싶다면 RetentionPolicy.RUNTIME
//소스상에서만 가지고 있고 .class 파일로 변환할땐 없애고 싶다면 RetentionPolicy.SOURCE
//런타임까지는 유지할 필요 없지만 .class파일에 변환시에도 가지고 있고 싶다면 RetentionPolicy.CLASS
@Retention(RetentionPolicy.CLASS) //어노테이션 정보를 어디까지 유지할지
@Target(ElementType.METHOD)//메서드에 적용할 거라면 ElementType.METHOD
public @interface PerfLoggin {
}
어노테이션 적용하기
@Component
public class SimpleEventService implements EventService{
@PerfLoggin
@Override
public void createEvent() {
try{
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Event created");
}
@PerfLoggin
@Override
public void publishEvent() {
try{
Thread.sleep(2000);
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.println("published an event");
}
}
advice의 어노테이션에다가 사용할 어노테이션 정의하기
@Component
@Aspect
public class PerfAspect {
@Around(" @annotation(PerfLoggin)") //<-요부분
public Object logPerf(ProceedingJoinPoint pip) throws Throwable {
Long begin = System.currentTimeMillis();
Object retVal = pip.proceed();
System.out.println(System.currentTimeMillis() - begin);
return retVal;
}
}
2. 패키지 루트로 정의하기
@Component
@Aspect
public class PerfAspect {
@Around("execution(* com.example.*.EventService.*(..))")
public Object logPerf(ProceedingJoinPoint pip) throws Throwable {
Long begin = System.currentTimeMillis();
Object retVal = pip.proceed();
System.out.println(System.currentTimeMillis() - begin);
return retVal;
}
}
3. bean id로 빈에 정의하기
@Component
@Aspect
public class PerfAspect {
@Around("bean(simpleEventService)") //해당 빈의 모든 클레스에 적용 (bean id)
public Object logPerf(ProceedingJoinPoint pip) throws Throwable {
Long begin = System.currentTimeMillis();
Object retVal = pip.proceed();
System.out.println(System.currentTimeMillis() - begin);
return retVal;
}
}