학원/수업 기록

AOP 관점 지향 프로그래밍

2022. 8. 25. 10:43

AOP (Aspect Oriented Programming) 

관점 지향 프로그래밍

 

: 프로그램이 실행되는 처리 과정을 바라보는 시점에 따라 분할하여 인식하고,

해당 시점에 추가적 구현을 하는 것.

 

 

 

1. AOP 용어 정리

 

Target

- 핵심 기능을 담고 있는 모듈로, 타겟은 부가기능을 부여할 대상이 된다.

 

Advice

- 관점 시점

- 타겟에 제공할 부가기능을 담고 있는 모듈.

 

Joinpoint

- 어드바이스가 적용될 수 있는 위치.

- 타겟 객체가 구현한 인터페이스의 모든 메소드는 조인 포인트가 된다.

- 진행중인 흐름. 현재 상태.

 

Pointcut

- 어드바이스를 적용할 타겟의 메소드를 선별하는 정규표현식

- 누구를 쪼개볼 건지.

- 포인트컷 표현식은 execution으로 시작하고 메소드의 Signature를 비교하는 방법을 주로 이용한다.

 

Aspect

- AOP의 기본 모듈.

- Aspect = Advice + Pointcut

- 싱글톤 형태의 객체로 존재한다.

 

Advisor

- Advisor = Advice + Pointcut

- 어드바이저는 Spring AOP에서만 사용되는 특별한 용어이다.

 

Weaving

- 포인트컷에 의해 결정된 타겟의 조인포인트에 부가기능(어드바이스)를 삽입하는 과정

- AOP가 핵심기능(타겟)의 코드에 영향을 주지 않으면서 필요한 부가기능(어드바이스)를 추가할 수 있도록 해주는 핵심적인 처리과정.

 

 

 

 

2. Spring AOP의 구현 방식

 

1. XML 기반의 POJO 클래스를 이용한 AOP 구현

- 부가기능을 제공하는 Advice 클래스를 작성한다.

- XML 설정 파일에 <aop:config>를 이용해서 애스펙트를 설정한다.

(즉, 어드바이스와 포인트컷을 설정함)

 

2. @Aspect 어노테이션을 이용한 AOP 구현

- @Aspect 어노테이션을 이용해서 부가기능을 제공하는 Aspect 클래스를 작성한다.

- 이 때 Aspect 클래스는 어드바이스를 구현하는 메서드와 포인트컷을 포함한다.

- XML 설정 파일에 <aop:aspectj-autoproxy />를 설정한다.

 

 

 

3. Advice의 종류

 

Around 어드바이스

: 타겟의 메서드가 호출되기 이전(before) 시점과 이후 (after) 시점에 모두 처리해야 할 필요가 잇는

부가기능을 정의한다.

-> Joinpoint 앞과 뒤에서 실행되는 Advice

 

Before 어드바이스

: 타겟의 메서드가 실행되기 이전(before) 시점에 처리해야 할 필요가 있는 부가기능을 정의한다.

-> Jointpoint 앞에서 실행되는 Advice

 

After Returning 어드바이스

타겟의 메서드가 정상적으로 실행된 이후(after) 시점에 처리해야 할 필요가 있는 부가기능을 정의한다.

-> Jointpoint 메서드 호출이 정상적으로 종료된 뒤에 실행되는 Advice

 

After Throwing 어드바이스

: 타겟의 메서드가 예외를 발생된 이후(after) 시점에 처리해야 할 필요가 있는 부가기능을 정의한다.

-> 예외가 던져 질때 실행되는 Advice

 

 

 

 

4. Advice를 정의하는 태그

 

<aop:before>

- 메서드 실행전에 적용되는 어드바이스를 정의한다.

 

<aop:after-returning>

- 메서드가 정상적으로 실행 된 후에 적용되는 어드바이스를 정의한다.

 

<aop:after-throwing>

- 메서드가 예외를 발생시킬 때 적용되는 어드바이스를 정의한다.

- try-catch 블록에서 catch 블록과 비슷하다.

 

<aop:after>

- 메서드가 정상적으로 실행되는지 또는 예외를 발생시키는지 여부에 상관없이 어드바이스를 정의한다.

- try-catch-finally에서 finally 블록과 비슷하다.

 

<aop:around>

- 메서드 호출 이전, 이후, 예외발생 등 모든 시점에 적용 가능한 어드바이스를 정의한다.

 

 

 

 

5. JointPoint 인터페이스

 

- JoinPoint는 Spring AOP 혹은 AspectJ에서 AOP가 적용되는 지점을 뜻한다.

- 해당 지점을 AspectJ에서 JoinPoint라는 인터페이스로 나타낸다.

 

 

 

 

6. JointPoint 메서드

 getArgs()   메서드 아규먼트를 반환한다.
 getThis()    프록시 객체를 반환한다.
 getTarget()   대상 객체를 반환한다.
 getSignature()  어드바이즈 또는 메서드의 설명(description)을 반환한다.
 toString()   어드바이즈 되는 메서드의 설명을 출력한다.

 

출처: https://shlee0882.tistory.com/206

 

 

 

 

 

메소드가 실행됐을 때 일어나는 흐름이 녹색 선이고 Join Point

메소드 끝났을 때 정상 실행 / 예외 발생.

이 Joinpoint를 Advice로 쪼개서 보는 그림.

모든 시점에서 동작하겠다는 게 Around (보통은 메소드 시작 전후)

PointCut : 적용 대상. 누구를 쪼개볼 건지

 

AOP 개념을 적용하면 핵심기능 코드 사이에 침투된 부가기능을 독립적인 Aspect로 구분해 낼수 있다.

구분된 부가기능 Aspect를 런타임 시에 필요한 위치에 동적으로 참여하게 할 수 있다.

 

 

 


 

 

 

servlet-context.xml 가면 AOP에 대한 내용 있음.

 

자바에서 쓰는 관점 지향 프로그램 - AspectJ

 

@Aspect : AOP용 클래스. AOP용 자바 라이브러리 안에 있음. 객체 생성 기능이 없어서 컴포넌트 추가로 달아주는 것.

@Component : 객체 생성. 외적인 용도의 클래스를 객체화 하려면 @컴포넌트 달아주면 스프링이 만들어준다.

@EnableAspectJAutoProxy : AspectJ 기능 활성화.

 

요즘은 xml을 굳이 쓰지 않고 추가적인 어노테이션만 기억하면 쓸 수 있음.

 

 


execution(접근권한 반환타입 범위)

반환타입 & 범위는 필수, 접근 권한은 옵션.

범위 형태를 잡기에는 execution이 좋다.

이해하기 쉬운 형태다.

 

- 범위 지정

execution : include 필터 (포함)

!execution : exclude 필터 (미포함)

* : 모든 것

*(..) : 모든 메소드

&& : and 필터

|| : or 필터

 

해석하면 -> 모든 반환타입으로 'com.spring.sample'의 모든 경로에서 'HomeController'를 찾고 그 안에 있는 모든 메소드가 적용대상(포인트컷)이다. 반환 타입 상관 없이 ~ 모든 메소드를 대상으로 한다고 설정하고 이름으로 testAOP를 붙여서 메소드 선언해준 것.

* 쓰면 like '%' 와 같음.

포인트컷을 메소드명(testAOP)으로 할당한 것.

joinPoint에 지점 전까지를 담고 있음?

해석하면 -> testAOP가 모든 시점을 만나면 그 아래를 실행하겠다는 것.

 

스프링이 도는 게 아니고 AspectJ라는 라이브러리가 따로 돌고 있기 때문에 수동으로 만들어줘야 한다.

그래서 리퀘스트를 통해 받아와야 하므로 리퀘스트 객체 취득을 위한 요청을 따로 별도로 해야 한다.

 

흐름을 끊어서 다른 데로 갈 수도 있다.

제 3의 선택지를 제공할 수 있다.

 

 

 


원래 AOPComponent

package com.spring.sample.common.component;

import javax.servlet.http.HttpServletRequest;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.ModelAndView;

@Aspect
@Component
@EnableAspectJAutoProxy
public class AOPComponent {
	//Pointcut -> 적용범위
	//@Pointcut(범위설정)
	/*
	 * 범위
	 * execution -> include필터
	 * !execution -> exclude필터
	 * * -> 모든것
	 * *(..) -> 모든 메소드
	 * .. -> 모든 경로
	 * && -> 필터 추가
	 */
	@Pointcut("execution(* com.spring.sample..HomeController.*(..))")
	public void testAOP() {} // pointcut의 이름과 같은 역할
	
	//ProceedingJoinPoint -> 대상 적용 이벤트 필터
	/*
	 * @Before -> 메소드 실행 전
	 * @After -> 메소드 실행 후
	 * @After-returning -> 메소드 정상실행 후
	 * @After-throwing -> 메소드 예외 발생 후
	 * @Around -> 모든 동작시점
	 */
	@Around("testAOP()")
	public ModelAndView testAOP(ProceedingJoinPoint joinPoint)
														throws Throwable {
		ModelAndView mav = new ModelAndView();
		
		//Request 객체 취득
		HttpServletRequest request
		= ((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest();
		
		mav = (ModelAndView) joinPoint.proceed(); //기존 이벤트 처리 행위를 이어서 진행
		
		System.out.println("------- testAOP 실행됨 ------");
		
		return mav;
	}
}

 

 

 

포인트컷 새로 추가.

포인트컷 여러 개 써도 문제 없다.

 

반환타입 다 줘도 상관 없음.

보통은 컨트롤러에만 건다.

메소드를 복수로 걸고 싶으면 .* 이렇게 모든 클래스라고 지정해줘도 됨.

@Pointcut("execution(* com.spring.sample..controller.ATController.*(..))")

우리는 모델앤뷰로 돌려주게 하고 있는데

ajax는 String을 반환하기 때문에 모델앤뷰로 구현이 안 돼서 문제가 된다.

메소드에 ajax나 action이 붙어 있어서 이걸 다 제외를 시켜줘야 한다.

얘네를 뺀 모든 메소드들이 로그인 처리 된다.

	@Pointcut("execution(* com.spring.sample..controller.ATController.*(..))"
			+ "")

마지막 따옴표 전에 엔터 치면 +"" 생김

	@Pointcut("execution(* com.spring.sample..controller.ATController.*(..))"
			+ "&& !execution(* com.spring.sample..controller.ATController.*Ajax(..))"
			+ "&& !execution(* com.spring.sample..controller.ATController.*Action(..))")

 

근데 list도 detail도 로그인 없이 쓸 수 있다.(볼 수 있다)

action은 AOP 따로 만들어야 해서 빼도

어쩌구

그러면 제외해줄 것이 많아지니까 차라리 주소 치고 들어가는 insert와 update를 빼주자.

insert이면서 update인 메소드는 없으니 or을 씀. delete는 action에서 처리함.

	@Pointcut("execution(* com.spring.sample..controller.ATController.*Insert(..))"
			+ "|| execution(* com.spring.sample..controller.ATController.*Update(..))")

 

&&로 처리할 거 다 처리하고 ||로 제외할 거만 해야 한다.

섞어쓰면 관리 안 된다. 차라리 포인트컷 여러개 만드는 게 좋음.

 

메소드명은 인자가 다르기 때문에 똑같이 써도 됨.

예외 발생할 수 있으니 안전장치 걸어주고

	@Pointcut("execution(* com.spring.sample..controller.ATController.*Insert(..))"
			+ "|| execution(* com.spring.sample..controller.ATController.*Update(..))")
	public void atAOP() {}
	
	@Around("atAOP()")
	public ModelAndView atAOP(ProceedingJoinPoint joinPoint) throws Throwable {
		ModelAndView mav = new ModelAndView();
		
		return mav;
	}

 

세션을 가져와야 로그인 했는지 아닌지 확인할 수 있으니

AOPComponent에서 리퀘스트 객체 취득 코드 복사해서 가져오고

세션 가져오기

	@Pointcut("execution(* com.spring.sample..controller.ATController.*Insert(..))"
			+ "|| execution(* com.spring.sample..controller.ATController.*Delete(..))")
	public void atAOP() {}
	
	@Around("atAOP()")
	public ModelAndView atAOP(ProceedingJoinPoint joinPoint) throws Throwable {
		ModelAndView mav = new ModelAndView();
		
		//Request 객체 취득
		HttpServletRequest request
		= ((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest();
		
		HttpSession session = request.getSession();
		
		return mav;
	}

 

 

위에서 로그인할 때 쓰던 세션 필터 복사해와서 넣고

AOPComponent에서 기존 이벤트 처리 행위 이어서 진행하는 코드 복사해와서 넣기.

	@Pointcut("execution(* com.spring.sample..controller.ATController.*Insert(..))"
			+ "|| execution(* com.spring.sample..controller.ATController.*Delete(..))")
	public void atAOP() {}
	
	@Around("atAOP()")
	public ModelAndView atAOP(ProceedingJoinPoint joinPoint) throws Throwable {
		ModelAndView mav = new ModelAndView();
		
		//Request 객체 취득
		HttpServletRequest request
		= ((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest();
		
		HttpSession session = request.getSession();
		
		//세션 필터 추가
		if(session.getAttribute("sMemNm") != null
				&& session.getAttribute("sMemNm") != "") { // 로그인 상태
			mav = (ModelAndView) joinPoint.proceed(); //기존 이벤트 처리 행위를 이어서 진행
		} else { // 비 로그인 상태
			mav.setViewName("redirect:testALogin");
		}
		
		return mav;
	}

 

 

ATController

insert에서 세션과 관련된 거 싹 빠지면서 간략해진다.

	@RequestMapping(value = "/ATInsert")
	public ModelAndView aTInsert(ModelAndView mav) {
		mav.setViewName("testa/T/insert");
		
		return mav;
	}

 

ATInsert 주소 치고 들어가면 로그인 하라고 로그인 페이지 testALogin로 이동한다.

 

분명 세션과 관련된 처리가 없는데 세션과 관련된 처리를 한다.

내가 진행중이던 상황에 개입을 해서 별도 분할해서 쓰는 게 AOP의 핵심.

일반적으로 권한 체크, 로그인 체크 때 쓴다.

 

세션 적용할 것들이 더 필요하면 범위를 수정해주면 된다.

 

일일이 하기 힘드니까 한 번에 동시에 처리하려고 AOP 이용한다.