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 |