Assisted Injection
+ Assisted injection is a dependency injection pattern used to construct an object where some parameters may be provided by the DI framework and others must be passed in at creation time (“assisted” if you will) by the user.
+
+
Avaje Inject will generate a factory implementation responsible for combining all of the parameters and creating the object.
+
+
@AssistFactory
+
+ To use assisted injection, we annotate our desired class with @AssistFactory to signal the generator to create a factory class.
+ @AssistFactory requires an interface/abstract class type that has the assisted types as parameters.
+
+
+ To mark fields/method parameters as assisted, we annotate them with @Assisted, as shown below:
+
+
+
@AssistFactory(CarFactory.class)
+publicclassCar{
+
+ @AssistedMakemake;
+ @InjectWheelwheel;
+
+ publicCar(@AssistedPaintpaint,@NullableEngineengine){
+ // ...
+ }
+
+ //will be triggered after contruction
+ @Inject
+ voidinjectMethod(@Assistedintsize,Steelsteel){
+ // ...
+ }
+
+ //Factory Type the generated code will implement
+ publicinterfaceCarFactory{
+ Carfabricate(Paintpaint,intsize,Makemake);
+ }
+}
+
+
+
+
We can now wire a factory instance and use in our application
<dependency><groupId>io.avaje</groupId>
- <artifactId>avaje.jsonb</artifactId>
+ <artifactId>avaje-jsonb</artifactId><version>${avaje.jsonb.version}</version></dependency><!-- if using spring web, add the below to use jsonb for http messaging -->
@@ -145,8 +146,8 @@
-
2a. JDK 22+
-
In JDK 22+, annotation processors are disabled by default, so we need to add a flag to re-enable.
+
2a. JDK 23+
+
In JDK 23+, annotation processors are disabled by default, so we need to add a flag to re-enable.
When we are unable to or do not wish to put @Json on the types we can use @Json.Import.
@@ -590,6 +594,64 @@
Generated Code
+
@Json.Creator
+
+ With @Json.Creator, we can override deserialization using a constructor or static factory method. Contructor/Method parameters can be annotated with @Alias to rename a deserialization field.
+
+ The Java Service Provider
+ Interface uses configuration files to
+ find and load concrete implementations of given
+ service provider interfaces. The downside of this system is that
+ configuration files are not checked by the java
+ compiler.
+ As such it is a common occurence to forget to add the correct entries.
+
+
+ This zero-dependency library uses annotation processing to automatically
+ generate the required META-INF/services
+ entries for
+ annotated classes. In addition, it will validate that the required
+ provides clauses are present in an application's module-info files.
+
+
+
Dependency
+
+ To use this library add the avaje-spi-service as a
+ provided/optional dependency. (The dependencies need
+ only be included at
+ compile-time and are not required at runtime)
+
+ On classes that you'd like registered, place the
+ @ServiceProvider annotation.
+ As long as you only have one interface/ superclass, that type is
+ assumed to be the SPI interface. So given the
+ example below:
+
+ If you have multiple interfaces and/or a base type, the library cannot infer the contract type. In such a case, we
+ specify the contract type explicitly in the @ServiceProvider:
+
+ For modular projects, the processor will validate that all the required provides clauses are accounted for. A compile error describing what `provides` statements are missing will be throw if validations fail. So given the following class and module-info:
+
- The generated code will generate code to retrive the value.
+ The generated code will generate code to retrieve the value.
// GET /{get.path}// Avaje Config will be used if detected on classpath
diff --git a/index.html b/index.html
index e5e703e..b472968 100644
--- a/index.html
+++ b/index.html
@@ -104,6 +104,14 @@
+ Java annotations can have members. We can use these members to further discriminate a qualifier.
+ This prevents a potential explosion of new annotation interfaces.
+ For example, instead of creating several qualifiers representing different payment methods, we could aggregate them into a single annotation with a member:
+
+ This library is an upgraded fork of the unmaintained hickory library that helps make coding annotation processors
+ more straighforward by generating annotation "Prisms" and other various utilities.
+
+
+
What's A Prism?
+
+ When writing annotation processors, the two conventional mechanisms to access the annotation attributes are both
+ awkward. Element.getAnnotation() can throw Exceptions if the annotation or its members are not
+ semantically correct, and it can also fail to work on some modular projects.
+ (This is one the reasons why annotationProcessorPaths is required for modular projects. This solution
+ is seriously limited and has it's own issues (See
+ MCOMPILER-412).
+ Moreover, when calling a member with a Class return type, you need to catch an exception to extract the
+ TypeMirror.
+
+ On the other hand, AnnotationMirror and AnnotationValue do a good job of modeling both
+ correct and incorrect annotations. Unfortunately, they provide no simple mechanism to determine whether correctness,
+ and provide no convenient functionality to access the member values in a simple type-specific way.
+
+ A Prism provides a solution to this problem by combining the advantages of the pure reflective model of
+ AnnotationMirror and the runtime (real) model provided by Element#getAnnotation(), hence
+ the term Prism to capture this idea of partial reflection.
+
+ A prism has the same member methods as the annotation except that the return types are translated from runtime types
+ to compile time types as follows...
+
+
+
Primitive members return their equivalent boxed class in the prism.
+
Class members return a TypeMirror from the mirror API.
+
Enum members return a String bearing the name of the enum constant (because the constant value in the mirror API
+ might not match those available in the runtime it cannot consistently return the appropriate enum).
+
Annotation members return a Prism of the annotation. If a prism for that annotation is generated from
+ the same @GeneratePrisms annotation as the prism that uses it, then an instance of that prism will be
+ returned. Otherwise, a Prism for that annotation is supplied as an inner class of the dependant
+ Prism.
+
+
Array members return a List where X is the appropriate prism mapping of the array
+ component as above.
@GeneratePrism(ExampleAnnotation.class)
+@SupportedAnnotationTypes(ExampleAnnotationPrism.PRISM_TYPE)
+publicfinalclassMyProcessorextendsAbstractProcessor{
+
+ //processing logic...
+
+ voidsomeFunction(Elementelement){
+
+ ExampleAnnotationPrismexampleAnnotation=ExampleAnnotationPrism.getInstanceOn(element);
+
+ //can get the fully qualified annotation type as a string
+ StringannotationQualifiedType=MyExampleAnnotationPrism.PRISM_TYPE;
+
+ //can easily retrieve the annotation values as if the annotation was present on the classpath.
+ Stringvalue=exampleAnnotation.someAttribute();
+ ...
+ }
+}
+
+
+
+
@GeneratedPrism
+
+ We use @GeneratedPrism to let the generator create a prism for an annotation. Prisms contain useful
+ static methods to extract annotation values from Elements.
+
+
+
Common Prism Members
+
+
PRISM_TYPE
+
+ All generated prisms will have a public static final String PRISM_TYPE field containing the target
+ annotation's fully qualified type.
+
+
+
isPresent
+
+ Returns a true if the target annotation is present on the element.
+
+ Return a list of prisms representing the target annotation on all the annotations of the given element. Will
+ recursively search all the annotations on the element.
+
+ If we have similar annotations (Think javax and jakarta) we can create a common interface the generated
+ prisms will extend.
+
+
@GeneratePrism(
+ value=javax.validation.NotNull.class,
+ name="JavaxNotNullPrism",
+ superInterfaces=NotNullPrism.class)
+@GeneratePrism(
+ value=jakarta.validation.NotNull.class,
+ name="JakartaNotNullPrism",
+ superInterfaces=NotNullPrism.class)
+publicinterfaceNotNullPrism{
+
+ //we create methods for the common annotation members
+ Stringmessage();
+
+ //we also need to create static methods to get the prisms
+ staticOptional<NotNullPrism>getInstanceOn(Elemente){
+
+ returnOptional.<NotNullPrism>empty()
+ .or(()->JakartaNotNullPrism.getOptionalOn(e))
+ .or(()->JavaxNotNullPrism.getOptionalOn(e))
+ .orElse(null);
+ }
+
+ staticOptional<NotNullPrism>getOptionalOn(Elemente){
+
+ returnOptional.<NotNullPrism>empty()
+ .or(()->JakartaNotNullPrism.getOptionalOn(e))
+ .or(()->JavaxNotNullPrism.getOptionalOn(e));
+ }
+}
+
+
+
+
@GenerateAPContext
+
+ As your annotation processor grows in size and complexity, you may find it difficult to properly access the
+ ProcessingEnvironment and its utilities. @GenerateAPContext generates a helper class that
+ stores the processing env and its utilities in a ThreadLocal for easy access anywhere in the processor.
+
+
+ To initialize/cleanup the generated APContext, we must initialize it during the init phase and clear it
+ when processing is over.
+
/**
+* Utiliy Class that stores the {@link ProcessingEnvironment} and provides various helper methods
+*/
+Generated("avaje-prism-generator")
+ublicfinalclassAPContext{
+
+ privatestaticintjdkVersion;
+ privatestaticbooleanpreviewEnabled;
+ privatestaticfinalThreadLocal<Ctx>CTX=newThreadLocal<>();
+
+ privateAPContext(){}
+
+ publicstaticfinalclassCtx{
+ privatefinalProcessingEnvironmentprocessingEnv;
+ privatefinalMessagermessager;
+ privatefinalFilerfiler;
+ privatefinalElementselementUtils;
+ privatefinalTypestypeUtils;
+ privateModuleElementmodule;
+
+ publicCtx(ProcessingEnvironmentprocessingEnv){
+
+ this.processingEnv=processingEnv;
+ messager=processingEnv.getMessager();
+ filer=processingEnv.getFiler();
+ elementUtils=processingEnv.getElementUtils();
+ typeUtils=processingEnv.getTypeUtils();
+ }
+
+ publicCtx(Messagermessager,Filerfiler,ElementselementUtils,TypestypeUtils){
+
+ this.processingEnv=null;
+ this.messager=messager;
+ this.filer=filer;
+ this.elementUtils=elementUtils;
+ this.typeUtils=typeUtils;
+ }
+ }
+
+ /**
+ * Initialize the ThreadLocal containing the Processing Enviroment. this typically should be
+ * called during the init phase of processing. Be sure to run the clear method at the last round
+ * of processing
+ *
+ * @param processingEnv the current annotation processing enviroment
+ */
+ publicstaticvoidinit(ProcessingEnvironmentprocessingEnv){
+ CTX.set(newCtx(processingEnv));
+ jdkVersion=processingEnv.getSourceVersion().ordinal();
+ previewEnabled=processingEnv.isPreviewEnabled();
+ }
+
+ /**
+ * Initialize the ThreadLocal containing the {@link ProcessingEnvironment}. Be sure to run the
+ * clear method at the last round of processing
+ *
+ * @param context the current annotation processing enviroment
+ * @param jdkVersion the JDK version number
+ * @param preview whether preview features are enabled
+ */
+ publicstaticvoidinit(Ctxcontext,intjdkVersion,booleanpreview){
+ CTX.set(context);
+ jdkVersion=jdkVersion;
+ previewEnabled=preview;
+ }
+ /** Clears the ThreadLocal containing the {@link ProcessingEnvironment}. */
+ publicstaticvoidclear(){
+ CTX.remove();
+ }
+
+ /**
+ * Returns the source version that any generated source and class files should conform to
+ *
+ * @return the source version as an int
+ */
+ publicstaticintjdkVersion(){
+ returnjdkVersion;
+ }
+
+ /**
+ * Returns whether {@code --preview-enabled} has been added to compiler flags.
+ *
+ * @return true if preview features are enabled
+ */
+ publicstaticbooleanpreviewEnabled(){
+ returnpreviewEnabled;
+ }
+
+ /**
+ * Prints an error at the location of the element.
+ *
+ * @param e the element to use as a position hint
+ * @param msg the message, or an empty string if none
+ * @param args {@code String#format} arguments
+ */
+ publicstaticvoidlogError(Elemente,Stringmsg,Object...args){
+ messager().printMessage(Diagnostic.Kind.ERROR,String.format(msg,args),e);
+ }
+
+ /**
+ * Prints an error.
+ *
+ * @param msg the message, or an empty string if none
+ * @param args {@code String#format} arguments
+ */
+ publicstaticvoidlogError(Stringmsg,Object...args){
+ messager().printMessage(Diagnostic.Kind.ERROR,String.format(msg,args));
+ }
+
+ /**
+ * Prints an warning at the location of the element.
+ *
+ * @param e the element to use as a position hint
+ * @param msg the message, or an empty string if none
+ * @param args {@code String#format} arguments
+ */
+ publicstaticvoidlogWarn(Elemente,Stringmsg,Object...args){
+ messager().printMessage(Diagnostic.Kind.WARNING,String.format(msg,args),e);
+ }
+
+ /**
+ * Prints a warning.
+ *
+ * @param msg the message, or an empty string if none
+ * @param args {@code String#format} arguments
+ */
+ publicstaticvoidlogWarn(Stringmsg,Object...args){
+ messager().printMessage(Diagnostic.Kind.WARNING,String.format(msg,args));
+ }
+
+ /**
+ * Prints a note.
+ *
+ * @param msg the message, or an empty string if none
+ * @param args {@code String#format} arguments
+ */
+ publicstaticvoidlogNote(Elemente,Stringmsg,Object...args){
+ messager().printMessage(Diagnostic.Kind.NOTE,String.format(msg,args),e);
+ }
+
+ /**
+ * Prints a note at the location of the element.
+ *
+ * @param e the element to use as a position hint
+ * @param msg the message, or an empty string if none
+ * @param args {@code String#format} arguments
+ */
+ publicstaticvoidlogNote(Stringmsg,Object...args){
+ messager().printMessage(Diagnostic.Kind.NOTE,String.format(msg,args));
+ }
+
+ /**
+ * Returns the elements annotated with the given annotation interface.
+ *
+ * @param round RoundEnviroment to extract the elements
+ * @param annotationFQN the fqn of the annotation
+ * @return the elements annotated with the given annotation interface,or an empty set if there are
+ * none
+ */
+ publicstaticSet<?extendsElement>elementsAnnotatedWith(
+ RoundEnvironmentround,StringannotationFQN){
+
+ returnOptional.ofNullable(typeElement(annotationFQN))
+ .map(round::getElementsAnnotatedWith)
+ .orElse(Set.of());
+ }
+
+ /**
+ * Create a file writer for the given class name.
+ *
+ * @param name canonical (fully qualified) name of the principal class or interface being declared
+ * in this file or a package name followed by {@code ".package-info"} for a package
+ * information file
+ * @param originatingElements class, interface, package, or module elements causally associated
+ * with the creation of this file, may be elided or {@code null}
+ * @return a JavaFileObject to write the new source file
+ */
+ publicstaticJavaFileObjectcreateSourceFile(CharSequencename,Element...originatingElements)
+ throwsIOException{
+ returnfiler().createSourceFile(name,originatingElements);
+ }
+
+ /**
+ * Returns a type element given its canonical name.
+ *
+ * @param name the canonical name
+ * @return the named type element, or null if no type element can be uniquely determined
+ */
+ publicstaticTypeElementtypeElement(Stringname){
+ returnelements().getTypeElement(name);
+ }
+
+ /**
+ * Returns the element corresponding to a type.The type may be a DeclaredType or
+ * TypeVariable.Returns null if the type is not one with a corresponding element.
+ *
+ * @param t the type to map to an element
+ * @return the element corresponding to the given type
+ */
+ publicstaticTypeElementasTypeElement(TypeMirrort){
+
+ return(TypeElement)types().asElement(t);
+ }
+
+ /**
+ * Get current {@link ProcessingEnvironment}
+ *
+ * @return the enviroment
+ */
+ publicstaticProcessingEnvironmentprocessingEnv(){
+ returnCTX.get().processingEnv;
+ }
+
+ /**
+ * Get current {@link Filer} from the {@link ProcessingEnvironment}
+ *
+ * @return the filer
+ */
+ publicstaticFilerfiler(){
+ returnCTX.get().filer;
+ }
+
+ /**
+ * Get current {@link Elements} from the {@link ProcessingEnvironment}
+ *
+ * @return the filer
+ */
+ publicstaticElementselements(){
+ returnCTX.get().elementUtils;
+ }
+
+ /**
+ * Get current {@link Messager} from the {@link ProcessingEnvironment}
+ *
+ * @return the messager
+ */
+ publicstaticMessagermessager(){
+ returnCTX.get().messager;
+ }
+
+ /**
+ * Get current {@link Types} from the {@link ProcessingEnvironment}
+ *
+ * @return the types
+ */
+ publicstaticTypestypes(){
+ returnCTX.get().typeUtils;
+ }
+
+ /**
+ * Determine whether the first type can be assigned to the second
+ *
+ * @param type string type to check
+ * @param superType the type that should be assignable to.
+ * @return true if type can be assinged to supertype
+ */
+ publicstaticbooleanisAssignable(Stringtype,StringsuperType){
+ returntype.equals(superType)||isAssignable(typeElement(type),superType);
+ }
+
+ /**
+ * Determine whether the first type can be assigned to the second
+ *
+ * @param type type to check
+ * @param superType the type that should be assignable to.
+ * @return true if type can be assinged to supertype
+ */
+ publicstaticbooleanisAssignable(TypeElementtype,StringsuperType){
+ returnOptional.ofNullable(type).stream()
+ .flatMap(APContext::superTypes)
+ .anyMatch(superType::equals);
+ }
+
+ privatestaticStream<String>superTypes(TypeElementelement){
+ finalvartypes=types();
+ returntypes.directSupertypes(element.asType()).stream()
+ .filter(type->!type.toString().contains("java.lang.Object"))
+ .map(superType->(TypeElement)types.asElement(superType))
+ .flatMap(e->Stream.concat(superTypes(e),Stream.of(e)))
+ .map(Object::toString);
+ }
+
+ /**
+ * Discover the {@link ModuleElement} for the project being processed and set in the context.
+ *
+ * @param annotations the annotation interfaces requested to be processed
+ * @param roundEnv environment for information about the current and prior round
+ */
+ publicstaticvoidsetProjectModuleElement(
+ Set<?extendsTypeElement>annotations,RoundEnvironmentroundEnv){
+ if(CTX.get().module==null){
+ CTX.get().module=
+ annotations.stream()
+ .map(roundEnv::getElementsAnnotatedWith)
+ .filter(not(Collection::isEmpty))
+ .findAny()
+ .map(s->s.iterator().next())
+ .map(elements()::getModuleOf)
+ .orElse(null);
+ }
+ }
+
+ /**
+ * Retrieve the project's {@link ModuleElement}. {@code setProjectModuleElement} must be called
+ * before this.
+ *
+ * @return the {@link ModuleElement} associated with the current project
+ */
+ publicstaticModuleElementgetProjectModuleElement(){
+ returnCTX.get().module;
+ }
+
+ /**
+ * Gets a {@link BufferedReader} for the project's {@code module-info.java} source file.
+ *
+ * <p>Calling {@link ModuleElement}'s {@code getDirectives()} method has a chance of making
+ * compilation fail in certain situations. Therefore, manually parsing {@code module-info.java}
+ * seems to be the safest way to get module information.
+ *
+ * @return
+ * @throws IOException if unable to read the module-info
+ */
+ publicstaticBufferedReadergetModuleInfoReader()throwsIOException{
+ varinputStream=
+ filer()
+ .getResource(StandardLocation.SOURCE_PATH,"","module-info.java")
+ .toUri()
+ .toURL()
+ .openStream();
+ returnnewBufferedReader(newInputStreamReader(inputStream));
+ }
+ }
+
+
+
+
+
@GenerateModuleInfoReader
+
+ There is currently a bug in javac where in certain situations calling ModuleElement#getDirectives on
+ the application's root module crashes compilation. Using @GenerateModuleInfoReader in combination with
+ APContext generates classes that allow you to read a
+ module's directives by parsing the module-info.java source file.
+
+ Using this annotation will generate classes with useful methods for processing TypeMirrors and their component
+ parts.
+
+
+
UType
+
+ This generated utility interface has methods for handling type mirrors and extracting their
+ component types and annotations (including TYPE_USE). In addition it allows us to get a readable
+ source-code safe string of the type names as well as the required imports.
+
+ The generated UType interface looks like the below.
+
+
+ Generated Code: (click to expand)
+
@Generated("avaje-prism-generator")
+publicinterfaceUType{
+
+ /**
+ * Create a UType from the given TypeMirror.
+ */
+ staticUTypeparse(TypeMirrormirror){
+ //interface implementation is generated too
+ returnTypeMirrorVisitor.create(mirror);
+ }
+
+ /**
+ * Return all the import types needed to write this mirror in source code (annotations included).
+ *
+ * @return Return the import types required.
+ */
+ Set<String>importTypes();
+
+ /**
+ * Return the full type as a code safe string. (with annotations if present)
+ *
+ * @return the full typeName
+ */
+ Stringfull();
+
+ /**
+ * Return the main type (outermost type). e.g for mirror {@ java.util.List<Something> you'll get java.util.List
+ *
+ * @return the outermost type
+ */
+ StringmainType();
+
+ /**
+ * Return the full (but unqualified) type as a code safe string. Use in tandem with {@link
+ * #importTypes()} to generate readable code
+ *
+ * @return the short name with unqualified type
+ */
+ StringshortType();
+
+ /**
+ * Return the first generic parameter.
+ *
+ * @see UType#componentTypes
+ */
+ defaultUTypeparam0(){
+ returnnull;
+ }
+
+ /**
+ * Return the second componentType.
+ *
+ * @see UType#componentTypes
+ */
+ defaultUTypeparam1(){
+ returnnull;
+ }
+
+ /**
+ * Retrieve the component types associated with this mirror.
+ *
+ * <ul>
+ * <li>{@link TypeKind#ARRAY}: will contain the array componentType
+ * <li>{@link TypeKind#DECLARED}: will contain the generic parameters
+ * <li>{@link TypeKind#TYPEVAR}: will contain the upper bound for the type variable
+ * <li>{@link TypeKind#WILDCARD}: will contain the extends bound or super bound
+ * <li>{@link TypeKind#INTERSECTION}: will contain the bounds of the intersection
+ * <li>{@link TypeKind#UNION}: will contain the alternative types
+ * </ul>
+ *
+ * @return the component types
+ */
+ defaultList<UType>componentTypes(){
+ returnList.of();
+ }
+
+ /** The {@link TypeKind} of the type mirror used to create this Utype. */
+ TypeKindkind();
+
+ /**
+ * Returns whether the type mirror is generic
+ *
+ * @return whether the type is generic
+ */
+ defaultbooleanisGeneric(){
+ returnfalse;
+ }
+
+ /**
+ * Return the annotation mirrors directly on the type.
+ *
+ * <p>For a {@code UType} representing {@code @NotEmpty Map<@Notblank String, Object>} you will
+ * get mirrors for {@code @NotEmpty} only
+ *
+ * @return the annotations directly present
+ */
+ defaultList<AnnotationMirror>annotations(){
+ returnList.of();
+ }
+
+ /**
+ * Return the annotation mirrors directly on the type and in within generic type use.
+ *
+ * <p>For a {@code UType} representing {@code @NotEmpty Map<@Notblank String, Object>} you will
+ * get mirrors for {@code @NotEmpty} and {@code @Notblank}
+ *
+ * @return all annotations present on this type
+ */
+ defaultList<AnnotationMirror>allAnnotationsInType(){
+ returnList.of();
+ }
+
+ /**
+ * Return the full type as a string, stripped of annotations.
+ *
+ * @return full type, but without annotations
+ */
+ defaultStringfullWithoutAnnotations(){
+ returnProcessorUtils.trimAnnotations(full()).replace(",",", ");
+ }
+
+ /**
+ * Return the short type as a string, stripped of annotations.
+ *
+ * @return short type, but without annotations
+ */
+ defaultStringshortWithoutAnnotations(){
+ returnProcessorUtils.trimAnnotations(shortType()).replace(",",", ");
+ }
+
+ /** Compare whether the current full() type is identical to the given UType's full() type */
+ @Override
+ booleanequals(Objectother);
+}
+
- Avaje-jsonb uses APT source code generation to generate
+ Avaje-Jsonb uses APT source code generation to generate
adapters for types annotated with @Json. This makes it a fast and light dependency, especially good for
http clients, CLI applications and use with GraalVM native images.
2. Add avaje-inject-generator annotation processor as a dependency with provided scope.
-
<!-- Annotation processor -->
+
<!-- Annotation processors -->
+<!-- if using lombok, it must be placed before the inject generator.
+<dependency>
+ <groupId>org.projectlombok</groupId>
+ <artifactId>lombok</artifactId>
+ <version>1.18.30</version>
+ <scope>provided</scope>
+</dependency> --><dependency><groupId>io.avaje</groupId><artifactId>avaje-inject-generator</artifactId>
@@ -338,17 +345,6 @@
2. Add avaje-inject-generator annotation processor as a dependency
-
2a. JDK 23+
-
In JDK 23+, annotation processors are disabled by default, so we need to add a flag to re-enable.
- A Singleton bean can implement javax.inject.Provider<T> to create a bean to
+ A Singleton bean can implement (javax/jakarta).inject.Provider<T> to create a bean to
be used in injection.
@Singleton
@@ -1216,6 +1217,55 @@
@Prototype
}
+
+
@Lazy
+
+ We can use @Lazy on beans/factory methods to defer bean initialization until the annotated bean is requested.
+
+
+
@Lazy
+publicclassSloth{
+
+ privatefinalMossmoss;
+
+ @Inject//this will not be called until the bean is requested
+ Sloth(Mossmoss){
+ this.moss=moss;
+ }
+}
+
+
+
+ There are two ways to lazily load the bean.
+
+
1. Directly retrieve from the bean scope:
+
finalvarscope=BeanScope.builder().build();
+scope.get(Sloth.class);//Sloth is initialized here
+
+
+
+
2. Use a Provider
+
+
Unlike regular provider beans, the providers for lazy beans will return the same singleton instance.
+
+
@Singleton
+classUseSloth{
+
+ privatefinalProvider<Sloth>slothProvider;
+
+ UseFoo(Provider<Sloth>slothProvider){
+ this.slothProvider=slothProvider;
+ }
+
+ voiddoStuff(){
+
+ // get the singleton Sloth instance and use it
+ Slothsloth=slothProvider.get();
+ ...
+ }
+}
+
- The avaje-jsonb-generator annotation processor will generate a JsonAdapter as java source code for each type annotated with @Json.
- These will be automatically registered with Jsonb using a service loader mechanism.
- For types we can not annotate with @Json we can instead use @Json.Import.
-
-
@Json
+
3. Add @Json onto types we want to serialize.
+
+ The avaje-jsonb-generator annotation processor will generate a JsonAdapter as java source code for each type
+ annotated with @Json.
+ These will be automatically registered with Jsonb using a service loader mechanism.
+ For types we can not annotate with @Json we can instead use @Json.Import.
+
// build using defaultsJsonbjsonb=Jsonb.builder().build();JsonType<Customer>customerType=jsonb.type(Customer.class);
@@ -206,33 +225,35 @@
-
- 5. JsonViews
-
-
This library supports dynamic json views which allow us to specify which specific properties to include when serialising to json.
+
+ 5. JsonViews
+
+
This library supports dynamic json views which allow us to specify which specific properties to include when
+ serialising to json.
-
Jsonbjsonb=Jsonb.builder().build();
+
Jsonbjsonb=Jsonb.builder().build();JsonType<Customer>customerType=jsonb.type(Customer.class);// only including the id and nameJsonView<Customer>idAndNameView=customerType.view("(id, name)");
-StringasJson=idAndNameView.toJson(customer);
+StringasJson=idAndNameView.toJson(customer);JsonView<Customer>myView=customerType.view("(id, name, billingAddress(*), contacts(lastName, email))");// serialize to json the above specified properties only
-StringasJson=myView.toJson(customer);
+StringasJson=myView.toJson(customer);
Spring/Avaje Inject Integration
- When used with Spring or Avaje Inject, a default Jsonb instance will be provided and used for serializing/deserializing Http messages. The following properties can be added to configure the default instance.
+ When used with Spring or Avaje Inject, a default Jsonb instance will be provided and used for
+ serializing/deserializing Http messages. The following properties can be added to configure the default instance.
-
- In the example above, org.example.jsonb.GeneratedComponent is generated code typically found in
+ In the example above, org.example.jsonb.GeneratedComponent is generated code typically found in
target/generated-sources/annotations.
-
@Json
-
-Types with @Json are picked up by avaje-jsonb-generator at compile time and a JsonAdapter is generated as java source code typically in target/generated-sources/annotations.
-
-
Constructors
-
- The types can be a record/class and have constructors. When types do not have a default constructor (e.g. record types) then the generated code will use the constructor. Fields in the constructor do not need or use setter methods.
-
+
@Json
+
+ Types with @Json are picked up by avaje-jsonb-generator at compile time and a JsonAdapter is generated
+ as java source code typically in target/generated-sources/annotations.
+
+
Constructors
+
+ The types can be a record/class and have constructors. When types do not have a default constructor (e.g. record
+ types) then the generated code will use the constructor. Fields in the constructor do not need or use setter
+ methods.
+
-
//Example record - all fields set via constructor
+
//Example record - all fields set via constructor@JsonpublicrecordAddress(Stringstreet,Stringsuburb,Stringcity){}
-
-All the fields of record types are set via constructor - no setters here.
-
-When a class has a constructor like the City example below, then fields in the constructor do not need or use a setter method. We only need a setter method for fields that are not in the constructor.
-
-
@Json
+
+ All the fields of record types are set via constructor - no setters here.
+
+ When a class has a constructor like the City example below, then fields in the constructor do not need or use a
+ setter method. We only need a setter method for fields that are not in the constructor.
+
- In the example above the id and name fields are set via constructor
- and only zone is set via setter method.
-
+
+ In the example above the id and name fields are set via constructor
+ and only zone is set via setter method.
+
-
Setter methods
-
- Fields that are not set via the constructor need to have a setter methods. There are 4 styles of setter methods that avaje-jsonb-generator will find.
-
-
// traditional setter
+
Setter methods
+
+ Fields that are not set via the constructor need to have a setter methods. There are 4 styles of setter methods that
+ avaje-jsonb-generator will find.
+
+
// traditional setterpublicvoidsetSuburb(Stringsuburb){this.suburb=suburb;}
@@ -338,20 +364,20 @@
Setter methods
-
Naming Convention
-
- We can specify a naming convention via the naming attribute of @Json.
- This naming convention translates field names to json property names.
- The result of changing the naming convention can be seen in the generated JsonAdapter code.
-
-
@Json(naming=LowerHyphen)
+
Naming Convention
+
+ We can specify a naming convention via the naming attribute of @Json.
+ This naming convention translates field names to json property names.
+ The result of changing the naming convention can be seen in the generated JsonAdapter code.
+
+
@Json(naming=LowerHyphen)publicclassCustomer{...}
-
//The Naming options are below with the default of Match.
+
//The Naming options are below with the default of Match.enumNaming{Match,LowerHyphen,
@@ -365,9 +391,9 @@
- Effectively, we have renamed this property and will not be able to deserialize this from a json of {"name":"Jolyne"}.
- It will now only deserialize for the new name. {"SomeOtherName":"Jolyne"}.
- If you wish to only specify an alias for the json property, use @Alias.
-
-
-
Generated Code
+
+ Effectively, we have renamed this property and will not be able to deserialize this from a json of
+ {"name":"Jolyne"}.
+ It will now only deserialize for the new name. {"SomeOtherName":"Jolyne"}.
+ If you wish to only specify an alias for the json property, use @Alias.
+
-
@Json.Property makes the following changes to the generated JsonAdapter:
-
@Generated
+
+ Generated Code: (click to expand)
+
@Json.Property makes the following changes to the generated JsonAdapter:
- With @Json.Creator, we can override deserialization using a constructor or static factory method. Contructor/Method parameters can be annotated with @Alias to rename a deserialization field.
-
+
@Json.Creator
+
+ With @Json.Creator, we can override deserialization using a constructor or static factory method.
+ Contructor/Method parameters can be annotated with @Alias to rename a deserialization field.
+
-We can use @Json.Unmapped to collect unmapped json during de-serialization and include it in serialization.
-
-The @Json.Unmapped annotation must be on a field of type Map
-
+
@Json.Unmapped
+
+ We can use @Json.Unmapped to collect unmapped json during de-serialization and include it in
+ serialization.
+
+ The @Json.Unmapped annotation must be on a field of type Map
+
+ We can use @Json.Value to specify a method that will provide the value used to serialize to/from json.
+
+
-
-We can use @Json.Value on Enum types to specify a method that provides the values that will be used to serialize to/from json. This works for any type that can be compared with .equals()
-
-In the example below the values used in the json content is "one value" and "two value" rather than the usual "ONE" and "TWO".
-
+
Inlining Classes
-
publicenumMyEnum{
+
+ When using @Json.Value on a class method, a special adapter is generated that will use this value to
+ (de)serialize.
+
+ In the example below, the class is serialized as it were a String object.
+
+ When using @Json.Value with Enum methods, a specialized adapter using an EnumMap will be
+ generated to cache the constant values for (de)serialization. This works for any method return type that can be
+ compared with .equals()
+
+ In the example below the values used in the json content is "one value" and "two value" rather than the usual "ONE"
+ and "TWO".
+
- Mark this Class as a MixIn Type that can add Jsonb Annotations on the specified type.
-
- Say we want to override the field serialization behavior on a class we can't modify.(Typically in an external project/dependency or otherwise)
-
+
+ Mark this Class as a MixIn Type that can add Jsonb Annotations on the specified type.
+
+ Say we want to override the field serialization behavior on a class we can't modify.(Typically in an external
+ project/dependency or otherwise)
+
For mapping polymorphic types we specify on the parent type a @Json.Subtype for each concrete sub-type that can represent that type.
+
For mapping polymorphic types we specify on the parent type a @Json.Subtype for each concrete sub-type
+ that can represent that type.
-
By default the "type property" that specifies the type in json is "@type". Use @Json(typeProperty=...) to specify the name of the type property.
+
By default the "type property" that specifies the type in json is "@type". Use
+ @Json(typeProperty=...) to specify the name of the type property.
+
-Note: There is a current limitation that polymorphic types do not yet support "Json Views".
+ Note: There is a current limitation that polymorphic types do not yet support "Json Views".
-
- With @CustomAdapter, you can define your own JsonAdapter for your more esoteric serialization needs.
- A custom adapter registered using this annotation must have a public constructor accepting a Jsonb instance (or a public static JsonAdapter.Factory FACTORY field for generic adapters), and must directly implement the JsonAdapter Interface.
-
+
@CustomAdapter
+
+ With @CustomAdapter, you can define your own JsonAdapter for your more esoteric serialization needs.
+ A custom adapter registered using this annotation must have a public constructor accepting a Jsonb instance (or a
+ public static JsonAdapter.Factory FACTORY field for generic adapters), and must directly implement the
+ JsonAdapter Interface.
+