Home > Today I Learned > 2026_05_04~2026_05_08 > 2026-05-08 일지

2026-05-08 일지
web spring thymeleaf html

Spring AOP


관리가 쉬운 코드, 유지보수가 좋은 코드를 짜는 것이 중요, 코드의 재사용성을 높이고 중복도 최소화할 수 있는 방향으로 짜게 된다. 이를 위해 선언형 프로그래밍, 함수형 프로그래밍 등의 방법이 존재한다.

유지보수적 지향으로 프로그램을 짜게 되는 것이 있는데 그 중 하나가

관점지향 프로그래밍 Aspect object Programming이다.

어노테이션으로 관리를 하게되는데 이것의 원리가 관점지향 프로그래밍이다.

로깅에 관한 관리가 필요하다. 그 로깅에는 AOP가 들어갈 수 밖에 없다. 이것을 정확하게 이해를 할 필요가 있다.

관점지향 프로그래밍

커진 단위를 모듈단위로 구분을 하는데 레이어 단위로 구분을 하게 된다. mvc패턴을 사용하게 된다. 우리가 서블릿을 만들었을 때

이것을 보완하기 위해 나온것이 스프링, 스프링 부트이다. 4버전이라는 것은 새로운 방식을 받아드리고 업그레이드를 할 필요가 있다. 프로젝트가 복잡해졌을때 수행하는 것이 모듈화와 레귤러화이다.

이러한 모듈화로 해결하기 힘든 것이 반복되는 코드를 분리하는 것이다. 어떤 애플리케이션을 개발하던지 로깅을 할 필요가 있다. 시간과 같은 스톱워치를 포함한다. 웹에서는 요청이 들어왔을때 요청을 수행하고 내보내느 시점을 기록하게 된다.

요청이 튄다?는 경우가 존재하는데 여기서 30ms에서 줄어든다면 느려지는 것이다. 이러한 것을 기록하는 로깅을 하기 위해서는 어떤 것이든 로그를 만들 필요가 있다. 트랜잭션을 확인하는 것은 로깅이 중복된다. 여러 메서드이건 클래스이건 반복적으로 사용되어야 한다. 코드 여러곳에 퍼져있으면 누적이되고 유지보수성이 떨어지고 가독성이 떨어진다. 이를 위한 것이 관점지향 프로그래밍이다. 프로그램을 객체단위로 나누어서 나타낸다고 하면 프로그래밍 관점으로 분리를 하게된다.

관심사

하나의 프로그램에는 여러가지 관심사가 존재한다. 여러가지 관심사를 따로 만들어두고 적용하자어떤 기준으로 나눌 수 있을지 살펴봐야 와닿을 수가 있다. 관심사가 뭔지 살필 필요가 있다

aop1.png

횡단 관심사와 주 관심사가 있다.

SW개발에서는 하나의 프로그램이 다양한 기능을 수행할 수 있는데 이것을 두가지로 나누면 주관심사와 횡단 관심사이다.

주 관심사 : 주된 기능
예: POST → 게시글 POST → 게시글 COMMENT→댓글

횡단 관심사 같은 경우는

트랜잭션이 적용되어 있지 않은경우랑 트랜잭션에 관한 작업 정의되지 않은 경우 우리는 그에 관한 처리가 된다. 또한 주관심사가 다루는 비지니스 로직이 이루어지는 과정도 하나의 트랜잭션이 된다. 애플리케이션의 관점도 주문에 관한 작업, 주문 아이템에 관한 관점이 있었다고 하면 롤백이 되면 다시 원래대로 돌릴 필요가 있다.

트랜잭션의 경우 비지니스 로직도 적용이 되는데 비지니스 로직을 롤백할 경우 다른 웹에 이에 관한 내용을 뿌릴 필요가 있다. 이렇게 여러 도메인에서 사용되지만 공통으로 적용되는 로깅, 트랜잭션 관리, 예외처리, 보안과 같은 것이 횡단관심사가 될 수가 있다.

한마디로 말하자면 애플리케이션 공통으로 적용하는 것이 횡단 관심사이다.

보안검사, 트랜잭션, 로그 -> 횡단 관심사
비지니스 로직 -> 주관심사

AOP는 이를 분리하는 기능을 제공해준다.
AOP는 횡단 관심사를 코드에서 분리할 것을 제공해준다. 이를 관점지향 프로그래밍의 정의라고 볼 수 있다.

관심사가 뭔지 관심사를 분리할 줄 알아야 한다.

같은 물건을 관점을 다른 것으로 보고 있기 때문에 겹쳐 보이는 것이다. 객체지향 프로그래밍에서 자바에서 함수형 프로그래밍을 지원을 해주지만 엄밀히 말하면 아니긴하다. 함수형 인터페이스를 지원해주는 것이다. 자바는 객체지향 프로그래밍인데 주관심사를 클래스와 메서드로 나누어서 설계를 하게된다.

주관심사라는 친구는 가장 먼저 고려해야 되는 친구가 된다. AOP에서도 가장 먼저 보호를 해야한다. 횡단 관심사는 프로그램 여러곳에서 공통적으로 사용되지만 특정한 한 기능으로 속하지는 않는다. 전체 시스템에서 반복적으로 나타난다. 여기서 되는 것은 로깅과 트랜잭션이 필요하다. 공통적으로 나타나고 반드시 되어야 하는 것은 횡단 관심사가 된다.

이처럼 코드의 중복이 많이 일어나는 것을 분리하고 모으는 것도 AOP의 관점 중 하나이다.

관심사는 Concern이다. cutting은 자르는 뜻이 있다. Cross-Cutting Concerns
이 용어가 중요하다. 이 AOP에 관한 것을 어노테이션으로 제공하고 있다.

Aspect

본질적인 작업과 부가적인 작업을 나누어서 생각할 수가 있다. 요리를 할 것이면 요리를 할 때 요리 자체가 핵심 요리가 된다. 부가적인 작업을 하나로 모듈화 해놓은 것이라고 봐도 된다. 반복적인 로직을 모아두고 특정시점에 한번에 시행되게 하는 것이다.

로그를 찍고 트랜잭션을 관리하고 하는 것을 하나의 Aspect라고 하고
특정시점을 지나면 이것을 진행하는 방식으로 진행된다. 핵심 로직은 건드리지 않고 로그, 트랜잭션, 권한 검사 같은 공통 기능을 분리해서 적용하기 위한 구조

spring에서는 aspect라는 어노테이션이 있다. 이 어노테이션을 통해 로직을 묶고 진행을 하게된다.

advice

advice는 aspect가 진행할 로직 aspect가 코드 뭉치이자 핵심개념이라고 보면 되고 이것을 실행하는 시점이 advice이다. Advice는 특정 시점에 실행되는 부가 기능 로직이라고도 생각해도 된다. afteradvice(@After), beforeadvice(@Before), afterThrowing(@AfterThrowing) 예외 발생한 후의 시점, advice는 aspect의 실행 시점이라고 봐도 된다.

간단한 Advice 종류 정리

  • Before Advice
    • 메서드 실행 전 동작
    • ex: 로그동작, 권한 검사
    • @Before
  • After Advice
    • 메서드 끝난 후 동작
    • 성공/실패 관계없이 동작
    • @After
  • After Returning Advice
    • 메서드가 정상적으로 종료된 후 실행
    • ex: 성공로그기록, 반환값처리
    • @AfterReturning
  • After Throwing Advice
    • 예외가 발생했을 때 실행
    • ex: 에러 로그 저장
    • @AfterThrowing
  • Around Advice
    • 메서드 실행 전후를 모두 제어가능
    • ex: 실행시간 측정, 트랜잭션 처리
    • @Around

PointCut

우리가 모든 서비스 클래스에 메서드를 나뭇가지라고 한다면 pointcut은 위치라고 봐도 된다. 어느 시점에 어느 위치에 있는 aspect를 실행시킬때 pointcut이 수행된다.
Spring AOP에서는 Pointcut을 지정할 때 주로 execution() 표현식을 사용한다.

@Before("execution(* com.example.service.*.*(..))")

이런 식으로 진행을 하게된다.

참고

이 안에서 spel (spring espect language)에 있는 표현언어와 DSL(Domain Specific Language)를 공부하는 것이 좋다.
spel을 공부하게 되면 dsl을 사용하고 이해하는 것이 좋아진다. 패키지 이름규칙, 어노테이션 이름규칙, 등 어떤대상의 이 aspect를 넣을 것인지를 정할 수가 있다.

target Object

Aop의 적용의 대상이 되는 object

프록시 패턴을 가지고 원본 패턴의 시작시점을 알 수가 있다. 상속을 이용한 방법, 리플렉션을 캐치해나가는 방면 이렇게 두가지 존재한다. 스프링에서는 상속방식이 쓰였으나 지금은 cglib라는 라이브러리를 통해서 동적 프록시를 제공하고 있다.

Join Point

aop가 실행될 수 있는 모든 시점, 지점이라고 보면 된다.
pointcut이랑 비슷한 개념, pointcut이 join point를 잡아주는 것

Weaving

뭔가를 꿰메다는 의미로 aspect를 aop에 적용시켜서 진행하는방법이다.
aop-weaving.png

프록시 패턴

스프링 AOP에서 관점이고 개념이다. 스프링에서는 이 AOP를 어떻게 구현했는가?
어떻게 이 코드를 변경하지 않고 가로채는데 성공할 수 있었나

public interface Service {
    void logic();
}
public class RealService implements Service {

    @Override
    public void logic() {
        System.out.println("실제 로직 수행");
    }
}
public class ProxyService implements Service {

    private final Service target;

    public ProxyService(Service target) {
        this.target = target;
    }

    @Override
    public void logic() {

        long start = System.currentTimeMillis();

        System.out.println("프록시: 로직 실행 전");

        target.logic();

        System.out.println("프록시: 로직 실행 후");

        long end = System.currentTimeMillis();

        System.out.println("실행 시간: " + (end - start));
    }
}

프록시패턴은 대리인이다. 대신 무언가를 하는 사람이다. 프록시 패턴은 원본패턴을 대신할 무언가를 준 다음에 수행하는 것이다. 일을 시켰는데 일을 대신하는 존재, 일을 연예인하게 맡기면 매니저가 수행하는것과 비슷하게 흘러간다.

프록시 객체에 일을 지정하면 실제 일은 실제 객체가 직접 수행한다.

리얼 액터는 실제로 해야 할 일을 여기서 구현을 하는 것, 실제 액터를 품고 있다. 똑같은 기능을 구현하는데 부가적으로 일을 수행한 다음 실제 객체에다가 넘겨주는 것, 이것을 라이브러리에 사용하는 것을 보면 여러가지로 실행됨을 볼 수가 있다.

JDK 동적 프록시, JDK Dynamic Proxy

프록시를 동적으로 만들 필요가 있다. AOP를 적용하여 로그를 남기는 기능을 추가하는 것을 추구하고 싶다면 동적으로 프록시 객체를 만들고 추가하는 방향으로 해주자. 스프링에서는 aop를 위해 CGLIB방식으로 전부다 바뀌었다.

cglib는 인터페이스가 필요가 없다. 또한 @entity는 파이널이 붙은 클래스는 쓸 수가 없다
final이 붙은 상수는 상속이 되지 않는다. 자바는 상수가 없다. 상수형 변수로는 자바에서 값의 재활동을 막아놓는다. final이 붙으면 CGLIB를 할 수 가 없다.