이 장의 키워드
AOP
프록시, 대상 객체
Advice의 순서
@Pointcut
@Around
핵심
공통 기능 구현 / 핵심 기능 구현을 분리
여러 객체에 공통으로 적용할 수 있는 기능을 분리해서 재사용성을 높여주는 프로그래밍 기법
핵심 기능의 코드를 수정하지 않으면서 공통 기능의 구현을 추가한다.
런타임에 프록시 객체를 생성해서 공통 기능을 삽입한다.
// Calculator 인터페이스
public interface Calculator {
public long factorial(long num);
}
<Calculator 인터페이스>
- 계승을 구하기위한 인터페이스 정의
// Calculator 첫번째 구현 클래스
public class ImpeCalculator implements Calculator{
@Override
public long factorial(long num) {
long result = 1;
for(long i = 1; i<=num; i++) {
result *= i;
}
return result;
}
}
<Calculator 첫번째 구현 클래스>
- for 문을 이용하여 계승 값을 구한다.
// Calculator 두번째 구현 클래스
public class RecCalculator implements Calculator {
@Override
public long factorial(long num) {
if (num == 0)
return 1;
else
return num * factorial(num -1);
}
}
<Calculator 두번째 구현 클래스>
- 재귀호출을 이용해서 계승 값을 구한다.
// 공통 기능(실행 시간 출력)이 구현된 클래스 => 프록시 객체가 된다.
public class ExeTimeCalculator implements Calculator {
private Calculator delegate; // Calculator 객체
public ExeTimeCalculator(Calculator delegate) {
this.delegate = delegate;
}
@Override
public long factorial(long num) {
long start = System.nanoTime(); // 전
long result = delegate.factorial(num);
long end = System.nanoTime(); // 후
System.out.printf("%s.factorial(%d) 실행 시간 = %d\n",
delegate.getClass().getSimpleName(),
num, (end-start));
return result;
}
}
<ExeTimeCalculator>
- Calculator를 구현해 공통 기능을 사용할 수 있는 클래스 => 프록시 객체가 된다.
- 생성자를 통해 다른 Calculator (ImpeCalculator, RecCalculator) 객체를 전달받아 delegate 필드에 할당
- factorial() 기능 자체를 직접 구현하기보다는 다른 객체에 factorial() 실행을 위임,
- 다른 부가적인 기능(실행 시간 측정) 실현
▶ 널리 사용되는 것은 Around Advice
① Aspect로 사용할 클래스에 @Aspect 애노테이션을 붙인다.
② @Pointcut 애노테이션으로 공통 기능을 적용할 Pointcut을 정의한다.
③ 공통 기능을 구현한 메서드에 @Around 애노테이션을 적용한다.
<ExeTimeAspect.java>
import java.util.Arrays;
import org.lang.*;
import com.sun.org.apache.bcel.internal.classfile.Signature;
@Aspect // Aspect로 사용할 클래스이다.
public class ExeTimeAspect {
// 공통 기능 적용할 Pointcut
@Pointcut("execution(public * chap07..*(..))")
private void publicTarget() {
}
// 공통 기능을 구현한 메서드에 @Around 붙이기
@Around("publicTarget()")
public Object measure(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.nanoTime(); // 실행 전
try {
Object result = joinPoint.proceed(); // 실제 대상 객체의 매서드 호출
return result;
} finally {
long finish = System.nanoTime(); // 실행 후
Signature sig = joinPoint.getSignature();
System.out.printf("%s.%s(%s) 실행 시간 : %d ns\n",
joinPoint.getTarget().getClass().getSimpleName(),
sig.getName(), Arrays.toString(joinPoint.getArgs())
(finish-start));
}
}
}
④ 스프링 설정 클래스를 작성한다.
<AppCtx.java>
@Configuration
@EnableAspectJAutoProxy
public class AppCtx {
@Bean
public ExeTimeAspect exeTimeAspect() {
return new ExeTimeAspect();
}
@Bean
public Calculator calculator() {
return new RecCalculator();
}
}
[MainAspect 클래스 - 14행]
Calculator cal = ctx.getBean("calculator", Calculator.class);
실제 생성할 빈 객체가 인터페이스를 상속하면 인터페이스를 이용해서 프록시를 생성
"calculator" 빈의 실제 타입은 Calculator를 상속한 프록시 타입
** @EnableAspectJAutoProxy(proxyTargetClass = true)
애노테이션의 proxyTargetClass 속성을 true로 지정하면 자바 클래스를 상속받아 프록시 생성
Pointcut 설정에 execution 명시자 사용, Advice를 적용할 메서드를 지정할 때 사용
execution(수식어패턴? 리턴타입패턴 클래스이름패턴?메서드이름패턴(파라미터패턴))
한 Pointcut에 여러 Advice 적용
<CacheAspect.java>
@Aspect
public class CacheAspect {
private Map<Long, Object> cache = new HashMap<>();
@Pointcut("execution(public * chap07..*(long))")
public void cacheTarget() {
}
@Around("cacheTarget()")
public Object execute(ProceedingJoinPoint joinPoint) throws Throwable {
Long num = (Long) joinPoint.getArgs()[0];
// 위에서 구한 키값이 cache에 존재할 때 : 키에 해당하는 값 리턴
if (cache.containsKey(num)) {
System.out.printf("CacheAspect: Cache에서 구함[%d]\n", num);
return cache.get(num);
}
// 위에서 구한 키값이 cache에 존재하지 않을 때 : 프록시 대상 객체 실행
Object result = joinPoint.proceed();
cache.put(num.result, result);
System.out.printf("CacheAspect: Cache에 추가[%d]\n", num);
return result;
}
}
<MainAspectWithCache.class>
public class MainAspectWithCache {
public static void main(String[] args) {}
AnnotationConfigApplicationContext ctx =
new AnnotationConfigApplicationContext(AppCtxWithCache.class);
Calculator cal = ctx.getBean("calculator", Calculator.class);
cal.factorial(7);
cal.factorial(7);
cal.factorial(5);
cal.factorial(5);
ctx.close();
}
첫 번째 실행 결과 : ExeTimeAspect와 CacheAspect 모두 적용
두 번째 실행 결과 : CacheAspect만 적용
Advice를 다음 순서로 적용했기 때문이다.
Calculator cal = ctx.getBean("calculator", Calculator.class); 에서 구한
두 번째 factorial() 실행
=> 첫 번째 실행으로 cache 맵에 데이터가 추가되어 있다.
=> cache.containsKey(num)이 true를 리턴한다.
=> joinPoint.proceed() 를 실행하지 않으므로 ExeTimeAspect나 실제 객체가 실행되지 않는다.
=> CacheAspect가 생성한 메시지만 출력한다.
** Order 애노테이션의 지정값에 따라 Aspect의 적용 순서를 결정할 수 있다.
만약 같은 Pointcut을 여러 Advice가 함께 사용
=> 공통 Pointcut 재사용
예시
CacheAspect 클래스의 @Around 애노테이션에서
ExeTimeAspect 클래스의 publicTarget() 메서드의 Pointcut을 사용하고 싶어한다.
@Aspect
public class ExeTimeAspect {
@Pointcut("execution(public * chap07..*(..))")
// publicTarget()메서드를 private에서 public으로 변경한다.
public void public Target() {
}
}
@Aspect
public class CacheAspect {
// 해당 Pointcut의 완전한 클래스 이름을 포함한 메서드 이름을 사용
@Around("aspect.ExeTimeAspect.publicTarget()")
public Object execute(ProceedingJoinPoint joinPoint) throws Throwable{
...
}
}
① Pointcut의 메서드를 public으로 설정한다.
② 다른 클래스에 위치한 @Around에서 Pointcut의 완전한 클래스 이름을 포함한 메서드 이름을 사용한다.
** 여러 Aspect에서 공통으로 사용하는 Pointcut이 있다면
별도 클래스에 Pointcut을 정의하고, 각 Aspect 클래스에서 해당 Pointcut을 사용하도록 구성
1. 핵심 기능의 실행은 다른 객체에 위임하고 부가적인 기능을 제공하는 객체는 (프록시)이며,
실제 핵심 기능을 구현하고 실행하는 객체는 (대상 객체)이다
2. 스프링은 AOP를 위한 프록시 객체를 생성할 때 실제 생성할 빈 객체가 인터페이스를 상속하면 (인터페이스)를
이용해서 프록시를 생성한다.
3. Advice를 적용 가능한 지점을 ( ㉠ )라고 하며, 이 때 실제 Advice가 적용되는 ( ㉠ )을 ( ㉡ )라 한다.
㉠Joinpoint ㉡Pointcut
4. 대상 객체의 메서드 실행 전, 후 또는 익셉션 발생 시점에 공통 기능을 실행하는데 사용되는 Advice는 (Around Advice)이다.
5. @Aspect 애노테이션을 붙인 클래스를 공통 기능으로 적용하려면 (@EnableAspectJAutoProxy) 애노테이션을 설정 클래스에 붙여야한다.
6. (execution 명시자)는 Advice를 적용할 메서드를 지정할 때 사용하며, Pointcut이나 Around 애노테이션에 지정할 수 있다.
7. (AOP)는 여러 객체에 공통으로 적용할 수 있는 기능을 분리해서 재사용성을 높여주는 프로그래밍 기법이다.
1. 7행, 14행의 빈칸을 채우세요
// 공통 기능(실행 시간 출력)이 구현된 클래스 => 프록시 객체가 된다.
// ExeTimeCalculator.java
public class ExeTimeCalculator implements Calculator {
private Calculator delegate; // Calculator 객체
public ExeTimeCalculator( ) {
this.delegate = delegate;
}
@Override
public long factorial(long num) {
long start = System.nanoTime(); // 전
long result = .factorial(num);
long end = System.nanoTime(); // 후
System.out.printf("%s.factorial(%d) 실행 시간 = %d\n",
delegate.getClass().getSimpleName(),
num, (end-start));
return result;
}
}
2. 1행, 5행, 10행의 빈칸을 채우세요.
@ // Aspect로 사용할 클래스이다.
public class ExeTimeAspect {
// 공통 기능 적용할
@ ("execution(public * chap07..*(..))")
private void publicTarget() {
}
// 공통 기능을 구현한 메서드에 @ 붙이기
@ ("publicTarget()")
public Object measure(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.nanoTime(); // 실행 전
try {
Object result = joinPoint.proceed(); // 실제 대상 객체의 매서드 호출
return result;
} finally {
long finish = System.nanoTime(); // 실행 후
Signature sig = joinPoint.getSignature();
System.out.printf("%s.%s(%s) 실행 시간 : %d ns\n",
joinPoint.getTarget().getClass().getSimpleName(),
sig.getName(), Arrays.toString(joinPoint.getArgs())
(finish-start));
}
}
}
[스프링2] 8장. DB연동 (2) (0) | 2022.11.17 |
---|---|
[스프링2] 8장. DB 연동 (1) (0) | 2022.11.17 |
[스프링2] 6장. 빈 라이프사이클과 범위 (0) | 2022.10.13 |
[스프링2] 5장. 컴포넌트 스캔 (0) | 2022.10.13 |
[스프링2] 4장. 의존 자동 주입 (0) | 2022.10.13 |