Spring AOP (관점지향프로그래밍)

최주영·2023년 7월 19일
0

spring

목록 보기
9/12

✅ AOP

  • 관점지향 프로그래밍으로, 횡단 관심사의 분리 를 허용해서 모듈성 을 증가시키는 것이 목적인 프로그래밍 패러다임

횡단 관심사

  • 여러 비즈니스 로직이 수행될 때 서로 중복되는 로직이 존재하는지에 대한 관심사

종단 관심사

  • 하나의 비즈니스 로직이 수행하기 위해 필요한 로직

위 사진에서 횡단 관심사는 (로킹, 보안, 트랜잭션) 과 같은 중복되는 관심사이며
종단관심사는 회원가입, 정보수정, 게시물관리 와 같은 비즈니스 로직이다

  • 결론 : AOP 는 횡단 관심사를 모듈로써 사용할 수 있게 하는 개념

  • AOP의 장점 : 객체들이 독립적으로 작용 -> 관점지향프로그래밍 적용시 더 (객체지향화 됨)


Filter vs Interceptor vs AOP

Filter : 일반적으로 스프링과 무관하게 전역적으로 처리해야 하는 작업들을 처리할때 사용

Interceptor : 로그인 체크, 권한체크, 프로그램 실행시간 계산작업 로그확인 등의 작업할 때 사용

AOP : 주로 '로깅', '트랜잭션', '에러 처리' 등 비즈니스단의 메소드 에서 조금 더 세밀하게 조정하고 싶을 때 사용 , Interceptor나 Filter와는 달리 메소드 전후의 지점에 자유롭게 설정이 가능


✅ AOP 용어
Aspect : 공통의 기능을 저장하는 클래스 생성

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

pointcut : 어드바이스를 적용할 타겟의 메서드를 선별하는 정규표현식
표현식은 execution으로 시작하고, 메서드의 Signature를 비교하는 방식

Join Point : Advice가 적용될 수 있는 위치


XML방식 및 어노테이션 방식에서 주로 사용하는 방법

  1. 공통의 관심사(기능)를 저장하는 클래스 생성 Aspect

  2. pointcut 설정 : 어느지점에서 실행할지를 결정하는 것 -> 어떤 메소드에서 실행할지(타겟) -> 표현식으로 써야함

  3. advisor : 실행할 시점을 설정 (위에서 설정한 타겟이 실행되기 전에 할껀지 후에할껀지 등등)
    ex) : before, after, Around(실행한 전후에), afterRetruning(리턴된후에), afterThrowing

  4. 실행할 메소드 : 공통의 로직을 구현하는 메소드, JoinPoint 클래스 제공

라이브러리 등록

  • xml 방식이든, 어노테이션 방식이든 라이브러리는 둘 다 등록해야함
<!-- pom.xml -->
		<!-- AspectJ -->
		<dependency>
			<groupId>org.aspectj</groupId>
			<artifactId>aspectjrt</artifactId>
			<version>${org.aspectj-version}</version>
		</dependency>	

xml 방식
예시

// aop-context.xml
<?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-4.3.xsd">
      
      <!-- xml 방식으로 aop 적용하기 -->
<bean id="loggerAspect" class="com.bs.spring.common.aop.LoggerAspect"/>
      	
      <aop:config>
      	  <aop:aspect ref="loggerAspect">
      	  	 <aop:pointcut expression="execution(* com.bs.spring..*(..))" id="logPointcut"/>
      	  	 execution(모든접근제어자  memo밑에있는 모든클래스안의 모든 메소드  매개변수개수 상관x
      	  	 <aop:before method="loggerBefore" pointcut-ref="logPointcut"/>
      	  	 <aop:after method="loggerAfter" pointcut-ref="logPointcut"/>
      	  </aop:aspect>
      </aop:config> 
      
      
      <!-- annotation 방식으로 AOP적용하기 -->
      <aop:aspectj-autoproxy/> 
      <!-- 이 태그가 존재해야, aop가 있다는것을 인식 -->
     <!--  6. springbeanconfiguration.xml 파일에서 <aop:aspectj-autoproxy/>선언해줌 -->
	<!-- 어노테이션방식에서만 작성하면됨 -->
      
</beans>

어노테이션 방식 순서

  1. aspect 역할을 할 클래스를 생성
  2. 클래스를 bean으로 등록 @Component (클래스선언부)
  3. 클래스를 aspect 등록 @Aspect (클래스선언부)
  4. @PointCut으로 포인트컷 설정
    -> (4-1) : 메서드 시그니처 패턴 execution
    -> (4-2) : 타입 시그니처 패턴 within
  5. @Befer, @After, @Around... 실행메소드에 설정 -> advisor 설정 (태그는 따로 안써도됨)
  6. springbeanconfiguration.xml 파일에서 <aop:aspectj-autoproxy/>선언해줌
    -> 어노테이션방식에서만 작성하면됨

어노테이션방식 예시 1

package com.bs.spring.common.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@Component  // 2. 클래스를 bean으로 등록 `@Component` 
@Aspect   // 3. 클래스를 aspect 등록
public class AnnoLoggerAspect { // 1. aspect 역할을 할 클래스를 생성
	
	// 4. @PointCut으로 포인트컷 설정
	@Pointcut("within(com.bs.spring.member..*)")
	public void loggerTest() {}
	
	// 5. @Befer, @After, @Around... 실행메소드에 설정  -> advisor 설정
	@Before("loggerTest()") // member 패키지 밑에 모든 함수들을 호출하기전에 발동
	public void loggerBefore(JoinPoint jp) {
		log.debug("======= anootation aop =======");
		Signature sig = jp.getSignature();
		log.debug(sig.getDeclaringTypeName()+" "+sig.getName());
		log.debug("==============================");
	}
	
	
	@Pointcut("execution(* com.bs.spring.memo..*(..))")
	public void memoLogger() {}
	
	
	@After("memoLogger()")
	public void afterLogger(JoinPoint jp) {
		log.debug("======= anootation aop after =======");
		Signature sig = jp.getSignature();
		log.debug(sig.getDeclaringTypeName()+" "+sig.getName());
		log.debug("=============aop after =================");
	}
	
	
	// Around -> 메소드 실행 전,후에 특정 로직을 실행할 때 사용
	// 실행하는 시간 체킹할때 많이 사용함
	// 반환형 : object, 매개변수타입 ProceedingJoinPoint
	@Around("execution(* com.bs.spring..*DaoImpl.*(..))")
	public Object daoLogger(ProceedingJoinPoint join) throws Throwable{
		// 전, 후 로직은 한번에 설정할 수 있다
		// 전, 후를 구분하는 구문은 ProceedingJoinPoint 클래스가 제공하는 proceed() 메소드를 이용
		// proceed() 메소드가 호출한 다음 라인은 후 처리, 그 전 라인은 전처리
		// proceed() 메소드는 Object를 반환한다
		
		// 메소드 실행시간 체크하기
		StopWatch stop = new StopWatch();
		stop.start();
		
		log.debug("================== around logger dao before =====================");
		log.debug("-------- 전처리 내용 구현 ---------");
		log.debug("==========================================================");
		Signature sig = join.getSignature();
		String classMethod = sig.getDeclaringType().getName()+sig.getClass();
		
		// 여기까지가 전처리 내용
		
		Object obj = join.proceed(); // proceed 메소드를 기준으로 전,후 나눔
		stop.stop();
		log.debug("================== around logger dao afetr =====================");
		log.debug("-------- 후처리 내용 구현 ---------");
		log.debug("실행시간 : "+stop.getTotalTimeMillis()+"ms");
		log.debug("==========================================================");
		
		return obj;  // 반환값은 따로 안쓰이며, 내부적으로 쓰임
	}
	
	
	// 에러가 발생했을때 발동
	@AfterThrowing(pointcut = "loggerTest()", throwing="e")
	public void afterThrowinglogger(JoinPoint jo, Throwable e) {
		log.debug("에러발생!!!");
		Signature sig = jo.getSignature();
		log.debug("{}",sig.getDeclaringTypeName()+" "+sig.getName());
		log.debug("{}",e.getMessage());
		StackTraceElement[] stacktrace = e.getStackTrace();
		for(StackTraceElement element : stacktrace) {
			log.debug("{}",element);
		}
	}
}

어노테이션방식 예시 2

package com.bs.spring.common.aop;

import javax.servlet.http.HttpSession;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;

import com.bs.spring.common.exception.AuthenticationException;
import com.bs.spring.member.Member;

@Component
@Aspect
public class AuthenticationCheckAop {
	
	// Aop로 차단하기
	@Before("execution(* com.bs.spring.memo..*(..))")
	public void checkcheck(JoinPoint jp) {
		// 로그인정보를 확인하여 아이디 확인하여 admin이면 통과! admin 아니면 막기!
		// spring이 제공하는 RequestContextHolder 클래스 이용해서 session 값을 가져올 수 있다
		HttpSession session = (HttpSession)RequestContextHolder.currentRequestAttributes()
		.resolveReference(RequestAttributes.REFERENCE_SESSION);
		
		Member loginMember = (Member)session.getAttribute("loginMember");
		if(loginMember==null || !loginMember.getUserId().equals("admin")) {
			throw new AuthenticationException("서비스 이용 권한이 부족합니다."); 
            // 사용자가 만든 AuthenticationException 예외 발생 
		}
		
	}
}
profile
우측 상단 햇님모양 클릭하셔서 무조건 야간모드로 봐주세요!!

1개의 댓글

comment-user-thumbnail
2023년 7월 19일

너무 좋은 글이네요. 공유해주셔서 감사합니다.

답글 달기