[Study-6, 7주차] AOP + QueryDSL + (N+1) 문제 및 RequestDTO 수정 + JPA Auditing 사용 (regDate, modDate) - ①
[Study-6, 7주차] AOP + QueryDSL + (N+1) 문제 및 RequestDTO 수정 + JPA Auditing 사용 (regDate, modDate) - ①
[Study-5주차] @Query + 조회 기능 (PostMan) + 연관 관계 추가(Visit-Vet) + 더미 테스트 코드 [Study-5주차] @Query + 조회 기능 (PostMan) + 연관 관계 추가(Visit-Vet) + 더미 테스트 코드 [Study-4주차] MapStruct, Exception
soohykeee.tistory.com
이번에는 AOP를 추가해줄 것이다. 기본적으로 AOP에 대해 알아야 적용이 가능하기에 AOP 정리한 내용을 아래에 첨부해놓겠다. 해당 사항을 읽어본 후 적용해보는 것이 효율적일것이다.
[Study] AOP ? (정의 + 용어 + 적용 방식)
[Study] AOP ? (정의 + 용어 + 적용 방식)
[Study] Spring 정리 (IoC, DI, AOP, POJO, MVC) [Study] Spring 정리 (IoC, DI, AOP, POJO, MVC) Spring 이란? 정의 Spring이란 간단히 말하면 Java 기반의 웹 어플리케이션을 개발할 수 있는 오픈소스 프레임워크이다. 더욱
soohykeee.tistory.com
처음에 AOP를 적용시켜본적이 없어서, 스터디원들의 코드를 참고하며 코드를 개발했다. 둘다 로깅을 AOP로 빼줬는데 한명은 메소드의 실행시간을 구해주는 로깅이였고, 한명은 클래스명과 메소드명을 보여주는 로기이였다. 스터디 진행 및 구글링을 통해 알아보니 2가지 모두 적용시켜주는 것이 좋을 것 같아 2가지 모두 적용시켜주기로 하였다.
처음에는 두가지 기능을 분리하여 작성해줄까했지만, 하나로 합쳐도 지장이 없을 것 같아서 하나의 aop로 작성해주었다.
AOP를 사용해주기 위해서 build.gradle에 의존성을 추가해줬다.
// AOP
implementation 'org.springframework.boot:spring-boot-starter-aop'
package kr.co.jshpetclinicstudy.infra.aop;
@Slf4j
@Aspect
@Component
public class MethodLoggingAspect {
@Pointcut("execution(* kr.co.jshpetclinicstudy.service.OwnerService.*(..))")
private void ownerService() {
}
@Pointcut("execution(* kr.co.jshpetclinicstudy.service.PetService.*(..))")
private void petService() {
}
@Pointcut("execution(* kr.co.jshpetclinicstudy.service.VetService.*(..))")
private void vetService() {
}
@Pointcut("execution(* kr.co.jshpetclinicstudy.service.VisitService.*(..))")
private void visitService() {
}
@Around("ownerService() || petService() || vetService() || visitService()")
public Object logServiceTime(ProceedingJoinPoint joinPoint) throws Throwable {
String className = joinPoint.getTarget().getClass().getSimpleName();
String methodName = joinPoint.getSignature().getName();
StopWatch stopWatch = new StopWatch();
stopWatch.start();
Object result = null;
try {
// 성공 로깅
result = joinPoint.proceed();
stopWatch.stop();
log.info("[serviceTime] ClassName:{} - MethodName:{} : {}ms", className, methodName, stopWatch.getTotalTimeMillis());
} catch (Throwable throwable) {
// 예외 정보 로깅
log.error("[EXCEPTION] {}-{} : {}", className, methodName, throwable.getMessage(), throwable);
throw throwable;
}
return result;
}
}
우선 해당 코드를 작성해준 후, 정상적으로 실행이 되는지 확인해보면 아래와 같이 정상적으로 작동하는 것을 확인할 수 있다.
PostMan을 통해 실행해보면 다음 처럼 예외가 발생하지 않았을 때는 성공적으로 클래스명, 메소드명, 소요시간이 나오게 되고, 예외가 발생했을 때는 클래스명, 메소드명, 예외 메시지가 log로 출력되는 것을 확인할 수 있다.
위의 코드를 하나씩 살펴보겠다. 우선 어노테이션부터 살펴보면, @Slf4j, @Aspect, @Componet를 사용해주었다.
@Component를 사용해준 이유는?
@Aspect를 사용하면 Component Scan 대상이 된다고 배웠었지만, 구글링을 해보니 @Aspect 어노테이션은 기본 스캔 대상에 포함되지 않는 것을 알게되었다. 따라서 해당 AOP 객체를 Bean으로 등록해주기 위해서는 @Aspect 뿐 아니라 @Component를 붙여줘야 하는 것이다.
스프링 공식 문서를 살펴보게되면, @Aspect + @Component 를 둘다 사용하는 것을 권장한다.
Autodetecting aspects through component scanning
You can register aspect classes as regular beans in your Spring XML configuration, via @Bean methods in @Configuration classes,
or have Spring autodetect them through classpath scanning — the same as any other Spring-managed bean.
However, note that the @Aspect annotation is not sufficient for autodetection in the classpath.
For that purpose, you need to add a separate @Component annotation
(or, alternatively, a custom stereotype annotation that qualifies, as per the rules of Spring’s component scanner).
@Slf4j vs @Log4j 둘 중 무엇을 사용할까?
필자는 그동안 log를 출력할 때 @Log4j 를 사용해주었다. 하지만 이번에 @Slf4j 를 알게되어 찾아보니 @Slf4j 가 요새 더욱 권장되고 있고 많이들 사용하는 추세라고 하여 해당 어노테이션을 작성해주었다. 간단하게 정리해보면 다음과 같다.
log4j과 slf4j는 모두 로깅 라이브러리이며, 각각의 장단점이 있다.
log4j는 기능이 매우 다양하며 설정이 유연하다. 또한 대규모 프로젝트에서도 안정적으로 동작하며, 일반적으로 slf4j보다 더 빠르다는 평가를 받고 있다. 그러나 log4j는 Apache Software Foundation에서 관리되지 않고 있으며, 2015년 이후로 업데이트가 중지되었다.
반면에 slf4j는 인터페이스로서 로깅 기능을 제공하므로, 실제 로깅 기능은 해당 인터페이스를 구현한 로깅 라이브러리(예: logback)에서 수행이 된다. 이는 slf4j를 사용하여 다른 로깅 라이브러리로의 전환을 용이하게 만들어주며, 호환성과 유연성이 높다는 장점이 있다. 또한 slf4j는 로깅 기능에 필요한 API만 제공하므로, 불필요한 메모리 사용을 줄일 수 있다.
따라서, 최근에는 대부분의 경우 slf4j를 사용하는 것이 좋다. slf4j는 log4j보다 호환성과 유연성이 높으며, 다른 로깅 라이브러리로의 전환을 용이하게 만들어준다. 또한, slf4j는 log4j와 비교하여 성능적인 차이가 크지 않다.
AOP는 Controller? Service? 어디에 적용해줄까?
각 서비스, Owner, Pet, Visit, Vet 별로 @Pointcut을 사용하여 분리한 것을 볼 수 있다. 또한 aop 적용을 controller가 아닌 service에 적용을 해주었다. 기본적으로 어디에 aop를 적용하는지에 대한 정답은 없다.하지만, AOP는 주로 비즈니스 로직이 구현되는 Service 계층에서 자주 사용이 된다고 한다.
Service 계층에서는 주로 비즈니스 로직이 수행이 되는데, 예를 들어, 회원 가입 기능을 구현하는 경우, 회원 가입 로직에 대한 AOP를 Service에 적용하여 입력값 검증, 로깅, 트랜잭션 처리 등의 공통적인 로직을 처리할 수 있다.
반면에 Controller 계층에서 AOP를 사용하는 경우, 로그를 기록하거나 인증, 권한 체크 등의 처리를 할 수 있다. 예를 들어, 웹 요청에 대한 로그를 기록하거나, 특정 URI에 대한 접근 권한 체크 등을 구현할 수 있다.
따라서, AOP를 어디에 적용할지는 어플리케이션의 구조와 요구사항에 따라 다르며, 주로 비즈니스 로직이 구현되는 Service 계층에서 적용하는 것이 일반적이다. 이러한 이유로 Service에 AOP를 적용해주었다.
그 다음 @Around에 pointcut을 명시해주었다. 클래스명과 메소드명은 위에서 보이는 것처럼 Target에 접근하여 가져오면 되고, 실행시간을 출력해주기 위해서 Spring에서 제공해주는 StopWatch 객체를 사용해주었다. 추가적으로 예외가 발생했을 경우에는 따로 로그를 출력해주도록 해주었다.