Spring Framework의 특징 세 가지중 하나인 AOP
를 알아볼 것이다.
1. AOP
: 관점지향프로그래밍(Aspect Oriented Programming)
이다. 쉽게 말해서 관점을 나눠서 프로그래밍 하겠다는 건데, 특정 로직을 비즈니스적인 관점과 부가적인 관점으로 나눠서 나눈 관점 기준으로 모듈화하여 프로그래밍하는 것이다.
예를 들면, 비즈니스적인 관점이라 하면 말그대로 우리가 구현하고자 하는 서비스의 핵심적인 로직부분을 가리키는 관점이고, 부가적인 관점은 해당 로직이 없어도 비즈니스에 아무런 영향이 없는 부분을 가리키는 DB연결로직
이나 로깅
등을 가리키는 관점이다.
그리고 각 로직마다 반복되는 부분을 흩어진 관심사(Crosscutting Concerns)
라고 하는데, AOP
에서는 이러한 부분을 위에서 언급한 관점지향적으로 모듈화하여 프로그래밍할 것이다.
2. AOP의 사례
AOP
의 대표적인 2가지 사례를 알아볼 것이다.
A. logging
: 로깅(logging)
기능은 로직마다 반복적이고 공통적으로 활용되는 대표적인 부가기능 중 하나이다. 이러한 로깅기능을 AOP
를 활용하여 프로그래밍 한다면 아래와 같이 코딩할 수 있다.
- ParameterAop.java
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
@Aspect
@Component
public class ParameterAop {
@Pointcut("execution(* com.example.aop.controller..*.*(..))")
private void cut(){}
@Before("cut()")
public void before(JoinPoint joinPoint){
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method method = methodSignature.getMethod();
System.out.println(method.getName());
Object[] args = joinPoint.getArgs();
for(Object obj : args){
System.out.println("type : "+obj.getClass().getSimpleName());
System.out.println("value : "+obj);
}
}
@AfterReturning(value = "cut()", returning = "returnObj")
public void afterReturn(JoinPoint joinPoint, Object returnObj){
System.out.println("return obj");
System.out.println(returnObj);
}
}
- RestApiController.java
import com.example.aop.annotation.Decode;
import com.example.aop.annotation.Timer;
import com.example.aop.dto.User;
import org.springframework.util.StopWatch;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api")
public class RestApiController {
@GetMapping("/get/{id}")
public String get(@PathVariable Long id, @RequestParam String name){
// TODO
return id+ " "+ name;
}
@PostMapping("/post")
public User post(@RequestBody User user){
return user;
}
}
ParameterAop.java
파일에서 AOP
로 모듈화할 클래스를 정의하였다.AOP
로 모듈화하기 위해서는 클래스명에 @Aspect
어노테이션을 명시해주어야 한다.@PointCut
어노테이션은 모듈화된 클래스가 적용될 클래스의 경로를 지정해준다.
지정된 경로의 클래스의 메소드가 실행될 때마다 AOP
로 정의된 클래스의 메소드가 실행될 것이다.
위의 코드에서 cut()
메소드는 @PointCut
으로 적용된 클래스의 메소드인 RestApiController.get()
과 RestApiController.post()
를 가리키게 된다.
@Before
어노테이션은 지정된 메소드가 실행되기 바로 이전에 실행될 메소드에 명시된다. 해당 어노테이션에 지정된 메소드는 cut()
메소드이다.
위 코드에서 before()
메소드의 파라미터로 받는 JoinPoint
는 cut()
메소드에서 정보를 가져올 때 활용되는 객체이다.
위 코드에서 JoinPoint
객체는 HTTP 메소드와 cut()
메소드에서 활용된 파라미터 값을 가져오기 위해서 활용되고 있다.
@AfterReturning
어노테이션은 @Before
어노테이션과 반대로 지정된 메소드가 실행된 후 바로 실행될 메소드에 명시된다. 해당 어노테이션에 지정된 메소드 역시 cut()
메소드이다.
그리고 @AfterReturning
어노테이션에서 returning
값을 지정해주면 cut()
메소드에서 반환된 값이 returning
값으로 지정된 변수에 입력되어 활용할 수 있다. 위 코드에서는 returnObj
변수에 활용되고 있다.
위 사례의 코드와 같이 프로그래밍을 한다면, 특정 비즈니스 로직이 실행되기 전후에 로깅기능을 모듈화하여 사용할 수 있다.
B. Timer
: 타이머(Timer)
기능은 특정 로직의 실행시간을 확인시켜주는 기능이다. 이 또한 비즈니스로직과 관련이 없는 기능이기 때문에 아래와 같이 AOP
로 모듈화할 수 있다.
- TimerAop.java
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;
@Aspect
@Component
public class TimerAop {
@Pointcut("execution(* com.example.aop.controller..*.*(..))")
private void cut(){}
@Pointcut("@annotation(com.example.aop.annotation.Timer)")
private void enableTimer(){}
@Around("cut() && enableTimer()")
public void around(ProceedingJoinPoint joinPoint) throws Throwable {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
Object result = joinPoint.proceed();
stopWatch.stop();
System.out.println("total time : "+stopWatch.getTotalTimeSeconds());
}
}
- Timer.java
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Timer {
}
- RestApiController.java
import com.example.aop.annotation.Decode;
import com.example.aop.annotation.Timer;
import com.example.aop.dto.User;
import org.springframework.util.StopWatch;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api")
public class RestApiController {
@Timer
@DeleteMapping("/delete")
public void delete() throws InterruptedException {
// db logic
Thread.sleep(1000 * 2);
}
}
Timer 기능도 역시 로깅 기능을 구현할 때와 마찬가지로 AOP
를 활용하여 모듈화시켰고 코드상의 차이도 크게 없다.
하지만 두 가지정도 차이나는 부분이 있는데, 해당부분만 언급해보겠다.
첫 번째 차이는 로깅 기능을 구현할 때에는 경로 지정방식으로 @PointCut
어노테이션에 해당 기능이 활용될 클래스의 경로를 지정하였는데,
Timer 기능을 구현할 때에는 @PointCut
어노테이션에 특정 어노테이션을 지정하여, 지정된 어노테이션이 붙은 클래스나 메소드가 실행될 때에만 Timer기능이 실행될 수 있도록 하였다.
이를 위해 Timer라는 별도의 인터페이스를 Timer.java
와 같이 작성하였다.
여기서 @Target
어노테이션은 임의로 선언된 어노테이션인 @Timer
어노테이션이 붙을 수 있는 타입을 지정하는 어노테이션이다.
위 코드에서는 메소드와 클래스에 붙을 수 있도록 지정하였다.
그리고 @Retention
어노테이션은 임의로 선언된 어노테이션인 @Timer
어노테이션의 생명주기(Life Cycle)
을 지정하는 어노테이션이다.
위 코드에서는 런타임
으로 생명주기를 지정하였고, 실행될 동안에는 계속 유효한 어노테이션으로 사실상 안죽는 어노테이션이라는 말이다.
두 번째 차이는 로깅 기능을 구현할 때에는 @Before
어노테이션과 @AfterReturning
어노테이션을 활용하여 실행시점과 동작을 정의하였는데,
타이머 기능을 구현할 때에는 @Around
어노테이션을 활용하여 비즈니스로직의 전후를 하나의 메소드에서 정의할 수 있도록 하였다.
이를 활용한 이유는 타이머 기능같은 경우는 시간데이터를 비즈니스로직이 실행되기 전후에 공유되어야 하는데, before/after로 메소드가 나뉘어 실행될 경우 시간데이터가 공유될 수 없기 때문이다.@Around
어노테이션도 마찬가지로 타겟 메소드를 지정하여 특정 로직이 실행될 때마다 실행될 수 있도록 설정할 수 있다.
그리고 before/after 어노테이션을 활용할 때와 다른점은 비즈니스 로직을 감싸는 형태로 메소드가 실행된다는 점이다.TimerAop.java
의 around()
메소드를 보면, joinPoint.proceed()
로 비즈니스 로직이 실행되는 부분이 있고,
전후로 stopWatch.start()
와 stopWatch.stop()
이 실행시켜서 전후에 어떤 메소드가 실행될지 프로그래밍 하였다.
그리고 stopWatch
객체를 활용하여 비즈니스 로직의 시작시간과 끝나는시간을 기록하였고, stopWatch.getTotalTimeSeconds()
메소드를 통해 기록된 시간의 차이를 알 수 있다.
마무리
: 위 두 사례에서 본 것과 같이 AOP
는 비즈니스 관점과 부가적인 관점을 나누어 비즈니스 로직과 부가적인 로직을 분리시켜주는 프로그래밍 방식이다.@Aspect
가 명시된 클래스에서 부가 기능들이 비즈니스 로직의 어떤 시점에 실행되고 어떤 방식으로 실행되는지, 어떤 타겟에서 실행되는지 설정할 수 있고, 이를통해 부가 기능을 철처하게 모듈화시킬 수 있다.
이어서
기존 계획은 AOP
를 알아본 이후에 PSA
도 함께 알아보려 했으나, DB와 같이 보는 것이 좋을 것 같아서 후순위로 조금 미루려한다.
대신 이어서 SOAP
를 알아보려 한다. 요즘은 REST연동 방식이 대세라곤 하지만, 레거시한 시스템에서는 아직 많이 활용되는 방식이라 알아두면 좋을 것 같고, 실제로 나는 로그인 연동기능을 구현할 때, SOAP
때문에 많이 애먹었어서 해당 부분을 까먹기전에 다뤄보려한다.
다음은 SOAP
을 알아보자!
'개발 > Spring Framework' 카테고리의 다른 글
[Spring] Bean 개념 및 생성 과정 (0) | 2023.03.06 |
---|---|
[Spring] Spring Security - 사전 학습 (0) | 2023.02.05 |
[Spring] Spring Framework란? - (2) Spring의 특징 (역사, IoC/DI) (0) | 2023.01.15 |
[Spring] Spring Framework란? - (1) 입문 (0) | 2023.01.08 |
[Spring] Spring 입문 (0) | 2023.01.08 |