AOP 란?
- AOP는 Aspect Oriented Programming의 약자로
관점 지향 프로그래밍
이라고 불린다. - 어떤 로직을 기준으로 핵심적인 관점, 부가적인 관점으로 나누어서 보고 그 관점을 기준으로 각각 모듈화하겠다는 것이다.
- 소스 코드상에서 다른 부분에 계속 반복해서 쓰는 코드들을 발견할 수 있는 데 이것을
흩어진 관심사
(Crosscutting Concerns)라 부른다. - 아래와 같이 흩어진 관심사를 Aspect로 모듈화하고
핵심적인 비즈니스 로직에서 분리하여 재사용하겠다는 것이 AOP의 취지
다.
프록시 패턴
- 위에서 보듯 Client는 이 interface 타입으로 Proxy 객체를 사용하게 된다.
- Proxy 객체는 기존의 타겟 객체(Real Subject)를 참조하고 있다.
- Proxy 객체와 타겟 객체(Real Subject) 의 타입은 같고, Proxy는 원래 해야 할 일을 가지고 있는 Real Subject를 감싸서 Client의 요청을 처리한다.
- Proxy 와 타겟 객체(Real Subject) 가 타입이 같은 이유는 기존 코드의 변경 없이 접근 제어 또는 부가 기능 추가를 위해서다.
AOP 의 세가지 처리 방식
- AOP의 핵심 기능은 코드를 수정하지 않으면서 공통 기능의 구현을 추가하는 것이라고 강조하고 있습니다.
- 핵심 기능에 공통 기능을 추가하는 방법에는 아래와 같이 3가지 방법이 존재합니다.
- 컴파일 : 자바 파일을 클래스 파일로 만들 때 바이트코드를 조작하여 적용된 바이트코드를 생성
- 로드 타임 : 컴파일은 원래 클래스 그대로 하고, 클래스를 로딩하는 시점에 끼워서 넣는다.
런타임 : A라는 클래스를 빈으로 만들 때 A라는 타입의 프록시 빈을 감싸서 만든 후에, 프록시 빈이 클래스 중간에 코드를 추가해서 넣는다.
- 스프링에서 많이 사용하는 방식은 프록시를 이용한 세 번째 방법입니다.
- 스프링 AOP는 프록시 객체를 자동으로 만들어줍니다.
AOP 주요 개념
- Aspect : 위에서 설명한 흩어진 관심사를 모듈화 한 것. 주로 부가기능을 모듈화함.
- Target : Aspect를 적용하는 곳 (클래스, 메서드 .. )
- Advice : 실질적으로 어떤 일을 해야할 지에 대한 것, 실질적인 부가기능을 담은 구현체
- Advice 의 동작 시점
- Before: 메소드 실행 전에 동작
- After: 메소드 실행 후에 동작
- After-returning: 메소드가 정상적으로 실행된 후에 동작
- After-throwing: 예외가 발생한 후에 동작
- Around: 메소드 호출 이전, 이후, 예외발생 등 모든 시점에서 동작
- Advice 의 동작 시점
- JointPoint : Advice가 적용될 위치, 끼어들 수 있는 지점.
- 메서드 진입 지점, 생성자 호출 시점, 필드에서 값을 꺼내올 때 등 다양한 시점에 적용가능
- PointCut : JointPoint의 상세한 스펙을 정의한 것.
- 'A란 메서드의 진입 시점에 호출할 것'과 같이 더욱 구체적으로 Advice가 실행될 지점을 정할 수 있음
PointCut 표현식
- 포인트컷을 이용하면 어드바이스 메소드가 적용될 비즈니스 메소드를 정확하게 필터링할 수 있다.
- 지시자(PCD, AspectJ pointcut designators)의 종류
- execution : 가장 정교한 포인트컷을 만들수 있다. 리턴타입 패키지경로 클래스명 메소드명(매개변수)
- within : 타입패턴 내에 해당하는 모든 것들을 포인트컷
- bean : bean이름으로 포인트컷
- 리턴타입 지정
- *: 모든 리턴타입 허용
- void: 리턴타입이 void인 메소드 선택
- !void: 리턴타입이 void가 아닌 메소드 선택
- 패키지 지정
- com.devljh.domain: 정확하게 com.devljh.domain 패키지만 선택
- com.devljh.domain..: com.devljh.domain 패키지로 시작하는 모든 패키지 선택
- 클래스 지정
- UserBO: 정확하게 UserBO 클래스만 선택
- *BO: 이름이 BO로 끝나는 클래스만 선택
- BaseObject+: 클래스 이름 뒤에 '+'가 붙으면 해당 클래스로부터 파생된 모든 자식 클래스 선택, 인터페이스 이름 뒤에 '+'가 붙으면 해당 인터페이스를 구현한 모든 클래스 선택
- 메소드 지정
- *(..): 모든 메소드 선택
- update*(..): 메소드명이 update로 시작하는 모든 메소드 선택
- 매개변수 지정
- (..) 모든 매개변수
- (*) 반드시 1개의 매개변수를 가지는 메소드만 선택
- (com.devljh.domain.user.model.User): 매개변수로 User를 가지는 메소드만 선택. 꼭 풀패키지명이 있어야함
- (!com.devljh.domain.user.model.User): 매개변수로 User를 가지지않는 메소드만 선택
- (Integer, ..): 한 개 이상의 매개변수를 가지되, 첫 번째 매개변수의 타입이 Integer인 메소드만 선택
- (Integer, *): 반드시 두 개의 매개변수를 가지되, 첫 번째 매개변수의 타입이 Integer인 메소드만 선택
JoinPoint Interface
- Signature getSignature() : 클라이언트가 호출한 메소드의 시그니처(리턴타입, 이름, 매개변수) 정보가 저장된 Signature 객체 리턴
- getName() : 클라이언트가 호출한 메소드의 이름 리턴
- String toLongString() : 클라리언트가 호출한 메소드의 리턴타입, 이름, 매개변수를 패키지 경로까지 포함해서 리턴
- String toShortString() : 클라이언트가 호출한 메소드 시그니처를 축약한 문자열로 리턴
- Object getTarget() : 클라이언트가 호출한 비즈니스 메소드를 포함하는 비즈니스 객체 리턴
- Object[] getArgs() : 클라이언트가 메소드를 호출할 때 넘겨준 인자 목록을 Object 배열로 리턴
public class BeforeAdvice {
public void beforeLog(JoinPoint jp) {
String method = jp.getSignature().getName();
Object[] args = jp.getArgs(); System.out.println(생략...);
}
}
Advice Annotation
- AOP는 Advice를 스프링 컨테이너에서 설정해주지 않아도 간단한 어노테이션을 이용하여 Advice를 주입하는 것이 가능하다.
- 아래와 같이 구현할 수 있다.
@Service @Aspect
public void LogAdvice(){
@Pointcut("execution(생략...)");
public void allPointcut(){};
@before("allPointcut")
public void printLog(){}
}
- 여기서 포인트컷은 아래와 같다.
@Pointcut("execution(생략...)");
public void allPointcut(){};
- 그리고 어드바이스 메소드는 아래와 같다.
@before("allPointcut")
public void printLog(){}
- 위와 같이 어드바이스 메소드가 결합될 포인트컷을 참조해야 한다.
- AOP 기능에서 가장 중요한 Aspect 설정은 @Aspect 어노테이션을 사용하여 설정한다.
- Aspect를 설정하기 위해서는 반드시 포인트컷+어드바이스 결합이 필요하다.
- Advice에서의 5가지 종류 중 After-returning과 After-Throwing 어드바이스는 특이하게 포인트컷 지정을 pointcut 속성을 사용하여 포인트컷을 참조한다.
- 그 이유는 비즈니스 메소드 수행 결과를 받아내기 위해 바인드 변수를 지정해야 하기 때문이다.
// AfterReturning은 returning 바인드 변수를 통해 비즈니스 로직의 결과 값을 받을 수 있다.
@AfterReturning(pointcut="getPointcut()", returning="returnObj")
// AfterThrowing은 throwing 바인드 변수를 통해 예외 처리 객체의 값을 받을 수 있다.
@AfterThrowing(pointcut="getPointcut()", throwing="exceptObj")
독립된 클래스로 포인트컷 분리
- 어노테이션으로 Aspect를 설정할 때 가장 큰 문제점은 어드바이스 클래스마다 포인트컷 설정이 포함되면서, 비슷하거 같은 포인트컷이 반복 선언되는 재사용 불가의 문제가 발생한다.
- 그래서 스프링은 이런 문제를 해결하고자 포인트컷을 아래와 같이 외부에 독립된 클래스에 따로 설정한다.
// 포인트컷을 독립적으로 관리하는 클래스를 설정.
@Aspect
public class PointCommon {
@Pointcut("execution(생략...)")
public void allPointcut(){}
@Pointcut("execution(생략...)")
public void allPointcut(){}
}
// 공통적으로 설정한 PointcutCommon의을 참조하여 포인트컷을 사용할 수 있다.
@Service
@Aspect
public class BeforeAdvice {
@Before("PointcutCommon.allPointcut()")
public void beforeLog(){//생략...}
}
- 이런식으로 독립적으로 관리하는 것이 포인트컷을 사용하는데 있어 더욱 효율적이고 유지보수에도 용이하다.
지시자(PCD, AspectJ pointcut designators)별 구현 방법
- 먼저 아래와 같은 코드가 있다고 해보자
@Component
public class AppRuner implements ApplicationRunner {
@Autowired
EventService eventService;
@Override
public void run(ApplicationArguments args) throws Exception {
eventService.created();
eventService.operation();
eventService.deleted();
}
}
@Component
public class SimpleServiceEvent implements EventService {
@Override
public void created() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("created");
}
@Override
public void operation() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("operation");
}
@Override
public void deleted() {
System.out.println("deleted");
}
}
- 이제 위 코드에 3가지 방법으로 AOP를 구현해 보겠다.
-
- execution expression 기반
// Aspect 정의
@Component
@Aspect
public class PerfAspect {
// Advice 정의 (Around 사용)
// Point Cut 표현식
// (com.example.demo 밑에 있는 모든 클래스 중 EventService 안에 들어있는 모든 메쏘드에 이 행위를 적용하라.)
@Around("execution(* com.example..*.EventService.*(..))")
public Object logPerf(ProceedingJoinPoint pjp) throws Throwable {
long begin = System.currentTimeMillis();
Object retVal = pjp.proceed();
System.out.println(System.currentTimeMillis() - begin);
return retVal;
}
}
-
- Annotation 기반
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface PerfLogging {
}
@Component
@Aspect
public class PerfAspect {
@Around("@annotation(PerfLogging)")
public Object logPerf(ProceedingJoinPoint pjp) throws Throwable {
long begin = System.currentTimeMillis();
Object retVal = pjp.proceed();
System.out.println(System.currentTimeMillis() - begin);
return retVal;
}
}
- 아래와 같이 적용시킬 메소드에 @PerfLogging 을 붙여주면 된다.
@Component
public class SimpleServiceEvent implements EventService {
@PerfLogging
@Override
public void created() {
...
}
@PerfLogging
@Override
public void operation() {
...
}
@Override
public void deleted() {
...
}
}
- 위의 코드를 실행시키면 @PerfLogging 가 붙은 created(), operation() 메소드만 AOP가 적용된 것을 확인할 수 있다.
- 특정 bean 기반
// Aspect 정의
@Component
@Aspect
public class PerfAspect {
// 빈이 가지고있는 모든 퍼블릭 메쏘드
@Around("bean(simpleServiceEvent)")
public Object logPerf(ProceedingJoinPoint pjp) throws Throwable {
long begin = System.currentTimeMillis();
Object retVal = pjp.proceed();
System.out.println(System.currentTimeMillis() - begin);
return retVal;
}
}
- 위와 같이 빈으로 등록된 simpleServiceEvent 내 모든 public 메소드에다가 적용시킬 수도 있다.
REFERENCES
'Spring' 카테고리의 다른 글
BindingResult (0) | 2022.02.26 |
---|---|
Bean Validation (BindingResult 개념을 먼저 숙지 해야된다.) (0) | 2022.02.26 |
ApplicationRunner,CommandLineRunner (0) | 2022.02.26 |
API 예외처리 (0) | 2022.02.26 |
@Valid, @Validated (0) | 2022.02.26 |