[Java] 바이트코드 조작

YulHee Kim·2021년 12월 20일
0

Java

목록 보기
7/8

백기선님의 더 자바, 코드를 조작하는 다양한 방법을 수강한 후 정리해보았습니다.

[ 바이트코드 조작]

바이트 코드를 수정하거나 생성하는 대표적인 예제인 코드커버리지 툴을 이용하여 살펴보겠습니다.

💡 코드커버리지는 어떻게 측정하는 걸까?

코드커버리지에 대해 알아보기 위해 간단한 예제를 적어보겠습니다.

public class Moim {
    
    int maxNumberOfAttendees;  //최대 참가자
    
    int numberOfEnrollment;   // 참가자 수
    
    public boolean isEnrollmentFull() {
        
        if (maxNumberOfAttendees == 0) {
            return false;
        }
    
        if (numberOfEnrollment < maxNumberOfAttendees) {
            return false;
        }
        
        return true;
    }

}

Moin에 대한 Test코드를 짜보겠습니다.

public class MoimTest {

    @Test
    public void isFull() {
        Moim moim = new Moim();
        moim.maxNumberOfAttendees = 100;
        moim.numberOfEnrollment = 10;
        Assert.assertFalse(moim.isEnrollmentFull());
             
    }

}

코드 커버리지는 이런 테스트코드를 위 소스코드에 대해 얼만큼 테스트했느냐를 확인하는 것입니다. 툴 중에 JaCoCo를 사용해보겠습니다.

플러그인 추가

<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>

메이븐 빌드

mvn clean verify

target에 site밑에 jacoco에서 index.html을 run하면 커버리지를 확인할 수 있습니다.

커버리지 만족 못할시 빌드 실패하도록 설정

       <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>

COVEREDRATIO를 수정하면됩니다.

💡 모자에서 토끼를 꺼내는 마술

아무것도 없는 Moja에서 "Rabbit"을 꺼내는 마술

Moja.class

public class Moja {

    public String pullOut() {
        return "";
    }
}

Masulsa.java

public class Masulsa {

    public static void main(String[] args) {
        System.out.println(new Moja().pullOut());
    }
}

위 결과는 "Rabbit!"입니다. 어떻게 가능한 걸까요? 바로 바이트코드 조작 라이브러리를 사용하면됩니다 ㅎㅎ

바이트코드를 조작하는 데 쓸 수 있는 라이브러리들입니다.

바이트코드 조작 라이브러리

  • ASM
  • Javassist
  • ByteBuddy
    그 중 쉽게 사용할 수 있는 ByteBuddy를 사용해보겠습니다.

bytebuddy 의존성 주입

<dependency>
    <groupId>net.bytebuddy</groupId>
    <artifactId>byte-buddy</artifactId>
    <version>1.11.22</version>
</dependency>

예제

Moja라는 클래스를 ByteBuddy().redefine()을 통해 재정의해보겠습니다.

public class Masulsa {

    public static void main(String[] args) {
        try {
            new ByteBuddy().redefine(Moja.class)
                 .method(named("pullOut")).intercept(FixedValue.value("Rabbit!"))
                 .make().saveIn(new File("타겟 밑에 있는 classes로 폴더 경로 지정"));
                 
        } catch (IOException e) {
            e.printStackTrace();
        }
        
        
      // System.out.println(new Moja().pullOut());
    }
}

Moja 클래스에 pullOut이라는 이름의 메서드를 가져와 "Rabbi!t"이라는 고정된 값을 리턴하게합니다. saveIn은 폴더 경로를 지정하는 것으로 클래스 패키지 경로 따라서 파일이 들어가게 됩니다. Moja().pullOut()메서드를 호출해보면 Rabbit!을 호출하게됩니다.

소스코드는 위에서 보여준 Moja.class이지만, 바이트 코드는 아래처럼 바뀌어있습니다.

public class Moja {
    public Moja() {
    }
    
    public String pullOut() { return "Rabbit!"; }

}

정말 사용하기 쉽게 되어있군요 ㅎㅎㅎ

profile
백엔드 개발자

0개의 댓글