Spring AOP

김정훈·2024년 7월 7일

Spring

목록 보기
4/24

AOP 프로그래밍

(Aspect Oriented Programming) : 관점 지향 프로그래밍
관점 : 개발자의 공통적인 관심사항

공통 기능, 핵심 기능 분리 기술

  • 공통 기능 : 스프링이 대신 수행
  • 핵심 기능 : 개발자 정의

spring-aop API
aspectjweaver 구현체

의존성 추가

implementation 'org.aspectj:aspectjweaver:1.9.22.1'

1. 프록시(proxy)

데코레이터 패턴

동적 프록시

  • java.lang.reflect.Proxy
  • InvocationHandler
  • 인터페이스를 통한 프록시 / 인터페이스 정의가 필수

CalculatorHandler

public class CalculatorHandler implements InvocationHandler {

    private Object obj;
    public CalculatorHandler(Object obj) {
        this.obj = obj;
    }

    /**
     * @param proxy the proxy instance that the method was invoked on
     * @param method : 호출한 메서드의 정보
     *               동적 메서드 호출 method.invoke(...)
     *
     * @param args : 메서드 호출시 넘겨준 값(인수)
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println(proxy.getClass());
        long stime = System.nanoTime(); //공통 기능
        try {
            Object result = method.invoke(obj, args); //핵심 기능 proxy 가 대신 수행
            return result;
        }finally {
            long etime = System.nanoTime(); //공통 기능
            System.out.printf("걸린시간 : %d%n", etime - stime);
        }
    }
}
void test1(){
	Object obj = Proxy.newProxyInstance(
        Calculator.class.getClassLoader(),
        new Class[]{Calculator.class},
        new CalculatorHandler(new RecCalculator())
        );

    Calculator cal = (Calculator)obj;
    long reulst = cal.factorial(10L);
    System.out.println(reulst);
    System.out.println(cal);
}

2. AOP

1) @Aspect

  • 공통 기능이 정의된 클래스

2) @Pointcut

  • execution 명시자 : Ant 패턴
  • 공통기능이 적용될 패키 범위, 메서드 패턴

3) execution의 ANT 패턴

  • .* : exam01.* 👉 exam01 패키지의 하위 클래스
    예) exam01.RecCalculator

  • ..* : exam01..* 👉 exam01 패키지를 포함한 하위 패키지 포함 모든 클래스
    예) exam01.sub.RecCalculator, exam01.sub1.sub2.RecCalculator

  • * aopex.*.*() : 모든 반환값 == * aopex.*()
    aopex.* 👉 aopex 패키지 모든 하위 클래스
    aopex.*.* 👉 aopex 패키지 모든 하위 클래스의 모든 메서드
    aopex.*.*() 👉 aopex 패키지 모든 하위 클래스의 매개변수가 없는 모든 메서드

  • * aopex..*.*(..) 👉 모든 반환값에 aopex패키지를 포함한 하위 패키지 포함 모든 클래스 모든 메서드 == * aopex..*(..)

  • * get*(*) 👉 get으로 시작하는 매개변수가 1개짜리 메서드
    * get*(*,*) 👉 get으로 시작하는 매개변수가 2개짜리 메서드

  • * read*(Integer, ..) 👉 메서드 명이 read 시작, 첫번째 매개변수는 Integer고정, 두번째 부터는 0개이상 매개변수

4) 공통 기능을 수행할 메서드 위에 선언

@Before : 메서드가 호출되기 전 공통 기능
@After : 메서드가 호출된 후 공통 기능
@AfterReturing : 반환값을 내보낸 후 공통 기능
@AfterThrowing : 예외가 발생한 후 공통 기능
@Around : 메서드 호출 전, 호출 후 공통 기능

ProceedingJoinPoint

Object proceed() : 핵심 기능 대신 실행
Signature getSignature() : 호출한 메서드 정보

  • getTarget() : 실제 메서드를 호출한 객체(RecCalculator..)
  • Object[] getArgs() : 인수목록

Signature

시그니처가 다르면 다른 메서드(오버로드)
String getName()
String toLongString()
String toShortString()

3. @Order

프록시 캐시

프록시에서 캐시를 적용하기 위해서 ProxyCache가 먼저 동작하고 ProxyCalculator가 수행되어야한다. 이를 해결하기 위해 Order을 통해 순서를 보장해야한다.

ProxyCache

@Aspect
@Order(1) //순서보장
public class ProxyCache {

    private Map<Long, Object> data = new HashMap<>();

    @Pointcut("execution(* exam01..*(..))")
    public void publicTarget(){

    }

    @Around("publicTarget()")
    public Object process(ProceedingJoinPoint joinPoint) throws Throwable {
        Object[] args = joinPoint.getArgs();
        Long num = (Long)args[0]; //첫번째 인자값

        if(data.containsKey(num)){// 이미 캐시에 저장된 결과 값이 있으면
            System.out.println("캐시사용");
            return data.get(num);
        }

        Object result = joinPoint.proceed();

        data.put(num, result); //캐시에 저장
        System.out.println("캐시에 저장");


        return result;
    }

}

ProxyCalculator2

@Aspect
@Order(2) //순서보장
public class ProxyCalculator2 {

    @Pointcut("execution(* exam01..*(..))")
    public void publicTarget(){}

    @Around("publicTarget()")
    public Object process(ProceedingJoinPoint joinPoint) throws Throwable {
        long stime = System.nanoTime();
        try {
            Object result = joinPoint.proceed();

            return result;
        }finally {
            long etime = System.nanoTime();
            System.out.printf("걸린시간 : %d%n", etime - stime);
        }
    }
}

AppCtx

@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true) //AOP 자동 설정 애노테이션,  인터페이스 기반 -> 서브클래스 기반 프록시 변경
public class AppCtx {

    @Bean
    public ProxyCache proxyCache(){
        return new ProxyCache();
    }
}

4. 프록시 생성방식

5. @Around의 Pointcut 설정과 @Pointcut 재사용

6. EnableAspectJAutoProxy(proxyTargetClass = true)

프록시의 기본설정은 인터페이스 기반이다. proxyTargetClass = true은 서브클래스 기반 프록시로 변경해준다.

@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true) //AOP 자동 설정 애노테이션,  인터페이스 기반 -> 서브클래스 기반 프록시 변경
public class AppCtx {

    @Bean
    public ProxyCalculator2 proxyCalculator2(){
        return new ProxyCalculator2();
    }

    @Bean
    public Calculator calculator() {
        return new RecCalculator();
    }

}

AppCtx

@Configuration
@EnableAspectJAutoProxy //AOP 자동 설정 애노테이션
public class AppCtx {

    @Bean
    public ProxyCalculator proxyCalculator(){
        return new ProxyCalculator();
    }

    @Bean
    public Calculator calculator() {
        return new RecCalculator();
    }

}
profile
안녕하세요!

0개의 댓글