[Spring] Spring AOP에 대한 이해

DYKO·2022년 11월 5일
0

Spring Framework

목록 보기
5/7

💡 Spring AOP 기능 및 목적

Spring AOP는 앞서 설명한 것과 같이 Aspect를 모듈화하여 객체 지향 프로그래밍을 보완하는 역할이다. 순수 자바로 구현되어 특별한 컴파일 과정이 필요하지 않고, Spring IoC 컨테이너에 의존한다. AOP 주입을 메서드 전후로만 지원하고, Spring Framework 에서 선언적 트랜잭션 관리에 중점적으로 사용된다.
Spring은 Spring AOP의 목표는 완벽한 AOP의 구현보다 Spring IoC와 매끄럽게 통합하여 문제를 해결하는데 도움이 되는 것이라고 한다. 따라서 세분화된 객체(도메인 객체)에 AOP를 적용하고 싶다면, AspectJ를 활용하라고 제안하고 있다. 즉, Spring은 AspectJ와 Spring AOP의 모든 기능을 활용하여 Spring 기반 애플리케이션에서 모든 AOP 기능을 사용할 수 있도록 지원하고 있다.

💡 Proxy 패턴을 이용한 Spring AOP 구현 방법

Proxy 패턴: 어떤 객체에 대한 접근을 제어하기 위한 용도로, 실제 객체의 메서드를 호출하면 그 호출을 중간에 가로채는 패턴

스프링에서는 다이내믹 프록시를 생성해서 Bean 객체로 등록하게 해주는 ProxyFactoryBean을 제공한다. ProxyFactoryBean은 순수하게 프록시를 생성하는 작업만을 담당하고, 프록시를 통해 제공해줄 Apsect는 별도의 빈에 구현할 수 있다. 그리고 생성하는 프록시에서 사용할 부가기능은 MethodInterceptor 인터페이스를 구현하여 만드는데, 해당 인터페이스의 invoke() 메서드는 ProxyFactoryBean으로부터 타깃 오브젝트에 대한 정보를 함께 제공받아 타깃이 다른 여러 프록시에서 함께 사용할 수 있다. (타깃이 달라지면 새로운 InvocationHandler를 구현해야 하는 다이내믹 프록시의 단점을 보완)
ProxyFactory를 통해 생성된 프록시는 클라이언트로부터 요청을 받으면 메서드 선정 알고리즘을 담은 오브젝트인 Pointcut에 부가기능을 부여할 메서드인지 확인 요청을 보낸다. 이때, 포인트컷 오브젝트는 Pointcut 인터페이스를 구현해서 만들 수 있다. 포인트컷에서 부가기능을 적용할 대상 메서드임을 확인 받으면, MethodInteceptor 타입의 Advice 객체를 호출하여 부가기능을 제공할 수 있도록 한다. 이때, Advice 객체에서 타깃의 메서드 호출이 필요할 시, 프록시로부터 전달받은 MethodInvocation 타입의 콜백 객체의 proceed() 메서드를 호출하여 타깃의 메서드를 호출하여 타깃에 의존하지 않도록 템플릿/콜백 구조로 되어 있다.
그렇다면 Advisor 왜 필요할까? AdvisorPointcutAdvice로 이루어진 객체이다. 포인트컷과 어드바이스는 별개의 객체로 등록할 수 있지만 어드바이저에 묶어 구현하는 이유는 애플리케이션에는 여러 개의 포인트컷과 어드바이스가 계속 추가될 수 있기 때문이다. 포인트컷과 어드바이스를 조합해 Advisor에 등록함으로써 어떤 어드바이스에 어떤 포인트컷을 적용할지 명확하게 한다.


💡 Spring AOP 구현

1. XML 기반의 POJO 클래스 이용

먼저, 아래와 같이 transaction 부가기능이 구현된 Advice 객체를 생성한다. 트랜잭션 Aspect를 적용할 메서드 선정을 위한 포인트컷 빈은 스프링이 제공하는 포인트컷 클래스를 사용한다.

package springbook.user.service;

public class TransactionAdvice implements MethodInterceptor {
	PlatformTransactionManager transactionManager;
	
	public void setTransactionManager(PlatformTransactionManager transactionManager) {
		this.transactionManager = transactionManager;
	}
	
	// 프록시에서 callback 객체를 인자로 받음
	public Object invoke(MethodInvocation invocation) throws Throwable {
		TransactionStatus status = this.transactionManager.getTransaction(new DefaultTransactionDefinition());
		
		try {
			Object ret = invocation.proceed(); // callback 호출(타깃 메소드 실행)
			this.transactionManager.commit(status);
			return ret;
		} catch (RuntimeException e) {
			this.transactionManager.rollback(status);
			throw e;
		}
	}
}

Advice 클래스 작성 후, XML 설정파일에 <aop:config>태그를 이용하여 Aspect를 설정한다. 이 때, AspectJ의 포인트컷 표현식을 사용하여 포인트컷을 설정할 수 있다.

<?xml version="1.0" encoding="UTF-8"?>
<!-- aop namespace 선언 -->
<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.xsd
						http://www.springframework.org/schema/aop
						http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
	
	...
  
	<!-- aop class bean 등록 -->
	<aop:config>
		<!-- ServiceImpl로 끝나는 모든 클래스의 upgrade로 시작하는 메서드를 선정 -->
		<aop:pointcut id="transactionPointcut" expression="execution(* *..*ServiceImpl.updgrade*(..))" />
		<aop:advisor advice-ref="transactionAdvice" pointcut-ref="transactionPointcut" />
	</aop:config>
</beans>

또한 아래와 같이 <aop:advisor> 태그를 사용할 수도 있다. 다만 하나의 포인트컷을 여러 개의 어드바이저에서 공유하려고 하는 경우에는 독립적인 포인트컷 태그로 등록해야 한다.

<aop:config>
	<aop:advisor advice-ref="transactionAdvice" pointcut="execution(* *..*ServiceImple.updgrade*(..))" />
</aop:config>

2. @Aspect 어노테이션 이용

@Aspect 어노테이션을 적용한 Aspect 클래스에 Advice를 구현하는 메서드와 Pointcut을 작성 후, XML 설정파일에 <aop:aspectj-autoproxy/>를 설정하면 해당 클래스를 Aspect로 사용 가능하다. 먼저 AOP와 관련된 어노테이션은 아래와 같다.

  • @Aspect: 어노테이션이 적용된 class를 Advisor (Pointcut + Advice)로 만듦
  • @Around: 메서드 전구역에 AOP 주입
  • @Before: 메서드 시작 전에 AOP 주입
  • @After: 메서드 종료 후에 AOP 주입
  • @AfterReturning: 메서드 정상 종료 후 AOP 주입
  • @AfterThrowing: 메서드에서 예외가 발생하며 종료된 후 AOP 주입
  • @Pointcut: Pointcut 지시자를 미리 설정

특정 기능을 사용했을 때 Logging을 위한 Aspect를 생성해보자. 먼저 @Aspect 태그가 적용된 클래스를 생성하고 주입할 부가기능이 작성된 메서드를 생성하여 Advice 어노테이션을 적용한다. Advice 어노테이션을 적용할 때, AspectJ의 pointcut 표현식을 사용하여 어드바이스에 적용할 포인트컷을 정의할 수 있다. 마지막으로 XML 설정파일에 <aop:aspectj-autoproxy/> 태그를 추가하고 해당 클래스를 Bean으로 등록하면 된다.

<beans ...>
	<aop:aspectj-autoproxy/>
	<bean id="loggingSomething" class="springbook.user.aspect.LoggingSomething"/>
</beans>
package springbook.user.aspect;
...

@Aspect
public class LoggingSomething {
	@Before("execution(* runSomething(..))")
	public void before (JoinPoint joinPoint) {
    	//joinPoint 객체를 통해 타겟의 정보를 알 수 있음
		System.out.println("@Before [" + joinPoint.getSignature().getName() + "] method.");
	}
}


참고문헌

스프링 입문을 위한 자바 객체지향의 원리와 이해, 김종민, 위키북스
토비의 스프링 3.1 Vol. 1 스프링의 이해와 원리, 이일민, 에이콘출판
AOP 입문자를 위한 개념 이해하기 - Tecoble
Aspect Oriented Programming with Spring - Spring Framework Docs
Aspect Oriented Programming - Catsbi's DLog
[SpringBoot] @Aspect 어노테이션 (tistory.com)
Spring AOP[3] @Aspect 어노테이션을 이용한 AOP 구현, Advice 정의하는 어노테이션 종류, Aspect 클래스 작성 및 테스트 (tistory.com)
프록시 패턴(Proxy pattern) - 꿈꾸는 지구별 개발자, Phang's IT Blog

profile
엔지니어가 되는 그 날 까지!

0개의 댓글