콘텐츠로 이동

2021 06 06

2021-06-06

바이트코드

  • Maven을 사용하여 빌드하기 - 참고 1: https://steven-life-1991.tistory.com/90 - 참고 2: https://ojava.tistory.com/147 - Maven 다운받아서 환경 변수로 bin 추가 - pom.xml - import되는 jar 파일과 버전 정보, 패키징 되는 기준 값 - 기본 구문과 추가 설정으로 나뉨 - Artifact: 프로젝트 생성시 생기는 필수 정보, 배포 방식/정보 포함 - properties: 자주 사용되는 정보 변수처럼 만들어 사용 - dependencies: 의존성 정의. 라이브러리 불러오기
  • 바이트 코드 조작 - 기존의 자바 소스 코드를 컴파일해서 생기는 클래스 파일 - 이 클래스 파일을 조작할 수 있음 - 어차피 jvm은 클래스파일을 로드해 한줄씩 읽으며 실행하기에 클래스 파일이 조작되면 코드와 다른 결과가 기대됨 - 다음과 같이 ByteBuddy 라이브러리 사용하여 코드를 작성할 수 있다 - Moja 클래스의 pullout 메서드가 "ByteRabbit!"을 반환하도록 하고, 이를 File("C:~~") 위치에 저장하자
    public static void main(String[] args) throws IOException {
        new ByteBuddy().redefine(Moja.class)
                .method(named("pullout")).intercept(FixedValue.value("ByteRabbit!"))
                .make().saveIn(new File("C:\\Users\\joel6\\Desktop\\the-java\\bytecode\\target\\classes"));
        System.out.println(new Moja().pullout());
    }
    
      - new Moja().pullout(); 
          - 처음 실행하면, 클래스 파일이 이렇게 작성됨
          ```java
          public class Moja {
              public Moja() {
              }
    
              public String pullout() {
                  return "";
              }
          }
          ```
    
          - Moja 클래스가 로딩되는 시점이 redifine(Moja.class)인데, 이때 생성된 클래스 파일을 로드해서 사용
          - new Moja().pullout() 에서 사용한 Moja 클래스는 이미 위에서 로딩된 클래스
          - 한 번 실행을 하고 나면, 바이트코드가 조작되어 클래스 파일이 다음과 같이 수정됨
          ```java
          public class Moja {
              public Moja() {
              }
    
              public String pullout() {
                  return "ByteRabbit!";
              }
          }
          ```
    
          - 이제 Main 실행하면 ByteRabbit! 출력
      - 이게 결국 자바 코드상의 바이트 코드 조작이기 때문에, 한 번은 자바 코드를 실행해야 클래스 파일에 내용이 반영이 됨
    
  • javaagent - 참고 1: https://catsbi.oopy.io/6136946a-9139-4541-b2af-2af93bb634a5 - 정의 - JVM에서 동작하는 Java 어플리케이션으로, JVM의 다양한 이벤트를 전달 받거나, 정보 질의, 바이트코드 제어 등을 특정 API를 통해 수행 - 특징 - Agent는 지정된 JVM의 실행 가능한 최초 진입점인 main 메서드 가로챌 수 있음 - 지정된 JVM에서 실행 - 지정된 JVM의 동일한 System Class Loader 내에서 로드 - 지정된 JVM의 Security policy 및 Context 영향 받음 - 실행시간에 동적으로 bytecode 조작 가능 - 사용 메서드 - Agent의 단일 진입점 premain 메서드 - Instumentation 인터페이스로 바이트 코드를 포함한 추가적인 정보 수집 도구 활용 가능
    public static void premain(String agentArgs, Instrumentation inst);
    public static void premain(String agentArgs)
    

    - 사용하기 - 클래스로더가 "클래스를 읽어올 때", javaagent를 거쳐 "변경된 바이트코드를 읽어들여 사용" - 그러다보니까 위에서 살펴볼 땐 한 번 실행해서 java 소스 코드로 바이트코드 변경했자나? - 여기서는 javaagent를 통해 애초 런타임 와중에 클래스 로더에서 읽어올 때 코드를 조작하자나? - 한 번에 바로 조작이 가능하지 - 실습 - 다음과 같이 premain 메서드를 작성하자

    public class MagicianAgent {
        public static void premain(String agentArgs, Instrumentation inst) {
            new AgentBuilder.Default()
                    .type(ElementMatchers.any())
                    .transform(new AgentBuilder.Transformer() {
                        public DynamicType.Builder<?> transform(DynamicType.Builder<?> builder, TypeDescription typeDescription, ClassLoader classLoader, JavaModule javaModule) {
                            return builder.method(named("pullout")).intercept(FixedValue.value("RabbitRabbit!"));
                        }
                    }).installOn(inst);
        }
    }
    

      - 이렇게 작성한 코드를 실행 가능한 jar 파일로 만들자
          - 아래 참조
      - 이제 javaagent를 통해 JVM Options를 적용하자!
          - *javaagent:C:\Users\joel6\Desktop\the-java\Magician\target\Magician-1.0-SNAPSHOT.jar*
          - 요런 갬성으로다가 java 클래스 파일들을 실행할 때 VM options를 줄 수 있나보다!
          ```
          $ java -server -Xms8g -Xmx8g -XX:MaxMetaspaceSize=256m -XX:+CMSClassUnloadingEnabled -Dspring.profiles.active=prod -jar app.jar 
          $ java -jar -Dspring.profiles.active=prod [jar파일명] 
          ```
    
      - 이 친구들은 컴파일된 클래스 파일을 바꾸는 게 아님
          - 클래스로더가 클래스를 "로딩"할 때 javaagent의 설정을 읽어들여 바이트코드를 변형하여 로드함
    
  • 실행 가능한 jar 파일 만들기 - 참고 1: https://boxfoxs.tistory.com/332 - pom.xml에서 premain-class에 대한 정보를 적어주자 - addClasspath 설정으로 class-path 요소들을 MANIFEST.MF 파일에 추가하자
    <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>MagicianAgent</Premain-Class>
                    <Can-Redefine-Classes>true</Can-Redefine-Classes>
                    <Can-Retransform-Classes>true</Can-Retransform-Classes>
                </manifestEntries>
            </archive>
        </configuration>
    </plugin>
    

    - mvn clean package 를 통해 빌드를 하면 "Magician-1.0-SNAPSHOT.jar"가 생성된 것을 볼 수 있음 - Magician > target > Magician-1.0-SNAPSHOT.jar > META-INF > MANIFEST.MF 로 해당 jar 파일의 정보를 보자

    Manifest-Version: 1.0
    mode: development
    Premain-Class: MagicianAgent
    Can-Redefine-Classes: true
    Can-Retransform-Classes: true
    Class-Path: byte-buddy-1.10.22.jar
    Build-Jdk-Spec: 1.8
    Created-By: Maven Archiver 3.4.0
    key: value
    url:
    

  • 바이트 코드 조작이 쓰이는 곳 - 프로그램 분석 - 디버깅 - 코드 복잡도 - 클래스 파일 생성 - 프록시 - 특정 API 호출 접근 제한 - JVM 기반 언어의 컴파일러 - 기타 소스코드 변경 - 프로파일러 - 최적화 - 로깅 - 스프링에서는 "Component Scan!"