봄 타이어를 차다

GraalVM의 사전 컴파일 기능을 깨달은 이래로 나는 줄곧 그것을 사용하고 있다.오랜 Spring 애호가로서 저는 Tanzu의 엔지니어들이 Spring AOT를 호환시키기 위한 노력을 자세히 관찰했습니다.최근에 그들은 the beta version 통합을 발표했다.이 문장에서 나는 하나를 만드는 것이 얼마나 쉬운지 보고 싶다기존 Spring Boot 응용 프로그램의 Docker 이미지입니다.

소개하다.


GraalVM은 다양한 기능을 제공합니다.여기서 기본 VM으로 불리는 구성 요소는 AOT에서 일반 바이트 코드를 실행 파일로 컴파일할 수 있도록 합니다.이 과정은 구축할 때 main 방법에서 응용 프로그램을 훑어보기 시작합니다.기본 VM은 생성된 바이너리 파일에 따르지 않는 코드를 생략합니다.
이 문제는 Spring 애플리케이션에 대한 문제입니다.이 프레임워크는 실행할 때 클래스 경로 스캔과 반사 같은 많은 작업을 했다.
이런 제한에 대응하는 일반적인 방법은 Graal VM에서 제공하는 자바 프록시 기록을 통해 JVM에서 실행되는 프로그램과 모든 상호작용을 하는 것이다.에이전트는 실행이 끝나면 모든 레코드의 상호 작용을 임시 프로파일에 덤프합니다.
  • 반사 채널
  • 서열화 클래스
  • 프록시 커넥터
  • 리소스 및 리소스 팩
  • JNI
  • 이 옵션은 거의 모든 가능한 자바 프로그램에서 본체 이미지를 만들 수 있도록 하는 매력적인 옵션입니다.하지만 당신의 필요에 따라 단점도 있습니다.
  • 에이전트의 전체 GraalVM 릴리스 제공
  • 테스트 어플리케이션 구석구석 테스트 키트
  • 새로운 버전마다
  • 에서 키트를 실행하고 프로필을 만드는 과정
    이 항목들은 모두 복잡하지 않지만, 과정은 시간이 소모되고 오류가 발생하기 쉽다.그것은 자동화할 수 있지만, 특정 버전이 테스트의 특정 용례를 잊어버리고 배치할 때 붕괴될 위험이 있다.

    Spring Native로 실험하기


    Spring Native는 진정한 봄 정신으로 구성을 단순화합니다.그 주요 사상은 코드에 직접'힌트'를 제공하는 것이다.전용 플러그인은 이 알림을 사용하고 필요한 프로필을 생성합니다.Spring 팀은 프레임의 코드에 이러한 프롬프트를 제공합니다.필요하면 프로그램의 코드에 대한 주석도 할 수 있다.
    Spring Native를 시험하기 위해 imperative-to-reactive 데모 코드를 사용했습니다.AOT는 다음과 같은 과제를 안고 있습니다.
  • Spring 애플리케이션
  • 주석을 사용하고 실행 시 반사 및 클래스 경로 스캔에 의존
  • 저는 Kotlin
  • 을 사용합니다.
  • H2, 메모리 데이터베이스
  • 사용
  • 마지막으로, 나는 삽입식 Hazelcast 실례에서 캐시 서열화 실체를 캐시했다.GraalVM의 최신 버전에 대한 시리얼화는 개선의 일부이기 때문에 매우 중요합니다.
  • 첫 번째 단계는 어플리케이션을 GraalVM과 호환시키는 것입니다.코드에서 삭제해야 합니다 Blockhound.Blockhound는 코드가 필요 없는 곳에서 실행되지 않았는지 확인할 수 있습니다.JRE 대신 JDK가 필요한 Java 에이전트입니다.이것은 프레젠테이션에 있어서는 좋지만, 생산 응용 프로그램과는 무관하다.
    본문을 작성할 때GraalVM은 자바의 두 가지 버전, 8과 11을 제공했다.처음에 자바 14를 사용했기 때문에 자바 버전을 14에서 11로 낮춰야 합니다.이 프로젝트는 코틀린에 있기 때문에 다른 영향은 없다.
    두 번째 단계는 POM에 의존항과 플러그인을 추가하는 것입니다.나는 이 두 파일을 모두 하나의 전용 설정 파일에 놓았는데, 이렇게 하면 프로그램이 정상적으로 실행될 수 있다.Maven Central 이외의 전용 Spring 저장소에 호스팅됩니다.
    <profiles>
      <profile>
        <id>native</id>
        <build>
          <plugins>
            <plugin>
              <groupId>org.springframework.experimental</groupId>
              <artifactId>spring-aot-maven-plugin</artifactId>
              <version>0.9.0</version>
              <executions>
                <execution>
                  <id>generate</id>
                  <goals>
                    <goal>generate</goal>
                  </goals>
                </execution>
              </executions>
            </plugin>
          </plugins>
        </build>
        <dependencies>
          <dependency>
            <groupId>org.springframework.experimental</groupId>
            <artifactId>spring-native</artifactId>
            <version>0.9.0</version>
          </dependency>
        </dependencies>
      </profile>
    </profiles>
    <repositories>
      <repository>
        <id>spring-release</id>
        <url>https://repo.spring.io/release</url>
      </repository>
    </repositories>
    <pluginRepositories>
      <pluginRepository>
        <id>spring-release</id>
        <url>https://repo.spring.io/release</url>
      </pluginRepository>
    </pluginRepositories>
    
    이 구성 세션에서는 native 구성 파일을 사용하여 네이티브 이미지를 생성할 수 있습니다.
    mvn spring-boot:build-image -Pnative
    

    첫번째 장애


    AOT 컴파일 프로세스는 시간이 오래 걸립니다.스택 추적이 표시되지만, Docker 이미지가 생성될 것입니다.다음 옵션을 사용하여 이미지를 실행할 수 있습니다.
    docker run -it --rm -p8080:8080 docker.io/library/imperative-to-reactive:1.0-SNAPSHOT     #1
    
  • 나는 --rm를 사용하기 때문에 용기가 실행된 후에 용기를 제거하고 디스크 공간을 낭비하지 않는다
  • 불행히도, 이것은 실패했지만, 다음과 같은 예외가 있다.
    Caused by: java.lang.ClassNotFoundException: org.springframework.boot.autoconfigure.r2dbc.ConnectionFactoryConfigurations$PooledConnectionFactoryCondition
        at com.oracle.svm.core.hub.ClassForNameSupport.forName(ClassForNameSupport.java:60) ~[na:na]
        at java.lang.Class.forName(DynamicHub.java:1260) ~[na:na]
        at org.springframework.util.ClassUtils.forName(ClassUtils.java:284) ~[na:na]
        at org.springframework.util.ClassUtils.resolveClassName(ClassUtils.java:324) ~[na:na]
        ... 28 common frames omitted
    
    보아하니 이 봄의 현지인은 이번 일을 놓친 것 같다.우리는 스스로 그것을 첨가해야 한다.다음과 같은 두 가지 방법이 있습니다.
  • Spring 네이티브 종속 항목
  • 에 대한 참고 사항
  • 또는 표준 GraalVM 구성 파일을 통해
  • 위 섹션에서는 Spring Native를 전용 Maven 구성 파일에 설정하도록 선택했습니다.따라서 일반 프로필을 사용합니다.
    [
    {
      "name":"org.springframework.boot.autoconfigure.r2dbc.ConnectionFactoryConfigurations$PooledConnectionFactoryCondition",
      "methods":[{"name":"<init>","parameterTypes":[] }]
    }
    ]
    
    다시 구축하고 실행하면 다음과 같은 결과가 발생합니다.
    Caused by: java.lang.NoSuchFieldException: VERSION
        at java.lang.Class.getField(DynamicHub.java:1078) ~[na:na]
        at com.hazelcast.instance.BuildInfoProvider.readStaticStringField(BuildInfoProvider.java:139) ~[na:na]
        ... 79 common frames omitted
    
    이번에는 Hazelcast와 관련된 정적 필드를 잃어버렸습니다.우리는 잃어버린 필드를 설정하고 다시 구축하고 다시 실행해야 한다.그것은 여전히 실패했다.씻고 반복: 자세한 건 알려주지 않을게요.관심 있는 경우 repo를 참조하십시오.
    나는 XML로 Hazelcast를 설정하기 때문에 전체 XML 초기화 과정이 필요하다.또한 네이티브 이미지에 리소스 패키지를 보관해야 하는 경우도 있습니다.
    {
    "bundles":[
      {"name":"com.sun.org.apache.xml.internal.serializer.XMLEntities"}
    ]
    }
    
    불행히도 구축은 계속 실패했다.클래스를 올바르게 구성했지만 여전히 XML과 관련된 예외입니다!
    Caused by: java.lang.RuntimeException: internal error
        at com.sun.org.apache.xerces.internal.impl.dv.xs.XSSimpleTypeDecl.applyFacets1(XSSimpleTypeDecl.java:754) ~[na:na]
        at com.sun.org.apache.xerces.internal.impl.dv.xs.BaseSchemaDVFactory.createBuiltInTypes(BaseSchemaDVFactory.java:207) ~[na:na]
        at com.sun.org.apache.xerces.internal.impl.dv.xs.SchemaDVFactoryImpl.createBuiltInTypes(SchemaDVFactoryImpl.java:47) ~[org.hazelcast.cache.ImperativeToReactiveApplicationKt:na]
        at com.sun.org.apache.xerces.internal.impl.dv.xs.SchemaDVFactoryImpl.<clinit>(SchemaDVFactoryImpl.java:42) ~[org.hazelcast.cache.ImperativeToReactiveApplicationKt:na]
        at com.oracle.svm.core.classinitialization.ClassInitializationInfo.invokeClassInitializer(ClassInitializationInfo.java:375) ~[na:na]
        at com.oracle.svm.core.classinitialization.ClassInitializationInfo.initialize(ClassInitializationInfo.java:295) ~[na:na]
        ... 82 common frames omitted
    

    YAML로 전환


    XML은 거대한 짐승이다. 나는 상술한 이상 배후의 정확한 원인을 이해할 충분한 전문가가 없다.공정도 정확한 해결 방법을 찾기 위해서다.이 예에서는 XML 구성에서 YAML 구성으로 전환하기로 결정했습니다.아무튼 간단합니다.
    hazelcast:
      instance-name: hazelcastInstance
    
    이러한 리소스를 리소스 구성 파일에 추가하는 것을 잊어서는 안 됩니다.
    {
    "resources":{
      "includes":[
        {"pattern":"hazelcast.yaml"}
      ]}
    }
    
    런타임에 문자 세트가 없으므로 빌드할 때 YAML 리더기를 초기화해야 합니다.
    Args = --initialize-at-build-time=com.hazelcast.org.snakeyaml.engine.v2.api.YamlUnicodeReader
    
    Hazelcast와 관련된 Reflective Access 클래스를 계속 추가해야 합니다.

    에이전트 없음


    이때, 우리는 운행할 때 새로운 이상을 만났다.
    Caused by: com.oracle.svm.core.jdk.UnsupportedFeatureError: Proxy class defined by interfaces [interface org.hazelcast.cache.PersonRepository, interface org.springframework.data.repository.Repository, interface org.springframework.transaction.interceptor.TransactionalProxy, interface org.springframework.aop.framework.Advised, interface org.springframework.core.DecoratingProxy] not found. Generating proxy classes at runtime is not supported. Proxy classes need to be defined at image build time by specifying the list of interfaces that they implement. To define proxy classes use -H:DynamicProxyConfigurationFiles=<comma-separated-config-files> and -H:DynamicProxyConfigurationResources=<comma-separated-config-resources> options.
        at com.oracle.svm.core.util.VMError.unsupportedFeature(VMError.java:87) ~[na:na]
        at com.oracle.svm.reflect.proxy.DynamicProxySupport.getProxyClass(DynamicProxySupport.java:113) ~[na:na]
        at java.lang.reflect.Proxy.getProxyConstructor(Proxy.java:66) ~[na:na]
        at java.lang.reflect.Proxy.newProxyInstance(Proxy.java:1006) ~[na:na]
        at org.springframework.aop.framework.JdkDynamicAopProxy.getProxy(JdkDynamicAopProxy.java:126) ~[na:na]
        at org.springframework.aop.framework.ProxyFactory.getProxy(ProxyFactory.java:110) ~[na:na]
        at org.springframework.data.repository.core.support.RepositoryFactorySupport.getRepository(RepositoryFactorySupport.java:309) ~[na:na]
        at org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport.lambda$afterPropertiesSet$5(RepositoryFactoryBeanSupport.java:323) ~[org.hazelcast.cache.ImperativeToReactiveApplicationKt:2.4.5]
        at org.springframework.data.util.Lazy.getNullable(Lazy.java:230) ~[na:na]
        at org.springframework.data.util.Lazy.get(Lazy.java:114) ~[na:na]
        at org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport.afterPropertiesSet(RepositoryFactoryBeanSupport.java:329) ~[org.hazelcast.cache.ImperativeToReactiveApplicationKt:2.4.5]
        at org.springframework.data.r2dbc.repository.support.R2dbcRepositoryFactoryBean.afterPropertiesSet(R2dbcRepositoryFactoryBean.java:167) ~[org.hazelcast.cache.ImperativeToReactiveApplicationKt:1.2.5]
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1845) ~[na:na]
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1782) ~[na:na]
        ... 46 common frames omitted
    
    이것은 대리에 관한 것으로 매우 간단하다.이 경우 Spring 데이터는 여러 다른 구성 요소 에이전트PersonRepository 인터페이스를 통해 생성됩니다.이것들은 모두 창고 추적에 열거되어 있다.GraalVM can handle proxies을(를) 구성해야 합니다.
    [
      ["org.hazelcast.cache.PersonRepository",
       "org.springframework.data.repository.Repository",
       "org.springframework.transaction.interceptor.TransactionalProxy",
       "org.springframework.aop.framework.Advised",
       "org.springframework.core.DecoratingProxy"]
    ]
    

    지금 서열화입니다.


    위의 구성을 통해 이미지가 성공적으로 시작되었으므로 마음이 따뜻합니다.
    2021-03-18 20:22:28.305  INFO 1 --- [           main] o.s.nativex.NativeListener               : This application is bootstrapped with code generated with Spring AOT
    
      .   ____          _            __ _ _
     /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
    ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
     \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
      '  |____| .__|_| |_|_| |_\__, | / / / /
     =========|_|==============|___/=/_/_/_/
     :: Spring Boot ::                (v2.4.3)
    
    ...blah blah blah...
    
    2021-03-18 20:22:30.654  INFO 1 --- [           main] o.s.b.web.embedded.netty.NettyWebServer  : Netty started on port 8080
    2021-03-18 20:22:30.655  INFO 1 --- [           main] o.s.boot.SpringApplication               : Started application in 2.355 seconds (JVM running for 2.358)
    
    이 시점에서 엔드포인트에 액세스하면 런타임 예외가 발생합니다.
    java.lang.IllegalStateException: Required identifier property not found for class org.hazelcast.cache.Person!
        at org.springframework.data.mapping.PersistentEntity.getRequiredIdProperty(PersistentEntity.java:105) ~[na:na]
    
    AOT에서 서열화 클래스를 빠뜨렸기 때문에 우리는 그것들을 관리해야 한다.에이전트에 대해 GraalVM은 무엇을 해야 하는지 알고 있지만, 명확한 설정이 필요하다.Person 클래스와 해당 속성의 클래스를 구성합니다.
    [
    {"name":"org.hazelcast.cache.Person"},
    {"name":"java.time.LocalDate"},
    {"name":"java.lang.String"},
    {"name":"java.time.Ser"}
    ]
    

    성공!


    이제 우리는 마침내 할 수 있다curl 달리기 영상:
    curl http://localhost:8080/person/1
    curl http://localhost:8080/person/1
    
    출력이 예상한 결과를 반환합니다.
    2021-03-15 09:54:18.994  INFO 1 --- [onPool-worker-3] o.h.c.CachingService : Person with id 1 not found in cache
    2021-03-15 09:54:19.108  INFO 1 --- [onPool-worker-3] o.h.c.CachingService : Person with id 1 put in cache
    2021-03-15 09:54:46.694  INFO 1 --- [onPool-worker-3] o.h.c.CachingService : Person with id 1 found in cache
    
    우리는 Sort 클래스를 루트 '/' 노드와 함께 설정해서 모든 실체를 한 번에 검색해야 한다.

    결론


    Spring Boot의 모든 "매직"에도 불구하고 Spring Native는 GraalVM에 필요한 대부분의 구성을 직접 처리할 수 있습니다.상술한 절차는 주로 응용 프로그램에 대한 코드이다.
    비록 이 프로그램은 단지 프레젠테이션 프로그램일 뿐이지만, 그것도 보잘것없는 것은 아니다.서열화, 메모리 캐시, 메모리 데이터베이스가 존재하지만, 본 컴퓨터의 이미지는 여전히 정상적으로 작동할 가능성이 있다.
    물론 모든 것이 완벽한 것은 아니다. 구축에 이상이 있고, 로그가 실행될 때 복사되며, Hazelcast 노드가 그룹에 가입할 수 없을 것 같다.
    그러나, 이것은 이미 충분하다. 특히 내가 얼마나 많은 시간을 썼는지에 관해서는.나는 1.0 버전을 시도하기를 갈망한다.그리고 나머지 경고를 더 자세히 조사할 수도 있다.
    이 기사의 전체 소스 코드는 Github의 native 지점에서 찾을 수 있습니다.

    hazelcast 프레젠테이션 / 반드시 반응해야 한다


    캐시를 유지하는 동시에 명령식 프로그래밍 모델에서 반응식 프로그래밍 모델로 점차적으로 이동하는 방법을 보여 주다


    한 걸음 더 나아가 말하면:
  • Spring Native documentation
  • Packaging OCI Images
  • 최초 발표는 2021년 3월 22일A Java Geek

    좋은 웹페이지 즐겨찾기