Spring AOP 테스트 및 커스텀태그 적용해보기

2021. 10. 14. 16:17개인노트

반응형

오늘은 Spring boot에서 AOP사용 커스텀 태그를 사용하여 적용시키는 부분을 공부해봤다.

이유는…

이전 프로젝트를 진행하던 당시에 특정 프로세스가 수행될때 로그처럼 DB 접근정보를 남겨달라는 요구사항이 있었는데(Insert).. 당시에는 AOP 제대로 모르던 시기라서 해당 서비스Impl 로직 앞뒤로 직접 추가하여 적용시켰다…

 

그게 이후에 마음에 걸려 새로이 공부하게되었다.

일단 sts툴에서 아래와같이 프로젝트를 생성하였다.

springboot 생성 설정
기본 maven 추가사항

위와같이 최초 설정 후 aop사용을 위해 아래를 pom.xml에 추가했다.

<!-- AspectJ 디펜던시 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-aop</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

그리고 아래와 같이 컨트롤러를 간단하게 생성진행!

@RestController
public class SampleController {

	@GetMapping(value = "/test1")
	public String test1() {
		
		return "test1";
	}
	@MyCustom
	@RequestMapping(value = "/test2")
	public String test2() {
		
		return "test2";
	}
}

하나는 GetMapping일때 AOP가 적용되도록 하였고, 나머지 하나는 MyCustom이라는 태그가 존재할때 aop를 적용시켜서 log를 찍게 셋팅하였다.

customAnnotation패키지에 MyCustom 어노테이션 생성

package com.test.sampleAOP.customAnnotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyCustom{
	
}

 

그리고 AOP를 적용시킬 SampleAdvice 클래스를 생성하였다.

package com.test.sampleAOP.advice;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class SampleAdvice {
	

	
	private static final Logger logger = LoggerFactory.getLogger(SampleAdvice.class);
	
	/**
	 *  	@GetMapping import org.springframework.web.bind.annotation.GetMapping
	 *  	getMapping일때 적용시키도록 포인트컷 설정(어디에)
	 */
	@Pointcut("@annotation(org.springframework.web.bind.annotation.GetMapping)")
	public void GetMapping() {
		
	}
	/*
	 * @Pointcut("@annotation(com.test.sampleAOP.customAnnotation.MyCustom)")
	 * public void CustomAnnotation() {
	 * 
	 * }
	 */
	
	//@Before("GetMapping()")
	@Before("@annotation(com.test.sampleAOP.customAnnotation.MyCustom)")
	public void before(JoinPoint joinPoint) {
		logger.info("=====================AspectJ TEST  : Before Logging Start=====================");
		logger.info("=====================AspectJ TEST  : Before Logging End=====================");
	}
	
    @AfterReturning(pointcut = "GetMapping()", returning = "result")
    //@AfterReturning("CustomAnnotation()")
    public void AfterReturning(JoinPoint joinPoint, Object result) {
        logger.info("=====================AspectJ TEST  : AfterReturning Logging Start=====================");
        logger.info("=====================AspectJ TEST  : AfterReturning Logging END=====================");
    }
    
    @After("GetMapping()")
    //@After("CustomAnnotation()")
    public void after(JoinPoint joinPoint) {
    	logger.info("=====================AspectJ TEST  : After Logging Start=====================");
    	logger.info("=====================AspectJ TEST  : After Logging END=====================");
    }
    @Around("GetMapping()")
    //@Around("CustomAnnotation()")
    public Object Around(ProceedingJoinPoint joinPoint) throws Throwable {
        logger.info("=====================AspectJ TEST  : Around Logging Start=====================");
        try {
            Object result = joinPoint.proceed();
            logger.info("=====================AspectJ TEST  : Around Logging END=====================");
            return result;
        }catch (Exception e) {
            logger.error("=====================AspectJ Around Exception=====================");
            logger.error(e.toString());
            return null;
        }
    }

}

여기서 주의할 점은 Around를 사용할경우 Object를 return 해야한다는 것이다.

joinPoint.proceed(); << 이 부분에서 프록시형태로 진행되기때문에..! 자세한 사항은 다른 블로그들에 잘 설명이 되어있다!

##실행된 로그##

test1 호출화면
test2 호출화면

##추가사항##

수행시간이 얼마나되는지 테스트가 필요하다거나 할때 심플하게 AOP를 적용하는 경우

LogExecuteTime 커스텀태그를 아래와 같이 생성한 뒤

package com.test.sampleAOP.customAnnotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LogExecuteTime {

}

Controller에도 해당부분을 추가

	@LogExecuteTime
	@RequestMapping(value = "/test3")
	public String test3() {
		return "test3";
	}

그리고 Advice에서 사용할 부분도 선언해주면..!

    @Around("@annotation(com.test.sampleAOP.customAnnotation.LogExecuteTime)")
    public Object AroundForTimeLog(ProceedingJoinPoint joinPoint) throws Throwable {
        logger.info("=====================AspectJ TEST  : AroundForTimeLog Logging Start=====================");
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        Object result = joinPoint.proceed();
        logger.info("=====================AspectJ TEST  : AroundForTimeLog Logging END=====================");
        stopWatch.stop();
        logger.info(stopWatch.prettyPrint());
        return result;
    }

시간이 얼마나 걸렸는지 심플하게 볼 수 있다..!

stopwatch 관련 수행이미지

 

포인트컷을 사용하는방법은 아래처럼 다양한 종류가 존재하는데 원하는 방식으로 사용하거나 프로젝트에서 사용하자고 명시된 방법에 맞춰서 적용시키는게 베스트방법 일듯하다.


지난 번에 이어서 Advice가 어떤 JoinPoint에 사용될 것인지를 지정하는 PointCut 표현식을 정리하겠습니다.

포인트컷에는 다양한 명시자를 이용할 수 있습니다.

execution Advice를 적용할 메서드를 명시할 때 사용합니다.
within 특정 타입에 속하는 메서드를 JoinPoint로 설정되도록 명시할 때 사용합니다.
bean 스프링 버전 2.5 버전부터 지원하기 시작했으며, 스프링 빈을 이용하여 JoinPoint를 설정합니다.

execution 명시자

execution([수식어] 리턴타입 [클래스이름].이름(파라미터)

  • 수식어 : public, private 등 수식어를 명시합니다. (생략 가능)
  • 리턴타입 : 리턴 타입을 명시합니다.
  • 클래스이름 및 이름 : 클래스이름과 메서드 이름을 명시합니다. (클래스 이름은 풀 패키지명으로 명시해야합니다. 생략도 가능)
  • 파라미터 : 메서드의 파라미터를 명시합니다.
  • " * " : 모든 값을 표현합니다.
  • " .. " : 0개 이상을 의미합니다.

Ex)

execution(public Integer com.edu.aop.*.*(*))

 - com.edu.aop 패키지에 속해있고, 파라미터가 1개인 모든 메서드

execution(* com.edu..*.get*(..))

 - com.edu 패키지 및 하위 패키지에 속해있고, 이름이 get으로 시작하는 파라미터가 0개 이상인 모든 메서드 

execution(* com.edu.aop..*Service.*(..))

 - com.edu.aop 패키지 및 하위 패키지에 속해있고, 이름이 Service르 끝나는 인터페이스의 파라미터가 0개 이상인 모든 메서드

execution(* com.edu.aop.BoardService.*(..))

 - com.edu.aop.BoardService 인터페이스에 속한 파마리터가 0개 이상인 모든 메서드

execution(* some*(*, *))

 - 메서드 이름이 some으로 시작하고 파라미터가 2개인 모든 메서드

within 명시자

Ex)

within(com.edu.aop.SomeService)

 - com.edu.aop.SomeService 인터페이스의 모든 메서드

 

within(com.edu.aop.*)

 - com.edu.aop 패키지의 모든 메서드

within(com.edu.aop..*)

 - com.edu.aop 패키지 및 하위 패키지의 모든 메서드

bean 명시자

Ex)

bean(someBean)

 - 이름이 someBean인 빈의 모든 메서드

bean(some*)

 - 빈의 이름이 some으로 시작하는 빈의 모든 메서드

 

참고사이트

https://icarus8050.tistory.com/8

 

Spring AOP PointCut 표현식 정리

 지난 번에 이어서 Advice가 어떤 JoinPoint에 사용될 것인지를 지정하는 PointCut 표현식을 정리하겠습니다. 포인트컷에는 다양한 명시자를 이용할 수 있습니다. execution Advice를 적용할 메서드를 명시

icarus8050.tistory.com

 

반응형