본문 바로가기
⚙️Backend/Spring

[Spring] Filter + AOP + Interceptor : AOP

by Bㅐ추 2020. 6. 6.
728x90
반응형

2020/06/06 - [🌎Web Application/Spring] - [Spring] Filter + AOP + Interceptor : Filter

 

지난 글과 이어진다.

4.  AOP

4-1. AOP 의미 다시 정리

AOP는 스프링의 3대 기반 기술중에 하나이다. AOP는 스프링의 기술 중에서 가장 이해하기 힘든 난해한 용어와 개념을 가진 기술로 악명이 높다.(왠지...) AOP를 바르게 이용하려면 OOP를 대체하려고 하는 것 처럼 보이는 AOP라는 이름 뒤에 감춰진, 그 필연적인 등장 배경과 스프링이 도입한 이유, 그 적용을 통해 얻을 수 있는 장점이 무엇인지에 대한 충분한 이해가 필요하다.

 

AOP는 OOP(Object Oriented Programming)을 보완하는 확정적인 개념이다.

즉, AOP란 OOP를 대신하는 새로운 개념이 아니라 OOP를 더욱 OOP답게 사용할 수 있도록 도와주는 개념이다.

 

기존의 OOP는 객체를 재 사용 함으로써 반복되는 코드의 양을 굉장히 많이 줄일 수가 있었지만, 객체의 재 사용에도 불구하고 반복되는 코드는 여전히 존재했다. 이러한 부분을 AOP가 해결해주었다.

 

기능을 핵심 기능(비즈니스 로직)과 공통기능(공통모듈)로 구분한 뒤에, 개발자의 코드 밖에서 필요한 시점에 비즈니스 로직에 삽입하여 실행되도록한다. 즉, OOP에서는 공통적인 기능을 각 객체의 횡단(흐름, 가로)으로 입력했다면, AOP는 공통적인 기능(공통모듈)을 종단(세로, 중간중간..)간으로 삽입할 수 있도록 한것! ( 글은 글인데 읽어도 이해가 안되는게 당연. )

 

그림1

위의 그림을 보자.

위의 그림은 OOP에서 로직 흐름을 보여준다.

layer가 3가지로 나뉘어져있는데 지금 중요한 것은 layer가 아니므로 노랑색 화살표만 보자.

노랑색 화살표는 총 3개로 각각 기능을 처리하는 로직을 의미한다.

계정,게시판,계좌이체 기능을 처리하는 로직이 3개라는 것.

보면 기능의 흐름이 횡단으로 이어져 있다. 

서로 다른 기능이라 각각 다른 로직으로 이루어져 있지만, 모두 똑같이 권한,로깅,트랜잭션이라는 기능을 처리해주고 있다. 기능에 따라 서로 다른 로직으로 구성을 했지만 완벽하게 서로 분리된 것이 아니라 아직까지 똑같은 코드가 있다는 의미이다. 모든 로직에 똑같은 코드가 반복적으로 삽입되어있다.

 

AOP가 없는 OOP에서는 다음과 같이 핵심기능(비즈니스 로직)을 횡단(쉽게 말하자면 한쪽에서 다른쪽으로 흘러가는 흐름, 노랑색 화살표)으로 이루어져 있고, 권한,로깅,트랜잭션또한 횡단으로 삽입되어 있다.

(둘 다 같은 방향으로 이루어져 있다.)

 

그렇지만, AOP가 적용된 OOP는 다음과 같이 권한,로깅,트랜잭션을 종단으로 삽입할 수 있도록 해준다.

 

그림2

그림1과 그림2를 비교했을 때, 권한 , 로깅, 트랙잭션과 같은 기능들이 각 계층의 바깥 쪽에서 종(세로)으로 연결이 된 것을 볼 수 있다.

즉 , 이처럼 프로그래밍에서 각 객체별로 횡단으로 흐름별로 처리한 것을 각 관점별로 외부에서 접근하는 것이 AOP의 핵심이다.

 

누군가 AOP가 뭐냐고 물어보면

관점 지향 프로그래밍으로, 기능을 핵심 비즈니스 기능(Core Concern:CC)공통 비즈니스 로직(Cross Cutting Concern:CCC 왜 Cross Cutting일까 생각해보자!! 그래도 모르겠다면 위의 그림 참고! ) 으로 '구분'하고, 공통 기능을 개발자의 코드 밖에서 필요한 시점에 적용하는 프로그래밍 방법.

이라고 설명하면 된다.!

 

Aspect,Advice,Pointcut...등등 이런거는 이따가 이해하도록 하자.

 

4-2. AOP적용

일단 따라해보기. 설명은 추후에.

 

일단 pom.xml에 AOP관련 라이브러리를 작성한다.

		<!-- aspectj weaver -->
		<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
		<dependency>
		    <groupId>org.aspectj</groupId>
		    <artifactId>aspectjweaver</artifactId>
		    <version>${org.aspectj-version}</version>
		</dependency>

그다음 공통기능이 될 클래스 LogAop 를 작성한다.

com/mvc/upgrade/common/aop/LogAop.java 추가.

 

package com.mvc.upgrade.common.aop;

import org.aspectj.lang.JoinPoint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class LogAop {
	public void before(JoinPoint join) {
		Logger logger = LoggerFactory.getLogger(join.getTarget()+"");
		logger.info("-------Aop Start-------");
		
		Object[] args = join.getArgs(); 
		if(args != null) {
			logger.info("method: "+join.getSignature().getName());
			for(int i=0;i<args.length;i++) {
				logger.info(i+"번째"+args[i]);
				
			}
		}
		
		System.out.println("before end");
	}
	
	public void after(JoinPoint join) {
		
		System.out.println("after start");
		Logger logger = LoggerFactory.getLogger(join.getTarget()+"");
		logger.info("-------Aop End-------");
		
	}
	
	public void afterThrowing(JoinPoint join) {
		Logger logger = LoggerFactory.getLogger(join.getTarget()+"");
		logger.info("---------Error Log----------");
		logger.info("ERROR : "+join.getArgs());
		logger.info("ERROR: "+join.toString());
		
	}
	
}

 

WEB-INF/spring/appServlet/aop-context/aop-context.xml

그리고, aop-context.xml 작성한다.

작성 전에, Namespace 에 aop를 체크해야 한다.

 

<?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"
   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-3.1.xsd">
   
   <bean name="logAop" class="com.mvc.upgrade.common.aop.LogAop" />
   
   <aop:config>
      <aop:pointcut expression="execution(public * com.mvc.upgrade.model.dao.*Dao*.*(..))" id="daoPoint" />
      <aop:aspect id="logAop" ref="logAop">
         <aop:before method="before" pointcut-ref="daoPoint" />
         <aop:after method="after" pointcut-ref="daoPoint" />
         <aop:after-throwing method="afterThrowing" pointcut-ref="daoPoint" />         
      </aop:aspect>
   </aop:config>   
   

</beans>

그리고 src/main/resources/log4j/log4j.xml 에 다음 코드를 추가한다.

	<!-- add -->
	<logger name="org.ibatis">
		<level value="info"/>
	</logger>
	<logger name="java.sql">
		<level value="info"/>
	</logger>

그리고 실행!

 

http://localhost:8787/upgrade/list.do 일 때,

http://localhost:8787/upgrade/detail.do?myno=2 일 때,

뭔지는 모르지만, Dao 접근할 때마다 AOP가 실행되는 것을 알 수 있다. 다시말해, 새로운 DAO를 만들더라도 AOP를 통해서 로그가 출력이 되는 것이다. 앞에서 각 관점별로 외부에서 접근한다고 이야기를 했던 것이 바로 이러한 것이다. 

 

 

코드설명

 

log4j란?

지금까지의 실습이 정상적으로 실행되는지 확인해보기 위해 자바의 println()메서드를 이용해서 데이터를 콘솔로 출력해서 보았다. 하지만 개발이 끝나고 실제 서비스를 한 후로는 더이상 메세지를 출력하는 구문은 필요가 없어진다.

따라서 주석처리를 하거나 삭제해야 한다. 하지만 유지관리를 하다보면 필요한 경우 다시 콘솔에 메세지를 출력해야 하는 경우도 발생하기도 한다. 아주 번거롭다.

실제 애플리케이션에서는 유지관리를 위해 웹사이트에 접속한 사용자의 정보나 각 클래스의 메서드 호출 시각 등 여러가지 정보를 파일로 저장해서 관리한다. 이러한 기능을 제공하는 것이 log4j.

 

다음 아래 코드는 sql문과 ibatis의 로그를 찍기위한 설정이였다.

	<logger name="org.ibatis">
		<level value="info"/>
	</logger>
	<logger name="java.sql">
		<level value="info"/>
	</logger>

 

 

이제 LogAop 클래스 소스코드를 이해하기 위해  AOP의 주요개념에 대해 알아보자.


1) 관점(Aspect) : 구현하고자 하는 횡단 관심사의 기능을 의미한다. 즉 공통기능이다. 여러 객체에 공통으로 적용될 기능. 종단으로 삽입하는 기능이다.  (한 개 이상의 포인트컷과 어드바이스의 조합으로 만들어진다.)

 

2) 조인포인트(Join point): 관점(Aspect)를 삽입하여 어드바이스(Advice)가 적용될 수 있는 위치를 말한다. 관점이 어디에 삽입되냐의 "어디"에 해당된다. 특정 작업시 실행되는 시점을 의미하기도 한다. = Aspect를 삽입하여 Advice가 적용될 수 있는 위치

Spring에서는 method만 가리킨다.(무조건 method단위)

 

3) 어드바이스(Advice)

그래서 그놈의 어드바이스는 무엇인가?

언제 공통 관심(Aspect)를 핵심 로직에 적용할 지 정의하는 코드이다.

또는, Aspect가 '무엇'을 '언제' 할 지를 정의

예를 들어, '메서드를 호출하기 전' (언제) 에 트랜잭션 시작(공통기능) 을 적용한다는 걸 정의하고 있다.

 

관점(Aspect)의 구현체조인 포인트에 삽입되어 동작될 코드이다. 

=Join Points에서 실행되어야 하는 코드(실제로 AOP 기능을 구현한 객체)이다.

 

어드바이스는 조인포인트와 결합하여 동작하는 시점에 따라 5개로 구분된다.

  • Before Advice : 조인포인트 전에 실행되는 advice
  • After returning advice : 조인포인트에서 성공적으로 리턴 된 후 실행되는 advice
  • After throwing advice : 예외가 발생하였을 경우 실행되는 advice
  • After advice : 조인포인트에서 메서드의 실행결과에 상관없이 무조건 실행되는 advice, 자바의 finally와 비슷한 역할을 한다.
  • Around advice : 조인포인트의 전 과정(전, 후)에 수행되는 advice

4) 포인트컷(PointCut)

Joinpoint의 부분집합으로서 실제 어드바이스(advice)가 적용되는 Joinpoint들이다. 패턴매칭을 이용하여 어떤 조인포인트를 사용할 것인지 결정한다. = Advice이 적용할 대상(주로 method)을 선정하는 방법이다.

 

포인트컷과 조인포인트가 상당히 헷갈릴 수 있는데,

Aspect가 삽입될 수 있는 지점들을 조인포인트라 하고, 그 중 삽입한 위치를 포인트컷이라 생각하면 된다.

Aspect가 삽입될 수 있는 지점들이 조인포인트인데, 그 중 진짜로 Aspect가 삽입된 지점을 포인트컷이라 부른다. 그러므로 포인트컷도 조인포인트로 볼 수 있다. 조인포인트 중에서 선택받은 것이 포인트컷이라고 부르는구나하고 생각하자.

 

5) 타겟(Target)

어드바이스(advice: 공통기능을 구현한 객체)를 받을 대상, 즉 핵심기능의 객체를 의미한다. 비지니스로직을 수행하는 클래스일수도 있지만, 프록시 객체(Object)가 될 수도 있다.

 

6) Proxy

타겟을 감싸서 타겟의 요청을 대신 받아주는 랩핑 오브젝트.

호출자 (클라이언트)에서 타겟을 호출하게 되면 타겟이 아닌 타겟을 감싸고 있는 프록시가 호출되어,

타겟 메소드 실행전에 선처리, 타겟 메소드 실행 후, 후처리를 실행시키도록 구성되어 있음. (AOP에서 프록시는 호출을 가로챈 후, 어드바이스에 등록된 기능을 수행 후 타겟 메소드를 호출함.)

 

7) Weaving

지정된 객체에 애스팩트를 적용해서 새로운 프록시 객체를 생성하는 과정

컴파일 타임, 클래스로드 타임, 런타임과 같은 시점에서 실행되지만, Spring AOP는 런타임에서 프록시 객체가 생성됨.

 


  정리
Aspect =pointcut + advice
=횡단 관심사의 기능(트랜잭션,로깅,권한..)
=공통기능
=여러 객체에 공통으로 적용 될 기능
=종단으로 삽입하는 기능
=<aop:>태그의 의미에서 advice+pointcut 으로 이루어져 있다.
Join point Aspect를 삽입할 위치
Aspect를 어디에 삽입하느냐에서 "어디"를 의미
Aspect를 삽입하여 해당 기능(Advice)이 적용될 수 있는 위치
Spring에서는 method 단위이다.
Advice Aspect기능의 구현체로 조인포인트에 삽입되어 동작될 코드
Join point에서 실행되어야 하는 코드
실제로 기능을 구현한 객체
조인포인트와 결합하여 동작하는 시점에 따라 5개로 구분된다.
=> "언제(조인포인트)" "무엇(Aspect의 기능)"을 할 지 정의된 코드
ex) <aop:before method="methodA" pointcut-ref="daoPoint"> :  daoPoint가 실행되기 전에(언제) methodA(무엇)실행
PointCut 실제로 Advice가 적용된 대상(위치,메소드)

 

 

왜 Aspect를 한 개 이상의 pointCut과 Advice의 조합으로 이루어진다고 하는 것 일까? 밑의 <aop:>태그를 보면 <aop:aspect>는 advice태그와 pointcut으로 이루어져 있음을 알 수 있다.

aop-context.xml

- <aop:config> : aop 정의

- <aop:aspect  id="logAop" ref="logAop"> : aop의 대상 객체

- <aop:pointcut id="daoPoint" expression="execution(public * com.mvc.upgrade.model.dao.*Dao*.*(..)))"/>: 포인트 컷을 설정한다. 포인트 컷은 어디에서 작동할 것인가 라고 이해하자.

 

밑에는 Advice를 정의한다. Advice는 '언제 무엇을 작동할 것인가'라고 이해한다.

"언제(조인포인트)" "무엇(Aspect의 기능)"을 할 지 정의된 코드

 

<aop:aspect id="logAop" ref="logAop">
         <aop:before method="before" pointcut-ref="daoPoint" />
         <aop:after method="after" pointcut-ref="daoPoint" />
         <aop:after-throwing method="afterThrowing"  pointcut-ref="daoPoint" />         
      </aop:aspect>

<?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"
   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-3.1.xsd">
   
   <bean name="logAop" class="com.mvc.upgrade.common.aop.LogAop" />
   
   <aop:config>
      <aop:pointcut expression="execution(public * com.mvc.upgrade.model.dao.*Dao*.*(..))" id="daoPoint" />
      <aop:aspect id="logAop" ref="logAop">
         <aop:before method="before" pointcut-ref="daoPoint" />
         <aop:after method="after" pointcut-ref="daoPoint" />
         <aop:after-throwing method="afterThrowing" pointcut-ref="daoPoint" />         
      </aop:aspect>
   </aop:config>   
   

</beans>

LogAop.java => 해당 방식은 XML방식으로 선언된 AOP

 

AOP 관련해서 JoinPoint를 파라미터로 전달받을 경우 반드시 첫번째 파라미터로 지정해야 함(그 외는 예외 발생)
JoinPoint 인터페이스는 호출되는 대상의 객체, 메서드, 그리고 전달되는 파라미터 목록에 접근할 수 있는 메서드를 제공한다.

Signature getSignature( ) - 호출되는 메서드에 대한 정보를 구함
Object getTarget( ) - 대상 객체를 구함
Object[ ] getArgs( ) - 파라미터 목록을 구함

 

org.aspectj.lang.Signature 인터페이스는 호출되는 메서드와 관련된 정보를 제공하기 위해 다음과 같은 메서드를 정의

String getName( ) - 메서드의 이름을 구함
String toLongName( ) - 메서드를 완전하게 표현한 문장을 구함(메서드의 리턴 타입, 파라미터 타입 모두 표시)
String toShortName( ) - 메서드를 축약해서 표현한 문장을 구함(메서드의 이름만 구함)

 

*AOP를 설정하는 태그

<aop:config> : aop 의 시작

<aop:aspect id="aopId" ref="공통모듈bean"> : aspect 선언

<aop:pointcut id="pointcutId" expression="expression"> : pointcut 선언

 

*Advice를 정의하는 태그

<aop:before>: pointcut에 설정된 메소드 실행 전에 적용되는 어드바이스를 정의한다.
<aop:after-returning>: pointcut에 설정된 메소드가 정상적으로 실행 된 후에 적용되는 어드바이스를 정의한다.
<aop:after-throwing> : pointcut에 설정된 메소드가 예외를 발생시킬 때 적용되는 어드바이스를 정의한다.
<aop:after>: pointcut에 설정된 메소드가 정상적으로 실행되는지 또는 예외를 발생시키는지 여부에 상관없이 어드바이스를 정의한다.
<aop:around>: pointcut에 설정된 메소드 호출 이전, 이후, 예외발생 등 모든 시점에 적용 가능한 어드바이스를 정의한다.

 

만약, XML 방식이 아닌 어노테이션 방식이라면 해당 Aspect(공통모듈)을 구현한 클래스에 어노테이션을 달고

xml에는 <aop:aspectj-autoproxy/> 을 추가해주면 된다.

2020/05/30 - [🌎Web Application/Spring] - [Spring] AOP(Aspect Oriented Programming) part 1

2020/05/30 - [🌎Web Application/Spring] - [Spring] AOP(Aspect Oriented Programming) part 2

2020/05/30 - [🌎Web Application/Spring] - [Spring] AOP(Aspect Oriented Programming) part 3

 

AOP의 경우에는 Interceptor나 Filter와 달리 메소드 전후의 지점을 자유롭게 설정가능(advice)하고, AOP는 주소, 파라미터, 어노테이션등 다양한 방법으로 대상을 지정할 수 있는 장점이 있다.

 

출처:

https://snoopy81.tistory.com/295

https://sarc.io/index.php/development/1267-aop

https://ktko.tistory.com/entry/Spring-AOP-%EA%B0%9C%EB%85%90-%EC%84%A4%EB%AA%85

https://doublesprogramming.tistory.com/115

https://addio3305.tistory.com/86

https://yuien.tistory.com/entry/AOP-%EA%B0%9C%EB%85%90

 

 

728x90
반응형