Loading...
Spring Framework Reference Documentation 7.0.2의 Using AspectJ with Spring Applications의 한국어 번역본입니다.
아래의 경우에 피드백에서 신고해주신다면 반영하겠습니다.
감사합니다 :)
이 장에서 지금까지 다룬 모든 내용은 순수한 Spring AOP입니다. 이 절에서는 요구 사항이 Spring AOP만으로 제공되는 기능을 넘어서는 경우에 Spring AOP 대신 또는 Spring AOP에 추가로 AspectJ compiler나 weaver를 어떻게 사용할 수 있는지 살펴봅니다.
Spring은 작은 AspectJ aspect library를 함께 제공하며, 이 library는 배포본 안에서
spring-aspects.jar라는 독립 실행형 artifact로 제공됩니다. 이 안의 aspect들을
사용하려면 이를 classpath에 추가해야 합니다.
Using AspectJ to Dependency Inject Domain Objects with Spring
과 Other Spring aspects for AspectJ
에서는 이 library의 내용과 사용 방법을 다룹니다.
Configuring AspectJ Aspects by Using Spring IoC 에서는 AspectJ compiler를 사용해 weaving된 AspectJ aspect에 대해 dependency injection을 어떻게 수행하는지 설명합니다. 마지막으로, Load-time Weaving with AspectJ in the Spring Framework 에서는 AspectJ를 사용하는 Spring application에 대한 load-time weaving을 소개합니다.
Spring container는 application context에 정의된 bean을 인스턴스화하고 설정합니다. 또한 bean factory에 미리 존재하는 객체를 넘기고, 적용할 설정을 포함하는 bean 정의의 이름을 제공함으로써 해당 객체를 설정하도록 요청하는 것도 가능합니다.
spring-aspects.jar에는 이 기능을 활용하여 임의의 객체에 대한 dependency injection을
가능하게 하는 annotation-driven aspect가 포함되어 있습니다. 이 지원은 어떤 container의
제어 범위 밖에서 생성되는 객체에 사용되도록 의도되었습니다. Domain object는
종종 이 범주에 속하는데, 이는 new operator를 사용해 프로그램적으로 생성되거나
database query의 결과로 ORM tool에 의해 생성되는 경우가 많기 때문입니다.
@Configurable annotation은 클래스를 Spring이 주도하는 설정 대상이 될 수 있는 클래스로
표시합니다. 가장 단순한 경우, 다음 예제에서 보듯이 이를 순수하게 marker annotation으로
사용할 수 있습니다:
1package com.xyz.domain; 2 3import org.springframework.beans.factory.annotation.Configurable; 4 5@Configurable 6public class Account { 7 // ... 8}
1package com.xyz.domain 2 3import org.springframework.beans.factory.annotation.Configurable 4 5@Configurable 6class Account { 7 // ... 8}
이와 같이 marker interface로 사용하면, Spring은 annotation이 붙은 타입(이 경우
Account)의 새 인스턴스를 동일한 이름의 bean 정의(일반적으로 prototype scope)의
설정을 사용해 구성합니다. (com.xyz.domain.Account) XML을 통해 정의된 bean의 기본
이름은 해당 타입의 fully-qualified name이므로, prototype 정의를 선언하는 편리한
방법은 다음 예제에서 보듯이 id attribute를 생략하는 것입니다:
1<bean class="com.xyz.domain.Account" scope="prototype"> 2 <property name="fundsTransferService" ref="fundsTransferService"/> 3</bean>
사용할 prototype bean 정의의 이름을 명시적으로 지정하고 싶다면, 다음 예제에서 보듯이 annotation 안에서 직접 지정할 수 있습니다:
1package com.xyz.domain; 2 3import org.springframework.beans.factory.annotation.Configurable; 4 5@Configurable("account") 6public class Account { 7 // ... 8}
1package com.xyz.domain 2 3import org.springframework.beans.factory.annotation.Configurable 4 5@Configurable("account") 6class Account { 7 // ... 8}
이제 Spring은 account라는 이름의 bean 정의를 찾아서 새로운 Account 인스턴스를
설정하기 위한 정의로 사용합니다.
또한 autowiring을 사용하여 전용 bean 정의를 전혀 지정하지 않을 수도 있습니다.
Spring이 autowiring을 적용하도록 하려면 @Configurable annotation의 autowire
property를 사용합니다. 타입 또는 이름에 의한 autowiring을 위해
@Configurable(autowire=Autowire.BY_TYPE) 또는
@Configurable(autowire=Autowire.BY_NAME)을 지정할 수 있습니다.
대안으로,
field나 method 수준에서 @Autowired 또는 @Inject를 통해 @Configurable bean에 대한
명시적인 annotation-driven dependency injection을 지정하는 것이 바람직합니다
(자세한 내용은 Annotation-based Container Configuration
을 참조하십시오).
마지막으로, 새로 생성 및 설정된 객체 안의 object reference에 대해
dependencyCheck attribute(예: @Configurable(autowire=Autowire.BY_NAME,dependencyCheck=true))
를 사용하여 Spring dependency checking을 활성화할 수 있습니다. 이 attribute가
true로 설정되면, Spring은 설정 후에 모든 property(primitive나 collection이 아닌)가
설정되었는지 검증합니다.
annotation만 사용해서는 아무 일도 일어나지 않는다는 점에 유의하십시오.
spring-aspects.jar 안의 AnnotationBeanConfigurerAspect가 annotation의 존재를
기반으로 동작합니다. 본질적으로, 이 aspect는 "@Configurable로 annotation된 타입의
새 객체 초기화가 반환된 후, annotation의 property에 따라 Spring을 사용해 새로 생성된
객체를 설정하라"라고 말합니다.
이 문맥에서 "initialization"은 새로 인스턴스화된
객체(예: new operator로 인스턴스화된 객체)뿐 아니라 deserialization 중인
Serializable 객체(예:
readResolve()
를 통해 등)를 의미합니다.
위 문단에서 핵심 구절 중 하나는 "본질적으로(in essence)"입니다. 대부분의 경우,<br>"새 객체 초기화가 반환된 후"라는 정확한 의미면 충분합니다. 이 문맥에서 "after<br>initialization"은 객체가 생성된 이후에 dependency가 주입된다는 뜻입니다. 이는<br>dependency가 클래스의 constructor body에서 사용할 수 없다는 것을 의미합니다.<br>dependency를 constructor body가 실행되기 전에 주입하여 constructor body 안에서<br>사용 가능하게 하려면, 다음과 같이
@Configurable선언에 이를 정의해야 합니다:<br>- Java<br>- Kotlin<br><br>java<br>@Configurable(preConstruction = true)<br><br><br>kotlin<br>@Configurable(preConstruction = true)<br><br><br>다양한 pointcut type의 언어 semantics에 대한 더 많은 정보는 AspectJ의<br>이 appendix와<br>AspectJ Programming Guide에서 확인할 수 있습니다.
이 기능이 동작하려면, annotation이 붙은 타입이 AspectJ weaver로 weaving되어야 합니다.
이를 위해 build-time Ant나 Maven task를 사용할 수 있습니다(예를 들어
AspectJ Development
Environment Guide를 참조) 또는 load-time weaving을 사용할 수도 있습니다
(Load-time Weaving with AspectJ in the Spring Framework
참조). AnnotationBeanConfigurerAspect 자체는 (새 객체를 설정하는 데 사용할
bean factory 참조를 얻기 위해) Spring에 의해 설정되어야 합니다.
관련 설정은 다음과 같이 정의할 수 있습니다:
1@Configuration 2@EnableSpringConfigured 3public class ApplicationConfiguration { 4}
1@Configuration 2@EnableSpringConfigured 3class ApplicationConfiguration
1<beans xmlns="http://www.springframework.org/schema/beans" 2 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 3 xmlns:context="http://www.springframework.org/schema/context" 4 xsi:schemaLocation="http://www.springframework.org/schema/beans 5 https://www.springframework.org/schema/beans/spring-beans.xsd 6 http://www.springframework.org/schema/context 7 https://www.springframework.org/schema/context/spring-context.xsd"> 8 9 <context:spring-configured /> 10 11</beans>
aspect가 설정되기 전에 생성된 @Configurable 객체 인스턴스는 debug log에
메시지가 출력되고 객체 설정이 이루어지지 않습니다. 예를 들어, Spring에 의해
초기화될 때 domain object를 생성하는 Spring 설정 내의 bean이 있을 수 있습니다.
이 경우 depends-on bean attribute를 사용하여 해당 bean이 configuration aspect에
의존함을 수동으로 지정할 수 있습니다. 다음 예제는 depends-on attribute 사용 방법을
보여줍니다:
1<bean id="myService" 2 class="com.xyz.service.MyService" 3 depends-on="org.springframework.beans.factory.aspectj.AnnotationBeanConfigurerAspect"> 4 5 <!-- ... --> 6 7</bean>
runtime에서 해당 semantics에 실제로 의존하려는 것이 아니라면 bean configurer aspect를<br>통한
@Configurableprocessing을 활성화하지 마십시오. 특히 container에 regular<br>Spring bean으로 등록된 bean 클래스에@Configurable을 사용하지 않도록 하십시오.<br>이렇게 하면 container를 통한 초기화와 aspect를 통한 초기화가 한 번씩, 총 두 번의<br>초기화가 발생합니다.
@Configurable Objects@Configurable 지원의 목표 중 하나는 hard-coded lookup과 관련된 어려움 없이
domain object의 독립적인 unit test를 가능하게 하는 것입니다.
@Configurable 타입이 AspectJ에 의해 weaving되지 않은 경우, unit test 중에는
annotation이 아무런 영향을 미치지 않습니다.
테스트 대상 객체에 mock이나 stub
property reference를 설정한 다음 평소처럼 진행하면 됩니다. @Configurable 타입이
AspectJ에 의해 weaving된 경우에도 container 밖에서 여전히 평소처럼 unit test를
수행할 수 있지만, @Configurable 객체를 생성할 때마다 Spring에 의해 설정되지
않았음을 나타내는 warning 메시지를 보게 됩니다.
@Configurable 지원을 구현하는 데 사용되는 AnnotationBeanConfigurerAspect는
AspectJ singleton aspect입니다. singleton aspect의 scope는 static member의 scope와
동일합니다. 즉, 타입을 정의하는 ClassLoader마다 aspect 인스턴스가 하나씩
존재한다는 뜻입니다.
이는 동일한 ClassLoader 계층 내에 여러 application context를
정의하는 경우, @EnableSpringConfigured bean을 어디에 정의할지와
spring-aspects.jar를 classpath의 어디에 둘지 고려해야 함을 의미합니다.
공통 business service, 해당 service를 지원하는 데 필요한 모든 것, 그리고 각 servlet에
대한 하나의 child application context(해당 servlet에 특화된 정의를 포함)를 정의하는
shared parent application context를 가진 전형적인 Spring web application 구성을
생각해 보십시오. 이 모든 context는 동일한 ClassLoader 계층 내에 공존하므로,
AnnotationBeanConfigurerAspect는 이들 중 하나에 대한 참조만 가질 수 있습니다.
이 경우 @EnableSpringConfigured bean을 shared (parent) application context에
정의하는 것을 권장합니다. 이는 domain object에 주입하고자 하는 service를 정의합니다.
결과적으로, @Configurable 메커니즘을 사용하여 child(servlet-specific) context에
정의된 bean에 대한 reference로 domain object를 설정할 수 없습니다(아마도 애초에
그렇게 하고 싶지는 않을 것입니다).
동일한 container 내에서 여러 web application을 배포할 때는 각 web application이
자신의 ClassLoader를 사용하여 spring-aspects.jar 안의 타입을 로드하도록
해야 합니다(예를 들어 spring-aspects.jar를 WEB-INF/lib에 배치함으로써).
spring-aspects.jar를 container-wide classpath에만 추가하여(shared parent
ClassLoader에 의해 로드되도록) 사용하는 경우, 모든 web application이 동일한
aspect 인스턴스를 공유하게 됩니다(아마도 원치 않는 동작일 것입니다).
@Configurable aspect 외에도 spring-aspects.jar에는
@Transactional annotation이 붙은 타입과 method에 대해 Spring의 transaction management를
구동하는 데 사용할 수 있는 AspectJ aspect가 포함되어 있습니다. 이는 주로
Spring container 밖에서 Spring Framework의 transaction 지원을 사용하려는 사용자를
대상으로 합니다.
@Transactional annotation을 해석하는 aspect는 AnnotationTransactionAspect입니다.
이 aspect를 사용할 때는 구현 클래스(또는 그 클래스 내의 method, 또는 둘 다)에
annotation을 붙여야 하며, 해당 클래스가 구현하는 interface(있다면)에는 붙이지
않아야 합니다. AspectJ는 annotation이 interface에 붙은 경우 이를 상속하지 않는다는
Java 규칙을 따릅니다.
클래스에 붙은 @Transactional annotation은 해당 클래스 내의 public operation 실행에
대한 기본 transaction semantics를 지정합니다.
클래스 내의 method에 붙은 @Transactional annotation은(존재하는 경우) 클래스
annotation이 제공하는 기본 transaction semantics를 override합니다. private method를
포함해 어떤 visibility의 method든 annotation을 붙일 수 있습니다. 비-public method
실행에 대한 transaction demarcation을 얻는 유일한 방법은 해당 method에 직접
annotation을 붙이는 것입니다.
Spring Framework 4.2부터
spring-aspects는 표준jakarta.transaction.Transactional<br>annotation에 대해 동일한 기능을 제공하는 유사한 aspect를 제공합니다.<br>자세한 내용은JtaAnnotationTransactionAspect를 확인하십시오.
AspectJ programmer 중 Spring configuration과 transaction management 지원을 사용하고
싶지만 annotation을 사용하고 싶지 않거나 사용할 수 없는 경우를 위해,
spring-aspects.jar에는 자신만의 pointcut 정의를 제공하기 위해 확장할 수 있는
abstract aspect도 포함되어 있습니다.
자세한 내용은 AbstractBeanConfigurerAspect와
AbstractTransactionAspect aspect의 source를 참조하십시오. 예를 들어, 다음 발췌문은
domain model에 정의된 객체의 모든 인스턴스를 fully qualified class name과 일치하는
prototype bean 정의를 사용하여 설정하는 aspect를 어떻게 작성할 수 있는지를
보여줍니다:
1public aspect DomainObjectConfiguration extends AbstractBeanConfigurerAspect { 2 3 public DomainObjectConfiguration() { 4 setBeanWiringInfoResolver(new ClassNameBeanWiringInfoResolver()); 5 } 6 7 // the creation of a new bean (any object in the domain model) 8 protected pointcut beanCreation(Object beanInstance) : 9 initialization(new(..)) && 10 CommonPointcuts.inDomainModel() && 11 this(beanInstance); 12}
Spring application에서 AspectJ aspect를 사용할 때, 이러한 aspect를 Spring으로
설정하고 싶어 하는 것은 자연스러운 기대입니다. AspectJ runtime 자체가 aspect 생성에
책임이 있으며, AspectJ가 생성한 aspect를 Spring으로 설정하는 방법은 aspect에서
사용하는 AspectJ instantiation model(per-xxx 절)에 따라 달라집니다.
대부분의 AspectJ aspect는 singleton aspect입니다. 이러한 aspect의 설정은 쉽습니다.
일반적인 방식으로 aspect 타입을 참조하는 bean 정의를 생성하고,
factory-method="aspectOf" bean attribute를 포함하면 됩니다.
이렇게 하면
Spring이 인스턴스를 직접 생성하려 하지 않고 AspectJ에 요청하여 aspect 인스턴스를
얻도록 보장합니다. 다음 예제는 factory-method="aspectOf" attribute 사용 방법을
보여줍니다:
1<bean id="profiler" class="com.xyz.profiler.Profiler" 2 factory-method="aspectOf"> (1) 3 4 <property name="profilingStrategy" ref="jamonProfilingStrategy"/> 5</bean>
| 1 | factory-method="aspectOf" attribute에 주의하십시오 |
Non-singleton aspect는 설정하기가 더 어렵습니다. 그러나 prototype bean 정의를
생성하고, AspectJ runtime에 의해 aspect 인스턴스가 생성된 후 spring-aspects.jar의
@Configurable 지원을 사용하여 해당 aspect 인스턴스를 설정함으로써 이를 수행할 수
있습니다.
AspectJ로 weaving하려는 일부 @AspectJ aspect(예: domain model 타입에 대한 load-time weaving 사용)와 Spring AOP에서 사용하려는 다른 @AspectJ aspect가 있고, 이들 aspect가 모두 Spring에서 설정되는 경우, Spring AOP @AspectJ auto-proxying 지원에 설정에서 정의된 @AspectJ aspect 중 어떤 정확한 subset을 auto-proxying에 사용할지 알려줘야 합니다.
이를 위해 <aop:aspectj-autoproxy/> 선언 안에 하나 이상의
<include/> element를 사용할 수 있습니다. 각 <include/> element는 이름 패턴을
지정하며, 최소 하나의 패턴과 이름이 일치하는 bean만 Spring AOP auto-proxy
설정에 사용됩니다. 다음 예제는 <include/> element 사용 방법을 보여줍니다:
1<aop:aspectj-autoproxy> 2 <aop:include name="thisBean"/> 3 <aop:include name="thatBean"/> 4</aop:aspectj-autoproxy>
<aop:aspectj-autoproxy/>element의 이름에 현혹되지 마십시오. 이를 사용하면<br>Spring AOP proxy가 생성됩니다. 여기서는 @AspectJ 스타일의 aspect 선언을 사용하지만,<br>AspectJ runtime은 관여하지 않습니다.
Load-time weaving(LTW)은 AspectJ aspect를 Java virtual machine(JVM)에 로드되는 시점에 application의 class file에 weaving하는 과정을 의미합니다. 이 절의 초점은 Spring Framework라는 특정 문맥에서 LTW를 설정하고 사용하는 방법입니다.
이 절은 LTW에 대한 일반적인 소개는 아닙니다. LTW의 세부 사항과 AspectJ만으로 LTW를 설정하는 방법(Spring이 전혀 관여하지 않는 경우)에 대한 전체 내용은 LTW section of the AspectJ Development Environment Guide을 참조하십시오.
Spring Framework가 AspectJ LTW에 제공하는 가치는 weaving 과정에 대해 훨씬 더 세밀한 제어를 가능하게 한다는 점입니다. 'Vanilla' AspectJ LTW는 JVM을 시작할 때 VM argument를 지정하여 활성화되는 Java(5+) agent를 사용해 수행됩니다. 따라서 이는 JVM 전체에 적용되는 설정이며, 어떤 상황에서는 괜찮을 수 있지만 종종 너무 조잡한 설정이 되곤 합니다.
Spring-enabled LTW는 LTW를 ClassLoader별로
활성화할 수 있게 해 주며, 이는 더 세밀하고 전형적인 application server 환경에서
볼 수 있는 'single-JVM-multiple-application' 환경에서 더 의미가 있을 수 있습니다.
또한 특정 환경에서는, 이 지원을 통해
application server의 launch script를 수정하여
-javaagent:path/to/aspectjweaver.jar 또는(이 절의 뒷부분에서 설명하는)
-javaagent:path/to/spring-instrument.jar를 추가할 필요 없이 load-time weaving을
사용할 수 있습니다.
개발자는 일반적으로 launch script와 같은 배포 설정을 담당하는 관리자에게 의존하지 않고, application context를 설정하여 load-time weaving을 활성화합니다.
이제 소개는 이쯤에서 마치고, 먼저 Spring을 사용하는 AspectJ LTW의 간단한 예제를 살펴본 다음, 예제에서 도입된 요소에 대한 자세한 내용을 설명하겠습니다. 전체 예제는 Petclinic sample application based on Spring Framework 를 참조하십시오.
당신이 시스템의 성능 문제 원인을 진단하는 업무를 맡은 application developer라고 가정해 봅시다. profiling tool을 바로 꺼내기보다는, 간단한 profiling aspect를 활성화하여 빠르게 성능 metric을 얻어 보겠습니다. 그런 다음, 곧바로 해당 영역에 세밀한 profiling tool을 적용할 수 있습니다.
여기서 제시하는 예제는 XML 설정을 사용합니다. Java configuration을 사용해<br>@AspectJ를 설정하고 사용할 수도 있습니다. 특히,
<context:load-time-weaver/>의<br>대안으로@EnableLoadTimeWeavingannotation을 사용할 수 있습니다(자세한 내용은<br>아래를 참조하십시오).
다음 예제는 profiling aspect를 보여 주며, 그리 화려하지는 않습니다. 이는 @AspectJ 스타일의 aspect 선언을 사용하는 time-based profiler입니다:
1package com.xyz; 2 3import org.aspectj.lang.ProceedingJoinPoint; 4import org.aspectj.lang.annotation.Aspect; 5import org.aspectj.lang.annotation.Around; 6import org.aspectj.lang.annotation.Pointcut; 7import org.springframework.util.StopWatch; 8import org.springframework.core.annotation.Order; 9 10@Aspect 11public class ProfilingAspect { 12 13 @Around("methodsToBeProfiled()") 14 public Object profile(ProceedingJoinPoint pjp) throws Throwable { 15 StopWatch sw = new StopWatch(getClass().getSimpleName()); 16 try { 17 sw.start(pjp.getSignature().getName()); 18 return pjp.proceed(); 19 } finally { 20 sw.stop(); 21 System.out.println(sw.prettyPrint()); 22 } 23 } 24 25 @Pointcut("execution(public * com.xyz..*.*(..))") 26 public void methodsToBeProfiled(){} 27}
1package com.xyz 2 3import org.aspectj.lang.ProceedingJoinPoint 4import org.aspectj.lang.annotation.Aspect 5import org.aspectj.lang.annotation.Around 6import org.aspectj.lang.annotation.Pointcut 7import org.springframework.util.StopWatch 8import org.springframework.core.annotation.Order 9 10@Aspect 11class ProfilingAspect { 12 13 @Around("methodsToBeProfiled()") 14 fun profile(pjp: ProceedingJoinPoint): Any? { 15 val sw = StopWatch(javaClass.simpleName) 16 try { 17 sw.start(pjp.getSignature().getName()) 18 return pjp.proceed() 19 } finally { 20 sw.stop() 21 println(sw.prettyPrint()) 22 } 23 } 24 25 @Pointcut("execution(public * com.xyz..*.*(..))") 26 fun methodsToBeProfiled() { 27 } 28}
또한 AspectJ weaver에 ProfilingAspect를 우리 클래스에 weaving하고 싶다는 것을
알려 주기 위해 META-INF/aop.xml 파일을 생성해야 합니다. 이 파일 convention,
즉 Java classpath에 META-INF/aop.xml이라는 파일(또는 여러 파일)이 존재하는 것은
표준 AspectJ 방식입니다. 다음 예제는 aop.xml 파일을 보여 줍니다:
1<!DOCTYPE aspectj PUBLIC "-//AspectJ//DTD//EN" "https://www.eclipse.org/aspectj/dtd/aspectj.dtd"> 2<aspectj> 3 4 <weaver> 5 <!-- only weave classes in our application-specific packages and sub-packages --> 6 <include within="com.xyz..*"/> 7 </weaver> 8 9 <aspects> 10 <!-- weave in just this aspect --> 11 <aspect name="com.xyz.ProfilingAspect"/> 12 </aspects> 13 14</aspectj>
AspectJ dump file와 warning과 같은 부작용을 피하기 위해(또한 효율성 측면에서도<br>best practice이므로), 일반적으로
aop.xml예제에서 보듯이 application package에<br>속한 클래스만 weaving하는 것을 권장합니다.
이제 Spring-specific 설정 부분으로 넘어갈 수 있습니다. LoadTimeWeaver를 설정해야
합니다(뒤에서 설명). 이 load-time weaver는 하나 이상의 META-INF/aop.xml 파일에
포함된 aspect 설정을 application의 클래스에 weaving하는 핵심 component입니다.
좋은 점은, 다음 예제에서 볼 수 있듯이 많은 설정이 필요하지 않다는 것입니다 (추가로 지정할 수 있는 옵션이 더 있지만, 이는 뒤에서 자세히 설명합니다):
1<?xml version="1.0" encoding="UTF-8"?> 2<beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xmlns:context="http://www.springframework.org/schema/context" 5 xsi:schemaLocation=" 6 http://www.springframework.org/schema/beans 7 https://www.springframework.org/schema/beans/spring-beans.xsd 8 http://www.springframework.org/schema/context 9 https://www.springframework.org/schema/context/spring-context.xsd"> 10 11 <!-- a service object; we will be profiling its methods --> 12 <bean id="entitlementCalculationService" 13 class="com.xyz.StubEntitlementCalculationService"/> 14 15 <!-- this switches on the load-time weaving --> 16 <context:load-time-weaver/> 17</beans>
이제 필요한 artifact(aspect, META-INF/aop.xml 파일, Spring 설정)가 모두 준비되었으므로,
LTW 동작을 보여 주기 위해 main(..) method를 가진 다음과 같은 driver 클래스를
생성할 수 있습니다:
1package com.xyz; 2 3// imports 4 5public class Main { 6 7 public static void main(String[] args) { 8 ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml"); 9 10 EntitlementCalculationService service = 11 ctx.getBean(EntitlementCalculationService.class); 12 13 // the profiling aspect is 'woven' around this method execution 14 service.calculateEntitlement(); 15 } 16}
1package com.xyz 2 3// imports 4 5fun main() { 6 val ctx = ClassPathXmlApplicationContext("beans.xml") 7 8 val service = ctx.getBean(EntitlementCalculationService.class) 9 10 // the profiling aspect is 'woven' around this method execution 11 service.calculateEntitlement() 12}
마지막으로 해야 할 일이 하나 남았습니다. 이 절의 서두에서 Spring을 사용하여
ClassLoader별로 LTW를 선택적으로 활성화할 수 있다고 말했으며, 이는 사실입니다.
그러나 이 예제에서는 Spring에서 제공하는 Java agent를 사용하여 LTW를 활성화합니다.
앞서 보여 준 Main 클래스를 실행하기 위해 다음 명령을 사용합니다:
1java -javaagent:C:/projects/xyz/lib/spring-instrument.jar com.xyz.Main
-javaagent는
JVM에서 실행되는 프로그램을 instrument하는 agent를 지정하고 활성화하기 위한 flag입니다. Spring Framework는
그러한 agent인 InstrumentationSavingAgent를 제공하며, 이는 앞선 예제에서
-javaagent argument의 값으로 제공된 spring-instrument.jar에 패키징되어 있습니다.
Main 프로그램 실행 결과 출력은 다음 예제와 비슷하게 보입니다.
(calculateEntitlement() 구현에 Thread.sleep(..) 문을 추가하여 profiler가
0 millisecond 이외의 값을 캡처하도록 했습니다(여기서 01234 millisecond는
AOP에 의해 도입된 overhead가 아닙니다). 다음 listing은 profiler를 실행했을 때
얻은 출력을 보여 줍니다:
1Calculating entitlement 2 3StopWatch 'ProfilingAspect': running time (millis) = 1234 4------ ----- ---------------------------- 5ms % Task name 6------ ----- ---------------------------- 701234 100% calculateEntitlement
이 LTW는 full-blown AspectJ를 사용해 수행되므로, Spring bean만을 대상으로 advising하는
것에 제한되지 않습니다. 다음은 Main 프로그램의 약간 변형된 버전으로, 동일한
결과를 얻을 수 있습니다:
1package com.xyz; 2 3// imports 4 5public class Main { 6 7 public static void main(String[] args) { 8 new ClassPathXmlApplicationContext("beans.xml"); 9 10 EntitlementCalculationService service = 11 new StubEntitlementCalculationService(); 12 13 // the profiling aspect will be 'woven' around this method execution 14 service.calculateEntitlement(); 15 } 16}
1package com.xyz 2 3// imports 4 5fun main(args: Array<String>) { 6 ClassPathXmlApplicationContext("beans.xml") 7 8 val service = StubEntitlementCalculationService() 9 10 // the profiling aspect will be 'woven' around this method execution 11 service.calculateEntitlement() 12}
위 프로그램에서 Spring container를 bootstrap한 다음, Spring context 밖에서 완전히
벗어나 StubEntitlementCalculationService의 새 인스턴스를 생성하는 방식을
주목하십시오. profiling advice는 여전히 weaving됩니다.
물론 이 예제는 단순합니다. 그러나 앞선 예제에서 Spring의 LTW 지원의 기본은 모두 소개되었으며, 이 절의 나머지 부분에서는 각 설정과 사용 방식 뒤에 있는 "이유"를 자세히 설명합니다.
이 예제에서 사용된
ProfilingAspect는 기본적이지만 꽤 유용합니다. 이는 개발자가<br>개발 중에 사용할 수 있고, UAT나 production에 배포되는 application build에서는<br>쉽게 제외할 수 있는 development-time aspect의 좋은 예입니다.
LTW에서 사용하는 aspect는 AspectJ aspect여야 합니다. AspectJ 언어 자체로 작성할 수도 있고, @AspectJ 스타일로 aspect를 작성할 수도 있습니다. 이렇게 작성된 aspect는 AspectJ와 Spring AOP 양쪽 모두에서 유효한 aspect가 됩니다.
또한 compile된 aspect class는 classpath에서 사용할 수 있어야 합니다.
META-INF/aop.xmlAspectJ LTW infrastructure는 Java classpath(직접 또는 보통은 jar file 안)에 존재하는
하나 이상의 META-INF/aop.xml 파일을 사용해 설정됩니다. 예를 들어:
1<!DOCTYPE aspectj PUBLIC "-//AspectJ//DTD//EN" "https://www.eclipse.org/aspectj/dtd/aspectj.dtd"> 2<aspectj> 3 4 <weaver> 5 <!-- only weave classes in our application-specific packages and sub-packages --> 6 <include within="com.xyz..*"/> 7 </weaver> 8 9</aspectj>
AspectJ dump file와 warning과 같은 부작용을 피하기 위해(또한 효율성 측면에서도<br>best practice이므로), 일반적으로
aop.xml예제에서 보듯이 application package에<br>속한 클래스만 weaving하는 것을 권장합니다.
이 파일의 구조와 내용은
AspectJ reference
documentation의 LTW 부분에 자세히 설명되어 있습니다. aop.xml 파일은 100% AspectJ이므로,
여기에서 더 이상 설명하지 않습니다.
Spring Framework의 AspectJ LTW 지원을 사용하려면 최소한 다음 library가 필요합니다:
spring-aop.jaraspectjweaver.jar또한 Spring-provided agent to enable instrumentation 을 사용하는 경우에는 다음도 필요합니다:
spring-instrument.jarSpring의 LTW 지원에서 핵심 component는 org.springframework.instrument.classloading
package에 있는 LoadTimeWeaver interface와 Spring 배포본에 함께 제공되는
수많은 구현체입니다. LoadTimeWeaver는 runtime에 하나 이상의
java.lang.instrument.ClassFileTransformers를 ClassLoader에 추가하는 일을
담당하며, 이를 통해 LTW를 포함한 다양한 흥미로운 application을 구현할 수 있습니다.
runtime class file transformation 개념에 익숙하지 않다면, 계속 읽기 전에<br>
java.lang.instrumentpackage에 대한 javadoc API documentation을 확인하십시오.<br>해당 문서는 포괄적이지는 않지만, 이 절을 읽는 동안 참고할 수 있는 핵심 interface와<br>class를 확인할 수 있습니다.
특정 ApplicationContext에 대해 LoadTimeWeaver를 설정하는 일은 한 줄만 추가하면
될 정도로 간단할 수 있습니다. (LTW 지원이 BeanFactoryPostProcessors를 사용하므로,
Spring container로 BeanFactory만 사용하는 것으로는 거의 충분하지 않고
ApplicationContext를 사용해야 한다는 점에 유의하십시오.)
Spring Framework의 LTW 지원을 활성화하려면 다음과 같이 LoadTimeWeaver를
설정해야 합니다:
1@Configuration 2@EnableLoadTimeWeaving 3public class ApplicationConfiguration { 4}
1@Configuration 2@EnableLoadTimeWeaving 3class ApplicationConfiguration
1<beans xmlns="http://www.springframework.org/schema/beans" 2 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 3 xmlns:context="http://www.springframework.org/schema/context" 4 xsi:schemaLocation="http://www.springframework.org/schema/beans 5 https://www.springframework.org/schema/beans/spring-beans.xsd 6 http://www.springframework.org/schema/context 7 https://www.springframework.org/schema/context/spring-context.xsd"> 8 9 <context:load-time-weaver /> 10 11</beans>
위 설정은 LoadTimeWeaver와 AspectJWeavingEnabler 등 여러 LTW-specific
infrastructure bean을 자동으로 정의하고 등록합니다. 기본 LoadTimeWeaver는
DefaultContextLoadTimeWeaver class이며, 이는 자동으로 감지된
LoadTimeWeaver를 데코레이션하려고 시도합니다. "자동으로 감지되는"
LoadTimeWeaver의 정확한 타입은 runtime 환경에 따라 달라집니다.
다음 표는 다양한 LoadTimeWeaver 구현체를 요약한 것입니다:
| Runtime Environment | LoadTimeWeaver implementation |
|---|---|
| Running in Apache Tomcat | TomcatLoadTimeWeaver |
| Running in GlassFish (limited to EAR deployments) | GlassFishLoadTimeWeaver |
| Running in Red Hat’s JBoss AS or WildFly | JBossLoadTimeWeaver |
JVM started with Spring InstrumentationSavingAgent<br>(java -javaagent:path/to/spring-instrument.jar) | InstrumentationLoadTimeWeaver |
Fallback, expecting the underlying ClassLoader to follow common conventions<br>(namely addTransformer and optionally a getThrowawayClassLoader method) | ReflectiveLoadTimeWeaver |
Table 1. DefaultContextLoadTimeWeaver LoadTimeWeavers
위 표에는 DefaultContextLoadTimeWeaver를 사용할 때 자동 감지되는
LoadTimeWeaver만 나열되어 있습니다. 어떤 LoadTimeWeaver 구현체를 사용할지
정확히 지정할 수도 있습니다.
특정 LoadTimeWeaver를 설정하려면, LoadTimeWeavingConfigurer interface를 구현하고
getLoadTimeWeaver() method를 override하면 됩니다(XML에서는 이에 상응하는 설정을
사용). 다음 예제는 ReflectiveLoadTimeWeaver를 지정합니다:
1@Configuration 2@EnableLoadTimeWeaving 3public class CustomWeaverConfiguration implements LoadTimeWeavingConfigurer { 4 5 @Override 6 public LoadTimeWeaver getLoadTimeWeaver() { 7 return new ReflectiveLoadTimeWeaver(); 8 } 9}
1@Configuration 2@EnableLoadTimeWeaving 3class CustomWeaverConfiguration : LoadTimeWeavingConfigurer { 4 5 override fun getLoadTimeWeaver(): LoadTimeWeaver { 6 return ReflectiveLoadTimeWeaver() 7 } 8}
1<beans xmlns="http://www.springframework.org/schema/beans" 2 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 3 xmlns:context="http://www.springframework.org/schema/context" 4 xsi:schemaLocation=" 5 http://www.springframework.org/schema/beans 6 https://www.springframework.org/schema/beans/spring-beans.xsd 7 http://www.springframework.org/schema/context 8 https://www.springframework.org/schema/context/spring-context.xsd"> 9 10 <context:load-time-weaver 11 weaver-class="org.springframework.instrument.classloading.ReflectiveLoadTimeWeaver"/> 12 13</beans>
설정에 의해 정의되고 등록된 LoadTimeWeaver는 잘 알려진 이름인 loadTimeWeaver를
사용하여 나중에 Spring container에서 검색할 수 있습니다. LoadTimeWeaver는
Spring의 LTW infrastructure가 하나 이상의 ClassFileTransformers를 추가하기 위한
메커니즘으로만 존재한다는 점을 기억하십시오.
실제로 LTW를 수행하는
ClassFileTransformer는 org.aspectj.weaver.loadtime package의
ClassPreProcessorAgentAdapter class입니다. weaving이 실제로 어떻게 수행되는지에
대한 세부 사항은 이 문서의 범위를 벗어나므로, 자세한 내용은
ClassPreProcessorAgentAdapter class의 class-level javadoc을 참조하십시오.
마지막으로 설명해야 할 설정 attribute는 aspectjWeaving attribute(XML을 사용할 경우
aspectj-weaving)입니다. 이 attribute는 LTW 활성화 여부를 제어합니다. 이 attribute는
세 가지 값 중 하나를 허용하며, attribute가 존재하지 않을 경우 기본값은
autodetect입니다. 다음 표는 세 가지 가능한 값을 요약한 것입니다:
| Annotation Value | XML Value | Explanation |
|---|---|---|
ENABLED | on | AspectJ weaving이 켜져 있으며, 적절한 경우 aspect가 load-time에 weaving됩니다. |
DISABLED | off | LTW가 꺼져 있습니다. load-time에 어떤 aspect도 weaving되지 않습니다. |
AUTODETECT | autodetect | Spring LTW infrastructure가 하나 이상의 META-INF/aop.xml 파일을 찾을 수<br>있다면 AspectJ weaving이 켜집니다. 그렇지 않으면 꺼집니다. 이것이 기본값입니다. |
Table 2. AspectJ weaving attribute values
이 마지막 절에서는 application server와 web container 같은 환경에서 Spring의 LTW 지원을 사용할 때 필요한 추가 설정을 다룹니다.
Tomcat과 JBoss/WildFly는 local instrumentation이 가능한 일반적인 app ClassLoader를
제공합니다. Spring의 native LTW는 이러한 ClassLoader 구현체를 활용하여 AspectJ
weaving을 제공할 수 있습니다.
앞에서 설명한 대로 load-time weaving을 단순히 활성화하면 됩니다.
특히 JVM launch script를 수정하여
-javaagent:path/to/spring-instrument.jar를 추가할 필요가 없습니다.
JBoss에서는 application이 실제로 시작되기 전에 app server scanning이 class를
로드하는 것을 방지하기 위해 scanning을 비활성화해야 할 수 있습니다. 빠른 해결책은
artifact에 다음 내용의 WEB-INF/jboss-scanning.xml이라는 이름의 파일을 추가하는
것입니다:
1<scanning xmlns="urn:jboss:scanning:1.0"/>
특정 LoadTimeWeaver 구현체가 지원하지 않는 환경에서 class instrumentation이
필요한 경우, JVM agent가 일반적인 해결책입니다.
이러한 경우를 위해 Spring은 Spring-specific(그러나 매우 일반적인) JVM agent인
spring-instrument.jar를 필요로 하는 InstrumentationLoadTimeWeaver를 제공합니다.
이는 일반적인 @EnableLoadTimeWeaving 및 <context:load-time-weaver/> 설정에 의해
자동으로 감지됩니다.
이를 사용하려면 다음 JVM option을 제공하여 Spring agent와 함께 virtual machine을 시작해야 합니다:
1-javaagent:/path/to/spring-instrument.jar
이는 JVM launch script를 수정해야 함을 의미하며, server와 운영 정책에 따라 application server 환경에서는 이를 사용할 수 없게 만들 수도 있습니다. 그렇긴 하지만, standalone Spring Boot application과 같이 one-app-per-JVM 배포에서는 일반적으로 전체 JVM 설정을 어차피 직접 제어합니다.
Programmatic Creation of @AspectJ Proxies
Further Resources