
1. AOP(Aspect-Oriented Programming)
- 관점 지향 프로그래밍
- 소프트웨어 개발에서 각각의 비즈니스 로직이나 기능 이외의 공통 관심 사항을 분리
→ 모듈화하는 기법
코드의 가독성과 유지 보수성을 높일 수 있음
- 리플렉션 (메서드 분석 가능)
- 주로 클래스와 메서드의 단위로 관심사의 분리
- 횡단 관심사(cross-cutting concerns)
보안, 로깅, 트랜잭션 관리 등과 같이 여러 모듈에서 반복적으로 발생하는 부분
여전히 여러 모듈에 걸쳐 분산
- 횡단 관심사를 '관점'이라 불리는 모듈로 모듈화하는 프로그래밍 패러다임
- 보통 메서드 호출이나 객체 생성과 같은 지점에 Advice를 적용하여 특정 관심사를 분리
- 관점(Aspect) → 코드의 여러 지점(Join Point)에서 공통적으로 발생하는 작업(Advice)을 적용
- Aspect(관점) : 횡단 관심사를 모듈화한 것
- Join Point(조인 포인트) : 조인 포인트는 프로그램 실행 중 관점이 적용될 수 있는 특정한 지
- Advice(어드바이스 ): 조인 포인트에서 실행되는 코드
관점의 횡단 관심사를 수행하는 실제 코드 블록
2. Aspect
- 생성 : 로깅 기능(Advice)을 포함하는 Aspect를 정의(클래스 생성)
- 정의 : 메서드 실행 전후에 로그를 남기는 코드 작성(수행할 메서드 생성)
- 설정 : 로깅 기능을 적용할 메서드를 결정
ex) 모든 public 메서드에 로깅을 적용하려면 해당하는 패턴을 Pointcut에 지정(깃발에 별칭주기)
- 적용 : 설정한 Pointcut에 따라 실행 전후에 로그를 남기는 Advice가 실행(PointCut(별칭) 적용)
3. 인터셉트(Interception)
- 메서드 호출이나 객체 생성과 같은 작업을 가로채는 기술
- 메서드 실행 전에 실행
- 주로 프레임워크나 라이브러리의 훅(hook) 또는 프록시를 통해 구현
- AOP의 구현 기술 중 하나로 사용 가능
4. 리플렉션(Reflection)
- 런타임에 클래스의 메타데이터를 검사하고 조작하는 기능
- 주로 클래스, 필드, 메서드 등의 정보를 동적으로 액세스하고 조작하는 데 사용
- AOP나 인터셉트와 달리 보다 일반적인 용도로 사용
- 스프링부트에서는 DispatcherServlet을 우리가 제어할 수 없음 → AOP 제공
깃발(어노테이션)을 원하는 위치에 설정
그 깃발이 있는 코드가 실행될 때 수행할 공통기능을 적용
5. AOP 적용 방법 3가지
첫번째 방법
- 깃발(어노테이션)을 만들기
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 {
}
- 깃발을 PointCut(별칭)으로 등록하기
- Advice를 만들기 (메서드 행위)
- PointCut을 Advice에 적용하기
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";
}
}


두번째 방법
- 이미 만들어져 있는 깃발을 PointCut(별칭)으로 등록하기
- Advice를 만들기 (메서드 행위)
- PointCut을 Advice에 적용하기
- 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을 이용하여 AOP를 설정하는 방법
- Spring Boot에서는 주로 어노테이션 기반의 AOP를 사용
execution expression을 이용하여 pointcut을 등록 가능
- 직접적으로 execution expression을 이용하여 pointcut을 등록하려면 여러가지 설정이 필요
Spring Boot에서는 AspectJ를 사용하여 간편하게 pointcut을 등록할 수 있는 기능을 제공
execution expression(메서드 Pointcut 지점 설정 시 사용 때)
// AspectJ의 포인트컷 표현식 사용 -> 메서드 실행 시점을 지정
// execution 키워드는 메서드 실행, 특정 메서드를 대상으로 포인트컷을 정의 가능
// 여기서 *는 모든 리턴 타입을 나타냅니다.
// com.example.service.MyService는 패키지명과 클래스명을 지정, MyService 클래스를 가리킴
// save(..)는 메서드명과 메서드 시그니처, 이는 save 메서드를 가리킴
// 괄호 안에(메서드의 매개변수 타입이 들어가야됨) ..을 사용하여 모든 매개변수 타입을 허용
@Pointcut("execution(* com.example.service.MyService.save(..))")
public void saveMethod() {}
- AspectJ는 Java와 비슷한 문법
execution expression을 이용하여 pointcut을 등록하기
메서드 매개변수가 여러개 일 때
@Pointcut("execution(* com.example.service.MyService.save(java.lang.String, java.lang.Integer))")
public void saveMethodWithStringAndIntegerParams() {}
execution expression(컨트롤러 Pointcut 지점 설정 시 사용 때)
@Pointcut("execution(* com.example.controller.UserController.*(..))")
public void userControllerMethods() {}
Share article