DAO에서의 중요 관심사는 sql전송, 받아온 데이터를 binding하는 작업이다.
그러나 그 이외에도 transaction처리, try-catch를 통한 exception처리, log등 중요 관심사 이외의 요구사항들이 존재한다.
AOP에서는 이러한 중요 관심사를 core concern, 중요 관심사 이외의 처리 사항들을 cross cutting concern이라 한다.
AOP는 source code내의 물리적으로 존재하는 모듈화한 부가 기능들까지 제거하기위해 나온 개념이다.
기존 객체를 구현하여 생성한 대리 객체이다.
요청이 들어오면 요청 객체에 대한 proxy 객체를 실행하며 proxy객체는 다시 내부적으로 원래 객체를 호출한다.
java 진영에서 표준화 해주기 시작함
개발자가 합쳐서 만드는것이 아닌 api가 weaving을 수행하여 만든 proxy객체
분리된 core concern과 cross concern을 합치는 과정
core concern, 기존처럼 interface기반으로 구현
advies가 붙는곳을 의미한다, spring에서는 method만이 join-point이다.
모든 메서드에서 실행되는것이 아닌 특정 메서드에만 적영되게 하는 rule이다.
spring framework에서는 AspectJExpressionPointcut lib를 통해 해당 기능을 제공하며 아래와 같은 rule을 사용한다.
<bean id="testPointcut" class="org.springframework.aop.aspectj.AspectJExpressionPointcut">
<property name="expression" value="execution(public !void get*(..))" />
</bean>
<!-- Advisor(Aspect) Definition: advice + Point Cut -->
<bean id="testAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
<property name="advice" ref="testAdvice"/>
<property name="pointcut" ref="testPointcut"/>
</bean>
advisor는 아래와 같이 xml에 서술 가능하다.
<!-- Dynamic Proxy 를 생성하는 ProxyFactoryBean 생성 -->
<bean id="message" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces" value="spring.service.aop.Message"/>
<property name="interceptorNames" value="testAdvisor" />
<property name="target" ref="messageTarget"/>
</bean>
invoke method 안에 선처리 후처리할 cross cutting concern을 구현하면 core concern method를 주입받아 core concern과 cross cutting concern을
weaving(결합)하는 역할을 한다.
public class LoggingHandler implements InvocationHandler {
private Object targetObject;
public LoggingHandler(){
}
public LoggingHandler(Object tagetObject) {
this.targetObject = tagetObject;
}
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println("선처리 cross concern 수행");
if(args != null){
System.out.println("[LOG] Target Object 호출 할 method에 전달되는 인자 : "+args[0]);
}
try {
//==> 타겟 객체의 Method 를 호출 하는 부분
return method.invoke(targetObject, args);
} catch (Exception e) {
throw e;
}finally{
System.out.println("후처리 cross concern 수행");
}
}
}
Spring AOP를 많이 사용하는 이유는 개별적으로 AOP만 지원하는 framework를 사용하면 개발자가 직접 cross concern과 cross cutting concern을 결합한
proxy를 직접 생성해야한다. Spring framework를 사용하면 bean/ioc container에서 dynamic proxy를 runtime에 동적으로 생성해준다.
xml을 이용한 AOP 사용 방법
xml meta data에 core concern class, cross cutting concern class를 서술한 뒤
ProxyFactoryBean에 core concern interface정보 그것을 구현한 구현체, cross cutting concern class를 서술한다
<!-- core concern bean -->
<bean id="messageTarget" class="spring.service.aop.impl.MessageImpl"/>
<!-- cross cutting concern bean -->
<bean id="testAdvice" class="spring.service.aop.advice.TestAdvice"/>
<!-- proxy(weaving) bean -->
<bean id="message" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces" value="spring.service.aop.Message"/>
<property name="interceptorNames" value="testAdvice"/>
<property name="target" ref="messageTarget"/>
</bean>
AOP interface들을 상속빋아 다양한 시점에 advice를 실행 할 수 있다.
public class TestAdvice implements MethodBeforeAdvice, AfterReturningAdvice, ThrowsAdvice, MethodInterceptor {
@Override
public void afterReturning(Object arg0, Method arg1, Object[] arg2, Object arg3) throws Throwable {
System.out.println("[after LOG] " + getClass() + " after Start");
System.out.println("[after LOG] " + "Target Call Method " + arg1);
System.out.println("[after LOG] " + "Target Call Method 타겟 객체 호출 후 return value " + arg0);
System.out.println("[after LOG] " + getClass() + " before End");
}
@Override
public void before(Method arg0, Object[] arg1, Object arg2) throws Throwable {
System.out.println("===============================================");
System.out.println("[before LOG] " + getClass() + " before Start");
System.out.println("[before LOG] " + " Target Call Method " + arg0);
if(arg1.length != 0) {
System.out.println("[before LOG] " + " Target Call Method 전달 Parameter " + arg1[0]);
}
System.out.println("[before LOG] " + getClass() + " before End");
}
@Override
public Object invoke(MethodInvocation arg0) throws Throwable {
System.out.println("[Around before LOG]" + getClass() + "invoke() Start");
System.out.println("[Around before LOG]" + "Target Call Method" + arg0);
if(arg0.getArguments().length != 0) {
System.out.println("[before LOG]" + "Target Call Method 전달 Parameter " + arg0.getArguments()[0]);
}
Object object = arg0.proceed();
System.out.println("[Around before LOG]" + "Target 객체 호출 후 return value " + object);
System.out.println("[Around before LOG]" + getClass() + "invoke() End");
return object;
}
public void afterThrowing(Throwable throwable) {
System.out.println("[exception]" + getClass() + "afterThrowing() Start");
System.out.println("[exception] Exception 발생");
System.out.println("[exception] Exception message : " + throwable.getMessage());
System.out.println("[exception]" + getClass() + "afterThrowing() End");
}
}
context 설정 정보를 가지고 있는 ApplicationContext interface를 통해 AOP 정보들를 가진 xml을 parsing하여 dynamic proxy를 생성 가능하다.
public class MessageTestAppUseSpringAOP02 {
///Main Method
public static void main(String[] args) throws Exception{
ApplicationContext context =
new ClassPathXmlApplicationContext("/config/messageservice02.xml");
Message message = (Message)context.getBean("message");
message.setMessage("Hello");
}
}
매번 xml에 advice, targetObject, point-cut을 조합하여 proxy를 만드는것은 번거로운일이다.
Spring에서는 autoProxy기능을 제공하여 동적으로 proxy 객체를 생성해준다.
<bean id="message" class="spring.service.aop.impl.MessageImpl"/>
<bean id="testAdvice" class="spring.service.aop.advice.TestAdvice"/>
<bean id="testAdvisor"
class="org.springframework.aop.aspectj.AspectJExpressionPointcutAdvisor">
<property name="advice" ref="testAdvice"/>
<property name="expression" value="execution(* *.getMessage(..))" />
</bean>
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>
xml에 모든 target object, advice, point-cut을 기술하면 xml이 방대해져 유지보수에 문제가 생긴다.
java에서 제공하는 annotation 기능을 이용하면 xml을 간략하게 사용 할 수 있다.
Bean Container에 등록하기 위해 bean정보만 기술
<bean id="message" class="spring.service.aop.impl.MessageImpl"/>
<!-- Advisor(Aspect = Advice + Point Cut) Definition :: Annotation 기반 -->
<bean id="testAspect" class="spring.service.aop.advice.TestAspectJ01"/>
<!-- Dynamic Proxy 를 자동으로 생성하는 AutoProxyCreator Definition :: Annotation 기반
<bean
class="org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator"/>
-->
<aop:aspectj-autoproxy/>
annotation을 통해 해당 code가 Aspect인지, advice인지 point-cut인지 기술
@Aspect
public class TestAspectJ02 {
public TestAspectJ02() {
System.out.println(":: TestAspectJ01 default constructor");
}
@AfterReturning(pointcut = "within(spring.service.aop..*)", returning = "returnValue")
public void afterReturning(JoinPoint joinPoint, Object returnValue) throws Throwable {
System.out.println("[after LOG] " + getClass() + " after Start");
System.out.println("[after LOG] " + "Target Call Method 타겟 객체" + joinPoint.getTarget().getClass().getName());
System.out.println("[after LOG] " + "Target Call Method 타겟 객체 method" + joinPoint.getSignature().getName());
System.out.println("[after LOG] " + "Target Call Method 타겟 객체 호출 후 return value " + returnValue);
System.out.println("[after LOG] " + getClass() + " before End");
}
@Before("execution(* *.getMessage(..))")
public void before(JoinPoint joinPoint) throws Throwable {
System.out.println("===============================================");
System.out.println("[before LOG] " + getClass() + " before Start");
System.out.println("[before LOG] " + " Target Call Method " + joinPoint.getSignature().getName());
if(joinPoint.getArgs().length != 0) {
System.out.println("[before LOG] " + " Target Call Method 전달 Parameter " + joinPoint.getArgs()[0]);
}
System.out.println("[before LOG] " + getClass() + " before End");
}
@Around("execution(* spring.service.aop.*.*(..) )")
public Object invoke(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("[Around before LOG]" + getClass() + "invoke() Start");
System.out.println("[Around before LOG]" + "Target Call Method 타겟 객체 method" + joinPoint.getSignature().getName());
System.out.println("[Around before LOG]" + "Target Call Method 타겟 객체 method" + joinPoint.getSignature().getName());
if(joinPoint.getArgs().length != 0) {
System.out.println("[before LOG]" + "Target Call Method 전달 Parameter " +joinPoint.getArgs()[0]);
}
Object object = joinPoint.proceed();
System.out.println("[Around before LOG]" + "Target 객체 호출 후 return value " + object);
System.out.println("[Around before LOG]" + getClass() + "invoke() End");
return object;
}
@AfterThrowing(pointcut="execution (public * *(..))", throwing="throwable")
public void afterThrowing(JoinPoint joinPoint, Throwable throwable) {
System.out.println("[exception]" + getClass() + "afterThrowing() Start");
System.out.println("[exception] Target Call Method 타겟 객체" + joinPoint.getTarget().getClass().getName());
System.out.println("[exception] Target Call Method 타겟 객체 method" + joinPoint.getSignature().getName());
System.out.println("[exception]" + getClass() + "afterThrowing() End");
}
}