
[ 첫번째 방법 ]
1. 깃발(어노테이션)을 만들고
2. 그 깃발을 PointCut(별칭)으로 등록 한다.
3. Advice를 만든다. (메서드 행위)
4. PointCut을 Advice에 적용한다
예제
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(){}
// 매개변수에 접근해서 값을 찾는 것을 가능 - 값을 주입하려면 @Around 사용해야함
@Before("hello()")
public void helloAdvice(JoinPoint jp) throws Throwable {
Object[] args = jp.getArgs();
for (Object arg : args) {
if(arg instanceof String){
String username = (String) arg;
System.out.println(username+"님 안녕");
}
}
}
}
[ HelloController ]
package shop.mtcoding.aopstudy.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import shop.mtcoding.aopstudy.config.annotation.Hello;
@RestController
public class HelloController {
@GetMapping("/hello/v1")
public String v1(){
return "v1";
}
// http://localhost:8080/hello/v2?username=ssar
@Hello
@GetMapping("/hello/v2")
public String v2(String username){
System.out.println("username : 값 변경? : "+username);
return "v2";
}
}

포스트맨 돌리는 실습 직접 해보기!
[ 두번째 방법 ]
1. 이미 만들어져 있는 깃발을 PointCut(별칭)으로 등록 한다.
2. Advice를 만든다. (메서드 행위)
3. PointCut을 Advice에 적용한다.
4. postMapping과 putMapping에 관한 매개변수에 대한 값을 파싱
→ 맵에 던져주고 가공해서 MyValidationException의 위임
예제
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.FieldError;
import shop.mtcoding.aopstudy.config.exception.MyValidationException;
import java.util.HashMap;
import java.util.Map;
// Aspect로 동작하기 위한 클래스임을 선언하는 어노테이션
@Aspect
// Spring의 컴포넌트로 등록되도록 하는 어노테이션
@Component
public class ValidAdvice {
// Pointcut을 선언 @PostMapping 어노테이션이 지정된 메소드를 대상으로 함
@Pointcut("@annotation(org.springframework.web.bind.annotation.PostMapping)")
public void postMapping() {
}
// @PutMapping 어노테이션이 지정된 메소드를 대상으로 함
@Pointcut("@annotation(org.springframework.web.bind.annotation.PutMapping)")
public void putMapping() {
}
// 메소드의 매개변수 중에 BindingResult가 있는 경우, 해당 객체에 에러가 있는지 검사
// 에러가 있으면 에러 정보를 Map에 담아 오류를 던짐
@Before("postMapping() || putMapping()")
public void validationAdvice(JoinPoint jp) throws Throwable {
Object[] args = jp.getArgs();
for (Object arg : args) {
if (arg instanceof BindingResult) {
BindingResult bindingResult = (BindingResult) arg;
if (bindingResult.hasErrors()) {
Map<String, String> errorMap = new HashMap<>();
// BindingResult에 포함된 모든 FieldError를 순회하면서 에러 정보를 Map에 담기
for (FieldError error : bindingResult.getFieldErrors()) {
errorMap.put(error.getField(), error.getDefaultMessage());
}
// 발생한 에러 정보를 담은 Map과 함께 MyValidationException을 throw
throw new MyValidationException(errorMap);
}
}
}
}
[ 컨트롤러 ]
@PostMapping("/valid")
public String join(@Valid JoinInDto joinInDto, BindingResult bindingResult){
return "ok";
}
[ JoinDTO ]
package shop.mtcoding.aopstudy.dto;
import lombok.Getter;
import lombok.Setter;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
@Getter @Setter
public class JoinInDto {
@NotNull //NULL 안됨
private String username;
@NotEmpty // 값이 비어 있으면 안됨
private String password;
@NotEmpty // 값이 비어 있으면 안됨.
@Size(min = 4, max = 10) // 4자 이상 10자 이하
private String email;
}
[ MyValidationException ]
package shop.mtcoding.aopstudy.config.exception;
import lombok.Getter;
import java.util.Map;
@Getter
public class MyValidationException extends RuntimeException {
private Map<String, String> erroMap;
public MyValidationException(Map<String, String> erroMap) {
this.erroMap = erroMap;
}
}

[ 세번째 방법 ]
깃발을 생성하지 않고, 특정한 패턴이 수행될 때 정의된 advice를 실행하게 할 수도 있다.
Spring Framework에서는 XML을 이용하여
Aspect-Oriented Programming (AOP)을 설정하는 방법이 있다.
그러나 Spring Boot에서는 주로 어노테이션 기반의 AOP를 사용하며,
execution expression을 이용하여 pointcut을 등록할 수 있습니다.
하지만, 직접적으로 execution expression을 이용하여 pointcut을 등록하려면
여러가지 설정을 해주어야 한다. 대신, Spring Boot에서는 AspectJ를 사용하여
간편하게 pointcut을 등록할 수 있는 기능을 제공한다.
AspectJ는 Java와 비슷한 문법을 가지며,
execution expression을 이용하여 pointcut을 등록하는 방법이 있다.
Share article