AOP (Aspect Oriented Programming)

2025. 12. 1. 09:47·spring

# AOP  관점지향 프로그래밍

스프링에서 AOP(Aspect Oriented Programming)는 복잡한 비즈니스 로직 속에서 중복되는 공통 기능을 분리하고 핵심 로직만 깔끔하게 유지하도록 돕는 중요한 개념이다.
특정 기능을 내가 바라보는 관점에서 모듈화 하여 한 곳에서 관리하는 방식으로 이해하면 접근하기 쉽다.


# AOP란?

AOP는 여러 모듈에 걸쳐 반복적으로 등장하는 공통 관심사를 따로 분리하여 핵심 로직에 영향을 주지 않고 한 곳에서 처리할 수 있게 해주는 기법이다.

💡API 진입 시 입력값 출력
💡메소드 실행 시간 측정
💡예외 발생 기록
💡사용자 인증/권한 체크
💡캐싱

❗이런 기능을 매번 직접 코드에 넣으면 중복이 생기고 유지보수가 어렵다.
👉AOP는 이런 공통 로직을 한 곳에서 처리해 준다

# AOP 핵심 Annotation 정리

Annotation 설명
@Aspect AOP를 정의하는 클래스
@Pointcut 공통 로직을 적용할 지점(메소드, 클래스 등)을 지정
@Before 대상 메소드 실행 전
@After 대상 메소드 실행 후
@AfterReturning 대상 메소드 성공적으로 종료 후(리턴 값 접근 가능)
@Around 메소드 실행 전·후 모두 감싸서 처리

# Pointcut 설정 방식

Pointcut 표현식은 AOP 핵심 중 하나이며 어떤 메서드에 AOP를 적용할지 정하는 규칙이다.

execution(* com.example.aop_.controller..*.*(..))
controller 패키지 및 하위 패키지 전체
모든 클래스
모든 메소드
모든 파라미터

 

다음과 같이 특정 클래스만 지정할 수도 있다.

execution(* com.example.aop_.controller.Mapper.*(..))

특정 메서드만 지정하는 방식도 가능하다.

execution(String com.example.aop_.controller.Mapper.todo(String, int))

# RestController 실습 예제

@RequestMapping(path = "/apis")
@RestController
public class TestRestController {

    @GetMapping(path = "/user/{id}")
    public String findUser(@PathVariable String id,
                           @RequestParam String name) {
        System.out.println("controller in findUser func()");
        return "id="+id+", name="+name;
    }

    @PostMapping(path = "/user")
    public ResponseEntity<UserDto> createUser(@RequestBody UserDto userDto) {
        System.out.println("controller in createUser func()");
        return  ResponseEntity.ok(userDto);
    }
}

UserDto

public class UserDto {
    private String id;
    private String pw;
    private String email;
    
    // getter/setter 생략

    @Override
    public String toString() {
        return "UserDto{" +
                "id='" + id + '\'' +
                ", pw='" + pw + '\'' +
                ", email='" + email + '\'' +
                '}';
    }
}

# 실무형 AOP – Logging

Logging AOP는 가장 많이 사용하는 기능 중 하나이다.
클래스명, 메서드명, 파라미터를 자동으로 출력하도록 구현할 수 있다.

@Aspect
@Component
public class LoggingAop {

    @Pointcut("execution(* com.example.newbie.controller..*.*(..))")
    private void cut(){}

    @Before("cut()")
    public void before(JoinPoint joinPoint) {

        StringBuilder sb = new StringBuilder();

        sb.append("class = ")
                .append(joinPoint.getTarget().getClass().getName())
                .append(", method = ")
                .append(joinPoint.getSignature().getName())
                .append("\n");

        Arrays.stream(joinPoint.getArgs()).forEach(o -> {
            sb.append("arg type = ").append(o.getClass().getName()).append(", ");
            sb.append("arg value = ").append(o).append("\n");
        });

        System.out.println(sb.toString());
    }
}

# AOP에서 파라미터 조작 + AfterReturning 처리

예를 들어 이메일 값을 인코딩했다가 다시 디코딩하는 로직을 AOP에서 처리할 수 있다.

@Before("cut2(userDto)")
public void before(JoinPoint joinPoint, UserDto userDto) {
    userDto.setEmail(Base64.getEncoder().encodeToString(userDto.getEmail().getBytes()));
}
@AfterReturning(value = "cut2(userDto)", returning = "returnObj")
public Object afterReturn(JoinPoint joinPoint, Object returnObj, UserDto userDto) {
    if (returnObj instanceof ResponseEntity response) {
        var dto = (UserDto) response.getBody();
        var decodeStr = new String(Base64.getDecoder().decode(dto.getEmail().getBytes()));
        dto.setEmail(decodeStr);
    }
    return returnObj;
}
이러한 방식으로 AOP는 컨트롤러 로직을 전혀 손대지 않으면서도 데이터를 가공할 수 있다.

# @Timer – 실행 시간 측정 AOP

Annotation을 직접 만들어 시간 측정 기능을 제공할 수 있다.

1) Annotation 정의

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface Timer {}

2) Timer AOP

@Aspect
@Component
public class TimerAop {

    @Pointcut("@annotation(com.example.newbie.annotation.Timer)")
    private void enableTimer(){}

    @Around("enableTimer()")
    public void around(ProceedingJoinPoint joinPoint) throws Throwable {
        StopWatch sw = new StopWatch();
        sw.start();
        joinPoint.proceed();
        sw.stop();
        System.out.println("total time=" + sw.getTotalTimeSeconds());
    }
}

3) Controller에서 사용

@Timer
@DeleteMapping("/user")
public void deleteUser(@RequestParam String id) throws InterruptedException {
    Thread.sleep(2000);
}

# AOP 캐싱 – 동일 요청을 빠르게 처리

특정 메서드가 시간이 오래 걸린다면 AOP로 캐싱을 구현해 두면 성능을 크게 향상할 수 있다.

@Aspect
@Component
public class CashingAop {

    private final Map<String, Object> map = new HashMap<>();

    @Around("execution(* com.example.newbie.service.*.*(..))")
    public Object cashingResult(ProceedingJoinPoint joinPoint) throws Throwable {
        String key = generateCacheKey(joinPoint);

        if (map.containsKey(key)) {
            System.out.println("Returning cached key: "+key);
            return map.get(key);
        }

        Object result = joinPoint.proceed();
        map.put(key, result);

        return result;
    }

    private String generateCacheKey(JoinPoint joinPoint) {
        return joinPoint.getSignature().toString();
    }
}

서비스 메서드

public String computeVal(String input) {
    Thread.sleep(1000);
    return "Compute value = " + input;
}

'spring' 카테고리의 다른 글

JPA(=Java Persistance API)  (0) 2025.12.01
Thymeleaf  (0) 2025.12.01
Validation  (0) 2025.12.01
Spring boot 기초  (0) 2025.11.28
Web?  (0) 2025.11.28
'spring' 카테고리의 다른 글
  • Thymeleaf
  • Validation
  • Spring boot 기초
  • Web?
차하비
차하비
chadev 님의 블로그 입니다.
  • 차하비
    chadev
    차하비
  • 전체
    오늘
    어제
    • 분류 전체보기 (47)
      • kubernetes, k8s (3)
      • docker (3)
      • cloud (6)
      • linux (12)
      • spring (7)
      • java (11)
      • error (4)
      • 자격증 (1)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    Java
    thread
    k8s
    restAPI
    KUBECTL
    URI
    docker
    진법
    클래스
    HTTP
    monolithic
    비트 연산
    Kubernetes
    객체지향
    Join
    Spring
    url
    모놀리식
    OOP
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.5
차하비
AOP (Aspect Oriented Programming)
상단으로

티스토리툴바