AOP ( Aspect Oriented Programming , 관점지향 프로그램 )
스프링 어플리케이션은 대부분 특별한 경우를 제외하고는 MVC 웹 어플리케이션에서는 Web Layer, Business Layer, Data Layer로 정의된다.
- Web Layer : REST API를 제공하고, Client 중심의 로직 적용
- Business Layer : 내부 정책에 따른 logic을 개발하며, 주로 해당 부분을 개발
- Data Layer : 데이터 베이스 및 외부와의 연동을 처리
횡단 관심
주요 Annotation
AOP를 사용하기에 앞서
AOP를 사용하기 위해서는 Dependencies를 추가해야함.
build.gradle -> Dependencies -> implementation 'org.springframework.boot:spring-boot-starter-aop' 추가 후 Gradle 탭 새로고침
- RestApiController.java
package com.example.aop.controller;
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){
return id + " " + name;
}
@PostMapping("/post")
public User post(@RequestBody User user){
System.out.println("post method" + user);
return user;
}
@Timer()
@DeleteMapping("/delete")
public void delete() throws InterruptedException {
// db logic
Thread.sleep(1000*2);
}
@Decode
@PutMapping("/put")
public User put(@RequestBody User user){
System.out.println("put");
System.out.println(user);
return user;
}
}
- TimerAop.java
package com.example.aop.aop;
import com.example.aop.annotation.Timer;
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.stereotype.Component;
import org.springframework.util.StopWatch;
import org.springframework.web.bind.annotation.DeleteMapping;
@Aspect
@Component //bean과 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());
}
}
- @Bean 과 @Component의 차이?
- @Bean은 클래스에 붙일 수 없음.
- @Component를 통해서 클래스 단위로 빈을 등록
- @Bean 같은 경우 메서드에서 빈 어노테이션 가능.
- @Configration: 하나의 클래스에 여러가지 빈이 등록가능
- ParameterAop.java
package com.example.aop.aop;
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()") // cut이 실행되는 지점의 before에서 실행됨.
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);
}
}
- DecodeAop.java
package com.example.aop.aop;
import com.example.aop.dto.User;
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.springframework.stereotype.Component;
import java.io.UnsupportedEncodingException;
import java.util.Base64;
@Aspect
@Component
public class DecodeAop {
@Pointcut("execution(* com.example.aop.controller..*.*(..))")
private void cut(){
}
@Pointcut("@annotation(com.example.aop.annotation.Decode)") // 타이머가 설정된 메소드에 로깅
private void enableDecode(){}
// 선처리
@Before("cut() && enableDecode()")
public void before(JoinPoint joinPoint) throws UnsupportedEncodingException {
Object[] args = joinPoint.getArgs();
for(Object arg : args){
if(arg instanceof User) {
User user = User.class.cast(arg);
String base64Email = user.getEmail();
String email = new String(Base64.getDecoder().decode(base64Email), "UTF-8");
user.setEmail(email);
}
}
}
// 후처리 후 반환
@AfterReturning(value = "cut() && enableDecode()", returning = "returnObj")
public void afterReturn(JoinPoint joinPoint, Object returnObj){
if(returnObj instanceof User){
User user = User.class.cast(returnObj);
String email = user.getEmail();
String base64Email = Base64.getEncoder().encodeToString(email.getBytes());
user.setEmail(base64Email);
}
}
}
- timer.annotation
package com.example.aop.annotation;
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 {
}
- Decode.annotation
package com.example.aop.annotation;
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 Decode {
}
정리
- @Aspact -> AOP 동작하기 위해서 적어야함
- @Component -> 컴포넌트로 스프링에서 관리하기 위함
- @PointCut(execution(*com.example.aop.controller..*.*(..))") /execution의 수식들은 굉장히 다양한데, 이는 따로 기술
- - 포인트컷 메소드임을 알리는 anntation.
- 포인트컷은 보통 패키지나 클래스 하위등에 사용함 - @Before("cut()") // 포인트컷이 실행되는 지점의 before에서 실행됨. 안의 내용은 포인트컷 메서드의 이름.
- @AfterReturning(value = "cut()", returning = "returnObj") // 반환값을 컷이라는 곳에서 함.
public void afterReturn(JoinPoint joinPoint, Object returnObj){
} - AfterReturning Annotation은 반환값도 존재하기 때문에 returning = "returnObj"를 추가하고, 파라미터의 이름과 매칭이 되어야함.
- @Bean 과 @Component의 차이?
@Bean은 클래스에 붙일 수 없음.
@Component를 통해서 클래스 단위로 빈을 등록
@Bean 같은 경우 메서드에서 빈 어노테이션 가능.
@Configration: 하나의 클래스에 여러가지 빈이 등록가능 - 스프링 - StopWatch 가 존재.
StopWatch stopwatch = new Stopwatch(); 로 생성
이러한 stopwatch로 디버깅을 하거나 서비스의 개선을 이루려 할 때, 메서드에 stopwatch 기능을 넣는 것임.
하지만 비지니스 로직과는 전혀 상관없는 것StopWatch에 대한 코드가 모든 메서드에 들어가 있음.
이것을 횡단관점에서 바깥으로 빼는 작업임.
'개발공부 > Spring' 카테고리의 다른 글
[Spring] 좋은 객체 지향 프로그래밍이란? [다형성?] (0) | 2024.04.09 |
---|---|
[Spring] Object Mapper (0) | 2022.02.15 |
[Spring] IoC (Inversion of Control), DI (0) | 2022.02.10 |
[spring] 간소화된 Advisor (0) | 2022.01.26 |
[spring] Point Cut(Weaving, Join Point) (0) | 2022.01.26 |