[Spring] AOP

2024. 7. 6. 22:55BE/Spring

1. AOP

 

  • Aspect Oriented Programming (관점 지향 프로그래밍)

 

기존 OOP에서는 핵심 로직을 여러 모듈에서 적용하는 데 있어 중복되는 코드가 발생한다.

 

  • 핵심 관심(core concern)
    : 핵심 비즈니스 로직
  • 공통 관심 사항(cross-cutting concern)
    : 핵심 로직을 수행하기 위해서 필요한 부가적인 로직
    : 로깅, DB 연결, 파일 입출력, 보안 등

 

핵심 관심(core concern)과 공통 관심 사항(cross-cutting concern)으로 나눠보고 그 관점을 기준으로 각각 모듈화 하겠다.

 

이때 여러곳에서 쓰이는 공통 관심 사항을 모듈화한 것을 Aspect라고 한다.

 

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

 

 


가. 주요 키워드

 

 

  • Aspect
    : 여러곳에서 사용되는 공통부분 코드를 모듈화한 것.
    : Singleton으로 구현.
  • Target
    : 핵심기능을 담고 있는 모듈.
    : 부가기능을 부여할 대상. (Class, method)
  • Advice
    : 어느 시점에 어떤 Aspect를 적용할지 정의한 것.
    : Target에 제공할 부가기능을 담고 있는 모듈.
  • Join point
    : Aspect가 적용될 수 있는 지점. (method, field)
    : target 객체가 구현한 인터페이스의 모든 method는 Join point가 된다.
    : Spring AOP는 method Join point만 지원한다.
  • Point cut
    : Join point의 상세 스펙을 정의한 것.
    : Advice를 적용할 target의 Join point를 선별하는 정규 표현식.
  • Advisor
    : Advisor = Advice + Pointcut
    : Spring AOP에서만 사용하는 용어.
  • Weaving
    : Pointcut에 의해서 결정된 target의 Join point에 advice를 삽입하는 과정.
    : AOP의 핵심 기능인 taget의 코드에 영향을 주지 않으면서 필요한 부가기능(advice)를 추가할 수 있도록 하는 핵심적인 처리과정.

 

주요 Pointcut 표현식 선택된 Joinpoint
execution(public * *(..)) public 메서드
execution(* set*(..)) 이름이 set으로 시작하는 모든 메서드
execution(* com.test.service.AccountService.*(..)) AccountService 인터페이스의 모든 메서드
execution(* com.test.service..(..)) service 패키지의 모든 메서드
execution(* com.test.service...(..)) service 패키지와 하위 패키지의 모든 메서드

 


2. Spring AOP

 

Spring은 Proxy 기반의 AOP를 지원한다.

 

Proxy는 클라이언트 요청과 핵심 관심(core concern) 사이에 위치한다.

 

이 과정에서 공통 관심 사항(cross-cutting concern)(aspect)을 수행한다.

 

Proxy pattern 뿐 아니라 target에 부가적인 기능을 부여한다는 면에서 decorator pattern의 측면도 가진다.

 

 

Proxy가 target에 대한 호출을 가로챈 다음 target의 method(Pointcut) 전후(advice)로 부가 기능(aspect)을 수행한다. (Weaving)

 

Spring은 동적 Proxy 기반으로 AOP를 구현한다.

 

Spring AOP는 method JoinPoint만 지원하고 target의 method가 호출되는 런타임 시점에 Weaving한다.

 

컴파일 시점에 Weaving하는 것이 아니기 때문에 매번 Weaving해야하고 이에 대한 비용이 발생한다.

 

Spring AOP를 구현하는 3가지 방법에 대하여 알아보자.

 

<!-- pom.xml -->
<dependencies>
    ...
    <!-- AspectJ :: AOP -->
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjrt</artifactId>
        <version>${org.aspectj-version}</version>
    </dependency>
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>${org.aspectj-version}</version>
    </dependency>
    ...
</dependencies>

실습에 앞서 aspectj를 추가해 두자.

 


3. POJO Class를 이용한 AOP 구현

 

가. xml 설정 파일

<!-- applicationContext.xml 또는 root-context.xml -->
...
<!-- AOP 설정 -->
<bean id="ptAdvice" class="com.company.aop.step01.around.PerformanceTraceAdvice" />
<bean id="boardContentEncodeAdvice" class="com.company.aop.step02.before.BoardContentEncodeAdvice"></bean>
<bean id="boardContentDecodeAdvice" class="com.company.aop.step03.after.returning.BoardContentDecodeAdvice"></bean>
<bean id="exceptionAdvice" class="com.company.aop.step04.after.throwing.ExceptionAdvice"></bean>
<bean id="countAdvice" class="com.company.aop.step05.after.CountAdvice"></bean>

<!-- com.company.board package안에 있는 Dao로 끝나는 모든 클래스의 모든 메소드에 적용 -->
<aop:config>
    <aop:pointcut id="publicMethod" expression="execution(public * com.company.board..*Dao.*(..))"/>
    <aop:aspect id="traceAspect" ref="ptAdvice">
        <aop:around method="trace" pointcut-ref="publicMethod" />
    </aop:aspect>
    <aop:aspect id="beforeAspect" ref="boardContentEncodeAdvice">
        <aop:before method="encode" pointcut-ref="publicMethod"/>
    </aop:aspect>
    <aop:aspect id="afterAspect" ref="boardContentDecodeAdvice">
        <aop:after-returning method="decode" pointcut-ref="publicMethod" returning="returnObj"/>
    </aop:aspect>
    <aop:aspect id="exceptionAspect" ref="exceptionAdvice">
        <aop:after-throwing method="exceptionAfter" pointcut-ref="publicMethod" throwing="ex"/>
    </aop:aspect>
    <aop:aspect id="afterAspect" ref="countAdvice">
        <aop:after method="after" pointcut-ref="publicMethod"/>
    </aop:aspect>
</aop:config>

 

 

 

around를 많이 사용한다.

 

around는 조건에 따라 target 메서드를 실행하지 않게 만들 수 있다.

 


나. POJO 기반 Advice Class 작성

 

  • POJO : Plain Old Java Object. 특정 기술에 종속되지 않는 순수 자바 객체.
advice arg return type
before 없거나 JoinPoint 객체 void
after-returning 없거나 JoinPoint 객체, target method에서 반환값. void
after-throwing 없거나 JoinPoint 객체, target method에서 반환값. void
after 없거나 JoinPoint 객체 void
around JoinPoint 객체 Object (target method의 실행 결과)

 

after-returningafter-throwing의 argument에서 주의할 점은 JoinPoint가 항상 target의 반환값 또는 예외 객체보다 앞에 위치한다는 점이다.

 

매개변수의 순서를 지키자.

 

// <aop:before method="encode" pointcut-ref="publicMethod"/>
public class BoardContentEncodeAdvice {
    public void encode(JoinPoint joinPoint) {
        String name = joinPoint.getSignature().toShortString();
        System.out.println("BoardCheckAdvice : " + name);
        Object[] args = joinPoint.getArgs();
        for(Object obj : args) { 
            if(obj instanceof BoardDto) {
                System.out.println("내용을 암호화하자!!!!");
                BoardDto boardDto = (BoardDto) obj;
                String content = boardDto.getContent();
                boardDto.setContent("암호화한 내용 ::: " + content);
            }
        }
    }    
}
// <aop:after-returning method="decode" pointcut-ref="publicMethod" returning="returnObj"/>
public class BoardContentDecodeAdvice {
    public void decode(JoinPoint joinPoint, Object returnObj) throws Throwable{
        System.out.println("HistoryAdvice : " + returnObj);
        System.out.println("목록의 내용을 복호화하자!!!");
        if(returnObj instanceof List) {
            List<BoardDto> list = (List)returnObj;
            for(BoardDto boardDto : list) {
                boardDto.setContent(boardDto.getContent().replace("암호화", "복호화"));
            }
        }
    }
}
// <aop:after-throwing method="exceptionAfter" pointcut-ref="publicMethod" throwing="ex"/>
public class ExceptionAdvice {
    public void exceptionAfter(Exception ex){
        System.out.println("ExceptionAdvice :: " + ex.getMessage());
        ex.printStackTrace();
    }
}
// <aop:after method="after" pointcut-ref="publicMethod"/>
public class CountAdvice {
    public void after(JoinPoint joinPoint) {
        String name = joinPoint.toShortString();
        System.out.println("CountAdvice : " + name);
    }
}
// <aop:around method="trace" pointcut-ref="publicMethod" />
public class PerformanceTraceAdvice {
    public Object trace(ProceedingJoinPoint joinPoint) throws Throwable {
        String signature = joinPoint.getSignature().toShortString();
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        Object result = joinPoint.proceed(); // target 실행
        stopWatch.stop();
        System.out.println("PerformanceTraceAdvice : " + signature + " 실행 시간 - " + stopWatch.getTotalTimeMillis());
        return result;
    }
}

JoinPoint joinPoint는 대상 객체 및 호출되는 메서드에 대한 정보 또는 파라미터에 대한 정보 포함.

 

around의 Advice class는 ProceedingJoinPoint을 가질 수 있음.

 

ProceedingJoinPoint.proceed()를 통해 target을 실행한다.

 

JoinPoint Method  설명
Object getTarget() target의 객체를 반환
Object[] getArgs() 파라미터 값을 배열로 반환.
Signature getSignature 호출되는 method 정보를 가진 Signature 객체 반환.
String getName() target method 이름 반환
String toLongString() target method 전체 syntax를 반환
String toShortString() target method를 축약해서 반환. 기본은 method 이름.

 


4. Annotation을 이용한 AOP 구현

 

가. xml 설정 파일

<!-- applicationContext.xml 또는 root-context.xml -->
<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.xsd">
    ...
    <aop:aspectj-autoproxy/>
  • <aop:aspectj-autoproxy/> : 스프링은 자동으로 @Aspect 어노테이션이 붙은 Bean을 찾아 advice를 적용한다.

 


나. Annotation 기반의 Advice Class 작성

 

  • @Aspect : Aspect Class로 등록
  • @Before(”pointcut”)
  • @AfterReturning(pointcut=””, returning=””)
  • @AfterThrowing(pointcut=””, throwing=””)
  • @After(”pointcut”)
  • @Around(”pointcut”)
@Aspect
public class BoardContentEncodeAdvice {

    @Before("execution(public * com.company.board..*Dao.*(..))")
    public void encode(JoinPoint joinPoint) {
        String name = joinPoint.getSignature().toShortString();
        System.out.println("BoardCheckAdvice : " + name);
        Object[] args = joinPoint.getArgs();
        for(Object obj : args) { 
            if(obj instanceof BoardDto) {
                System.out.println("내용을 암호화하자!!!!");
                BoardDto boardDto = (BoardDto) obj;
                String content = boardDto.getContent();
                boardDto.setContent("암호화한 내용 ::: " + content);
            }
        }
    }    
}
@Aspect
public class BoardContentDecodeAdvice {

    @AfterReturning(pointcut="execution(public * com.company.board..*Dao.*(..))", returning="returnObj")
    public void decode(JoinPoint joinPoint, Object returnObj) throws Throwable{
        System.out.println("HistoryAdvice : " + returnObj);
        System.out.println("목록의 내용을 복호화하자!!!");
        if(returnObj instanceof List) {
            List<BoardDto> list = (List)returnObj;
            for(BoardDto boardDto : list) {
                boardDto.setContent(boardDto.getContent().replace("암호화", "복호화"));
            }
        }
    }
}
@Aspect
public class ExceptionAdvice {

    @AfterThrowing(pointcut="execution(public * com.company.board..*Dao.*(..))", throwing="ex")
    public void exceptionAfter(Exception ex){
        System.out.println("ExceptionAdvice :: " + ex.getMessage());
        ex.printStackTrace();
    }
}
@Aspect
public class CountAdvice {

    @After("execution(public * com.company.board..*Dao.*(..))")
    public void after(JoinPoint joinPoint) {
        String name = joinPoint.toShortString();
        System.out.println("CountAdvice : " + name);
    }
}
@Aspect
public class PerformanceTraceAdvice {

    @Around("execution(public * com.company.board..*Dao.*(..))")
    public Object trace(ProceedingJoinPoint joinPoint) throws Throwable {
        String signature = joinPoint.getSignature().toShortString();
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        Object result = joinPoint.proceed(); // target 실행
        stopWatch.stop();
        System.out.println("PerformanceTraceAdvice : " + signature + " 실행 시간 - " + stopWatch.getTotalTimeMillis());
        return result;
    }
}

 

pointcut을 매번 반복하기 싫다면 다음과 같은 방법도 있다.

@Aspect
public class PerformanceTraceAdvice {

    @Pointcut("execution(public * com.company.board..*Dao.*(..))")
    public void profileTarget(){}

    @Around("profileTarget()")
    public Object trace(ProceedingJoinPoint joinPoint) throws Throwable {
        ...
    }
}

 


5. AOP 실행 순서

@Aspect
@Order(1)
public class FirstAdvice {
    // 첫 번째로 실행되는 advice 코드
}

@Aspect
@Order(2)
public class SecondAdvice {
    // 두 번째로 실행되는 advice 코드
}
<aop:config>
  <aop:aspect id="myAspect1" ref="aBean1" order="1">
    <!-- aspect configurations -->
  </aop:aspect>

  <aop:aspect id="myAspect2" ref="aBean2" order="2">
    <!-- aspect configurations -->
  </aop:aspect>
</aop:config>

order="우선 순위" 또는 @Order(우선순위)로 실행 순서를 설정한다.

 

우선순위의 값이 작을수록 빨리 실행한다.

 


 

  Filter Interceptor AOP
구현 Jakarta.servlet.Filter org.springframework.web.servlet.HandlerInterceptor  
관리 컨테이너 Servlet Container Spring Container Spring Container (Root)
적용 위치 web.xml servlet-context.xml execution으로 설정된 공통 코드 메서드
적용 대상 설정 url url execution에서 설정된 메서드(인자), 클래스 등 다양
동작 순서 Dispatcher Servlet 전달 전후 Controller에게 전달되기 전후 Proxy 패턴의 형태로 메서드 앞에 실행
request/response 객체 조작 가능 여부 O X X
주요 역할 Spring에 무관한 웹 자원 처리 Controller 관련 요청 및 응답 처리 주로 Business Logic 처리 후 Controller 처리
용도 사용자 인증, 권한 검사, 요청과 응답에 대한 로그, 문자열 인코딩, 웹 보안 관련 기능 인증 프로세스(로그인, 사용자 권한 관리), 사용자 세션 처리, 프로그램 실행 시간 측정 로깅, 트랜잭션, 에러 처리

 

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

[Spring] ControllerAdvice  (0) 2024.07.06
[Spring] Java Config  (0) 2024.07.06
[Spring] Interceptor  (0) 2024.07.06
[Spring] Filter  (0) 2024.07.06
[Spring] Spring Web Application의 동작 원리  (0) 2024.07.06