AOP란
AOP(Aspect-Oriented Programming)는 프로그래밍 패러다임의 하나로, 관심사를 분리하여 코드 의 모듈화를 개선하기 위한 기법입니다. 이를 통해 코드의 가독성과 유지 보수성을 높일 수 있습니다.
용어
- Advice: 부가기능을 수행할 메서드 생성
- Pointcut: 부가 기능을 적용할 핵식로직을 결정 (사용할 곳에 어노테이션)
- Aspect: 부가 기능과 해당 부가 기능을 어디에 적용할지 정의한 것 (클래스 생성 Advice + Pointcut) (클래스 파일)
- Joinpoint : 부가기능이 핵심로직 실행전에 실행될지 후에 실행될지를 결정
로그인 메서드 login() 가 있다면 이 메서드는 핵심로직이다.
로그인 메서드가 실행될 때 마다 로그를 남기고 싶다면, 로그를 남길 메서드를 만들어야 한다. 이것을 부가로직이라고 한다.
또는 DTO의 유효성검사 에러체크같은 기능 또한 부가로직이다. 이를 한 곳에 모아 처리할 수 있다.
부가로직에 pointcut을 적용하여 login() 메서드가 실행될때마다 발동하게 한다.
pointcut(login())
log(){
System.out.println("로그 실행됨"); }
다음으로 joinpoint를 통해서 언제 실행될지 결정한다.
Before
pointcut(login())
log(){
System.out.println("로그 실행됨"); }
하지만 위 방식으로 작성하게 되면 join메서드가 실핼될 때 로그를 남길수는 없다.
그래서 어노테이션을 만드는 것이 좋다.
@MyLog
login(){}
@MyLog
join(){}
After
pointcut(@MyLog)
log(){
System.out.println("로그 실행됨"); }
AOP 적용 방법
0. 의존성
implementation 'org.springframework.boot:spring-boot-starter-aop'
implementation 'org.springframework.boot:spring-boot-starter-validation'
1. 첫번째 방법
- 깃발(어노테이션)을 만들고 그 깃발을 pointcut으로 등록한다.
- Advice를 만든다. (메서드행위)
- joinpoint를 적용한다.
package shop.mtcoding.aopstudy.config.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Hello {
}
package shop.mtcoding.aopstudy.config.advice;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class HelloAdvice {
// 포인트 컷을 어노테이션에 적용 @Pointcut("@annotation(shop.mtcoding.aopstudy.config.annotation.Hello)") public void hello(){}
// JoinPoint 적용
@Before("hello()")
public void helloAdvice(JoinPoint jp) throws Throwable {
Object[] args = jp.getArgs();
if(args.length < 1){
System.out.println("아무개님 안녕"); }
else{
for (Object arg : args) {
if(arg instanceof String){
String username = (String) arg; System.out.println(username+"님 안녕");
} }
} }
}
2. 두번째 방법
- 이미 만들어져 있는 어노테이션에 pointcut 등록한다.
- Advice를 만든다.
- joinpoint를 적용한다.
package shop.mtcoding.aopstudy.config.advice;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindingResult;
import org.springframework.validation.Errors;+
import org.springframework.validation.FieldError;
import shop.mtcoding.aopstudy.config.exception.MyValidationException;
import java.util.HashMap;
import java.util.Map;
@Aspect
@Component
public class ValidAdvice {
@Pointcut("@annotation(org.springframework.web.bind.annotation.PostMapping)")
public void postMapping() {
}
@Pointcut("@annotation(org.springframework.web.bind.annotation.PutMapping)")
public void putMapping() {
}
@Before("postMapping() || putMapping()")
public void validationAdvice(JoinPoint jp) throws Throwable {
Object[] args = jp.getArgs();
for (Object arg : args) {
if (arg instanceof BindingResult) {
Errors errors = (Errors) arg;
if (errors.hasErrors()) {
Map<String, String> errorMap = new HashMap<>();
for (FieldError error : errors.getFieldErrors()) {
errorMap.put(error.getField(),
error.getDefaultMessage());
}
throw new MyValidationException(errorMap);.
}
}
}
}
}
3. 세번째 방법
깃발을 생성하지 않고, 특정한 패턴이 수행될 때 정의된 Advice를 실행하게 할수도 있다.
Spring 프레임워크에서는 XML을 이용해서 AOP를 설정하는 방법이 있다.
하지만 스프링부트에서는 주로 어노테이션 기반의 AOP를 사용하며, execution expression을 이용하여 pointcut을 등록할 수 있다.
하지만 직접적으로 execution expression을 이용하여 pointcut을 등록하려면 여러가지 설정을 해주어야 한다.
대신 스프링부트에서는 Aspectj를 사용하여 간편하게 pointcut을 등록할 수 있는 기능을 제공한다. Aspectj는 java와 비슷한 문법을 가지며, execution expression을 이용하여 pointcut을 등록하는 방법이 있다.
- execution(* onj.spring.aop..(..)) : onj.spring.aop 패키지의 모든 메소드가 포인트 컷
- execution(* onj.spring.aop...(..)) : onj.spring.aop 패키지와 하위 패키지의 모든 메소드가 포인트 컷
- execution(public void insert*(..)) : public에 리턴값, 패키지명 없고 메서드 이름은 insert로 시작, 인자값은 0개 이상인 메서드가 포인트 컷
- execution(public * *(..)) : public 메소드가 포인트 컷
- execution(* onj.spring.aop..()) : 리턴형 관계없고 onj.spring.aop 패키지의 모든 클래스, 인자값이 없는 모든 메서드가 포인트 컷
- execution(* onj.spring.aop...(..)) : 리턴형 관계없고 onj.spring.aop 패키지 및 하위 패키지에 있는 모든 클래스, 인자값이 0개 이상인 메서드가 포인트 컷
- execution(* delete()) : 메서드 이름이 delete으로 시작하는 인자값이 1개인 메서드가 포인트 컷
- execution(* delete(,*)) : 메서드 이름이 delete로 시작하는 인자값이 2개인 메서드가 포인트 컷
특정 어노테이션의 파라미터로 있는 메소드를 pointcut으로 잡으려면, execution expression을 이용하여 어노테이션이 적용된 메소드를 대상으로 pointcut을 작성하면 된다. 다만, 이 경우에는 @annotation 대신에 @args 키워드를 사용해야 한다.
*정규표현식을 사용해서 어노테이션 찾고 값 주입하기
package shop.mtcoding.aopstudy.config.advice;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class LogAdvice {
@Around("execution(* shop.mtcoding.aopstudy.controller.UserController.*
(..))")
public Object logAdvice(ProceedingJoinPoint jp) throws Throwable { Object result = jp.proceed(); System.out.println(result+"리턴됨");
return result;
} }
AOP 정리
AOP에서 Before, After, Around는 각각 다른 시점에서 AOP 어드바이스를 실행하는 방법이다.
1. @Before : 메서드 실행 전에 어드바이스를 실행합니다. 이 시점에서 JoinPoint 객체를 통해 메 서드의 인자, 클래스, 메서드 이름 등의 정보를 가져올 수 있습니다. @Before 어노테이션을 사용하여 선언합니다.
@Before("execution(* com.example.myapp.service.MyService.*(..))")
public void beforeAdvice(JoinPoint joinPoint) {
System.out.println("Before Advice: " + joinPoint.getSignature().getName());
}
위의 예제에서는 @Before 어노테이션을 사용하여 com.example.myapp.service.MyService 클래스 의 모든 메서드에 적용될 어드바이스를 작성했습니다. 메서드 실행 전에 해당 메서드의 시그니처 정보를 출력하는 기능을 수행합니다.
2. @After : 메서드 실행 후에 어드바이스를 실행합니다. 이 시점에서 JoinPoint 객체를 사용할 수 있으며, 메서드의 실행 결과나 예외 정보 등을 가져올 수 있습니다. @After 어노테이션을 사용하여 선언합니다.
@After("execution(* com.example.myapp.service.MyService.*(..))")
public void afterAdvice(JoinPoint joinPoint) {
System.out.println("After Advice: " + joinPoint.getSignature().getName());
}
위의 예제에서는 @After 어노테이션을 사용하여 com.example.myapp.service.MyService 클래스의 모든 메서드에 적용될 어드바이스를 작성했습니다. 메서드 실행 후 해당 메서드의 시그니처 정보를 출력 하는 기능을 수행합니다.
3. @Around : 메서드 실행 전후 모두 어드바이스를 실행합니다. 이 시점에서 ProceedingJoinPoint 객체를 사용하여 메서드 실행을 수행하고, 실행 결과를 반환할 수 있습 니다. @Around 어노테이션을 사용하여 선언하며, 메서드 실행 전후에 추가적인 로직을 수행 할 수 있습니다.
@Around("execution(* com.example.myapp.service.MyService.*(..))")
public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("Before Advice: " + joinPoint.getSignature().getName());
Object result = joinPoint.proceed();
System.out.println("After Advice: " + joinPoint.getSignature().getName());
return result;
}
위의 예제에서는 @Around 어노테이션을 사용하여 com.example.myapp.service.MyService 클래스 의 모든 메서드에 적용될 어드바이스를 작성했습니다. 메서드 실행 전후에 해당 메서드의 시그니처 정보 를 출력하고, 메서드 실행을 수행한 후 실행 결과를 반환합니다.
즉, @Before 는 메서드 실행 전에 추가적인 로직을 수행하는 어드바이스이고, @After 는 메서드 실행 후에 추가적인 로직을 수행하는 어드바이스입니다. @Around 는 메서드 실행 전후에 추가적인 로직을 수행할 수 있는 가장 범용적인 어드바이스입니다.
AOP에서 메서드의 파라미터 값을 검사하거나 변경하는 작업을 수행할 때는 @Before 어노테이션 을 사용하고, 메서드 실행 시에 새로운 값을 주입하거나, 결과를 가공하는 작업을 수행할 때는
@Around 어노테이션을 사용하는 것이 일반적입니다.
'I leaned > 스프링,스프링부트' 카테고리의 다른 글
Spring RestDoc 적용법 (0) | 2023.07.27 |
---|---|
@Transaction(readOnly=true) (0) | 2023.07.25 |
컨트롤러 단위 테스트 (0) | 2023.07.17 |
리포지토리 단위 테스트 (0) | 2023.07.17 |
Open In View (0) | 2023.07.17 |