아무것도 없는 Box에서 “Icecream”을 꺼내기
바이트코드 조작 라이브러리
ASM: https://asm.ow2.io/ 어려움..
Javassist: https://www.javassist.org/ 조금 덜 어려움..
ByteBuddy: https://bytebuddy.net/#/
ByteBuddy maven dependency 추가
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
<version>1.10.1</version>
</dependency>
Box.java
public class Box {
public String pullOut() {
return "";
}
}
App.java
public class App {
public static void main( String[] args ) {
// try {
// new ByteBuddy()
// .redefine(Box.class).method(ElementMatchers.named("pullOut")).intercept(FixedValue.value("icecream!"))
// .make().saveIn(new File("/Users/jueunlee/Workspaces/Workspace_Spring/bytebuddy.ex/target/classes/"));
// } catch (IOException e) {
// e.printStackTrace();
// }
System.out.println(new Box().pullOut());
}
}
결과 화면
Box의 java 소스는 그대로이나, claas 파일의 바이트 코드를 변경시킴. Box 인스턴스 생성 시에는 이미 컴파일된 클래스 파일을 참조하므로, 변경된 바이트 코드가 실행됨. 그 결과 Icecream 🍦이 나온다!!
로딩 시에 생길 수 있는 이슈..
로더가 읽어드리는 정보
FQCN .. packge, full package 경로, class name, + classloader!!
이 경우 box라는 클래스는 하나지만, JVM 안에서는 full package 경로, name 까지 같은 호환이 되지 않은 class가 두 개.. !!
많은 걸 할 수 있지 않을까ㅏㅏ?
한 번 읽은 Box.java 파일은 다시 클래스 로딩을 하지 않는다.
public class App {
public static void main( String[] args ) {
ClassLoader classLoader = Box.class.getClassLoader();
TypePool typePool = TypePool.Default.of(classLoader);
try {
new ByteBuddy().redefine(typePool.describe("me.jueun.bytebuddy.ex.Box").resolve(),
ClassFileLocator.ForClassLoader.of(classLoader))
.method(ElementMatchers.named("pullOut")).intercept(FixedValue.value("icecream!"))
.make().saveIn(new File("/Users/jueunlee/Workspaces/Workspace_Spring/bytebuddy.ex/target/classes/"));
} catch (IOException e) {
e.printStackTrace();
}
System.out.println(new Box().pullOut());
}
}
그러나 클래스 실행 순서에 의존적..
Javaagent JAR 파일 만들기
- https://docs.oracle.com/javase/8/docs/api/java/lang/instrument/package-summary.html
- 붙이는 방식은 시작시 붙이는 방식 premain과 런타임 중에 동적으로 붙이는 방식 agentmain이 있다.
- Instrumentation을 사용한다.
Javaagent 붙여서 사용하기
- 클래스로더가 클래스를 읽어올 때 javaagent를 거쳐서 변경된 바이트코드를 읽어들여 사용한다.
MasulsaAgent.java
public class MasulsaAgent {
public static void premain( String agentAgrs, Instrumentation inst ) {
new AgentBuilder.Default().type(ElementMatchers.any()).transform(( builder, typeDescription, classLoader, module ) -> builder.method(ElementMatchers.named("pullOut")).intercept(FixedValue.value("Icecream!"))).installOn(inst);
}
}
pom.xml
<dependencies>
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
<version>1.10.1</version>
</dependency>
</dependencies>
<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>me.jueun.javaagent.ex.MasulsaAgent</Premain-Class>
<Can-Redefine-Classes>true</Can-Redefine-Classes>
<Can-Retransform-Classes>true</Can-Retransform-Classes>
</manifestEntries>
</archive>
</configuration>
</plugin>
</plugins>
</build>
JVM option 으로 해당 jar파일을 사용하여 실행하도록...
Javaagent 적용
-javaagent:/Users/keesun/workspace/MasulsaJavaAgent/target//Users/jueunlee/Workspaces/Workspace_Spring/javaagent.ex/target/javaagent.ex-0.0.1-SNAPSHOT.jar
바이트코드 조작하여 할 수 있는 것!
프로그램 분석
- 코드에서 버그 찾는 툴
- 코드 복잡도 계산
클래스 파일 생성
- 프록시 ( spring AOP, hibernate LAZY loading 객체, Mock 대상 객체 )
- 특정 API 호출 접근 제한
- 스칼라 같은 언어의 컴파일러
그밖에도 자바 소스 코드 건리지 않고 코드 변경이 필요한 여러 경우에 사용할 수 있다.
- 프로파일러 (newrelic) 분산 환경에서의 프로파일러 (pinpoint 같은..?)
애플리케이션이 메모리는 얼마나 쓰는 지, 쓰레드는 몇 개인 지, 어떤 쓰레드가 바쁜 지, 얼마나 일 하고 있는 지... - 최적화
- 로깅
- ...
스프링이 컴포넌트 스캔을 하는 방법 (asm)
- 컴포넌트 스캔으로 빈으로 등록할 후보 클래스 정보를 찾는데 사용
- ClassPathScanningCandidateComponentProvider -> SimpleMetadataReader
- ClassReader와 Visitor 사용해서 클래스에 있는 메타 정보를 읽어온다.
참고
- https://www.youtube.com/watch?v=39kdr1mNZ_s
- ASM, Javassist, ByteBuddy, CGlib
'JAVA' 카테고리의 다른 글
make DI Framework by reflection (0) | 2019.09.29 |
---|---|
Reflection (0) | 2019.09.29 |
Byte Code (0) | 2019.09.28 |
Class Loader (0) | 2019.09.28 |
JVM (0) | 2019.09.28 |