[Spring] AOP란 무엇인가 - log4j2 적용

H_dev·2022년 4월 29일
1

😎 이번 글에서는 AOP에 대해 정리해보도록 한다.

스프링의 주요 특징인 AOP의 개념을 살펴보자


- AOP란?

AOP는 Aspect Oriented Programming 약자로 관점 지향 프로그래밍이라고 한다. 무슨 말인지 솔직히 단번에 이해하기 힘들다.

먼저 위 사진에서 색으로 표시된 부분은 클래스A,B,C 에서 반복, 중복되는 코드들이다.

주황색을 예로 들면, 주황색은 같은 기능을 하는 코드가 A,B,C 모두 들어가있다. 이때 주황색 코드의 기능을 수정하고자 한다면?
A에 가서 수정하고 B에 가서 수정하고 C에가서 수정하는 번거로운 일이 발생한다. 이렇게 되면 유지보수가 쉽지 않게되고, 해결해주는 것이 바로 AOP 이다.

이렇게 반복되는 부분들이 흩어져 있는 것을 흩어진 관심사(Crosscutting Concerns) 라고 한다.
AOP는 흩어진 것을, 사진 아래 표시된 Aspect를 통해서 공통 로직이나 부가기능들을 분리하는 모듈화를 하고 어느 곳에서 사용할지 정의하여 관리할 수 있다.

즉, 핵심 비즈니스 로직에서 분리하여 기존코드는 변경하지 않고 필요한 부분마다 재사용할 수 있게 한다는 점이다!!


- 주요 용어를 알아보자 👀

  • Aspect : 흩어진 관심사를 모듈화 한 것

  • Advice : 실질적으로 어떤 일을 해야할지를 정의한 것

  • Pointcut : 어디에(A class의 ~~메서드) 적용해야 하는지 정의한 것

  • Target : Aspect의 적용 대상을 정의한 것

  • JointPoint : Advice가 적용될 시점을 정의한 것(메서드 진입 지점, 생성자 호출 시점 등 다양한 시점)


😎 해결과제 (코드)

  1. Spring 샘플 프로젝트를 생성 후 post 방식으로 파라미터를 전달받는 api를 3개 제작
  2. 각 api는 전달받은 파라미터 중 하나를 응답
  3. AOP를 이용하여 각 API 호출 시 입력된 파라미터와 출력된 파라미터를 각각 input, output으로 파일 분리하여 로그 기록

먼저, log4j2.xml 파일을 설정에 대한 내용

log4j2.xml

  • <configuration> 내에 설정 내용을 정의한다.

  • <Properties>에는 설정파일에서 사용할 속성들을 정의한다.

  • <Appenders> 에는 로그가 실제로 기록되는 방법(형태)과 file 생성에 대한 정책을 정의한다.

  • <Loggers>에는 어떤 패키지의 로그를 어디에 기록할지 정의한다.

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status = "INFO">
	<Properties>
		<Property name = "inputFileName">input</Property>
		<Property name = "outputFileName">output</Property>
		<Property name = "consoleLayout">[%d{yyyy-MM-dd HH:mm:ss}] [%level] [%t] [%logger{36}(%L)] - %m %n</Property>
		<Property name = "fileLayout">[%d{yyyy-MM-dd HH:mm:ss}] [%c{1}] [%L] [%p] %m %n</Property>
	</Properties>
	
	<Appenders>
	
		<!-- console 패턴정의 -->
		<Console name = "console" target = "SYSTEM_OUT">
			<PatternLayout pattern = "${consoleLayout}"></PatternLayout>
		</Console>
		
		
		<!-- input 로그를 기록하기 위한 파일 패턴 및 정책 정의 -->
		<RollingFile name = "inputFile" fileName = "logs/${inputFileName}.log" filePattern = "logs/${inputFileName}.%d{yyyy-MM-dd-hh}.log">
			<PatternLayout pattern = "${fileLayout}"></PatternLayout>
            <Policies>
                <TimeBasedTriggeringPolicy modulate = "true" interval = "24"></TimeBasedTriggeringPolicy><!-- 시간별 로그 파일 생성-->
            </Policies>
            <DefaultRolloverStrategy max = "10" fileIndex = "min"></DefaultRolloverStrategy>
        </RollingFile>
        
        
        <!-- output 로그를 기록하기 위한 파일 패턴 및 정책 정의 -->
        <RollingFile name = "outputFile" fileName = "logs/${outputFileName}.log" filePattern = "logs/${outputFileName}.%d{yyyy-MM-dd-hh}.log">
			<PatternLayout pattern = "${fileLayout}"></PatternLayout>
            <Policies>
                <TimeBasedTriggeringPolicy modulate = "true" interval = "24"></TimeBasedTriggeringPolicy><!-- 시간별 로그 파일 생성-->
            </Policies>
            <DefaultRolloverStrategy max = "10" fileIndex = "min"></DefaultRolloverStrategy>
        </RollingFile>
        
	</Appenders>
	
	<Loggers>
		<root level="error" additivity="false">
			<AppenderRef ref="console"></AppenderRef>
		</root>
	
	    <!-- 스프링 프레임워크에서 찍는건 info / 콘솔만 설정 -->
        <logger name="org.springframework" level="info" additivity="false">
            <AppenderRef ref="console" />
        </logger>
        
        <!-- input aspect 클래스 분리 / 콘솔과 input file 기록 -->
        <logger name = "com.example.demo.aop.InputLogAspect" level = "debug" additivity = "false">
            <AppenderRef ref = "console"></AppenderRef>
            <AppenderRef ref = "inputFile"></AppenderRef>
        </logger>
        
        <!-- output aspect 클래스 분리 / 콘솔과 output file 기록 -->
        <logger name = "com.example.demo.aop.OutputLogAspect" level = "debug" additivity = "false">
            <AppenderRef ref = "console"></AppenderRef>
            <AppenderRef ref = "outputFile"></AppenderRef>
        </logger>
	</Loggers>


</Configuration>

InputLogAspect.java

@Log4j2
@Component
@Aspect
public class InputLogAspect {
	private static final Logger logger = LogManager.getLogger(InputLogAspect.class);
	// api 가 동작하기 이전에 AOP 동작
	@Before("execution(* com.example..*.RestApiController.*(..))")
	public void before(JoinPoint jp) {
		// 해당 api 이름 가져오기 위해서 (메소드 이름)
		MethodSignature methodSignature = (MethodSignature) jp.getSignature();
		Method method = methodSignature.getMethod();
		Object[] params = jp.getArgs();
		logger.debug("<<<< input >>>>");
		logger.debug(method.getName() + " input pram - " + params[0]);
		logger.debug("<<<< end input >>>>\n");
	}
}
  • @Before 어노테이션을 통해 pointcut을 지정하고 메서드 정보와, 파라미터를 받아와 로그를 기록한다.

OutputLogAspect.java

@Log4j2
@Component
@Aspect
public class OutputLogAspect {
	private static final Logger logger = LogManager.getLogger(OutputLogAspect.class);
	// api 가 성공적으로 동작한 후에 AOP 동작
	@AfterReturning(pointcut = "execution(* com.example..*.RestApiController.*(..))", returning = "result")
	public void afterReturning(JoinPoint jp, Object result) {
		// 해당 api 이름 가져오기 위해서 (메소드 이름)
		MethodSignature methodSignature = (MethodSignature) jp.getSignature();
		Method method = methodSignature.getMethod();
		logger.debug("<<<< output >>>>");
		logger.debug(method.getName() + " output pram - " + result);
		logger.debug("<<<< end output >>>>\n");
		
	}
}
  • @AfterRetruning 어노테이션을 통해 pointcut을 지정하고 api가 성공적으로 동작한 뒤에 응답한 결과를 받아와 로그를 기록한다.

RestApiController.java

@RestController
public class RestApiController {
	
	@PostMapping("/api/res1")
	public Object res1(@RequestBody List<Object> params) { // json object를 list로
		return params.get(0);
	}
	
	@PostMapping("/api/res2")
	public Object res2(@RequestBody List<Object> params) { // json object를 list로
		return params.get(1);
	}
	
	@PostMapping("/api/res3")
	public Object res3(@RequestBody List<Object> params) { // json object를 list로
		return params.get(2);
	}

}
  • json 오브젝트들을 리스트로 매핑하여 파라미터를 한꺼번에 전달받는다.

  • 그 중 아무 값이나 응답해준다.

  • service나 dto 같은 부분은 간단하게 AOP를 테스트 하기 위해 제외하였다.


코드가 정상적으로 실행된다면?
위와 같은 상황에서는 어떤 파라미터들이 담긴 post요청이 들어오게 되면 api가 호출되어 동작하기 전에, 사전작업인 Before AOP가 동작하여 넘어온 파라미터들을 로그파일에 기록하게 된다.


그 후, api에서 어떤 파라미터를 응답해 준 뒤, AfterReturning AOP가 동작하여 응답한 파라미터를 로그파일에 기록해야한다.

결과를 살펴보자!! 테스트는 post man을 사용하였다.

🎈 결과

postman

아래와 같이 API를 호출하고 Body에 전달한 파라미터를 넣어준다.

이 중 하나를 응답받아야 한다. 응답값은 아래와 같다.


그리고 동시에 console와 log file (input, output)에 찍혔는지 확인한다.

console

log file (input, output)

성공적으로 AOP가 동작하였다!!!

profile
성장 개발일지

0개의 댓글