[Spring] Spring AOP

Wonkyun Jung·2023년 4월 18일
1

스프링

목록 보기
2/6
post-thumbnail

관점 지향 프로그래밍(AOP)

AOP(Aspect Oriented Programming) 개요

  • 핵심 관심 사항과 공통 관심 사항
    • 핵심 관심 사항(core concern)과 공통 관심 사항(cross-cutting concern)

    • 기존 OOP에서는 공통관심사항을 여러 모듈에서 적용하는데 있어 중복된 코드를 양상하는 한계가 존재해서 이런 한계를 극복하기 위해 AOP가 등장

    • AOP는 문제를 해결하기 위한 핵심 관심 사항과 전체에 적용되는 공통 관심 사항을 기준으로 프로그래밍함으로써 공통 모듈을 손쉽게 적용할 수 있게 함

      • AOP는 application에서의 관심사의 분리(기능의 분리) 즉, 핵심적인 기능에서 부가적인 기능을 분리한다

      • 분리한 부가기능을 Aspect라는 독특한 모듈 형태로 만들어서 설계하고 개발하는 방법

      • OOP를 적용하여도 핵심기능에서 부가기능을 쉽게 분리된 모듈로 작성하기 어려운 문제점을 AOP가 해결

      • AOP는 부가기능을 Aspect로 정의하여, 핵심기능에서 부가기능을 분리함으로써 핵심기능을 설계하고 연구할 때 객체지향적인 가치를 지킬 수 있도록 도와주는 개념


AOP 적용 예

  • 간단한 메소드의 성능 검사
    • 개발 도중 특히 DB에 대량의 데이터를 넣고 뺴는 등의 배치 작업에 대하여 시간을 측정해 보고 쿼리를 개선하는 작업은 매우 의미가 있다. 이때 해당 작업을 하는 코드를 밖에서 설정하고 해당 부부분을 사용하는 것이 편리하다

  • 트랜잭션 처리
    • 트랜잭션의 경우 비즈니스 로직의 전후에 설정된다

    • 하지만 매번 사용하는 트랜잭션(try-catch)의 코드는 번거롭고, 소스를 더욱 복잡하게 보여준다

  • 예외 반환
    • 스프링에는 DataAccessException이라는 매우 잘 정의되어 있는 예외 계층 구조가 있다

    • 예전 하이버네이트 예외들은 몇 개 없었고 그나마도 UncatchedException이 아니었다

    • 이렇게 구조가 별로 안 좋은 예외들이 발생했을 때, 그걸 잡아서 잘 정의되어있는 예외 계층 구조로 변환해서 다시 던지는 Aspect는 제 3의 프레임워크를 사용할 때, 본인의 프레임워크나 애플리케이션에서 별도의 예외 계층 구조로 변환하고 싶을 때 유용하다.

  • 아키텍쳐 검증

  • 기타
    • 하이버네이트와 JDBC를 같이 사용할 경우, DB 동기화 문제 해결

    • 멀티쓰레드 Safety 관련하여 작업해야 하는 경우, 메소드들에 일괄적으로 락을 설정하는 Aspect

    • 데드락등으로 인한 PessimisticLockingFailureException등의 예외를 만났을 때 재시도하는 Aspect

    • 로깅, 인증, 권한 등


AOP 구조

  • 핵심 관심 사항: BankingService, AccountService, CustomerService

  • 공통 관심 사항: Security, Transaction, Other



Spring AOP

Spring AOP 용어


  • Target: 핵심기능을 담고 있는 모듈로 target은 부가기능을 부여할 대상이 됨

  • Advice: 어느 시점에 어떤 공통 관심 기능(Aspect)을 적용할지 정의한 것. Target에 제공할 부가기능을 담고 있는 모듈


  • JoinPoint
    • Aspect가 적용 될 수 있는 지점 (method, field)
    • 즉 target 객체가 구현한 인터페이스의 모든 method는 JoinPoint가 됨

  • Pointcut
    • 공통 관심 사항이 적용될 JoinPoint
    • Advice를 적용할 target 의 method를 선별하는 정규 표현식
    • Pointcut 표현식은 execution으로 시작하고 method의 signature를 비교하는 방법을 주로 이용

  • Aspect
    • 여러 객체에서 공통으로 적용되는 공통 관심 사항
    • AOP의 기본 모듈
    • Aspect = Advice + Pointcut
    • Aspect는 Singleton 형태의 객체로 존재

  • Advisor
    • Advisor = Advice + pointcut
    • Advisor는 Spring AOP에서만 사용되는 특별한 용어

  • Weaving
    • 어떤 Advice를 어떤 Pointcut에 적용시킬 것인가에 대한 설정

    • 즉 Pointcut에 의해서 결정된 타겟의 JoinPoint에 부가기능(Advice)을 삽입하는 과정을 뜻함

    • Weaving은 AOP의 핵심기능(Target)의 코드에 영향을 주지 않으면서 필요한 부가기능(Advice)을 추가할 수 있도록 해주는 핵심적인 처리과정

Pointcut 표현식


AOP 비교


Spring AOP 특징


  • Spring은 프록시(proxy)기반 AOP를 지원

    • Spring은 Target 객체에 대한 Proxy를 만들어 제공

    • Target을 감싸는 Proxy는 실행시간(Runtime)에 생성

    • Proxy는 Advice를 Target 객체에 적용하면서 생성되는 객체


  • 프록시(Proxy)가 호출을 가로챈다(Intercept)

    • Proxy는 Target 객체에 대한 호출을 가로챈 다음 Advice의 부가기능 로직을 수행하고 난 후에 Target의 핵심 기능 로직을 호출한다(전처리 Advice)

    • 또는 Target의 핵심 기능 로직 method를 호출한 후에 부가기능(Advice)을 수행하는 경우도 있다 (후처리 Advice)


  • Spring AOP는 method JoinPoint만 지원

    • Spring은 동적 Proxy를 기반으로 AOP를 구현하므로 method JoinPoint만 지원한다

    • 즉, 핵심기능으; method가 호출되는 런타임 시점에만 부가기능을 적용할 수 있다

    • 반면 AspectJ 같은 고급 AOP framework를 사용하면 객체의 생성, 필드값의 조회와 조작, static method 호출 및 초기화 등의 다양한 작업에 부가기능을 적용할 수 있다.


Spring AOP 구현


Spring AOP 구현방법

  • POJO class를 이용한 AOP 구현

  • Spring API를 이용한 AOP 구현

  • Annotation을 이용한 AOP 구현


POJO기반 AOP 구현방법

  • XML Schema 확장 기법을 통해 설정 파일을 작성

  • POJO기반 Advice Class 작성


POJO 기반 AOP 구현 - Advice 작성

  • POJO 기반 Aspect Class 작성
    • 설정 파일의 advice 관련 태그에 맞게 작성한다

    • <bean>으로 등록하며 <aop:aspect>의 ref 속성으로 참조한다

    • 공통 기능 메소드: advice 관련 태그들의 method 속성의 값이 method의 이름이 된다

POJO 기반 AOP 구현 - Advice 작성

  • pointcut: 직접 pointcut을 설정. 호출 할 method의 패전 지정

  • pointcut-ref: <aop:aspect> 태그의 id명을 넣어 pointcut 지정

  • method: Aspect Bean에서 호출할 method명 지정


POJO 기반 AOP 구현 - Advice Class

  • POJO 기반의 Class로 작성
    • class명이나 method명에 대한 제한은 없다

    • method 구문은 호출되는 시점에 따라 달라질 수 있다

    • method 이름은 advice 태그 <aop:before>에서 method 속성의 값이 method 명이 된다

POJO 기반 AOP 구현 - Advice 종류(1/5)

  • Before Advice
    • 대상 객체의 메소드가 실행되기 전에 실행됨
    • return type: 리턴 값을 갖더라도 실제 Advice의 적용과정에서 사용되지 않기 때문에 void를 쓴다
    • argument: 없거나 대상객체 및 호출되는 메소드에 대한 정보 또는 파라미터에 대한 정보가 필요하다면 JoinPoint 객체를 전달


  • Before Advice 실행 순서

  1. 빈 객체를 사용하는 코드에서 스프링이 생성한 AOP 프록시의 메소드를 호출

  2. AOP 프록시는 <aop:before>에서 지정한 메소드를 호출

  3. AOP 프록시는 Aspect 기능 실행 후 실제 빈 객체의 메소드를 호출


POJO 기반 AOP 구현 - Advice 종류(2/5)

  • After Returning Advice
    • 대상 객체의 method 실행이 정상적으로 끝난 뒤 실행 됨

    • return type: void

    • argument:

      • 없거나 org.aspectj.lang.JoinPoint 객체를 받는다. JoinPoint는 항상 첫 argument로 사용

      • 대상 method에서 반환되는 특정 객체 타입의 값을 argument로 받을 수 있다


  • After Returning Advice 실행 순서

  1. 빈 객체를 사용하는 코드에서 스프링이 생성한 AOP 프록시의 메소드를 호출

  2. AOP 프록시는 실제 빈 객체의 메소드를 호출 (정상 실행)

  3. AOP 프록시는 .<aop:after-returning>에서 지정한 메소드를 호출


POJO 기반 AOP 구현 - Advice 종류(3/5)

  • After Throwing Advice
    • 대상 객체의 method 실행 중 예외가 발생한 경우 실행 됨

    • return type: void

    • argument:

      • 없거나 JoinPoint 객체를 받는다. JoinPoint는 항상 첫 argument로 사용


      • 대상 method에서 전달되는 예외 객체를 argument로 받을 수 있다


  • After Throwing Advice 실행순서

  1. 빈 객체를 사용하는 코드에서 스프링이 생성한 AOP 프록시 메소드를 호출

  2. AOP 프록시는 실제 빈 객체의 메소드를 호출(exception 발생)

  3. AOP 프록시는 .<aop:after-throwing>에서 지정한 메소드를 호출


POJO 기반 AOP 구현 - Advice 종류(4/5)

  • After Advice
    • 대상 객체의 method가 정상적으로 실행 되었는지 아니면 exception을 발생시켰는지의 여부와 상관없이 메소드 실행 종료 후 공통 기능 적용

    • return type: void

    • argument

      • 없거나 JoinPoint 객체를 받는다. JoinPoint는 항상 첫 argument로 사용


  • After Advice 실행 순서
  1. 빈 객체를 사용하는 코드에서 스프링이 생성한 AOP 프록시 메소드를 호출

  2. AOP 프록시는 실제 빈 객체의 메소드를 호출(정상 실행, exception 발생: java의 finally와 같음)

  3. AOP 프록시는 .<aop:after>에서 지정한 메소드를 호출


POJO 기반 AOP 구현 - Advice 종류(5/5)

  • Around Advice
    • 위의 네 가지 Advice를 다 구현 할 수 있는 Advice

    • return type: Object

    • argument:

      • org.aspectj.lang.ProceedingJoinPoint를 반드시 첫 argument로 지정


  • Around Advice 실행 순서

  1. 빈 객체를 사용하는 코드에서 스프링이 생성한 AOP 프록시 메소드를 호출

  2. AOP 프록시는 <aop:around>에서 지정한 메소드를 호출

  3. AOP 프록시는 실제 빈 객체의 메소드를 호출

  4. AOP 프록시는 <aop:around>에서 지정한 메소드를 호출


JoinPoint Class 구성요소

  • 대상 객체에 대한 정보를 가지고 있는 객체로 Spring Container로 부터 받는다

  • org.aspectj.lang 패키지에 있다

  • 반드시 Aspect method의 첫 argument로 와야한다

  • 주요 method


@aspect Annotation을 이용한 AOP 구현



AOP 구현 프로젝트

applicationContext.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-4.0.xsd
		http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-4.3.xsd
		http://www.springframework.org/schema/aop 
        http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
	
  		<!--annotation 을 이용 직접 Aspect Class에 Advice 및 Pointcut 설정-->
		<aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>
  		<!--Aspect를 사용하기 위한 객체 -->
		<bean class="UserAspect" id="userAspect"></bean>
  
  		<!-- Main에서 사용할 객체들 생성 -->
		<bean class="GeneralUser" id="generalUser"></bean>
		<bean class="AdminUser" id="adminUser"></bean>
		
  
  		<!--annotation을 안 쓴다면 이렇게 구현해줘야 한다 -->
		<!-- <aop:config>
			<aop:pointcut expression="execution(* *())" id="mypt"/>
			<aop:aspect ref="userAspect">
				<aop:before method="before" pointcut-ref="mypt"/>
				<aop:after-returning method="afterReturn" pointcut-ref="mypt"/>
				<aop:after-throwing method="afterThrow" pointcut-ref="mypt"/>
				<aop:after method="after" pointcut-ref="mypt"/>
			</aop:aspect>
		</aop:config> -->
  
</beans>


AopTest.java

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


public class AopTest {

	public static void main(String[] args) {
		ApplicationContext factory = new ClassPathXmlApplicationContext("applicationContext.xml");
		GeneralUser guser = factory.getBean("generalUser", GeneralUser.class);
		AdminUser auser = factory.getBean("adminUser", AdminUser.class);
	
		//테스트 코드 작성
		System.out.println("***********1. GeneralUser");
		guser.useApp();
		
		System.out.println("***********2. AdminUser");
		try {
			auser.useApp();
		} catch(Exception e) {
			
		}
		
		((ClassPathXmlApplicationContext)factory).close();
	}

}


UserAspect.java

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

//@Aspect 어노테이션을 적용한 클래스는 PointCut과 Advice 설정 그리고 Aspect 구현을 함께 제공
@Aspect
public class UserAspect {
	
    //<aop:aspectj-autoproxy proxy-target-class="true"> tag ->직접 구현 
	//* *() -> 모든 반환형 & 모든 함수 이름에 대해서 실행한다
    @Before("execution(* *())")
	 public void before() {
	 	System.out.println("애플리케이션을 시작합니다.");
	 }
	
    //pointcut (공통 관심 사항이 적용될 JoinPoint)
	@AfterReturning(pointcut="execution(* *())", returning="ret")
	 public void afterReturn() {
		System.out.println("애플리케이션의 사용을 끝냅니다.");
	 }
	
    //pointcut (공통 관심 사항이 적용될 JoinPoint)
	@AfterThrowing(pointcut="execution(* *())", throwing="e")
	 public void afterThrow() throws Throwable {
		System.out.println("애플리케이션에 문제가 생겨 점검합니다.");
	 }
	
	@After("execution(* *())")
	 public void after() {
	 	System.out.println("애플리케이션을 상태와 관련없이 종료합니다.");
	 }
}

User.java

public interface User {
	void useApp() throws ApplicationException;
}

GeneralUser.java

public class GeneralUser implements User{
	@Override
	public void useApp() {
		System.out.println("애플리케이션을 사용합니다.");
	}
}


AdminUser.java

import java.util.Random;

public class AdminUser implements User {

	@Override
	public void useApp() throws ApplicationException {
		System.out.println("애플리케이션을 관리합니다.");
		
        // 예외 발생 코드 
		Random random = new Random();
		 if(random.nextInt(2) == 1) {
		 	throw new ApplicationException();
		 }
	}
}


ApplicationException.java 

public class ApplicationException extends Exception {
	public ApplicationException() {
		this.printStackTrace();
	}
}

0개의 댓글