앞에서 프록시 객체와 데코레이터 패턴에 대해서 알아봤는데, 스프링도 이를 이용해서 AOP를 구현하고 있다.
스프링 AOP를 사용해보자. @Aspect 어노테이션을 사용해서 구현해볼 것이다.
순서
1. @Aspect 가 붙은 클래스 만들기
2. 설정파일에 @EnableAspectJAutoProxy 붙이기
3. target object 사용하기
+ 여러개의 Advice 와 Pointcut 사용하기
1. @Aspect 가 붙은 클래스 만들기
이전에 만들었던 프록시 UpperCaseMessage 는 이제 필요없다. 대신 다음과 같이 @Aspect 가 붙은 클래스가 필요하다.
package aspects;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class Aspects {
@Pointcut("execution(public String *..*.*(..))")
private void upperCasePointcut() {}
@Around("upperCasePointcut()")
public String upperCaseAdvice(ProceedingJoinPoint joinPoint)
throws Throwable {
Object targetResult = joinPoint.proceed();
validateString(targetResult);
return ((String) targetResult).toUpperCase();
}
private void validateString(Object object) {
if (!object.getClass().equals(String.class)) {
throw new IllegalStateException(
"리턴값이 String 이 아닌 메서드에 대해 이 advice 를 사용할 수 없습니다.");
}
}
}
부가기능(공통기능) 하나를 advice 라고 한다. 여기서는 "String 타입의 리턴값을 upper case로 바꾸는 것"이 advice이다.
코드로는 @Around 가 붙은 메서드, 즉 upperCaseAdvice가 advice에 해당한다.
Around Advice 는 Spring에서 구현 가능한 어드바이스 종류들 중 하나이다. 이 외에도 Before Advice, After Returning Advice, After Throwing Advice, AfterAdvice 가 있다.
@Pointcut("execution(public String *..*.*(..))")
이 부분이 뭔지 살펴보자.
앞서 advice에 대해 얘기했는데, advice는 각각 어디에 적용될지 정해질 필요가 있다.
이 정보를 담는것이 pointcut 이다.
예시에서는 아래 코드를 통해 포인트컷을 정의하고 있다.
@Pointcut("execution(public String *..*.*(..))")
private void upperCasePointcut() {}
@Pointcut 어노테이션의 속성값 execution(public String *..*.*(..))
이건 뭘까?
"접근제어자가 public 이고 리턴 타입이 String인 모든 메서드" 라는 뜻의 포인트컷 표현식이다.
포인트컷 표현식 작성법은 다음 포스팅에서 다룰 예정
@Around 애너테이션의 속성값을 보면 "upperCasePointcut()" 이렇게 되어있다.
@Around("upperCasePointcut()")
upperCasePointcut() 에서정의하는 포인트컷에 upperCaseAdvice 를 적용하겠다는 것이다.
2. 설정파일에 @EnableAspectJAutoProxy 붙이기
package config;
import aspects.Aspects;
import message.GreetingMessage;
import message.LoveMessage;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@EnableAspectJAutoProxy
public class AppConfiguration {
@Bean
public Aspects aspects() {
return new Aspects();
}
@Bean
public GreetingMessage greetingMessage() {
return new GreetingMessage();
}
@Bean
public LoveMessage loveMessage() {
return new LoveMessage();
}
}
@Aspect 에노테이션을 붙인 클래스를 공통기능으로 적용하려면 @EnableAspectJAutoProxy 애너테이션을 설정 클래스에 붙여야한다. 이 애노테이션을 추가하면 스프링은 @Aspect 애너테이션이 붙은 Bean 객체를 찾아서 빈 객체의 @Pointcut 설정과 @Around 설정을 사용한다.
3. target object 사용하기
import config.AppConfiguration;
import message.Message;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.AbstractApplicationContext;
public class Test {
public static void main(String[] args) {
AbstractApplicationContext applicationContext =
new AnnotationConfigApplicationContext(AppConfiguration.class);
Message greetingMessage = applicationContext.getBean("greetingMessage", Message.class);
System.out.println(greetingMessage.getValue());
Message loveMessage = applicationContext.getBean("loveMessage", Message.class);
System.out.println(loveMessage.getValue());
}
}
사용은 이런식으로 하면 된다.
+ 여러개의 어드바이스 및 포인트컷 사용하기
package aspects;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class Aspects {
@Pointcut("execution(public String *..*.*(..))")
private void upperCasePointcut() {}
@Around("upperCasePointcut()")
public String upperCaseAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
Object targetResult = joinPoint.proceed();
validateString(targetResult);
return ((String) targetResult).toUpperCase();
}
@Pointcut("execution(public String *..*.*(..))")
private void startWithAsteriskPointcut() {}
@Around("startWithAsteriskPointcut()")
public String startWithAsteriskPointcutAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
Object targetResult = joinPoint.proceed();
validateString(targetResult);
return "*" + targetResult;
}
private void validateString(Object object) {
if (!object.getClass().equals(String.class)) {
throw new IllegalStateException("리턴값이 String 이 아닌 메서드에 대해 이 advice 를 사용할 수 없습니다.");
}
}
}
'2021 Spring Study' 카테고리의 다른 글
Spring 13. DB 연동 (1) - JDBC 프로그래밍부터 알고 가자 (0) | 2021.01.20 |
---|---|
Spring 12. AOP 프로그래밍 (3) - 포인트컷 표현식 (0) | 2021.01.13 |
Spring 10. AOP 프로그래밍 (1) - 프록시와 AOP (0) | 2021.01.13 |
Spring 9. Bean 라이프사이클 미션 (0) | 2021.01.08 |
Spring 8. 컴포넌트 스캔 연습 (0) | 2021.01.07 |