@@ -10,7 +10,7 @@ ifdef::env-github[]
10
10
endif::[]
11
11
:toc:
12
12
:toclevels: 3
13
- :clojure-version: 1.11.1
13
+ :clojure-version: 1.12.0
14
14
:graal-build-time-version: 1.0.5
15
15
16
16
== Rationale
@@ -148,24 +148,30 @@ It will compile just fine:
148
148
----
149
149
$ mkdir -p classes
150
150
$ clojure -M -e "(compile 'refl.main)"
151
- $ native-image -cp "$(clojure -Spath):classes" -H:Name=refl -H:+ReportExceptionStackTraces \
152
- --features=clj_easy.graal_build_time.InitClojureClasses --no-fallback refl.main
151
+ $ native-image \
152
+ -cp "$(clojure -Spath):classes" \
153
+ -H:Name=refl \
154
+ -H:+ReportExceptionStackTraces \
155
+ --features=clj_easy.graal_build_time.InitClojureClasses \
156
+ --no-fallback \
157
+ refl.main
153
158
----
154
159
But when we go to run the native image, we'll see the following failure:
155
160
[source,shell]
156
161
----
157
162
$ ./refl
158
163
Exception in thread "main" java.lang.IllegalArgumentException: No matching field found: toUpperCase for class java.lang.String
159
- at clojure.lang.Reflector.getInstanceField(Reflector.java:397)
160
- at clojure.lang.Reflector.invokeNoArgInstanceMember(Reflector.java:440)
161
- at refl.main$refl_str.invokeStatic(main.clj:5)
162
- at refl.main$refl_str.invoke(main.clj:4)
163
- at refl.main$_main.invokeStatic(main.clj:8)
164
- at refl.main$_main.doInvoke(main.clj:7)
165
- at clojure.lang.RestFn.invoke(RestFn.java:397)
166
- at clojure.lang.AFn.applyToHelper(AFn.java:152)
167
- at clojure.lang.RestFn.applyTo(RestFn.java:132)
168
- at refl.main.main(Unknown Source)
164
+ at clojure.lang.Reflector.getInstanceField(Reflector.java:426)
165
+ at clojure.lang.Reflector.invokeNoArgInstanceMember(Reflector.java:469)
166
+ at refl.main$refl_str.invokeStatic(main.clj:5)
167
+ at refl.main$refl_str.invoke(main.clj:4)
168
+ at refl.main$_main.invokeStatic(main.clj:8)
169
+ at refl.main$_main.doInvoke(main.clj:7)
170
+ at clojure.lang.RestFn.invoke(RestFn.java:400)
171
+ at clojure.lang.AFn.applyToHelper(AFn.java:152)
172
+ at clojure.lang.RestFn.applyTo(RestFn.java:135)
173
+ at refl.main.main(Unknown Source)
174
+ at [email protected] /java.lang.invoke.LambdaForm$DMH/sa346b79c.invokeStaticInit(LambdaForm$DMH)
169
175
----
170
176
171
177
==== Use Type Hints to Avoid Reflection
@@ -215,8 +221,13 @@ If we recompile our updated source:
215
221
----
216
222
$ mkdir -p classes
217
223
$ clojure -M -e "(compile 'refl.main)"
218
- $ native-image -cp "$(clojure -Spath):classes" -H:Name=refl -H:+ReportExceptionStackTraces \
219
- --features=clj_easy.graal_build_time.InitClojureClasses --no-fallback refl.main
224
+ $ native-image \
225
+ -cp "$(clojure -Spath):classes" \
226
+ -H:Name=refl \
227
+ -H:+ReportExceptionStackTraces \
228
+ --features=clj_easy.graal_build_time.InitClojureClasses \
229
+ --no-fallback \
230
+ refl.main
220
231
----
221
232
We no longer see our reflection warning and our native image now works just fine:
222
233
[source,clojure]
@@ -225,10 +236,7 @@ $ ./refl
225
236
ALL GOOD!
226
237
----
227
238
228
- NOTE: As an example, prior versions of Clojure's own `clojure.stacktrace` made use of reflection (see https://clojure.atlassian.net/browse/CLJ-2502[JIRA CLJ-2502]).
229
- But this has been addressed via type hints.
230
-
231
- Enable or disable the `*warn-on-reflection*` depending on the alias, the following methods are available for each tool.
239
+ To enable or disable `\*warn-on-reflection*`, the following methods are available for each tool.
232
240
233
241
- `leiningen`: Use `:global-vars` in project.clj
234
242
[source,clojure]
@@ -287,9 +295,14 @@ Then recompile specifying our reflection config:
287
295
----
288
296
$ mkdir -p classes
289
297
$ clojure -M -e "(compile 'refl.main)"
290
- $ native-image -cp "$(clojure -Spath):classes" -H:Name=refl -H:+ReportExceptionStackTraces \
298
+ $ native-image \
299
+ -cp "$(clojure -Spath):classes" \
300
+ -H:Name=refl \
301
+ -H:+ReportExceptionStackTraces \
291
302
-H:ReflectionConfigurationFiles=reflect-config.json \
292
- --features=clj_easy.graal_build_time.InitClojureClasses --no-fallback refl.main
303
+ --features=clj_easy.graal_build_time.InitClojureClasses \
304
+ --no-fallback \
305
+ refl.main
293
306
----
294
307
295
308
We have success:
@@ -299,21 +312,33 @@ $ ./refl
299
312
ALL GOOD!
300
313
----
301
314
302
- See the https://www.graalvm.org/reference-manual/native-image/Reflection/ [GraalVM docs on reflection for details] on the reflection config format.
315
+ See the https://www.graalvm.org/jdk23/ reference-manual/native-image/metadata/#reflection [GraalVM docs on reflection for details] on the reflection config format.
303
316
304
317
==== Reflection Config for Arrays
305
- To configure reflection config for an array of Java objects, you need to specify `[Lfully.qualified.class`.
306
- For example a `Statement[]` would be specified as `"[Ljava.sql.Statement"`.
318
+ To configure reflection config for an array of Java objects, you need to specify `[Lfully.qualified.class;`.
319
+ Arrays of primitives or arrays are slightly different.
320
+ For example a `Statement[]` would be specified as `"[Ljava.sql.Statement;"`.
307
321
308
- You can discover this name by calling `(.getClass instance)` in a REPL.
322
+ You can discover this name by calling `(.getName (class instance) )` in a REPL.
309
323
A contrived example:
310
324
[source,clojure]
311
325
----
312
326
❯ clj
313
- Clojure 1.11.1
327
+ Clojure 1.12.0
314
328
user=> (def foo (java.util.Locale/getAvailableLocales))
315
- user=> (.getClass foo)
316
- [Ljava.util.Locale;
329
+ #'user/foo
330
+ user=> (.getName (class foo))
331
+ "[Ljava.util.Locale;"
332
+ user=> (.getName java.util.Locale/1)
333
+ "[Ljava.util.Locale;"
334
+ user=> (.getName java.util.Locale/2)
335
+ "[[Ljava.util.Locale;"
336
+ user=> (.getName java.util.Locale/3)
337
+ "[[[Ljava.util.Locale;"
338
+ user=> (.getName byte/1)
339
+ "[B"
340
+ user=> (.getName byte/2)
341
+ "[[B"
317
342
----
318
343
319
344
==== Automatically Discovering Reflection Config [[reflection-discovery]]
@@ -344,39 +369,56 @@ Let's recompile our original reflection example app and then run it from GraalVM
344
369
----
345
370
$ mkdir -p classes
346
371
$ clojure -M -e "(compile 'refl.main)"
372
+ Reflection warning, refl/main.clj:7:3 - reference to field toUpperCase can't be resolved.
347
373
refl.main
348
374
$ java -agentlib:native-image-agent=caller-filter-file=filter.json,config-output-dir=. \
349
- -cp $(clojure -Spath):classes refl.main
375
+ -cp $(clojure -Spath):classes \
376
+ refl.main
350
377
ALL GOOD!
351
378
----
352
379
353
- This will output `reflect-config .json`:
380
+ This will output `reachability-metadata .json`:
354
381
[source,json]
355
382
----
356
383
[
357
- {
358
- "name":"java.lang.String",
359
- "queryAllPublicMethods":true,
360
- "methods":[{"name":"toUpperCase","parameterTypes":[] }]
361
- },
362
- {
363
- "name":"java.lang.reflect.Method",
364
- "methods":[{"name":"canAccess","parameterTypes":["java.lang.Object"] }]
365
- },
366
- {
367
- "name":"java.util.concurrent.atomic.AtomicBoolean",
368
- "fields":[{"name":"value"}]
369
- },
370
- {
371
- "name":"java.util.concurrent.atomic.AtomicReference",
372
- "fields":[{"name":"value"}]
373
- }
384
+ "reflection": [
385
+ ...
386
+ {
387
+ "type": "java.lang.String",
388
+ "methods": [
389
+ {
390
+ "name": "toUpperCase",
391
+ "parameterTypes": []
392
+ }
393
+ ]
394
+ },
395
+ {
396
+ "type": "java.lang.reflect.Method",
397
+ "methods": [
398
+ {
399
+ "name": "canAccess",
400
+ "parameterTypes": [
401
+ "java.lang.Object"
402
+ ]
403
+ }
404
+ ]
405
+ },
406
+ {
407
+ "type": "java.util.concurrent.atomic.AtomicBoolean",
408
+ "fields": [
409
+ {
410
+ "name": "value"
411
+ }
412
+ ]
413
+ },
414
+ ...
415
+ ]
374
416
]
375
417
----
376
418
377
419
The entry for `java.lang.reflect.Method` is expected, see link:#clojure.lang.reflector[clojure.lang.Reflector].
378
420
379
- // TODO: Why AtomicBoolean and AtomicReference ?
421
+ // TODO: Why AtomicBoolean?
380
422
381
423
You then feed this generated reflection config to native-image just like you would for a link:#hand-coded-reflection-config[hand-coded one].
382
424
@@ -442,7 +484,8 @@ The most convenient place for you to set that system property will vary dependin
442
484
443
485
=== Optional Transitive Dependencies
444
486
445
- A Clojure app that optionally requires transitive dependencies can be made to work under GraalVM with https://github.com/borkdude/dynaload[dynaload].
487
+ A Clojure app that optionally requires transitive dependencies can often be made to work under GraalVM
488
+ by replacing dynamic calls to `requiring-resolve` and `require` with https://github.com/borkdude/dynaload[dynaload].
446
489
You'll want to follow https://github.com/borkdude/dynaload#graalvm[its advice for GraalVM].
447
490
448
491
=== Static Linking
0 commit comments