Skip to content

Commit ce32410

Browse files
authored
Merge pull request #2457 from joshlong/boot3-aot
add a module for Spring Boot 3 AOT
2 parents bbd7b10 + 3d710de commit ce32410

File tree

7 files changed

+219
-5
lines changed

7 files changed

+219
-5
lines changed

.github/workflows/codeql-analysis.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,11 @@ jobs:
2929
steps:
3030
- name: Checkout repository
3131
uses: actions/checkout@v3
32-
32+
- name: Setup Java
33+
uses: actions/setup-java@v3
34+
with:
35+
distribution: 'temurin'
36+
java-version: 17.0.x
3337
# Initializes the CodeQL tools for scanning.
3438
- name: Initialize CodeQL
3539
uses: github/codeql-action/init@v2

.github/workflows/maven.yml

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,16 +43,23 @@ jobs:
4343
path: ~/.m2/repository
4444
key: ${{ runner.os }}-maven-${{ matrix.java }}-${{ hashFiles('pom.xml', '**/pom.xml') }}
4545
- name: Build with Maven
46+
shell: bash
4647
run: |
47-
mvn clean test -q -B --define=org.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn
48+
if [ $(grep -E '^(8|11)\.' <<< '${{ matrix.java }}') ]; then
49+
# some module doesn't compile on java platform lower than 17, need to skip them by specifying a profile
50+
MODS_OVERRIDES='-pl !spring-aot'
51+
fi
52+
mvn -q -B --define=org.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn $MODS_OVERRIDES clean test
4853
build-graalvm:
4954
runs-on: ubuntu-latest
5055
name: GraalVM Maven Test
5156
steps:
5257
- uses: actions/checkout@v3
53-
- uses: DeLaGuardo/setup-graalvm@48f2bf339ab7d35e31029b1822a213681fdfc42e
58+
- uses: graalvm/setup-graalvm@v1
5459
with:
55-
graalvm-version: '19.3.0.java8'
60+
version: '22.3.0'
61+
java-version: '17'
62+
components: 'native-image'
5663
- name: Build with Maven
5764
run: mvn -q test -B -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn
5865
e2e:
@@ -62,6 +69,11 @@ jobs:
6269
- uses: actions/checkout@v3
6370
- name: Create k8s Kind Cluster
6471
uses: helm/[email protected]
72+
- name: Setup Java
73+
uses: actions/setup-java@v3
74+
with:
75+
distribution: 'temurin'
76+
java-version: 17.0.x
6577
- name: Run E2E with Maven
6678
run: |
6779
mvn clean install \
@@ -81,7 +93,7 @@ jobs:
8193
uses: actions/setup-java@v3
8294
with:
8395
distribution: 'temurin'
84-
java-version: 11.0.x
96+
java-version: 17.0.x
8597
- name: Cache local Maven repository
8698
uses: actions/cache@v3
8799
with:

pom.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
<module>extended</module>
1616
<module>fluent</module>
1717
<module>spring</module>
18+
<module>spring-aot</module>
1819
<module>e2e</module>
1920
<module>examples</module>
2021
<module>client-java-contrib/cert-manager</module>
@@ -63,6 +64,7 @@
6364
<spring.boot.version>2.7.5</spring.boot.version>
6465
<spring.version>5.3.23</spring.version>
6566
<prometheus.client.version>0.15.0</prometheus.client.version>
67+
<reflections.version>0.10.2</reflections.version>
6668

6769
<e2e.skip>true</e2e.skip>
6870

spring-aot/pom.xml

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
4+
<modelVersion>4.0.0</modelVersion>
5+
<groupId>io.kubernetes</groupId>
6+
<artifactId>client-java-spring-aot-integration</artifactId>
7+
<packaging>bundle</packaging>
8+
<name>client-java-spring-aot-integration</name>
9+
<url>https://github.com/kubernetes-client/java</url>
10+
<parent>
11+
<artifactId>client-java-parent</artifactId>
12+
<groupId>io.kubernetes</groupId>
13+
<version>16.0.0-SNAPSHOT</version>
14+
<relativePath>../pom.xml</relativePath>
15+
</parent>
16+
<properties>
17+
<spring.boot.version>3.0.0</spring.boot.version>
18+
</properties>
19+
<dependencies>
20+
<dependency>
21+
<groupId>org.reflections</groupId>
22+
<artifactId>reflections</artifactId>
23+
<version>${reflections.version}</version>
24+
</dependency>
25+
<dependency>
26+
<groupId>io.kubernetes</groupId>
27+
<artifactId>client-java-spring-integration</artifactId>
28+
<version>${project.version}</version>
29+
</dependency>
30+
<dependency>
31+
<groupId>org.springframework.boot</groupId>
32+
<artifactId>spring-boot</artifactId>
33+
</dependency>
34+
<dependency>
35+
<groupId>junit</groupId>
36+
<artifactId>junit</artifactId>
37+
<scope>test</scope>
38+
</dependency>
39+
<dependency>
40+
<groupId>org.springframework.boot</groupId>
41+
<artifactId>spring-boot-test</artifactId>
42+
<scope>test</scope>
43+
</dependency>
44+
<dependency>
45+
<groupId>org.springframework</groupId>
46+
<artifactId>spring-test</artifactId>
47+
<scope>test</scope>
48+
</dependency>
49+
</dependencies>
50+
<build>
51+
<plugins>
52+
<plugin>
53+
<groupId>org.jacoco</groupId>
54+
<artifactId>jacoco-maven-plugin</artifactId>
55+
<version>0.8.8</version>
56+
<executions>
57+
<execution>
58+
<id>jacoco-initialize</id>
59+
<goals>
60+
<goal>prepare-agent</goal>
61+
</goals>
62+
</execution>
63+
<execution>
64+
<id>jacoco-report</id>
65+
<phase>test</phase>
66+
<goals>
67+
<goal>report</goal>
68+
</goals>
69+
</execution>
70+
</executions>
71+
</plugin>
72+
<plugin>
73+
<groupId>org.apache.felix</groupId>
74+
<artifactId>maven-bundle-plugin</artifactId>
75+
<extensions>true</extensions>
76+
</plugin>
77+
<plugin>
78+
<groupId>org.apache.maven.plugins</groupId>
79+
<artifactId>maven-compiler-plugin</artifactId>
80+
<configuration>
81+
<source>17</source>
82+
<target>17</target>
83+
</configuration>
84+
</plugin>
85+
</plugins>
86+
</build>
87+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
/*
2+
Copyright 2022 The Kubernetes Authors.
3+
Licensed under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License.
5+
You may obtain a copy of the License at
6+
http://www.apache.org/licenses/LICENSE-2.0
7+
Unless required by applicable law or agreed to in writing, software
8+
distributed under the License is distributed on an "AS IS" BASIS,
9+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
See the License for the specific language governing permissions and
11+
limitations under the License.
12+
*/
13+
package io.kubernetes.client.spring.aot;
14+
15+
import com.google.gson.annotations.JsonAdapter;
16+
import io.kubernetes.client.extended.controller.Controller;
17+
import io.swagger.annotations.ApiModel;
18+
import java.lang.annotation.Annotation;
19+
import java.util.Collection;
20+
import java.util.HashSet;
21+
import java.util.Set;
22+
import org.jetbrains.annotations.NotNull;
23+
import org.reflections.Reflections;
24+
import org.slf4j.Logger;
25+
import org.slf4j.LoggerFactory;
26+
import org.springframework.aot.hint.MemberCategory;
27+
import org.springframework.aot.hint.RuntimeHints;
28+
import org.springframework.aot.hint.TypeReference;
29+
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution;
30+
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor;
31+
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
32+
import org.springframework.boot.autoconfigure.AutoConfigurationPackages;
33+
34+
/**
35+
* GraalVM native images need to know about when you might reflectively work with types at runtime.
36+
* The Kubernetes Java client works with several types reflectively at runtime. This code uses the
37+
* third-party Reflections library to reflect upon all the code in your Spring Boot application or
38+
* in the official Kubernetes Java client that has {@link ApiModel}, {@link JsonAdapter}, and
39+
* registers them. It also registers a few other specific handful of classes that should be
40+
* accounted for, in specific cases.
41+
*
42+
* @author Josh Long
43+
*/
44+
@SuppressWarnings("unused")
45+
public class KubernetesBeanFactoryInitializationAotProcessor
46+
implements BeanFactoryInitializationAotProcessor {
47+
48+
private static final Logger LOGGER =
49+
LoggerFactory.getLogger(KubernetesBeanFactoryInitializationAotProcessor.class);
50+
51+
private final MemberCategory[] allMemberCategories = MemberCategory.values();
52+
53+
@Override
54+
public BeanFactoryInitializationAotContribution processAheadOfTime(
55+
@NotNull ConfigurableListableBeanFactory beanFactory) {
56+
return (generationContext, beanFactoryInitializationCode) -> {
57+
RuntimeHints hints = generationContext.getRuntimeHints();
58+
String[] classNames =
59+
new String[] {
60+
"com.google.gson.JsonElement", //
61+
"io.kubernetes.client.informer.cache.ProcessorListener", //
62+
"io.kubernetes.client.extended.controller.Controller", //
63+
"io.kubernetes.client.util.generic.GenericKubernetesApi$StatusPatch", //
64+
"io.kubernetes.client.util.Watch$Response" //
65+
};
66+
for (String className : classNames) {
67+
LOGGER.info("registering " + className + " for reflection");
68+
hints.reflection().registerType(TypeReference.of(className), allMemberCategories);
69+
}
70+
registerForPackage("io.kubernetes", hints);
71+
Collection<String> packages = AutoConfigurationPackages.get(beanFactory);
72+
for (String packageName : packages) {
73+
registerForPackage(packageName, hints);
74+
}
75+
};
76+
}
77+
78+
private void registerForPackage(String packageName, RuntimeHints hints) {
79+
Reflections reflections = new Reflections(packageName);
80+
Set<Class<?>> apiModels = reflections.getTypesAnnotatedWith(ApiModel.class);
81+
Set<Class<? extends Controller>> controllers = reflections.getSubTypesOf(Controller.class);
82+
Set<Class<?>> jsonAdapters = findJsonAdapters(reflections);
83+
Set<Class<?>> all = new HashSet<>();
84+
all.addAll(jsonAdapters);
85+
all.addAll(controllers);
86+
all.addAll(apiModels);
87+
for (Class<?> clazz : all) {
88+
LOGGER.info("registering " + clazz.getName() + " for reflection");
89+
hints.reflection().registerType(clazz, allMemberCategories);
90+
}
91+
}
92+
93+
private <R extends Annotation> Set<Class<?>> findJsonAdapters(Reflections reflections) {
94+
Class<JsonAdapter> jsonAdapterClass = JsonAdapter.class;
95+
Set<Class<?>> classes = new HashSet<>();
96+
for (Class<?> clazz : reflections.getTypesAnnotatedWith(jsonAdapterClass)) {
97+
JsonAdapter annotation = clazz.getAnnotation(jsonAdapterClass);
98+
if (null != annotation) {
99+
classes.add(annotation.value());
100+
}
101+
classes.add(clazz);
102+
}
103+
return classes;
104+
}
105+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor=io.kubernetes.client.spring.aot.KubernetesBeanFactoryInitializationAotProcessor
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
io.kubernetes.client.spring.extended.controller.config.KubernetesInformerAutoConfiguration
2+
io.kubernetes.client.spring.extended.controller.config.KubernetesReconcilerAutoConfiguration
3+
io.kubernetes.client.spring.extended.manifests.config.KubernetesManifestsAutoConfiguration

0 commit comments

Comments
 (0)