관점 지향 프로그래밍 AOP(Aspect Oriented Programming)
AOP
메서드 안의 주기능과 보조기능을 분리한 후 선택적으로 메서드에 적용해서 사용한다는 개념
AOP를 사용하면 전체 코드에 흩어져 있는 보조 기능을 공통으로 묶어 관리할 수 있고, 설정을 통해 이러한 보조 기능을 특정 메서드나 클래스에 적용할 수 있음
ex) 주요 기능(회원/상품/주문관리 등)은 아니지만 보안/ 로깅/ 트랜잭션 처리 와 같이 모든 기능에 공통으로 필요로 하는 작업을 AOP로 처리함
스프링에서의 AOP 기능
관련 용어 정리
- aspect : 구현하고자 하는 보조 기능
- advice : aspect의 실제 구현체(class)를 의미하며 메서드 호출을 기준으로 여러 joinpoint에서 실행
- joinpoint : advice를 적용하는 지점으로 스프링에서는 메서드 결합점만 제공
- pointcut : advice가 적용되는 대상을 지정함. package / class/ method 이름을 정규식으로 지정하여 사용
- target : advice가 적용되는 클래스
- weaving : advice를 주기능에 적용하는 것
스프링에서의 AOP기능 구현 방법 > AOP관련 API 또는 @Aspect 어노테이션의 사용
스프링 API를 이용한 AOP 기능 구현
target(적용대상) 클래스 지정 > advice(보조기능구현) 클래스 지정 > 설정 파일에서 pointcut 설정 > 설정 파일에서 advice와 pointcut을 결합하는 advisor 설정 > 설정파일에서 스프링의 proxyFactoryBean 클래스를 이용해 target클래스에 advice 설정 > getBean() 메서드로 bean 객체에 접근해 사용
> AOP기능을 수행하는 advice 인터페이스들의 추상 메서드 종류
void before(Method method, Object[] ars, Object target) throws Throwable{
// MethodBeforeAdvice 인터페이스의 추상메서드에 대한 구현부
// 해당 메서드 실행 전
}
// Method method : 대상 객체에서 실행된 메서드를 나타내는 메서드 객체
// Object[] args : 메서드 인자 목록
// Object target : 대상 객체
void afterReturnig(Object returnValue, Method method, Object[] ars, Object target, Exception ex){
// ArterReturningAdvice 인터페이스의 추상메서드에 대한 구현부
// 해당 메서드 실행 후
}
// Object returnValue : 대상 객체의 메서드가 반환하는 값
// Exception ex : 발생한 예외 타입
void afterThrowing(Method method, Object[] ars, Object target, Exception ex){
// TrowsAdvice 인터페이스의 추상메서드에 대한 구현부
// 해당 메서드에서 예외 발생 시
}
Object invoke(MethodInvocation invocation)throw Trowable{
// MethodInterceptor 인터페이스의 추상메서드에 대한 구현부
// 해당 메서드의 실행 전후와 예외 발생 시
}
// MethodInvocation invocation : 대상 객체의 모든 정보를 담고 있는 객체(호출된 메서드, 인자 등)
> 이중 MethodInterceptor는 invoke() 메서드를 이용해 다른 세가지 인터페이스들의 기능을 동시 수행할 수 있음
// AOP 설정 XML
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN"
"http://www.springframework.org/dtd/spring-beans-2.0.dtd">
<beans>
<bean id="calcTarget" class="com.pro.yoon.Calculator" /> //target class bean
<bean id="logAdvice" class="com.pro.yoon.LoggingAdvice" /> //logging advice bean
<bean id="proxyCal" class="org.springframework.aop.framework.ProxyFactoryBean">
//ProxyFactoryBean : target과 advice를 엮어줌
<property name="target" ref="calcTarget"/> //target bean을 calcTarget bean으로 지정함
<property name="interceptorNames">
//Spring ProxyFactoryBean의 interceptorNames 속성에
//'logAdvice'를 advice bean으로 설정하여 target 클래스의 메서드 호출시 logAdvice를 실행함
<list>
<value>logAdvice</value> //<value>태그를 사용하여 advice를 추가해주면 됨
</list>
</property>
</bean>
</beans>
> 주기능인 target클래스 Calculator와 log를 확인하는 보조기능의 advice 클래스 예제
package com.pro.yoon;
// Target class
public class Calculator {
public void add(int x, int y) {
int result = x+y;
System.out.println("결과 ::" + result);
}
public void subtract(int x, int y) {
int result = x-y;
System.out.println("결과 ::" + result);
}
public void multiply(int x, int y) {
int result = x*y;
System.out.println("결과 ::" + result);
}
public void devide(int x, int y) {
int result = x/y;
System.out.println("결과 ::" + result);
}
}
package com.pro.yoon;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
// Advice class
public class LoggingAdvice implements MethodInterceptor{
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("[메서드 호출 전 : LoggingAdvice]");
System.out.println(invocation.getMethod() + "메서드 호출 전"); // 메서드 호출 전 수행 구문
Object object = invocation.proceed();
// invocation을 이용해 실행하고자 하는 target 클래스의 메서드를 호출하고 그 return 값을 object에 담아 리턴
System.out.println("[메서드 호출 후 : LoggingAdvice]");
System.out.println(invocation.getMethod() + "메서드 호출 후"); // 메서드 호출 후 수행 구문
return object;
}
}
// 실행 클래스 CalcTest
package com.pro.yoon;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class CalcTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("AOPTest.xml");
// AOPTest.xml을 찾아 읽고 빈을 생성함
Calculator cal = (Calculator)context.getBean("proxyCal");
// id가 proxyCal인 bean(=ProxyFactoryBean)을 찾음
cal.add(100, 20); // Target 클래스의 메서드 실행 시 logAdvice를 실행함
System.out.println();
cal.subtract(100, 20);
System.out.println();
cal.multiply(100, 20);
System.out.println();
cal.devide(100, 20);
System.out.println();
}
}
// 결과
[메서드 호출 전 : LoggingAdvice]
public void com.pro.yoon.Calculator.add(int,int)메서드 호출 전
결과 ::120
[메서드 호출 후 : LoggingAdvice]
public void com.pro.yoon.Calculator.add(int,int)메서드 호출 후
[메서드 호출 전 : LoggingAdvice]
public void com.pro.yoon.Calculator.subtract(int,int)메서드 호출 전
결과 ::80
[메서드 호출 후 : LoggingAdvice]
public void com.pro.yoon.Calculator.subtract(int,int)메서드 호출 후
[메서드 호출 전 : LoggingAdvice]
public void com.pro.yoon.Calculator.multiply(int,int)메서드 호출 전
결과 ::2000
[메서드 호출 후 : LoggingAdvice]
public void com.pro.yoon.Calculator.multiply(int,int)메서드 호출 후
[메서드 호출 전 : LoggingAdvice]
public void com.pro.yoon.Calculator.devide(int,int)메서드 호출 전
결과 ::5
[메서드 호출 후 : LoggingAdvice]
public void com.pro.yoon.Calculator.devide(int,int)메서드 호출 후
> advice 클래스는 target클래스의 모든 메서드 호출 시 적용됨
> 실제 스프링에서는 특정 패키지/클래스/메서드 이름에만 AOP 기능을 적용할 수 있음
AOP 핵심 정리
초기 용어 / 개념 정리
> 타겟, 프록시, 위빙, 어드바이스, 포인트컷, 애스펙트
적용 시점 및 포인트컷을 활용한 aspect 관리 / aspectj 표현식 활용 방법
타겟 : aop가 걸릴 대상 / 프록시 : aop가 걸린 대상 (타겟과 프록시는 타입이 같음 / 형식이 완전 같은 객체)
** aop를 건다 > 추가 기능
//예시
class Target {
void hello() {
sysout("1");
}
void bye() {
sysout("2");
}
}
// Target 타입의 객체 생성시 호출 가능한 메서드 : hello(), bye()
class TargetTest {
Target t1 = new Target();
Target t2 = 어떠한 작업을 통한 프록시 생성; // 호출 가능한 메서드 : hello(), bye() 로 동일함
}
// hello() bye() 라는 이 두개의 메서드가 joinpoint
조인 포인트 : aop가 적용 가능한 지점 (적용될 후보 메서드/ 언제나 메서드임)
포인트컷 : aop가 적용될 조건식 (메서드 or 클래스 or 패키지/ 적용될 후보의 조건식)
> 포인트컷이 조인 포인트보다 좀더 구체적이고 좁은 개념임
ex) select * from tab1 이게 조인포인트라면 , 포인트컷은 select * from tab1 where bno = '123'과 같이 적용 범위가 좁아짐
쿼리에서 where를 조건절이라고 하듯 포인트컷도 조건식임
어드바이스 : 적용된 추가 메서드 / 적용 시점에 따라 before, after, afterReturning, around, afterThrowing
예시) 목적에 따라 advice 클래스를 before로 만들거나 타겟 클래스의 특정 메서드에 합치고자 하는 경우
// before로 만들 클래스
class A {
void print() {
sysout("A");
}
}
Target 클래스에 1을 출력하는 hello()메서드와 2를 출력하는 bye()메서드가 있을 때, hello()메서드에만 before를 건다면 t1.hello() 호출 시 그냥 타겟 클래스이니 1만 출력할 것이고 t2.hello() 를 호출하면 t2인스턴스는 비포가 적용된 프록시니까 A 가 나오고 1을 출력
타겟은 변화가 없으니 원래 일을 그대로 수행하고 프록시는 추가된 어드바이스에 따라/ 포인트컷에 따라 추가 기능이 붙음
포인트컷 인터페이스의 핵심은 어떤 클래스 어떤 메서드에 적용할 것이냐 즉 '어디에' 정보만 있음
클래스 필터는 클래스 동일 여부만 묻고 methodMatcher는 메서드 시그니처를 분석해서 참거짓 여부를 판단함
>>>> 결론, 포인트컷은 어떤 클래스 어떤 메서드인가 를 구분하는 boolean의 구성이라는 것
weaving : 어디에(포인트컷) 무엇을(어드바이스) 적용하는 과정으로 관념적 의미
aspect : 어디에(포인트컷) 무엇을(어드바이스) 적용하는 객체 / 자바의 어드바이저(좀더 범용적)와 같은 역할
proxy : 위빙이 적용된 것을 프록시라 부름 (advisor나 aspect를 이용해 새로 생성된 객체)
/* 정리 */
aspect = advice + pointcut
advisor = advice + pointcut OR advisor = advice + target
즉, AOP는 개발자가 어떤 메서드 작업을 수행하는데 조건에 따라 추가 기능 수행을 하고자 할 때 전후 처리로 자유롭게 등록해서 쓰는 기능