-
[Java] 바이트코드 조작하기 (JavaAgent, JVM, Jacoco)프로그래밍 언어/Java 2022. 6. 6. 21:50해당 포스팅은 백기선님의 [더 자바, 코드를 조작하는 다양한 방법] 을 토대로 작성된 글입니다.
코드 커버리지는 어떻게 측정할까? (Jacoco 동작원리)
Jacoco 를 사용해 보신 적이 있으신가?
소스코드에 대한 테스트 케이스가 얼마나 작성되어
코드를 얼마나 커버했는지 측정하는 라이브러리인데
아래와 같이 if 문 실행까지 체크가 가능하다.https://frozenpond.tistory.com/72 어떻게 플러그인하나를 추가했다고
우리가 작성한 코드를 인식하고 그에 대한 테스트가 이루어졌는지 알 수 있을까?
심지어 우리 코드(.java 파일
,.class 파일
)는 변경이 되지 않았는데 말이다!
이는 바로 JVM 힙영역(메모리)에 클래스정보가 올라가기전에
클래스로드 시점에 인터셉트 하여 바이트코드를 조작하기 때문이다.
때문에 프로그램을 실행하면서 컴파일한 .class 파일(바이트코드)도 변경되지 않고
Jacoco 의 로직이 추가되는 것이다!
JVM 힙 메모리영역에 올라간 바이트코드는 우리가 컴파일한 .class 파일과는 다르다.
바이트코드는 클래스가 로딩되기 직전에도 이미 컴파일단계에서 .class 파일이 만들어지기 때문에 조작가능하다.
이렇게 기존 코드를 건드리지 않으며 추가 목적을 달성하는 것을 Transparent 하다고 한다.
Java Agent 를 사용하여 클래스로드 시점에 바이트코드 조작하기
바이트코드를 조작당할 Client 프로젝트 - [Project 1]
아래와 같이 간단한 구조로 만들었다.
public class HomePage { public String render(){ System.out.println("HomePage 가 렌더링 되었습니다."); return ""; } } public class MyApp { public static void main(String[] args) { HomePage homePage = new HomePage(); System.out.println(homePage.render()); System.out.println(homePage.render()); } }
조작 전 실행결과 - [Project 1]
콘솔에 빨간색으로 강조한 영역이 보이는가?
사실 우리의 코드는 이미 조작당하고 있다. (ㄷㄷㄷ)
IntelliJ 도 JavaAgent 를 사용하여 우리의 코드를 조작하고 있었다...바이트코드를 조작할 jar 파일 만들기 - [Project 2]
새로운 프로젝트를 생성해 바이트코드를 조작할 jar 파일을 만들어야 한다.
premain 은 JavaAgent를 로딩할 때 필요한 메서드이며, JavaAgent 는 ClassLoad 시점에 실행된다.
부족하지만 코드 커버리지를 측정하듯이
해당 함수가 호출될때 호출이 몇번되었는지
체크하도록 바이트코드를 조작해보았다.MyAgent.java - [Project 2]
package com; public class MyAgent { public static void premain(String agentArgs, Instrumentation inst){ new AgentBuilder.Default() .type(ElementMatchers.any()) // 모든 타입에 대하여 .transform(new AgentBuilder.Transformer() { @Override public DynamicType.Builder<?> transform(DynamicType.Builder<?> builder, TypeDescription typeDescription, ClassLoader classLoader, JavaModule javaModule) { return builder.method(named("render")) // render() 메서드에 대해 .intercept(MethodDelegation.to(MyInterceptor.class)); } }).installOn(inst); } public static class MyInterceptor { static int callCount; public static String intercept(@SuperCall Callable<String> zuper) throws Exception { zuper.call(); callCount++; return callCount + " 번 호출하셨네요. 나는 다 알아요 ㅎㅎ"; } } }
pom.xml 추가 - [Project 2]
// 바이트코드를 조작하는 라이브러리 <dependencies> <dependency> <groupId>net.bytebuddy</groupId> <artifactId>byte-buddy</artifactId> <version>1.10.1</version> </dependency> </dependencies> // jar 로 만들 플러그인 <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <version>3.1.2</version> <configuration> <archive> <index>true</index> <manifest> <addClasspath>true</addClasspath> </manifest> <manifestEntries> <mode>development</mode> <url>${project.url}</url> <key>value</key> <Premain-Class>com.MyAgent</Premain-Class> // 해당프로젝트에서 premain을 사용하는 클래스 위치 <Can-Redefine-Classes>true</Can-Redefine-Classes> <Can-Retransform-Classes>true</Can-Retransform-Classes> </manifestEntries> </archive> </configuration> </plugin> </plugins> </build>
.jar 파일로 만들기 - [Project 2]
해당 명령어를 입력하여 jar 파일로 만든다.
mvn clean package
바이트코드를 조작하기 - [Project 1]
VM 옵션 추가 - [Project 1]
-javaagent:{jar파일경로}
를 입력한다pom.xml 추가 - [Project 1]
bytebuddy 를 추가하는 이유는 agent 코드가 bytebuddy 로 만들어졌기 때문이다.
Spring, Hibernate 등 유명라이브러리는
본인들 라이브러리내에 bytebuddy 같은 바이트코드조작 라이브러리를 포함시켜놓았다.<dependencies> <dependency> <groupId>net.bytebuddy</groupId> <artifactId>byte-buddy</artifactId> <version>1.10.1</version> </dependency> </dependencies>
실행결과
대충 비슷하게 Jacoco 흉내를 내보았다.
이런식으로 기존 코드를 변경하지 않고,
클래스로드시점에 바이트코드를 조작하여 JVM 힙메모리에 올려놓는 것이다.
# Reference
[Spring] Jacoco - 코드 커버리지 체크하기(TDD)
# 코드 커버리지(Code Coverage)란? 코드 커버리지는 소프트웨어의 테스트를 논할 때 얼마나 테스트가 충분한가를 나타내는 지표 소프트웨어 테스트를 진행했을 때 코드 자체가 얼마나 실행되었냐
developer-ping9.tistory.com
더 자바, 코드를 조작하는 다양한 방법 - 인프런 | 강의
여러분이 사용하고 있는 많은 자바 라이브러리와 프레임워크가 "어떻게" 이런 기능을 제공할 지 궁금한적 있으신가요? 이번 강좌를 통해 자바가 제공하는 다양한 코드 또는 객체를 조작하는 방
www.inflearn.com
java.lang.instrument (Java Platform SE 8 )
Package java.lang.instrument Description Provides services that allow Java programming language agents to instrument programs running on the JVM. The mechanism for instrumentation is modification of the byte-codes of methods. Package Specification An agent
docs.oracle.com
[Java] JVM 이 무엇인지, 어떻게 작동하는지 대충 훑어보자! (feat. 클래스로더)
싱글톤패턴 중 Initialization on demand holder idiom 라는 구현 기법이 있는데 해당 기법은 JVM 의 클래스로더 매커니즘을 이용한 방식이라 한다. 스프링을 사용할 때도 종종 ClassNotFoundException 이 터질 때..
developer-ping9.tistory.com
728x90'프로그래밍 언어 > Java' 카테고리의 다른 글
[Java] 클래스 로드와 클래스 초기화 (StaticInnerClass, InnerClass, MemoryLeak) (0) 2022.11.06 [Java/Kotlin] Static nested class 와 Inner Class 중 무엇을 써야할까? (0) 2022.11.06 [Java] JVM 이 무엇인지, 어떻게 작동하는지 대충 훑어보자! (feat. 클래스로더) (1) 2022.05.29 [Java/OS] 세마포어(Semaphore)에 관하여 (2) 2022.03.18 [Java] ThreadLocal 이 필요할 때 (feat. Thread, Thread-Safe, OS, Spring Security) (0) 2022.03.15