바이트 코드 조작으로 클래스 로딩 직전에도 클래스 파일만 있으면, 정보를 빼오거나 새로운 클래스를 만드는 등 여러가지를 할 수 있음
테스트 코드가 소스코드의 얼만큼을 테스트 했는지에 대한 측정
JaCoCo
pom.xml에 플러그인을 추가
<build>
<plugins>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.4</version>
<executions>
<execution>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>report</id>
<phase>prepare-package</phase>
<goals>
<goal>report</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
cmd창에 "mvn clean verify"를 입력해 메이븐 빌드
커버리지 만족 못하면 빌드 실패하도록 설정(min 50%)
<execution>
<id>jacoco-check</id>
<goals>
<goal>check</goal>
</goals>
<configuration>
<rules>
<rule>
<element>PACKAGE</element>
<limits>
<limit>
<counter>LINE</counter>
<value>COVEREDRATIO</value>
<minimum>0.50</minimum>
</limit>
</limits>
</rule>
</rules>
</configuration>
</execution>
바이트 코드 조작으로 코드에 포인트를 심어 해당 포인트를 거칠때마다 카운트를 한다. 이제부터 이러한 툴이 어떤식으로 동작하는지 알아보려한다.
마술코드
public class Moja{
public String pullOut(){
return "";
}
}
public class Masulsa{
public static void main(String[] args){
System.out.println(new Moja().pullOut());
}
}
출력
공백 출력
바이트 코드 조작 라이브러리
클래스 로더는 클래스를 읽을 때, 기존의 Moja
라는 클래스를 읽고 ByteBuddy로 만든 Moja
를 읽어들인다. 조작전 Moja가 먼저 로딩되었기에 그대로 실행하면 조작전 클래스의 메서드가 실행될 것이다.
ByteBuddy를 이용해 위 마술코드
를 다시 작성한다.
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
<version>1.10.1</version>
</dependency>
우선, 위 의존성을 추가한다.
public class Masulsa{
public static void main(String[] args){
try{
new ByteBuddy().redefine(Moja.class)
.method(named("pullOut")).intercept(FixedValue.value("Rabbit!"))
.make().saveIn();//saveIn안에는 Moja 클래스의 경로를 넣어주면 됨. classes까지!
}catch(IOException e){
e.printStackTrace();
}
System.out.println(new Moja().pullOut());
}
}
출력
Rabbit!
바이트코드 실행 -> 바이트코드 제거 후 모자 클래스의 pullOut 메소드 실행 -> 토끼등장!
=> 클래스 로딩 순서에 의존적
클래스 로딩 순서에 상관없이 이같이 구현하기 위해서는?
Agent 생성 -> .jar파일 만듦 -> JVM option에 .jar파일 경로 추가
클래스 로딩시에 적용되기 때문에 클래스 파일 자체가 바뀌지는 않는다.(기존 코드를 건드리지 않음) 로딩시 자바 에이전트를 거쳐 변경된 바이트 코드를 읽어오고, 이를 메모리에 적재한다.
Javaagent 붙여서 사용하기
Javaagent를 사용한 마술코드
public class MasulsaAgent {
public static void premain(String agentArgs, Instrumentation inst) {
new AgentBuilder.Default()
.type(ElementMatchers.any())
.transform((builder, typeDescription, classLoader, javaModule) -> builder.method(named("pullOut")).intercept(FixedValue.value("Rabbit!"))).installOn(inst);
}
}
이렇게 Agent를 만들고, 이를 .jar로 패키징 하면서 특정 값을 넣어줘야 한다.
밑에와 같이 <build>
를 추가하고 <manifestEntries>
안에 <Premain-Class>
이하 것들을 추가한다.(manifest를 jar 플러그인으로 만들기 위해)
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.alwayslee.MasulsaAgent</Premain-Class>
<Can-Redefine-Classes>true</Can-Redefine-Classes>
<Can-Retransform-Classes>true</Can-Retransform-Classes>
</manifestEntries>
</archive>
</configuration>
</plugin>
</plugins>
</build>
Javaagent JAR 파일 만들기
mvn clean packaging
JVM option
프로그램 분석
클래스 파일 생성
자바 코드의 변경 없이 변경 사항 적용 가능
.......
스프링이 컴포넌트 스캔을 하는 방법(asm)
참고 영상 및 툴
ASM, Javassist, ByteBuddy, CGlib