본문으로 바로가기

AOP 적용

category 웹/Spring 2019. 5. 4. 15:58

우선 구성은 아래와 같이 합니다.

aop 역할을 할 LogAdvice 클래스와 aop를 적용할 서비스인 SampleServiceImpl를 구현합니다.

 

JUnit 테스를 적용하기 위해 테스트 코드 또한 작성합니다.

 

pom.xml을 수정을 해야 합니다. 아래와 같이 수정합니다.

 

5.0.7 자바 버전 1.8 aspectj 1.9.2로 합니다. 1.9.0은 aspectjweaver가 작동을 안 합니다.

로그는 1.2.17로 해줍니다.

 

junit은 4.12 버전으로 추가해줍니다.

 

마지막으로 위에 처럼 추가를 해줍니다.

 

서비스에 적용을 할 것이기 때문에 service 코드를 작성합시다.

 

간단하게 서비스 기능은 두 문자열을 정수로 변환하여 더해주는 기능입니다.

 

SampleService 인터페이스와 SampleServiceImpl을 아래와 같이 작성합니다.

 

package org.zerock.service;

public interface SampleService {
	public Integer doAdd(String str1,String str2) throws Exception;
}
package org.zerock.service;

import org.springframework.stereotype.Service;

@Service
public class SampleServiceImpl implements SampleService {

	@Override
	public Integer doAdd(String str1, String str2) throws Exception {
		// TODO Auto-generated method stub
		
		return Integer.parseInt(str1) + Integer.parseInt(str2);
	}
	
}

@Service 어노테이션으로 스프링에 서비스 bean을 등록해줍니다.

 

이제 AOP 기능(로그 기록, 에러, 파라미터 정보 출력 등)을 구현해 봅시다.

 

AOP를 구현하기 앞서서 먼저 rootconfig.xml에 빈을 등록해줍니다.

 

우선 네임스페이스에 들어가서 

aop와 context를 등록해줍니다.

 

그다음 rootconfig.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"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://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 -->
	<context:annotation-config></context:annotation-config>
		<!-- service 스캔 -->
		<context:component-scan base-package="org.zerock.service"></context:component-scan>
		<!-- aop 스캔 -->
		<context:component-scan base-package="org.zerock.aop"></context:component-scan>
		<!-- 프록시 생성 -->
		<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>

 

즉 scan을 service와 aop 패키지를 해줍니다. 마지막으로 자동으로 프락시를 생성해줍니다. 

 

앞서 말했듯이 AOP를 구현할 때 프락시가 크게 동작한다고 했습니다.

 

Advice를 작성해야 합니다. 아래와 같이 LogAdvice 클래스를 작성해 줍니다.

 

package org.zerock.aop;

import java.util.Arrays;

import org.aspectj.lang.ProceedingJoinPoint;
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.springframework.stereotype.Component;

import lombok.extern.log4j.Log4j;

@Aspect
@Log4j
@Component
public class LogAdvice {
	//doAdd 메서드 실행 전 파라미터와 함께 로그를 기록해준다.
	@Before("execution(* org.zerock.service.SampleService*.doAdd(String,String)) && args(str1,str2)")
	public void logBeforeWithParam(String str1,String str2) {
		log.info("==================");
		log.info("str1: "+str1);
		log.info("str2: "+str2);
	}
	//예외 발생 후 로그를 기록 해준다.
	@AfterThrowing(pointcut = "execution(* org.zerock.service.SampleService*.*(..))",throwing="exception")
	public void logException(Exception exception) {
		log.info("Exception....!!!");
		log.info("exception: "+exception);
	}
	//Around와 ProceedJointPoint를 이용하여 메서드를 직접 실행하고 시간을 측정한다. 좀더 구체적인 AOP 기능 구현
	@Around("execution(* org.zerock.service.SampleService*.*(..))")
	public Object logTime(ProceedingJoinPoint pjp) {
		long start = System.currentTimeMillis();
		
		log.info("Target: "+pjp.getTarget());
		log.info("Param: "+Arrays.toString(pjp.getArgs()));
		
		//invoke method
		Object result =null;
		try {
			result = pjp.proceed();
		}catch(Throwable e) {
			e.printStackTrace();
		}
		
		long end = System.currentTimeMillis();
		log.info("Time: "+ (end-start));
		
		return result;
	}
	
	
	
}

 

주석 처리를 하였기 때문에 쉽게 이해할 수 있을 겁니다.

 

우선 Adivce보다 큰 개념인 추상화한 Aspect을 어노테이션으로 적용시켜주고 

 

pointcut을 execution으로 표현식을 적용합니다. 즉 맨 앞 *은 접근 제한자 맨 마지막 * 두 개는 각각 클래스 이름과 메서

 

드 이름을 뜻합니다.

 

이제 테스트를 하기 위해서 테스트 코드를 작성합시다.

 

package org.zerock.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import lombok.Setter;
import lombok.extern.log4j.Log4j;

import org.junit.Test;
import org.junit.runner.RunWith;


@RunWith(SpringJUnit4ClassRunner.class)
@Log4j
@ContextConfiguration({"file:src/main/webapp/WEB-INF/spring/root-context.xml"})
public class SampleServiceTests {
	
	@Setter(onMethod_=@Autowired)
	private SampleService service;
	
	@Test
	public void testClass() {
		log.info(service);
		log.info(service.getClass().getName());
	}
	
	@Test
	public void testAdd() throws Exception{
		log.info(service.doAdd("123", "456"));
	}
	
	/*@Test
	public void testAddError() throws Exception{
		log.info(service.doAdd("123", "ABC"));
	}*/
}

 

SampleService를 자동 주입해주고 이를 이용합니다.

 

우선 처음 testClass() 메서드는 프락시가 정상적으로 작동을 하는지를 보기 위함입니다. 실행 시 

 

위처럼 Proxy22가 떠야 정상입니다.

 

두 번째 메서드인 testAdd()는 각 인자를 넘겨 doAdd를 실행하는데 앞서 LogAdvice를 통해 

 

로그 기록과 파라미터 기록 에러 처리 등을 수행할 겁니다. 

 

LogAdvice에서 logTime이 먼저 기록이 되고 logBeforeWithParam이 적용될 겁니다.

 

따라서 아래와 같이 실행이 됩니다.

 

즉 Around와 ProceedingJoinPoint를 통해 Target과 Param을 찍어주고

 

doAdd 메서드가 실행되기 전 logBeforeWithParam이 실행되어 ========로그와 각 파라미터를 출력하며

 

마지막으로 LogAdvice에서 실행시간을 측정해준 것이 출력이 됩니다. 

 

어렵지만 신기하기도 합니다. 분명 AOP 기능 구현을 따로 작성하고 핵심 비즈니스인 두 문자열을 합치는 기능만 

 

구현했음에도 불구하고 로그 기록 등 결합돼서 실행이 됩니다. 

 

이것이 바로 AOP 기능의 꽃이라고 할 수 있겠습니다. 

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

Spring 트랜잭션 설정  (0) 2019.05.06
Spring 트랜잭션 & 데이터베이스 설계  (0) 2019.05.06
AOP 개념  (0) 2019.05.03
REST AJAX 댓글 등록 처리  (2) 2019.04.28
REST 방식  (0) 2019.03.31