본문 바로가기

Spring

[Spring] AOP

AOP 란?

AOP(Aspect-Oriented Programming)는 관점 지향 프로그래밍이라는 의미로 번역되는데, 객체지향에서 특정 비즈니스 로직에 걸림돌이 되는 공통 로직을 제거할 수 있는 방법을 제공해줍니다. AOP를 적용하면 기존의 코드에 첨삭 없이, 메서드의 호출 이전 혹은 이후에 필요한 로직을 수행하는 방법을 제공합니다. 정리하면 AOP가 추구하는 것은 관심사의 분리로 개발자가 염두에 두어야 하는 일들은 별도의 관심사로 분리하고 핵심 배즈니스 로직만을 작성하는 것을 권장합니다.

AOP 용어

용어 설명
aspect 구현하고자 하는 보조 기능
advice aspect의 실제 구현체(클래스). 메서드 호출을 기준으로 여러 지점에서 실행
joinpoint advice를 적용하는 지점을 의미. 스프링은 method 결합점만 제공한다. target 객체가 가진 메서드
pointcut advice가 적용되는 대상을 지정. 패키지이름/클래스이름/메서드이름을 정규식으로 지정하여 사용한다.
target advice가 적용되는 클래스
weaving advice를 주기능에 적용하는 것을 의미
proxy target에 대한 접근 방법을 제어하는 것으로 proxy는 내부적으로 target을 호출하지만 중간에 필요한 관심사들을 거쳐서 호출한다. 주로 스프링 AOP 기능을 이용해서 자동으로 생성되는 auto-proxy 방식을 이용

스프링 AOP 실습

실습은 IDE는 Intellij, 빌드툴은 gradle을 사용하여 진행하였습니다.

먼저 프로젝트를 생성하고 build.gradle 파일에 아래 dependency를 추가해줍니다. 

dependencies {
	...
	implementation 'org.springframework.boot:spring-boot-starter-aop'
}

다음으로 service 패키지를 생성하고 AOP 실습에 사용할 SampleService 인터페이스와 이를 구현한 SampleServiceImpl 클래스를 생성합니다. doAdd 메서드는 단순히 문자열을 변환해서 더하기 연산을 하는 작업입니다.

package org.zerock.service;

public interface SampleService {

    public Integer doAdd(String str1, String str2) throws Exception;

}
package org.zerock.service;

import org.springframework.stereotype.Service;

@Service
public class SampleServiceImpl implements SampleService {
    
    @Override
    public Integer doAdd(String str1, String str2) throws Exception {
        
        return Integer.parseInt(str1) + Integer.parseInt(str2);
        
    }
    
}

SampleServiceImpl에서는 이 클래스가 스프링에서 빈으로 사용될 수 있도록 @Service 어노테이션과 상속받고 있는 SampleService 인터페이스의 doAdd 메서드를 오버라이드(재정의)하겠다는 @Override 어노테이션을 선언해줍니다. 참고로 @Override를 사용하면 컴파일 시점에서 오버라이드가 제대로 됐는데 체크해줍니다.

 

이제 aop 패키지를 생성하고 기존의 서비스 로직에서 분리한 관심사를 구현한 Advice(LogAdvice)를 작성해줍니다.

package org.zerock.aop;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class LogAdvice {

    private static final Logger LOGGER = LoggerFactory.getLogger(LogAdvice.class);

    @Before( "execution(* org.zerock.service.SampleService*.*(..))")
    public void logBefore() {

        LOGGER.info("========================");
    }

}

LogAdvice 클래스가 Aspect를 구현한 것임을 나타내는 @Aspect 어노테이션과 빈으로 인식하기 위한 @Component 어노테이션을 추가해 줍니다.

@Before 어노테이션은 Advice와 관련된 어노테이션인데 내부적으로 Pointcut을 지정합니다. 그리고 @Before 어노테이션 내부 문자열은 AspectJ의 표현식(expression)으로 접근제한자와 특정 클래스의 메서드를 지정할 수 있습니다. 앞에 *은 접근제한자를 의미하고 뒤에 *은 클래스의 이름과 메서드의 이름을 의미합니다. 여기서 쓰여진 표현식의 의미는 모든 접근제한자를 포함하고 위 경로에서 SampleService라는 이름을 포함한 모든 클래스의 모든 메서드를 의미합니다.

어드바이스 관련 어노테이션

종류 설명
@Around 핵심 기능 수행 전과 후 (@Before + @After)
@Around 어노테이션은 선언한 메서드에 ProceedingJoinPoint를 파라미터로 받아 사용합니다.
@Before 핵심 기능 수행 전
@After 핵심 기능 수행 후 성공/실패 여부와 상관없이 반드시 동작 (try, catch 문의 finally와 비슷)
@AfterReturning 핵심 기능 수행 성공 시 (함수의 리턴값 사용가능)
@AfterThrowing 핵심 기능 수행 실패 시 (예외(Exception)가 발생한 경우)

이제 AOP가 잘 적용됐는지 테스트해보겠습니다.

package org.zerock.service;

import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class SampleServiceTests {

    private static final Logger LOGGER = LoggerFactory.getLogger(SampleServiceTests.class);

    @Autowired
    private SampleService service;

    @Test
    public void testClass() {

        LOGGER.info(String.valueOf(service));
        LOGGER.info(service.getClass().getName());

    }

    @Test
    public void testAdd() throws Exception {

        LOGGER.info(String.valueOf(service.doAdd("123", "456")));

    }

}

먼저 testClass() 테스트는 AOP 설정을 한 Target에 대해서 Proxy 객체가 정상적으로 만들어져 있는지를 확인하는 테스트입니다. 만약 정상적으로 생성됐다면 아래와 같이 $$EnhancerBySpringCGLIB 프록시 클래스로 출력될것입니다.

2022-09-29 21:41:08.203  INFO 13320 --- [           main] org.zerock.service.SampleServiceTests    : org.zerock.service.SampleServiceImpl@7ab2a07e
2022-09-29 21:41:08.204  INFO 13320 --- [           main] org.zerock.service.SampleServiceTests    : org.zerock.service.SampleServiceImpl$$EnhancerBySpringCGLIB$$d429555e

두번째 testAdd() 테스트는 AOP를 적용한 타겟 메서드를 실행했을때 원하는 대로 출력이 되는지 확인하는 테스트입니다. AOP가 잘 적용 됐다면 아래와 같이 출력될것입니다.

2022-09-29 21:41:08.152  INFO 13320 --- [           main] org.zerock.aop.LogAdvice                 : ========================
2022-09-29 21:41:08.180  INFO 13320 --- [           main] org.zerock.service.SampleServiceTests    : 579

실행결과