23.02.16

이동욱·2023년 8월 6일
0

NaverCloudCamp

목록 보기
26/42

학습내용

  1. AOP 용어
  2. InvocationHandler
  3. Spring AOP
  4. Spring AutoProxy
  5. Spring AutoProxy Annotation

AOP 용어

  1. core concern, cross cutting concern

DAO에서의 중요 관심사는 sql전송, 받아온 데이터를 binding하는 작업이다.
그러나 그 이외에도 transaction처리, try-catch를 통한 exception처리, log등 중요 관심사 이외의 요구사항들이 존재한다.
AOP에서는 이러한 중요 관심사를 core concern, 중요 관심사 이외의 처리 사항들을 cross cutting concern이라 한다.
AOP는 source code내의 물리적으로 존재하는 모듈화한 부가 기능들까지 제거하기위해 나온 개념이다.

  1. proxy(직접구현)

기존 객체를 구현하여 생성한 대리 객체이다.
요청이 들어오면 요청 객체에 대한 proxy 객체를 실행하며 proxy객체는 다시 내부적으로 원래 객체를 호출한다.
java 진영에서 표준화 해주기 시작함

  1. Dynamic proxy(runtime에 자동으로 구현)

개발자가 합쳐서 만드는것이 아닌 api가 weaving을 수행하여 만든 proxy객체

  1. weaving

분리된 core concern과 cross concern을 합치는 과정

  1. target Object

core concern, 기존처럼 interface기반으로 구현

  1. aspect
    부가 기능인 advice와 advice가 붙을 위치인 point-cut이 합쳐진
  1. join-point

advies가 붙는곳을 의미한다, spring에서는 method만이 join-point이다.

  1. point-cut

모든 메서드에서 실행되는것이 아닌 특정 메서드에만 적영되게 하는 rule이다.
spring framework에서는 AspectJExpressionPointcut lib를 통해 해당 기능을 제공하며 아래와 같은 rule을 사용한다.

	<bean 	id="testPointcut" class="org.springframework.aop.aspectj.AspectJExpressionPointcut">
		<property 	name="expression" 	 value="execution(public !void get*(..))" />
	</bean>
  1. advisor
    advice와 point-cut이 합쳐진 개념으로 정해진 point-cut에 따라 원하는 메서드에 advice 기능을 실행한다.
	<!-- 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>	

InvocationHandler

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

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");

	}
}

Spring AutoProxy

매번 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"/>

Spring AutoProxy Annotation

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");
	}
}
profile
Backend Developer

0개의 댓글