-
Spring Doc - Core - AOPDocs/Spring 2021. 5. 3. 17:10
Spring framework Document 읽기
- 현재 담당하는 서비스가 Spring5를 사용하진 않지만 5의 문서를 읽으면서 추가된 내용이 아니라 Spring framework 자체의 내용에 집중해볼 예정입니다.
Docs : https://docs.spring.io/spring/docs/5.2.3.RELEASE/spring-framework-reference/core.html#spring-core
Repo : https://github.com/spring-projects/spring-framework
Github repository에서 "T"를 누르면 파일 검색이 가능합니다.
Aspect Oriented Programming with Spring
- OOP의 핵심 단위로 객체(class)가 있다면 AOP의 핵심 단위로는 Aspect가 있습니다.
- AOP는 DI만큼이나 Spring의 핵심 기능입니다.
- Spring은 "schema-based" 방식과 "@AspectJ" 방식으로 간편하게 커스텀 aspect를 만드는것을 지원합니다.
AOP Concepts
용어 정리
전체 코드 중에서 aop를 적용 가능한 위치들(Joinpoint)
"Aservice.Afunction"에 aop를 적용시킬 것이다(Pointcut)
"Aservice.Afunction"이 실행되기 전,후에("around") "로깅"을 할것이다(Advice)
-> 로깅을 위해서 Aservice.Afunction에 aop를 적용할 것이다 (Aspect)- Aspect
- 여러 클래스를 가로지르는 관점의 단위
- Spring AOP의 aspect는 "schema-based" 방식과 "@AspectJ" 방식으로 구현할 수 있습니다.
- Join point
- 실행중인 프로그램의 메소드의 실행, 예외의 처리 같은 어떠한 지점들, AOP를 적용시킬 후보들
- Spring AOP에서는 method 실행 에만 가능합니다.
- Advice
- 선택한 join point (=pointcut) 에서 수행할 동작
@Before
,@AfterReturning
,@AfterThrowing
,@After
(finally)@Around
로 동작을 수행할 위치를 지정가능
- Pointcut
- Joint point 중에서 aop를 적용하기 위해 선택한 지점
- Introduction
- 프록시 대상에 메소드나 필드를 추가해주는 방법
- Target object (Advised Object)
- Aspect에 의해 다뤄지는 객체, Spring에서는 언제나 runtime에 proxy 객체 로 생성됩니다.
- AOP proxy
- Aspect를 적용하기 위해 AOP 프레임워크가 생성한 proxy 객체
- Spring에서는 JDK dynamic proxy 또는 CGLIB proxy 가 사용됩니다.
- JDK dynamic proxy는 JDK에 포함되어있고 CGLIB는 오픈소스지만 spring-core에 repackage 되어있습니다.
- Proxy 대상이 interface의 구현체라면 JDK dynamic proxy를 사용하며 그렇지 않다면 CGLIB가 사용됩니다.
- JDK dynamic proxy는 reflection을 사용해서 proxy object를 생성하기 때문에 성능 저하의 원인이 될 수 있으며
CGLIB는 상속을 이용해서 proxy object를 생성하기 때문에 final이나 private, static method에 대한 AOP가 불가능 합니다.
- Weaving
- Target object를 생성하기 위해서 aspect를 다른 application이나 객체와 연결하는 행위
- AspectJ 컴파일러를 사용해서 compile time 에 완료될 수 있고, Spring AOP 처럼 run time 에 수행될 수도있습니다.
- Aspect
Spring AOP의 기능과 목표
Spring AOP는 pure java로 구현되어있기 때문에 추가적인 컴파일 설정이 필요 없습니다.
현재 method에 관련된 join point만 제공하기때문에 field interception이 필요하다면
AspectJ
를 사용해야합니다.다른 AOP 프레임워크와 다른점은 Spring IoC 컨테이너와 결합하여 사용하기 쉽게하기 위해 완벽한 AOP를 구현하지 않는다는 점입니다.
- Method excution에 대한 처리만 가능하다는점, proxy 방식으로만 동작한다는 점을 의미합니다.
- 완벽한 AOP를 위해서는 AspectJ같은 AOP 프레임워크가 필요하며, 필요시 Spring기반의 Spring AoP + AspectJ 통합 환경을 구성할 수 있습니다.
- 상세한 aop 설정은 불가능 하지만, enterprise java 환경에서 필요로 하는 일반적인 설정들은 간단하게 가능합니다.
Spring의 핵심 원칙중 하나는 비침습성(non-invasiveness)으로 spring에 의존적인 코드를 사용하는것을 강제하지 않습니다. Spring AOP, AspectJ 혹은 둘다 사용하는것 모두 사용자가 선택할 수 있고, @AspectJ 스타일의 설정, xml 스타일의 설정 등도 모두 선택하여 사용할 수 있습니다.
Spring AOP는 proxy 기반 이기 때문에 객체내 호출(self invacation)은 가로채지 못합니다.
객체 내 호출에 AOP를 적용하기 위해서는 native AspectJ weaving을 사용하거나
AopContext
를 통해 직접 proxy 객체를 다시 꺼내와서 호출하는 방법이 있습니다.// 참고용 커스텀 클래스 public class AopUtils { public static <T> T proxy(T current) { try { return (T)AopContext.currentProxy(); } catch (IllegalStateException e) { return current; } } }
@component public class ProxyTest { public Object getValue() { //getCachedValue를 직접 호출하면 this.getCachedValue()가 호출되기 때문에 @Cacheable이 동작하지 않습니다. return AopUtils.proxy(this).getCachedValue(); } @Cacheable(CACHE_KEY) public Object getCachedValue() { //return value } }
- 위의 방식을 사용하기 위해서는
@EnableAspectJAutoProxy(exposeProxy = true)
등의 방식으로 AopContext를 노출시켜줘야 합니다.
- 위의 방식을 사용하기 위해서는
@AspectJ
@AspectJ는 AspectJ의 어노테이션이지만, spring에서 pointcut을 표현하는데 사용할 수 있습니다.
- @AspectJ 어노테이션을 사용해도 여전히 pure Spring AOP로 동작하기 때문에 AspectJ의 컴파일러나 위버(weaver), 의존성 추가가 필요하지 않습니다.
AspectJ의 의존성 추가가 필요 없다는 의미로 spring aop의 의존성은 당연히 필요합니다.
- @AspectJ 어노테이션을 사용해도 여전히 pure Spring AOP로 동작하기 때문에 AspectJ의 컴파일러나 위버(weaver), 의존성 추가가 필요하지 않습니다.
@AspectJ 어노테이션을 사용하기 위해선 configuration에
@EnableAspectJAutoProxy
를 추가해주면 됩니다.Auto-proxying이란 aspect에 의해 advised되는 bean을 자동으로 proxy객체로 생성함을 의미합니다.
@Aspect
@Aspect
를 사용해서 aspect를 만들어 pointcut, advice, introduction 등을 선언 할 수 있습니다.@Aspect
어노테이션은 classpath에 존재한다고 해도 component scan을 통해서 자동으로 감지되지 않으며@component
등을 추가로 붙여서 감지되도록 해야 합니다.@Aspect
는 auto-proxying의 대상에서 제외하는 마커 인터페이스로 사용되기 때문에 다른 aspect의 대상이 되지 못합니다.@Pointcut
@Pointcut
은 advice를 적용할 위치를 찾는데 사용되며 expression과 signature 두 파트로 나뉩니다.// 모든 public 메서드의 경우 @Pointcut("execution(public * (..))") // the pointcut expression private void anyPublicOperation() {} // the pointcut signature // com.xyz.someapp.trading 패키지에 포함된 경우 @Pointcut("within(com.xyz.someapp.trading..)") // the pointcut expression private void inTrading() {} // the pointcut signature // com.xyz.someapp.trading 패키지에 포함되는 모든 public 메서드 @Pointcut("anyPublicOperation() && inTrading()") // the pointcut expression private void tradingOperation() {} // the pointcut signature
- Expression은 AspectJ5의 pointcut 표현식을 따릅니다.
- Signature의 return type은 반드시 void 여야 합니다.
PointCut Designators(PCD)
- Spring에서는 method excution만 지원하기 때문에 아래 종류의 PCD만 제공합니다.
- Spring에서는 추가적으로 bean name으로 적용할 수 있는
bean
PCD를 제공합니다.
- Spring에서는 추가적으로 bean name으로 적용할 수 있는
- PCD는 크게 3가지 그룹으로 나눌 수 있습니다.
- Kinded
- 특정 종류의 join point 선택에 사용
execution
,get
,set
,call
,handler
- Scoping
- join point의 그룹 선택에 사용
within
,withincode
- Contextual
- 컨텍스트와 관련 내용에 사용
this
,target
,@annotation
- Kinded
- Spring에서는 method excution만 지원하기 때문에 아래 종류의 PCD만 제공합니다.
사용법 cheat sheet
Pointcut 잘 쓰는법
- 포인트컷 매칭은 AspectJ가 컴파일타임에 최적화 시키기 때문에 일반적으로 크게 신경 쓸 필요는 없지만, 성능을 더욱 향상 시키기 탐색범위를 줄여주는 것이 좋습니다.
- 잘 쓰여진 Pointcut은 최소한 Kinded && Scoping 타입을 포함해야 합니다.
- Scoping 타입 없이 Kinded 또는 Contextual 타입을 사용하는것은 동작에는 문제가 없지만 weaving 성능에 영향을 줄 수 있습니다.
- Scoping 타입은 매우 빠르게 처리되고, 탐색범위를 좁히는데 큰 도움이 되기 때문에 가능하면 반드시 포함해야 합니다.
@Advice
Advice는 pointcut 표현식과 함께 표현되며, 매칭된 pointcut의 주변(
@before
,@after
,@around
)에서 수행됩니다.@Aspect public class Example { //@Before = advice //execution(* com.xyz.myapp.dao..(..)) = pointcut @Before("execution(* com.xyz.myapp.dao..(..))") public void doAccessCheck() { // ... } @AfterReturning( pointcut="com.xyz.myapp.SystemArchitecture.dataAccessOperation()", returning="retVal") public void doAccessCheck(Object retVal) { // retVal 자체를 다른 객체로 변경 할 수는 없습니다. (return type도 void) } @Around("com.xyz.myapp.SystemArchitecture.businessService()") public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable { // start stopwatch Object retVal = pjp.proceed(); // stop stopwatch return retVal; } }
Parameter
Advice 메서드는 첫번째 파라미터로
JoinPoint
를 받을 수 있습니다.JoinPoint
말고도args
나@annotation
를 통해서 원하는 파라미터를 넣어주는것도 가능합니다.@Pointcut("com.xyz.myapp.SystemArchitecture.dataAccessOperation() && args(account,..)") private void accountDataAccessOperation(Account account) {} @Before("accountDataAccessOperation(account)") public void validateAccount(Account account) { // ... } //한줄로도 선언 가능 @Before("com.xyz.myapp.SystemArchitecture.dataAccessOperation() && args(account,..)") public void validateAccount2(Account account) { // ... }
args(account,..)
를 선언함으로써 적어도 하나의 파라미터가 있는dataAccessOperation
에 대한 pointcut을 명시할 수 있고, account 객체를 advice에 제공해줄 수 있습니다.
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Auditable { AuditCode value(); } @Before("com.xyz.lib.Pointcuts.anyPublicMethod() && @annotation(auditable)") public void audit(Auditable auditable) { AuditCode code = auditable.value(); // ... }
Introductions
Introduction을 사용하면 adviced된 객체를 특정 인터페이스의 구현체인것 처럼 동작하게 만들 수 있습니다.
@Aspect public class UsageTracking { @DeclareParents(value="com.xzy.myapp.service.*+", defaultImpl=DefaultUsageTracked.class) public static UsageTracked mixin; @Before("com.xyz.myapp.SystemArchitecture.businessService() && this(usageTracked)") public void recordUsage(UsageTracked usageTracked) { usageTracked.incrementUseCount(); } }
@DeclareParents
를 통해 introduction을 생성 할 수 있으며 value 값인com.xzy.myapp.service.*+
패키지에 속한 bean들이UsageTracked
인터페이스의 구현체 됩니다.포인트컷 포현식을 잘 못 이해하고 있는건지 모르겠지만
"com.xyz.myapp.SystemArchitecture.businessService()" 가
"com.xzy.myapp.service" 패키지가 아니니깐 동작 안해야 하는거 아닌가.. 모르겠네요
Aspect Instantiation Models
Aspect 인스턴스는 default로 application context 단위 싱글톤으로 생성됩니다.
Aspect가 싱글톤이 아닌 다른 lifecycle을 갖게 하려면
perthis
,pertarget
를 사용하여 지정해 줄 수 있습니다.@Aspect("perthis(com.xyz.myapp.SystemArchitecture.businessService())") public class MyAspect { private int someState; @Before(com.xyz.myapp.SystemArchitecture.businessService()) public void recordServiceUsage() { // ... } }
- 명시한 joinpoint당 하나의 aspect 인스턴스가 생성됩니다.
Spring AOP가 아닌 AspectJ 사용하기'Docs > Spring' 카테고리의 다른 글
Spring Doc - Core - Web MVC (0) 2021.05.03 Spring Doc - Core - Transaction (0) 2021.05.03 Spring Doc - Core -IOC Container (0) 2021.05.03 - 현재 담당하는 서비스가 Spring5를 사용하진 않지만 5의 문서를 읽으면서 추가된 내용이 아니라 Spring framework 자체의 내용에 집중해볼 예정입니다.