Java

Spring Boot LTW(Load-time Weaving) 활성화 하기

호랑범고래 2024. 11. 18. 23:00
반응형

 

Spring AOP는 기본 Runtime Weaving(RTW)을 사용한다.

 

Weaving: linking aspects with other application types or objects to create an advised object. This can be done at compile time (using the AspectJ compiler, for example), load time, or at runtime. Spring AOP, like other pure Java AOP frameworks, performs weaving at runtime.
Aspect를 다른 애플리케이션 유형이나 객체와 연결하여 **조언된 객체(advised object)**를 생성하는 과정입니다. 이는 컴파일 타임(예: AspectJ 컴파일러 사용), 로드 타임, 또는 런타임에서 수행할 수 있습니다. Spring AOP는 다른 순수 Java AOP 프레임워크처럼
런타임에서 **위빙(weaving)**을 수행합니다.

 

Weaving에 대해서는 3가지 방법이 있고 언제 수행하느냐에 따라 Runtime, Load-time, Compile-time으로 나뉘게 된다.

 

Compile-time weaving (e.g., with AspectJ compiler) modifies the bytecode before the application is run. It's more powerful and can apply aspects to all join points (including private methods), but requires special tools and extra compilation steps.
Load-time weaving allows aspects to be applied during the class loading process. It’s often used with Java agents or custom class loaders.
Runtime weaving (e.g., used by Spring AOP) applies aspects dynamically during the execution of the program. It is easy to configure and often the most flexible approach for typical Java applications, although it may have performance overhead due to proxying.

 

 Spring Boot Application을 개발하면서 모든 객체가 Bean이면 문제가 없겠지만, 순수 Java객체를 Bean으로 등록하지 않고 사용하는 경우는 이 AOP가 동작하지 않는다.

 이러할 때는 Load-time Weaving을 사용해야 하고 프로젝트 코드, 의존성 추가 등 몇가지 작업이 필요하다.

간단한 Gradle 프로젝트와 함께 활성화 방법에 대해서 알려주고자 한다.

 

기본 환경

Java 21

Gradle 8.10

Spring Framework 6.1.X

 

작업 순서

1. Gradle 종속성 추가하기

   aspectweaver, spring-instrument를 jvm argument로 넘겨주기 위한 작업이다.

   application 실행 시 ./gradlew bootRun 명령어를 이용해야하고 IDE에서는 별도로 jvm argument로 -javaagent:{jar파일경로} 를 설정해주어야 동작한다.

plugin {
    id("org.springframework.boot:spring-boot-starter-aop") version "1.1.4"
}

dependency {
    implementation("org.springframework:spring-aspects")
    implementation("org.springframework:spring-instrument")
    implementation("org.springframework.boot:spring-boot-starter-aop")
}

tasks.getByName<BootRun>("bootRun") {
    doFirst {
        project.configurations.runtimeClasspath.get().forEach {
            if(it.name.contains("spring-instrument")) {
                print(it.absolutePath)
                jvmArgs("-javaagent:${it.absolutePath}")
            }
            if(it.name.contains("aspectjweaver")) {
                print(it.absolutePath)
                jvmArgs("-javaagent:${it.absolutePath}")
            }
        }
    }
}

tasks.withType<Test>().configureEach {
    useJUnitPlatform()

    doFirst {
        project.configurations.runtimeClasspath.get().forEach {
            if(it.name.contains("spring-instrument")) {
                print(it.absolutePath)
                jvmArgs("-javaagent:${it.absolutePath}")
            }
            if(it.name.contains("aspectjweaver")) {
                print(it.absolutePath)
                jvmArgs("-javaagent:${it.absolutePath}")
            }
        }
    }
}

 

2. LoadTimeWeaving Annotation도 활성화 해주어야 한다.

@Configuration
@EnableSpringConfigured
@EnableLoadTimeWeaving
public class AppConfig {
}

 

3.  간단히 덧셈을 하는 계산기를 하나 만들어 보면 다음과 같이 표현이 가능하다.

package com.example.aop;

public class Calculator {

    public long sum(int x, int y) {
        long ret = 0;
        while (x <= y) {
            ret +=x;
            x += 1;
        }
        return ret;
    }
}

 

@RestController
public class MyController {

    @RequestMapping(value = "/sum")
    public ResponseEntity<String> sum() {
        Calculator calculator = new Calculator();

        long ret = calculator.sum(1, 10000000);
        return ResponseEntity.ok(String.valueOf(ret));
    }
}

 

4. 예제로 계산시간을 알려주는 aspect를 작성하면

@Aspect
@Component
public class CalculatorAdvise {

    @Around("execution(* com.example.aop.Calculator.sum(..))")
    public Object aroundSum(ProceedingJoinPoint pjt) throws Throwable {
        StopWatch sw = new StopWatch();
        sw.start();

        Object result = pjt.proceed();

        sw.stop();
        System.out.println("sw.getTotalTimeMillis() = " + sw.getTotalTimeMillis());

        return result;
    }
}

 

5.  ltw를 쓰게 되면 aop.xml을 resources/META-INF 아래 생성해주고 아래와 같은 규칙으로 작성해 주어야 한다.

<!DOCTYPE aspectj PUBLIC "-//AspectJ//DTD//EN" "https://www.eclipse.org/aspectj/dtd/aspectj.dtd">
<aspectj>
    <weaver options="-Xset:weaveJavaxPackages=true">
        <!-- only weave classes in our application-specific packages and sub-packages -->
        <include within="com.example.aop..*"/>
    </weaver>

    <aspects>
        <!-- weave in just this aspect -->
        <aspect name="com.example.aop.aspect.CalculatorAdvise"/>
    </aspects>
</aspectj>

 

 

최종 실행 결과. 

curl localhost:8080/sum

 

 Bean이 아닌 순수 자바 객체에도 Aspect가 잘 적용 된 것을 알 수 있다.

관련 소스는 아래에 공개되어 있다.

https://github.com/James-b-K/java-aspectweaver

반응형