이전 포스팅에 이어서 이번에는 Annotation 방식으로 AOP를 다뤄보자. 고급지게 표현하자면 @AspectJ 방식을 사용해볼 것이다.
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>
실행 결과
'Spring Series > Spring Framework' 카테고리의 다른 글
[Spring] @Controller 보충 설명 (0) | 2021.01.01 |
---|---|
[Spring] MVC 프로젝트 구조 파악하기 (2) | 2020.12.31 |
STS에 Spring Legacy Project가 없을 경우?? (5) | 2020.12.30 |
[Spring] profile 속성 / AOP(1) - xml 이용 (0) | 2020.12.28 |
[Spring] properties 파일(2) (0) | 2020.12.23 |
댓글