일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- 시스템설계면접예시
- git commit merge
- 리팩터링2판테스트
- react-ga
- 시스템설계
- 헤드퍼스트전략패턴
- cypressBDD
- 시스템설계면접
- 리팩토링2판4장
- file not found Error
- FirebaseAnalytics
- 시스템설계면접팁
- Git commit 합치기
- 리액트구글애널리틱스
- react
- git squash
- 가상면접3장
- 전략패턴
- 디자인패턴
- 시스템설계방법
- gitsquash
- cypress React
- 가상면접으로대규모시스템
- 가상면접2장
- formik react-query submitting not working
- formik submitting not working
- git commit 협업
- 테스트코드책
- awss3
- s3이미지다운로드됨
- Today
- Total
mingg IT
[Spring] AOP 개념 정리 (ver 2022년) 본문
AOP(Aspect Oriented Programming) 의 약자임. 관점 지향 프로그래밍이라고 부름.
어떤 로직을 기준으로 핵심적인 관점, 부가적인 관점으로 나누어서 보고 그 관점을 기준으로 각각 모듈화하겠다는 것.
핵심적인 관점은 우리가 적용하는 비지니스 로직.
부가적인 관점은 이러한 비지니스로직을 수행하기위해 행해지는 데이터베이스 연결, 로깅, 파일 입출력을 예로 들 수 있음.
Aspect로 모듈화하고 핵심적인 비지니스 로직에서 분리하여 재사용하겠다는 것이 AOP의 취지임.
AOP 주요 용어
- Aspect : 모듈화 한것. 주로 부가 기능을 모듈화 한다.
- Target : Aspect를 적용하는 곳 (ex 클래스, 메서드)
- Advice : 실질적으로 어떤 일을 해야할지. 실질적인 부가기능을 담은 구현체. 부가기능의 로직
- JointPoint : Advice가 적용될 위치, 끼어들 수 있는 지점, 메서드 진입 지점, 생성자 호출 지점, 필드에서 값을 꺼내올 때 등 다양한 시점에 적용 가능
- PointCut : JointPoint의 상세한 스펙을 정의한것. (A란 메서드의 진입 시점에 호출할 것'과 같이 더욱 구체적으로 Advice가 실행될 지점을 정할 수 있음)
크게 로그를찍는 예제와 Timer를 사용하는 예제 두가지를 알아보려고 한다.
로그를 찍는 예제
- AOP를 사용하지 않았을 경우 예제
RestApiController.java
@RestController
@RequestMapping("/aoptest")
public class RestApiController {
@GetMapping("/get/{id}")
public void get(@PathVariable Long id,
@RequestParam String name) {
System.out.println("get method");
System.out.println("get method " + id);
System.out.println("get method " + name);
}
@PostMapping("/post")
public void post(@RequestBody User user) {
System.out.println("post method : " + user);
}
}
User.java
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private Long id;
private String name;
private String email;
}
- Get request를 날렸을 경우
- Post Request를 날렸을 경우
현재 Get, Post 의 메소드마다 System.out.print를 이용하여 다 로그를 찍었다. 허나 이 메소드가 2개가 아닌 100개 200개라면 모든 메소드마다 로그를 찍을 순 없다.
이때 AOP를 이용하여 로그를 찍는 기능을 하나로 모듈화 하여 사용할 수 있다.
- AOP를 사용한 예제
User.java
@GetMapping("/get/{id}")
public String get(@PathVariable Long id,
@RequestParam String name) {
System.out.println("get method");
// System.out.println("get method " + id);
// System.out.println("get method " + name);
return id + " " + name;
}
@PostMapping("/post")
public User post(@RequestBody User user) {
System.out.println("post method : " + user);
return user;
}
우선 User에 있던 System.out.println을 모두 주석처리 해준다. get 은 id와 이름을 return하도록 수정하고 post 는 user를 return 하도록 수정한다.
ParameterAop.java
package com.example.AOP.aop;
import java.lang.reflect.Method;
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;
@Aspect
@Component
public class ParameterAop {
@Pointcut("execution(* com.example.AOP..*.*(..))")
private void cut() {
}
// cut이 해당되는 시점 이전에 수행하겠음.
@Before("cut()")
public void before(JoinPoint joinPoint) {
Object[] args = joinPoint.getArgs();
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method method = methodSignature.getMethod();
System.out.println("method type :" + method);
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("returnObj : " + returnObj);
}
}
AOP Aspect, Component annotation을 사용한다.
@PointCut을 사용하여 AOP를 적용할 위치를 설정한다.
@Before 은 해당 메소드를 실행하기 이전에 수행한다.
MethodSignature를 사용하면 method의 type을 알 수 있다.
@AfterReturning 은 메소드 실행 이후 return된 object에 접근할 수 있다.
- GET 수행 시
- POST 수행 시
결과를 확인해보면 method type으로 해당 컨트롤러에서 사용했던 method의 type이 출력되고
type, value 를 출력해준다.
이후 각 메소드에서 System.out으로 찍었던 get method나 post method가 찍힘으로써 해당 메소드가 호출되기 전에 수행됨을 확인할 수 있다.
마지막에 returnObj를 통해 AOP 의 @AfterReturning가 정상 동작함을 확인할 수 있다.
내가 외부에서 어떤 요청이 들어왔는지, 어떤 메소드를 수행했는지, 어떤 값을 return했는지 알 수 있기 때문에 디버깅을 통해 따라가면서 어디서 부터 잘못 되었는지 편하게 확인할 수 있다.
Timer를 사용하는 예제
- AOP를 사용하지 않는 경우
@PostMapping("/post")
public User post(@RequestBody User user) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
System.out.println("post method : " + user);
Thread.sleep(1000 * 2);
System.out.println("total time : " + stopWatch.getTotalTimeSeconds());
return user;
}
@DeleteMapping("/delete")
public void delete() throws InterruptedException {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
stopWatch.stop();
Thread.sleep(1000 * 2);
System.out.println("total time : " + stopWatch.getTotalTimeSeconds());
}
시간을 측정하는 StopWatch를 메소드마다 만들어주고 실행 해주어야한다.
비지니스로직과는 전혀 상관없는 부가로직이 메소드마다 들어가있다.
이를 해결하기위해 사용하는 것이 AOP임. 즉 비지니스로직과 부가로직을 따로 빼서 관리하는것.
- AOP를 사용할 경우
우선 시간을 측정해주는 custom annotation을 하나 만든다.
Timer.java
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 {
}
TimerAop.java
@Aspect
@Component
public class TimerAop {
@Pointcut("execution(* com.example.AOP..*.*(..))")
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());
}
}
cut 메소드와, enableTimer 메소드 두가지를 사용한다.
@Around 를 이용해서 cut, enableTimer가 수행될 경우 Time을 측정 할 수 있도록 설정했고
내부에 Time을 측정할 수 있는 로직을 작성한다.
RestApiController.java
@Timer
@DeleteMapping("/delete")
public void delete() throws InterruptedException {
Thread.sleep(1000 * 2);
}
delete 메소드에서 만들었던 Timer annotation을 사용한다.
- delete 메서드 호출
delete method를 수행했을 경우, cut(), enableTimer() 둘다 수행되었기 때문에 시간이 측정되고, method Type도 보여주게 된다.
만약 @Timer를 사용용하지 않은 post를 호출하게되면 어떻게 될까 ?
- post 메서드 호출
@Timer 를 사용하지 않았기 때문에 cut() 만 작동하여 method Type, value, returnObj등이 출력됨을 확인할 수 있다.
Timer같은 경우에는 메서드의 시간을 측정할 수 있기 때문에 외부와 통신을 하거나, DB를 사용하여 시간이 얼마나 걸렸는지 필요할 경우에 사용하면 된다.
@Timer를 사용해서 측정한 시간을 DB에 저장하거나, 모니터링 하는데 사용할 수 있다. 혹은 서버 안에서 5초안에 끝내야하는 서비스인데 5초가 넘어갈 경우 서버관리자에게 알려주거나 이런 용도로 쓸 수 있다.
로그를 사용한 예제, Timer를 사용한 예제로 공부를 해보았는데 결론은 AOP는 비지니스 로직과는 전혀 상관없는 것을 한곳에 모아서 모듈화 하기 위해 사용한다.
어떤 로직을 기준으로 핵심적인 관점, 부가적인 관점으로 나누어서 보고 그 관점을 기준으로 각각 모듈화하겠다는 것.
핵심적인 관점은 우리가 적용하는 비지니스 로직.
부가적인 관점은 이러한 비지니스로직을 수행하기위해 행해지는 데이터베이스 연결, 로깅, 파일 입출력을 예로 들 수 있음.
Aspect로 모듈화하고 핵심적인 비지니스 로직에서 분리하여 재사용하겠다는 것이 AOP의 취지임.
'BackEnd' 카테고리의 다른 글
[Spring] Swagger-UI 3.0.0 not working 해결하기 (0) | 2022.02.07 |
---|---|
[MongoDB]MongooseServerSelectionError: connect ECONNREFUSED ::1:27017 에러 해결 (4) | 2022.02.05 |
[Jpa] @transactional 격리수준과 전파속성 (0) | 2021.12.26 |
[JPA] 영속성 컨텍스트 와 EntityManager (0) | 2021.12.24 |
[JPA] deleteAll(), deleteAllInBatch(), deleteInBatch() 차이점 (0) | 2021.12.09 |