Keep going
AOP라는 패러다임 본문
- AOP는 흔히 '관점 지향 프로그래밍' 이라는 용어로 번역되는데, 이때 관점 이라는 용어는 개발자들에게는 관심사라는 말로 통용된다.
- 관심사는 개발 시 필요한 고민이나 염두에 두어야 하는 일이라고 생각할 수 있는데, "파라미터가 올바르게 들어왔는지" "이 작업을 하는 사용자가 적절한 권한을 가진 사용자인지", "이 작업에서 발생할 수 있는 예외는 어떻게 처리해야 하는지" 등이 있다.
- 핵심 로직은 아니지만 , 코드를 온전하게 만들기 위한 고민들로, 전통적인 방식에선 개발자가 반복적으로 이 고민을 코드에 반영한다.
- AOP는 이러한 고민을 다른 방식으로 접근하는데. 바로 "관심사의 분리"이다. AOP는 개발자가 염두에 두어야 할 것들을 관심사로 분리하고, 핵심 비즈니스 로직만을 작성할 것을 권장한다.
- AOP는 과거에 개발자가 작성했던, 관심사 + 비즈니스 로직을 분리해서 별도의 코드로 작성하도록 하고, 실행할 때 이를 결합하는 방식으로 접근한다.
- 개발자가 작성한 코드와 분리된 관심사를 구현한 코드를 컴파일 혹은 실행 시점에 결합시킨다.
- 실제 실행은 결합된 상태의 코드가 실행되기 때문에 개발자들은 핵심 비즈니스 로직에만 근거해서 코드를 작성하고, 나머지는 어떤 관심사들과 결합할 것인지를 설정한 것만으로 모든 개발을 마칠 수 있다.
18.1 AOP 용어들
- AOP는 기존의 코드를 수정하지 않고, 원하는 기능들과 결합할 수 있는 패러다임

- Target : 개발자가 작성한 핵심 비즈니스 로직을 가지는 객체
- 순수한 비즈니스 로직을 의미하고, 어떠한 관심사들과도 관계를 맺지 않는다.
- 순수한 코어라고 볼 수 있다.
- Proxy : Target을 전체적으로 감싸고 있는 존재
- Proxy는 내부적으로 Target을 호출하지만, 중간에 필요한 관심사들을 거쳐서 Target을 호출하도록 자동 혹은 수동으로 작성된다.
- 직접 코드를 통해 구현하는 경우도 있지만, 대부분 스프링 AOP의 기능 이용해서 자동으로 생성되는 방식 이용.
- JoinPoint : Target 객체가 가진 메서드
- 외부에서의 호출은 Proxy 객체를 통해서 Target 객체의 JoinPoint를 호출하는 방식

- JoinPoint는 Target이 가진 여러 메서드.
- Target에는 여러 메서드가 존재하기 때문에 어떤 메서드에 관심사를 결합할 것인지를 결정해야 하는데 이 결정을 Pointcut 이라고 한다.
- Pointcut : 관심사와 비즈니스 로직이 결합되는 지점을 결정
- Aspect : 관심사 자체를 의미하는 추상 명사
- Advice : Aspect를 구현한 코드 (실제 걱정거리를 분리해 놓은 코드)
구분 | 설명 |
Before Advice | Target의 JoinPoint를 호출하기 전에 실행되는 코드이다. 코드의 실행 자체에는 관여할 수 없다. |
After Returning Advice | 모든 실행이 정상적으로 이루어진 후에 동작하는 코드이다. |
After Throwing Advice | 예외가 발생한 뒤에 동작하는 코드이다. |
After Advice | 정상적으로 실행되거나 예외가 발생했을 때 구분 없이 실행되는 코드이다. |
Around Advice | 메서드의 실행 자체를 제어할 수 있는 가장 강력한 코드이다. 직접 대상 메서드를 호출하고 결과나 예외를 처리할 수 있다. |
Advice는 과거의 스프링에서 별도의 인터페이스로 구현되고, 이를 클래스로 구현하는 방식으로 제작했으나 스프링 3버전 이후에는 어노테이션만으로도 모든 설정이 가능하다.
Target에 어떤 Advice를 적용하는 방법
- XML을 이용한 설정
- 어노테이션을 이용하는 방법
구분 | 설명 |
execution(@execution) | 메서드를 기준으로 Pointcut을 설정한다. |
within(@within) | 특정한 타입을 기준으로 Pointcut을 설정한다. |
this | 주어진 인터페이스를 구현한 객체를 대상으로 Pointcut을 설정한다. |
args(@args) | 특정한 파라미터를 가지는 대상들만을 Pointcut을 설정한다. |
@annotation | 특정한 어노테이션이 적용된 대상들만을 Pointcut으로 설정한다. |
Pointcut은 Advice를 어떤 JoinPoint에 결합할 것인지를 결정하는 설정.
AOP에서 Target은 결과적으로 Pointcut에 의해서 자신에게는 없는 기능들을 가지게 된다.
18.2 AOP 실습
- AOP 기능은 주로 일반적인 Java API를 이용하는 클래스들에 적용한다.
- (아래 실습에서는 서비스 계층에 AOP를 적용, AOP의 예제는 ①서비스 계층 메서드 호출 시 모든 파라미터들을 로그로 기록, ②메서드들의 실행 시간을 기록 )
-
예제 프로젝트 생성
- AOP는 AspectJ라는 라이브러리 도움을 많이 받기 때문에 스프링 버전을 고려하여 AspectJ의 버전 역시 1.9.0 으로 버전을 높여준다.
- AOP 설정과 관련해서 가장 중요한 라이브러리는 AspectJ Weaver라는 라이브러리이다. 스프링은 AOP 처리가 된 객체를 생성할 때 AspectJ Weaver 라이브러리의 도움을 받아서 동작한다.
- AspectJweaver 라이브러리
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>${org.aspectj-version}</version>
</dependency>
|
cs |
-
서비스 계층 설계
- 예제로 사용할 객체는 SampleService 인터페이스의 doAdd() 메서드를 대상으로 진행한다.
- SampleServiceImpl 을 작성할 때는 반드시 @Service라는 어노테이션을 추가해서 스프링에서 빈으로 사용될 수 있도록 설정한다.
- SampleService 인터페이스
package org.zerock.service;
public interface SampleService {
public Integer doAdd(String str1, String str2)throws Exception;
}
|
cs |
- SampleServiceImpl 클래스
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 {
return Integer.parseInt(str1) + Integer.parseInt(str2);
}
}
|
cs |
-
Advice 작성
- log.info( )를 이용해서 로그를 기록해 오던 부분을 제외했다.
- 지금까지 해왔던 수많은 로그를 기록하는 일은 '반복적이면서 핵심 로직도 아니고, 필요하기는 한' 기능이기 때문에 '관심사'로 간주할 수 있다.
- LogAdvice - 로그를 기록해주는 관심사
package org.zerock.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.zerock.service.SampleService*.*(..))")
public void logBefore() {
log.info("====================");
}
}
|
cs |
- @Aspect : 해당 클래스의 객체가 Aspect를 구현한 것임으로 나타내기 위해서 사용
- @Component는 AOP와는 관계가 없지만 스프링에서 빈으로 인식하기 위해 사용
- logBefore( )는 @Before 어노테이션을 적용하고 있다. @Before는 BeforeAdvice를 구현한 메서드에 추가한다.
- Advice와 관련된 어노테이션들은 내부적으로 Pointcut을 지정한다. 별도의 @Pointcut으로 지정해서 사용할 수도 있다.
- @Before 내부의 execution 문자열은 AspectJ의 표현식이다. execution의 경우 접근제한자와 특정 클래스의 메서드를 지정할 수도 있다.
18.3 AOP 설정
- 스프링 프로젝트에 AOP를 설정하는 것은 스프링 2버전 이후에는 간단히 자동으로 Proxy 객체를 만들어주는 설정을 추가해주면 된다.
- root-context.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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="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 -->
<context:annotation-config></context:annotation-config>
<context:component-scan base-package="org.zerock.service">
</context:component-scan>
<context:component-scan base-package="org.zerock.aop">
</context:component-scan>
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
|
cs |
- <component-scan>을 이용해서 'org.zerock.service' 패키지와 'org.zerock.aop' 패키지를 스캔한다. 이 과정에서 SamplerServiceImpl 클래스와 LogAdvice는 스프링의 빈으로 등록될 것이다.
- <aop:aspectj-autoproxy>를 이용해서 LogAdvice에 설정한 @Before가 동작하게 된다.
18.4 AOP 테스트
- 정상적인 상황이라면 SampleServiceImpl, LogAdvice는 같이 묶여서 자동으로 Proxy 객체가 생성된다.
- SampleServiceTests 클래스
package org.zerock.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)
@ContextConfiguration({"file:src/main/webapp/WEB-INF/spring/root-context.xml"})
//Java Config
//@ContextConfiguration(classes={org.zerock.config.RootConfig.class, org.zerock.config.ServletConfig.class})
@Log4j
public class SampleServiceTests {
@Setter(onMethod_=@Autowired)
private SampleService service;
@Test
public void testClass() {
log.info(service);
log.info(service.getClass().getName());
}
}
|
cs |
- AOP 설정을 한 Target에 대해서 Proxy 객체가 정상적으로 만들어져 있는지를 확인하는 것이다.
- <aop:aspectj-aitoproxy>가 정상적으로 모든 동작을 하고, LogAdvice에 설정 문제가 없다면 service 변수의 클래스는 단순히 org.zerock.service.SampleServiceImpl의 인스턴스가 아닌 생성된 Proxy 클래스의 인스턴스가 된다.

- SampleServiceTests 클래스 - testAdd( ) 메서드
@Test
public void testAdd() throws Exception {
log.info(service.doAdd("123", "456"));
}
|
cs |

-
args를 이용한 파라미터 추적
- LogAdvice가 SampleService의 doAdd( )를 실행하기 직전에 간단한 로그를 기록하지만, 상황에 따라서는 해당 메서드에 전달되는 파라미터가 무엇인지 기록하거나, 예외가 발생했을 때 어떤 파라미터에 문제가 있는지 알고 싶은 경우도 많다.
- LogAdvice에 적용된 @Before 은 어떤 위치에 Advice를 적용할 것인지를 결정하는 Pointcut인데 설정 시에 args를 이용해 간단히 파라미터를 구할 수 있다.
- LogAdvice 클래스
@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);
}
|
cs |
-logBeforeWithParam()에서는 'execution'으로 시작하는 Pointcut 설정에 doAdd( ) 메서드를 명시하고, 파라미터의 타입을 지정했다.
- 뒤쪽의 &&args를 이용하는 설정은 간단히 파라미터를 찾아서 기록할 때에는 유용하지만 파라미터가 다른 여러 종류의 메서드에 적용하는 데에는 간단하지 않다는 단점이 있따.
-
@AfterThrowing
- 파라미터의 값이 잘못되어서 예외가 발생하는 경우가 많다.
- AOP의 @AfterThrwoing 어노테이션은 지정된 대상이 예외를 발생한 후에 동작하면서 문제를 찾을 수 있도록 도와줄 수 있다.
@AfterThrowing(pointcut="execution(* org.zerock.service.SampleService*.*(..))",
throwing="exception")
public void logException(Exception exception) {
log.info("Exception....!!!");
log.info("exception : " + exception);
}
|
cs |
logException에 적용된 @AfterThrowing은 pointcut과 throwing 속성을 지정하고 변수 이름을 exception으로 지정한다.
- SampleServiceTests - 에러 발생할 테스트 코드
@Test
public void testAddError() throws Exception {
log.info(service.doAdd("123","ABC"));
}
|
cs |

-
@Around와 ProceedingJoinPoing
- @Around는 조금 특별하게 동작하는데 직접 대상 메서드를 실행할 수 있는 권한을 가지고 있고, 메서드의 실행 전과 실행 후에 처리가 가능하다.
- ProceedingJoinPoint는 @Around와 같이 결합해서 파라미터나 예외 등을 처리할 수 있다.
- LogAdvice 클래스
@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;
}
|
cs |
- logTIme의 Pointcut 설정은 SampleService*.*(..)로 지정한다.
- logTime은 특별하게 ProceedingJoinPoint 파라미터를 지정하는데 이는 AOP의 대상이 되는 Target이나 파라미터를 파락할 뿐 아니라 직접 실행을 결정할 수도 있다.
- @Before 등과 달리 @Around가 적용되는 메서드의 경우 리턴 타입이 void 아닌 타입으로 설정하고 메서드 실행 결과 역시 직접 반환하는 형태로 작성해야 한다.
출처 : 코드로 배우는 스프링 웹 프로젝트 [구멍가게 코딩단]
'Records > Spring Framework' 카테고리의 다른 글
댓글과 댓글 수에 대한 처리 (0) | 2021.03.26 |
---|---|
스프링에서 트랜잭션 관리 (0) | 2021.03.22 |
Ajax 댓글 처리 (0) | 2021.03.18 |
REST 방식으로 전환 (0) | 2021.03.16 |
검색처리 (0) | 2021.03.15 |