ex)팩토리얼 연산
5! -> 5 4 3 2 1

🔸factorial 연산이 핵심 기능!
🔹시간 계산 -> 공통 기능!
공통 기능과 핵심 기능을 분리해주는 패턴이 데코레이터 패턴이다!
public class ProxyCalculator implements Calculator{
// ImplCalculator, RecCalculator 대신 수행 해주려면 자료형은? => 다형성을 이용
private Calculator calculator; // 다형성! 부모 인터페이스/ 열어 놓는 구조로 정의 직접 고정해서 정의하는것은 좋지 않다 ex) Calculator calculator = new ImplCalculator();
//외부에서 주입받은 calculator 계산기를 가지고 연산을 대신 수행
public ProxyCalculator(Calculator calculator) {
this.calculator = calculator;
}
@Override
public long factorial(long num) {
//다른 계산기의 핵심기능을 대신 수행한다.
long result = calculator.factorial(num); //다른 계산기의 factorial 연산을 대신 수행
return 0;
}
}
package exam01;
import org.junit.jupiter.api.Test;
public class Ex01 {
...
@Test
void test2(){
//직접 핵심 기능을 수행하진 않지만 계산기의 기능을 가진 객체를 넣어주면 계산기의 기능을 대신 수행해준다.
ProxyCalculator cal1 = new ProxyCalculator(new ImplCalculator());
long result1 = cal1.factorial(10L);
System.out.printf("cal1=%d\n", result1);
ProxyCalculator cal2 = new ProxyCalculator(new RecCalculator());
long result2 = cal2.factorial(10L);
System.out.printf("cal2=%d\n", result2);
}
}
ProxyCalculator가 직접 수행한 것 아님 ! 핵심기능을 대입해준것일뿐..?!


모든 클래스마다 이렇게 정의하는 것은 비효율 적이라 동적프록시 라는 것을 사용한다.
java.lang.reflect.Proxy
InvocationHandler 인터페이스


출력


외부에서 실제 객체 주입받아서 사용해보기
프록시 내에서 대신 수행
Calculator인터페이스
package exam01;
public interface Calculator {
//프록시는 인터페이스가 반드시 있어야 적용할 수 있다.
long factorial(long num); //추상메서드 정의
}
RecCalculator 클래스 구현체
package exam01;
public class RecCalculator implements Calculator {
//재귀 방식의 구현체 - 프록시가 대신 수행해줄것
@Override
public long factorial(long num) {
if(num < 1L){
return 1L;
}
return num * factorial(num - 1);
}
}
CalculatorHandler
public class CalculatorHandler implements InvocationHandler {
private Object obj;
public CalculatorHandler(Object obj) {
this.obj = obj;
}
//RecCalculator 구현체 수행을 대신 수행해줌
//invoke메서드로 유입유입!
//factorial호출할때마다 이 메서드로 유입됨
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//Object proxy -> 가짜 객체
//method에 동적 호출 가능
long stime = System.nanoTime(); //추가 기능 - 공통 기능, 공통 관심사
try{
Object result = method.invoke(obj, args);
//Calculator에 있는 factorial 메서드 호출 (핵심기능 대신 수행)
return result;
}finally {
long etime = System.nanoTime(); //추가 기능 - 공통 기능, 공통 관심사
System.out.println("걸린시간: "+ (etime - stime));
}
}
}
Test
/**
* 1.ClassLoader: 프록시 클래스가 로드될 때 사용할 클래스 로더입니다.
* 2.Interfaces: 프록시가 구현할 인터페이스 배열입니다.
* 3.InvocationHandler: 프록시 인스턴스의 메서드 호출을 처리할 핸들러입니다
*/
@Test
void test1(){
//범용적으로 반환값은 object
//매개변수: 1. 클래스로더, 2. Class 클래스 인터페이스, 3. InvocationHandler
Object obj = Proxy.newProxyInstance(
Calculator.class.getClassLoader(),
new Class[]{Calculator.class},
new CalculatorHandler(new RecCalculator())//생성자 매개변수에 구현체 객체(호출하고자 한 객체) 주입
);
//Calculator에 정의 된 invoke메서드가 호출됨
Calculator cal = (Calculator)obj;
long result = cal.factorial(10L);
//바로 실행 x, invoke 메서드 거쳐서 실행됨
System.out.println(result);
}
출력
>걸린시간: 17400
>3628800
invoke메서드가 대신해서 수행해주고 있다

AOP는 Aspect Oriented Programming의 약자로, 여러 객체에 공통적으로 적용할 수 있는 기능을 분리해서 재사용성을 높여주는 프로그래밍 기법이다.
AOP는 핵심 기능과 공통 기능의 구현을 분리함으로써 핵심 기능을 구현한 코드의 수정 없이 공통 기능을 적용할 수 있게 만들어 준다.
Spring context 의존성을 추가하면 spring-aop API도 추가된다.
implementation 'org.springframework:spring-context:6.1.10'
spring-aop API
구현체 -> aspectjweaver 추가해야 aop를 사용할 수 있다.
스프링 AOP에서 정의한 관점 지향 프로그래밍의 개념을 실제로 구현하는 라이브러리는 aspectjweaver 이다.
AspectJ의 aspectjweaver는 스프링 AOP가 제공하는 다양한 AOP 기능을 실제로 실행할 수 있도록 도와준다.
implementation'org.aspectj:aspectjweaver:1.9.22.1'
관점 = 개발자의 공통적인 관심사항
공통기능, 핵심 기능 분리 기술
◼공통 기능 - 스프링이 대신 수행(개발자의 공통적인 관심사항)
◼핵심 기능 - 개발자 정의
AOP의 기본 개념은 핵심 기능에 공통 기능을 삽입하는 것이다. 즉 핵심 기능의 코드를 수정하지 않으면서 공통 기능의 구현을 추가하는 것이 AOP이다.
1) @Aspect
2) @Pointcut
3) 공통 기능을 수행할 메서드 위에 정의 할 애노테이션
<Advice 종류>
@Before: 대상 객체의 메서드(핵심기능 실행 메서드)가 호출 되기 전 공통 기능 정의
@After: 대상 객체의 메서드가 호출 된 후 공통 기능 정의

@AfterReturing: 반환값을 내보낸 후 수행 될 공통 기능을 정의
@AfterThrowing: 예외가 발생 한 후 공통기능으로 수행될 부분을 정의
@Around: 대상 객체의 메서드 호출 전, 호출 후 공통 기능을 정의
👩💻



설정 자동화 @EnableAspectJAutoProxy: AOP 자동 설정 애노테이션


바로 호출되는것이 아니라 process를 거쳐서 동작한다.

동일하게 동작한다.
ex) @Pointcut("execution( exam01..(..))")
.* -> exam01.* -> exam01 패키지의 하위 클래스
ex) exam01.RecCalculator
..* -> exam01..* -> exam01 패키지를 포함한 하위 패키지 포함 모든 클래스
ex) exam01.sub.RecCalculator, exam01.sub1.sub2.RecCalculator
execution(수식어패턴? 리턴타입패컨 클래스이름패턴?메서드이름패턴(매개변수패턴))
수식어패턴'은 생략 가능하며 public, protected 등이 온다. 스프링 AOP는 public 메서드에만 적용할 수 있기 떄문에 사실상 public만 의미있다.
'리턴타입패턴'은 리턴 타입을 명시한다. '클래스이름패턴'과 '메서드이름패턴'은 클래스이름 및 메서드 이름을 패턴으로 명시한다. '매개변수패턴'은 매칭될 매개변수에 대해서 명시한다.
각 패턴은 '*'를 이용하여 모든 값을 표현할 수 있다. 또한 '..'(점 두 개)을 이용하여 0개 이상이라는 의미를 표현할 수 있다. (모두)

✅ * aopex.*.*() = * apoex.*() 동일
* -> 모든 반환값
aopex.* -> aopex 패키지 모든 하위 클래스
.* -> aopex 패키지 모든 하위 클래스의 모든 메서드
() -> 매개변수가 없는 형태
* aopex.*.*(): aopex 패키지 모든 하위 클래스의 매개변수가 없는 모든 메서드
✅ * aopex..*.*(..): 모든 반환값에 aopex 패키지를 포함한 하위 패키지 포함 모든 클래스, 모든 메서드
= * aopex..*(..) 동일
✅ * get*(*): get으로 시작하는 매개변수가 1개짜리 메서드
✅ * get*(*,*) : get으로 시작하는 매개변수가 2개 짜리 메서드
✅ * read*(Integer, ..): 메서드 명이 read로 시작하고 첫번째 매개변수는 Integer로 고정, 두번째 부터는 0개 이상 매개변수
ProxyCache -> ProxyCalculator
순서바뀌면 X
-> 프록시의 적용 순서, 숫자가 작은 순서 부터 적용
프록시 캐시
@Aspect
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;
}
}


org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'exam01.RecCalculator' available

@EnableAspectJAutoProxy가 설정된 경우, Spring은 Calculator 인터페이스를 기반으로 프록시 객체를 생성한다.
이 프록시는 Calculator 인터페이스를 기반으로 동작하며 RecCalculator 클래스의 타입을 갖지 않는다
따라서 ctx.getBean(RecCalculator.class)로 빈을 요청하면, 스프링 컨텍스트는 RecCalculator 타입의 빈을 찾지만 실제로 등록된 빈은 Calculator 인터페이스 타입의 프록시 객체이기 때문에, 스프링은 이를 RecCalculator로 인식하지 않는다.

RecCalculator 타입으로 빈을 찾기 원한다면, Spring AOP 설정을 클래스 기반 프록시로 변경해야 한다.
@EnableAspectJAutoProxy 애노테이션에 proxyTargetClass = true 옵션을 추가


@EnableAspectJAutoProxy(proxyTargetClass = true)
RecCalculator 클래스 기반으로 프록시가 만들어지기 때문에 오류 없이 실행이 가능해진다.
많이 사용될때 재활용을 위해서 따로 클래스로 빼서 사용도 가능하다.



