본문 바로가기
Spring Framework/study

AOP(Aspect Oriented Programming) ?

by bloodFinger 2019. 12. 18.

AOP 란?
어플리케이션의 핵심적인 기능에서 부가적인 기능을 분리해서 애스펙트라는 모듈로 만들어서 설계하고 개발하는 방법을 AOP(Aspect Oriented Programming)이라고 한다

Aspect란?

어플리케이션의 핵심 기능은 아니지만, 어플리케이션을 구성하는 중요한 요소이고, 부가적인 기능을 담당하는 요소이다.

 

객체지향 모듈을 오프젝트라 부르는것과 비슷하게 부가기능 모듈을 애스펙트라고 부르며, 핵심기능에 부가되어 의미를 갖는 특별한 모듈이라 생각하시면 됩니다. 애스펙트는 부가될 기능을 정의한 어드바이스와 어드바이스를 어디에 적용할지를 결정하는 포인트컷을 함께 갖고 있습니다.


Aspect = Advice + Pointcut
어드바이저는 단순한 형태의 애스펙트라고 볼 수 있다.

 

 

 

AOP 구현 방법 - 3가지

1. 컴파일 A.java ----(AOP)----> A.class   <aspectJ>

 

2. 바이트 코드 조작  A.java --> A.class -----(AOP)-----> memory <aspectJ>

 - 컴파일 된 타깃 클래스의 파일 자체를 수정하거나 클래스가 JVM에 로딩되는 시점을 가로채서 바이트코드를 조작

 

3. 프록시 패턴 <Spring AOP>

 

1번과 2번 사용 이유!

  • 스프링과 같은 DI 컨테이너의 도움이 필요 없다
    스프링과 같은 컨테이너가 사용되지 않는 환경에서 AOP 적용이 가능하다

  • 프록시보다 훨씬 강력하고 유연하다.
    프록시 방식은 타깃 오브젝트가 생성되고 난 후부터 적용이 가능하다
    하지만 AspectJ는 어떤 순간에든지 적용이 가능하다
    클래스 바이트코드를 직접 조작하는 것이기 때문에 거의 제한이 없다
    대부분의 부가기능은 프록시 방식을 사용해 메소드의 호출 시점에 부여하는 것으로도 충분하다
    AspectJ와 같은 고급 AOP 기술은 바이트코드 조작을 위해 JVM의 실행옵션을 변경하고, 별도의 바이트코드 컴파일러를 사용하고, 특별한 클래스 로더를 사용하는 등 번거로운 작업이 필요하다

 

 

AOP 용어 정리

Target 

  부가기능을 부여할 대상, 핵심기능이 담긴 클래스

 Advice

 부가기능을 담음 모듈

 Joinpoint

 Advice를 적용 가능한 지점을 의미 ,메소드 호출, 필드 값 변경 등

스프링의 프록시 AOP에서 조인 포인트는 메소드의 실행 단계뿐이다

타깃 오브젝트가 구현한 인터페이스의 모든 메소드가 조인 포인트가 된다

 Pointcut

 조인 포인트를 선별하는 기능을 정의한 모듈

 가능한 조인 포인트들 중에 실제로 부가기능을 적용할 것들을 선별한다

 클래스를 선정하고, 그 안의 메소드를 선정하는 과정을 거친다

 실제로 Advice가 적용되는 Joinpoint를 나타낸다

 Proxy

  클라이언트와 타깃 사이에 존재하면서 부가기능을 제공하는 오브젝트

  클라이언트는 타깃을 요청하지만, 클라이언트에게는 DI를 통해 타깃 대신 프록시가 주입된다

  클라이언트의 메소드 호출을 대신 받아서 타깃에게 위임하며, 그 과정에서 부가기능을 부여한다

  스프링 AOP는 프록시를 이용한다

 Advisor

 포인트컷과 어드바이스를 하나씩 갖고 있는 오브젝트

  AOP의 가장 기본이 되는 모듈이다

  스프링은 자동 프록시 생성기가 어드바이저 단위로 검색해서 AOP를 적용한다

 Aspect

 다수의 포인트컷과 어드바이스의 조합으로 만들어진다

 보통 싱글톤 형태의 오브젝트로 존재한다

  어드바이저는 아주 단순한 애스펙트라고 볼 수 있다

 Weaving 

  Advice를 핵심로직코드에 적용하는 것을 Weaving라고 한다. 

 

그럼 2가지 방법(xml , annotation)으로 예시를 통해서 이해해보자

 

XML설정으로 AOP 구현

-인터페이스를 만들고

public interface MessageBean {
	
	//조인포인트 - 모두 / 포인트 컷 - display 제외한 모두
 
	public void showPrintBefore();
	public void viewPrintBefore();
	
	public void showPrintAfter();
	public void viewPrintAfter();
	
	public String showPrint();
	public void viewPrint();
	
	public void display();
	
}

 -MessageBean 인터페이스를 구현한 클래스   

import lombok.Setter;

//target 클래스
@Setter
public class MessageBeanImpl implements MessageBean {
	private String str;
	//private String LoggingAdvice;
	
	@Override
	public void showPrintBefore() {
		System.out.println("showPrintBefore 메세지 = " + str);
	}
	@Override
	public void viewPrintBefore() {
		
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("viewPrintBefore 메세지 = " + str);
	}
	
	
	@Override
	public void showPrintAfter() {
		System.out.println("showPrintAfter 메세지 = " + str);
	}
	@Override
	public void viewPrintAfter() {
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("viewPrintAfter 메세지 = " + str);
	}
	
	@Override
	public String showPrint() {
		System.out.println("showPrint 메세지 = " + str);
		return "SPRING AOP";
	}
	@Override
	public void viewPrint() {
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("viewPrint 메세지 = " + str);
	}
	
	@Override
	public void display() {
		System.out.println("display 메세지 = " + str);
	}
	
}

 

 

 

package sample01;

import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.util.StopWatch;

//공통 모듈 ,aspect
public class LoggingAdvice {
	
	public void beforeTrace() {
		System.out.println("before trace..");
	}
	
	public void afterTrace() {
		System.out.println("after trace..");
	}
	
	public void beforeDisplay() {
		System.out.println("beforeDisplay ...");
	}
	
	public void trace(ProceedingJoinPoint joinPoint) throws Throwable {
		System.out.println("before trace..");
		
		joinPoint.proceed();
        
		System.out.println("after trace..");
	}
}

타켓 클래스 생성

 

package sample01;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class HelloSpring {

	public static void main(String[] args) {
		ApplicationContext context = new ClassPathXmlApplicationContext("acQuickStart.xml");
		MessageBean messageBean = (MessageBean) context.getBean("messageBeanImpl");
		messageBean.showPrintBefore();
		System.out.println("----------------------");
		messageBean.viewPrintBefore();
		System.out.println("----------------------");
		messageBean.display(); //대상자가 포인트컷이 아니기 때문에 안들어온다
		
		messageBean.showPrintAfter();
		System.out.println("----------------------");
		messageBean.viewPrintAfter();
		System.out.println("----------------------");
		messageBean.display(); //대상자가 포인트컷이 아니기 때문에 안들어온다
		
		messageBean.showPrint();
		System.out.println("----------------------");
		messageBean.viewPrint();
		System.out.println("----------------------");
		messageBean.display(); //대상자가 포인트컷이 아니기 때문에 안들어온다
	}

}

 

xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:aop="http://www.springframework.org/schema/aop"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">

	<bean id="messageBeanImpl" class="sample01.MessageBeanImpl">
		<property name="str" value="하이2"/>
	</bean>
	
	<bean id="loggingAdvice" class="sample01.LoggingAdvice"></bean>
	
	<!-- AOP before -->
	<aop:config>
		<aop:pointcut expression="execution(public void sample01.MessageBeanImpl.*PrintBefore(..))" id="methodBefore"/>
		<aop:aspect ref="loggingAdvice">
			<aop:before method="beforeTrace" pointcut-ref="methodBefore"/>
		</aop:aspect>
	</aop:config>
	
	<aop:config>
		<aop:pointcut expression="execution(public * *.*.*PrintAfter(..))" id="printAfter"/>
		<aop:aspect ref="loggingAdvice">
			<aop:after method="afterTrace" pointcut-ref="printAfter"/>
		</aop:aspect>
	</aop:config>
	
	<!-- display before -->
	<!-- <aop:config>
		<aop:pointcut expression="execution(public void sample01.MessageBeanImpl.*display(..))" id="displyBefore"/>
		<aop:aspect ref="loggingAdvice">
			<aop:before method="beforeDisplay" pointcut-ref="displyBefore"/>
		</aop:aspect>
	</aop:config> -->
	
	<aop:config>
		<aop:pointcut expression="execution(public * *.*.*Print(..))" id="methodAround"/>
		<aop:aspect ref="loggingAdvice">
			<aop:around method="trace" pointcut-ref="methodAround"/>
		</aop:aspect>
	</aop:config>

</beans>

 

Annotation으로 AOP구현

HelloSpring.java

package sample01;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class HelloSpring {

	public static void main(String[] args) {
		ApplicationContext context = new ClassPathXmlApplicationContext("acQuickStart.xml");
		MessageBean messageBean = (MessageBean) context.getBean("messageBeanImpl");
		messageBean.showPrintBefore();
		System.out.println("----------------------");
		
		messageBean.showPrintAfter();
		System.out.println("----------------------");
		
		messageBean.showPrint();
		System.out.println("----------------------");

	}

}

 

MessageBean.java

package sample01;

public interface MessageBean {
	
	//조인포인트 는 모두
	//포인트 컷 아래 6개
	public void showPrintBefore();
	public void viewPrintBefore();
	
	public void showPrintAfter();
	public void viewPrintAfter();
	
	public String showPrint();
	public void viewPrint();
	
	public void display();
	
}

 

MessageBeanImpl.java

package sample01;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;


//target 클래스
@Component
public class MessageBeanImpl implements MessageBean {
	private @Value("hava a nice day") String str;
	
	@Override
	public void showPrintBefore() {
		System.out.println("showPrintBefore 메세지 = " + str);
	}
	@Override
	public void viewPrintBefore() {
		
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("viewPrintBefore 메세지 = " + str);
	}
	
	
	@Override
	public void showPrintAfter() {
		System.out.println("showPrintAfter 메세지 = " + str);
	}
	@Override
	public void viewPrintAfter() {
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("viewPrintAfter 메세지 = " + str);
	}
	
	@Override
	public String showPrint() {
		System.out.println("showPrint 메세지 = " + str);
		return "SPRING AOP";
	}
	@Override
	public void viewPrint() {
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("viewPrint 메세지 = " + str);
	}
	
	@Override
	public void display() {
		System.out.println("display 메세지 = " + str);
	}
	
}

 

LoggingAdvice.java

package sample01;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.context.annotation.Role;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;

//공통 모듈 ,aspect
@Component
@Aspect
public class LoggingAdvice {
	@Before("execution(public void sample01.MessageBeanImpl.*PrintBefore())")
	public void beforeTrace() {
		System.out.println("before trace..");
	}
	@After("execution(public * *.*.*PrintAfter(..))")
	public void afterTrace() {
		System.out.println("after trace..");
	}
	
	@Around("execution(public * *.*.*Print(..))")
	public void trace(ProceedingJoinPoint joinPoint) throws Throwable {
		String methodName = joinPoint.getSignature().toShortString(); //어떤 메소드가 나를 호출했는지 이름을 가져오기
		System.out.println("어떤 메소드가 나를 호출 methodName = " + methodName);
		
		StopWatch sw = new StopWatch();
		sw.start(methodName);
		System.out.println("호출전 : " + methodName);
		
		Object ob = joinPoint.proceed();
		System.out.println("ob = " + ob);
		
		sw.stop();
		System.out.println("호출후 처리시간 =" +methodName);
		System.out.println("처리시간 " + sw.getTotalTimeMillis()/1000 + "초");
	}
}

 

 

 

결과

'Spring Framework > study' 카테고리의 다른 글

Spring 트랜잭션 처리 @Transactional  (0) 2020.03.07
Spring Bean Scope ?  (0) 2020.03.06
DI(Dependency Injection) ?  (0) 2019.12.23
IoC Container ?  (0) 2019.12.23
테스트  (0) 2019.12.20