본문 바로가기
WEB/Spring

[Spring] Spring AOP + 예제 실습

by 댕꼬 2022. 4. 24.
728x90

Spring  AOP


AOP란

: AOP는 (Aspect Oriented Programming)의 약자로,  여러 객체에 공통으로 적용할 수 있는 기능을 분리해서 재사용성을 높여주는 프로그래밍 기법이다. AOP는 핵심기능과 공통기능의 구현을 분리함으로써 핵심기능을 구현한 코드의 수정 없이 공통 기능을 적용할 수 있게 만들어준다.

 

 

AOP방식

 

스프링이 제공하는 AOP방식은 프록시를 이용한 방식이다. 

스프링 AOP는 프록시객체를 자동으로 만들어 실제 객체의 기능을 실행하기 전.후에 공통기능을 호출한다.

AOP 주요용어

용어 의미
Advice 언제 공통기능을 핵심로직에 적용할 지를 정의
예) 메서드를 호출하기 전(언제)에 트랜잭션시작(공통기능)기능 적용
JointPoint Advice를 적용가능한 지점(메서드)
스프링은 프록시를 이용해서 AOP를 구현하기 때문에 메서드 호출에 대한 JointPoint만 지원
PointCut JointPoint의 부분집합으로, 실제 Advice가 적용되는 JointPoint를 나타냄
Weaving Advice를 핵심로직 코드에 적용하는 것
Aspect 공통기능 (클래스 기준) , Aspect = Advice(언제)+PointCut(메소드선정알고리즘)

 

 

Advice의 종류

종류 설명
Before 대상 객체의 메서드 호출 전 공통기능 실행
After Returning 대상 객체의 메서드가 Exception없이 정상적으로 실행된 이후 공통기능 실행
After Throwing 대상 객체의 메서드가 Exception이 발생했을 때 공통기능 실행
After Exception여부에 상관없이 항상실행 (finally와 비슷)
Around 메서드 실행 전,후 또는 Exception발생시점에 공통기능 실행
다양한 시점에 원하는 기능을 삽입할 수 있기 때문에 널리사용된다.

 

 

스프링 AOP 구현

 

- Aspect로 사용할 클래스에 @Aspect애노테이션을 붙인다.

- @Pointcut애노테이션으로 공통기능을 적용한 Pointcut을 정의한다.

- 공통기능을 구현한 메서드에 원하는 시점에 따라 Advice를 택하여 애노테이션을 적용한다.

 

 

 

Spring Framework기반 사용자정보 관리 애플리케이션


기존의 메서드를 수정하지 않고 메서드 동작전/후에 로그를 남기는 프로그램 작성 후 Junit으로 테스트

 

요구사항 

=>AOP를 이용하여 model의 하위패키지에서 선언된 클래스들의 메서드들이 호출될 때 마다 로그출력

- pom.xml에서 org.aspectj-version확인 후, aspectjweaver의존성 확인

- root-context파일에서 model과aop패키지 컴포넌트 스캔하고, 자동으로 프록시를 생성하도록 설정

- aop패키지에 LoggingAspect.java 구현

  (UserService의 insert()메서드와 selectAll()메서드가 성공적으로 수행됐을때 로그출력)

- src/test/java패키지에 AOPTest.java 파일을 추가하여 UserService를 가져와 로그 출력하기

 

 

root-context.java

<?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:context="http://www.springframework.org/schema/context"
	xmlns:aop="http://www.springframework.org/schema/aop"
	xmlns:mybatis="http://mybatis.org/schema/mybatis-spring"
	xsi:schemaLocation="http://mybatis.org/schema/mybatis-spring http://mybatis.org/schema/mybatis-spring-1.2.xsd
		http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
		http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">

	<!-- Root Context: defines shared resources visible to all other web components -->
	<!-- 어노테이션으로 설정한 aspect, model 패키지 하위 클래스를 빈으로 등록하기 위해 컴포넌트 스캔 범위 지정 -->
	<context:component-scan
		base-package="com.ssafy.hw.model,com.ssafy.hw.aop"></context:component-scan>

	<!-- xml 기반으로 자동으로 프록시를 생성하도록 설정한다. -->
	<aop:aspectj-autoproxy></aop:aspectj-autoproxy>

</beans>

 

LoggingAspect.java

package com.ssafy.hw.aop;

import java.util.Arrays;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

@Component
@Aspect
public class LoggingAspect {
	private static Logger logger = LoggerFactory.getLogger(LoggingAspect.class);
	
	/**
	 * 메서드 정상 결과 반환이후에 동작해야 한다.
	 * point cut의 구성은 아래와 같다.
	 * 리턴 타입: * 이므로 모든 리턴 타입에 대해 적용된다.
	 * 클래스명: com.ssafy.ws.model 패키지로 시작하고 .. 이므로 하위의 모든 경로, 클래스에 적용된다.
	 * 메서드명: insert 이므로 insert 라는 이름을 가진 메서드에 적용된다.
	 * 파라미터:.. 이므로 파라미터의 개수, 타입에 상관없이 적용된다.
	 * @param jp JoinPoint를 통해 joinpoint의 다양한 정보에 접근할 수 있다.
	 */
	// insert 메소드 이후 로그 출력
	@AfterReturning(value = "execution(* com.ssafy.hw.model.repo..*.insert(..))")
	public void logInsert(JoinPoint jp) {
		logger.debug("사용자 정보 삽입 성공!! 전달 파라미터:{}",Arrays.toString(jp.getArgs()));
	}
	// selectAll 메소드 이후 로그 출력
	@AfterReturning(value = "execution(* com.ssafy.hw.model.repo..*.selectAll())")
	public void logSelectAll() {
		logger.debug("사용자 정보 모두 찾기 성공!!");
	}
}

 

AopTest.java

package com.ssafy.hw;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;

import com.ssafy.hw.dto.User;
import com.ssafy.hw.model.service.UserService;


@RunWith(SpringRunner.class)
@ContextConfiguration(locations = "file:src/main/webapp/WEB-INF/spring/root-context.xml")
public class AopTest{	
	
	// 서비스 빈 가져오기, 어노테이션을 통해 설정파일을 불러왔으므로 어노테이션 기반 빈 사용
	@Autowired
	UserService service;

	@Test
	public void testAOP() {
		// LoggingAspect에서 작성한 로그가 제대로 출력되는지 확인
		service.insert(new User("kim", "1234", "김아무개" , "kim@naver.com", 20));
		service.selectAll();
	}
}

 

log4.xml (main과 test 2군데 있음에 주의)

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log4j:configuration PUBLIC "-//APACHE//DTD LOG4J 1.2//EN" "log4j.dtd">
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">

	<!-- Appenders -->
	<appender name="console" class="org.apache.log4j.ConsoleAppender">
		<param name="Target" value="System.out" />
		<layout class="org.apache.log4j.PatternLayout">
			<param name="ConversionPattern" value="%-5p: %c - %m%n" />
		</layout>
	</appender>
	
	<!-- Application Loggers -->
	<logger name="com.ssafy.hw">
		<level value="debug" />
	</logger>
	
	<!-- 3rdparty Loggers -->
	<logger name="org.springframework.core">
		<level value="info" />
	</logger>
	
	<logger name="org.springframework.beans">
		<level value="info" />
	</logger>
	
	<logger name="org.springframework.context">
		<level value="info" />
	</logger>

	<logger name="org.springframework.web">
		<level value="info" />
	</logger>

	<!-- Root Logger -->
	<root>
		<priority value="warn" />
		<appender-ref ref="console" />
	</root>
	
</log4j:configuration>

 

 

실행결과

insert()와 selectAll()를 실행시키니 콘솔창에 로그가 잘 출력되는것을 확인할 수 있었다.

 

 

 

프로젝트 구조

728x90

'WEB > Spring' 카테고리의 다른 글

[Spring] Spring FileUpload + 예제 실습  (0) 2022.04.24
[Spring] Spring Interceptor /Filter + 예제 실습  (0) 2022.04.24
[Spring] Spring MVC + 예제 실습  (0) 2022.04.23
[Spring] Spring DI + 예제 실습  (0) 2022.04.16
[Spring] Spring이란  (0) 2022.04.16

댓글