주요 개념
1. Aspect:반복해서 여러 곳에서 사용되는 공통 코드
2. Target: Aspect가 적용되는 곳
3. Advice: Aspect의 실질적인 기능에 대한 구현체
4. Joint point: Advice가 Target에 적용되는 시점(지점), 메서드 진입 / 생성자 호출 / 필드에서 값을 꺼낼 때 등, 스프링에서 Joint point는 항상 메서드 실행 시점을 의미함
5. Point cut: Joint point의 상세 스펙을 정의한 것(어디에 적용해야 하는지)
[RequestIP: 0:0:0:0:0:0:0:1] error Msg: Connection refused: localhost/127.0.0.1:8000
AOP 구현체
1. AspectJ
2. 스프링 AOP
AOP 적용방법
1. 컴파일 타임(AspectJ): 컴파일 시점(자바 파일을 클래스 파일로 만들 때)에 바이트 코드를 조작하여 AOP가 적용된 바이트 코드를 생성
2. 로드 타임(AspectJ): 컴파일은 기존 클래스 그대로 한 후, 클래스를 로딩하는 시점에 클래스 정보를 변경하는 방법
3. 런타임(스프링 AOP): 클래스를 Bean으로 만들 때 클래스 타입의 Proxy Bean을 감싸서 만들고, Proxy Bean이 Aspect 코드를 추가
스프링에서 제공하는 스프링 AOP
특징
프록시 패턴이라는 디자인 패턴을 사용해서 AOP 효과를 냄: // 프록시 패턴은 프록시 클래스를 직접 구현하는데 스프링 AOP는 프록시 객체를 자동으로 만들어 줌, 공통 기능을 구현한 클래스만 잘 구현하면 됨
스프링 빈에만 AOP 적용 가능
동적 프록시 빈을 만들어 등록시켜 줌
- 빈 라이프 사이클 중 실행되는 BeanPostProcessor 구현체를 구현
- AbstractAutoProxyCreator implements BeanPostProcessor
Proxy: 클라이언트와 타깃 사이에 투명하게 존재하며 부가기능을 제공하는 오브젝트. DI를 통해 타겟 대신 클라이언트에게 주입되며 클라이언트의 메서드 호출을 대신 받아서 타깃에 위임하며 이 과정에서 부가기능을 부여
스프링 AOP 구현 방법
1. 의존성 추가
maven
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
gradle
implementation 'org.springframework.boot:spring-boot-starter-aop'
2. 스프링 부트 애플리케이션 클래스에 @EnableAspectJAutoProxy 어노테이션 추가
AspectJ 자동 프록시 활성화
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@SpringBootApplication
@EnableAspectJAutoProxy
public class ProjectTemplateApplication {
public static void main(String[] args) {
SpringApplication.run(ProjectTemplateApplication.class, args);
}
}
3. Aspect 클래스 작성
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
@Aspect // 반복해서 여러 곳에서 사용되는 공통 코드임을 알리는 어노테이션
@Component // 스프링 Bean 등록
public class TimeTraceAop {
// Aspect 기능 구현 부분
// @Around: 메서드 실행 전/후에 실행되는 Advice 정의
// ProceedingJoinPoint: Around Advice에서만 사용되는 파라미터로, 메서드 실행을 직접 제어할 수 있는 기능을 제공
@Around("execution(* com.example.hello..*(..))") // execution으로 적용범위 지정
public Object execute(ProceedingJoinPoint joinPoint) throws Throwable {
// 메서드 실행 전 현재 시간을 start 변수에 저장
long start = System.currentTimeMillis();
// 메서드 실행 전 현재 시간 출력
System.out.println("START: " + joinPoint.toString());
try {
// 메서드 실행
return joinPoint.proceed();
} finally { // finally를 사용하여 메서드가 에러가 나도 실행되도록 처리
// 메서드 실행 후 현재 시간을 finish 변수에 저장
long finish = System.currentTimeMillis();
// 현재시간-시작시간을 연산하여 메서드 실행에 걸린 시간을 timeMs 변수에 저장
long timeMs = finish - start;
// 메서드 종료 후 실행
System.out.println("END: " + joinPoint.toString() +" " + timeMs + "ms" );
}
}
}
1) 어노테이션 기반
어노테이션 | 설명 |
@Around | Advice가 타겟 메서드를 감싸 타겟 메서드 호출 전, 후에 Advice 기능 수행 |
@Before | Advice 타겟 메서드가 호출되기 전에 Advice 기능 수행 |
@After | 타겟 메서드의 결과에 관계없이 타겟 메서드과 완료되면 Advice 기능 수행 |
@AfterRunning | 타겟 메서드가 성공적으로 결과값을 반환 한 후에 Advice 기능 수행 |
@AfterThrowing | 타겟 메서드가 수행 중 예외를 던지면 Advice 기능 수행 |
2) execution expression
execution 패턴으로 경로 지정
구조
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)
- modifiers-pattern: 메서드의 접근 제어자 public, protected, private, static, final 등을 지정
- ret-type-pattern: 메서드의 반환 타입을 지정, 클래스명이나 와일드카드(*) 사용
- declaring-type-pattern: 메서드를 정의한 클래스나 인터페이스 지정
- name-pattern: 메서드의 이름을 지정, 와일드카드(*)를 사용가능
- param-pattern: 메서드의 매개변수를 지정, 와일드카드(*)를 사용하여 임의의 타입을 나타낼 수 있
- throws-pattern: 메서드가 던지는 예외를 지정
응용하기
1. 커스텀 어노테이션이 붙은 pointcut에 Aspect 실행하기
1) @PerfLogging 커스텀 어노테이션 생성
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface PerfLogging {
}
2) @Around 어노테이션을 사용하여 해당 어노테이션이 붙은 메서드에 성능 로깅 추가
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class PerfLoggingAspect {
private static final Logger logger = LoggerFactory.getLogger(PerfLoggingAspect.class);
@Around("@annotation(PerfLogging)") // 적용될 어노테이션을 명시할 수 있다.
public Object logPerf(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
Object result = joinPoint.proceed();
long endTime = System.currentTimeMillis();
long executionTime = endTime - startTime;
logger.info(joinPoint.getSignature() + " executed in " + executionTime + "ms");
return result;
}
}
3) 해당 메서드를 적용시킬 특정 메서드에 @PerfLogging 어노테이션을 붙여주기만 하면 logPerf() 기능 동작
import org.springframework.stereotype.Service;
@Service
public class MyService {
@PerfLogging
public void myMethod() {
// 메서드 내용
}
}
2. 특정 Bean 전체에 해당 기능을 적용시키기
1) @Around 어노테이션을 사용하여 특정 빈에 적용될 Aspect 정의
bean(simpleService)를 지정하여 simpleService라는 이름의 빈에만 해당 Aspect가 적용되도록 함
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class MyServiceAspect {
private static final Logger logger = LoggerFactory.getLogger(MyServiceAspect.class);
@Around("bean(simpleService)") //@Around 어노테이션에 bean(simpleServcieEvent)처럼 적용될 빈을 명시할 수 있음
public Object logMethodExecution(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
Object result = joinPoint.proceed();
long endTime = System.currentTimeMillis();
long executionTime = endTime - startTime;
logger.info(joinPoint.getSignature() + " executed in " + executionTime + "ms");
r
해당 빈(simpleService)이 가지고 있는 모든 public 메서드는 위에서 정의된 logMethodExecution() 메서드를 실행하여 로깅 기능이 적용됨
import org.springframework.stereotype.Service;
@Service("simpleService")
public class SimpleService {
public void method1() {
// 메서드 내용(적용할 기능)
}
public void method2() {
// 메서드 내용(적용할 기능)
}
}
'Spring > Springboot' 카테고리의 다른 글
Controller, Service, Repository의 역할 (0) | 2022.11.08 |
---|---|
Controller와 HTTP Request 메시지 (0) | 2022.11.08 |
빌드 관리 도구 Maven과 Gradle 비교 (0) | 2022.11.08 |
HTTP 메시지 이해 (0) | 2022.09.05 |
CORS(Cross-Origin Request) 알아보기 (0) | 2022.09.04 |