웹프로그래밍/spring

스프링4.0 - AOP 기초

Gamcho 2018. 7. 15. 19:23

AOP란?

어플리케이션의 공통 관심 사항과 핵심 관심 사항을 기준으로 프로그래밍 하므로써 공통 모듈을 여러 코드에 쉽게 적용하는 것


핵심 로직에서 공통 기능을 직접 호출 하지는 않는다.

공통 기능은 컴파일하거나 클래스를 로딩할때, 또는 클래스의 객체를 생성할때 AOP가 적용된다. 

즉, 핵심 로직 구현 코드에  공통 기능이 삽입된다.


※spring API 문서:

https://docs.spring.io/spring-framework/docs/2.0.x/reference/aop.html




AOP 용어

Joinpoint - 실행지점, 즉 Advice가 적용될 메소드


Pointcut - JoinPoint와 Advice를 연결해주는 설명어. 예를들어, Pointcut이 가리키는 특정 이름의 메소드에 Advice가 적용된다.

 

Advice - 공통 관심 기능을 JoinPoint에서 언제 적용할 것인지를 정의한다. ex) around, before, after Advice 등..


Weaving - 핵심로직과 Aspect를 연결하는 것.  Spring AOP에서는 runtime 단계에서 수행된다.


Aspect - 공통 관심 모듈(ex. 트랜잭션 관리, 보안)




  • 3가지 Weaving 방식


컴파일 시에 weaving - 자바 코드를 컴파일 하는 단계에서 핵심 로직이 구현된 자바 코드에 공통 코드를 삽입하면 AOP가 적용된 클래스 파일이 생성된다.


클래스 로딩 시에 weaving - JVM이 로딩한 클래스의 바이너리 정보를 토대로 알맞은 위치에 공통 코드를 삽입한 새로운 클래스 바이너리 코드를 만든다.


런타임 시에 weaving - 소스 코드나 클래스 정보를 직접 변경하지 않고 프록시를 이용하여 AOP를 적용한다. 오직 핵심 로직의 메소드가 실행되기 전이나 후에 프록시 객체를 이용하여 Advice를 적용한다. (필드 값 변경과 같은 Joinpoint는 적용할 수 없다.)



  • Advice 종류

Before Advice - 대상 객체의 메서드 호출 전

After Returning Advice - 대상 객체의 메서드가 익셉션 업이 실행 된 이후

After Throwing Advice - 대상 객체의 메서드 실행 도중 익셉션이 발생한 경우

After Advice - 익셉션 여부에 관계없이 대상 객체의 메서드 실행 후

Around Advice - 대상 객체의 메서드 실행 전, 후







스프링에서의 AOP


스프링은 프록시 기반의 AOP를 지원한다. 오직 메소드 호출 Joinpoint만 지원하며 자바 언어 기반이다.

필드 값 변경과 같은 다양한 Joinpoint를 사용하려면 AspectJ를 사용해야 한다.



  • 프록시를 이용한 AOP

스프링 컨테이너를 초기화하는 과정에서 xml 설정 정보에 저장된 bean 객체에 대한 프록시 객체를 생성해서

원본 bean 객체 대신 프록시 객체를 사용한다. 그 방식은 2가지로 나뉜다.


대상 객체가 인터페이스를 구현한 경우 - java.lang.reflect.Proxy를 이용하여 프록시 객체를 생성한다. 이때 생성된 프록시 객체는 대상 객체와 동일한

인터페이스를 구현하므로 인터페이스의 정의되지 않은 메서드에 대해서는 AOP가 적용되지 않는다.


대상 객체가 인터페이스를 구현하지 않은 경우 - 스프링이 CGLIB를 이용하여 대상 객체에 대한 프록시 객체를 생성한다. 즉, CGLIB가 대상 클래스를 상속받아 프록시를 구현한다. 그러므로 대상 클래스 또는 그 메서드가 final 인 경우 AOP를 적용할 수 없다.




  • XML 스키마 기반 AOP 구현


1. pom.xml에 스프링 AOP를 사용하기 위한 의존 설정 추가


<dependencies>

<dependency>

<groupId>org.springframework</groupId>

<artifactId>spring-aop</artifactId>

<version>4.0.4.RELEASE</version>

</dependency>


<dependency>

<groupId>org.aspectj</groupId>

<artifactId>aspectjweaver</artifactId>

<version>1.7.4</version>

</dependency>

</dependencies>



2. 공통 기능을 제공할 클래스 구현

먼저, 클래스에서 org.aspectj.lang.ProceedingJoinPoint 클래스를 import한다.

 그리고 Object 타입을 리턴하고 ProcedingJoinPoint 타입을 파라미터로 갖는 public 메소드를 만든다.

ProcedingJoinPoint 타입의 joinPoint 파라미터에 의해서 메소드를 공통 기능으로 구현할 수 있다.


import org.aspectj.lang.ProceedingJoinPoint;



public class Profiler {

public Object trace(ProceedingJoinPoint joinPoint) throws Throwable {

String signatureString = joinPoint.getSignature().toShortString();

System.out.println(signatureString + "시작");

long start = System.currentTimeMillis();

try {

Object result = joinPoint.proceed();

return result;

} finally {

long finish = System.currentTimeMillis();

System.out.println(signatureString + "종료");

System.out.println(signatureString + "실행 시간:"+(finish - start)+"ms");

}

}

}




3. XML 설정 파일에서 <aop:config>로 Aspect 설정 후 Advice가 적용될 Pointcut을 정한다.

<aop:pointcut>에서 expression은 spring.chap06 패키지 및 하위패키지에 모든 public 메서드를 Pointcut으로 설정한다.




<?xml version="1.0" encoding="UTF-8"?>


<beans xmlns="http://www.springframework.org/schema/beans"

xmlns:aop="http://www.springframework.org/schema/aop"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

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.xsd">


<!-- 공통 기능 제공 클래스 등록 -->

<bean id="profiler" class="spring.chap06.aop.Profiler"/>


<!-- Aspect 설정:Advice를 어떤 Pointcut에 적용할지 설정 -->

<aop:config>

<aop:aspect id="traceAspect" ref="profiler">

<aop:pointcut id="publicMethod" 

expression="execution(public * spring.chap06..*(..))"/>

<aop:around method="trace" pointcut-ref="publicMethod"/>

</aop:aspect>

</aop:config>


<!-- 핵심 기능 제공 클래스 -->

<bean id="writeArticleService" class="spring.chap06.board.WriteArticleServiceImpl">

<constructor-arg>

<ref bean="articleDao" />

</constructor-arg>

</bean>


<bean id="articleDao" class="spring.chap06.board.MemoryArticleDao"/>


<bean id="memberService" class="spring.chap06.member.MemberServiceImpl"/>

</beans>