AOP란?
- = Aspect Oriented Programming
- 여러 객체에서 공통적으로 사용하고 있는 기능을 분리해서 모듈화하고 재사용하는 프로그래밍 기법
- 핵심 기능과 공통 기능의 구현을 분리하여 핵심 기능의 코드 수정없이 공통 기능 적용 가능
- 여러 클래스, 메서드에 공통적으로 나타나는 코드들을 Concern 이라한다.
- 각 클래스에 있는 Crosscutting Concerns(흩어진 관심사)를 Aspect로 모듈화하고 핵심적인 비즈니스 로직에서 분리하여 재사용하겠다는 것이 AOP의 취지이다.
- 대표적으로 인증, 트랜잭션 관리, 로깅 등과 같은 인프라 로직을 AOP로 처리한다.
- 스프링은 프록시 기반 AOP를 지원한다.
AOP 적용 방식
- 컴파일 시점에 코드에 공통기능을 삽입
- AOP 개발 도구가 소스 코드를 컴파일 하기 전에 공통 구현 코드를 소스에 삽입하는 방식
- 스프링 AOP에서 지원 X, AspectJ 같은 AOP 전용 도구를 사용해서 적용
- 클래스 로딩 시점(로드 타임)에 바이트 코드에 공통 기능을 삽입
- 클래스를 로딩할 때 바이트 코드에 공통 기능을 클래스에 삽입하는 방식
- 스프링 AOP에서 지원 X, AspectJ 같은 AOP 전용 도구를 사용해서 적용
- 런타임에 프록시 객체를 생성해서 공통 기능을 삽입
- 프록시 객체를 생성하고 그 안에 실제 객체를 넣어 실제객체를 통해 핵심기능을 실행하고 공통 기능은 프록시 객체가 처리하는 방식
- 스프링 AOP에서 지원
- 스프링 AOP가 프록시 객체를 자동으로 생성하여 처리해주기 때문에 프록시 클래스를 직접 구현할 필요가 없다.
- 개발자는 공통 기능을 구현한 클래스만 구현하면 된다.
AOP 프록시 동작 방식
핵심 기능을 가진 객체와 프록시와의 관계
- 프록시는 실제 객체가 인터페이스를 상속하면 인터페이스를 이용해서 프록시를 생성합니다. (실제 객체의 하위 X)
AOP 용어
- Aspect (관점,관심)
- 여러 객체에 공통으로 적용되는 기능
- 트랜잭션(@Transactional) 이나 캐시(@Cacheable), 보안 등 이 좋은 예
- 클래스 단위에서 @Aspect 로 적용
- Aspect 클래스 내부에는 Advice와 Point Cut이 들어간다.
- Advice (조언)
- AOP에서 실제로 적용하는 기능을 뜻함
- 언제 공통 관심 기능을 핵심 로직에 적용할지를 정의
- 실제로 적용할 기능을 작성한다.
- 어노테이션에는 포인트컷을 알려줘야한다.
- 종류
- Before
- 대상 객체의 메서드 호출 전에 공통 기능을 실행
- After Returning
- 대상 객체의 메서드가 익셉션 없이 실행된 이후에 공통기능을 실행
- After Throwing
- 대상 객체의 메서드를 실행하는 도중 익셉션이 발생한 경우에 공통기능을 실행
- After
- 익셉션 발생여부에 상관없이 대상객체의 메서드 실행 후 공통 기능 실행
- try - catch -finally 에서 finally 와 유사
- Around
- 대상 객체 메서드 실행하기 전/후 , 익셉션 발생 시점 등 다양한 시점에 공통 기능 실행
- 주로 사용한다!
- Before
- 종류
- Joinpoint (연결 포인트)
- Advice를 적용 할 수 있는 지점, 실행될 수 있는 연결 포인트
- 메서드 호출, 필드 값 변경 등
- 스프링은 프록시를 이용해서 AOP를 구현하기 때문에 메서드 호출에 대한 Joinpoint만 지원한다.
- Pointcut (포인트 선택방법)
- 실제 Advice가 적용되는 Joinpoint
- Joinpoint 중에서 해당 Aspect를 적용할 대상을 뽑을 조건식
- 스프링에서는 정규표현식이나 AspectJ의 문법을 이용하여 Pointcut을 정의할 수 있다
- 적용 대상을 의미한다.
- 여러 Aspect에서 공통으로 사용하는 Pointcut이 있다면 별도 클래스로 분리하는 것이 좋다.
- 분리한 Pointcut 클래스는 따로 빈으로 등록할 필요가 없다. 어차피 advice 어노테이션에서 해당 클래스에 접근 가능하다면 사용할 수 있기 때문.
- Target
- Advice가 적용될 대상 (클래스,메서드 등)
- AOP Proxy
- 대상 오브젝트에 Aspect를 적용하는 경우, Advice를 덧붙이기 위해 하는 작업을 의미
- 주로 CGLIB(실행 중에 실시간으로 코드를 생성하는 라이브러리) 프록시를 사용하여 프록싱 처리한다.
- Weaving
- Advice를 핵심 로직 코드에 적용하는 것을 의미
AOP 예시 코드 - 기본
테스트 실행 파일 : AppRun.java
package com.dokuny.spring_study;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
@Component
public class AppRun implements ApplicationRunner {
@Autowired
private Hello hello;
@Autowired
private World world;
@Override
public void run(ApplicationArguments args) throws Exception {
hello.print();
world.print();
}
}
- 스프링 부트 환경이기 때문에 테스트하기 위해서 ApplicationRunner 인터페이스를 구현하는 클래스를 작성했습니다.
- 만약 부트환경이 아니라면, 설정 파일에 @EnableAspectJAutoProxy 를 붙여줘야합니다.
핵심 기능 클래스 : HelloWorld.interface, Hello.java, World.java
package com.dokuny.spring_study;
// 인터페이스
public interface HelloWorld {
void print();
}
package com.dokuny.spring_study;
import org.springframework.stereotype.Component;
@Component
public class Hello implements HelloWorld{
@Override
public void print() {
System.out.println("Hello!");
}
}
package com.dokuny.spring_study;
import org.springframework.stereotype.Component;
@Component
public class World implements HelloWorld {
@Override
public void print() {
System.out.println("World!");
}
}
- 핵심 기능을 가지고 있는 HelloWorld 인터페이스를 구현한 클래스들입니다.
Aspect 클래스 : TestAspect.java
package com.dokuny.spring_study;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class TestAspect {
// PointCut 설정
@Pointcut("execution(public void HelloWorld.print())")
private void publicTaget() {}
// Advice 설정
@Around("publicTaget()")
public Object measure(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("이전");
Object result = joinPoint.proceed(); // 공통 기능 실행
System.out.println("이후");
return result;
}
}
- @Component
- Aspect 클래스를 빈으로 등록해야하기 때문에 붙입니다.
- @Aspect
- 이 클래스가 Aspect 클래스임을 알려줍니다.
- @EnableAspectJAutoProxy 를 가진 설정 파일이 @Aspect 를 가진 클래스를 찾아서 실행시킵니다.
- @Pointcut
- execution 명시자 표현식을 통해서 공통 기능을 적용할 Pointcut을 정의합니다.
- 스프링 AOP는 public 메소드에만 적용할 수 있기 때문에 수식어패턴은 public만 유의미합니다.
- * 은 모든 값을 표현, .. 은 0개 이상이라는 뜻
- execution(public 메서드 대상) 뿐만 아니라 within(특정 패키지 대상) 등도 사용 가능합니다.
- 더불어, @Pointcut("포인트컷1 && 포인트컷2") 이런식으로도 사용 가능합니다.
execution(수식어패턴? 리턴타입패턴 클래스이름패턴?메서드이름패턴(파라미터패턴))
- execution 명시자 표현식을 통해서 공통 기능을 적용할 Pointcut을 정의합니다.
- @Around
- Advice를 설정합니다. 어노테이션은 Advice 종류에서 선택해서 사용합니다.
- @Around(”Pointcut Name()”) 형식으로 사용합니다.
- 미리 작성한 포인트컷 대신 직접적으로 execution 표현식을 사용하여 포인트컷을 넣을 수 있습니다.
- 어노테이션 방식으로도 적용 가능합니다. 이 부분은 후술하겠습니다.
- ProceedingJoinPoint
- Advice가 적용되는 대상을 의미합니다.
- 즉, 적용 대상 메소드를 의미하며 이 객체를 통해 대상 메소드에 대한 정보를 가져올 수 있습니다.
- Object proceed() : 실제 객체의 메소드를 실행합니다.
- Signature getSignature() : 호출되는 메서드에 대한 정보를 구합니다.
- Object getTarget() : 대상 객체를 구합니다.
- Object[] getArgs() : 파라미터 목록을 구합니다
- 실행 결과
이전
Hello!
이후
이전
World!
이후
Advice 적용 순서 지정하기
- @Order 사용
- 여러 Advice를 적용해야 한다면, @Aspect 가 붙은 클래스 단위에다가 @Order(숫자)를 추가합니다.
- 숫자가 작을 수록 우선적으로 적용합니다.
어노테이션 방식 AOP
- 어노테이션을 이용하면 어노테이션이 붙은 메서드만 적용시킬 수 있다.
커스텀 어노테이션 생성
package com.dokuny.spring_study;
import java.lang.annotation.*;
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface CustomAspectAnno {
}
package com.dokuny.spring_study;
import org.springframework.stereotype.Component;
@Component
public class Hello implements HelloWorld{
// 커스텀 어노테이션 적용
@CustomAspectAnno
@Override
public void print() {
System.out.println("Hello!");
}
}
package com.dokuny.spring_study;
import org.springframework.stereotype.Component;
@Component
public class World implements HelloWorld {
// 커스텀 어노테이션 적용
@CustomAspectAnno
@Override
public void print() {
System.out.println("World!");
}
}
package com.dokuny.spring_study;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class TestAspect {
// @annotation(어노테이션이름)을 사용하여 어노테이션을 이용한다는 것을 알린다..
@Around("@annotation(CustomAspectAnno)")
public Object measure(ProceedingJoinPointjoinPoint) throws Throwable {
System.out.println("이전");
Objectresult=joinPoint.proceed(); // 공통 기능 실행
System.out.println("이후");
returnresult;
}
}
'Spring Framework > Spring' 카테고리의 다른 글
Spring Validation (0) | 2022.06.07 |
---|---|
Data Binding (0) | 2022.06.07 |
DI 정리 (0) | 2022.06.06 |
Spring Framework 소개 (0) | 2022.06.05 |
Spring 등장 배경 (0) | 2022.06.02 |
댓글