Dev/Spring

Spring[AOP - Aspect Oriented Programming]

OK-가자 2021. 10. 17. 14:50

Spring[AOP - Aspect Oriented Programming]

1. AOP 개요

(A)spect (O)riented (P)rogramming : 관점 지향 프로그래밍

가장 기초가 되는 개념은 관심의 분리(Separation of Concern)

자료 001

  • (A)spect (O)riented (P)rogramming : 관점 지향 프로그래밍
  • 가장 기초가 되는 개념은 관심의 분리(Separation of Concern)
    1. 핵심관심 : 시스템의 핵심 가치와 목적이 그대로 드러난 관심영역
    2. 횡단관심: 핵심관심 전반에 걸쳐 반복적으로 나오게 되는 로깅, 트랜잭션, 보안, 인증, 리소스 풀링, 에러체크 등의 관심영역
    3. 관심의 분리: 여러 핵심관심에 걸쳐 등장하는 횡단관심을 분리하여 독립적인 모듈로 만들고 핵심관심이 실행되는 동안 횡단관심을 호출하는 코드를 직접 명시하지 않고 선언적으로 처리
    4. 핵심관심 모듈의 중간중간에서 필요한 횡단관심 모듈을 직접 호출하지 않고 위빙(Weaving)이라 불리는 작업을 이용하여 횡단관심 코드가 삽입되도록 만든다.
    5. 핵심관심모듈에서는 횡단관심모듈이 무엇인지 조차 인식할 필요가 없음

2. AspectJ

  • 1990년대 후반에 Java를 확장하여 AOP를 지원하도록 만들어진 최초이며 대표적인 프레임워크.

  • 자바의 클래스 파일과 비슷한 Aspect를 작성한 후 AspectJ의 컴파일러로 컴파일을 하면 관심모듈의 사이사이에 횡단관심모듈들이 삽입되는 위빙 작업이 일어나고 이때 자바 VM에서 사용될 수 있는 클래스파일이 만들어지는 원리.

  • 2005.12월에 AspectJ 5.0 이 발표되었고 자바 5.0에서 추가된 제너릭, 어노테이션등을 활용하게 되면서 자바 정규 문법을 이용하여 작성이 가능하게 되었음. 즉 자바컴파일러로 개발 가능.

  • Spring 2.0의 경우 AspectJ 라이브러리를 활용하여 AspectJ 5와 동일하게 어노테이션 해석기능을 수행.

3. AOP의 구성 요소

1) JoinPoint(언제)

  • 횡단관심모듈은 코드의 아무 때나 삽입이 되는 건 아니다.
  • 조인포인트라 불리는 특정 시점에서만 삽입이 가능하다.
  • 예)
    • 메서드가 호출되거나 리턴 되는 시점
    • 필드를 액세스 하는 시점
    • 객체가 생성되는 시점
    • 예외가 던져지는 시점
    • 예외 핸들러가 동작하는 시점
    • 클래스가 초기화되는 시점 등이다.
  • AOP 프레임워크에 따라 제공되는 조인포인트는 다르며 스프링은 메소드 조인포인트만 제공함

2) PointCut (어디에서)

  • 어느 조인포인트에 횡단관심모듈을 삽입할지를 결정하는 기능

  • 횡단관심이 삽입될 특정 클래스의 특정 메소드를 선택하는 방법 정의

    3) Advice(or Interceptor, 무엇을 )

  • 횡단관심모듈(로깅, 보안, 트랜잭션 등)

4) Weaving(위빙)

  • 어드바이스(횡단관심)를 삽입하는 과정
  • 위빙작업이 일어나는 시간
    • 컴파일 시 == 특별한 컴파일러 필요
    • 클래스 로딩시 == 특별한 클래스로더 필요
    • 런타임시 == 프록시를 이용한 방법(스프링)

5) Aspect( Advisor )

  1. 어디에서 무엇을 언제 할 것인가?
  2. PointCut + Advice를 정의

4. Spring AOP

[실습1]

ProductService 객체의 findProduct (String) 메서드가

  • (1)시작지점
  • (2)종료지점
  • (3)예외를 보내지 않고 정상 종료 되는 지점
  • (4)메소드 시작/종료 지점
  • (5)메소드에서 예외가 발생한 지점

5가지 지점에 해당 Advice가 실행되도록 코드를 작성하는 실습

1. Dependecy 추가

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <org.springframework-version>4.0.3.RELEASE</org.springframework-version>
</properties>

<!-- spring context -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>${org.springframework-version}</version>
</dependency>

<!-- spring aspect -->
<dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aspects</artifactId>
        <version>${org.springframework-version}</version>
</dependency>

2. 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:tx="http://www.springframework.org/schema/tx" 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/tx http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config />
    <context:component-scan base-package="com.bit2015.aoptest">
        <context:include-filter type="annotation"
            expression="org.springframework.stereotype.Repository" />
        <context:include-filter type="annotation"
            expression="org.springframework.stereotype.Service" />
        <context:include-filter type="annotation"
            expression="org.springframework.stereotype.Component" />
    </context:component-scan>

    <aop:aspectj-autoproxy />

</beans>

3. ProductService.java

@Service
public class ProductService {
    public ProductVo findProduct( String name ) {
        System.out.println( "finding " + name + "..." );
        return new ProductVo( name );
    }
}

4. ProductVo.java

public class ProductVo {
   private String name;
   public ProductVo( String name ) {
      this.name = name;
   }
   public String getName() {
      return name;
   }
   public void setName(String name) {
      this.name = name;
   }

   @Override
   public String toString() {
    return "ProductVo [name=" + name + "]";
   }
}

5. App.java

public class App {
    public static void main( String[] args ){
        ApplicationContext applicationContext = 
                new ClassPathXmlApplicationContext( "config/applicationContext.xml" );

        ProductService productService = 
                            (ProductService)applicationContext.getBean( "productService" );

    productService.findProduct( "TV" );
    }
}

6. Aspect 클래스 구현

@Aspect
@Component
public class MyAspect {


}

6-1. Before Advice 구현

@Before( "execution(* *..*.findProduct(..))" )
public void before() {
   // 메소드 시작시점에 동작하는 어드바이스
   System.out.println( "call [before advice]" );
}

PointCut 의 기술 방법

  • 가장 많이 사용하는 execution 포인트 컷은 호출되는 쪽의 메서드를 조건으로 포인트 컷을 기술한다.
excution( 접근자  반환타입  패키지.클래스(인터페이스).메소드(인수) thorws 예외
  1. 접근자 생략가능
  2. thorws 예외 생략가능
  3. '*'(와일드 카드) 사용가능
  4. 패키지에서의 와일드 카드는 .. 를 사용한다.
  5. 메소드의 인수에서 .. 를 사용하면 모든 인수를 의미하게 된다.
  6. 패키지명.클래스 명은 생략할 수 있다.

6-2. After Advice 구현

@After( "execution(public * com.bit2015.aoptest..Produc*.findProduct(..))" )
public void after() {
   // 메소드 종료 시점에 호출
   System.out.println( "call [after advice]" );
}

6-3. AfterReturning Advice 구현

@AfterReturning( value="execution(* *..ProductService.*(..))", returning="vo" )
public void aferReturning( ProductVo vo ) {
    // 메소드 호출이 예외를 보내지 않고 정상 종료 했을 때 동작하는 어드바이스
    System.out.println( "call [aferReturning advice] : " + vo );
}

6-4. Around Advice 구현

@Around( "execution(* findProduct(String))" )
public ProductVo around(ProceedingJoinPoint pjp ) throws Throwable {
    // 메소드 호출 전후에 동작하는 어드바이스
    System.out.println( "call [around device] : before" );
    Object[] a = { "Camera" };
    ProductVo vo = (ProductVo)pjp.proceed(  a  );
    System.out.println( "call [around device] : after" );

    return vo;
}

6-5. AfterThrowing Advice 구현

@AfterThrowing( value="execution(* findProduct(String))", throwing="ex" )
public void afterThrowing( Throwable ex ) {
    System.out.println( "call [afterThrowing] : " + ex.toString() );
}

[실습2]

모든 DAO 메서드의 실행 시간을 log로 남기는 Aspect 작성하기

org.springframework.util.StopWatch 클래스를 사용합니다.

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

Spring[IOC Container]  (0) 2021.10.17
Spring[@Valid]  (0) 2021.10.17
Spring[Logging]  (0) 2021.10.17
Spring[MultipartResolver]  (0) 2021.10.16
Spring[Interceptor-Annotation활용]  (0) 2021.10.16