본문 바로가기
  • 실행력이 모든걸 결정한다
Spring Series/Spring Framework

[Spring] AOP(2) - @(Annotation) 이용

by 김코더 김주역 2020. 12. 30.
반응형

이전 포스팅에 이어서 이번에는 Annotation 방식으로 AOP를 다뤄보자. 고급지게 표현하자면 @AspectJ 방식을 사용해볼 것이다.

kimcoder.tistory.com/230

 

[Spring] profile 속성 / AOP(1) - xml 이용

1. profile 속성 - 개발 환경에 따라 bean 설정을 달리 적용하고 싶을 때 사용하는 속성이다. 1) Profile 지정하기 (1) XML 방식 - 의 profile 속성을 이용하여 bean을 나눌 수 있다. (2) Annotation 방식 - 설정..

kimcoder.tistory.com

 

 

1. @AspectJ

- @AspectJ는 애스펙트를 클래스, 메소드, 어노테이션으로 정의하는 방법이다.

- @AspectJ의 애스펙트는 다양한 조합을 갖는 포인트컷과 어드바이스를 하나의 모듈로 정의할 수 있다.

 

이전 포스팅에서는 Advice 종류 5가지에 따른 xml 설정 방법을 살펴봤다.

 

@AspectJ 방식에서는 위의 Advice들을 org.aspectj.lang.annotation에 있는 어노테이션으로 사용할 수 있다.

  • @Before
  • @AfterReturning
  • @AfterThrowing
  • @After
  • @Around

 

 

2. @Aspect와 @Pointcut

- 애스펙트로 사용할 클래스에는 @Aspect라는 어노테이션을 붙여서 사용한다.

@Aspect
public class LogAop {...}

 

- 포인트컷은 다음과 같이 @Pointcut 어노테이션으로 설정할 수 있다. 리턴 타입이 void인 메소드에 @Pointcut을 붙이고 포인트컷 표현식을 넣으면 된다. 메소드 내부에는 코드를 작성할 필요가 없다. 단지 포인트컷의 이름(메소드의 이름)과 파라미터를 정의하는 용도로만 쓰인다.

@Pointcut("within(com.example.demo.*)")
private void publicTimer() {}

이처럼 독립된 메소드로 정의된 포인트컷은 여러 개의 어드바이스에서 사용될 수 있다. 

 

- 생성한 포인트컷과 어드바이스를 조합하기 위해서는 다음과 같이 어드바이스 메소드의 어노테이션에 포인트컷의 이름을 지정해주면 된다. @Around는 어드바이스 어노테이션들 중 하나로 잠시 후에 설명할 것이다. 그리고 파라미터에 있는 조인 포인트는 AOP 적용 대상 메소드를 가리킨다고 이해하면 된다.

@Around("publicTimer()")
public Object loggerAop(ProceedingJoinPoint joinpoint) throws Throwable {
    // 부가 기능 로직
}

다른 어드바이스에서 참조될 포인트컷이 아니라면 어드바이스 메소드의 어노테이션에 직접 포인트컷 표현식을 넣어도 된다.

@Around("within(com.example.demo.*)")
public Object loggerAop(ProceedingJoinPoint joinpoint) throws Throwable {
    // 부가 기능 로직
}

그리고 다른 클래스에 있는 어드바이스에서 포인트컷을 사용하려면 클래스와 메소드 이름을 모두 사용해야 한다.

@Around("com.example.demo.LogAop.publicTimer()")

 

 

3. 어드바이스 어노테이션

- 각 어드바이스 어노테이션이 의미하는 바는 이미 설명했으므로 간단한 예시 정도만 살펴보면 될 것 같다.

- 어드바이스 메소드의 파라미터에 JointPoint 오브젝트를 추가해서 타겟 메소드의 정보를 확인할 수 있다.

 

1) @Around

- 파라미터로 ProceedingJoinPoint 타입의 오브젝트를 받을 수 있다. ProceedingJoinPoint를 통해 타겟 메소드를 직접 실행하고 리턴값까지 받을 수 있다. 

- 클라이언트에 반환해야 하는 리턴값이 있다면 이 곳에서 반환하면 된다.

@Around(...)
public Object loggerAop(ProceedingJoinPoint joinpoint) throws Throwable {
    Object ret = joinpoint.proceed();
    return ret;
}

 

2) @Before

- 파라미터로 JoinPoint 타입의 오브젝트를 받을 수 있다. JointPoint는 ProceedingJoinPoint의 슈퍼 인터페이스다.

- JoinPoint로 타켓 메소드(실행 지점)에 대한 정보를 가져올 수는 있지만 타겟 메소드를 실행시킬 수는 없다.

@Before(...)
public void loggerAop(JoinPoint joinpoint) {...}

 

3) @AfterReturning

- 어노테이션의 returning 속성을 통해 타겟 메소드의 리턴값을 담을 파라미터 이름을 지정할 수 있다. 해당 파라미터 타입과 타겟 메소드의 리턴 타입이 일치하다면 어드바이저가 실행된다.

- 포인트컷은 어노테이션의 pointcut 속성에 지정해주면 된다.

@AfterReturning(pointcut="..." returning="ret")
public void loggerAop(Object ret) {...}

- 파라미터에 JoinPoint를 함께 사용할 수도 있다. 이 때는 JoinPoint가 우선해야 한다.

 

4) @AfterThrowing

- 어노테이션의 throwing 속성을 통해 예외를 담을 파라미터 이름을 지정할 수 있다. 해당 파라미터의 예외 타입과 타겟 메소드의 예외 타입이 일치하다면 어드바이저가 실행된다.

@AfterThrowing(pointcut="..." throwing="ex")
public void loggerAop(DataAccessException ex) {...}

- 모든 예외를 다 전달받으려면 파라미터 타입을 Throwable로 지정하면 된다.

 

5) @After

- 반드시 반환되어야 하는 리소스가 있거나 메소드 실행 결과를 항상 로그로 남겨야 하는 경우에 사용할 수 있다.

- 리턴 값이나 예외를 직접 전달받을 수 없다.

 

 

4. 포인트컷 메소드 파라미터

- 특정 타입을 지정하는 포인트컷 표현식을 작성할 때, 다음과 같이 패키지 이름을 포함한 클래스 이름 전체를 적는 것이 불편할 수 있다.

@Pointcut("execution(com.example.demo.annotation.SecretObject)")
private void soPointcut() {}

이런 경우에는 다음과 같이 메소드 파라미터를 이용해서 타입 정보를 자바 코드로 작성하는 것을 추천한다. 표현식 안에 파라미터의 이름을 대신 작성하면 된다.

@Pointcut("execution(secretObject)")
private void soPointcut(SecretObject secertObject) {}

물론 이 방법은 어드바이스 어노테이션 안에 있는 포인트컷 표현식에도 적용할 수 있다. 타입 정보는 어드바이스 메소드의 파라미터에 들어가있으면 된다.

@Before("execution(secretObject)")
public void loggerAop(SecretObject secertObject) {}

 

- 메소드 파라미터를 적용해서 정의해 둔 포인트컷은 다음과 같이 어드바이스 메소드에서 사용할 수 있다.

@Before("soPointcut(secretObject)")
public void loggerAop(SecretObject secertObject) {}

 

 

5. @AspectJ AOP 컨테이너 인프라 bean 등록

- @Aspect로 애스펙트 클래스를 정의할 수 있게 해주는 @AspectJ AOP 컨테이너 인프라 bean을 등록해줘야 한다.

 

1) Xml 설정의 경우

- 아래에 있는 네임스페이스 태그만 추가해주면 된다.

<aop:aspectj-autoproxy/>

 

2) 어노테이션 설정의 경우

- Spring 3.1에서 추가된 @EnableAspectJAutoProxy을 이용한다. @EnableAspectJAutoProxy 어노테이션을 설정 클래스에 추가해주면 된다.

@Configuration
@EnableAspectJAutoProxy
public class AppConfig {...}

이 때 @Aspect 클래스는 bean으로 등록시켜준다.

@Aspect
@Component
public class MyAspect {...}

- 만약 애스펙트를 적용한 프록시를 인터페이스가 아닌 클래스에 적용하고 싶다면 proxyTargetClass 속성을 true로 주면 된다.

@EnableAspectJAutoProxy(proxyTargetClass=true)

 

 

 

[추가 예제]

예제 내용은 이전 포스팅과 동일하다.

 

<pom.xml>, <DemoApplication.java>, <ForOperation.java>, <WhileOperation.java>

소스 코드는 이전 포스팅과 동일

 

 

<LogAop.java>

loggerAop 메소드는 이전 포스팅과 비슷한데, 어노테이션이 추가 되었다는 차이점이 보일 것이다.

애스펙트는 클래스에 @Aspect라는 어노테이션을 붙여서 만든다.

이전 포스팅의 xml 설정 파일에 작성했던 pointcut과 around가 @Pointcut과 @Around 어노테이션으로 표현된 것이다.

package com.example.demo;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class LogAop {
	
	@Pointcut("within(com.example.demo.*)")
	private void publicTimer() {}
	
	@Around("publicTimer()")
	public Object loggerAop(ProceedingJoinPoint joinpoint) throws Throwable {
		String str = joinpoint.getSignature().toShortString();
		System.out.println("start - "+str);
		long start = System.currentTimeMillis();
		try {
			Object obj = joinpoint.proceed();
			return obj;
		} finally {
			long end = System.currentTimeMillis();
			System.out.println("end - "+str);
			System.out.println("Time : "+(end-start));
		}
	}
}

 

 

<application.xml>

아래와 같이 <aop:aspectj-autoproxy/> 네임스페이스 태그만 추가해주면 bean으로 등록된 클래스 중에서 @Aspect가 붙은 것을 모두 애스펙트로 등록해준다. 이는 프록시를 자동으로 감지하겠다는 의미이며, LogAop.java가 proxy에 해당된다. 애스펙트는 bean으로 등록되어 있어야 한다.

<?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"
       xsi:schemaLocation="
           http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
           http://www.springframework.org/schema/aop
           http://www.springframework.org/schema/aop/spring-aop-3.1.xsd
           ">
	
    <aop:aspectj-autoproxy />
    <bean id="logAop" class="com.example.demo.LogAop" />
	  
    <bean id="whileoperation" class="com.example.demo.WhileOperation">
    	<property name="num" value="100"/>
    </bean>
    
    <bean id="foroperation" class="com.example.demo.ForOperation">
    	<property name="num" value="100"/>
    </bean>
    
</beans>

 

 

실행 결과

 

 

 

반응형

댓글