잊지 않겠습니다.

Spring을 사용할 때, Proxy mode에서의 @Transactional의 문제점에 대해서 전 글에서 알아봤습니다.

Proxy 모드에서의 가장 큰 문제는 @Transaction이 적용되지 않은 method 내부에서의 @Transaction의 호출입니다. 이 문제를 해결하기 위해서는 Proxy mode에서의 @Transaction이 아닌, AspectJ mode에서 @Transaction을 사용해줘야지 됩니다.

AspectJ mode는 2가지 방법을 제공합니다.

Load-Time Weaver

객체를 Load 할때, AspectJ에 의해서 wearving된 객체를 넘겨주는 방식입니다. 아래와 같은 방식으로 동작하게 됩니다.

  1. application context에 로드된 객체의 loading
  2. aspectj weaver에 의한 객체 weaving (@Transaction annotation이 있는 class, method에 대한 transaction 처리가 된 객체로 변경)
  3. 객체의 이용

위 순서를 보시면 아실 수 있듯이, 이는 객체의 사용에 대해서 약간의 performance의 하락을 가지고 오게 됩니다. application context에서 객체를 load 할 때, aspectj weaver에서 하는 또 다른 일들을 지정하게 됩니다.

Compile-Time Weaver

객체를 Load 할 때, 위와 같은 문제가 있기 때문에, compile 시에 aspectj에서 간섭해서 필요한 객체에 weaving을 시켜서 class를 만들어내는 방식입니다. 이렇게 되면, application context의 load시에 다른 절차가 없기 때문에, performance의 하락도 없는 거의 완벽한 방법으로 구성이 가능합니다.

LTW vs CTW

Load-Time Weaver의 단점은 다음과 같습니다.

  1. application context에 객체가 로드될 때, aspectj weaver와 spring-instrument에 의한 객체 handling이 발생하기 때문에 performance가 저하된다.
  2. web container의 실행시, LTW를 위한 설정이 필요하다.

반면에 Compile-Time Weaver의 단점은 다음과 같습니다.

  1. 개발환경의 구성이 어렵다.
  2. lombok과 같은 compile시에 간섭하는 여러 plugin들과의 매우 다채로운 충돌이 발생한다. 특히 lombok과는 같이 사용하지 못한다고 생각해도 과언이 아니다.

LTW는 운영상의 문제를 발생시킬 수 있고, CTW는 개발상의 문제를 발생시킬 수 있다는 생각이 듭니다. (이런 생각이 들면 무조건 CTW로 가야지 되긴 하는데….;;;)

LTW를 이용한 @Transaction의 처리

먼저 DomainConfiguration에 LTW를 이용한 @Transaction을 다음과 같이 지정해줍니다.

@EnableTransactionManagement(mode = AdviceMode.ASPECTJ, order = 0)

그리고, LTW를 이용하기 위해서 LTW를 활성화시켜야지 됩니다. LTW활성화는 다음과 같습니다.

@EnableLoadTimeWeaving

이것만으로 끝이 아닙니다. LTW는 반드시 다음 jvm option을 가져야지 됩니다. 위에서 서술했듯이, 객체가 로드 될 때, aspectj weaver와 spring-instrument가 각각 객체에 대한 처리를 해줘야지 됩니다. 먼저 aspectj weaver는 실질적으로 일을 하는 객체들이 모여있고, spring-instrument의 경우에는 aspectj weaver에 class loader를 위임하는 일을 맡아서 하게 됩니다. jvm argument에 다음 option을 추가시켜줍니다.

-javaagent:/fullpath/aspectjweaver-1.8.1.jar
-javaagent:/fullpath/spring-instrument-4.0.6.RELEASE.jar

aspectjweaver와 spring-instrument의 뒤에 붙는 버젼은 aspectj와 spring의 버젼과 동일합니다.

intelliJ

intelliJ를 사용하고 있고, JUnit test를 돌리는 경우에는 Run/Debug Configuration에 다음 설정을 추가해줘야지 됩니다.

VM Option에 jvm argument를 default로 넣어주고, 실행시 언제나 사용하도록 구성되어야지 됩니다.

gradle

gradle의 test 시에 역시 다음 jvmargs가 필요하기 때문에 다음과 같은 설정이 필요합니다.

    test {
        jvmArgs '-javaagent:/weavers/spring-instrument-4.0.6.RELEASE.jar ' +
                '-javaagent:/weavers/aspectjweaver-1.8.1.jar'
    }
tomcat

tomcat에서 LTW를 사용하기 위해서는 2가지 방법이 있습니다. 특정 application context에서만 사용할 수 도 있고, 모든 application context에서 사용할 수 있습니다.

특정 application context에서만 LTW를 이용

먼저, spring-instrument.jar파일을 tomcat의 lib 폴더 안에 copy시켜줍니다. 그 후, application context의 context.xml안에 다음 내용을 추가합니다.

<Context path="/ltwdemo">
    <Loader loaderClass="org.springframework.instrument.classloading.tomcat.TomcatInstrumentableClassLoader"/>
</Context>
모든 application context에서 LTW를 이용

tomcat 시작시, jvmargs에 -javaagent:/weavers/spring-instrument-4.0.6.RELEASE.jar를 추가해주면 됩니다.

CTW를 이용한 @Transaction의 처리

CTW를 이용하는 경우, 이는 compile시에 처리하는 것이기 때문에 code상의 변화는 거의 없습니다. @EnableLoadTimeWeaver만을 제거시켜주고, mode를 AspectJ로 설정해주면 됩니다.

@EnableTransactionManagement(mode = AdviceMode.ASPECTJ, order = 0)

우리가 개발을 할때, compile을 하는 도구는 거의 2가지입니다. IDE와 build tool(maven, gradle, ant)입니다.

IntelliJ

setting의 compile option을 다음과 같이 변경합니다.

  • Use compiler : Ajc
  • Path to Ajc compiler : AspectJtools.jar 위치 지정
  • Command line parameters : -1.8 (JavaVersion 설정)
gradle

CTW에 대해서 open source로 plugin이 존재합니다. (https://github.com/eveoh/gradle-aspectj)
사용방법이 조금 까다롭습니다. 주의점은 다음과 같습니다.

  • ext.aspectjVersion property가 apply plugin:aspectj 보다 먼저 선언되어야지 됩니다. (파일 위치상에서 line이 더 위여야지 됩니다.)
  • spring-aspectj component가 아래와 같이 두번 선언되어야지 됩니다.
      aspectpath "org.springframework:spring-aspects:${rootProject.ext.springVersion}"
      compile "org.springframework:spring-aspects:${rootProject.ext.springVersion}"
    

다음은 CTW가 적용된 build.gradle의 전체 내용입니다.

apply plugin: 'java'

sourceCompatibility = 1.8
targetCompatibility = 1.8

version = '1.0'
buildscript {
    repositories {
        maven {
            url "https://maven.eveoh.nl/content/repositories/releases"
        }
    }
    dependencies {
        classpath "nl.eveoh:gradle-aspectj:1.4"
    }
}

repositories {
    mavenCentral()
}

ext {
    javaVersion = "1.8"
    springVersion = "4.0.6.RELEASE"
    springjpaVersion = "1.6.0.RELEASE"
    querydslVersion = "3.3.2"
    hibernateVersion = "4.3.4.Final"
    springsecurityVersion = "3.2.4.RELEASE"
    aspectjVersion = '1.8.1'
}

apply plugin: 'aspectj'
dependencies {
    compile 'org.slf4j:slf4j-api:1.7.6'
    compile 'org.slf4j:jcl-over-slf4j:1.7.6'
    compile 'ch.qos.logback:logback-classic:1.0.13'
    compile 'ch.qos.logback:logback-core:1.0.13'

    compile "org.springframework:spring-context:${rootProject.ext.springVersion}"
    aspectpath "org.springframework:spring-aspects:${rootProject.ext.springVersion}"
    compile "org.springframework:spring-aspects:${rootProject.ext.springVersion}"
    compile "org.springframework.data:spring-data-jpa:$rootProject.ext.springjpaVersion"
    compile group: 'org.apache.httpcomponents', name: 'httpclient', version:'4.2.5'
    compile 'org.apache.commons:commons-lang3:3.3.2'

    compile 'org.aspectj:aspectjrt:1.8.1'
    compile 'org.aspectj:aspectjtools:1.8.1'
    compile 'org.aspectj:aspectjweaver:1.8.1'

    testCompile "junit:junit:4.11"
    testCompile 'org.mockito:mockito-core:1.9.5'
    testCompile 'org.hamcrest:hamcrest-all:1.3'
    testCompile "org.springframework:spring-test:${rootProject.ext.springVersion}"

    compile 'mysql:mysql-connector-java:5.1.31'
}

Summary

LTW와 CTW를 이용한 @Transaction에 대해서 정리해봤습니다. 이상하게 인터넷에서 대부분의 코드가 @EnableTransactionManagement에서 mode를 바꾸면 된다. 식의 글만 있고, 명확히 어떤 일들을 해줘야지 되는지 적혀 있지 않아서 한번 정리해볼 필요성을 느껴서 작성하게 되었습니다. CTW가 모든 면에서 우월성을 가지고 있지만, 저는 lombok없는 개발은 어떻게 할지 잘 모르겠다는 생각까지 들 정도로 중독되어서… 걱정중입니다.; lombok과 CTW를 같이 사용할 방법에 대한 고민이 좀 더 필요할 것 같습니다.

Posted by Y2K
,