Spring AOP(1) AOP란?, 관심분리, Pointcut과 Execution, Advice와 5가지 동작시점, Aspect

Yeppi's 개발 일기·2022년 6월 24일
0

Spring&SpringBoot

목록 보기
4/16
post-thumbnail

1. AOP 란?

1) AOP 개념

관심 지향 / 관점 지향 프로그래밍

  • Aspect Oriented Programming

  • OOP 를 효과적으로 사용하는 것

  • 대부분 MVC 웹 어플리케이션에서 Web Layer, Business Layer, Data Layer 로 정의

    • Web Layer : REST API 제공, Client 중심의 로직 적용
    • Business Layer : logic 개발
    • Data Layer : DB 같은 외부 연동 처리


2) AOP 특징

특정 구역에 반복되는 로직들을 한곳에 모아서 해결

  • 비즈니스 로직만 집중
  • 공통 기능을 외부 클래스로 관리
  • 자바 코드는 직접 변경하지 않아도 됨
  • oop 의 단점 개선(ex. 외부 클래스.메서드()로 사용)


2. 관심 분리

1) 관심 분리란?

  • 비즈니스 클래스 (XXXImpl 클래스)

    • 비즈니스 클래스에 비즈니스 메서드가 여러개 존재하는 클래스
    • 비즈니스 클래스에 핵심 관심과 횡단 관심이 들어있다❗
  • 비즈니스 로직을 구현하는 메서드에 로킹 처리, 예외 처리, 트랜잭션 처리 등
    핵심 비즈니스 로직 외 부가적인 코드들(횡단 관심 부분)이 많이 첨부 된다.

    ⇒ 배보다 배꼽이 더 크다
    ex. 각 회사마다 각 메서드는 20줄을 넘기지 않는다는 기준도 있다

  • 비즈니스 메서드의 핵심 로직을 하나 개발한 후, 나머지 메서드의 핵심 로직에 복붙으로 개발하면?
    👉 메서드가 여러개일때 매우 불편(수정 시, 다른 메서드도 계속 수정)
    👉 핵심 로직(로직 처리) 부분을 분리할 필요가 있다.
  • 위와 같은 문제점을 해결하기 위해?

    👉 메서드가 여러 기능을 하지 않도록 분리시켜, 재사용성 + 가독성 증가❗
    👉 = 관심 분리 = 핵심 관심과 횡단 관심을 분리❗



2) 핵심 관심과 횡단 관심

핵심 관심 Crossutting Concerns

  • 각 메서드들의 핵심 비즈니스 로직(핵심 로직)
  • 비즈니스 클래스 = XXXServiceImpl 클래스

👉 비즈니스 클래스에는 비즈니스 메서드가 구현되어 있으며, 비즈니스 메서드가 가진 코드가 핵심 관심 코드다


횡단 관심 Core Concerns

  • 각 메서드들의 핵심 관심 외
    로깅, 익셉션, 트랜잭션, 각 로직별 경과 시간 확인, 인코딩 방식 결정
  • 횡단 관심 클래스 = XXXAdvice 클래스

👉 횡단 관심 클래스에는 비즈니스 메서드마다 실행될 공통의 로직이 들어있으며, 공통 로직이 횡단 관심 코드다


즉, 비즈니스 메서드에 비즈니스 로직만 구현할 수 있다.
컨트롤러와 디자인을 분리시키는 것과 같은 개념으로 이해하자.



3. AOP 용어

1) Joinpoint

  • 클라이언트가 호출하는 모든 비즈니스 메서드

  • XXXServiceImpl 클래스의 모든 메서드






2) Pointcut

  • 수많은 메서드 중에 내가 원하는 메서드 출력

  • 필터링된 조인포인트(=필터링된 비즈니스 메서드)

  • 사전처리와 사후처리 가능

  • 트랜잭션 처리

    • 영구적으로 반영한다면 commit, 반영안한다면 rollback
    • select 에서는 동작하지 않음
  • 필터링 하는 이유?

    • 횡단 관심으로 구현한 기능(트랜잭션 처리)을 원하는 메서드에서만 동작하게 하기 위해서
    • 모든 메서드에서 공통 기능(횡단 관심)을 동작하도록 할 때,
      전체 메서드 or 특정 메서드에서만 선택하여 동작시킬 수 있음
  • expression을 어떻게 설정하느냐에 따라 필터링이 달라짐
    • execution = 명시자(Spring에서), 함수
    • execution 표현식이 중요(아래에서 살펴보자)





3) Execution 표현식

  • 5가지 필터링
  • 공백 불가
    • return 타입과 패키지경로 사이에만 공백
  1. 리턴 타입 필터링

    • * : 모든 타입을 필터링
    • void : 리턴 타입이 void인 메서드만 필터링
    • !void : 리턴 타입이 void가 아닌 메서드만 필터링
  2. 패키지 경로 필터링

    • com.ssamz.biz.board : 정확하게 com.ssamz.biz.board인 패키지 필터링
    • com.ssamz.biz.. : com.ssamz.biz로 시작하는 모든 패키지 필터링
    • com.ssamz.biz..board : com.ssamz로 시작하는 모든 패키지 중에서 패키지 마지막이 board인 패키지를 필터링
  3. 클래스명 필터링

    • BoardServiceImpl : BoardServiceImpl 클래스를 필터링
    • *Impl : 클래스 이름이 Impl로 끝나는 모든 클래스(XXXImpl)를 필터링
  4. 메서드명 필터링

    • getBoardList : 이름이 정확하게 getBoardList인 메서드 필터링
    • get* : 이름이 get 으로 시작하는 모든 메서드(get()) 포함
  5. 매개 변수 필터링

    • (..) : 모든 경우의 매개변수의 개수와 타입을 허용



📌실습📌

  • return 타입이 void 인 메서드만 처리
    <aop:pointcut id="allPointcut" expression="execution(* com.ssamz.biz..*Impl.*(..))"/>
  • id 는 유니크 해야하고, getPointcut 으로 동작시키고 싶을 때
    • get으로 시작하는 메서드만(특정 메서드만) 사전처리

      <aop:config>
      		<aop:pointcut id="allPointcut" expression="execution(* com.ssamz.biz..*Impl.*(..))"/>
      		<aop:pointcut id="getPointcut" expression="execution(* com.ssamz.biz..*Impl.get*(..))"/>
      
      		<aop:aspect ref="log">
      			<aop:before pointcut-ref="getPointcut" method="printLog"/>
      		</aop:aspect>
      	</aop:config>
      ===> BoardDAO 생성
      ===> BoardServiceImpl 생성
      <사전 처리> 핵심 비즈니스 로직 수행 전 동작
      ===> getBoardList 사전처리
  • allPointcut 이라면?
    • 모든 메서드 사전처리

      ===> BoardDAO 생성
      ===> BoardServiceImpl 생성
      <사전 처리> 핵심 비즈니스 로직 수행 전 동작
      ===> insertBoard 사전처리
      <사전 처리> 핵심 비즈니스 로직 수행 전 동작
      ===> getBoardList 사전처리





4) Advice

  • 횡단 관심에 해당하는 공통 기능의 코드

  • 어드바이스 동작 시점을 5가지로 지정

    • before, after, aftrer-returning, after-throwing, around

Before

  • 비즈니스 메서드 실행 전에 동작
  • 아래 6) Aspect 예제 등 다른 예제 참고



After

  • 비즈니스 메서드 실행 후 무조건 실행
  • ex. getUser 메서드 실행 후에 로직 수행
    ===> BoardDAO 생성
    ===> BoardServiceImpl 생성
    ===> getUser 사전처리
    <사전 처리> 핵심 비즈니스 로직 수행 전 동작



After Returning

  • 비즈니스 메서드가 성공적으로 반환되면 동작
	<!-- 횡단관심에 해당하는 Advice 클래스 등록 -->
	<bean id="log" class="com.ssamz.biz.common.LogAdvice"/>
	<bean id="afterReturning" class="com.ssamz.biz.common.AfterReturningAdvice"/>
	
	<!-- AOP 설정 -->
	<aop:config>
		<aop:pointcut id="allPointcut" expression="execution(* com.ssamz.biz..*Impl.*(..))"/>
		<aop:pointcut id="getPointcut" expression="execution(* com.ssamz.biz..*Impl.get*(..))"/>

		. . .
		
		<aop:aspect ref="afterReturning">
			<aop:after-returning pointcut-ref="getPointcut" method="afterLog"/>
		</aop:aspect>
	</aop:config>
<사전 처리> 핵심 비즈니스 로직 수행 전 동작
===> getUser 사전처리
<사후 처리> 핵심 비즈니스 로직 수행 후 동작
  1. pointcut-ref="getPointcut"에 해당하는 메서드 실행 후

  2. 뭔가 리턴된 후에(<aop:after-returning . . .>)

  3. 해당 id(afterReturning)가 가지고 있는

  4. 해당 메서드(afterLog)를 실행


📌실습📌

  • 메서드가 정상적으로 수행되어 사후처리 로그 및 로그인 여부까지 알 수 있다.
  • AfterReturningAdvice
    public class AfterReturningAdvice {
    
    	public void afterLog(Object returnObj) {
    		System.out.println("<사후 처리> 비즈니스 메서드가 리턴한 값 : " + returnObj.toString());
    		
    		// 비즈니스 메서드가 리턴한 데이터를 이용해서 관리자가 로그인했는 지 여부를 확인
    		if(returnObj instanceof UserVO) {
    			UserVO user = (UserVO) returnObj;
    			
    			if(user.getRole().equals("ADMIN")) {
    				System.out.println(user.getName() + "님 관리자 전용 페이지로 이동합니다...");
    			}
    		}
    	}
    }
  • client에서 실행 결과
    ===> BoardDAO 생성
    ===> BoardServiceImpl 생성
    
    <사전 처리> 핵심 비즈니스 로직 수행 전 동작
    ===> getUser 사전처리
    <사후 처리> 비즈니스 메서드가 리턴한 값 : UserVO(id=test, password=test, name=yeppi, role=ADMIN)
    yeppi님 관리자 전용 페이지로 이동합니다...
    yeppi님 환영합니다.



After Throwing

  • 비즈니스 메서드 실행 중 예외가 발생하면 동작

  • AfterThrowingAdvice

    public class AfterThrowingAdvice {
    
        public void exceptionLog() {
            System.out.println("<예외 처리> 비즈니스 메서드 수행 중 예외 발생");
        }
    }
  • business-layer.xml

        <!-- 횡단관심에 해당하는 Advice 클래스 등록 -->
        <bean id="log" class="com.ssamz.biz.common.LogAdvice"/>
        <bean id="afterReturning" class="com.ssamz.biz.common.AfterReturningAdvice"/>
        <bean id="afterThrowing" class="com.ssamz.biz.common.AfterThrowingAdvice"/>
    
        <!-- AOP 설정 -->
        <aop:config>
            <aop:pointcut id="allPointcut" expression="execution(* com.ssamz.biz..*Impl.*(..))"/>
            <aop:pointcut id="getPointcut" expression="execution(* com.ssamz.biz..*Impl.get*(..))"/>
    
            . . .
    
            <aop:aspect ref="afterThrowing">
                <aop:after-throwing pointcut-ref="allPointcut" method="exceptionLog"/>
            </aop:aspect>
        </aop:config>
    1. 예외 발생 후에(뭔가 리턴된 후에)

    2. 컨테이너는 afterThrowing 이 가지고 있는 exceptionLog 를 모든 메서드(allPointcut)에서 실행

  • 클라이언트 실행 결과
    <사전 처리> 핵심 비즈니스 로직 수행 전 동작
    <예외 처리> 비즈니스 메서드 수행 중 예외 발생
    Exception in thread "main" java.lang.IllegalArgumentException



Around

  • 메서드 호출 가로채서 비즈니스 메서드 실행 전후에 특정 로직 삽입

  • AroundAdvice.java

    public class AroundAdvice {
    
        public Object aroundLog(ProceedingJoinPoint jp) throws Throwable{ // Throwable 은 exception 의 부모
            Object returnObj = null;
    
            System.out.println("--<Before Logic>--");
            returnObj = jp.proceed(); // proceed() -> return 타입 Object, 예외 throws 는 Throwable
            System.out.println("--<After Logic>--");
    
            return returnObj;
        }
    }
  • xml

    <bean id="around" class="com.ssamz.biz.common.AroundAdvice"/>
    
        <!-- AOP 설정 -->
        <aop:config>
            <aop:pointcut id="allPointcut" expression="execution(* com.ssamz.biz..*Impl.*(..))"/>
            <aop:pointcut id="getPointcut" expression="execution(* com.ssamz.biz..*Impl.get*(..))"/>
    
            . . .
    
            <aop:aspect ref="around">
                <aop:around pointcut-ref="allPointcut" method="aroundLog"/>
            </aop:aspect>
        </aop:config>
    <사전 처리> 핵심 비즈니스 로직 수행 전 동작
    --<Before Logic>--
    ===> getUser 사전처리
    --<After Logic>--
    <사후 처리> 비즈니스 메서드가 리턴한 값 : UserVO(id=test, password=test, name=yeppi, role=ADMIN)
    yeppi님 관리자 전용 페이지로 이동합니다...
    yeppi님 환영합니다.
    1. 클라이언트가 getUser 메서드 호출 요청
    2. 어라운드가 가로챔
    3. before 수행 시작
    4. jp.proceed()getUser() 메서드 호출
    5. 다시 권한이 돌아와서 returnObj 값 받아오고
    6. after 수행 시작
    7. 클라이언트에게 반환

    👉 필터와 유사한 동작




🍑ProceedingJoinPoint < JoinPoint🍑

  • ProceedingJoinPointJoinPoint 를 상속 받음
  • ProceedingJoinPoint 는 확장된 상태
  • ProceedingJoinPoint 에만 proceed() 메서드가 추가되어 있음
  • 문법
    • Around 로 동작하는 메서드만 Proceed 를 매개변수로 사용하고
    • 나머지는 JoinPoint 사용하기

🍑after VS after-returning🍑

  • after-returning

    <aop:aspect ref="afterReturning">
    	<aop:after-returning pointcut-ref="getPointcut" method="afterLog" returning="returnObj"/>
    </aop:aspect>
    • return 값 뿌리기 가능
    • afterLog 메서드 매개변수 returnObj 변수에 return 객체를 넘겨라(바인딩 해줘라)
  • after 로 등록한 메서드는 return 값을 받지 못함

👉 반환 여부에 따라


🍑before VS after VS around🍑

  • before
    • 비즈니스 메서드 호출된 시간 출력하고 싶을 때
  • after
    • 반대로 언제 해당 메서드가 종료되었는 지 시간 정보를 출력하고 싶을 때
  • around
    • 메서드 실행 시간이 얼마나 걸렸는 지 알고 싶을 때
    • 많이 사용하진 않음

👉 아예 용도가 다름






5) Weaving

  • 논리적인 용어
  • 크게 중요하지 않음(by 강사님)
  • Aspect 가 지정된 객체로 새로운 프록시 객체(CGLIB)를 생성하는 과정





6) Aspect

  • = Advisor
    = Pointcut + Advice
  • 필터링된 비즈니스 메서드(Pointcut) + 횡단관심에 해당하는 공통 기능의 메서드(Advice)
    ⇒ 이 둘을 적절히 연결하는 것

  • 핵심 관심 + 횡단 관심

    <!-- 횡단관심에 해당하는 Advice 클래스 등록 -->
    	<bean id="log" class="com.ssamz.biz.common.LogAdvice"/>
    	
    	<!-- AOP 설정 -->
    	<aop:config>
    		<aop:pointcut id="allPointcut" expression="execution(* com.ssamz.biz..*Impl.*(..))"/>
    		<aop:pointcut id="getPointcut" expression="execution(* com.ssamz.biz..*Impl.get*(..))"/>
    
    		<aop:aspect ref="log">
    			<aop:before pointcut-ref="allPointcut" method="printLog"/>
    		</aop:aspect>
    	</aop:config>
    • 핵심 관심(AOP의 P) pointcut-ref="allPointcut"
      횡단 관심(AOP의 A) method="printLog"
      이 둘을 연결(AOP의 O)

    • xml 파일은 컨테이너를 위한 파일(컨테이너에 지시/설정)

    • 동작 흐름

      1. pointcut-ref="allPointcut" 모든 비즈니스 메서드가 실행되기 전에(before)
      2. id 가 log 로 지정된(<bean id="log" class"">) 클래스들은
      3. 설정된 해당 메서드( method="printLog")를 실행하라고 하는 것


정리

Pointcut 👉 핵심 관심, 필터링 기능
Advice 👉 횡단 관심, 필터링 동작 시점
Ascpect 👉 Pointcut 과 Advice 의 연결 고리

AOP를 적용하면? 핵심 비즈니스 로직을 분리하여 유지보수가 편리해진다.

profile
imaginative and free developer. 백엔드 / UX / DATA / 기획에 관심있지만 고양이는 없는 예비 개발자👋

0개의 댓글