728x90
AOP(Aspect Oriented Programming)의 개념
- 관점 지향 프로그래밍
- 어떤 로직을 기준으로 핵심적인 관점, 부가적인 관점으로 나눠보고 그 관점을 기준으로 각각 모듈화
- 핵심적인 관점 : 개발자가 적용하고자 하는 핵심 로직
- 부가적인 관점 : 핵심 로직을 수행하기 위해 필요한 DB 연결(JDBC), 로깅, 파일 입출력 등
AOP의 등장 배경
- 회사 상사가 회원 가입 하는 시간을 측정하는 로그를 남겨 달라고 지시
코드 수정 전
public void join(JoinRequest joinRequest) {
memberRepository.save(joinRequest.toMember());
}
코드 수정 후
public void join(JoinRequest joinRequest) {
long begin = System.currentTimeMillis();
try{
memberRepository.save(joinRequest.toMember());
} finally {
log.info("join spent {} ms", System.currentTimeMillis() - begin);
}
}
- 이후 상사가 시간을 측정하는 코드를 모든 Service에 적용하라고 지시하였는데, 작업 중 공통적인 코드를 발견하게 됨
회원 가입 코드
public void join(JoinRequest joinRequest) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
try{
memberRepository.save(joinRequest.toMember());
} finally {
stopWatch.stop();
log.info("join spent {} ms", stopWatch.getLastTaskTimeMillis());
}
}
로그인 코드
public void join(LoginRequest loginRequest) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
try{
memberRepository.findByUsername(LoginRequest.getUsername());
...
} finally {
stopWatch.stop();
log.info("join spent {} ms", stopWatch.getLastTaskTimeMillis());
}
}
- 핵심 로직
- try{} 구문 안에 있는 로직
- Ex) memberRepository.save(), memberRepository.findByUsername()
- 부가 기능(로깅)
- try{} 구문을 제외한 나머지
- Ex) StopWatch, finally{} 구문
AOP의 필요성
- 공통적인 부가 기능을 여러 메서드에 반복적으로 작성하는 것은 코드 중복을 초래
- AOP는 이러한 공통 부가 기능(예: 로깅, 성능 측정 등)을 핵심 로직에서 분리하여 관리하기 위해 등장한 개념
- AOP를 활용하면 코드 중복 없이 공통 로직을 모듈화하여 적용 가능
- 따라서, AOP를 적용하면 이와 같은 부가 기능을 별도로 관리하고, 필요한 비즈니스 로직에 공통 부가 기능을 쉽게 적용할 수 있어 유지보수성과 가독성이 크게 향상
AOP의 용어
- Aspect
- 공통적으로 쓰이는 부가 기능의 코드를 모듈화
Ex) 로깅, 트랙잭션 관리, 예외 처리
- 공통적으로 쓰이는 부가 기능의 코드를 모듈화
- Target
- Aspect가 적용 되는 대상 (Class, Method 등)
- 특정 기능이 부여될 코드
- Advice
- Aspect 기능에 대한 실제 구현체
→ 어떤 부가 기능인지 정의 - 다양한 어노테이션을 사용하여 특정 시점에 Advice가 실행되도록 지정 가능
- @Before : 타겟 메서드가 호출되기 전에 실행
- @After : 타겟 메서드가 종료된 후(결과에 상관없이) 실행
- @AfterReturning : 타겟 메서드가 성공적으로 리턴 된 후 실행
- @AfterThrowing : 타겟 메서드 수행 중 예외가 발생한 경우 실행
- @Around : 타겟 메서드의 호출 전과 후에 모두 실행
- Aspect 기능에 대한 실제 구현체
- Join Point
- Adivce가 Target에 적용될 수 있는 지점
Ex) 메서드 호출, 생성자 호출, 필드 접근 등 - 스프링 AOP는 메서드 실행 시점만 적용
- Adivce가 Target에 적용될 수 있는 지점
- Point Cut
- Advice가 적용될 Join Point의 조건을 정의
→ 즉, 어떤 메서드에 Advice를 적용할지 상세하게 설정 가능 - 메서드 이름 패턴이나 특정 패키지의 메서드에만 적용하도록 설정 가능
- Advice가 적용될 Join Point의 조건을 정의
- Proxy
- 클라이언트와 실제 타겟 객체 사이에 존재하는 중개 객체
- 클라이언트는 Proxy를 통해 타겟 메서드를 호출하게 되며, Proxy는 그 과정에서 부가 기능을 수행
- Spring에서는 DI를 통해 Proxy 객체가 주입
→ DI를 통해 타겟 대신 클라이언트에게 주입되며 클라이언트의 메소드 호출을 대신 받아서 타겟에 위임하며 이 과정에서 부가 기능을 부여
다양한 AOP 적용 방법
- AspectJ : 컴파일 단계나 바이트코드 조작을 통해 AOP를 구현하는 대표적인 프레임워크
- Spring AOP : 프록시 패턴을 이용해 AOP 기능을 제공하는 프레임워크
컴파일 시점 (컴파일 타임 위빙)
- A.java -- (AOP) → A.class(AspectJ)
- A.java 파일을 컴파일하면서 AspectJ가 AOP 코드를 삽입하여 A.class 파일에 부가 기능을 포함
- 즉, 컴파일 단계에서 부가 기능이 클래스에 삽입되기 때문에 이후 코드 수정 없이 바로 실행
바이트코드 조작 (로드 타임 위빙)
- A.java → A.class -- (AOP) → 메모리(AspectJ)
- A.java 파일이 일반적으로 컴파일된 후 AspectJ가 A.class 파일을 메모리에 로드할 때 바이트코드를 수정하여 부가 기능을 삽입
- 클래스 로더를 통해 클래스가 JVM에 로드되는 시점을 가로채서 바이트코드를 조작하며, .java 파일과 최종 실행되는 .class 파일 내용이 달라질 수 있음
이 방법을 사용하는 이유
- 컨테이너 독립성
- DI 컨테이너나 Spring을 사용하지 않는 환경에서도 AOP 적용이 가능
- 더 강력하고 유연한 AOP
- 프록시 방식보다 더 다양한 곳에 부가 기능을 삽입 가능
- 메서드 호출뿐만 아니라 생성자, 필드 접근, 정적 초기화 등에도 적용 가능
프록시 패턴 (Spring AOP)
- 프록시의 역할
- 클라이언트와 타겟 객체 사이에 위치하여 타겟 객체에 대한 메서드 호출을 가로채고, 메서드 호출 전후로 부가 기능 추가
- Spring DI 컨테이너를 통해 프록시 객체를 빈으로 등록하고 주입하여 실행
- 프록시 방식의 장점
- 스프링 컨테이너와 JDK의 다이나믹 프록시 혹은 CGLIB 프록시를 통해 쉽게 AOP를 구현
- 특별한 환경이나 추가 기술이 필요하지 않으며, Spring 컨테이너에 의존해 동작하기 때문에 Spring 환경에서 빠르게 적용 가능
- 제한
- 프록시는 메서드 호출을 대상으로 하므로 메서드 이외의 요소(예: 필드 접근, 생성자 호출 등)에는 AOP를 적용 불가
- 이와 같은 제한으로 인해 유연성은 AspectJ의 바이트코드 조작 방식보다 낮지만, 필요한 곳에만 AOP를 빠르게 적용하기에는 충분
AOP를 사용하지 않는 기본적인 시간 측정 Bean(ExampleService) 구현
인터페이스 구현
public interface ExampleService {
public void start();
public void process();
public void end();
}
구현체(ExampleServiceImpl) 구현
@Component
public class ExampleServiceImpl implements ExampleService {
@Override
public void start() {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
try{
Thread.sleep(1000);
System.out.println("start");
}catch(Exception e){
e.printStackTrace();
}
stopWatch.stop();
System.out.println(stopWatch.prettyPrint());
}
@Override
public void process() {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
try{
Thread.sleep(1000);
System.out.println("processing");
}catch(Exception e){
e.printStackTrace();
}
stopWatch.stop();
System.out.println(stopWatch.prettyPrint());
}
@Override
public void end() {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
try{
Thread.sleep(1000);
System.out.println("ended");
}catch(Exception e){
e.printStackTrace();
}
stopWatch.stop();
System.out.println(stopWatch.prettyPrint());
}
}
테스트 코드
class ExampleServiceImplTest {
@Test
public void exampleTest() throws Exception {
ExampleService service = new ExampleServiceImpl();
service.start();
service.process();
service.end();
}
}
결과
AOP를 이용해 시간측정 Bean(ExampleService) 개선 - Execution expression
공통 모듈 분석
- 이전 코드(ExampleServiceImpl)에서 StopWatch를 이용해 성능 측정을 하는부분이 start, process, end 메서드에 모두 포함 (공통 코드)
- 따라서 해당 코드를 공통 모듈로 묶어서 정의 가능
공통 모듈 추출 및 구현
@Component
@Aspect
public class PerfAspect {
// io.security.corespringsecurity.aopsecurity.ExampleService 클래스내의 모든 메서드에 공통 모듈을 적용
@Around(value = "execution(* io.security.corespringsecurity.aopsecurity.ExampleService.*(..))")
public Object logPerf(ProceedingJoinPoint pjp) throws Throwable {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
Object retVal = pjp.proceed();
stopWatch.stop();
System.out.println(stopWatch.prettyPrint());
return retVal;
}
}
- @Component : Spring이 이 클래스를 빈으로 관리하도록 지정
- @Aspect : 이 클래스가 AOP의 Aspect임을 나타냄
- @Around(value = "execution(* io.security.corespringsecurity.aopsecurity.ExampleService.*(..))")
- @Around : 이 Advice는 대상 메서드의 실행 전과 후에 실행
- execution : 이 Pointcut 표현식은 메서드의 실행 시점을 잡아내는 데 사용
- 표현식 구성
- * : 반환 타입 지정, 여기서는 *이므로 모든 반환 타입을 대상
- io.security.corespringsecurity.aopsecurity.ExampleService : 특정 패키지와 클래스 경로, 이 클래스의 모든 메서드에 AOP가 적용
- * : 메서드명 지정, *로 되어 있어 ExampleService 클래스의 모든 메서드가 포함
- ( .. ) : 메서드의 파라미터를 지정, ( .. )은 모든 타입과 개수의 파라미터를 허용한다는 의미
- 의미 : 이 코드는 ExampleService 클래스의 모든 메서드가 실행될 때마다, 해당 메서드 실행 전후로 @Around Advice가 적용
구현체 공통 모듈 삭제
@Component
public class ExampleServiceImpl implements ExampleService {
@Override
public void start() {
try {
Thread.sleep(1000);
System.out.println("start");
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void process() {
try {
Thread.sleep(1000);
System.out.println("processing");
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void end() {
try {
Thread.sleep(1000);
System.out.println("ended");
} catch (Exception e) {
e.printStackTrace();
}
}
}
테스트 코드
@SpringBootTest
class ExampleServiceImplTest {
@Autowired
ExampleService service;
@Test
public void exampleTest() throws Exception {
service.start();
service.process();
service.end();
}
}
- 이전 코드와 차이점
- @Aspect 를 적용하기 위해 @SpringBootTest 어노테이션을 추가해서 해당 @Aspect를 등록해주고 ExampleService DI
AOP를 이용해 시간측정 Bean(ExampleService) 개선 - Annotation
- 구현체의 특정 메서드들만 부가기능을 부여하기 위해 기존 Execution expression 방식을 쓰면 표현식을 다 작성해야하기 때문에 번거로움
- 따라서 Annotation을 이용하여 편하게 적용
어노테이션 구현
- @Documented
- 이 어노테이션을 사용하는 메서드의 문서화에 포함될 수 있도록 지정
- @Target(ElementType.METHOD)
- 이 어노테이션은 메서드에만 적용할 수 있도록 제한
- @Retention(RetentionPolicy.CLASS)
- 이 어노테이션 정보는 컴파일된 클래스 파일에 포함되지만, 런타임 시에는 JVM에 로드되지 않음
- 런타임에 사용하려면 RetentionPolicy.RUNTIME으로 설정
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface PerfLogging {
}
Aspect 클래스 수정 (Execution Expression → Annotation)
@Component
@Aspect
public class PerfAspect {
@Around("@annotation(PerfLogging)")
public Object logPerf(ProceedingJoinPoint pjp) throws Throwable {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
Object retVal = pjp.proceed();
stopWatch.stop();
System.out.println(stopWatch.prettyPrint());
return retVal;
}
}
- @Around("@annotation(PerfLogging)")
- @PerfLogging 어노테이션이 적용된 메서드를 대상으로 AOP Advice를 적용하겠다는 의미
- logPerf 메서드
- ProceedingJoinPoint pjp : 대상 메서드를 호출할 수 있는 객체, 이를 통해 원래 메서드를 실행하면서 부가 기능 추가
- StopWatch : 메서드 실행 시간을 측정하기 위해 StopWatch를 사용
- pjp.proceed() : 원래의 대상 메서드를 호출
- stopWatch.prettyPrint() : 메서드의 실행 시간 측정 결과를 출력
구현체(ExampleServiceImpl)에 어노테이션 추가
@Component
public class ExampleServiceImpl implements ExampleService {
@PerfLogging
@Override
public void start() {
try {
Thread.sleep(1000);
System.out.println("start");
} catch (Exception e) {
e.printStackTrace();
}
}
@PerfLogging
@Override
public void process() {
try {
Thread.sleep(1000);
System.out.println("processing");
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void end() {
try {
Thread.sleep(1000);
System.out.println("ended");
} catch (Exception e) {
e.printStackTrace();
}
}
}
- 동작 방식
- AOP 설정 확인
- PerfAspect 클래스에 정의된 @Around("@annotation(PerfLogging)") 어드바이스는 @PerfLogging 어노테이션이 적용된 메서드들을 대상으로 함
- @PerfLogging이 적용된 메서드 찾기
- @PerfLogging 어노테이션이 붙은 start와 process 메서드는 PerfAspect의 logPerf 메서드에 의해 감싸짐
- end 메서드에는 @PerfLogging이 없기 때문에, AOP가 적용되지 않음
- 메서드 호출 시 AOP 실행
- start나 process 메서드를 호출하면 AOP 프레임워크는 해당 메서드를 직접 실행하지 않고 logPerf 어드바이스 메서드를 먼저 실행
- 성능 측정
- logPerf 메서드에서 StopWatch를 사용해 메서드가 시작하는 시점부터 끝나는 시점까지의 시간을 측정
- ProceedingJoinPoint의 pjp.proceed() 호출로 원래 메서드(start 또는 process)가 실행
- 메서드가 완료되면 StopWatch를 종료하고, 소요 시간을 로그로 출력
- AOP 설정 확인
Ex) exampleService.start();
- logPerf 메서드가 start 메서드를 감싸고 실행
- StopWatch가 시작되고 start 메서드가 실행
- start 메서드는 1초 대기 후 "start"를 출력하고 종료
- StopWatch가 중지되고, 실행 시간을 출력
실행 결과
AOP를 이용해 시간측정 Bean(ExampleService) 개선 - Bean
@Component
@Aspect
public class PerfAspect {
@Around("bean(exampleServiceImpl)")
public Object logPerf(ProceedingJoinPoint pjp) throws Throwable {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
Object retVal = pjp.proceed();
stopWatch.stop();
System.out.println(stopWatch.prettyPrint());
return retVal;
}
}
bean(beanName)
- bean()은 AOP의 포인트컷 표현식 중 하나로, 특정 이름의 빈에 대해서만 Aspect를 적용하도록 지정
- exampleServiceImpl이라는 이름의 빈에 대해 logPerf 메서드를 실행하도록 설정
- 이 빈의 모든 메서드에 대해 AOP가 적용
참고 자료
https://hyunki99.tistory.com/69
[Spring] 자바 스프링 AOP란?, AOP 개념 정리 (프록시, AspectJ)
https://www.youtube.com/watch?v=Hm0w_9ngDpM 우아한테크코스 제이님의 AOP 테코톡을 정리해 봤습니다. AOP는 Aspect-Oriented-Programming의 약자로 관점 지향 프로그래밍이라는 뜻입니다. 이름만 봐서는, OOP(객체 지
hyunki99.tistory.com
https://catsbi.oopy.io/fb62f86a-44d2-48e7-bb9d-8b937577c86c
AOP(Aspect Oriented Programming)
1. 개요
catsbi.oopy.io
728x90
'Language > Spring' 카테고리의 다른 글
[Spring] DispatcherServlet의 개념과 흐름 (0) | 2024.11.17 |
---|---|
[공통처리] Filter, Interceptor, AOP 차이점 및 흐름과 역할 (0) | 2024.11.15 |
[Entity] 엔티티와 영속성 컨텍스트 (1) | 2024.11.09 |
[JPA/Hibernate] Hibernate 기초 (1) | 2024.11.09 |
[JPA] JPA의 개념 / JPA를 사용해야하는 상황과 그렇지 못한 상황 (1) | 2024.11.08 |