[Spring] AOP

sunnyboy·2025년 4월 10일

Spring

목록 보기
1/3

AOP를 왜 사용해야 할까

먼저 10분 테코톡에서 봤던 재미있는 에피소드를 언급하면서 시작하려고 한다.

어떤 주니어 개발자가 이런 업무 지시를 받았다.
“회원가입 하는 시간 좀 측정해서 로그로 남겨주세요.”

  • 기존의 DTO로부터 회원가입 정보를 받아 DB에 저장하는 메서드
public void join(JoinRequest joinRequest) {
		memberRepository.save(joinRequest.toMember());
}

  • 지시를 받고 작성한 새로운 메서드
public void join(JoinRequest joinRequest) {
		stopWatch stopWatch = new StopWatch();
		stopWatch.start();
		
		try{
				memberRepository.save(joinRequest.toMember());
		} finally {
				stopWatch.stop();
				log.info("join spent {} ms", stopWatch.getLastTaskTimeMillis());
		}
}

뚝딱뚝딱 만들어서 다음 날 갔더니

“너무 좋네요. 이제 우리 회사의 모든 서비스에 다 같은 형식으로 남겨주세요.”


근데 이 회사에는 1억 개의 서비스가 존재했고, 이 개발자는 퇴사했다.

코드를 좀 뜯어서 생각해보면, 실제 서비스를 제공할 때 필요없는 부분이 있다.

비즈니스 로직은 꼭 제공되어야 하지만,

시간 측정 로그 생성, 권한 체크, 트랜잭션 걸기 등등… 부가적인 부분은 그럴 필요가 없다.

이와 같은 부가적인 로직을 인프라 로직이라고 한다.

인프라 로직

  • 애플리케이션의 모든 영역에서 등장
  • 중복, 반복되는 코드 때문에 유지보수 어려움
  • 비즈니스 로직과 섞여서 구분도 힘들고 로직 자체를 이해하기도 어려움

횡단 관심사

인프라 로직의 중복이 횡으로 나타나기 때문에 횡단 관심사 라고 부른다.


AOP (Aspect Oriented Programming)

  • Aspect : 횡단 관심사
  • Advice : 부가 기능
    • Before, After, AfterReturning, AfterThrowing 등
  • Join Point : Advice를 어디에 적용할 것인가 ??
    • 메서드, 필드, 객체, 생성자 등
    • Spring 에서는 메서드에만 가능하다.
  • Pointcut : 어떤 Join Point에 Advice를 적용할 지 결정
    • execution(* com.example.service.*.*(..))
  • Weaving : Advice를 Join Point에 실제로 적용하는 행위
    • Spring에서는 Runtime 시점에만 자동으로 적용
  • Proxy : Advice를 적용하기 위해 생성된 가짜 객체
  • Target : 부가 기능을 부여할 대상
    • 회원가입, 로그인 등

예시 코드를 보면 좀 더 이해가 쉽다.

@Component
@Aspect
public class _Aspect {

    @Before("execution(* d_aop.b_autoproxy.*.*(..))")
    public void before(){
        System.out.println("출근 카드를 찍는다.");
    }
}
용어위 코드에서의 예
Aspect_Aspect 클래스 전체
Advicebefore() 메서드 (횡단 관심사: "출근 카드를 찍는다." 출력)
Pointcut"execution(* d_aop.b_autoproxy.*.*(..))" (어떤 메서드에 적용할지 정하는 표현식)
Join Pointd_aop.b_autoproxy 패키지 내 모든 메서드 실행 시점 (실제 Advice 적용 지점)
WeavingAdvice(before)를 Pointcut에 맞는 Join Point에 적용하는 작업
ProxySpring이 만들어주는 가짜 객체 (Advice를 끼워 넣기 위한 Wrapper 객체)

application-context.xml 은 무엇인가?

Spring 컨테이너에 등록할 Bean, DI, AOP 설정 등을 정의하는 설명서 같은 파일.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:context="http://www.springframework.org/schema/context"
  xmlns:aop="http://www.springframework.org/schema/aop"
  xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">

  <context:component-scan base-package="d_aop.b_autoproxy" />
  <aop:aspectj-autoproxy/>
</beans>

Component scan : <context:component-scan base-package="d_aop.b_autoproxy" />

  • 지정한 패키지 내 @Component가 붙은 모든 Class 를 Bean 으로 등록
  • @Controller : MVC 의 Controller
  • @Service : 비즈니스 로직 담당 클래스
  • @Repository : DAO 클래스

에는 기본적으로 @Component 를 포함하고 있음.


🔥컴포넌트 스캔을 하지 않으면?
매번 클래스마다 XML에 하나하나 Bean 으로 등록해야 하고

<bean id="harryPotter" class="a_regist.a_xml.bean.Book" /> 

뭐 클래스 이름 바뀌거나 하면 고치고
실수하면 에러나고 불편한 게 여간 많은 게 아님 !!


AOP 설정 : <aop:aspectj-autoproxy/>

  • @Aspect 어노테이션이 붙은 클래스를 자동으로 AOP 설정
  • 이거 없으면 어노테이션을 붙여도 아무런 일도 안 생김
@Component
@Aspect
public class _Aspect {

    @Before("execution(* d_aop.b_autoproxy.*.*(..))")
    public void before(){
        System.out.println("출근 카드를 찍는다.");
    }

    @After("execution(public * d_aop.b_autoproxy.*.*(..))")
    public void after(){
        System.out.println("집에 간다.");
    }

    @AfterThrowing(value = "execution(public * d_aop.b_autoproxy.*.*(..))", throwing = "ex")
    public void afterThrowing(Exception ex){
        System.out.println(ex.getMessage());
        System.out.println("쉬는 날이었다.");
    }

    @AfterReturning(pointcut = "execution(* d_aop.b_autoproxy.Man.*(..))", returning = "res")
    public void afterReturning(Object res){
        System.out.println(res);
    }
}

이렇게 작성한 파일을 Java 코드에서 이렇게 불러온다.

ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
            new String[]{"/aop/autoproxy/application-context.xml"});

@Before("execution(* d_aop.b_autoproxy.*.*(..))")

d_aop.b_autoproxy 패키지의 모든 클래스의 모든 메서드 실행 전에 before() 를 실행

package d_aop.b_autoproxy;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Run {

    public static void main(String[] args) {

        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
            new String[]{"/aop/autoproxy/application-context.xml"});

        Man man = context.getBean("man", Man.class);

        man.develop();
    }
}

Spring은 어떻게 작동할까?

  • <aop:aspectj-autoproxy/> 설정을 보고
    • AOP를 사용한다는 것을 Spring이 감지함
  • @Aspect 붙은 클래스(여기서는 _Aspect)를 찾아서
    • Advice/Pointcut 정보를 파악
  • 그 후 @Component된 대상 객체들(Man, Woman, Child)을 Proxy 객체로 감싸서 생성
  • Run.java에서 man.develop()을 호출하면
    • Proxy(Man).develop() 호출됨
    • 먼저 _Aspect.before() 실행됨
    • 그 다음 진짜 Man.develop() 실행됨
profile
Data Analysis, ML, Backend 이것저것 합니다

0개의 댓글