하이 ~~!! 여러분 !!
오늘은 Spring 에서 아주 중요한 개념인 AOP (Aspect Oriented Programming) 에 대해 이야기 해보려한다.
딱봐도 쉽게 설명을 해줄꺼고 , 구현도 간단하게 해볼꺼다.
AOP
AOP는 비지니스 로직 등의 핵심 기능 (Core Concerns) 들과 로깅, 보안 , 트랜잭션 처리 등 핵심 기능을 도와주는 부가기능 (Cross-cutting Concerns) 으로 분리해서 모듈화 하는 것을 의미한다.
간단한 게시판이 있다고 가정해보자.
게시글을 읽고 쓰고, 답글을 읽고 쓰고, 회원가입하고 로그인하는 그런 아주 일반적인 어플리케이션이다.
그런데 이렇게 어플리케이션을 개발하다보면 로그도 남겨야하고 , 보안적인 이슈도 처리하는 등의 공통적인 부가기능들이 생겨나게 되는데.. 이런 것들을 모듈마다 구현해주면 코드가 굉장히 복잡해질것이다.
우리는 이런 부가기능들을 핵심기능에서 분리하여 모듈화 할 수 있다.
이것이 바로 AOP(Aspect Oriented Programming) 이다.
일반적인 java에서 AOP를 하려면 굉장히 복잡하고 어렵다. 하지만 !! 우리가 지금 하고 있는게 무엇인가 !! 바로 Spring이다. Spring에서는 아주 쉽게 AOP를 구현할 수 있도록 Spring AOP를 제공하고 있다.
Spring AOP
Primary Concern은 우리가 흔히 아는 핵심기능이다. 다른말로는 Core Concern이라고도 한다. 여기에는 비지니스 로직만 구현된다.
그리고 Cross- Cutting Concern 은 로깅, 보안 등의 기능을하는 부가 기능이다. 이건 Runtime 때 Core Concern과는 별도로 만들어진다. 그리고 Point Cut 이라는 문법이 있는데, 이건 Cross-Cutting Concern을 어디에 적용시키느냐의 위치정보가 들어있는 문법이다. Point Cut 과 Cross-Cutting Concern을 합쳐서 Aspect라고 한다. Spring에선 Adviser라고 부르기도한다. 그리고 실제 런타임 때 이를 핵심기능에 끼워넣는 걸 Weaving이라고 한다.
Spring AOP 구현
자 이제.. 이론적인 공부는 다들 잘 마쳤으니. ㅎㅎ 이번에는 실제로 구현을 한번 해보도록하자.
일단 AOP를 구현하기 위해서는 아래 디펜던시를 pom.xml에 설정 해줘야한다.
1
2
3
4
|
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
|
그리고 Application 클래스에 아래의 어노테이션도 추가해주자.
@EnableAspectJAutoProxy
자 이렇게 해주면 준비 끝이다.
일단 로그 관련 코드를 한번 구현해보자~
LoggingAspect
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
31
32
33
34
35
36
37
38
39
40
|
import org.springframework.stereotype.Component;
@Aspect
@Component
public class LoggingAspect {
@Before("execution(* com.test.crudPjt.service.*.get*(..))")
public void loggerBefore() {
System.out.println("get으로 시작하는 메서드가 시작됩니다.");
}
@After("execution(* com.test.crudPjt.service.*.get*(..))")
public void loggerAfter() {
System.out.println("get으로 시작하는 메서드가 끝났습니다.");
}
@Around("execution(* com.test.crudPjt.controller.UserController.*(..))")
public Object loggerAround(ProceedingJoinPoint pjp) throws Throwable {
long beforeTimeMillis = System.currentTimeMillis();
System.out.println("[UserController] 실행 시작 : "
+pjp.getSignature().getDeclaringTypeName() + "."
+pjp.getSignature().getName());
Object result = pjp.proceed();
long afterTimeMillis = System.currentTimeMillis() - beforeTimeMillis;
System.out.println("[UserController] 실행 완료: " + afterTimeMillis + "밀리초 소요"
+pjp.getSignature().getDeclaringTypeName() + "."
+pjp.getSignature().getName());
return result;
}
}
|
그리 어렵지 않은 내용이다. Aspect도 여느 스프링 클래스들과 마찬가지로 POJO로 구성된다.
또 @Component 어노테이션을 보면 알듯이 이 Aspect는 어플리케이션이 서버에 올라갈때 같이 로딩된다.
Before은 메서드가 수행되기전, After는 메서드가 수행된후, Around는 전후 둘다 로깅을 남긴다.
다른 어드바이스 들도 있다. 아래의 표를 참고해서 다음에 한번 꼭 구현해보길 권장한다.
종류 |
기능 |
@Before |
메서드 호출 되기 이전에 어드바이스 기능 수행 |
@After |
메서드 호출 된 이후에 어드바이스 기능 수행 ( 결과에 상관 x) |
@AfterReturning |
메서드가 성공적으로 리턴값 반환한 경우 어드바이스 기능 수행 |
@AfterThrowing |
메서드 수행중 예외 발생 시 어드바이스 기능 수행 |
@Around |
메서더 호출 전 / 후 어드바이스 기능 수행 |
Around에서 proceed라는 함수가 있는데 이걸 기준으로 전후를 나눈다.
그리고 각 어노테이션 뒤에 사용되는 문법은 어디로 끼워넣을지에 대한 위치정보가 들어가 있다.
나는 모든 service의 get이 들어가는 메서드에 대해 전후로 로그가 남도록 했고, 또 UserController가 기동 될때 로그가 남도록 했다.
그리고 스프링 AOP 같은 경우는 메서드 호출에 대해서만 Weaving 할수 있다는 점 잘 기억하자.
실제로 어플리케이션을 기동하고 , PostMan 에서 get이 들어간 메서드를 호출하기 위해 URL을 날려봤다.
결과는 잘 나오고 ... 이제 Console 을 보면 ㅎㅎ...
이렇게 내가 남긴 로그가 잘 남는 것 알 수 있다. 로그남기는건 프로젝트가 커지면 커질 수록 중요하니 잘 ~ 기억하자.
다음은 보안 관련해서 Aspect를 사용하는 예도 한번 작성해보자.
우리가 토큰으로 인증하는 걸 저번 포스팅에서 배웠었는데 .. 이번에는 서비스의 각 메서드마다 토큰 인증이 필요한 메서드를 정해주는 걸 해볼거다.
토큰 인증하는 부분 기억안나시면 아래의 링크 보고 오자.
https://devkingdom.tistory.com/113
SecurityAspect
먼저 바로 Aspect를 만들기 전에 아래의 어노테이션을 하나 만들어주자.
1
2
3
4
5
6
7
8
9
10
|
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface TokenRequired {
}
|
그리고 해당 어노테이션의 기능을 SecurityAspect에 구현해주자.
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
31
32
33
34
35
36
37
38
39
40
41
42
43
|
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class SecurityAspect {
@Before("@annotation(tokenRequired)")
public void authenticateWithToken(TokenRequired tokenRequired) {
ServletRequestAttributes requestAttributes =
(ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
HttpServletRequest request =requestAttributes.getRequest();
// request header에 있는 토큰 체크하기
String token = request.getHeader("token");
if(StringUtils.isEmpty(token)) {
throw new IllegalArgumentException("token is empty");
}
Claims claims = Jwts.parser().setSigningKey(
DatatypeConverter.parseBase64Binary(
SecurityServiceImpl.SECRET_KEY))
.parseClaimsJws(token).getBody();
if (claims == null || claims.getSubject() == null) {
throw new IllegalArgumentException("Token Error!! Claims are null!!");
}
System.out.println("토큰의 subject로 자체 인증해주세요.");
}
}
|
기능은 간단하다. 이제 이 @TokenRequired라는 어노테이션이 딱 붙어 있으면!! 헤더에 토큰이 있는지 꺼내서 검증을 해주는 로직이 실행된다.
그리고 나서 메서드에 해당 어노테이션을 써주면 구현은 끝난다.
1
2
3
4
5
6
|
@TokenRequired
@GetMapping("/{userid}")
public User getUserByUserId(@PathVariable String userid) {
return userService.getUserByUserId(userid);
}
|
이것 역시 테스트해보면.. !
헤더에 토큰 없이 보내면 .. ㅎㅎ
이런 에러를 던지고 !
토큰을 발급받아 헤더에 넣어주면 ㅎㅎ 이렇게 잘 ~~ 수행되는걸 확인할 수 있다. ㅎㅎ
자~~.. 오늘은 여기까지 하도록 하겠다 ㅎㅎ 그럼 모두 즐거운 코딩하자.
'Develop > Spring' 카테고리의 다른 글
[Spring] Spring에서 예외 처리하기 (1) | 2020.04.05 |
---|---|
[Spring] Spring 에서 로그 남기기 (0) | 2020.04.05 |
[Spring] JUnit을 활용한 스프링 메서드 모듈 테스트하기 (0) | 2020.03.29 |
[Spring] JWT 를 활용한 인증 구현 (7) | 2020.03.28 |
[Spring] Spring 프로젝트 개선하기 - 인터페이스/클래스 분리 (0) | 2020.03.25 |
댓글