A. AOP란
- AOP(Aspect Oriented Programming)이란 Aspect라는 종단 관심사(Crosscutting Concerns)를 담은 모듈을 기반으로 프로그래밍을 하는 것입니다
- OOP에서 모듈의 관심사가 Class이듯이 AOP에서는 Aspect 입니다.
- Aspect는 기본적으로 어드바이스와 포인트 컷으로 이루어 집니다.
- 포인트컷은 적용 위치를 선별하고 어드바이스는 부가 기능을 추가합니다.
- Aspect를 통해 핵심 관심사(핵심 비즈니스)와 부가 기능을 분리할 수 있고 반복되는 부가기능은 재사용 가능합니다.
- 예) 트랜잭션, 로깅등
B. AOP의 원리 및 발전
1. Proxy 패턴
- 부가 기능을 추가하는 AOP의 Aspect는 프록시 패턴에서 시작합니다.
- 기능
- 타깃과 같은 메소드를 구현하고 있다가 메소드 호출시 타깃 오브젝트로 위임
- 지정된 요청에 대해 부가기능 수행 가능
- 단점
- 타깃 인터페이스를 구현하고 위임하는 코드를 작성하기 번거롭다.
- 타깃 인터페이스의 메소드가 추가 변경 될때마다 함께 수정 해주어야 한다.
- 부가 기능 코드가 중복될 가능성이 많습니다
- 타깃 인터페이스를 구현하고 위임하는 코드를 작성하기 번거롭다.
public class ProxyAction implements Action {
private OriginalAction action;
@Override
public void displayImage() {
if (action == null) {
action = new OriginalAction();
}
action.displayImage();
//대리 실행.
}
}
2.다이나믹 프록시
- 프록시에서 프록시 타겟에게 의존된 방식 → 다이나믹 프록시는 InvocationHandler로 어떤 클래스든 리플랙션을 이용하여 코드 변경 없이 프록시로 만들 수 있고 FactoryBean을 통해서 빈 등록이 가능하다. + 필요시 부가 기능 등록도 가능
- 한계: 여러 클래스에 부가기능 추가는 어렵다./ 구현된 Handler가 팩토리 빈 개수만큼 생김.
- 리플랙션 이용
- 오브젝트는 만들어 주지만 부가기능은 직접 작성
- 부가 기능은 프록시 오브젝트와 독립적으로 InvocationHandler를 구현한 오브젝트에 담음.
- 프록시 클래스는 클래스의 메타정보를 바탕으로 InvocationHandler의 리플렉션을 이용하는 invoke을 동작시켜서 메서드를 동작.
public class Test implements InvocationHandler {
Hello hi;
public Test(Hello hi) {
this.hi = hi;
}
@Override
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
String ret = (String) method.invoke(hi, objects);
return ret;
}
}
// ret은 return이 있는 경우. 이렇게 메서드 단위로 만들어서
Hello proxiedCheckIn = (Hello) Proxy.newProxyInstance(Hello.class.getClassLoader(), new Class[]{Hello.class}, new Test(new Hi()));
proxiedCheckIn.printName();
- 프록시를 빈으로 등록 시키려면 FactoryBean을 만들어야 합니다.
public interface FactoryBean<T>{
T getObject() throws Exception;
Class<? extends T> getObjectType();
boolean isSingleton();
}
public class MessageFactoryBean implements FactoryBean<Message>{
String text; //Message의 필드
public void setText(String text){
this.text = text;
}
public Message getObject() throws Exception {
return Message.newMessage(this.text);
//Message가 정적 팩토리 메서드로 생성. 다른 방법도 가능
// --> 이때 프록시인 경우 프록시를 넣어 준다.
// ... set등 생성후
return Proxy.newProxyInstance(getClass().getClassLoader(),
new Class[]{theInterface}, txHandler);
}
public Class<? extends Message> getObjectType() {
return Message.class;
}
public boolean isSingleton(){
return false;
}
}
@Test
public void test(){
Object message = context.getBean("message");
//XML등록 후 받아 올 수 있다.
}
3. 다이나믹 프록시 - 스프링의 방법
- ProxyFactoryBean
- 여러개의 어드바이저(Advisor)와 관련 포인트컷을 등록할 수 있기에 동시에 등록시에는 addAdvisor()를 이용해서 Advisor에 담아서 등록.
- MethodInterceptor 인터페이스를 통해 생성 → ProxyFactoryBean에게서 타깃 오브젝트의 정보도 받기에 타깃을 알 필요없이 독립적으로 만들어 진다.
- 관심사의 분리
- 프록시로 부터 포인트컷(PointCut)과 어드바이스(Advice) 분리.
- 부가 기능을 제공하는 Advice
- 메소드 선정 알고리즘인 PointCut → 타깃 메서드인지 확인 한다.
ProxyFactoryBean pfBean = new ProxyFactoryBean();
pfBean.setTarget(new HelloTarget()); // 타깃 설정
pfBean.addAdvice(new UppercaseAdvice()); // 부가 기능 추가.
static class UppercaseAdvice implements MethodInterceptor {
public Object invoke(MethodInvocation invocation) throws Throwable{
String ret = (String)invocation.proceed(); //타깃 오브젝트 전달하지 않음.
return ret.toUpperCase();
}
}
- 빈 후처리기(Bean PostProcessor)
- 스프링에서는 프록시 생성 기능을 IoC 적용하여 구현한 “빈 후처리기”를 통해서 자동 생성 할 수 있다.
- ProxyFactoryBean에 포인트컷과 어드바이저를 등록해 놓으면 포인트 컷이
- 포인트컷에는 클래스 필터(Class Filter)와 메소드 매처(Method Matcher)가 있다.
- 포인트컷 표현식(Pointcut Expression)
- AspectJ 포인트컷 표현식을 사용한다.
- AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut(); 를 통해 등록 가능
- 표현식
- [ , ] 대괄호는 옵션항목이라 생략 가능/ ; 는 Or 조건
- … 여러개의 패키지/ * 모든 현 폴더의 패키지
- execution([접근 제한자 패턴] 타입 패턴 [타입패턴]. 이름패턴 (타입패턴; “..”, …)
- 타입 패턴 첫번째 : 리턴 타입
- 타입 패턴 두번째(옵션): 패키지와 타입이름을 포함 클래스의 타입 (생략시 모든 클래스에 적용)
- 이름 패턴: 메소드의 이름
- 타입 패턴 세번쨰: 메소드의 파라미터 타입 패턴 ‘,’ 로 구분하며 순서대로/ 없으면 (). 타입과 개수 상관없이 모두 다 혀용하는 패턴 = “…”
- 마지막은 예외에 대한 이름 타입 패턴
4. AOP (Aspect Oriented Programming, 관점 지향 프로그래밍)
- 종류
- 스프링 AOP
- 스프링 컨테이너를 이용하여 Proxy를 생성하는 방식
- AspectJ
- 컴파일된 타깃 클래스 파일 자체를 수정하거나 JVM이 클래스 로딩하는 시점에 가로채서 바이트코드를 조작
- 스프링 AOP
- 스프링 AOP 종류
- 동적 프록시 (JDK Dynamic Proxy)
- 하나 이상의 인터페이스를 구현하였을떄.
- Spring Framework의 기본 방식
- 리플렉션을 통해 생성하여 성능 떨어짐
- CGLIB (Code General Library)
- 인터페이스를 구현하지 않았을때 사용
- SpringBoot 2.0부터 기본 방식
- final 클래스나 매서드에 적용 불가 → 예외가 아니라 단순히 동작하지 않는다.
- 바이트 코드를 조작해서 만들기에 성능이 빠르다.
- JDK Proxy에 비해 unexpected class casting exception 나올 가능성 적음.
- 동적 프록시 (JDK Dynamic Proxy)
- 추가 용어
- 조인 포인트(Join Point): 어드바이스가 적용될 수 있는 위치 = 포인트 컷을 통해서 조인 포인트를 선별.
- Aspect: OOP의 클래스 처럼 AOP의 기본 모듈. 포인트컷과 어드바이스의 조합으로 만ㄴ들어 지며 보통 싱글톤 형태의 오브젝트로 존재. 클래스와 다르게 템플릿과 실체의 차이가 없다.
- 등록법: 4가지를 등록 빈으로 등록해야 한다.
- 자동 프록시 생성기
- @EnableAspectJAutoProxy : DefaultAdvisorAutoProxyCreator를 상속해 발전 시킨 AnnotationAwareAdvisorAutoProxyCreator를 등록. 이건 @Aspect어노테이션을 읽어서 프록시를 만든다.
- 어드바이스: 에스팩트 클래스의 부가 메서드
- 포인트컷: @Before, @After..etc
- 어드바이저 : @Aspect을 통해서 에스팩트 클래스를 정의하고 스프링이 자동으로 어드바이저 생성
- 자동 프록시 생성기
- *주의점
- AOP는 자기 자신이 호출하는 경우에는 일어나지 않는다.(내부 메소드 문제)
- 왜냐하면 AOP의 적용은 스프링 컨테이너가 빈후처리기를 통해 런타임에 프록시를 적용하기 때문에.
- AOP는 자기 자신이 호출하는 경우에는 일어나지 않는다.(내부 메소드 문제)
C.AOP 적용
- Authentication은 항상 받도록 하고 Authentication이 필요하지 않는 곳에 적용을 풀기 위해 사용하였습니다.
1. 자동 프록시 생성기
@SpringBootApplication
@EnableAspectJAutoProxy
public class MemberContextApplication {
public static void main(String[] args) {
SpringApplication.run(MemberContextApplication.class, args);
}
}
2. 어드바이스, 포인트 컷, 어드바이저
@Aspect
@Component
public class AuthenticationAspect {
@Autowired
private HttpServletRequest request;
@Before("execution(public * com.membercontext.memberAPI.web.controller.*.*(..)) " +
"&& !@annotation(com.membercontext.memberAPI.application.aop.authentication.NoAuthentication)")
public void authenticate() {
String cookieValue = checkCookie();
checkSession(cookieValue);
}
3. 어노테이션
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface NoAuthentication {
}
출처