일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- Java
- Stateless
- Spring
- Setter
- 의존성 주입
- javascript
- Singleton
- JPA
- Kotlin
- vue-cli
- BEAN
- thymeleaf
- dependency injection
- Security
- 라이프 사이클
- vuex
- Excel
- 캐시
- 프로토타입
- 로그인
- js
- 싱글톤
- cache
- di
- Vue.js
- HTTP 메서드
- DB
- Repository
- HTTP
- VUE
- Today
- Total
jhhan의 블로그
Spring - AOP 본문
이번에는 AOP에 대해서 다뤄보겠습니다.
전에도 AOP에 대해서 다룬 적이 있는데,,,
그 때는 AOP의 뜻과 개념만 얼핏 서술했습니다.
사실 그 때 AOP에 대해서 알지 못해서 그냥 어떻게든 적고 끝났습니다.
이번에는 코드를 통해서 좀 더 알아보겠습니다.
그래도 AOP를 완전히 이해한 것은 아니어서 설명이 부족할 수도 있겠네요...
먼저 AOP가 필요한 상황에 대해서 알아보겠습니다.
○ 예시: 모든 메소드의 호출 시간을 측정할려면??
저런 예시가 있다 할 때 모든 메소드에 측정 로직을 추가할 수도 있겠지만,,,
제가 만드는 작은 프로젝트도 메소드가 10개가 넘어가던데... 10개에 일일이 다 추가하기에는 너무 비효율적입니다..
한번 예시를 들어보죠
이전에 작성했던 MemberService 클래스로 예시를 들어보겠습니다.
@Transactional
public class MemberService {
private final MemberRepository memberRepository;
@Autowired
public MemberService(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
/**
* 회원 가입
*/
public Long join(Member member) {
/* 중복 회원 안됨 */
long start = System.currentTimeMillis();
try {
validateDuplicateMember(member); // 중복 회원 검증
memberRepository.save(member);
return member.getId();
} finally {
long finish = System.currentTimeMillis();
System.out.println("join = " + (finish - start) + "ms");
}
}
private void validateDuplicateMember(Member member) {
memberRepository.findByName(member.getName())
.ifPresent(m -> {
throw new IllegalStateException("이미 존재하는 회원입니다.");
});
}
/**
* 전체 회원 조회
*/
public List<Member> findMembers() {
return memberRepository.findAll();
}
public Optional<Member> findOne(Long memberID) {
return memberRepository.findById(memberID);
}
}
join 메서드를 한번 보면...
start와 finish로 시작시간, 끝시간을 구한 후 출력하게 했습니다.
머... 1개 정도야 쉽게 할 수 있겠지만,,, 10개만 넘어가도 귀찮습니다.
(그리고 실제 업무에서는 메서드가 몇십 단위가 아닌 몇백 단위부터 시작할테니... 일일이 다 쓸 생각이 안들겠죠?)
아 저 코드가 잘 돌아가는지 테스트를 해보겠습니다.
(이전에 적어뒀던 MemberServiceIntegrationTest로 실행을 해보죠)
네 join = 195ms로 잘 뜨는 것을 확인할 수 있네요...
어쨌든 이래서 AOP라는 개념이 필요합니다.
AOP: Aspect Oriented Programming(관점 지향 프로그래밍.. 이라고 번역됩니다)
AOP는 공통관심사항(cross-cutting concern)과 핵심관심사항(cor concern)을 분리합니다.
약간 용어를 헷갈려하실 수도 있을텐데, 좀 쉽게 말해보겠습니다.(그냥 저만의 방식으로...)
- 핵심관심사항: 로직의 핵심입니다. 여기서는 회원가입 & 회원조회 같은 것들이 해당됩니다.
- 공통관심사항: 모든 로직에서 쓸 수 있어야 합니다. 혹은 메서드 안에서 핵심 로직을 제외한
다른 부가적인 기능을 가리킨다고 봐도 될 것 같습니다.
여기서는 '메서드의 호출시간'이겠네요.
그럼 공통관심사항을 따로 만들어보겠습니다.
현 프로젝트의 구조입니다. (-> 와우 어느덧 추가된게 되게 많네요..)
aop라는 패키지를 만들고, TimeTraceAop라는 클래스를 만듭니다.
@Aspect
@Component
public class TimeTraceAop {
@Around("execution(* hello.hellospring..*(..))")
public Object execute(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
System.out.println("START: " + joinPoint.toString());
try {
return joinPoint.proceed();
} finally {
long finish = System.currentTimeMillis();
long timeMs = finish - start;
System.out.println("END: " + joinPoint.toString() + " " + timeMs + "ms");
}
}
}
코드는 위와같이 적습니다.
- @Aspect: 이 클래스가 aspect라는 것을 나타냅니다.
- @Component: 스프링 빈으로 등록합니다. (혹은 예전에 작성했던 SpringConfig에 추가하셔도 됩니다.)
- @Around: 특정 타겟 메서드를 감싸서 실행한다는 표시이다.
추가 : @Before: 메소드가 실행되기 전에
@After: 메소드가 실행된 후에
-> 어느 시점에 적용할 지를 정한다. - execution: 해당 패키지 경로에 포함되는 녀석을 실행한다.
여기서는 hello.hellospring.. 이라고 되어있는데, 현재 저의 패키지 구조가 hello.hellospring..으로 되어있다.
즉, 모든 메서드에 적용한다는 뜻임. - execution의 경우 본인이 원하는 것에서만 실행하게 할 수 있다.
예를 들어 "execution(* hello.hellospring.service..*(..))" 라고 쓰면 hello.hellospring.service 밑에 있는
메서드에서만 진행이 된다. - ProceedingJoinPoint: 아직 이 부분을 조금 모릅니다. 어쨌든 joinPoint.proceed()라는 메서드를 사용하면
다음 메소드로 진행을 할 수 있게 해준다고 합니다.. 좀 어렵네요.. - System.currentTimeMillis() : 현재 시간을 밀리세컨드 단위로 받게 한다.
코드에 대한 간략 설명은 이정도면 될 것 같네요..
아, 혹시 @Component가 싫고 SpringConfig로 진행하고 싶은 경우 아래와 같이 합니다.
(일단 @Component를 지워줍니다.)
@Configuration
public class SpringConfig {
private final MemberRepository memberRepository;
@Autowired
public SpringConfig(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
@Bean
public MemberService memberService() {
return new MemberService(memberRepository);
}
@Bean
public TimeTraceAop timeTraceAop() {
return new TimeTraceAop();
}
}
SpringConfig 파일에 다음과 같이 적으면 스프링 빈 등록이 됩니다.
그럼 이제 실행시켜 보죠.
실행하면 바로 이런 로그가 남는 것을 확인할 수 있습니다.
그러면 http://localhost:8080/members 주소로 들어가 봅니다. 그리고 로그 창을 보면...!!
이런 로그가 남는 것을 확인할 수 있습니다.
각각 MemberController, MemberService, JpaRepository를 사용했네요.
그럼 아까 execution을 바꿔보겠습니다.
execution(* hello.hellospring.service..*(..)) 으로 바꾸고 Rerun을 해보면...
아까보다는 로그가 줄어든 것을 확인할 수 있습니다.
MemberService만 사용했네요. service 패키지 밑에는 MemberService밖에 없으니 당연한 결과같습니다.
이렇게 간단 AOP를 진행해봤습니다.
- 회원가입 & 회원 조회 같은 핵심관심사항과 시간 측정인 공통관심사항이 분리된 것을 알 수 있습니다.
- 그리고 시간을 측정하는 로직을 별도의 공통 로직으로 만들었습니다.
- 이렇게 하면 핵심사항과 공통사항을 따로 그리고 깔끔하게 유지할 수 있습니다.
- 변경이 필요한 경우 해당 로직만 변경하면 바로 반영이 됩니다.
- 그리고 적용대상 역시 선택할 수 있습니다.
AOP를 적용하니 코드 관리가 훨씬 깔끔하네요.. ㅎㅎ
이렇게 AOP에 대해서 조금 더 알아봤습니다.
어차피 이건 기초적인 거니깐 ,,, 이걸 기반으로 AOP에 대해서 더 자세히 알았으면 하네요..
사실 AOP를 이렇게 써보긴 했지만, 제가 직접 AOP를 적용하기에는 아직은 어려울 것 같습니다.
AOP는 계속 봐도 어렵네요...
이렇게 AOP 글을 마무리하겠습니다.
'Spring' 카테고리의 다른 글
Spring - Excel 파일 다운로드(간단) (4) | 2021.01.06 |
---|---|
스프링 코어(1) - 스프링 컨테이너 & 빈 (1) | 2021.01.04 |
Spring - JdbcTemplate (0) | 2020.10.17 |
Spring - 웹 기능(MVC) (0) | 2020.09.28 |
Spring - DI(2) (0) | 2020.09.26 |