java, spring

[Spring Boot] AOP - Aspect Oriented Programming

isaac.kim 2022. 3. 19. 14:42
728x90
반응형

[Spring Boot] AOP - Aspect Oriented Programming

 

목표

1. AOP 개념과 주요 용어

2. @Aspect 어노테이션

 

 

내용

1. Aspect Oriented Programming의 개념과 주요 용어를 파악한다.

2. 포인트 컷 문법을 이해하고 구현한다.

3. @Aspect 어노테이션을 이용해 다양한 Advice를 구현할 수 있다.

 

 

참고 내용

객체지향 기본원칙을 적용해서 핵심기능과 부가기능을 분리해서 모듈화 하는 것은 매우 어렵다. AOP 방법으로 상당 부분 해결할 수 있다.

 

스프링 AOP는 매우 강력한 기능이고 실제로 여러 프로젝트에서 거의 필수적으로 사용되고 있으며 적극적으로 사용함으로써 전체적인 애플리케이션의 품질을 높일 수 있다.

 

 

AOP의 개요와 용어

 

AOP (Aspect Oriented Programming)

 

■ 핵심기능과 부가기능

  • 업무(Biz) 로직을 포함하는 기능을 핵심 기능 (Core Concerns)
  • 핵심기능을 도와주는 부가적인 기능(로깅, 보안 등)
  • 부가기능 (Cross-cutting Concerns)
  • 핵심기능에서 부가기능을 분리해서 모듈화 하는 프로그래밍 기법 - AOP

로그를 남기거나, 핵심기능을 사용할 때 인증, 인가를 한다던가, 비즈니스 로직의 트랜잭션 처리를 하는 작업은 모든 핵심기능에 공통적으로 사용되는 기능으로 볼 수 있다. AOP는 비즈니스 로직을 나타내는 핵심기능을 작성할 때마다 공통적으로 작성하는 부가기능(로깅, 보안, 트랜잭션)을 분리해서 프로그래밍하는 기법이다. Java에서 AOP를 사용하기 위해 AspectJ라는 것을 사용한다고 하는데, 문법이 조금은 달라 더 편하게 사용할 수 있는 spring-aop를 사용하는 것을 권장한다고 한다.

 

핵심기능과 부가기능

핵심기능 관점에서 봤을 때 부가기능은 게시판 대상 서비스의 getBoards() 메서드나 유저 대상 서비스의 getUsers() 메서드의 호출되는 시점 앞, 뒤로 before(), after()라는 공통 메서드를 사용해 부가기능을 처리하는 것이다. 이러한 before, after를 Cross-Cutting Concerns이라 할 수 있다.

 

AOP 용어 정리

Point-Cut은 어디에 끼워 넣는지를 나타내는 문법이다.

Point-Cut과 Cross-Cutting Concern을 합쳐서 Aspect, spring-aop에서는 Advisor라고도 한다. (Aspect가 일반적)

Runtime 시에 Aspect를 끼워 넣는 행위를 weaving이라고 한다.

 

 

Spring Boot AOP 설정 3가지

1. Spring Boot Starter AOP 라이브러리 설정

디펜던시에 추가 (gradle 기준)

// https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-aop
implementation 'org.springframework.boot:spring-boot-starter-aop:2.6.4'

 

2. @EnableAspectJAutoProxy 적용

main 메서드를 갖는 Application 클래스에 어노테이션을 추가한다.

@EnableAspectJAutoProxy
@SpringBootApplication
public class ExApplication {
	public static void main(String[] args) {
		SpringApplication.run(ExApplication.class, args);
	}
}

 

3. 공통기능 정의와 공통기능 사용될 시점 정의 (Advice or Aspect)

  • @Aspect, @Component라는 어노테이션을 함께 붙여준다.
  • Advice 어노테이션 @Before, @After, @Around 등의 어노테이션을 메서드 단위에 붙여준다.
  • Advice 어노테이션에는 Point-cut 문법으로 부가기능을 적용할 대상을 설정한다.
  • 부가기능의 적용 대상은 메서드 단위이다.
  • 실제 부가기능을 수행하는 메서드를 Advice라고 부르기도 한다.
  • Advice 어노테이션, Point-cut 문법, Advice 메서드를 통틀어 Aspect라고 한다.

UserStudyController의 모든 메서드를 호출하기 전에 testAOP 메서드를 호출하는 부가기능이다. 즉, Aspect이다.

 

 

AOP 보완 및 부연 설명

Aspect 대상 클래스의 Advice 메서드(setLog, logger)에 point-cut 문법으로 aop의 대상 메서드를 규정한다. point-cut에 설정된 위치(weaving 할 위치)의 메서드가 호출될 때 Aspect는 @Before, @Around과 같은 Advice 어노테이션을 기준으로 Aspect의 weaving 시점을 나타낸다. 실제 부가 기능으로는 Aspect의 어노테이션이 설정된 Advice 메서드(setLog, logger)가 동작하게 된다.

 

어노테이션 @Around가 세팅된 Advice 메서드를 보면 Parameter로 ProceedingJoinPoint가 인자로 사용된다. 중간에 Object result = pjp.proceed(); 를 기준으로 위 코드는 Before 시점에 수행하고, 아래 코드는 After 시점에 수행한다. return result를 했을 때 @Around로 동작한다고 한다.

 

※ AspectJ와 달리 spring-aop에서는 메서드 단위의 설정이 가능하고, 핵심 기능 메서드가 사용될 때 weaving 시점을 정할 수 있다.

 

 

Advice 종류

@Before (이전)

어드바이스 타깃 메서드가 호출되기 전에 어드바이스 기능을 수행

 

@After (이후)

타깃 메서드의 결과에 관계없이(즉 성공, 예외 관계없이) 타깃 메서드가 완료되면 어드바이스 기능을 수행

 

@AfterReturning (정상적 반환 이후)

타깃 메서드가 성공적으로 결괏값을 반환 후에 어드바이스 기능을 수행

 

@AfterThrowing (예외 발생 이후)

타깃 메서드가 수행 중 예외를 던지게 되면 어드바이스 기능을 수행

Spring Boot에서 @AfterThrowing을 사용해서 aop 예외처리 기능으로도 사용한다.

 

@Around (메서드 실행 전후)

어드바이스가 타깃 메서드를 감싸서 타깃 메서드 호출 전과 후에 어드바이스 기능을 수행

 

스프링 AOP 구조

@Before("포인트 컷 표현식")
public void 어드바이스메소드() {
	....
}

 

Point-cut 문법 설명 - 포인트 컷 표현식

@Around("execution (* com.project.blog.service.UserService.*(..))")

@Around : 어드바이스 어노테이션

execution : 포인트 컷 지정자

첫 번째 *은 return 타입을 나타낸다. *은 모든 리턴 타입을 대상으로 한다.

com.project.blog.service는 타깃이 되는 패키지 구조를 나타내고 그 뒤로는 클래스와 메서드의 위치를 가리킨다. 현재 위 표현식으로 볼 때 UserService 클래스의 모든 메서드에 @Around를 지정한다.

인자(argument) 타입(..)은 현재 모든 타입 인자를 허용한다.

 

UserService 클래스의 모든 메서드(*)에 @Around Advice 메서드를 적용한다.

 

@Before("execution(* com.project.blog.service.*.get*(..))") // point-cut

해당 패키지 위치의 모든 클래스에 get으로 시작하는 모든 메서드에 대해서 포인트 컷 @Before 시점에 Advice 메서드를 weaving 시킨다.

 

 

 

어노테이션을 이용한 AOP

Point-cut으로 aop 설정하는 방식이 아닌, 어노테이션을 만들어서 aop를 설정하는 방법도 있다. 특정한 메서드에 대해서만 aop를 적용하고자 할 때 point-cut으로 설정하기 난해한 경우에 사용하기 용이하다.

 

어노테이션 작성

package com.project.blog.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface TokenRequired {
    
}

TokenRequired 어노테이션을 생성한다.

 

 

토큰을 이용한 어드바이스

@Aspect
@Component
public class SecurityAspect {

    @Before("@annotation(tokenRequired)")
    public void tokenRequiredWithAnnotation(TokenRequired tokenRequired)
            throws Throwable {
        System.out.println("Before tokenRequiredWithAnnotation");
    }
}

인자에 설정된 변수명(tokenRequired)을 Advice 어노테이션의 경로에 @annotation(tokenRequired)로 세팅해주면,

위에서 생성한 어노테이션(@TokenRequired)을 사용한 핵심 기능의 메서드를 호출할 때 weaving 하는 Aspect가 세팅된다.

 

■ 어노테이션을 이용한 Advice 적용

 

접속 URL이 http://localhost:8080/으로 들어오는 경우에 @TokenRequired를 걸어두었으니, 위에서 설정한 aop로 로그를 출력하는 것을 확인할 수 있을 것이다.

http://localhost:8080/ 호출 후 로그 확인


JWT인증을 AOP로 적용하기(응용 편)

Advice

실제 토큰이 인증되면 메서드를 실행하고, 그렇지 않으면 해당 메서드를 실행하지 않고 예외를 던지는 기능

package com.project.blog.advice;

import com.project.blog.annotation.TokenRequired;
import com.project.blog.service.SecurityServiceImpl;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import javax.xml.bind.DatatypeConverter;

@Aspect
@Component
public class SecurityAspect {

    // JWT인증을 AOP로 적용하기
    // TokenRequired 어노테이션이 필요한 경우의 Advice
    @Before("@annotation(tokenRequired)")
    public void tokenRequiredWithAnnotation(TokenRequired tokenRequired)
            throws Throwable {
        ServletRequestAttributes requestAttributes =
                (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
        HttpServletRequest request = requestAttributes.getRequest();

        // checks for token in request header
        String tokenInheader = request.getHeader("token");
        if(StringUtils.isEmpty(tokenInheader)) {
            throw  new IllegalArgumentException("Empty token");
        }
        Claims claime = Jwts.parser().setSigningKey(DatatypeConverter
                .parseBase64Binary(SecurityServiceImpl.secretKey))
                .parseClaimsJws(tokenInheader).getBody();
        if(claime == null || claime.getSubject() == null) {
            throw new IllegalArgumentException("Token Error : Claim is null");
        }
        if(!claime.getSubject().equalsIgnoreCase("kim")) {
            throw new IllegalArgumentException("Subject doesn't match in the token");
        }
        //System.out.println("Before tokenRequiredWithAnnotation");
    }
}

 

위에서 만든 코드(SecurityAspect)를 수정하여서 @TokenRequired 어노테이션을 사용하는 메서드에 대해서 위 해당하는 Aspect를 실행한다. 예외가 발생하면 예외를 던지고, 정상적인 경우 호출하는 핵심기능이 실행된다.

 

token 없는 요청

 

 

이전에 만든 토큰 생성 코드로 토큰을 생성한다.

 

header에 token을 담아서 @TokenRequired 어노테이션을 사용하는 메서드를 호출하면 다음과 같이 정상적인 return 메시지를 받을 수 있다. 

위에서는 "http://localhost:8080/" 요청에 대한 Controller의 메서드에 걸어두었고 해당 return 메시지는 내가 세팅한 것이므로 정상적으로 출력된 것을 확인할 수 있다.


Advice 종류 정리 (Advice Annotation)

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

 

 

정리

  • AOP는 핵심기능에서 부가기능을 분리해서 모듈화 하는 프로그래밍 기법을 말한다.
  • AOP는 핵심기능(Core Concerns)과 부가기능 (Cross Cutting Concerns)을 분리하여 개발할 수 있게 해 준다.
  • 부가기능에 해당하는 코드인 어드바이스와 적용한 위치를 문법적으로 표현한 포인트 컷을 합친 개념이 Aspect이다.
  • 스프링 AOP는 메서드 조인 포인트만 지원하여 간단하게 쉽게 AOP를 구현할 수 있다.
  • 포인트 컷의 문법을 이용하여 원하는 위치에 부가기능 어드바이스를 추가할 수 있다.
  • Advice는 타깃 객체 이전과 이후 그리고 이전/이후 모두에 적용 가능하다.

 


도움이 되셨다면 광고 꾹! 

감사합니다.^^

 

728x90
반응형

'java, spring' 카테고리의 다른 글

JPA 맛보기  (0) 2022.06.10
[Spring Boot] 로그와 예외처리  (0) 2022.03.19
[Spring Boot] RESTful 웹서비스 테스트  (0) 2022.03.17
[Spring Boot] REST API, CRUD 구현  (0) 2022.03.17
[SpringBoot] API Security and JWT  (0) 2022.03.15