이번 포스팅에서는 AOP의 개념과 Spring AOP를 적용하는 법 등 AOP
와 Spring AOP
에 대해 포스팅 해보고자 합니다.
AOP
(Aspect-Oriented Programming, 관점 지향 프로그래밍) 방식은핵심 관심사
(Core Concerns)와 횡단 관심사
(Cross-cutting Concerns)을 분리하여 가독성과 모듈성을 증진시킬 수 있는 프로그래밍 패러다임입니다.
프로그램을 개발하다보면 꼭 필요한 구문이지만 반복적이면서 길이만 차지하는 코드들이 많이 발견되는 것을 볼 수 있습니다.
이런 것을 보일러 플레이트(boilerplate) 코드
라고 하는데요!
가장 대표적인 예로 JDBC
를 들수 있습니다.
JDBC에서는
위의 프로세스대로 최소 10줄 이상의 코드가 발생합니다. 여기서 각 로직별 꼭 필요한 부분은 SQL문을 생성하고 ResultSet 결과를 처리하는 부분만이죠.
이러한 코드가 모든 로직에 있다고 생각하면 어떨까요? 생각만 해도 읽기 어려운 느낌이 나고 수정하기 엄청 귀찮고 복잡하겠죠?
이러한 부가적인 코드들을 횡단 관심사로 분리(AOP로 처리)하고 해당 메소드에는 핵심 로직만 수행할 수 있도록 코드를 분리하는 것을 AOP라고 할 수 있습니다.
AOP는 주로 사용되는 부가 기능인 로깅
, 트랜잭션
, 보안
, 캐싱
등에 활용됩니다.
특정 대상(메소드)에서 횡단 로직(Advice)이 수행될 시점을 의미합니다.
이러한 시점(JoinPoint
)에는 before
, after-returning
, after-throwing
, after
, around
등이 있습니다.
횡단 로직(Advice)이 수행될 대상을 의미합니다.
Pointcut
은 표현식
으로 표현되는데, 대표적으로는 execution
이 있습니다.
excution({접근 제어자} /{패키지 이름}.{클래스 이름}.{메소드 이름({매개변수})})
횡단 관심사를 구현한 실제 구현체(Class)
를 의미합니다.
Aspect
는 JoinPoint
와 Advice
의 결합한 객체를 의미합니다. 다시말해서, 횡단 관심사 로직과 로직이 수행될 시점을 결합하여 모듈화한 객체입니다.
pom.xml
은 Maven 프로젝트의 프로젝트 관리 파일입니다.
이 파일에 Spring AOP 라이브러리 의존성
을 추가하면 자동으로 Maven
이 라이브러리를 관리해줍니다.
Spring에서 aop를 사용하기 위해서는 aop 관련 라이브러리인 AspectJ를 추가해야 합니다.
Spring Legacy Project에서 Project 생성시 자동 생성되는 Runtime(rt)라이브러리가 있지만, 실제 개발을 위해서는 weaver 라이브러리를 추가해주어야 합니다.
아래와 같이 라이브러리를 추가합니다.
※ 버전은 얼마든지 변경될수 있습니다.
<org.aspectj-version>1.9.9.1</org.aspectj-version>
<!-- AspectJ Runtime -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>${org.aspectj-version}</version>
</dependency>
<!-- AspectJ Util -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>${org.aspectj-version}</version>
</dependency>
AOP를 구현하는 방법에는 XML
방식, Annotation
기반 방식으로 총 두가지가 있습니다.
<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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/aop/
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd ">
<aop:config>
<!-- Spring AOP Pointcut definitions -->
<aop:pointcut id="loggingOperation"
expression="execution(* com.demo.aop.EmployeeManager.*(..))" />
<aop:pointcut id="transactionOperation"
expression="execution(* com.demo.aop.EmployeeManager.getEmployeeById(..))" />
<!-- Spring AOP aspect 1 -->
<aop:aspect id="loggingAspect" ref="loggingAspectBean">
<!-- Spring AOP advises -->
<aop:before pointcut-ref="loggingOperation" method="logBefore" />
<aop:after pointcut-ref="loggingOperation" method="logAfter" />
</aop:aspect>
<!-- Spring AOP aspect 2 -->
<aop:aspect id="transactionAspect" ref="transactionAspectBean">
<aop:before pointcut-ref="transactionOperation" method="getEmployeeById" />
</aop:aspect>
</aop:config>
<!-- Spring AOP aspect instances -->
<bean id="loggingAspectBean" class="com.demo.aop.LoggingAspect" />
<bean id="transactionAspectBean" class="com.demo.aop.TransactionAspect" />
<!-- Target Object -->
<bean id="employeeManager" class="com.demo.aop.EmployeeManagerImpl" />
</beans>
<aop:config>
(aop 루트 노드) 안에<aop:pointcut>
태그로 실행시점(pointcut) 정의<aop:aspect>
태그로 클래스(advice)를 실행시점(pointcut)과 결합 정의Annotation을 적용하기 위해서는 root-context안에 aop 네임스페이스를 추가하고, autoproxy를 설정해야 합니다.
autoproxy란 @Aspect가 적용된 빈을 Aspect로 사용할 수 있게하는 설정입니다.
<?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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd">
<!-- Root Context: defines shared resources visible to all other web components -->
<!-- AspectJ 라이브러리를 이용한 Proxy 객체를 생성 -->
<aop:aspectj-autoproxy />
</beans>
Annotation | Description |
---|---|
@Aspect | 이 Class가 횡단 관심사 로직을 모듈화한 객체(Aspect)라는 것을 알려주는 Annotation |
@Pointcut | 횡단 관심사가 적용될 JoinPoint들을 표현식으로 정의한 것 |
@Before | Method 가 호출되기 전에 실행되는 Advice 기능 수행 |
@Around | Method 실행 중에 호출 전후에 어드바이스 기능을 수행 |
@After | Method의 결과에 관계 없이(성공, 예외 관련없이. 마치 finally 같은 개념) 타겟 메소드가 완료되면 어드바이스 기능을 수행 |
@AfterReturning | Method가 성공적으로 결과 값을 반환한 후 Advice 기능 수행 |
@AfterThrowing | Method가 수행 중 예외를 던지게 되면 Advice 기능 수행 |
아래는 코드 사용 예시입니다.
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
/*
- Aspect: 해당 클래스가 Aspect라는 것을 선언합니다.
- Component: Spring이 해당 빈을 서칭할 수 있도록 선언합니다.
*/
@Aspect
@Component
public class CommonAspect {
// before advice
@Before("execution(* method1())")
public void beforeMethod() {
System.out.println("beforeMethod 호출");
}
// after advice
@After("execution(* method1())")
public void afterMethod() {
System.out.println("afterMethod 호출");
}
// around advice
@Around("execution(* method1())")
public Object aroundMethod(ProceedingJoinPoint pjp) throws Throwable{
System.out.println("aroundMethod 호출 1");
Object result = pjp.proceed();
System.out.println("aroundMethod 호출 2");
return result;
}
// after-returning advice
@AfterReturning("execution(* method1())")
public void afterReturningMethod() {
System.out.println("afterReturning 호출");
}
// after-throwing advice
@AfterThrowing("execution(* method1())")
public void afterThrowingMethod() {
System.out.println("afterThrowing 호출");
}
}