BackEnd/Spring

Spring학습_Day4_AOP

Leo.K 2022. 6. 28. 09:12

AOP(Aspect Oriented Programming)

  • 관점지향 프로그래밍
  • Aspect: 어플리케이션의 핵심 기능은 아니지만 어플리케이션을 구성하는 중요 한 요소이고, 부가적인 기능을 담당하는 요소
    • 쇼핑몰에서 물건을 구매하고, 장바구니에 담는 기능은 핵심 기능이라고 할 수 있다. 
    • 특정 사용자가 무슨 물건을 사고, 언제 구매를 했고, 낮 시간에는 어떤 연령대가 많이 접속하는지에 대한 정보는 핵심 기능이 아닌 부가적인 기능이다. 굳이 쇼핑몰이 아니더라도 다른 어플리케이션에서도 사용할 만한 정보
    • 즉, 부가적인 기능은 스프링에서 만들어줄테니 핵심적인 기능만을 집중해서 만들도록 지원해주는 것이다.
  • 어플리케이션의 핵심적인 기능에서 부가적인 기능을 분리해서 Aspect 모듈로 만들어서 설계하고 개발하는 방법
  • OOP(객체지향프로그래밍)을 돕는 보조적인 기술로 OOP 모듈화의 핵심은 클래스이지만 AOP 모듈화의 핵심은 관점
  • 용어
    • Advice: 부가 기능을 담은 모듈로 공통 로직을 담고 있는 코드
    • Joinpoint: Advice를 적용 가능한 지점
      • Spring AOP에서는 각 객체의 메소드
    • Pointcut: Joinpoint를 선별하는 기능을 정의한 모듈
    • Target: 대상 메소드를 가지는 객체
    • Proxy: Advice가 적용되었을 때 만들어지는 객체
    • Weaving: Advice를 핵심로직코드에 적용하는 것

 

[ 기존 어플리케이션 구현 방식 ]

[ AOP가  적용된 어플리케이션 구현 방식]

코어성 업무(상품, 결제)에서 부가기능을 관점으로 분리시키고 적용만 시키게 된다. 각각의 코어성 업무에 종속적으로 부가기능을 삽입하면 부가기능이 하나가 수정된 경우 모든 코어성 업무에도 수정을 해주어야 한다. 하지만 이렇게 관점으로 분리를 시키면 부가기능만 수정하고 관계만 수정해주면 되기 때문에 유지보수에 압도적으로 용이하다.

 

그렇다면 이제는 AOP를 사용하기 위한 라이브러리를 추가해줄 것이다. 아래의 사이트에 접속해서 총 3개의 라이브러리를 pom.xml에 <dependecies>태그 사이에 추가하자.

https://mvnrepository.com/

 

Maven Repository: Search/Browse/Explore

extras-cats Last Release on Jun 26, 2022

mvnrepository.com

  1. aspectj weaver
  2. aspectj runtime
  3. aspectj tools

 

pom.xml에 aop namespace추가하기 

https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop-extensibility

 

Core Technologies

In the preceding scenario, using @Autowired works well and provides the desired modularity, but determining exactly where the autowired bean definitions are declared is still somewhat ambiguous. For example, as a developer looking at ServiceConfig, how do

docs.spring.io

위의 링크로 접속을 한다. 왼쪽 목차의 인덱스 중에서 1.5.4의 Scoped Beans as Dependencies를 클릭하면 xml namespace를 찾을 수 있고 이를 복사한다.

 

Advice 동작 시점

  • Before: 메소드 실행 전 동작
  • After
    • After Returning: 메소드가 성공적으로 리턴되면 동작
    • After Throwing: 메소드 실행 중 예외가 발생하면 동작(try-catch문의 catch문의 역할)
    • After: 메소드 실행 후 동작(무조건 동작. finally와 같은 역할) 
  • Around: 메소드 실행 전후에 처리할 로직을 삽입하여 동작

PointCut 표현식

  • execution(* t_tok03.ItemTarget.*())
    • *: 리턴타입
    • t_tok03: 패키지 경로
    • ItemTarget: 클래스명. *Target, *Impl처럼 *키워드 사용 가능
    • *(): 모든 메소드.
      • get*(): get으로 시작하는 모든 메소드
      • set*(..): set으로 시작하는 모든 메소드

 

[ AOP 실습해보기 ]

간단하게 코어 기능으로 물건과, 상품을 구매하는 코어 로직을 구성해보고, 관점만 추가함으로써 발생하는 부가 로직을 살펴보자. 준비 과정으로는 xml파일을 초기화하기 위해 반드시 위의 aspectj 라이브러리를 복사해와야 한다.

[ 환경설정 ]

beans.xml

  • main에서 사용할 객체들을 프로젝트가 실행했을 때 사용할 수 있도록 환경설정으로 통해 생성한다.
  • 11번행 : logginAdvice를 id로 가지는 클래스를 관점으로 추가하겠다는 의미이다.
  • 13번행: t_tok03패키지 아래 ItemTarget클래스 아래 모든 메소드가 실행되기 전에 logginAdvice내부에 beforeAdvice메서드를 실행한다.
  • 14번행: t_tok03패키지 아래 ItemTarget클래스 아래 buyItem()메소드가 실행되기 전에만 logginAdvice내부에 beforeBuyAdvice메서드를 실행한다.
  • 16번행: t_tok03패키지 아래 Target으로 끝나는 클래스이름을 가진 모든 클래스 아래 buy로 시작하는 모든 메소드가 실행된 후에 logginAdvice내부에 afterAdvice메서드를 실행한다.
  • 17번행: t_tok03패키지 아래 Target으로 끝나는 클래스이름을 가진 모든 클래스 아래 buy로 시작하는 모든 메소드가 실행기 전 후에 logginAdvice내부에 aroundAdvice메서드를 실행한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!--핵심 로직인 ItemTarget을 bean으로 설정-->
<bean id="itemTarget" class="t_tok03.ItemTarget"> </bean>
<bean id="goodsTarget" class="t_tok03.GoodsTarget"> </bean>
 
<!--관점 로직인 LoggingAdvice를 bean으로 설정-->
<bean id="logginAdvice" class="t_tok03.LoggingAdvice"> </bean>
 
 
<!--관점을 부여한다.-->
<aop:config>
    <aop:aspect ref="logginAdvice"><!--형식 : 접근제한자 패키지명.클래스명.클래스-->
        <!--지정한 클래스가 실행할 때, method를 실행하라.-->
        <aop:before method="beforeAdvice" pointcut="execution(* t_tok03.ItemTarget.*())"/>
        <aop:before method="beforeBuyConfirm" pointcut="execution(* t_tok03.ItemTarget.buyItem())" />
        <!--모든 접근제한자에 대해서 t_tok03패키지 아래에 Target으로 끝나는 클래스 내부에 buy로 시작하는 메소드가 ""끝나면"" afterAdvice를 실행해라-->
        <!--<aop:after method="afterAdvice" pointcut="execution(* t_tok03.*Target.buy*(..))" />-->
        <aop:around method="aroundAdvice" pointcut="execution(* t_tok03.*Target.buy*(..))" />
    </aop:aspect>
</aop:config>
cs

[ 핵심 로직 ] - 상품 구매 

ItemTarget.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package t_tok03;
 
//Target : 실제 비즈니스 로직을 처리하는 클래스
public class ItemTarget {//핵심 로직
    //아이템을 조회
    public void selectItem() {
        System.out.println("아이템을 조회합니다.");
    }
    
    //아이템을 구매 
    public void buyItem() {
        System.out.println("아이템을 구매합니다.");
    }
    
    //아이템을 대량 구매 
    public void buyItems() {
        System.out.println("아이템을 대량으로 구매합니다.");
    }
    
}
 
cs

 

GoodsTarget.java

1
2
3
4
5
6
7
8
9
package t_tok03;
 
public class GoodsTarget {
    //상품 구매 
    public void buyGoods() {
        System.out.println("상품을 구매합니다.");
    }
}
 
cs

[ 부가 로직 ] - 로깅 처리를 예로 들겠다.

LoggingAdvice.java -> 부가 로직을 담은 모듈로 공통 로직을 담고 있는 코드이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package t_tok03;
 
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
 
//Advice : 부가기능을 담당하는 역할. 로깅을 담당하는 역할. 이 클래스가 관점이 된다.
public class LoggingAdvice {
    
    public void beforeAdvice() {
        System.out.println("####메소드 실행전에 로그를 출력합니다.####");
    }
    
    public void beforeBuyConfirm() {
        System.out.println("@@@@@구매 전 아이템 확인은 필수입니다.@@@@@");
    }
    
    public void afterAdvice() {
        System.out.println("$$$$$구매해주셔서 감사합니다.$$$$$");
    }
    
    //joinPoint는 around에서만 사용이 가능하다.
    public void aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
        
        //추가 로직(메소드 실행 전)
        Signature method = joinPoint.getSignature(); //aspectj에 있는 인터페이스 Signature
        System.out.println("&&&&& 내가 실행한 메소드 :::" + method.getName() +"&&&&&");
        
        //메소드 실행
        joinPoint.proceed();
        
        //추가 로직(메소드 실행 후)
        if("buyGoods".equals(method.getName())) {
            System.out.println("Goods를 구매해주셔서 감사합니다.");
        } else {
            System.out.println("Item을 구매해주셔서 감사합니다.");
        }
    }
}
 
cs

 

[ 출력 ]

Main.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package t_tok03;
 
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
 
public class Main {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        ItemTarget itemTarget = (ItemTarget) context.getBean("itemTarget");
        //관점을 추가했더니 코어기능을 수행할 때, Main에서 작성하지 않은 부가기능이 출력이 된다.
        itemTarget.selectItem();
        System.out.println();
        
        itemTarget.buyItem();
        System.out.println();
        
        itemTarget.buyItems();
        System.out.println();
        
        GoodsTarget goodsTarget = (GoodsTarget) context.getBean("goodsTarget");
        goodsTarget.buyGoods();
    }
}
 
cs

 

'BackEnd > Spring' 카테고리의 다른 글

SpringMVC_Parameter_국비_Day83  (0) 2022.06.29
SpringCollection_국비Day82  (0) 2022.06.28
Spring_국비Day81  (0) 2022.06.27
Spring 학습_Day3_IoC_DI  (0) 2022.06.20
Spring 학습 Day2_개발환경 구축  (0) 2022.06.18