Study/Spring

스프링 핵심기술 06. AOP

going.yoon 2022. 2. 13. 13:01

이제 스프링 AOP에 대해 알아보자.

AOP란 Aspect-Oriented Programming으로 흩어진 Aspect들을 모듈화하는 프로그래밍 기법을 말한다.

자바에서는 AspectJ스프링AOP를 통해 구현하며, 컴파일시점 / 로드타임 / 런타임에 AOP를 적용시킬 수 있다.

 

AOP 관련 개념이 잘 설명된 포스팅을 좀 찾아보았다.

 

https://tecoble.techcourse.co.kr/post/2021-06-25-aop-transaction/

 

AOP 입문자를 위한 개념 이해하기

이 글은 AOP 개념이 생소한 입문자들을 위한 포스팅입니다. 1. OOP의 한계 image…

tecoble.techcourse.co.kr

https://atoz-develop.tistory.com/entry/Spring-%EC%8A%A4%ED%94%84%EB%A7%81-AOP-%EA%B0%9C%EB%85%90-%EC%9D%B4%ED%95%B4-%EB%B0%8F-%EC%A0%81%EC%9A%A9-%EB%B0%A9%EB%B2%95

 

[Spring] 스프링 AOP 개념 이해 및 적용 방법

[Spring] 스프링 AOP 개념 이해 및 적용 방법 1. AOP(Aspect Oriented Programming) Spring은 Spring Triangle이라고 부르는 세 가지 개념을 제공해준다. 각각 IoC, AOP, PSA를 일컫는다. AOP는 Aspect Ori..

atoz-develop.tistory.com

https://sabarada.tistory.com/97?category=803157 

 

[Spring] Spring AOP - 원리편

안녕하세요. 오늘은 Spring AOP의 3번째 시간으로 마지막 시간입니다. 오늘은 AOP가 Spring 내부에서 구현되는 원리에 대해서 한번 알아보는 시간을 가져보도록 하겠습니다. AOP를 사용하는 방법 및 기

sabarada.tistory.com

 

 

스프링의 AOP란?

1. 프록시 기반의 AOP의 구현체이다.

2. 스프링 BEAN에만 적용할 수 있으며

3. 모든 AOP 기능을 제공하는 것이 아니라 가장 빈번하게 발생하는 문제에 대한 해결책을 제공하는 것이 목적이다.

 

프록시 패턴을 사용하는 이유는, 기존의 코드를 변경하지 않고 접근을 제어하거나 부가 기능을 추가하기 위함이다.

클라이언트가 interface로 프록시 객체에 접근을 하는데, 이 프록시 객체는 타겟 객체를 참조하고 있으며 실제 클라이언트의 요청을 처리하게 된다.

출처 - 인프런 백기선의 스프링 핵심원리

 

 

그렇다면 한번 예제를 통해 알아보자

간단한 Event클래스를 만들고 이 이벤트를 Create, Publish할 때는 수행시간을 로깅하고 Delete할 때는 로깅하지 않고 싶다.

//#1. EventService라는 인터페이스를 만든다.
public interface EventService {
    void createEvent();
    void publishEvent();
    void deleteEvent();
}

 

//#2. EventService 인터페이스를 상속받는 SimpleEventService 객체를 만든다.
// 여기엔 비지니스로직이 작성된다.
@Service
public class SimpleEventService implements EventService{
    @Override
    public void createEvent() {
        try{
            Thread.sleep(1000);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        System.out.println("Created an Event");
    }

    @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("Delete event");
    }
}

 

이 Service 객체 안에 create, publish 메소드에 각각 로그를 추가해줘도 되지만, 그렇게 하지 않고 Proxy 객체를 생성한다.

 

//#3. Proxy객체 생성.
@Primary // 같은 타입의 Bean이 있을 때 얘를 우선으로 선택
@Service // 얘도 Service Bean으로 등록
public class ProxySimpleEvent implements EventService{ 
    // #3.1 target객체(SimpleEventService)가 구현한 interface와 형 일치해야한다.

    @Autowired
    SimpleEventService simpleEventService; 
    // #3.2 타겟 객체를 주입받는다.

    @Override
    public void createEvent() {
        long begin = System.currentTimeMillis();
        simpleEventService.createEvent();
        // #3.3 타겟객체의 함수를 호출, 추가하고 싶은 기능을 추가한다.
        System.out.println("create 수행시간 : " + (System.currentTimeMillis() - begin));
    }

    @Override
    public void publishEvent() {
        long begin = System.currentTimeMillis();
        simpleEventService.publishEvent();
        System.out.println("create 수행시간 : " + (System.currentTimeMillis() - begin));
    }


    @Override
    public void deleteEvent(){
        simpleEventService.deleteEvent();
    }
}

코드 실행 결과

 

 

 

하지만 이렇게 구현하는 경우는, 매번 프록시 객체를 생성해줘야 하고 매번 타겟객체를 주입받고, 매번 타겟 객체의 메소드를 호출해야하는 번거로움이 존재한다. 스프링에서는 이를 간결하게 처리하기 위한 기능을 제공한다. 바로 AbstractAutoProxyCreator 인터페이스를 사용하면 되는데 이는 BeanPostProcessor를 상속받는다.

public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport
      implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware {

 

그러면 스프링에서 Annotation을 사용하여 AOP를 구현하는 방법을 코드로 알아보자. 이 방법에서 ProxySimpleEvent객체는 삭제한다.

// #1. Aspect를 구현해주는 객체를 하나 생성한다.
@Component // #1.1 Bean으로 등록되어있어야 하며
@Aspect // #1.2 especially Aspect로 등록이 되어야 한다.
public class PerfAspect {


    // #2. pointcut을 설정하는 여러가지 방법
    // #2.1 상대 경로를 입력해준다.
    //@Around("execution(* com.going..*.*(..))")

    // #2.2 PerfLogging이라는 애노테이션이 붙은 메소드에 적용시킨다.
    @Around("@annotation(PerfLogging)")
    public Object logPerf(ProceedingJoinPoint pjp) throws Throwable{
        long begin = System.currentTimeMillis();

        // #2.2.1 메소드 자체를 실행시켜주는 행위를 proceed라고 보면 됨.
        Object retVal = pjp.proceed();
        System.out.print("실행시간 : ");
        System.out.println(System.currentTimeMillis()-begin);
        return retVal;
    }

    // #2.3 simPleEventService Bean의 생성전에 pointcut을 설정
    @Before("bean(simpleEventService)")
    public void hello(){
        System.out.println("=====Start Event Service====");
    }
}

 

 


// #4. PefrLogging이라는 애노테이션을 만들어준다.
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS) 
// class 파일까지 이 애노테이션을 유지하겠다.
// source로 지정해주면 컴파일시 사라지고, Runtime으로 설정해주면 런타임까지 유효함. Class가 default
public @interface PerfLogging {
}


// #5. Service Bean에서 Logging을 하고 싶은 메소드에 에노테이션을 붙여준다.
@Service
public class SimpleEventService implements EventService{
    @Override
    @PerfLogging
    public void createEvent() {
        try{
            Thread.sleep(1000);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        System.out.println("Created an Event");
    }

    @Override
    @PerfLogging
    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("Delete event");
    }
}