[JAVA] class 파일 조작하기

김다혜·2020년 2월 23일
0

inflearn-the-java

목록 보기
1/1
post-thumbnail

나는 작년 lombok의 원리를 궁금해하다가, inflearn에서 백기선 님의 강의인 '더 자바, "코드를 조작하는 다양한 방법" 이라는 인강을 접하게 되었다.

아래의 내용은 inflearn, 백기선님의 '더 자바, 코드를 조작하는 다양한 방법' 인강의 내용을 바탕으로 했으며, 지극히 개인적으로 공부한 내용을 정리하고자 함😉

바이트코드 조작은 계속 되고 있다

(바이트 조작의 예시)

  • 프로그램 분석
    • 코드에서 버그 찾는 툴
    • 코드 복잡도 계산
    • 코드 커버리지 계산 (JaCoCo)
  • 클래스 파일 생성
    • 프록시
  • springcomponent scan (asm 사용)

모자에서 토끼 꺼내기

첫 번째, class 파일 직접 조작하기

😮 바이트 코드 조작 전

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

Moja.java 에서 pullOut()메서드에서는 빈 스트링을 return 하고 있다.

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

따라서 Masulsa.java 에서 pullOut() 메서드를 호출하면 당연히 빈 스트링이 출력된다.

🧐 바이트 코드 조작 후

Moja를 조작하여 "rabbit!"을 출력해보자.
(ByteBuddy를 사용해서 class 파일을 조작해보자)

public class Masulsa {
    public static void main(String[] args) {
        try {
            new ByteBuddy().redefine(Moja.class)
                    .method(named("pullOut")) // 조작할 메서드 명 적어주기
                    .intercept(FixedValue.value("Rabbit!")) // RETURN 할 값 적어주기
                    .make()
                    .saveIn(new File("project\\inflearn-code\\the-java\\target\\classes\\")); // class 파일 경로 넣어주기 (해당 경로에 조작된 코드 저장하겠다.)
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

해당 코드를 동작시키면 Moja.class의 코드 (바이트코드)가 아래와 같이 조작된다.

public class Moja {
    public Moja() {
    }

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

조작하고나서 System.out.println(new Moja().pullOut());을 호출하면, "Rabbit!"이 출력되게 된다.

두 번째, class 로딩 시점에 코드 조작하기

😮 첫 번째 방식과 다른 점은 ?

  • 더욱 transparent(비 침투적인, 기존 코드를 조작하지 않는..)한 방식 (class 파일이 조작되지 않는다. class 파일은 코딩한 그대로 생성된다.)
  • 클래스 로딩할 때에 적용이 된다. (클래스 로딩 시점 = 클래스 로더가 .clas 파일을 읽어, 그 내용에 따라 적절한 바이너리 데이터를 만들어 '메서드' 영역에 저장하는 시점)

💁‍♀️ 조작해보자

javaAgentbyteBuddy 사용하여 javaAgent를 만들어보자

MasulsaAgent.java

public class MasulsaAgent {
    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("pullOut")).intercept(FixedValue.value("Rabbit by MasulsaAgent"));
                    }
                }).installOn(inst);
    }
}

실행 시
-javaAgent: {MasulsaAgent.jar 파일}을 설정하여 실행 (javaAgent 적용)

🙆‍♀️ 결과
따라서, System.out.println(new Moja().pullOut());을 호출한다면, "Rabbit by MasulsaAgent"가 출력되게 된다.

  • premain 메서드가 포함된 jar로 만들어진 javaagent를 사용할 때에 JVM에서 해당 메서드를 먼저 호출하려고 시도한다.
  • 클래스 로딩 시점에 조작된다.
  • 따라서, class 파일은 조작되지 않는다.

클래스 로드가 클래스를 읽어올 때 javaagent를 거쳐서 변경된 바이트코드를 읽어들여 사용한다.

0개의 댓글