[Spring] 스프링 AOP 무작정 사용하기 🤩

스티치·2021년 5월 12일
0

무작정 따라하기

목록 보기
1/1

주저리

😃 안녕하세요! Undefind입니다.
이번에 취준진담을 개발하면서 Spring AOP를 처음으로 사용해 로깅을 해보았습니다.
개발을 진행하며 트랜잭션 로깅, 메로리 로깅 등을 사용하였던 느낌을 받아, 무작정 사용하기 시리즈를 포스팅으로 정하여 공부하고 실제로 리팩토링 하려고 합니다.

🤔 무슨 이야기를 할꺼냐

1. Spring의 장점 및 특징
2. Spring AOP의 개념 (어노테이션 정리)
3. Spring AOP의 특징
4. 개발하고 있는 어플리케이션의 간단한 AOP 예제

본 포스팅에선 Spring AOP의 중요성 부터, 왜 Spring AOP를 사용하는지 그리고 어떻게 무작정 사용하는지 등을 순서대로 말씀드리겠습니다!

1️⃣ Spring이 뭔데 쓰냐?

Spring Framework
우리는 Spring이라고 부르지만 "Spring Framework"라고 하는 것이 정확한 표현이다.
자바 엔터프라이즈 개발을 위한 "오픈 소스" 애플리케이션 프레임워크로 종속 객체를 생성해주고, 조립할 수 있는 프레임워크이다.

  • "경량 컨테이너"로서 자바 객체를 직접 관리한다. (경량 컨테이너)
  • 애플리케이션의 느슨한 결합을 촉진한다.(제어 역행)
  • 컨트롤의 제어권이 사용자가 아니라 프레임워크에있다. (제어 역행)
  • 각각의 계층이나 서비스들 간의 의존성이 존재할 경우 프레임워크가 서로 연결시켜준다. (DI)
  • 트랜잭션이나 로깅, 보안과 같이 여러 모듈에서 공통적으로 사용하는 기능의 경우 AOP를 지원한다. (AOP)

이 모든것을 이해하는 데에는 인프런 강의의 김영한씨의 Spring 기초 강의가 도움을 주었다.
꼭 결재해서 보는 것을 추천한다. 😏

2️⃣ Spring AOP를 모르면 범죄지 ^^7

AOP(Aspect Oriented Programming) 관점지향 프로그래밍
어떤 로직을 기준으로 핵심적인 관점, 부가적인 관점으로 나누어 보고 그 관점을 기준으로 각각 모듈화하여 개발하는 프로그래밍이다.

  • 핵심적인 관점 : 비즈니스 로직
  • 부가적인 관점 : 데이터베이스 연결, 로깅, 파일 입출력, 트랜잭션 등

다음과 같이 소스 코드 상에서 반복되는 코드들을 흩어진 관심사(Crosscutting Concerns)라고 부른다.

위와 같이 흩어진 관심사를 Aspect로 모듈화하고 핵심적인 비즈니스 로직에서 분리하여 재사용하겠다는 것이 AOP의 취지이다. (Spring AOP를 사용하는 목적)

Spring Boot의 AOP 어노테이션

다양한 어노테이션이 많다. 😭 될 수 있으면 다 이해하고 외우는 것이 좋다.

  • @Aspect : 흩어진 관심사를 모듈화 한 것. 주로 부가 기능을 모듈화한다.
  • @Target : Aspect를 적용하는 곳이다. (클래스, 메서드 .. )
  • @Advice : 실질적으로 어떤 일을 해야할 지에 대한 것, 실질적인 부가기능을 담은 구현체이다.
  • @JointPoint : Advice가 적용될 위치, 끼어들 수 있는 지점. 메서드 진입 지점, 생성자 호출 시점, 필드에서 값을 꺼내올 때 등 다양한 시점에 적용한다.
  • @PointCut : JointPoint의 상세한 스펙을 정의한 것. 'A란 메서드의 진입 시점에 호출할 것'과 같이 더욱 구체적으로 Advice가 실행될 지점을 정할 수 있다.

Aspect 실행 시점을 지정할 수 있는 어노테이션

  • @Before (이전) : 어드바이스 타겟 메소드가 호출되기 전에 어드바이스 기능을 수행
  • @After (이후) : 타겟 메소드의 결과에 관계없이(즉 성공, 예외 관계없이) 타겟 메소드가 완료 되면 어드바이스 기능을 수행
  • @AfterReturning (정상적 반환 이후) : 타겟 메소드가 성공적으로 결과값을 반환 후에 어드바이스 기능을 수행
  • @AfterThrowing (예외 발생 이후) : 타겟 메소드가 수행 중 예외를 던지게 되면 어드바이스 기능을 수행
  • @Around (메소드 실행 전후) : 어드바이스가 타겟 메소드를 감싸서 타겟 메소드 호출전과 후에 어드바이스 기능을 수행

모든 어노테이션은 한번 읽어보면 개발할 수 있다!. 글이 많다고 포기하지 말고 정독하기!! 😳

3️⃣ 왜 Spring AOP를 사용하는데??

프록시

  • 프록시 패턴 기반의 AOP 구현체, 프록시 객체를 쓰는 이유는 접근 제어 및 부가기능을 추가하기 위함이다.
  • 스프링 빈에만 AOP를 적용 가능하다.
  • 모든 AOP 기능을 제공하는 것이 아닌 스프링 IoC와 연동하여 엔터프라이즈 애플리케이션에서 가장 흔한 문제 (중복코드, 프록시 클래스 작성의 번거로움, 객체들 간 관계 복잡도 증가 ...) 에 대한 해결책을 지원하는 것이 목적이다.

4️⃣ 무작정 사용하기 🧐

이번 프로젝트에서는 Maven을 사용하여 프로젝트의 모듈 의존성을 관리하였다.
Spring AOP를 이용하여 함수의 시간 체크를 하려고한다.
함수 시간 체크는 개발에 있어서 아주 중요한 기능이다.
아주 기본적인 기능,,,,

Pom.xml에 AOP에 관한 모듈을 추가한다.

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

이렇게 모듈 의존성을 추가해주었다면 설정 끝이다. 개발 완료!! 🐱

커스텀 어노테이션을 만든다.

어노테이션으로 내가 원하는 패키지에 있는 함수만 실행하게 구성하였습니다.
Pointcut 어노테이션을 패키징으로 처리할 수 있습니다.
관련 포스팅 : 다양한 Pointcut

AOP 클래스를 만들어 타임 체크에 대한 기능을 구현한다.

@Aspect // ---------------------------------------- (1)
@Component  // ------------------------------------ (2)      
@Slf4j   // --------------------------------------- (3)  
public class TimeCheckAop {

    private long beforeTime;
    private long afterTime;

    @Pointcut("@annotation(com.gsm.chwijuntime.aop.timecheck.TimeCheck)") // -------- (4)
    public void TimeCheck() {}

    @Before("TimeCheck()")   // ------------- (5)
    public void before(JoinPoint joinPoint) {
        beforeTime = System.currentTimeMillis();
    }

    @AfterReturning(pointcut = "TimeCheck()", returning = "result")   // ---------- (6)
    public void AfterReturning(JoinPoint joinPoint, Object result) {
        afterTime = System.currentTimeMillis();
        log.info("코드 수행 시간(ms) : " + (afterTime - beforeTime) + "ms");
        log.info("코드 수행 시간(s) : " + ((afterTime - beforeTime)/1000.0) + "s");
        log.info("result = " + result);     // ------------- (7)
    }

}

(1) : 흩어진 관심사를 모듈화하기 위해 선언, 여기서는 시간 체크 클래스입니다.
(2) : Spring Bean에 등록하기 위해 선언한다.
(3) : log를 사용하기 위해 선언한다.
(4) : JointPoint의 상세한 스펙을 정의한 것. 'TimeCheck란 어노테이션의 진입 시점에 호출할 것'과 같이 더욱 구체적으로 실행 시점을 정하기 위해 선언한다.
(5) : 어드바이스 타겟 메소드가 호출되기 전에 어드바이스 기능을 수행, 여기서는 메소드가 실행할 시점에 시간을 저장한다.
(6) : 타겟 메소드가 성공적으로 결과값을 반환 후에 어드바이스 기능을 수행, 여기서는 메소드가 끝난 시점에 시간을 저장한다. (returning은 타겟 메소드의 리턴 값이 그대로 전달되어 (7) 라인에 리턴된 값이 출력된다.)

이제 AOP 클래스를 사용해보자!!! 😶

    @MemoryCheck @TimeCheck    // --------- (1)
    @Override
    public Member findMember(MemberLoginDto memberLoginDto) {
        Member member = memberRepository.findByMemberEmail(memberLoginDto.getMemberEmail()).orElseThrow(EmailNotFoundException::new);
        boolean check = passwordEncoder.matches(memberLoginDto.getMemberPassword(), member.getMemberPassword());
        if(!check) {
            throw new IncorrectPasswordException();
        }
        return member;
    }

(1) : 위에서 만든 커스텀 어노테이션이다. 해당 메소드를 실행하게 되면 AOP가 적용된다. Before 어노테이션으로 시작 시점을 저장하고 AfterReturning 어노테이션으로 종료 시점을 저장하여 메소드의 실행 시간을 구해준다. 참고로 Return된 값은 MemberLoginDto이다.

마치며 😳

이번 포스팅에선 Sprign AOP의 개념과, Spring AOP를 사용하여 메소드의 시간을 측정하는 것을 구현해 보았습니다. AOP는 부가 기능을 분리해 모듈화 함으로써 재사용이 가능한 코드를 만드는 것이 목적입니다.

성장 과정이 궁금하시다면 깃허브 팔로우 한번씩 부탁드릴께요 😃
다음 무작정 따라하기 주제는 무엇으로 할까요??? 기대 많이 해주세요 😏 쀼쀼

profile
DevOps 엔지니어

0개의 댓글