[Spring] - AOP : 관점 지향 프로그래밍

NoowaH·2021년 10월 4일
0
post-thumbnail

👨‍💻 AOP : Aspect Oriented Programming


  • 관점지향 프로그래밍
  • 여러 오브젝트에 나타나는 공통적인 부가 기능을 모듈화 해서 재사용하는 기법
  • 문제를 바라보는 관점을 기준으로 프로그래밍하는 기법
  • 횡단 관심 (공통 로직) 사항의 분리 : Separation of Cross-Cutting Concern
  • 로깅, 보안, transaction 처럼 비즈니스 로직 biz의 여러 부분의 공통적으로 사용되는 부가적인 횡단 관심사항들을 분리해서 Aspect라는 별도의 모듈로 설계


🙄🙄 Aspect

  • 여러 객체에 공통으로 적용되는 공통 관심사항
  • 여러개의 Advice와 여러개의 Pointcut의 결합체 의미


AOP 용어


Target : AOP 적용 대상 biz 로직

Advice : AOP 기능 type & 실행 시점

  • before : biz 실행 전
  • after : biz 실행 후
  • after-throwing : biz 예외처리 AOP
  • after-returning : biz 리턴값 반환 후

Join Point : 적용할 위치

  • pointcut : advice가 적용될 지점
  • execution() , within() 함수로 지점 호출




설정:


pom.xml 에 필요햔 dependencies

  	<dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.20</version>
    </dependency>
    
    <!-- 
	AOP 적용시에 필요한 추가 library
	- byte code를 실시간 동적으로 자동 생성해주는 기능의 library
	-->
	<dependency>
		<groupId>cglib</groupId>
		<artifactId>cglib</artifactId>
		<version>2.2.2</version>
	</dependency>
	
	<!-- 
	AOP 기능의 framework
	- spring이 AOP 기술을  활용해서 spring 스럽게 활용
	-->
	<dependency>
		<groupId>org.aspectj</groupId>
		<artifactId>aspectjweaver</artifactId>
		<version>1.7.3</version>
	</dependency>
   


AOP : XML 기반 설정


NoticeAspect.java : common / AOP 부가기능 로직

package common;

import org.aspectj.lang.ProceedingJoinPoint;

public class NoticeAspect {
    
	// buy*(..) 메소드 실행전 실행
	public static void beforeNotice() {
		System.out.println("common 1 - 구매를 하실건가요?" );
	}
	
	// buy*(..) 메소드 실행후 실행
	public static void afterNotice() {
		System.out.println("common 2 - 구매 완료 하셨습니다" );
	}
	
	// 예외 발생시 공통 : 예외가 발생되면 자동 실행되는 메소드
	// 발생된 예외를 parameter로 받아서 메세지값 출력
	// 현 ㅣ sell ~(int)에만 적용 
	public void noticeException(Exception e) {
		System.out.println(e.getMessage());
	}
	
	// biz 메소드가 리턴시 적용할 공통 로직이라 간주
	public void noticeReturnValue(Object v) {
		System.out.println("biz가 리턴한 데이터 - " + v);
	}

    //	import org.aspectj.lang.ProceedingJoinPoint;
	public Object aroundCommon(ProceedingJoinPoint point) {
		
		System.out.println("around common 1 - 구매를 하실건가요?" );
		
		Object v = null;
		try {
			v = point.proceed(); // 실제 호출된 biz 메소드 실행
		} catch (Throwable e) {
			System.out.println(e.getMessage()); // throwing 처리 구간
		} 
		
		System.out.println("around common 2 - 구매 완료 하셨습니다" );
		return v; // 핵심 로직 실행 결과값 반환
	}
}

biz 로직

package step02.aop.biz;

public class Car {
	
	public String buy() {
		System.out.println("자동차 구매 buy()");
		return "return test를 위한 buy";
	}

	public void sell(int money) throws Exception {
		if(money < 10000) {
			throw new Exception("너무 저렴하니 판매 불가");
		}else {
			System.out.println("정상 판매");
		}
	}
	
	public String playdata() {
		return "encore playdata";
	}
}

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/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">

	<aop:aspectj-autoproxy />
	
	<!-- biz spring bean -->
	<bean id="car2" class="step02.aop.biz.Car"/>
	
	<!-- common spring bean -->
	<bean id="common" class="common.NoticeAspect"/>
	
	<aop:config>
		<!-- biz 로직의 적용 메소드 지정 -->
		<aop:pointcut id="bizLogic" 
                      expression="execution(* step02.aop.biz.Car.buy*(..))"/>
        
        <aop:aspect ref="common">
			<!-- Advice 작성 구간 -->
        </aop:aspect>
        
	</aop:config>

</beans>

🔵 <aop:aspectj-autoproxy /> : AOP 사용을 위한 필수 설정

🔵 biz 와 common 을 bean binding 필수

🔵 <aop:config></aop:config> : AOP 설정 구간

🔵 <aop:aspect ref="common"></aop:aspect> : Advice 작성 구간

  • ref : AOP bean 호출

🔹 <aop:pointcut id="bizLogic" expression="..."/>

  • pointcut의 expression, 즉 advice가 적용될 지점을 id로 참조할 수 있게 생성


🔹expression 작성 방법


  • execution() :

    "execution(* step02.aop.biz.Car.buy*(..))"

  • Car.java에서 buy~() 라는 이름을 가짐 함수 모두에 적용

  • (..) : 모든 종류의 parameter일 때 사용되는 표현 (int)로 작성시 int 데이터타입의 parameter를 1개 받는 함수에만 적용

  • within()

    "within(step02.aop.biz.Car)"

  • 선택된 biz 객체에 대해 모두 적용

  • "within(step02.aop.biz.*)" 과 같은 방식으로 1개 이상에 bean 객체 선택 가능



📌 execution & within() 차이점 :


  • within()의 최대 조회 깊이는 .java
  • biz 클래스 내부 메소드에 따라 구분이 불가능
  • 구체적인 구분이 필요하면 execution() 사용!


XML : before & after


<aop:config>
    <aop:pointcut id="bizLogic" 
                  expression="execution(* step02.aop.biz.Car.buy*(..))"/>

    <aop:aspect ref="common">
        <!-- 호출된 biz 로직 실행전용 aop -->
        <aop:before method="beforeNotice" pointcut-ref="bizLogic"/>

        <!-- 호출된 biz 로직 실행 후용 aop -->
        <aop:after method="afterNotice" pointcut-ref="bizLogic"/>

    </aop:aspect>
</aop:config>

🔵 <aop:before method="beforeNotice" pointcut-ref="bizLogic"/>

  • bizLogic pointcut 기준에 맞는 함수 실행 전에 beforeNotice() 함수호출

🔵 <aop:after method="afterNotice" pointcut-ref="bizLogic"/>

  • bizLogic : pointcut 기준에 맞는 함수 실행 전에 afterNotice() 함수호출


XML : after-throwing


<aop:config>
    <aop:pointcut id="bizLogic" 
                  expression="execution(* step02.aop.biz.Car.buy*(..))"/>

    <aop:aspect ref="common">
        <aop:after-throwing method="noticeException" 
                            throwing="e" 
                            pointcut="execution(* step02.aop.biz.Car.sell*(int))"/>

    </aop:aspect>
</aop:config>

biz logic:

	// biz 내부 throws test method
	public void sell(int money) throws Exception {
		if(money < 10000) {
			throw new Exception("너무 저렴하니 판매 불가");
		}else {
			System.out.println("정상 판매");
		}
	}

AOP logic:

	public void noticeException(Exception e) {
		System.out.println(e.getMessage());
	}

🔵🚩 throwing

  • biz로직이 실행될 때 try/catch문에서 던지는 Exception을 AOP 로직이 받는다
  • AOP 로직에서 받는 Exception 변수명과 동일하게 작성
  • pointcut : 개별로 지정하고 싶을 때 바로 tag 안에 작성
  • pointcut-ref : 기존 <aop:pointcut/> 을 참조하고 싶을 떄 id 기준으로 binding


XML : after-returning


👩‍💻 biz 로직이 return 값을 반환할 때

<aop:config>
    <aop:pointcut id="bizLogic" 
                  expression="execution(* step02.aop.biz.Car.buy*(..))"/>

    <aop:aspect ref="common">
		<aop:after-returning method="noticeReturnValue" 
                             pointcut-ref="bizLogic" 
                             returning="v"/>

    </aop:aspect>
</aop:config>

biz logic:

	public String playdata() {
		return "encore playdata";
	}

AOP logic:

	public void noticeReturnValue(Object v) {
		System.out.println("biz가 리턴한 데이터 - " + v);
	}

🔵🚩 returning

  • biz로직이 return 하는 값을 AOP 로직이 parameter로 받음
  • AOP 로직에서 받는 parameter변수명과 동일하게 작성
  • ❕ parameter의 Data-type은 Object로, 유동적인 반환을 가능하게 설계 권장(? 아마도)


XML : Around


👀 before,after,after-throwing,after-returning 하나의 tag로 처리가 가능

<aop:config>
    <aop:pointcut id="bizLogic" 
                  expression="execution(* step02.aop.biz.Car.buy*(..))"/>

	<aop:around method="aroundCommon" 
                pointcut="within(step02.aop.biz.*)"/>

    </aop:aspect>
</aop:config>

🔵 아래 코드는 위에 하나씩 실행 했던 모든 advice를 하나의 메소드로 처리한다

	public Object aroundCommon(ProceedingJoinPoint point) {
		
		System.out.println("around common 1 - 구매를 하실건가요?" );
		
		Object v = null;
		try {
			v = point.proceed(); // 실제 호출된 biz 메소드 실행
		} catch (Throwable e) {
			System.out.println(e.getMessage()); // throwing 처리 구간
		} 
		
		System.out.println("around common 2 - 구매 완료 하셨습니다" );
		return v; // 핵심 로직 실행 결과값 반환
	}

ProceedingJoinPoint

  • Around Advice에서 사용할 공통 기능 메서드는 라미터로 전달 받은 ProceedingJoinPointproceed() 메소드로 실행



AOP : Annotation 기반 설정


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">
	
	<aop:aspectj-autoproxy/>
	
	<!-- annotation 사용하겠다는 설정 -->
	<context:annotation-config />
	
	<context:component-scan base-package="common"/>
	<context:component-scan base-package="step02.aop.biz"/>

</beans>

AOP:

package common;

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;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class NoticeAspect {
	
	@Before("execution(* step02.aop.biz.Car.buy*(..))")
	public static void beforeNotice() {
		System.out.println("common 1 - 구매를 하실건가요?" );
	}
	
	@After("execution(* step02.aop.biz.Car.buy*(..))")
	public static void afterNotice() {
		System.out.println("common 2 - 구매 완료 하셨습니다" );
	}
	
	@AfterThrowing(pointcut="execution(* step02.aop.biz.Car.sell*(int))", 
                   throwing="e")
	public void noticeException(Exception e) {
		System.out.println(e.getMessage());
	}
	
	@AfterReturning(pointcut = "execution(* step02.aop.biz.Car.buy*(..))", 
                    returning = "v")
	public void noticeReturnValue(Object v) {
		System.out.println("biz가 리턴한 데이터 - " + v);
	}
	
	@Around("execution(* step02.aop.biz.Car.sell*(int))")
	public Object aroundCommon(ProceedingJoinPoint point) {
		
		System.out.println("around common 1 - 구매를 하실건가요?" );
		
		Object v = null;
		try {
			v = point.proceed(); // 실제 호출된 biz 메소드 실행
		} catch (Throwable e) {
			System.out.println(e.getMessage()); // throwing 처리 구간
		} 
		
		System.out.println("around common 2 - 구매 완료 하셨습니다" );
		return v; // 핵심 로직 실행 결과값 반환
	}
}
profile
조하운

0개의 댓글