본문 바로가기
Spring공부/2-AOP와 트랜잭션

AOP

by 으노으뇨 2021. 10. 11.
728x90
반응형
SMALL

기본개요

AOP란? Asepect_Orented Programming  즉, 관점 지향 프로그래밍이라는 의미로 번역되는데, 객체지향에서 특정 비즈니스 로직에 걸림돌이 되는 공통 로직을 제거할 수 있는 방법을 제공한다.

AOP를 적용하면 기존의 코드에 첨삭 없이 메서드의 호출 이전 혹은 이후에 필요한 로직을 수행하는 방법을 제공한다.

트랜잭션 작업은 DB를 이용할때 두 개 이상의 작업이 같이 영향을 받는 경우에 필요하다.

과거에는 코드내에 개발자가 직접 이를 지정하고 사용했다면, 스프링에서는 XML이나 어노테이션만으로 트랜잭션이 처리된 결과를 만들어 낼 수 있다.


그런데 이 관점이라는 용어가 현실적으로 와닿지 않기 때문에 어렵게 느껴지고있다. 

개발자들에겐 관심사(Concern)이라는 말로 통용된다. 

-파라미터가 올바르게 들어왔을까?

-이 작업을 하는 사용자가 적절한 권한을 가진 사용자인가?

-이 작업에서 발생할 수 있는 모든 예외는 어떻게 처리해야하는가?

이런 고민들이 코드를 짜기 위해 필요한 고민들이라고 생각한다.

예를들어

"두개의 숫자를 나누고 싶다."

이것을 구현하기위 해서는 "핵심 로직은" 두 개의 숫자를 누는 것이지만,

그런데 이 기능이 정상작동할때 체크사항들(0으로 나누고있나? 값 타입은 어떻게 가져오고 내보내는가? 등)

등을 나타나게 해주는 기능을 구현하는 것이다. ("주변로직")

개발자가 작성한 코드에 따로 컴파일 혹은 실행시점에 결합시킨다. 실제 실행은 결합된 상태의 코드가 실행되기 때문에 

개발자들은 핵심 비즈니스 로직에만 근거해서 코드를 작성, 나머지는 어떤 관심사들과 결합할 것인지 설정하는 것이다.

예를들어 , 잘못된 파라미터가 들어와서 예외가 발생하는 상황을 기존 코드의 수정없이도 제어할 수 있다.


AOP 용어들

기존의 코드들을 수정하지 않고 원하는 기능들과 결합할 수 잇는 패러다임이다. 

개발자의 입장에서 AOP를 적용한다는 것은 기존의 코드를 수정하지 않고도 원하는 관심사들을 엮을 수 있다는 점이다.

  • Aspect : 위에서 설명한 흩어진 관심사를 모듈화 한 것. 주로 부가기능을 모듈화함.
  • Target : Aspect를 적용하는 곳 (클래스, 메서드 .. )
  • Advice : 실질적으로 어떤 일을 해야할 지에 대한 것, 실질적인 부가기능을 담은 구현체
  • JointPoint : Advice가 적용될 위치, 끼어들 수 있는 지점. 메서드 진입 지점, 생성자 호출 시점, 필드에서 값을 꺼내올 때 등 다양한 시점에 적용가능
  • PointCut : JointPoint의 상세한 스펙을 정의한 것. 'A란 메서드의 진입 시점에 호출할 것'과 같이 더욱 구체적으로 Advice가 실행될 지점을 정할 수 있음

실습

AOP기능은 주로 일반적으로 JavaAPI를 이용하는 클래스들에 적용한다. 

실습으로는 서비스 계층에서 AOP를 적용하겠다.

AOP의 예제로는 서비스 계층의 메서드 호출 시 모든 파라미터들을 로그로 기록,

메서드를 실행 시간 기록

이렇게 2개로 나누어 하겠다.

		<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
		<dependency>
			<groupId>org.aspectj</groupId>
			<artifactId>aspectjweaver</artifactId>
			<version>${org.aspectj-version}</version>
		</dependency>

pom.xml 에 의존성을 주입하자.

그리고 서비스 계층을 설계하겠다.

SampleSerive.java 인터페이스와

SampleSeriveImpl 자바 파일을 만들자

package org.study.service;

public interface SampleService {
	public Integer doAdd(String str1, String str2) throws Exception;
}

예제로 사용할 덧셈메스드이다.

문자열을 변환해서 더하기 연산을 하는 단순 작업이다.

@SampleServiceImpl을 작성할 때에는 반드시 @Service라는 어노테이션을 넣자!

그럼 빈에서 사용될 수 있따.

package org.study.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);
	}

}

지금 작성한코드를 보면 기존에 log.info()를 통해 로그를 기록해 오던 부분이 빠져있다.

수많은 로그를 기록하는 일은 반복적이면서 핵심 로직도 아니고, 필요하기는 한 기능이기 때문에 관심사로 간주할 수 있따.

AOP개념에서 Adivce는 관심사를 실제로 구현한 코드이므로 지금부터 로그를 기록해주는 LogAdvice를 설계할 것이다.

AOP기능의 설정은 XML방식이기는 하지만, 어노테이션만을 이용해서 AOP관련 설정을 진행 할 예정이다.

프로젝트에 org.study.aop패키지를 생성하고 LogAdvice라는 클래스를 추가해보자

package org.study.aop;

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 {
	@Before("execution(* org.study.service.SampleService*.*(..)")
	public void logBefore() {
		log.info("==================");
	}
}

logAdvice클래스의 선언부에는 @Aspect 어노테이션이 있다.

@Aspect는 해당 클래스의 객체가 Aspect를 구현한 것임을 나타내기 위해서 사용한것이다.

@Component는 AOP와 관계는 없지만 스프링에서 빈으로 인식하기 위해서 사용한다.

@Before어노테이션으로 적용한다. 이것은 BeforeAdvice를 구현한 메서드에 추가한다.

@After @ AfterRetirning 등 동일한 방식으로 적용한다.

Advice와 관련된 어노테이션들은 내부적으로  poingcut을 지정한다.

pointcut은 별도의 @pointcut으로 지정해서 사용할 수 도있다.


AOP설정

스프링 프로젝트에 AOP를 설정하는 것은 간단히 자동으로 Proxy객체를 만들어주는 설정을 추가해 주면된다.

root-context.xml을 선택해서 네임스페이스에 aop와 context를 추가한다.

그리고 root-context.xml파일에 아래와 같은 내용을 추가한다.

	<context:component-scan
		base-package="org.study.service"></context:component-scan>
	<context:component-scan
		base-package="org.study.aop"></context:component-scan>
        
        <aop:aspectj-autoproxy></aop:aspectj-autoproxy>

root-context애서는 componet-scan을 통해서 service, aop패키지를 스캔한다.

이과정에서 sampleServiceImpl클래스와 LogAdvice는 스프링의 빈 으로 등록될 것이고, <aop:aspectj-autoproxy>를 이용해서 @Before가 동작하게 된다.

이클립스에도 여타 다른 파일과같이 빨간화살표? 아이콘이 메서드 옆에 같이 생성된것을 알 수 있따.


AOP테스트

정상적인 상황이라면 SampleServiceImpl, LogAdvice는 같이 묶여서 자동으로 Proxy 객체가 생성된다. 

테스트 관련 폴더에 org.study.service.SampleServiceTets를 추가하자

package org.study.service;

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.SpringJUnit4ClassRunner;

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

@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());

	}
}

SampleServiceTests에서 가장먼저 작성해 봐야 하는 코드는 AOP 설정을 한 Tartget에 대해 Proxy객체가 정상적으로 만들어져있는지 확인하는 것이다.

<aop:aspectj-autoproxy>가 정상적으로 작동을 하고, LogAdvice에 설정 문제가 없다면 service 변수의 클래스는 단순히 org.study.service.SampleServiceImpl의 인스턴스가 아닌 생성된 Proxy클래스의 인스턴스가 된다.

단순히 service변수를 출력했을 때는 기존에 사용하듯이 SampleServiceImpl클래스의 인스턴스 처럼 보일수 있따.

이것은 toString()의 결과이다. 그래서 세밀하게 확인을 원한다면 getClass()를 이용해서 파악한다.

com.sun.proxy.$Proxy 는 JDK의 다이나믹 프록시 기법이 적용된 기법이다.

이를 이용해서 SampleServiceImpl에 있는 더하기 코드를 테스트 코드를 작성하자

	@Test
	public void testAdd() throws Exception {

		log.info(service.doAdd("123", "456"));

	}

dpAdd()를 실행하면 LogAdvice의 설정이 같이 적용되어 아내롸 같이 로그가 기록된다.


args를 이용한 파라미터 추적

LogAdvice가 SampleService의 doAdd()를 실행하기 직전에 간단한 로그를 기록하지만, 상황에 따라서는

해당 메서드에 전달되는 파라미터가 무엇인지 기록하거나, 예외가 발생했을 때 어떤 파라미터에 문제가 있는지 알고 싶은 경우도 있다. 

LogAdvice에 적용된 @Before("execution(* org.study.service.SampleService*.*(..))")는 어떤 위치에

Advice를 적용할 것인지를 결정하는 Pointcut 인데, 설정 시에 args를 이용하면 간단히 파라미터를 구할 수 있따.

  @Before("execution(* org.zerock.service.SampleService*.doAdd(String, String)) && args(str1, str2)")
  public void logBeforeWithParam(String str1, String str2) {

    log.info("str1: " + str1);
    log.info("str2: " + str2);
  }

logBeforeWithParam()에서는 'excution'으로 시작하는 Point 설정에 doAdd() 메서드를 명시하고, 파라미터의 타입을 지정했다.

뒤쪽의 &&aregs부분에는 변수명을 지정하는데 이 2종류의 정보를 이용해서 logBeforeWithParam() 메서드의 파라미터를 설정하게 된다.

기존의 테스트 코드를 실행하면 단순한 로그와 더불어 전달된 파라미터 역시 파악 할 수있따.

위의 값과 다르게 str1: str2: 이렇게 예쁘게 전달된 파라미터역시 파악할수 있따.


@AfterThowing

코드를 실행하다 보면 파라미터의 값이 잘못되어서 예외가 발생하는 경우가 있다.

AOP의 @AfterThowing어노테이션을 이용해서 예외를 발생한 후 동작하면서 문제를 찾도록 도와 줄수있다.

	@AfterThrowing(pointcut = "execution(* org.study.service.SampleService*.*(..))", throwing = "exception")
	public void logException(Exception exception) {

		log.info("요류가 발생한것같아!!....!!!!");
		log.info("오류내용 : " + exception);

	}

pointcut과 thowing속성을 지정하고 변수 이름을 exception으로 지정하고 테스트 코드에서 고의적으로 예외가 발생할 코드를 작성해서 테스트하자

  @Test
  public void testAddError() throws Exception {
    
    log.info(service.doAdd("123", "ABC"));
    
  }

숫자가아닌 ABC 문자열 타입으로 예외를 일으켰다.

문자열을 나타내면서 오류를 보여준다.


@Around와 ProceedingJoinPoint

AOP를 이용해서 좀더 구체적인 처리를 하고 싶다면 @Around 와 ProceedingJoinPoint를 이용한다.

@Around는 조금 특별하게 동작한다.

직접 대상 매서드를 실행할 수 있는 권한을 가지고 있고, 메서드의 실행 전과 실행 후에 처리가 가능하다

ProceedingJoinPoint는 @Around와 같이 결합해서 파라미터나 예외등을 처리할 수 있따.

LogAdvice에

 @Around("execution(* org.study.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) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
    
    long end = System.currentTimeMillis();
    
    log.info("TIME: "  + (end - start));
    
    return result;
  }

logTime()의 Pointcut설정은 '...SampleService*.*(..)'로 지정한다. logTIme()은 특별하게 ProceedingJoinPoint라는 파라미터를 지정한다.

ProceedingJoinPoint는 AOP의 대상이 되는 Tartget이나 파라미터 등을 파악할 뿐만아니라 직접 실행을 결정 할 수도 있다.

@Before등과 달리 @Around가 적용되는 메서드의 경우에는 리턴 타입이 void가 아닌 타입으로 지정하고, 메서드의 실행 겨로가 역시 직접 반환하는 형태로 작성한다.

Tests클래스의 testAdd()를 실행시켜보았따.

이때 @Around가 먼저 동작하고 @Before등이 실행된 후에 메서드가 실행되는데 걸린 시간이 로그로 기록되는 것을 볼 수 있따.

 

728x90
반응형
LIST

'Spring공부 > 2-AOP와 트랜잭션' 카테고리의 다른 글

트랜잭션처리(2)  (0) 2021.10.11
트랜잭션관리(1)  (0) 2021.10.11

댓글