본문 바로가기
Spring

[Spring] AOP 에 대해서 (2)

by jaee_ 2021. 10. 4.
본 글은 📚 스프링 입문을 위한 자바 객체지향의 원리와 이해 를 읽고 정리한 내용입니다.

 

이전글 : [Spring] AOP 에 대해서 (1)

 

[Spring] AOP 에 대해서 (1)

본 글은 📚 스프링 입문을 위한 자바 객체지향의 원리와 이해 를 읽고 정리한 내용입니다. 스프링의 3대 프로그래밍 모델 중 첫 번째인 DI를 지난 글에서 살펴봤다. 두 번째는 AOP이다. AOP는 Aspec

yeoonjae.tistory.com


👌🏻 일단 덤벼 보자 - 용어편

용어 영한 사전
Aspect 관점, 측면, 양상
Advisor 조언자, 고문
Advice 조언, 충고
JoinPoint 결합점
PointCut 자르는 점

위의 표의 용어를 하나씩 알아보자. 


🤷‍♂️ PointCut - 자르는 점? Aspect 적용 위치 지정자!

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class MyAspect {
	@Before("execution(* runSomething())") // * runSomething() 
	public void before(JoinPoint joinPoint) {
		System.out.println("얼굴 인식 확인 : 문을 개방하라");
//		System.out.println("열쇠로 문을 열고 집에 들어간다.");
	}
}

위의 코드에서 주석으로 표시한 * runSomething() 부분이 바로 PointCut이다. @Before("execution(* runSomething())")은 지금 선언하고 있는 메소드를 PointCut 메소드가 실행되기 전(@Before)에 실행하라는 의미다. 여기서 public void before은 횡단 관심사를 실행하는 메소드가 된다. 

 

결국 PointCut이라고 하는 것은 횡단 관심사를 적용할 타깃 메소드를 선택하는 지시자(메소드 선택 필터)인 것이다. 즉, "타깃 클래스의 타깃 메소드 지정자"라고 할 수 있다. Aspect 적용 위치 지정자라고 적은 이유는 AspectJ처럼 메소드뿐만 아니라 속성 등에도 Aspect를 적용할 수 있기에 그것들까지 고려한 이름이다. PointCut을 메소드 선정 알고리즘이라고도 한다. 

 

타깃 메소드 지정자에는 익히 잘 알려진 정규식과 AspectJ 표현식 등을 사용할 수 있다. 간단히 소개하면 다음과 같다. 

[접근제한자패턴]리턴타임패턴 [패키지&클래스패턴]메소드이름패턴(파라미터패턴) [throws 예외패턴]

대괄호는 생략이 가능한 것을 표시한 것이고, 필수요소는 리턴 타입 패턴, 메소드 이름 패턴, 파라미터 패턴이다. 


🤷‍♂️ JoinPoint - 연결점? 연결 가능한 지점!

 

Pointcut 은 JointPoint의 부분 집합이다. 스프링 AOP는 인터페이스를 기반으로 한다고 설명했다. 그럼 인터페이스는 무엇일까? 추상메소드의 집합이다. 그러니 스프링 AOP 는 메소드에만 적용 가능하다는 결론에 도달한다. 

 

Pointcut의 후보가 되는 모든 메소드들이 JoinPoint, 즉 Aspect 적용이 가능한 지점이 된다. 그래서 JoinPoint란 "Aspect적용이 가능한 모든 지점" 이라고 할 수 있다. 따라서 Aspect를 적용할 수 있는 지점 중 일부가 Pointcut이 되므로 Pointcut은 JoinPoint의 부분 집합인 셈이다. 

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class MyAspect {
	@Before("execution(* runSomething())") 
	public void before(JoinPoint joinPoint) { // JoinPoint
		System.out.println("얼굴 인식 확인 : 문을 개방하라");
//		System.out.println("열쇠로 문을 열고 집에 들어간다.");
	}
}

그럼 위의 코드에서 주석으로 표시된 JoinPoint는 무엇일까? 그건 그때그때 다르다. 이전 글에서 작성했던 코드를 기반으로 하면 JoinPoint는 romeo의 runSomething() 혹은 juliet의 runSomething() 메소드가 된다.

 

JoinPoint 파라미터를 이용하면 실행 시점에 실제 호출된 메소드가 무엇인지, 실제 호출된 메소드를 소유한 객체가 무엇인지, 또 호출된 메소드의 파라미터가 무엇인지 등의 정보를 확인할 수 있다. 

 

정리해보면

  • 광의(넓은 의미)의 JoinPoint란 Aspect 적용이 가능한 모든 지점이다.
  • 협의(좁은 의미)의 JoinPoint란 호출된 객체의 메소드이다. 

🤷‍♂️ Advice - 조언? 언제, 무엇을!

 

Advice는 pointcut에 적용할 로직, 즉 메소드를 의미하는데 여기에 더해 언제라는 개념까지 포함하고 있다. 결국 Advice는 Pointcut에게 언제, 무엇을 적용할지 정의한 메소드이다.

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class MyAspect {
	@Before("execution(* runSomething())") 	
	public void before(JoinPoint joinPoint) { 
		System.out.println("얼굴 인식 확인 : 문을 개방하라");
//		System.out.println("열쇠로 문을 열고 집에 들어간다.");
	}
}

위의 코드에서 지정된 Pointcut 이 시작되기 전 (@Before) 에 before() 메소드를 실행하라고 되어있음을 확인할 수 있다. 


🤷‍♂️ Aspect - 관점? 측면? Advisor 의 집합체!

 

AOP 에서 Aspect는 여러 개의 Advice 와 여러 개의 Pointcut의 결합체를 의미하는 용어다. 수식으로 표현하면 아래와 같다. 

Aspect = Advice들 + Pointcut들

Advice는 언제(When), 무엇을(What) 의미하는 것이었다. Pointcut은 어디에(Where)를 의미하는 것이었다. 결국 Aspect는 When + Where + What(언제, 어디에, 무엇을)이 된다. 위에서 살펴봤던 예제들을 기반으로 해석해보면 Pointcut인 public void aop002.Boy.runSomething() 메소드가 시작되기 전 (@Before)에 before() 메소드를 실행하라고 되어있는 것을 볼 수 있다. 


🤷‍♂️ Advisor - 조언자? 어디서, 언제, 무엇을!

 

Advisor는 다음과 같은 수식으로 표현할 수 있다. 

Advisor = 한 개의 Advice + 한 개의 Pointcut

Advisor는 스프링 AOP 에서만 사용하는 용어이며 다른 AOP 프레임워크에서는 사용하지 않는다. 또 스프링 버전이 올라가면서 사용하지 말라고 권고하는 기능이기도 하다. 스프링이 발전해 오면서 Aspect가 나왔기 때문이다. 


✋🏻 일단 덤벼 보자 - POJO와 XML 기반 AOP

기존의 어노테이션 기반으로 작성한 AOP 예제를POJO와 XML기반으로 전환하기 위해 변경할 곳은 Aspect가 정의되어 있는 MyAspect.java와 스프링 설정 정보 XML파일이다. 먼저 MyAspect.java부터 살펴보자.

// 기존 어노테이션 기반 - MyAspect.java가 스프링 프레임워크에 종속

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class MyAspect {
	@Before("execution(* runSomething())")
	public void before(JoinPoint joinPoint) {
		System.out.println("얼굴 인식 확인 : 문을 개방하라");
//		System.out.println("열쇠로 문을 열고 집에 들어간다.");
	}
}
// 변경 POJO&XML 기반 - MyAspect.java가 스프링 프레임워크에 종속되지 않음

import org.aspectj.lang.JoinPoint;

public class MyAspect {
	public void before(JoinPoint joinPoint) {
		System.out.println("얼굴 인식 확인 : 문을 개방하라");
//		System.out.println("열쇠로 문을 열고 집에 들어간다.");
	}
}

기존 코드와 변경된 것은 @Aspect 과 @Before 어노테이션이 사라졌다. 그렇게 MyAspect.java는 스프링 프레임워크에 의존하지 않는 POJO가 된다. 

 

그럼 이제 스프링 설정파일인 XML파일을 보자. 

// 기존 
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns=생략 />
	<aop:aspectj-autoproxy />

	<bean id="myAspect" class="aop002.MyAspect"/>
	
	<bean id="boy" class="aop002.Boy"/>
	<bean id="girl" class="aop002.Girl"/>
	
</beans>
// 변경 - aop 관련 태그 추가
<생략>
	<aop:aspectj-autoproxy />

	<bean id="myAspect" class="aop002.MyAspect"/>
	
	<bean id="boy" class="aop002.Boy"/>
	<bean id="girl" class="aop002.Girl"/>
	
    <aop:config>
    	<aop:aspect ref = "myAspect">
        	<aop:before method="before" pointcut="execution(* runSomething())" />
        </aop:aspect>
    </aop:config>
</beans>

기존의 MyAspect.java에서 사라진 어노테이션들이 변경 후의 XML파일에 태그로 나타났다. 

변경 전 MyAspect.java 와 변경 후 스프링 설정 파일 

위 코드로 변경 후 Start.java를 실행하면 결과는 다음과 같이 나온다. 

INFO ...(생략)
얼굴 인식 확인 : 문을 개방하라
컴퓨터로 게임을 한다.
얼굴 인식 확인 : 문을 개방하라
책을 펴고 공부를 한다.

🖐🏻 AOP 기초 완성

이번에는 After 어드바이스를 살펴보자. After 어드바이스는  해당 JoinPoint 메소드를 실행한 후에 실행된다. 코드로 보자. 기존의 코드에선 Advice를 만드는 클래스와 스프링 설정 파일의 AOP 설정 두 곳이 변경된다. 

// Aspect 클래스 
import org.aspectj.lang.JoinPoint;

public class MyAspect {
	public void before(JoinPoint joinPoint) {
		System.out.println("얼굴 인식 확인 : 문을 개방하라");
//		System.out.println("열쇠로 문을 열고 집에 들어간다.");
	}
	
	public void lockDoor(JoinPoint joinPoint) {
		System.out.println("주인 나갔다. 문 잠가!");
	}
}
// 설정파일 XML파일
<생략>

	<aop:aspectj-autoproxy />

	<bean id="myAspect" class="aop002.MyAspect" />

	<bean id="boy" class="aop002.Boy" />
	<bean id="girl" class="aop002.Girl" />
	
	<aop:config>
		<aop:aspect ref="myAspect">
			<aop:before method="before" pointcut="execution(* runSomething())" />
			<aop:after method="lockDoor" pointcut="execution(* runSomething())" />
		</aop:aspect>
	</aop:config>
</beans>

위와 같이 코드를 변경한 후 Start.java를 실행하면 결과는 다음과 같다. 

INFO...(생략)
얼굴 인식 확인 : 문을 개방하라
컴퓨터로 게임을 한다.
주인 나갔다. 문 잠가!
얼굴 인식 확인 : 문을 개방하라
책을 펴고 공부를 한다.
주인 나갔다. 문 잠가!

위의 파일을 보면 중복되는 내용이 있다. 일반적으로 프로그래밍에서 중복은 리팩토링의 대상으로 해소해줘야 한다!

XML 설정 파일 기반이라면 XML파일만 아래와 같이 수정하면 된다. 

// 설정파일 XML파일수정
<생략>

	<aop:aspectj-autoproxy />

	<bean id="myAspect" class="aop002.MyAspect" />

	<bean id="boy" class="aop002.Boy" />
	<bean id="girl" class="aop002.Girl" />
	
	<aop:config>
    	<aop:pointcut expression="execution(* runSomething())" id = "iampc"/> -- 이 부분 변경
		<aop:aspect ref="myAspect">
			<aop:before method="before" pointcut-ref="iampc" />	-- 이 부분 변경
			<aop:after method="lockDoor" pointcut-ref="iampc" /> -- 이 부분 변경
		</aop:aspect>
	</aop:config>
</beans>

댓글