SpringFramework : Spring AOP

김다린·2024년 4월 17일

MVC

목록 보기
5/5

1. AOP 개요

AOP란, 문제를 바라보는 관점을 기준으로 프로그래밍하는 기법을 말한다.
관점지향 프로그래밍이라고도 함.

문제를 해결하기 위한 핵심 관심 사항(PointCut)과 전체에 적용되는 공통 관심 사항(Aspect)을 기준으로 프로그래밍함으로써 공통 모듈을 여러 코드에 쉽게 적용할 수 있게 만들어 준다.

OOP(객체 지향 프로그래밍)스럽게 만들어준다.

핵심 관심 사항 (core concern)
: 핵심로직, 핵심 비즈니스 로직 ex)계좌이체, 이자계산, 대출처리 등

공통 관심 사항(cross-cutting concern)
: 공통기능으로 어플리케이션 전반에 걸쳐 필요한 기능

AOP에서 중요한 개념은 횡단 관점의 분리(Separation of Cross-Cutting Concern)이다.

횡단관점이라는 말을 들었을때는 붕 뜬 개념처럼 다가오는데, 찾아보니 흩어진 관심사 라고 표현하면 조금 더 와닿는다.


출처 : https://expert0226.tistory.com/200

각 모듈 내에는 각각의 핵심코드들이 존재하지만, 그 기능을 사용하기 위해서 공통 기능을 호출하는 코드까지는 각 모듈로부터 분리할 수 없다. 그렇기 때문에 분리한 공통기능을 이요하기 위한 코드가 각 모듈에 횡단으로 산재되게 된다.

이러한 현상을 횡단적 산재라고 부르는데, 이렇게 되면 OOP스러워지지 않는다.

핵심코드 안에 공통코드가 포함되어 분리되지 않기 때문이다.

이를 해결하기 위해 횡단관점을 분리한 코드가 필요하다.

예를 들어보자, 이전에 작성한 sayHello라는 코드가 실행되는 시간을 로그로 찍어보고 싶다.

그러면 직관적인 방법으로 sayHello내에

System.out.println(">>>LOG<<< : "+new Date());

를 추가할 수 있다. 그러면 이러한 로그를 찍어보고 싶은 클래스가 100개라면? 확인한 후에 다시 해당 println코드를 모두 주석처리를 해야한다면?

노가다도 이런 노가다가 없다.

해당 코드를 HelloLog라는 클래스로 만들어 넣고 HelloLog.log()로 호출한다면?

주석처리를 100번반복해야하는 노가다는 줄일 수 있다.

하지만 여전히 OOP로써는 부족하다. 여전히 공통코드인 HelloLog.log()가 핵심코드인 sayHello 내에 있으니까.

여기서 AOP가 등장을 한다. AOP는 객체를 핵심 관심사횡단 관심사로 분리하고, 횡단 관심사를 관점(Aspect)라는 모듈로 정의하고 핵심 관심사와 엮어서 처리할 수 있는 방법을 제공한다. (출처 : 표준프레임워크)

아까 들었던 예시를 기준으로 하면 sayHello는 핵심 관심사, 그 안에 들어갔던 HelloLog는 공통 관심사로 분리해주는거다.

그럼 어떻게 분리해주는가? Proxy를 사용하는 방법, xml을 사용하는 방법, annotation을 사용하는 방법. 3가지 정도가 있다. 이를 순서대로 알아보자

그 전에 AOP 용어들도 익혀두어야 한다. 각 메서드를 보고 용어들을 말할 수 있을 정도로!

용어설명
Target object하나 또는 그 이상의 Aspect에 의해 Advice되는 객체 핵심로직을 구현하는 클래스.HelloService 객체
JoinPoint애플리케이션을 실행할 때 특정 작업이 시작될 수 있는 시점을 의미한다.
클래스가 로드되는 시점, 인스턴스가 생성되는 시점, 메서드 호출 시점 등이 조인포인트에 해당한다. joinPoint는 Advice를 적용할 수 있는 시점들이다. 스프링에서는 프록시기반 AOP를 지원하기 때문에 메서드 호출 joinPoint만 지원한다.
모든 biz() 메서드 sayHello() sayGoodbye()
PointCut조인포인트의 부분집합입니다. 실제로 어드바이스가 적용되는 조인포인트들을 의미합니다. 스프링에서는 정규표현식이나 Aspect의 문법을 이용하여 포인트컷을 정의할 수 있습니다.sayHello()
Aspect여러 객체에 공통으로 적용되는 공통 관심 객체를 의미함.HelloLog 객체
Advice핵심코드에 삽입되어 동작할 수 있는 공통코드와 시점을 의미합니다. 어드바이스 시점은 before, after, after-throwing, after-returning, around가 있다.~ 전(before)에 log() 메서드를 실행
WeavingAdvice(공통코드+시점)을 핵심코드에 삽입하는 것을 의미HelloServiceProxy 클래스를 만드는 것

2. 직접 Proxy클래스를 만드는 AOP

Target object를 상속받고, PointCut메서드를 재정의함

AOP 구현


스프링에서는다음3가지방식으로AOP구현을지원한다.

  1. XML 기반의 POJO 클래스를 이용한AOP구현
  2. AspectJ 5/6에서 정의한 @Aspect 아노테이션 기반의 AOP구현
  3. 스프링 API를 이용한AOP구현
public class HelloLog {
	public static void log() {
		System.out.println(">>>LOG<<< : "+new Date());

	}

클래스를 하나 더 만들어 HelloLog를 만들어 준다.

public class HelloService implements IHelloService {

	@Override
	public String sayHello(String name) {
		HelloLog.log(); //횡단적 산재
		return "Hello~ "+name;
	}

	@Override
	public String sayGoodbye(String name) {
		
		return "Bye~ "+name;
	}
}

원래대로라면 HelloService에 추가하면 되지만 이렇게 하면 핵심코드가 뒤섞여 횡단적 산재(흩뿌려짐)가 된다.

이를 방지하기 위해 Proxy 클래스를 하나더 만들어 주어 핵심코드를 분리해주도록 한다.

public class HelloServiceProxy extends HelloService {

	public String sayHello(String name) {
		HelloLog.log();
		return super.sayHello(name);
	}
}

이제 만들어진 Proxy로 Bean을 만들어야 하기 때문에 application-config.xml 내의 클래스 주소도 변경을 해준다.

<bean id="helloService" class="com.example.myapp.HelloServiceProxy"/>
<bean id="helloController" class="com.example.myapp.HelloController">
	<property name="helloService" ref="helloService"></property>
</bean>

그러면 HelloService 내에 log 코드를 작성하지 않아도 HelloServiceProxy 객체에서 sayHello를 재정의하여 sayHello가 실행되기 전에 로그를 찍을 수 있다.

Weaving 방식

  1. 컴파일 시 위빙

    별도 컴파일러를통해핵심관심사모듈의사이사이에관점(Aspect) 형태로 만들어진횡단관심사코드들이삽입되어관점(Aspect)이 적용된최종바이너리가만들어지는방식
    (ex. AspectJ)

  2. 클래스 로딩 시 위빙

    별도의Agent를이용하여 JVM이클래스를로딩할때해 당클래스의바이너리정보를 변경한다. 즉, Agent가횡단 관심사코드가 삽입된 바이너리코드를 제공함으로써 AOP 를지원하게된다.(ex. AspectWerkz)

  3. 런타임시 위빙

    소스코드나 바이너리파일의 변경없이 프록시를 이용하여 AOP를 지원하는 방식이다. 프록시를 통해 핵심 관심사를 구현한 객체에 접근하게 되는데, 프록시는 핵심 관심사 실행 전 후에 횡단관심사를 실행한다.

    따라서 프록시 기반의 런타임 엮기의 경우 메소드호출시에만 AOP를 적용할 수 있다는 제한점이 있다.(ex. Spring AOP)

3. XML을 이용한 AOP

xml 파일 내에서 AOP 생성을 위해서는pom.xml 파일 내에 의존성 파일을 추가해주어야 한다.

<dependency>
 <groupId>org.aspectj</groupId>
 <artifactId>aspectjweaver</artifactId>
 <version>1.9.1</version>
 </dependency>

application-config.xml 파일 내에 직접 <aop:config> , <aop:pointcut> 등을 추가하여 weaing을 만들 수 있다.

Advice


aop:before

before 어드바이스는핵심코드가실행되기전에공통코드가실행

예: <aop:before pointcut-ref="hello" method="log" />

public static void log(JoinPoint joinPoint) {
		Signature signature=joinPoint.getSignature();
		String methodName = signature.getName();
//		String methodName = signature.toLongString();
		//메소드원형을 signature라고 부름
		System.out.println(">>>LOG<<< : 핵심코드 메서드명 - "+ methodName);
		System.out.println(">>>LOG<<< : "+new Date());
	}

aop:after

after 어드바이스는 핵심코드가 실행된 후 (리턴값이없을경우또는finally 블록이실행된후)
공통코드가실행

예: <aop:after pointcut-ref="hello" method="log" />(메서드 동일)

aop:after-returning

after-returning 어드바이스는 핵심코드 메서드가 리턴한 다음 공통코드가 실행

예: <aop:after-returning method="resultLog" pointcut-ref="hello" returning="resultObj"/>

// 핵심코드가 어떤 타입으로 반환할지 잘 모르는 경우 Object로 설정해둔다. 
	public void resultLog(JoinPoint joinPoint, Object resultObj) {
		Signature signature=joinPoint.getSignature();
		String methodName = signature.getName();
		//메소드원형을 signature라고 부름
		System.out.println(">>>RESULT LOG<<< : 핵심코드 메서드명 - "+ methodName);
		System.out.println("핵심코드의 반환 값 : "+resultObj.toString());
	}

aop:after-throwing

after-throwing 어드바이스는 핵심코드에서 예외가 발생할 경우 공통코드가 실행

예: <aop:after-throwing printcut-ref="xxx" method="exceptionLog" throwing="exception" />

메서드예: public void exceptionLog(Exception exception) { … }

예외를 발생시키기 위해서 sayHello() 메서드를 다음과 같이 수정해준다.

	public String sayHello(String name) {
//		HelloLog.log(); //횡단적 산재
		System.out.println("HelloService.sayHello() 실행");
		int rand=(int)(Math.random()*10);
		if(rand<5) {
			throw new RuntimeException("강제 예외 발생");	
		}
		return "Hello~ "+name;
	}
public void exceptionLog(JoinPoint joinPoint, Exception exception) {
		Signature signature=joinPoint.getSignature();
		String methodName = signature.getName();
		//메소드원형을 signature라고 부름
		System.out.println(">>>RESULT LOG<<< : 핵심코드 메서드명 - "+ methodName);
		System.out.println("예외 발생 - 메시지 : "+exception.getMessage());
	}

aop:around

around 어드바이스는 핵심코드가 실행되는 동안 공통코드가 실행

public Object aroundLog(ProceedingJoinPoint joinPoint) {
		//ProcedingJoinPoint is a JoinPoint /상속관계 이기때문에 joinpoint가 가지고 있던 signature 함수를 사용가능함
		Object result = null;
		Signature signature =joinPoint.getSignature();
		String methodName=signature.getName();
		System.out.println(">>>BEFORE LOG<<< : 메서드 이름 - "+methodName);
		
		try {
			result = joinPoint.proceed(); //핵심코드가 실행됨
			
		} catch (Throwable e) {
			System.out.println(">>>EXCEPTION LOG<<< : 예외 메시지 - " + e.getMessage());
		}finally {
			System.out.println(">>>FINALLY<<<");
		}
		return result;
		//aroundLog 반환 타입을 String으로 할 경우 return (String)result 식으로  강제 형변환이 필요함
	}

PointCut


포인트컷을 정의하기 위한 표현식으로는 execution, within, bean 세가지 표현식이 존재한다.
execution을 제일 많이 사용하고 그뒤로 within, bean 이 차례로 자주 사용된다.

  • bean 표현식: 빈의이름을 이용하여 포인트컷 지정
  • within 표현식 : 지정한 패키지 또는 클래스내의 모든 메서드를 포인트컷으로 지정
  • execution 표현식: 가장 일반적인 포인트컷 표현식. 메서드 시그니처별로 포인트컷 지정 가능

5. Annotation을 이용한 AOP

annotation을 이용한 aop를 생성하려면 1.x 버전 이상의 springFramework가 필요하기 때문에 pom.xml에서 버전을 수정해준다.

공통코드(HelloLog) 내에 @Component (xml 파일 내에 context 추가해줘야함) 를 추가하거나

<bean id="helloLog" class="com.example.myapp.HelloLog"></bean>

를 xml파일에 추가시켜줘야함

공통코드인 곳에 @Aspect 를 추가시켜준다.

profile
한걸음씩 뚜벅뚜벅

0개의 댓글