diff --git a/.gitignore b/.gitignore
index 4e9567af1aa..05954172d63 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,6 +3,7 @@ target
.classpath
.project
.settings
+.env
bin
build.log
integration-repo
@@ -55,4 +56,3 @@ tmp
plans
-
diff --git a/models/spring-ai-openai-official/README.md b/models/spring-ai-openai-official/README.md
new file mode 100644
index 00000000000..a51fe3c2cf6
--- /dev/null
+++ b/models/spring-ai-openai-official/README.md
@@ -0,0 +1,5 @@
+# OpenAI Java API Library
+
+This is the official OpenAI Java SDK from OpenAI, which provides integration with OpenAI's services, including Azure OpenAI.
+
+[OpenAI Java API Library GitHub repository](https://github.com/openai/openai-java)
diff --git a/models/spring-ai-openai-official/pom.xml b/models/spring-ai-openai-official/pom.xml
new file mode 100644
index 00000000000..8cb93891c14
--- /dev/null
+++ b/models/spring-ai-openai-official/pom.xml
@@ -0,0 +1,88 @@
+
+
+
+
+ 4.0.0
+
+ org.springframework.ai
+ spring-ai-parent
+ 1.1.0-SNAPSHOT
+ ../../pom.xml
+
+ spring-ai-openai-official
+ jar
+ Spring AI Model - OpenAI Official
+ OpenAI Java API Library support
+ https://github.com/spring-projects/spring-ai
+
+
+ https://github.com/spring-projects/spring-ai
+ git://github.com/spring-projects/spring-ai.git
+ git@github.com:spring-projects/spring-ai.git
+
+
+
+
+
+
+
+
+
+ org.springframework.ai
+ spring-ai-model
+ ${project.parent.version}
+
+
+
+ com.openai
+ openai-java
+ ${openai-official.version}
+
+
+
+
+ org.springframework
+ spring-context-support
+
+
+
+ org.slf4j
+ slf4j-api
+
+
+
+
+ org.springframework.ai
+ spring-ai-test
+ ${project.version}
+ test
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+ io.micrometer
+ micrometer-observation-test
+ test
+
+
+
+
diff --git a/models/spring-ai-openai-official/src/main/java/org/springframework/ai/openaiofficial/OpenAiOfficialEmbeddingModel.java b/models/spring-ai-openai-official/src/main/java/org/springframework/ai/openaiofficial/OpenAiOfficialEmbeddingModel.java
new file mode 100644
index 00000000000..64238f9eeed
--- /dev/null
+++ b/models/spring-ai-openai-official/src/main/java/org/springframework/ai/openaiofficial/OpenAiOfficialEmbeddingModel.java
@@ -0,0 +1,166 @@
+package org.springframework.ai.openaiofficial;
+
+import com.openai.client.OpenAIClient;
+import com.openai.models.embeddings.CreateEmbeddingResponse;
+import com.openai.models.embeddings.EmbeddingCreateParams;
+import io.micrometer.observation.ObservationRegistry;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.ai.chat.metadata.DefaultUsage;
+import org.springframework.ai.document.Document;
+import org.springframework.ai.document.MetadataMode;
+import org.springframework.ai.embedding.AbstractEmbeddingModel;
+import org.springframework.ai.embedding.Embedding;
+import org.springframework.ai.embedding.EmbeddingRequest;
+import org.springframework.ai.embedding.EmbeddingResponse;
+import org.springframework.ai.embedding.EmbeddingResponseMetadata;
+import org.springframework.ai.embedding.observation.DefaultEmbeddingModelObservationConvention;
+import org.springframework.ai.embedding.observation.EmbeddingModelObservationContext;
+import org.springframework.ai.embedding.observation.EmbeddingModelObservationConvention;
+import org.springframework.ai.embedding.observation.EmbeddingModelObservationDocumentation;
+import org.springframework.ai.model.EmbeddingUtils;
+import org.springframework.ai.observation.conventions.AiProvider;
+import org.springframework.util.Assert;
+import org.springframework.util.CollectionUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Embedding Model implementation using the OpenAI Java SDK.
+ *
+ * @author Julien Dubois
+ */
+public class OpenAiOfficialEmbeddingModel extends AbstractEmbeddingModel {
+
+ private static final String DEFAULT_MODEL_NAME = OpenAiOfficialEmbeddingOptions.DEFAULT_EMBEDDING_MODEL;
+
+ private static final EmbeddingModelObservationConvention DEFAULT_OBSERVATION_CONVENTION = new DefaultEmbeddingModelObservationConvention();
+
+ private static final Logger logger = LoggerFactory.getLogger(OpenAiOfficialEmbeddingModel.class);
+
+ private final OpenAIClient openAiClient;
+
+ private final OpenAiOfficialEmbeddingOptions defaultOptions;
+
+ private final MetadataMode metadataMode;
+
+ private final ObservationRegistry observationRegistry;
+
+ private EmbeddingModelObservationConvention observationConvention = DEFAULT_OBSERVATION_CONVENTION;
+
+ public OpenAiOfficialEmbeddingModel(OpenAIClient openAiClient) {
+ this(openAiClient, MetadataMode.EMBED);
+ }
+
+ public OpenAiOfficialEmbeddingModel(OpenAIClient openAiClient, MetadataMode metadataMode) {
+ this(openAiClient, metadataMode, OpenAiOfficialEmbeddingOptions.builder().model(DEFAULT_MODEL_NAME).build());
+ }
+
+ public OpenAiOfficialEmbeddingModel(OpenAIClient openAiClient, MetadataMode metadataMode,
+ OpenAiOfficialEmbeddingOptions options) {
+ this(openAiClient, metadataMode, options, ObservationRegistry.NOOP);
+ }
+
+ public OpenAiOfficialEmbeddingModel(OpenAIClient openAiClient, MetadataMode metadataMode,
+ OpenAiOfficialEmbeddingOptions options, ObservationRegistry observationRegistry) {
+
+ Assert.notNull(openAiClient, "com.openai.client.OpenAIClient must not be null");
+ Assert.notNull(metadataMode, "Metadata mode must not be null");
+ Assert.notNull(options, "Options must not be null");
+ Assert.notNull(options.getModel(), "Model name must not be null");
+ Assert.notNull(observationRegistry, "Observation registry must not be null");
+ this.openAiClient = openAiClient;
+ this.metadataMode = metadataMode;
+ this.defaultOptions = options;
+ this.observationRegistry = observationRegistry;
+ }
+
+ @Override
+ public float[] embed(Document document) {
+ EmbeddingResponse response = this
+ .call(new EmbeddingRequest(List.of(document.getFormattedContent(this.metadataMode)), null));
+
+ if (CollectionUtils.isEmpty(response.getResults())) {
+ return new float[0];
+ }
+ return response.getResults().get(0).getOutput();
+ }
+
+ @Override
+ public EmbeddingResponse call(EmbeddingRequest embeddingRequest) {
+ OpenAiOfficialEmbeddingOptions options = OpenAiOfficialEmbeddingOptions.builder()
+ .from(this.defaultOptions)
+ .merge(embeddingRequest.getOptions())
+ .build();
+
+ EmbeddingRequest embeddingRequestWithMergedOptions = new EmbeddingRequest(embeddingRequest.getInstructions(),
+ options);
+
+ EmbeddingCreateParams embeddingCreateParams = options
+ .toOpenAiCreateParams(embeddingRequestWithMergedOptions.getInstructions());
+
+ if (logger.isTraceEnabled()) {
+ logger.trace("OpenAiOfficialEmbeddingModel call {} with the following options : {} ", options.getModel(),
+ embeddingCreateParams);
+ }
+
+ var observationContext = EmbeddingModelObservationContext.builder()
+ .embeddingRequest(embeddingRequestWithMergedOptions)
+ .provider(AiProvider.OPENAI_OFFICIAL.value())
+ .build();
+
+ return Objects.requireNonNull(
+ EmbeddingModelObservationDocumentation.EMBEDDING_MODEL_OPERATION
+ .observation(this.observationConvention, DEFAULT_OBSERVATION_CONVENTION, () -> observationContext,
+ this.observationRegistry)
+ .observe(() -> {
+ CreateEmbeddingResponse response = this.openAiClient.embeddings().create(embeddingCreateParams);
+
+ var embeddingResponse = generateEmbeddingResponse(response);
+ observationContext.setResponse(embeddingResponse);
+ return embeddingResponse;
+ }));
+ }
+
+ private EmbeddingResponse generateEmbeddingResponse(CreateEmbeddingResponse response) {
+
+ List data = generateEmbeddingList(response.data());
+ EmbeddingResponseMetadata metadata = new EmbeddingResponseMetadata();
+ metadata.setModel(response.model());
+ metadata.setUsage(getDefaultUsage(response.usage()));
+ return new EmbeddingResponse(data, metadata);
+ }
+
+ private DefaultUsage getDefaultUsage(CreateEmbeddingResponse.Usage nativeUsage) {
+ return new DefaultUsage(Math.toIntExact(nativeUsage.promptTokens()), 0,
+ Math.toIntExact(nativeUsage.totalTokens()), nativeUsage);
+ }
+
+ private List generateEmbeddingList(List nativeData) {
+ List data = new ArrayList<>();
+ for (com.openai.models.embeddings.Embedding nativeDatum : nativeData) {
+ List nativeDatumEmbedding = nativeDatum.embedding();
+ long nativeIndex = nativeDatum.index();
+ Embedding embedding = new Embedding(EmbeddingUtils.toPrimitive(nativeDatumEmbedding),
+ Math.toIntExact(nativeIndex));
+ data.add(embedding);
+ }
+ return data;
+ }
+
+ public OpenAiOfficialEmbeddingOptions getDefaultOptions() {
+ return this.defaultOptions;
+ }
+
+ /**
+ * Use the provided convention for reporting observation data
+ * @param observationConvention The provided convention
+ */
+ public void setObservationConvention(EmbeddingModelObservationConvention observationConvention) {
+ Assert.notNull(observationConvention, "observationConvention cannot be null");
+ this.observationConvention = observationConvention;
+ }
+
+}
diff --git a/models/spring-ai-openai-official/src/main/java/org/springframework/ai/openaiofficial/OpenAiOfficialEmbeddingOptions.java b/models/spring-ai-openai-official/src/main/java/org/springframework/ai/openaiofficial/OpenAiOfficialEmbeddingOptions.java
new file mode 100644
index 00000000000..90898e41fa4
--- /dev/null
+++ b/models/spring-ai-openai-official/src/main/java/org/springframework/ai/openaiofficial/OpenAiOfficialEmbeddingOptions.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright 2023-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.ai.openaiofficial;
+
+import com.openai.models.embeddings.EmbeddingCreateParams;
+import org.springframework.ai.embedding.EmbeddingOptions;
+
+import java.util.List;
+
+import static com.openai.models.embeddings.EmbeddingModel.TEXT_EMBEDDING_ADA_002;
+
+/**
+ * Configuration information for the Embedding Model implementation using the OpenAI Java
+ * SDK.
+ *
+ * @author Julien Dubois
+ */
+public class OpenAiOfficialEmbeddingOptions implements EmbeddingOptions {
+
+ public static final String DEFAULT_EMBEDDING_MODEL = TEXT_EMBEDDING_ADA_002.asString();
+
+ /**
+ * An identifier for the caller or end user of the operation. This may be used for
+ * tracking or rate-limiting purposes.
+ */
+ private String user;
+
+ /**
+ * The model name used. When using Azure AI Foundry, this is also used as the default
+ * deployment name.
+ */
+ private String model;
+
+ /**
+ * The deployment name as defined in Azure AI Foundry. On Azure AI Foundry, the
+ * default deployment name is the same as the model name. When using OpenAI directly,
+ * this value isn't used.
+ */
+ private String deploymentName;
+
+ /*
+ * The number of dimensions the resulting output embeddings should have. Only
+ * supported in `text-embedding-3` and later models.
+ */
+ private Integer dimensions;
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ @Override
+ public String getModel() {
+ return this.model;
+ }
+
+ public void setModel(String model) {
+ this.model = model;
+ }
+
+ public String getUser() {
+ return this.user;
+ }
+
+ public void setUser(String user) {
+ this.user = user;
+ }
+
+ public String getDeploymentName() {
+ return this.deploymentName;
+ }
+
+ public void setDeploymentName(String deploymentName) {
+ this.deploymentName = deploymentName;
+ }
+
+ @Override
+ public Integer getDimensions() {
+ return this.dimensions;
+ }
+
+ public void setDimensions(Integer dimensions) {
+ this.dimensions = dimensions;
+ }
+
+ @Override
+ public String toString() {
+ return "OpenAiOfficialEmbeddingOptions{" + "user='" + user + '\'' + ", model='" + model + '\''
+ + ", deploymentName='" + deploymentName + '\'' + ", dimensions=" + dimensions + '}';
+ }
+
+ public EmbeddingCreateParams toOpenAiCreateParams(List instructions) {
+
+ EmbeddingCreateParams.Builder builder = EmbeddingCreateParams.builder();
+
+ // Use deployment name if available (for Azure AI Foundry), otherwise use model
+ // name
+ if (this.getDeploymentName() != null) {
+ builder.model(this.getDeploymentName());
+ }
+ else if (this.getModel() != null) {
+ builder.model(this.getModel());
+ }
+
+ if (instructions != null && !instructions.isEmpty()) {
+ builder.input(EmbeddingCreateParams.Input.ofArrayOfStrings(instructions));
+ }
+ if (this.getUser() != null) {
+ builder.user(this.getUser());
+ }
+ if (this.getDimensions() != null) {
+ builder.dimensions(this.getDimensions());
+ }
+ return builder.build();
+ }
+
+ public static final class Builder {
+
+ private final OpenAiOfficialEmbeddingOptions options = new OpenAiOfficialEmbeddingOptions();
+
+ public Builder from(OpenAiOfficialEmbeddingOptions fromOptions) {
+ this.options.setUser(fromOptions.getUser());
+ this.options.setModel(fromOptions.getModel());
+ this.options.setDeploymentName(fromOptions.getDeploymentName());
+ this.options.setDimensions(fromOptions.getDimensions());
+ return this;
+ }
+
+ public Builder merge(EmbeddingOptions from) {
+ if (from instanceof OpenAiOfficialEmbeddingOptions castFrom) {
+
+ if (castFrom.getUser() != null) {
+ this.options.setUser(castFrom.getUser());
+ }
+ if (castFrom.getModel() != null) {
+ this.options.setModel(castFrom.getModel());
+ }
+ if (castFrom.getDeploymentName() != null) {
+ this.options.setDeploymentName(castFrom.getDeploymentName());
+ }
+ if (castFrom.getDimensions() != null) {
+ this.options.setDimensions(castFrom.getDimensions());
+ }
+ }
+ return this;
+ }
+
+ public Builder from(EmbeddingCreateParams openAiCreateParams) {
+
+ if (openAiCreateParams.user().isPresent()) {
+ this.options.setUser(openAiCreateParams.user().get());
+ }
+ this.options.setModel(openAiCreateParams.model().toString());
+ this.options.setDeploymentName(openAiCreateParams.model().toString());
+ if (openAiCreateParams.dimensions().isPresent()) {
+ this.options.setDimensions(Math.toIntExact(openAiCreateParams.dimensions().get()));
+ }
+ return this;
+ }
+
+ public Builder user(String user) {
+ this.options.setUser(user);
+ return this;
+ }
+
+ public Builder deploymentName(String deploymentName) {
+ this.options.setDeploymentName(deploymentName);
+ return this;
+ }
+
+ public Builder model(String model) {
+ this.options.setModel(model);
+ return this;
+ }
+
+ public Builder dimensions(Integer dimensions) {
+ this.options.dimensions = dimensions;
+ return this;
+ }
+
+ public OpenAiOfficialEmbeddingOptions build() {
+ if (this.options.deploymentName == null) {
+ this.options.deploymentName = this.options.model;
+ }
+ return this.options;
+ }
+
+ }
+
+}
diff --git a/models/spring-ai-openai-official/src/main/java/org/springframework/ai/openaiofficial/OpenAiOfficialImageModel.java b/models/spring-ai-openai-official/src/main/java/org/springframework/ai/openaiofficial/OpenAiOfficialImageModel.java
new file mode 100644
index 00000000000..9dc5dc5f321
--- /dev/null
+++ b/models/spring-ai-openai-official/src/main/java/org/springframework/ai/openaiofficial/OpenAiOfficialImageModel.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright 2023-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.ai.openaiofficial;
+
+import com.openai.client.OpenAIClient;
+import com.openai.models.images.ImageGenerateParams;
+import io.micrometer.observation.ObservationRegistry;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.ai.image.Image;
+import org.springframework.ai.image.ImageGeneration;
+import org.springframework.ai.image.ImageModel;
+import org.springframework.ai.image.ImagePrompt;
+import org.springframework.ai.image.ImageResponse;
+import org.springframework.ai.image.ImageResponseMetadata;
+import org.springframework.ai.image.observation.DefaultImageModelObservationConvention;
+import org.springframework.ai.image.observation.ImageModelObservationContext;
+import org.springframework.ai.image.observation.ImageModelObservationConvention;
+import org.springframework.ai.image.observation.ImageModelObservationDocumentation;
+import org.springframework.ai.observation.conventions.AiProvider;
+import org.springframework.ai.openaiofficial.metadata.OpenAiOfficialImageGenerationMetadata;
+import org.springframework.ai.openaiofficial.metadata.OpenAiOfficialImageResponseMetadata;
+import org.springframework.util.Assert;
+
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Image Model implementation using the OpenAI Java SDK.
+ *
+ * @author Julien Dubois
+ */
+public class OpenAiOfficialImageModel implements ImageModel {
+
+ private static final String DEFAULT_MODEL_NAME = OpenAiOfficialImageOptions.DEFAULT_IMAGE_MODEL;
+
+ private static final ImageModelObservationConvention DEFAULT_OBSERVATION_CONVENTION = new DefaultImageModelObservationConvention();
+
+ private final Logger logger = LoggerFactory.getLogger(OpenAiOfficialImageModel.class);
+
+ private final OpenAIClient openAiClient;
+
+ private final OpenAiOfficialImageOptions defaultOptions;
+
+ private final ObservationRegistry observationRegistry;
+
+ private ImageModelObservationConvention observationConvention = DEFAULT_OBSERVATION_CONVENTION;
+
+ public OpenAiOfficialImageModel(OpenAIClient openAIClient) {
+ this(openAIClient, OpenAiOfficialImageOptions.builder().model(DEFAULT_MODEL_NAME).build(),
+ ObservationRegistry.NOOP);
+ }
+
+ public OpenAiOfficialImageModel(OpenAIClient openAiClient, OpenAiOfficialImageOptions options,
+ ObservationRegistry observationRegistry) {
+ Assert.notNull(openAiClient, "com.openai.client.OpenAIClient must not be null");
+ Assert.notNull(options, "OpenAiOfficialImageOptions must not be null");
+ Assert.notNull(options.getModel(), "Model name must not be null");
+ Assert.notNull(observationRegistry, "Observation registry must not be null");
+ this.openAiClient = openAiClient;
+ this.defaultOptions = options;
+ this.observationRegistry = observationRegistry;
+ }
+
+ public OpenAiOfficialImageOptions getDefaultOptions() {
+ return this.defaultOptions;
+ }
+
+ @Override
+ public ImageResponse call(ImagePrompt imagePrompt) {
+ OpenAiOfficialImageOptions options = OpenAiOfficialImageOptions.builder()
+ .from(this.defaultOptions)
+ .merge(imagePrompt.getOptions())
+ .build();
+
+ ImageGenerateParams imageGenerateParams = options.toOpenAiImageGenerateParams(imagePrompt);
+
+ if (logger.isTraceEnabled()) {
+ logger.trace("OpenAiOfficialImageOptions call {} with the following options : {} ", options.getModel(),
+ imageGenerateParams);
+ }
+
+ var observationContext = ImageModelObservationContext.builder()
+ .imagePrompt(imagePrompt)
+ .provider(AiProvider.OPENAI_OFFICIAL.value())
+ .build();
+
+ return Objects.requireNonNull(
+ ImageModelObservationDocumentation.IMAGE_MODEL_OPERATION
+ .observation(this.observationConvention, DEFAULT_OBSERVATION_CONVENTION, () -> observationContext,
+ this.observationRegistry)
+ .observe(() -> {
+ var images = this.openAiClient.images().generate(imageGenerateParams);
+
+ if (images.data().isEmpty() && images.data().get().isEmpty()) {
+ throw new IllegalArgumentException("Image generation failed: no image returned");
+ }
+
+ List imageGenerations = images.data().get().stream().map(nativeImage -> {
+ Image image;
+ if (nativeImage.url().isPresent()) {
+ image = new Image(nativeImage.url().get(), null);
+ }
+ else if (nativeImage.b64Json().isPresent()) {
+ image = new Image(null, nativeImage.b64Json().get());
+ }
+ else {
+ throw new IllegalArgumentException(
+ "Image generation failed: image entry missing url and b64_json");
+ }
+ var metadata = new OpenAiOfficialImageGenerationMetadata(nativeImage.revisedPrompt());
+ return new ImageGeneration(image, metadata);
+ }).toList();
+ ImageResponseMetadata openAiImageResponseMetadata = OpenAiOfficialImageResponseMetadata
+ .from(images);
+ ImageResponse imageResponse = new ImageResponse(imageGenerations, openAiImageResponseMetadata);
+ observationContext.setResponse(imageResponse);
+ return imageResponse;
+ }));
+ }
+
+ /**
+ * Use the provided convention for reporting observation data
+ * @param observationConvention The provided convention
+ */
+ public void setObservationConvention(ImageModelObservationConvention observationConvention) {
+ Assert.notNull(observationConvention, "observationConvention cannot be null");
+ this.observationConvention = observationConvention;
+ }
+
+}
diff --git a/models/spring-ai-openai-official/src/main/java/org/springframework/ai/openaiofficial/OpenAiOfficialImageOptions.java b/models/spring-ai-openai-official/src/main/java/org/springframework/ai/openaiofficial/OpenAiOfficialImageOptions.java
new file mode 100644
index 00000000000..da65ae2ca0e
--- /dev/null
+++ b/models/spring-ai-openai-official/src/main/java/org/springframework/ai/openaiofficial/OpenAiOfficialImageOptions.java
@@ -0,0 +1,366 @@
+/*
+ * Copyright 2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.ai.openaiofficial;
+
+import com.openai.models.images.ImageGenerateParams;
+import com.openai.models.images.ImageModel;
+import org.springframework.ai.image.ImageOptions;
+import org.springframework.ai.image.ImagePrompt;
+
+import java.util.Objects;
+
+/**
+ * Configuration information for the Image Model implementation using the OpenAI Java SDK.
+ *
+ * @author Julien Dubois
+ */
+public class OpenAiOfficialImageOptions implements ImageOptions {
+
+ public static final String DEFAULT_IMAGE_MODEL = ImageModel.DALL_E_3.toString();
+
+ /**
+ * The number of images to generate. Must be between 1 and 10. For dall-e-3, only n=1
+ * is supported.
+ */
+ private Integer n;
+
+ /**
+ * The model name used. When using Azure AI Foundry, this is also used as the default
+ * deployment name. By default, dall-e-3.
+ */
+ private String model = ImageModel.DALL_E_3.toString();
+
+ /**
+ * The deployment name as defined in Azure AI Foundry. On Azure AI Foundry, the
+ * default deployment name is the same as the model name. When using OpenAI directly,
+ * this value isn't used.
+ */
+ private String deploymentName;
+
+ /**
+ * The width of the generated images. Must be one of 256, 512, or 1024 for dall-e-2.
+ */
+ private Integer width;
+
+ /**
+ * The height of the generated images. Must be one of 256, 512, or 1024 for dall-e-2.
+ */
+ private Integer height;
+
+ /**
+ * The quality of the image that will be generated. hd creates images with finer
+ * details and greater consistency across the image. This param is only supported for
+ * dall-e-3. standard or hd
+ */
+ private String quality;
+
+ /**
+ * The format in which the generated images are returned. Must be one of url or
+ * b64_json.
+ */
+ private String responseFormat;
+
+ /**
+ * The size of the generated images. Must be one of 256x256, 512x512, or 1024x1024 for
+ * dall-e-2. Must be one of 1024x1024, 1792x1024, or 1024x1792 for dall-e-3 models.
+ */
+ private String size;
+
+ /**
+ * The style of the generated images. Must be one of vivid or natural. Vivid causes
+ * the model to lean towards generating hyper-real and dramatic images. Natural causes
+ * the model to produce more natural, less hyper-real looking images. This param is
+ * only supported for dall-e-3. natural or vivid
+ */
+ private String style;
+
+ /**
+ * A unique identifier representing your end-user, which can help OpenAI to monitor
+ * and detect abuse.
+ */
+ private String user;
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ @Override
+ public Integer getN() {
+ return this.n;
+ }
+
+ public void setN(Integer n) {
+ this.n = n;
+ }
+
+ @Override
+ public String getModel() {
+ return this.model;
+ }
+
+ public void setModel(String model) {
+ this.model = model;
+ }
+
+ @Override
+ public Integer getWidth() {
+ return this.width;
+ }
+
+ public void setWidth(Integer width) {
+ this.width = width;
+ this.size = this.width + "x" + this.height;
+ }
+
+ @Override
+ public Integer getHeight() {
+ return this.height;
+ }
+
+ public void setHeight(Integer height) {
+ this.height = height;
+ this.size = this.width + "x" + this.height;
+ }
+
+ @Override
+ public String getResponseFormat() {
+ return this.responseFormat;
+ }
+
+ public void setResponseFormat(String responseFormat) {
+ this.responseFormat = responseFormat;
+ }
+
+ public String getSize() {
+ if (this.size != null) {
+ return this.size;
+ }
+ return (this.width != null && this.height != null) ? this.width + "x" + this.height : null;
+ }
+
+ public void setSize(String size) {
+ this.size = size;
+ }
+
+ public String getUser() {
+ return this.user;
+ }
+
+ public void setUser(String user) {
+ this.user = user;
+ }
+
+ public String getQuality() {
+ return this.quality;
+ }
+
+ public void setQuality(String quality) {
+ this.quality = quality;
+ }
+
+ @Override
+ public String getStyle() {
+ return this.style;
+ }
+
+ public void setStyle(String style) {
+ this.style = style;
+ }
+
+ public String getDeploymentName() {
+ return this.deploymentName;
+ }
+
+ public void setDeploymentName(String deploymentName) {
+ this.deploymentName = deploymentName;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof OpenAiOfficialImageOptions that)) {
+ return false;
+ }
+ return Objects.equals(this.n, that.n) && Objects.equals(this.model, that.model)
+ && Objects.equals(this.deploymentName, that.deploymentName) && Objects.equals(this.width, that.width)
+ && Objects.equals(this.height, that.height) && Objects.equals(this.quality, that.quality)
+ && Objects.equals(this.responseFormat, that.responseFormat) && Objects.equals(this.size, that.size)
+ && Objects.equals(this.style, that.style) && Objects.equals(this.user, that.user);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(this.n, this.model, this.deploymentName, this.width, this.height, this.quality,
+ this.responseFormat, this.size, this.style, this.user);
+ }
+
+ @Override
+ public String toString() {
+ return "OpenAiOfficialImageOptions{" + "n=" + n + ", model='" + model + '\'' + ", deploymentName='"
+ + deploymentName + '\'' + ", width=" + width + ", height=" + height + ", quality='" + quality + '\''
+ + ", responseFormat='" + responseFormat + '\'' + ", size='" + size + '\'' + ", style='" + style + '\''
+ + ", user='" + user + '\'' + '}';
+ }
+
+ public ImageGenerateParams toOpenAiImageGenerateParams(ImagePrompt imagePrompt) {
+ if (imagePrompt.getInstructions().isEmpty()) {
+ throw new IllegalArgumentException("Image prompt instructions cannot be empty");
+ }
+
+ String prompt = imagePrompt.getInstructions().get(0).getText();
+ ImageGenerateParams.Builder builder = ImageGenerateParams.builder().prompt(prompt);
+
+ // Use deployment name if available (for Azure AI Foundry), otherwise use model
+ // name
+ if (this.getDeploymentName() != null) {
+ builder.model(this.getDeploymentName());
+ }
+ else if (this.getModel() != null) {
+ builder.model(this.getModel());
+ }
+
+ if (this.getN() != null) {
+ builder.n(this.getN().longValue());
+ }
+ if (this.getQuality() != null) {
+ builder.quality(ImageGenerateParams.Quality.of(this.getQuality().toLowerCase()));
+ }
+ if (this.getResponseFormat() != null) {
+ builder.responseFormat(ImageGenerateParams.ResponseFormat.of(this.getResponseFormat().toLowerCase()));
+ }
+ if (this.getSize() != null) {
+ builder.size(ImageGenerateParams.Size.of(this.getSize()));
+ }
+ if (this.getStyle() != null) {
+ builder.style(ImageGenerateParams.Style.of(this.getStyle().toLowerCase()));
+ }
+ if (this.getUser() != null) {
+ builder.user(this.getUser());
+ }
+
+ return builder.build();
+ }
+
+ public static final class Builder {
+
+ private final OpenAiOfficialImageOptions options;
+
+ private Builder() {
+ this.options = new OpenAiOfficialImageOptions();
+ }
+
+ public Builder from(OpenAiOfficialImageOptions fromOptions) {
+ this.options.setN(fromOptions.getN());
+ this.options.setModel(fromOptions.getModel());
+ this.options.setDeploymentName(fromOptions.getDeploymentName());
+ this.options.setWidth(fromOptions.getWidth());
+ this.options.setHeight(fromOptions.getHeight());
+ this.options.setQuality(fromOptions.getQuality());
+ this.options.setResponseFormat(fromOptions.getResponseFormat());
+ this.options.setSize(fromOptions.getSize());
+ this.options.setStyle(fromOptions.getStyle());
+ this.options.setUser(fromOptions.getUser());
+ return this;
+ }
+
+ public Builder merge(ImageOptions from) {
+ if (from instanceof OpenAiOfficialImageOptions castFrom) {
+ if (castFrom.getN() != null) {
+ this.options.setN(castFrom.getN());
+ }
+ if (castFrom.getModel() != null) {
+ this.options.setModel(castFrom.getModel());
+ }
+ if (castFrom.getDeploymentName() != null) {
+ this.options.setDeploymentName(castFrom.getDeploymentName());
+ }
+ if (castFrom.getWidth() != null) {
+ this.options.setWidth(castFrom.getWidth());
+ }
+ if (castFrom.getHeight() != null) {
+ this.options.setHeight(castFrom.getHeight());
+ }
+ if (castFrom.getQuality() != null) {
+ this.options.setQuality(castFrom.getQuality());
+ }
+ if (castFrom.getResponseFormat() != null) {
+ this.options.setResponseFormat(castFrom.getResponseFormat());
+ }
+ if (castFrom.getSize() != null) {
+ this.options.setSize(castFrom.getSize());
+ }
+ if (castFrom.getStyle() != null) {
+ this.options.setStyle(castFrom.getStyle());
+ }
+ if (castFrom.getUser() != null) {
+ this.options.setUser(castFrom.getUser());
+ }
+ }
+ return this;
+ }
+
+ public Builder N(Integer n) {
+ this.options.setN(n);
+ return this;
+ }
+
+ public Builder model(String model) {
+ this.options.setModel(model);
+ return this;
+ }
+
+ public Builder deploymentName(String deploymentName) {
+ this.options.setDeploymentName(deploymentName);
+ return this;
+ }
+
+ public Builder responseFormat(String responseFormat) {
+ this.options.setResponseFormat(responseFormat);
+ return this;
+ }
+
+ public Builder width(Integer width) {
+ this.options.setWidth(width);
+ return this;
+ }
+
+ public Builder height(Integer height) {
+ this.options.setHeight(height);
+ return this;
+ }
+
+ public Builder user(String user) {
+ this.options.setUser(user);
+ return this;
+ }
+
+ public Builder style(String style) {
+ this.options.setStyle(style);
+ return this;
+ }
+
+ public OpenAiOfficialImageOptions build() {
+ if (this.options.deploymentName == null) {
+ this.options.deploymentName = this.options.model;
+ }
+ return this.options;
+ }
+
+ }
+
+}
diff --git a/models/spring-ai-openai-official/src/main/java/org/springframework/ai/openaiofficial/metadata/OpenAiOfficialImageGenerationMetadata.java b/models/spring-ai-openai-official/src/main/java/org/springframework/ai/openaiofficial/metadata/OpenAiOfficialImageGenerationMetadata.java
new file mode 100644
index 00000000000..742a2d95f56
--- /dev/null
+++ b/models/spring-ai-openai-official/src/main/java/org/springframework/ai/openaiofficial/metadata/OpenAiOfficialImageGenerationMetadata.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2023-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.ai.openaiofficial.metadata;
+
+import org.springframework.ai.image.ImageGenerationMetadata;
+
+import java.util.Objects;
+import java.util.Optional;
+
+/**
+ * Represents the metadata for image generation using the OpenAI Java SDK.
+ *
+ * @author Julien Dubois
+ */
+public class OpenAiOfficialImageGenerationMetadata implements ImageGenerationMetadata {
+
+ private final String revisedPrompt;
+
+ public OpenAiOfficialImageGenerationMetadata(Optional revisedPrompt) {
+ if (revisedPrompt.isPresent()) {
+ this.revisedPrompt = revisedPrompt.get();
+ }
+ else {
+ this.revisedPrompt = null;
+ }
+ }
+
+ public String getRevisedPrompt() {
+ return this.revisedPrompt;
+ }
+
+ @Override
+ public String toString() {
+ return "OpenAiOfficialImageGenerationMetadata{" + "revisedPrompt='" + revisedPrompt + '\'' + '}';
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof OpenAiOfficialImageGenerationMetadata that)) {
+ return false;
+ }
+ return Objects.equals(this.revisedPrompt, that.revisedPrompt);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(this.revisedPrompt);
+ }
+
+}
diff --git a/models/spring-ai-openai-official/src/main/java/org/springframework/ai/openaiofficial/metadata/OpenAiOfficialImageResponseMetadata.java b/models/spring-ai-openai-official/src/main/java/org/springframework/ai/openaiofficial/metadata/OpenAiOfficialImageResponseMetadata.java
new file mode 100644
index 00000000000..2ecf121d095
--- /dev/null
+++ b/models/spring-ai-openai-official/src/main/java/org/springframework/ai/openaiofficial/metadata/OpenAiOfficialImageResponseMetadata.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2023-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.ai.openaiofficial.metadata;
+
+import com.openai.models.images.ImagesResponse;
+import org.springframework.ai.image.ImageResponseMetadata;
+import org.springframework.util.Assert;
+
+import java.util.Objects;
+
+/**
+ * Represents the metadata for image response using the OpenAI Java SDK.
+ *
+ * @author Julien Dubois
+ */
+public class OpenAiOfficialImageResponseMetadata extends ImageResponseMetadata {
+
+ private final Long created;
+
+ protected OpenAiOfficialImageResponseMetadata(Long created) {
+ this.created = created;
+ }
+
+ public static OpenAiOfficialImageResponseMetadata from(ImagesResponse imagesResponse) {
+ Assert.notNull(imagesResponse, "imagesResponse must not be null");
+ return new OpenAiOfficialImageResponseMetadata(imagesResponse.created());
+ }
+
+ @Override
+ public Long getCreated() {
+ return this.created;
+ }
+
+ @Override
+ public String toString() {
+ return "OpenAiOfficialImageResponseMetadata{" + "created=" + created + '}';
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof OpenAiOfficialImageResponseMetadata that)) {
+ return false;
+ }
+ return Objects.equals(this.created, that.created);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(this.created);
+ }
+
+}
diff --git a/models/spring-ai-openai-official/src/test/java/org/springframework/ai/openaiofficial/OpenAiOfficialTestConfiguration.java b/models/spring-ai-openai-official/src/test/java/org/springframework/ai/openaiofficial/OpenAiOfficialTestConfiguration.java
new file mode 100644
index 00000000000..fe62fbb8127
--- /dev/null
+++ b/models/spring-ai-openai-official/src/test/java/org/springframework/ai/openaiofficial/OpenAiOfficialTestConfiguration.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2023-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.ai.openaiofficial;
+
+import com.openai.client.OpenAIClient;
+import com.openai.client.okhttp.OpenAIOkHttpClient;
+import io.micrometer.observation.tck.TestObservationRegistry;
+import org.springframework.ai.document.MetadataMode;
+import org.springframework.boot.SpringBootConfiguration;
+import org.springframework.context.annotation.Bean;
+
+/**
+ * Context configuration for OpenAI official SDK tests.
+ *
+ * @author Julien Dubois
+ */
+@SpringBootConfiguration
+public class OpenAiOfficialTestConfiguration {
+
+ @Bean
+ public OpenAIClient openAIClient() {
+ return OpenAIOkHttpClient.fromEnv();
+ }
+
+ @Bean
+ public OpenAiOfficialEmbeddingModel openAiEmbeddingModel(OpenAIClient client) {
+ return new OpenAiOfficialEmbeddingModel(client);
+ }
+
+ @Bean
+ public OpenAiOfficialImageModel openAiImageModel(OpenAIClient client) {
+ return new OpenAiOfficialImageModel(client);
+ }
+
+}
diff --git a/models/spring-ai-openai-official/src/test/java/org/springframework/ai/openaiofficial/OpenAiOfficialTestConfigurationWithObservability.java b/models/spring-ai-openai-official/src/test/java/org/springframework/ai/openaiofficial/OpenAiOfficialTestConfigurationWithObservability.java
new file mode 100644
index 00000000000..9670d30e740
--- /dev/null
+++ b/models/spring-ai-openai-official/src/test/java/org/springframework/ai/openaiofficial/OpenAiOfficialTestConfigurationWithObservability.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2023-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.ai.openaiofficial;
+
+import com.openai.client.OpenAIClient;
+import com.openai.client.okhttp.OpenAIOkHttpClient;
+import io.micrometer.observation.tck.TestObservationRegistry;
+import org.springframework.ai.document.MetadataMode;
+import org.springframework.boot.SpringBootConfiguration;
+import org.springframework.context.annotation.Bean;
+
+import static org.springframework.ai.openaiofficial.OpenAiOfficialEmbeddingOptions.DEFAULT_EMBEDDING_MODEL;
+import static org.springframework.ai.openaiofficial.OpenAiOfficialImageOptions.DEFAULT_IMAGE_MODEL;
+
+/**
+ * Context configuration for OpenAI official SDK tests.
+ *
+ * @author Julien Dubois
+ */
+@SpringBootConfiguration
+public class OpenAiOfficialTestConfigurationWithObservability {
+
+ @Bean
+ public TestObservationRegistry testObservationRegistry() {
+ return TestObservationRegistry.create();
+ }
+
+ @Bean
+ public OpenAIClient openAIClient() {
+ return OpenAIOkHttpClient.fromEnv();
+ }
+
+ @Bean
+ public OpenAiOfficialEmbeddingModel openAiEmbeddingModel(OpenAIClient client,
+ TestObservationRegistry observationRegistry) {
+ return new OpenAiOfficialEmbeddingModel(client, MetadataMode.EMBED,
+ OpenAiOfficialEmbeddingOptions.builder().model(DEFAULT_EMBEDDING_MODEL).build(), observationRegistry);
+ }
+
+ @Bean
+ public OpenAiOfficialImageModel openAiImageModel(OpenAIClient client, TestObservationRegistry observationRegistry) {
+ return new OpenAiOfficialImageModel(client,
+ OpenAiOfficialImageOptions.builder().model(DEFAULT_IMAGE_MODEL).build(), observationRegistry);
+ }
+
+}
diff --git a/models/spring-ai-openai-official/src/test/java/org/springframework/ai/openaiofficial/embedding/OpenAiOfficialEmbeddingIT.java b/models/spring-ai-openai-official/src/test/java/org/springframework/ai/openaiofficial/embedding/OpenAiOfficialEmbeddingIT.java
new file mode 100644
index 00000000000..ee058bd293d
--- /dev/null
+++ b/models/spring-ai-openai-official/src/test/java/org/springframework/ai/openaiofficial/embedding/OpenAiOfficialEmbeddingIT.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright 2023-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.ai.openaiofficial.embedding;
+
+import com.openai.models.embeddings.EmbeddingModel;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
+import org.springframework.ai.document.Document;
+import org.springframework.ai.embedding.EmbeddingRequest;
+import org.springframework.ai.embedding.EmbeddingResponse;
+import org.springframework.ai.embedding.TokenCountBatchingStrategy;
+import org.springframework.ai.openaiofficial.OpenAiOfficialEmbeddingModel;
+import org.springframework.ai.openaiofficial.OpenAiOfficialEmbeddingOptions;
+import org.springframework.ai.openaiofficial.OpenAiOfficialTestConfiguration;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.core.io.DefaultResourceLoader;
+import org.springframework.core.io.Resource;
+
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+/**
+ * Integration tests for {@link OpenAiOfficialEmbeddingModel}.
+ *
+ * @author Julien Dubois
+ */
+@SpringBootTest(classes = OpenAiOfficialTestConfiguration.class)
+@EnabledIfEnvironmentVariable(named = "OPENAI_API_KEY", matches = ".+")
+class OpenAiOfficialEmbeddingIT {
+
+ private final Resource resource = new DefaultResourceLoader().getResource("classpath:text_source.txt");
+
+ @Autowired
+ private OpenAiOfficialEmbeddingModel openAiOfficialEmbeddingModel;
+
+ @Test
+ void defaultEmbedding() {
+ assertThat(this.openAiOfficialEmbeddingModel).isNotNull();
+
+ EmbeddingResponse embeddingResponse = this.openAiOfficialEmbeddingModel
+ .embedForResponse(List.of("Hello World"));
+ assertThat(embeddingResponse.getResults()).hasSize(1);
+ assertThat(embeddingResponse.getResults().get(0)).isNotNull();
+ assertThat(embeddingResponse.getResults().get(0).getOutput()).hasSize(1536);
+ assertThat(embeddingResponse.getMetadata().getUsage().getTotalTokens()).isEqualTo(2);
+ assertThat(embeddingResponse.getMetadata().getUsage().getPromptTokens()).isEqualTo(2);
+
+ assertThat(this.openAiOfficialEmbeddingModel.dimensions()).isEqualTo(1536);
+ assertThat(embeddingResponse.getMetadata().getModel())
+ .isEqualTo(EmbeddingModel.TEXT_EMBEDDING_ADA_002.toString());
+ }
+
+ @Test
+ void embeddingBatchDocuments() throws Exception {
+ assertThat(this.openAiOfficialEmbeddingModel).isNotNull();
+ List embeddings = this.openAiOfficialEmbeddingModel.embed(
+ List.of(new Document("Hello world"), new Document("Hello Spring"), new Document("Hello Spring AI!")),
+ OpenAiOfficialEmbeddingOptions.builder()
+ .model(EmbeddingModel.TEXT_EMBEDDING_ADA_002.toString())
+ .build(),
+ new TokenCountBatchingStrategy());
+ assertThat(embeddings.size()).isEqualTo(3);
+ embeddings.forEach(
+ embedding -> assertThat(embedding.length).isEqualTo(this.openAiOfficialEmbeddingModel.dimensions()));
+ }
+
+ @Test
+ void embeddingBatchDocumentsThatExceedTheLimit() throws Exception {
+ assertThat(this.openAiOfficialEmbeddingModel).isNotNull();
+ String contentAsString = this.resource.getContentAsString(StandardCharsets.UTF_8);
+ assertThatThrownBy(() -> this.openAiOfficialEmbeddingModel.embed(
+ List.of(new Document("Hello World"), new Document(contentAsString)),
+ OpenAiOfficialEmbeddingOptions.builder()
+ .model(EmbeddingModel.TEXT_EMBEDDING_ADA_002.toString())
+ .build(),
+ new TokenCountBatchingStrategy()))
+ .isInstanceOf(IllegalArgumentException.class);
+ }
+
+ @Test
+ void embedding3Large() {
+
+ EmbeddingResponse embeddingResponse = this.openAiOfficialEmbeddingModel
+ .call(new EmbeddingRequest(List.of("Hello World"),
+ OpenAiOfficialEmbeddingOptions.builder()
+ .model(EmbeddingModel.TEXT_EMBEDDING_3_LARGE.toString())
+ .build()));
+ assertThat(embeddingResponse.getResults()).hasSize(1);
+ assertThat(embeddingResponse.getResults().get(0)).isNotNull();
+ assertThat(embeddingResponse.getResults().get(0).getOutput()).hasSize(3072);
+ assertThat(embeddingResponse.getMetadata().getUsage().getTotalTokens()).isEqualTo(2);
+ assertThat(embeddingResponse.getMetadata().getUsage().getPromptTokens()).isEqualTo(2);
+ assertThat(embeddingResponse.getMetadata().getModel())
+ .isEqualTo(EmbeddingModel.TEXT_EMBEDDING_3_LARGE.toString());
+ }
+
+ @Test
+ void textEmbeddingAda002() {
+
+ EmbeddingResponse embeddingResponse = this.openAiOfficialEmbeddingModel
+ .call(new EmbeddingRequest(List.of("Hello World"),
+ OpenAiOfficialEmbeddingOptions.builder()
+ .model(EmbeddingModel.TEXT_EMBEDDING_3_SMALL.toString())
+ .build()));
+ assertThat(embeddingResponse.getResults()).hasSize(1);
+ assertThat(embeddingResponse.getResults().get(0)).isNotNull();
+ assertThat(embeddingResponse.getResults().get(0).getOutput()).hasSize(1536);
+
+ assertThat(embeddingResponse.getMetadata().getUsage().getTotalTokens()).isEqualTo(2);
+ assertThat(embeddingResponse.getMetadata().getUsage().getPromptTokens()).isEqualTo(2);
+ assertThat(embeddingResponse.getMetadata().getModel())
+ .isEqualTo(EmbeddingModel.TEXT_EMBEDDING_3_SMALL.toString());
+ }
+
+}
diff --git a/models/spring-ai-openai-official/src/test/java/org/springframework/ai/openaiofficial/embedding/OpenAiOfficialEmbeddingModelObservationIT.java b/models/spring-ai-openai-official/src/test/java/org/springframework/ai/openaiofficial/embedding/OpenAiOfficialEmbeddingModelObservationIT.java
new file mode 100644
index 00000000000..fe946a5a651
--- /dev/null
+++ b/models/spring-ai-openai-official/src/test/java/org/springframework/ai/openaiofficial/embedding/OpenAiOfficialEmbeddingModelObservationIT.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2023-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.ai.openaiofficial.embedding;
+
+import io.micrometer.observation.tck.TestObservationRegistry;
+import io.micrometer.observation.tck.TestObservationRegistryAssert;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
+import org.springframework.ai.embedding.EmbeddingRequest;
+import org.springframework.ai.embedding.EmbeddingResponse;
+import org.springframework.ai.embedding.EmbeddingResponseMetadata;
+import org.springframework.ai.embedding.observation.DefaultEmbeddingModelObservationConvention;
+import org.springframework.ai.observation.conventions.AiOperationType;
+import org.springframework.ai.observation.conventions.AiProvider;
+import org.springframework.ai.openaiofficial.OpenAiOfficialEmbeddingModel;
+import org.springframework.ai.openaiofficial.OpenAiOfficialEmbeddingOptions;
+import org.springframework.ai.openaiofficial.OpenAiOfficialTestConfiguration;
+import org.springframework.ai.openaiofficial.OpenAiOfficialTestConfigurationWithObservability;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+
+import java.util.List;
+
+import static com.openai.models.embeddings.EmbeddingModel.TEXT_EMBEDDING_3_SMALL;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.springframework.ai.embedding.observation.EmbeddingModelObservationDocumentation.HighCardinalityKeyNames;
+import static org.springframework.ai.embedding.observation.EmbeddingModelObservationDocumentation.LowCardinalityKeyNames;
+
+/**
+ * Integration tests for observation instrumentation in
+ * {@link OpenAiOfficialEmbeddingModel}.
+ *
+ * @author Julien Dubois
+ */
+@SpringBootTest(classes = OpenAiOfficialTestConfigurationWithObservability.class)
+@EnabledIfEnvironmentVariable(named = "OPENAI_API_KEY", matches = ".+")
+public class OpenAiOfficialEmbeddingModelObservationIT {
+
+ @Autowired
+ TestObservationRegistry observationRegistry;
+
+ @Autowired
+ OpenAiOfficialEmbeddingModel embeddingModel;
+
+ @BeforeEach
+ void setUp() {
+ this.observationRegistry.clear();
+ }
+
+ @Test
+ void observationForEmbeddingOperation() {
+ var options = OpenAiOfficialEmbeddingOptions.builder()
+ .model(TEXT_EMBEDDING_3_SMALL.toString())
+ .dimensions(1536)
+ .build();
+
+ EmbeddingRequest embeddingRequest = new EmbeddingRequest(List.of("Here comes the sun"), options);
+
+ EmbeddingResponse embeddingResponse = this.embeddingModel.call(embeddingRequest);
+ assertThat(embeddingResponse.getResults()).isNotEmpty();
+
+ EmbeddingResponseMetadata responseMetadata = embeddingResponse.getMetadata();
+ assertThat(responseMetadata).isNotNull();
+
+ TestObservationRegistryAssert.assertThat(this.observationRegistry)
+ .doesNotHaveAnyRemainingCurrentObservation()
+ .hasObservationWithNameEqualTo(DefaultEmbeddingModelObservationConvention.DEFAULT_NAME)
+ .that()
+ .hasContextualNameEqualTo("embedding " + TEXT_EMBEDDING_3_SMALL)
+ .hasLowCardinalityKeyValue(LowCardinalityKeyNames.AI_OPERATION_TYPE.asString(),
+ AiOperationType.EMBEDDING.value())
+ .hasLowCardinalityKeyValue(LowCardinalityKeyNames.AI_PROVIDER.asString(),
+ AiProvider.OPENAI_OFFICIAL.value())
+ .hasLowCardinalityKeyValue(LowCardinalityKeyNames.REQUEST_MODEL.asString(),
+ TEXT_EMBEDDING_3_SMALL.toString())
+ .hasLowCardinalityKeyValue(LowCardinalityKeyNames.RESPONSE_MODEL.asString(), responseMetadata.getModel())
+ .hasHighCardinalityKeyValue(HighCardinalityKeyNames.REQUEST_EMBEDDING_DIMENSIONS.asString(), "1536")
+ .hasHighCardinalityKeyValue(HighCardinalityKeyNames.USAGE_INPUT_TOKENS.asString(),
+ String.valueOf(responseMetadata.getUsage().getPromptTokens()))
+ .hasHighCardinalityKeyValue(HighCardinalityKeyNames.USAGE_TOTAL_TOKENS.asString(),
+ String.valueOf(responseMetadata.getUsage().getTotalTokens()))
+ .hasBeenStarted()
+ .hasBeenStopped();
+ }
+
+}
diff --git a/models/spring-ai-openai-official/src/test/java/org/springframework/ai/openaiofficial/image/OpenAiOfficialImageModelIT.java b/models/spring-ai-openai-official/src/test/java/org/springframework/ai/openaiofficial/image/OpenAiOfficialImageModelIT.java
new file mode 100644
index 00000000000..4791e05b447
--- /dev/null
+++ b/models/spring-ai-openai-official/src/test/java/org/springframework/ai/openaiofficial/image/OpenAiOfficialImageModelIT.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2023-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.ai.openaiofficial.image;
+
+import com.openai.models.embeddings.EmbeddingModel;
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.ai.image.Image;
+import org.springframework.ai.image.ImageOptionsBuilder;
+import org.springframework.ai.image.ImagePrompt;
+import org.springframework.ai.image.ImageResponse;
+import org.springframework.ai.image.ImageResponseMetadata;
+import org.springframework.ai.openaiofficial.OpenAiOfficialImageModel;
+import org.springframework.ai.openaiofficial.OpenAiOfficialTestConfiguration;
+import org.springframework.ai.openaiofficial.metadata.OpenAiOfficialImageGenerationMetadata;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Integration tests for observation instrumentation in {@link OpenAiOfficialImageModel}.
+ *
+ * @author Julien Dubois
+ */
+@SpringBootTest(classes = OpenAiOfficialTestConfiguration.class)
+@EnabledIfEnvironmentVariable(named = "OPENAI_API_KEY", matches = ".+")
+public class OpenAiOfficialImageModelIT {
+
+ private final Logger logger = LoggerFactory.getLogger(OpenAiOfficialImageModelIT.class);
+
+ @Autowired
+ private OpenAiOfficialImageModel imageModel;
+
+ @Test
+ void imageAsUrlTest() {
+ var options = ImageOptionsBuilder.builder().height(1024).width(1024).build();
+
+ var instructions = """
+ A cup of coffee at a restaurant table in Paris, France.
+ """;
+
+ ImagePrompt imagePrompt = new ImagePrompt(instructions, options);
+
+ ImageResponse imageResponse = this.imageModel.call(imagePrompt);
+
+ assertThat(imageResponse.getResults()).hasSize(1);
+
+ ImageResponseMetadata imageResponseMetadata = imageResponse.getMetadata();
+ assertThat(imageResponseMetadata.getCreated()).isPositive();
+
+ var generation = imageResponse.getResult();
+ Image image = generation.getOutput();
+ assertThat(image.getUrl()).isNotEmpty();
+ logger.info("Generated image URL: {}", image.getUrl());
+ assertThat(image.getB64Json()).isNull();
+
+ var imageGenerationMetadata = generation.getMetadata();
+ Assertions.assertThat(imageGenerationMetadata).isInstanceOf(OpenAiOfficialImageGenerationMetadata.class);
+
+ OpenAiOfficialImageGenerationMetadata openAiOfficialImageGenerationMetadata = (OpenAiOfficialImageGenerationMetadata) imageGenerationMetadata;
+
+ assertThat(openAiOfficialImageGenerationMetadata).isNotNull();
+ assertThat(openAiOfficialImageGenerationMetadata.getRevisedPrompt()).isNotBlank();
+
+ }
+
+}
diff --git a/models/spring-ai-openai-official/src/test/java/org/springframework/ai/openaiofficial/image/OpenAiOfficialImageModelObservationIT.java b/models/spring-ai-openai-official/src/test/java/org/springframework/ai/openaiofficial/image/OpenAiOfficialImageModelObservationIT.java
new file mode 100644
index 00000000000..539a647f1e5
--- /dev/null
+++ b/models/spring-ai-openai-official/src/test/java/org/springframework/ai/openaiofficial/image/OpenAiOfficialImageModelObservationIT.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2023-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.ai.openaiofficial.image;
+
+import io.micrometer.observation.tck.TestObservationRegistry;
+import io.micrometer.observation.tck.TestObservationRegistryAssert;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
+import org.springframework.ai.image.ImagePrompt;
+import org.springframework.ai.image.ImageResponse;
+import org.springframework.ai.image.observation.DefaultImageModelObservationConvention;
+import org.springframework.ai.observation.conventions.AiOperationType;
+import org.springframework.ai.observation.conventions.AiProvider;
+import org.springframework.ai.openaiofficial.OpenAiOfficialImageModel;
+import org.springframework.ai.openaiofficial.OpenAiOfficialImageOptions;
+import org.springframework.ai.openaiofficial.OpenAiOfficialTestConfiguration;
+import org.springframework.ai.openaiofficial.OpenAiOfficialTestConfigurationWithObservability;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+
+import static com.openai.models.images.ImageModel.DALL_E_3;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.springframework.ai.image.observation.ImageModelObservationDocumentation.HighCardinalityKeyNames;
+import static org.springframework.ai.image.observation.ImageModelObservationDocumentation.LowCardinalityKeyNames;
+
+/**
+ * Integration tests for {@link OpenAiOfficialImageModel}.
+ *
+ * @author Julien Dubois
+ */
+@SpringBootTest(classes = OpenAiOfficialTestConfigurationWithObservability.class)
+@EnabledIfEnvironmentVariable(named = "OPENAI_API_KEY", matches = ".+")
+public class OpenAiOfficialImageModelObservationIT {
+
+ @Autowired
+ TestObservationRegistry observationRegistry;
+
+ @Autowired
+ private OpenAiOfficialImageModel imageModel;
+
+ @BeforeEach
+ void setUp() {
+ this.observationRegistry.clear();
+ }
+
+ @Test
+ void observationForImageOperation() {
+ var options = OpenAiOfficialImageOptions.builder()
+ .model(DALL_E_3.asString())
+ .height(1024)
+ .width(1024)
+ .responseFormat("url")
+ .style("natural")
+ .build();
+
+ var instructions = """
+ A cup of coffee at a restaurant table in Paris, France.
+ """;
+
+ ImagePrompt imagePrompt = new ImagePrompt(instructions, options);
+
+ ImageResponse imageResponse = this.imageModel.call(imagePrompt);
+ assertThat(imageResponse.getResults()).hasSize(1);
+
+ TestObservationRegistryAssert.assertThat(this.observationRegistry)
+ .doesNotHaveAnyRemainingCurrentObservation()
+ .hasObservationWithNameEqualTo(DefaultImageModelObservationConvention.DEFAULT_NAME)
+ .that()
+ .hasContextualNameEqualTo("image " + DALL_E_3.asString())
+ .hasLowCardinalityKeyValue(LowCardinalityKeyNames.AI_OPERATION_TYPE.asString(),
+ AiOperationType.IMAGE.value())
+ .hasLowCardinalityKeyValue(LowCardinalityKeyNames.AI_PROVIDER.asString(),
+ AiProvider.OPENAI_OFFICIAL.value())
+ .hasLowCardinalityKeyValue(LowCardinalityKeyNames.REQUEST_MODEL.asString(), DALL_E_3.asString())
+ .hasHighCardinalityKeyValue(HighCardinalityKeyNames.REQUEST_IMAGE_SIZE.asString(), "1024x1024")
+ .hasHighCardinalityKeyValue(HighCardinalityKeyNames.REQUEST_IMAGE_RESPONSE_FORMAT.asString(), "url")
+ .hasBeenStarted()
+ .hasBeenStopped();
+ }
+
+}
diff --git a/models/spring-ai-openai-official/src/test/resources/text_source.txt b/models/spring-ai-openai-official/src/test/resources/text_source.txt
new file mode 100644
index 00000000000..5f777418da0
--- /dev/null
+++ b/models/spring-ai-openai-official/src/test/resources/text_source.txt
@@ -0,0 +1,4124 @@
+
+ Spring Framework Documentation
+
+
+ Version 6.0.0
+
+ Chapter 1. Spring Framework Overview
+
+
+ Spring makes it easy to create Java enterprise applications. It provides everything you need to
+ embrace the Java language in an enterprise environment, with support for Groovy and Kotlin as
+ alternative languages on the JVM, and with the flexibility to create many kinds of architectures
+ depending on an application’s needs. As of Spring Framework 5.1, Spring requires JDK 8+ (Java SE
+ 8+) and provides out-of-the-box support for JDK 11 LTS. Java SE 8 update 60 is suggested as the
+ minimum patch release for Java 8, but it is generally recommended to use a recent patch release.
+
+ Spring supports a wide range of application scenarios. In a large enterprise, applications often exist
+ for a long time and have to run on a JDK and application server whose upgrade cycle is beyond
+ developer control. Others may run as a single jar with the server embedded, possibly in a cloud
+ environment. Yet others may be standalone applications (such as batch or integration workloads)
+ that do not need a server.
+
+
+ Spring is open source. It has a large and active community that provides continuous feedback based
+ on a diverse range of real-world use cases. This has helped Spring to successfully evolve over a very
+ long time.
+
+ 1.1. What We Mean by "Spring"
+
+
+ The term "Spring" means different things in different contexts. It can be used to refer to the Spring
+ Framework project itself, which is where it all started. Over time, other Spring projects have been
+ built on top of the Spring Framework. Most often, when people say "Spring", they mean the entire
+ family of projects. This reference documentation focuses on the foundation: the Spring Framework
+ itself.
+
+
+ The Spring Framework is divided into modules. Applications can choose which modules they need.
+ At the heart are the modules of the core container, including a configuration model and a
+ dependency injection mechanism. Beyond that, the Spring Framework provides foundational
+ support for different application architectures, including messaging, transactional data and
+ persistence, and web. It also includes the Servlet-based Spring MVC web framework and, in
+ parallel, the Spring WebFlux reactive web framework.
+
+
+ A note about modules: Spring’s framework jars allow for deployment to JDK 9’s module path
+ ("Jigsaw"). For use in Jigsaw-enabled applications, the Spring Framework 5 jars come with
+ "Automatic-Module-Name" manifest entries which define stable language-level module names
+ ("spring.core", "spring.context", etc.) independent from jar artifact names (the jars follow the same
+ naming pattern with "-" instead of ".", e.g. "spring-core" and "spring-context"). Of course, Spring’s
+ framework jars keep working fine on the classpath on both JDK 8 and 9+.
+
+ 1.2. History of Spring and the Spring Framework
+
+
+ Spring came into being in 2003 as a response to the complexity of the early J2EE specifications.
+ While some consider Java EE and its modern-day successor Jakarta EE to be in competition with
+ Spring, they are in fact complementary. The Spring programming model does not embrace the
+ Jakarta EE platform specification; rather, it integrates with carefully selected individual
+
+ specifications from the traditional EE umbrella:
+
+
+ • Servlet API (JSR 340)
+
+ • WebSocket API (JSR 356)
+
+ • Concurrency Utilities (JSR 236)
+
+ • JSON Binding API (JSR 367)
+
+ • Bean Validation (JSR 303)
+
+ • JPA (JSR 338)
+
+ • JMS (JSR 914)
+
+ • as well as JTA/JCA setups for transaction coordination, if necessary.
+
+
+ The Spring Framework also supports the Dependency Injection (JSR 330) and Common Annotations
+ (JSR 250) specifications, which application developers may choose to use instead of the Spring-
+ specific mechanisms provided by the Spring Framework. Originally, those were based on common
+ javax packages.
+
+ As of Spring Framework 6.0, Spring has been upgraded to the Jakarta EE 9 level (e.g. Servlet 5.0+,
+ JPA 3.0+), based on the jakarta namespace instead of the traditional javax packages. With EE 9 as
+ the minimum and EE 10 supported already, Spring is prepared to provide out-of-the-box support
+ for the further evolution of the Jakarta EE APIs. Spring Framework 6.0 is fully compatible with
+ Tomcat 10.1, Jetty 11 and Undertow 2.3 as web servers, and also with Hibernate ORM 6.1.
+
+
+ Over time, the role of Java/Jakarta EE in application development has evolved. In the early days of
+ J2EE and Spring, applications were created to be deployed to an application server. Today, with the
+ help of Spring Boot, applications are created in a devops- and cloud-friendly way, with the Servlet
+ container embedded and trivial to change. As of Spring Framework 5, a WebFlux application does
+ not even use the Servlet API directly and can run on servers (such as Netty) that are not Servlet
+ containers.
+
+
+ Spring continues to innovate and to evolve. Beyond the Spring Framework, there are other projects,
+ such as Spring Boot, Spring Security, Spring Data, Spring Cloud, Spring Batch, among others. It’s
+ important to remember that each project has its own source code repository, issue tracker, and
+ release cadence. See spring.io/projects for the complete list of Spring projects.
+
+ 1.3. Design Philosophy
+
+
+ When you learn about a framework, it’s important to know not only what it does but what
+ principles it follows. Here are the guiding principles of the Spring Framework:
+
+
+ • Provide choice at every level. Spring lets you defer design decisions as late as possible. For
+ example, you can switch persistence providers through configuration without changing your
+ code. The same is true for many other infrastructure concerns and integration with third-party
+ APIs.
+
+ • Accommodate diverse perspectives. Spring embraces flexibility and is not opinionated about
+ how things should be done. It supports a wide range of application needs with different
+ perspectives.
+
+ • Maintain strong backward compatibility. Spring’s evolution has been carefully managed to
+ force few breaking changes between versions. Spring supports a carefully chosen range of JDK
+ versions and third-party libraries to facilitate maintenance of applications and libraries that
+ depend on Spring.
+
+ • Care about API design. The Spring team puts a lot of thought and time into making APIs that are
+ intuitive and that hold up across many versions and many years.
+
+ • Set high standards for code quality. The Spring Framework puts a strong emphasis on
+ meaningful, current, and accurate javadoc. It is one of very few projects that can claim clean
+ code structure with no circular dependencies between packages.
+
+ 1.4. Feedback and Contributions
+
+
+ For how-to questions or diagnosing or debugging issues, we suggest using Stack Overflow. Click
+ here for a list of the suggested tags to use on Stack Overflow. If you’re fairly certain that there is a
+ problem in the Spring Framework or would like to suggest a feature, please use the GitHub Issues.
+
+ If you have a solution in mind or a suggested fix, you can submit a pull request on Github.
+ However, please keep in mind that, for all but the most trivial issues, we expect a ticket to be filed
+ in the issue tracker, where discussions take place and leave a record for future reference.
+
+
+ For more details see the guidelines at the CONTRIBUTING, top-level project page.
+
+ 1.5. Getting Started
+
+
+ If you are just getting started with Spring, you may want to begin using the Spring Framework by
+ creating a Spring Boot-based application. Spring Boot provides a quick (and opinionated) way to
+ create a production-ready Spring-based application. It is based on the Spring Framework, favors
+ convention over configuration, and is designed to get you up and running as quickly as possible.
+
+
+ You can use start.spring.io to generate a basic project or follow one of the "Getting Started" guides,
+ such as Getting Started Building a RESTful Web Service. As well as being easier to digest, these
+ guides are very task focused, and most of them are based on Spring Boot. They also cover other
+ projects from the Spring portfolio that you might want to consider when solving a particular
+ problem.
+
+ Chapter 2. Core Technologies
+
+
+ This part of the reference documentation covers all the technologies that are absolutely integral to
+ the Spring Framework.
+
+
+ Foremost amongst these is the Spring Framework’s Inversion of Control (IoC) container. A thorough
+ treatment of the Spring Framework’s IoC container is closely followed by comprehensive coverage
+ of Spring’s Aspect-Oriented Programming (AOP) technologies. The Spring Framework has its own
+ AOP framework, which is conceptually easy to understand and which successfully addresses the
+ 80% sweet spot of AOP requirements in Java enterprise programming.
+
+
+ Coverage of Spring’s integration with AspectJ (currently the richest — in terms of features — and
+ certainly most mature AOP implementation in the Java enterprise space) is also provided.
+
+
+ AOT processing can be used to optimize your application ahead-of-time. It is typically used for
+ native image deployment using GraalVM.
+
+ 2.1. The IoC Container
+
+
+ This chapter covers Spring’s Inversion of Control (IoC) container.
+
+
+ 2.1.1. Introduction to the Spring IoC Container and Beans
+
+ This chapter covers the Spring Framework implementation of the Inversion of Control (IoC)
+ principle. IoC is also known as dependency injection (DI). It is a process whereby objects define
+ their dependencies (that is, the other objects they work with) only through constructor arguments,
+ arguments to a factory method, or properties that are set on the object instance after it is
+ constructed or returned from a factory method. The container then injects those dependencies
+ when it creates the bean. This process is fundamentally the inverse (hence the name, Inversion of
+ Control) of the bean itself controlling the instantiation or location of its dependencies by using
+ direct construction of classes or a mechanism such as the Service Locator pattern.
+
+
+ The org.springframework.beans and org.springframework.context packages are the basis for Spring
+ Framework’s IoC container. The BeanFactory interface provides an advanced configuration
+ mechanism capable of managing any type of object. ApplicationContext is a sub-interface of
+ BeanFactory. It adds:
+
+
+ • Easier integration with Spring’s AOP features
+
+ • Message resource handling (for use in internationalization)
+
+ • Event publication
+
+ • Application-layer specific contexts such as the WebApplicationContext for use in web
+ applications.
+
+
+ In short, the BeanFactory provides the configuration framework and basic functionality, and the
+ ApplicationContext adds more enterprise-specific functionality. The ApplicationContext is a
+ complete superset of the BeanFactory and is used exclusively in this chapter in descriptions of
+ Spring’s IoC container. For more information on using the BeanFactory instead of the
+
+ ApplicationContext, see the section covering the BeanFactory API.
+
+
+ In Spring, the objects that form the backbone of your application and that are managed by the
+ Spring IoC container are called beans. A bean is an object that is instantiated, assembled, and
+ managed by a Spring IoC container. Otherwise, a bean is simply one of many objects in your
+ application. Beans, and the dependencies among them, are reflected in the configuration metadata
+ used by a container.
+
+
+ 2.1.2. Container Overview
+
+ The org.springframework.context.ApplicationContext interface represents the Spring IoC container
+ and is responsible for instantiating, configuring, and assembling the beans. The container gets its
+ instructions on what objects to instantiate, configure, and assemble by reading configuration
+ metadata. The configuration metadata is represented in XML, Java annotations, or Java code. It lets
+ you express the objects that compose your application and the rich interdependencies between
+ those objects.
+
+
+ Several implementations of the ApplicationContext interface are supplied with Spring. In stand-
+ alone applications, it is common to create an instance of ClassPathXmlApplicationContext or
+ FileSystemXmlApplicationContext. While XML has been the traditional format for defining
+ configuration metadata, you can instruct the container to use Java annotations or code as the
+ metadata format by providing a small amount of XML configuration to declaratively enable support
+ for these additional metadata formats.
+
+
+ In most application scenarios, explicit user code is not required to instantiate one or more
+ instances of a Spring IoC container. For example, in a web application scenario, a simple eight (or
+ so) lines of boilerplate web descriptor XML in the web.xml file of the application typically suffices
+ (see Convenient ApplicationContext Instantiation for Web Applications). If you use the Spring Tools
+ for Eclipse (an Eclipse-powered development environment), you can easily create this boilerplate
+ configuration with a few mouse clicks or keystrokes.
+
+
+ The following diagram shows a high-level view of how Spring works. Your application classes are
+ combined with configuration metadata so that, after the ApplicationContext is created and
+ initialized, you have a fully configured and executable system or application.
+
+ Figure 1. The Spring IoC container
+
+
+ Configuration Metadata
+
+ As the preceding diagram shows, the Spring IoC container consumes a form of configuration
+ metadata. This configuration metadata represents how you, as an application developer, tell the
+ Spring container to instantiate, configure, and assemble the objects in your application.
+
+
+ Configuration metadata is traditionally supplied in a simple and intuitive XML format, which is
+ what most of this chapter uses to convey key concepts and features of the Spring IoC container.
+
+
+ XML-based metadata is not the only allowed form of configuration metadata. The
+ Spring IoC container itself is totally decoupled from the format in which this
+ configuration metadata is actually written. These days, many developers choose
+ Java-based configuration for their Spring applications.
+
+
+ For information about using other forms of metadata with the Spring container, see:
+
+
+ • Annotation-based configuration: Spring 2.5 introduced support for annotation-based
+ configuration metadata.
+
+ • Java-based configuration: Starting with Spring 3.0, many features provided by the Spring
+ JavaConfig project became part of the core Spring Framework. Thus, you can define beans
+ external to your application classes by using Java rather than XML files. To use these new
+ features, see the @Configuration, @Bean, @Import, and @DependsOn annotations.
+
+ Spring configuration consists of at least one and typically more than one bean definition that the
+ container must manage. XML-based configuration metadata configures these beans as
+ elements inside a top-level element. Java configuration typically uses @Bean-annotated
+ methods within a @Configuration class.
+
+ These bean definitions correspond to the actual objects that make up your application. Typically,
+ you define service layer objects, data access objects (DAOs), presentation objects such as Struts
+ Action instances, infrastructure objects such as Hibernate SessionFactories, JMS Queues, and so
+ forth. Typically, one does not configure fine-grained domain objects in the container, because it is
+
+ usually the responsibility of DAOs and business logic to create and load domain objects. However,
+ you can use Spring’s integration with AspectJ to configure objects that have been created outside
+ the control of an IoC container. See Using AspectJ to dependency-inject domain objects with Spring.
+
+
+ The following example shows the basic structure of XML-based configuration metadata:
+
+
+
+
+
+
+
+ ① ②
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ① The id attribute is a string that identifies the individual bean definition.
+
+ ② The class attribute defines the type of the bean and uses the fully qualified classname.
+
+ The value of the id attribute refers to collaborating objects. The XML for referring to collaborating
+ objects is not shown in this example. See Dependencies for more information.
+
+
+ Instantiating a Container
+
+ The location path or paths supplied to an ApplicationContext constructor are resource strings that
+ let the container load configuration metadata from a variety of external resources, such as the local
+ file system, the Java CLASSPATH, and so on.
+
+
+ Java
+
+
+ ApplicationContext context = new ClassPathXmlApplicationContext("services.xml",
+ "daos.xml");
+
+
+
+ Kotlin
+
+
+ val context = ClassPathXmlApplicationContext("services.xml", "daos.xml")
+
+ After you learn about Spring’s IoC container, you may want to know more about
+ Spring’s Resource abstraction (as described in Resources), which provides a
+ convenient mechanism for reading an InputStream from locations defined in a
+ URI syntax. In particular, Resource paths are used to construct applications
+ contexts, as described in Application Contexts and Resource Paths.
+
+
+ The following example shows the service layer objects (services.xml) configuration file:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ The following example shows the data access objects daos.xml file:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ In the preceding example, the service layer consists of the PetStoreServiceImpl class and two data
+ access objects of the types JpaAccountDao and JpaItemDao (based on the JPA Object-Relational
+ Mapping standard). The property name element refers to the name of the JavaBean property, and the
+ ref element refers to the name of another bean definition. This linkage between id and ref
+ elements expresses the dependency between collaborating objects. For details of configuring an
+ object’s dependencies, see Dependencies.
+
+
+
+ Composing XML-based Configuration Metadata
+
+ It can be useful to have bean definitions span multiple XML files. Often, each individual XML
+ configuration file represents a logical layer or module in your architecture.
+
+
+ You can use the application context constructor to load bean definitions from all these XML
+ fragments. This constructor takes multiple Resource locations, as was shown in the previous section.
+ Alternatively, use one or more occurrences of the element to load bean definitions from
+ another file or files. The following example shows how to do so:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ In the preceding example, external bean definitions are loaded from three files: services.xml,
+ messageSource.xml, and themeSource.xml. All location paths are relative to the definition file doing
+ the importing, so services.xml must be in the same directory or classpath location as the file doing
+ the importing, while messageSource.xml and themeSource.xml must be in a resources location below
+ the location of the importing file. As you can see, a leading slash is ignored. However, given that
+ these paths are relative, it is better form not to use the slash at all. The contents of the files being
+ imported, including the top level element, must be valid XML bean definitions, according
+ to the Spring Schema.
+
+ It is possible, but not recommended, to reference files in parent directories using a
+ relative "../" path. Doing so creates a dependency on a file that is outside the
+ current application. In particular, this reference is not recommended for
+ classpath: URLs (for example, classpath:../services.xml), where the runtime
+ resolution process chooses the “nearest” classpath root and then looks into its
+ parent directory. Classpath configuration changes may lead to the choice of a
+ different, incorrect directory.
+
+ You can always use fully qualified resource locations instead of relative paths: for
+ example, file:C:/config/services.xml or classpath:/config/services.xml.
+ However, be aware that you are coupling your application’s configuration to
+ specific absolute locations. It is generally preferable to keep an indirection for such
+ absolute locations — for example, through "${…}" placeholders that are resolved
+ against JVM system properties at runtime.
+
+
+ The namespace itself provides the import directive feature. Further configuration features beyond
+ plain bean definitions are available in a selection of XML namespaces provided by Spring — for
+ example, the context and util namespaces.
+
+
+
+ The Groovy Bean Definition DSL
+
+ As a further example for externalized configuration metadata, bean definitions can also be
+ expressed in Spring’s Groovy Bean Definition DSL, as known from the Grails framework. Typically,
+ such configuration live in a ".groovy" file with the structure shown in the following example:
+
+
+
+ beans {
+ dataSource(BasicDataSource) {
+ driverClassName = "org.hsqldb.jdbcDriver"
+ url = "jdbc:hsqldb:mem:grailsDB"
+ username = "sa"
+ password = ""
+ settings = [mynew:"setting"]
+ }
+ sessionFactory(SessionFactory) {
+ dataSource = dataSource
+ }
+ myService(MyService) {
+ nestedBean = { AnotherBean bean ->
+ dataSource = dataSource
+ }
+ }
+ }
+
+
+
+ This configuration style is largely equivalent to XML bean definitions and even supports Spring’s
+ XML configuration namespaces. It also allows for importing XML bean definition files through an
+ importBeans directive.
+
+ Using the Container
+
+ The ApplicationContext is the interface for an advanced factory capable of maintaining a registry of
+ different beans and their dependencies. By using the method T getBean(String name, Class
+ requiredType), you can retrieve instances of your beans.
+
+ The ApplicationContext lets you read bean definitions and access them, as the following example
+ shows:
+
+
+ Java
+
+
+ // create and configure beans
+ ApplicationContext context = new ClassPathXmlApplicationContext("services.xml",
+ "daos.xml");
+
+
+ // retrieve configured instance
+ PetStoreService service = context.getBean("petStore", PetStoreService.class);
+
+
+ // use configured instance
+ List userList = service.getUsernameList();
+
+
+
+ Kotlin
+
+
+ import org.springframework.beans.factory.getBean
+
+
+ // create and configure beans
+ val context = ClassPathXmlApplicationContext("services.xml", "daos.xml")
+
+
+ // retrieve configured instance
+ val service = context.getBean("petStore")
+
+
+ // use configured instance
+ var userList = service.getUsernameList()
+
+
+
+ With Groovy configuration, bootstrapping looks very similar. It has a different context
+ implementation class which is Groovy-aware (but also understands XML bean definitions). The
+ following example shows Groovy configuration:
+
+
+ Java
+
+
+ ApplicationContext context = new GenericGroovyApplicationContext("services.groovy",
+ "daos.groovy");
+
+
+
+ Kotlin
+
+
+ val context = GenericGroovyApplicationContext("services.groovy", "daos.groovy")
+
+
+
+ The most flexible variant is GenericApplicationContext in combination with reader delegates — for
+ example, with XmlBeanDefinitionReader for XML files, as the following example shows:
+
+ Java
+
+
+ GenericApplicationContext context = new GenericApplicationContext();
+ new XmlBeanDefinitionReader(context).loadBeanDefinitions("services.xml", "daos.xml");
+ context.refresh();
+
+
+
+ Kotlin
+
+
+ val context = GenericApplicationContext()
+ XmlBeanDefinitionReader(context).loadBeanDefinitions("services.xml", "daos.xml")
+ context.refresh()
+
+
+
+ You can also use the GroovyBeanDefinitionReader for Groovy files, as the following example shows:
+
+
+ Java
+
+
+ GenericApplicationContext context = new GenericApplicationContext();
+ new GroovyBeanDefinitionReader(context).loadBeanDefinitions("services.groovy",
+ "daos.groovy");
+ context.refresh();
+
+
+
+ Kotlin
+
+
+ val context = GenericApplicationContext()
+ GroovyBeanDefinitionReader(context).loadBeanDefinitions("services.groovy",
+ "daos.groovy")
+ context.refresh()
+
+
+
+ You can mix and match such reader delegates on the same ApplicationContext, reading bean
+ definitions from diverse configuration sources.
+
+
+ You can then use getBean to retrieve instances of your beans. The ApplicationContext interface has a
+ few other methods for retrieving beans, but, ideally, your application code should never use them.
+ Indeed, your application code should have no calls to the getBean() method at all and thus have no
+ dependency on Spring APIs at all. For example, Spring’s integration with web frameworks provides
+ dependency injection for various web framework components such as controllers and JSF-managed
+ beans, letting you declare a dependency on a specific bean through metadata (such as an
+ autowiring annotation).
+
+
+ 2.1.3. Bean Overview
+
+ A Spring IoC container manages one or more beans. These beans are created with the configuration
+ metadata that you supply to the container (for example, in the form of XML definitions).
+
+
+ Within the container itself, these bean definitions are represented as BeanDefinition objects, which
+ contain (among other information) the following metadata:
+
+ • A package-qualified class name: typically, the actual implementation class of the bean being
+
+ defined.
+
+ • Bean behavioral configuration elements, which state how the bean should behave in the
+ container (scope, lifecycle callbacks, and so forth).
+
+ • References to other beans that are needed for the bean to do its work. These references are also
+ called collaborators or dependencies.
+
+ • Other configuration settings to set in the newly created object — for example, the size limit of
+ the pool or the number of connections to use in a bean that manages a connection pool.
+
+
+ This metadata translates to a set of properties that make up each bean definition. The following
+ table describes these properties:
+
+
+ Table 1. The bean definition
+
+ Property Explained in…
+
+ Class Instantiating Beans
+
+ Name Naming Beans
+
+ Scope Bean Scopes
+
+ Constructor arguments Dependency Injection
+
+ Properties Dependency Injection
+
+ Autowiring mode Autowiring Collaborators
+
+ Lazy initialization mode Lazy-initialized Beans
+
+ Initialization method Initialization Callbacks
+
+ Destruction method Destruction Callbacks
+
+
+ In addition to bean definitions that contain information on how to create a specific bean, the
+ ApplicationContext implementations also permit the registration of existing objects that are created
+ outside the container (by users). This is done by accessing the ApplicationContext’s BeanFactory
+ through the getBeanFactory() method, which returns the DefaultListableBeanFactory
+ implementation. DefaultListableBeanFactory supports this registration through the
+ registerSingleton(..) and registerBeanDefinition(..) methods. However, typical applications
+ work solely with beans defined through regular bean definition metadata.
+
+
+ Bean metadata and manually supplied singleton instances need to be registered as
+ early as possible, in order for the container to properly reason about them during
+ autowiring and other introspection steps. While overriding existing metadata and
+ existing singleton instances is supported to some degree, the registration of new
+ beans at runtime (concurrently with live access to the factory) is not officially
+ supported and may lead to concurrent access exceptions, inconsistent state in the
+ bean container, or both.
+
+
+
+ Naming Beans
+
+ Every bean has one or more identifiers. These identifiers must be unique within the container that
+ hosts the bean. A bean usually has only one identifier. However, if it requires more than one, the
+
+ extra ones can be considered aliases.
+
+
+ In XML-based configuration metadata, you use the id attribute, the name attribute, or both to specify
+ the bean identifiers. The id attribute lets you specify exactly one id. Conventionally, these names
+ are alphanumeric ('myBean', 'someService', etc.), but they can contain special characters as well. If
+ you want to introduce other aliases for the bean, you can also specify them in the name attribute,
+ separated by a comma (,), semicolon (;), or white space. As a historical note, in versions prior to
+ Spring 3.1, the id attribute was defined as an xsd:ID type, which constrained possible characters. As
+ of 3.1, it is defined as an xsd:string type. Note that bean id uniqueness is still enforced by the
+ container, though no longer by XML parsers.
+
+
+ You are not required to supply a name or an id for a bean. If you do not supply a name or id explicitly,
+ the container generates a unique name for that bean. However, if you want to refer to that bean by
+ name, through the use of the ref element or a Service Locator style lookup, you must provide a
+ name. Motivations for not supplying a name are related to using inner beans and autowiring
+ collaborators.
+
+
+ Bean Naming Conventions
+
+ The convention is to use the standard Java convention for instance field names when naming
+ beans. That is, bean names start with a lowercase letter and are camel-cased from there.
+ Examples of such names include accountManager, accountService, userDao, loginController, and
+ so forth.
+
+
+ Naming beans consistently makes your configuration easier to read and understand. Also, if
+ you use Spring AOP, it helps a lot when applying advice to a set of beans related by name.
+
+
+
+
+ With component scanning in the classpath, Spring generates bean names for
+ unnamed components, following the rules described earlier: essentially, taking the
+ simple class name and turning its initial character to lower-case. However, in the
+ (unusual) special case when there is more than one character and both the first
+ and second characters are upper case, the original casing gets preserved. These are
+ the same rules as defined by java.beans.Introspector.decapitalize (which Spring
+ uses here).
+
+
+
+ Aliasing a Bean outside the Bean Definition
+
+ In a bean definition itself, you can supply more than one name for the bean, by using a
+ combination of up to one name specified by the id attribute and any number of other names in the
+ name attribute. These names can be equivalent aliases to the same bean and are useful for some
+ situations, such as letting each component in an application refer to a common dependency by
+ using a bean name that is specific to that component itself.
+
+ Specifying all aliases where the bean is actually defined is not always adequate, however. It is
+ sometimes desirable to introduce an alias for a bean that is defined elsewhere. This is commonly
+ the case in large systems where configuration is split amongst each subsystem, with each
+ subsystem having its own set of object definitions. In XML-based configuration metadata, you can
+ use the element to accomplish this. The following example shows how to do so:
+
+
+
+
+
+ In this case, a bean (in the same container) named fromName may also, after the use of this alias
+ definition, be referred to as toName.
+
+
+ For example, the configuration metadata for subsystem A may refer to a DataSource by the name of
+ subsystemA-dataSource. The configuration metadata for subsystem B may refer to a DataSource by
+ the name of subsystemB-dataSource. When composing the main application that uses both these
+ subsystems, the main application refers to the DataSource by the name of myApp-dataSource. To have
+ all three names refer to the same object, you can add the following alias definitions to the
+ configuration metadata:
+
+
+
+
+
+
+
+
+ Now each component and the main application can refer to the dataSource through a name that is
+ unique and guaranteed not to clash with any other definition (effectively creating a namespace),
+ yet they refer to the same bean.
+
+
+ Java-configuration
+
+ If you use Javaconfiguration, the @Bean annotation can be used to provide aliases. See Using
+ the @Bean Annotation for details.
+
+
+
+
+ Instantiating Beans
+
+ A bean definition is essentially a recipe for creating one or more objects. The container looks at the
+ recipe for a named bean when asked and uses the configuration metadata encapsulated by that
+ bean definition to create (or acquire) an actual object.
+
+
+ If you use XML-based configuration metadata, you specify the type (or class) of object that is to be
+ instantiated in the class attribute of the element. This class attribute (which, internally, is a
+ Class property on a BeanDefinition instance) is usually mandatory. (For exceptions, see
+ Instantiation by Using an Instance Factory Method and Bean Definition Inheritance.) You can use
+ the Class property in one of two ways:
+
+
+ • Typically, to specify the bean class to be constructed in the case where the container itself
+ directly creates the bean by calling its constructor reflectively, somewhat equivalent to Java
+ code with the new operator.
+
+ • To specify the actual class containing the static factory method that is invoked to create the
+ object, in the less common case where the container invokes a static factory method on a class
+ to create the bean. The object type returned from the invocation of the static factory method
+ may be the same class or another class entirely.
+
+ Nested class names
+
+ If you want to configure a bean definition for a nested class, you may use either the binary
+ name or the source name of the nested class.
+
+
+ For example, if you have a class called SomeThing in the com.example package, and this
+ SomeThing class has a static nested class called OtherThing, they can be separated by a dollar
+ sign ($) or a dot (.). So the value of the class attribute in a bean definition would be
+ com.example.SomeThing$OtherThing or com.example.SomeThing.OtherThing.
+
+
+
+
+
+ Instantiation with a Constructor
+
+ When you create a bean by the constructor approach, all normal classes are usable by and
+ compatible with Spring. That is, the class being developed does not need to implement any specific
+ interfaces or to be coded in a specific fashion. Simply specifying the bean class should suffice.
+ However, depending on what type of IoC you use for that specific bean, you may need a default
+ (empty) constructor.
+
+
+ The Spring IoC container can manage virtually any class you want it to manage. It is not limited to
+ managing true JavaBeans. Most Spring users prefer actual JavaBeans with only a default (no-
+ argument) constructor and appropriate setters and getters modeled after the properties in the
+ container. You can also have more exotic non-bean-style classes in your container. If, for example,
+ you need to use a legacy connection pool that absolutely does not adhere to the JavaBean
+ specification, Spring can manage it as well.
+
+
+ With XML-based configuration metadata you can specify your bean class as follows:
+
+
+
+
+
+
+
+
+
+
+ For details about the mechanism for supplying arguments to the constructor (if required) and
+ setting object instance properties after the object is constructed, see Injecting Dependencies.
+
+
+
+ Instantiation with a Static Factory Method
+
+ When defining a bean that you create with a static factory method, use the class attribute to specify
+ the class that contains the static factory method and an attribute named factory-method to specify
+ the name of the factory method itself. You should be able to call this method (with optional
+ arguments, as described later) and return a live object, which subsequently is treated as if it had
+ been created through a constructor. One use for such a bean definition is to call static factories in
+ legacy code.
+
+
+ The following bean definition specifies that the bean will be created by calling a factory method.
+ The definition does not specify the type (class) of the returned object, but rather the class
+ containing the factory method. In this example, the createInstance() method must be a static
+ method. The following example shows how to specify a factory method:
+
+
+
+
+
+ The following example shows a class that would work with the preceding bean definition:
+
+
+ Java
+
+
+ public class ClientService {
+ private static ClientService clientService = new ClientService();
+ private ClientService() {}
+
+
+ public static ClientService createInstance() {
+ return clientService;
+ }
+ }
+
+
+
+ Kotlin
+
+
+ class ClientService private constructor() {
+ companion object {
+ private val clientService = ClientService()
+ @JvmStatic
+ fun createInstance() = clientService
+ }
+ }
+
+
+
+ For details about the mechanism for supplying (optional) arguments to the factory method and
+ setting object instance properties after the object is returned from the factory, see Dependencies
+ and Configuration in Detail.
+
+
+
+ Instantiation by Using an Instance Factory Method
+
+ Similar to instantiation through a static factory method, instantiation with an instance factory
+ method invokes a non-static method of an existing bean from the container to create a new bean.
+ To use this mechanism, leave the class attribute empty and, in the factory-bean attribute, specify
+ the name of a bean in the current (or parent or ancestor) container that contains the instance
+ method that is to be invoked to create the object. Set the name of the factory method itself with the
+ factory-method attribute. The following example shows how to configure such a bean:
+
+
+
+
+
+
+
+
+
+
+
+
+ The following example shows the corresponding class:
+
+
+ Java
+
+
+ public class DefaultServiceLocator {
+
+
+ private static ClientService clientService = new ClientServiceImpl();
+
+
+ public ClientService createClientServiceInstance() {
+ return clientService;
+ }
+ }
+
+
+
+ Kotlin
+
+
+ class DefaultServiceLocator {
+ companion object {
+ private val clientService = ClientServiceImpl()
+ }
+ fun createClientServiceInstance(): ClientService {
+ return clientService
+ }
+ }
+
+
+
+ One factory class can also hold more than one factory method, as the following example shows:
+
+
+
+
+
+
+
+
+
+
+
+
+
+ The following example shows the corresponding class:
+
+
+ Java
+
+
+ public class DefaultServiceLocator {
+
+
+ private static ClientService clientService = new ClientServiceImpl();
+
+
+ private static AccountService accountService = new AccountServiceImpl();
+
+
+ public ClientService createClientServiceInstance() {
+ return clientService;
+ }
+
+
+ public AccountService createAccountServiceInstance() {
+ return accountService;
+ }
+ }
+
+
+
+ Kotlin
+
+
+ class DefaultServiceLocator {
+ companion object {
+ private val clientService = ClientServiceImpl()
+ private val accountService = AccountServiceImpl()
+ }
+
+
+ fun createClientServiceInstance(): ClientService {
+ return clientService
+ }
+
+
+ fun createAccountServiceInstance(): AccountService {
+ return accountService
+ }
+ }
+
+
+
+ This approach shows that the factory bean itself can be managed and configured through
+ dependency injection (DI). See Dependencies and Configuration in Detail.
+
+
+ In Spring documentation, "factory bean" refers to a bean that is configured in the
+ Spring container and that creates objects through an instance or static factory
+ method. By contrast, FactoryBean (notice the capitalization) refers to a Spring-
+ specific FactoryBean implementation class.
+
+
+
+ Determining a Bean’s Runtime Type
+
+ The runtime type of a specific bean is non-trivial to determine. A specified class in the bean
+ metadata definition is just an initial class reference, potentially combined with a declared factory
+ method or being a FactoryBean class which may lead to a different runtime type of the bean, or not
+
+ being set at all in case of an instance-level factory method (which is resolved via the specified
+ factory-bean name instead). Additionally, AOP proxying may wrap a bean instance with an
+ interface-based proxy with limited exposure of the target bean’s actual type (just its implemented
+ interfaces).
+
+ The recommended way to find out about the actual runtime type of a particular bean is a
+ BeanFactory.getType call for the specified bean name. This takes all of the above cases into account
+ and returns the type of object that a BeanFactory.getBean call is going to return for the same bean
+ name.
+
+ 2.1.4. Dependencies
+
+ A typical enterprise application does not consist of a single object (or bean in the Spring parlance).
+ Even the simplest application has a few objects that work together to present what the end-user
+ sees as a coherent application. This next section explains how you go from defining a number of
+ bean definitions that stand alone to a fully realized application where objects collaborate to achieve
+ a goal.
+
+
+ Dependency Injection
+
+ Dependency injection (DI) is a process whereby objects define their dependencies (that is, the other
+ objects with which they work) only through constructor arguments, arguments to a factory method,
+ or properties that are set on the object instance after it is constructed or returned from a factory
+ method. The container then injects those dependencies when it creates the bean. This process is
+ fundamentally the inverse (hence the name, Inversion of Control) of the bean itself controlling the
+ instantiation or location of its dependencies on its own by using direct construction of classes or the
+ Service Locator pattern.
+
+
+ Code is cleaner with the DI principle, and decoupling is more effective when objects are provided
+ with their dependencies. The object does not look up its dependencies and does not know the
+ location or class of the dependencies. As a result, your classes become easier to test, particularly
+ when the dependencies are on interfaces or abstract base classes, which allow for stub or mock
+ implementations to be used in unit tests.
+
+
+ DI exists in two major variants: Constructor-based dependency injection and Setter-based
+ dependency injection.
+
+
+
+ Constructor-based Dependency Injection
+
+ Constructor-based DI is accomplished by the container invoking a constructor with a number of
+ arguments, each representing a dependency. Calling a static factory method with specific
+ arguments to construct the bean is nearly equivalent, and this discussion treats arguments to a
+ constructor and to a static factory method similarly. The following example shows a class that can
+ only be dependency-injected with constructor injection:
+
+ Java
+
+
+ public class SimpleMovieLister {
+
+
+ // the SimpleMovieLister has a dependency on a MovieFinder
+ private final MovieFinder movieFinder;
+
+
+ // a constructor so that the Spring container can inject a MovieFinder
+ public SimpleMovieLister(MovieFinder movieFinder) {
+ this.movieFinder = movieFinder;
+ }
+
+
+ // business logic that actually uses the injected MovieFinder is omitted...
+ }
+
+
+
+ Kotlin
+
+
+ // a constructor so that the Spring container can inject a MovieFinder
+ class SimpleMovieLister(private val movieFinder: MovieFinder) {
+ // business logic that actually uses the injected MovieFinder is omitted...
+ }
+
+
+
+ Notice that there is nothing special about this class. It is a POJO that has no dependencies on
+ container specific interfaces, base classes, or annotations.
+
+
+ Constructor Argument Resolution
+
+ Constructor argument resolution matching occurs by using the argument’s type. If no potential
+ ambiguity exists in the constructor arguments of a bean definition, the order in which the
+ constructor arguments are defined in a bean definition is the order in which those arguments are
+ supplied to the appropriate constructor when the bean is being instantiated. Consider the following
+ class:
+
+
+ Java
+
+
+ package x.y;
+
+
+ public class ThingOne {
+
+
+ public ThingOne(ThingTwo thingTwo, ThingThree thingThree) {
+ // ...
+ }
+ }
+
+ Kotlin
+
+
+ package x.y
+
+
+ class ThingOne(thingTwo: ThingTwo, thingThree: ThingThree)
+
+
+
+ Assuming that the ThingTwo and ThingThree classes are not related by inheritance, no potential
+ ambiguity exists. Thus, the following configuration works fine, and you do not need to specify the
+ constructor argument indexes or types explicitly in the element.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ When another bean is referenced, the type is known, and matching can occur (as was the case with
+ the preceding example). When a simple type is used, such as true, Spring cannot
+ determine the type of the value, and so cannot match by type without help. Consider the following
+ class:
+
+
+ Java
+
+
+ package examples;
+
+
+ public class ExampleBean {
+
+
+ // Number of years to calculate the Ultimate Answer
+ private final int years;
+
+
+ // The Answer to Life, the Universe, and Everything
+ private final String ultimateAnswer;
+
+
+ public ExampleBean(int years, String ultimateAnswer) {
+ this.years = years;
+ this.ultimateAnswer = ultimateAnswer;
+ }
+ }
+
+ Kotlin
+
+
+ package examples
+
+
+ class ExampleBean(
+ private val years: Int, // Number of years to calculate the Ultimate Answer
+ private val ultimateAnswer: String // The Answer to Life, the Universe, and
+ Everything
+ )
+
+
+
+ Constructor argument type matching
+ In the preceding scenario, the container can use type matching with simple types if you explicitly
+ specify the type of the constructor argument by using the type attribute, as the following example
+ shows:
+
+
+
+
+
+
+
+
+
+
+ Constructor argument index
+ You can use the index attribute to specify explicitly the index of constructor arguments, as the
+ following example shows:
+
+
+
+
+
+
+
+
+
+
+ In addition to resolving the ambiguity of multiple simple values, specifying an index resolves
+ ambiguity where a constructor has two arguments of the same type.
+
+ The index is 0-based.
+
+
+ Constructor argument name
+ You can also use the constructor parameter name for value disambiguation, as the following
+ example shows:
+
+
+
+
+
+
+
+
+
+
+ Keep in mind that, to make this work out of the box, your code must be compiled with the debug
+ flag enabled so that Spring can look up the parameter name from the constructor. If you cannot or
+
+ do not want to compile your code with the debug flag, you can use the @ConstructorProperties JDK
+ annotation to explicitly name your constructor arguments. The sample class would then have to
+ look as follows:
+
+
+ Java
+
+
+ package examples;
+
+
+ public class ExampleBean {
+
+
+ // Fields omitted
+
+
+ @ConstructorProperties({"years", "ultimateAnswer"})
+ public ExampleBean(int years, String ultimateAnswer) {
+ this.years = years;
+ this.ultimateAnswer = ultimateAnswer;
+ }
+ }
+
+
+
+ Kotlin
+
+
+ package examples
+
+
+ class ExampleBean
+ @ConstructorProperties("years", "ultimateAnswer")
+ constructor(val years: Int, val ultimateAnswer: String)
+
+
+
+
+ Setter-based Dependency Injection
+
+ Setter-based DI is accomplished by the container calling setter methods on your beans after
+ invoking a no-argument constructor or a no-argument static factory method to instantiate your
+ bean.
+
+ The following example shows a class that can only be dependency-injected by using pure setter
+ injection. This class is conventional Java. It is a POJO that has no dependencies on container specific
+ interfaces, base classes, or annotations.
+
+ Java
+
+
+ public class SimpleMovieLister {
+
+
+ // the SimpleMovieLister has a dependency on the MovieFinder
+ private MovieFinder movieFinder;
+
+
+ // a setter method so that the Spring container can inject a MovieFinder
+ public void setMovieFinder(MovieFinder movieFinder) {
+ this.movieFinder = movieFinder;
+ }
+
+
+ // business logic that actually uses the injected MovieFinder is omitted...
+ }
+
+
+
+ Kotlin
+
+
+ class SimpleMovieLister {
+
+
+ // a late-initialized property so that the Spring container can inject a
+ MovieFinder
+ lateinit var movieFinder: MovieFinder
+
+
+ // business logic that actually uses the injected MovieFinder is omitted...
+ }
+
+
+
+ The ApplicationContext supports constructor-based and setter-based DI for the beans it manages. It
+ also supports setter-based DI after some dependencies have already been injected through the
+ constructor approach. You configure the dependencies in the form of a BeanDefinition, which you
+ use in conjunction with PropertyEditor instances to convert properties from one format to another.
+ However, most Spring users do not work with these classes directly (that is, programmatically) but
+ rather with XML bean definitions, annotated components (that is, classes annotated with @Component,
+ @Controller, and so forth), or @Bean methods in Java-based @Configuration classes. These sources are
+ then converted internally into instances of BeanDefinition and used to load an entire Spring IoC
+ container instance.
+
+ Constructor-based or setter-based DI?
+
+ Since you can mix constructor-based and setter-based DI, it is a good rule of thumb to use
+ constructors for mandatory dependencies and setter methods or configuration methods for
+ optional dependencies. Note that use of the @Autowired annotation on a setter method can
+ be used to make the property be a required dependency; however, constructor injection with
+ programmatic validation of arguments is preferable.
+
+ The Spring team generally advocates constructor injection, as it lets you implement
+ application components as immutable objects and ensures that required dependencies are
+ not null. Furthermore, constructor-injected components are always returned to the client
+ (calling) code in a fully initialized state. As a side note, a large number of constructor
+ arguments is a bad code smell, implying that the class likely has too many responsibilities and
+ should be refactored to better address proper separation of concerns.
+
+
+ Setter injection should primarily only be used for optional dependencies that can be assigned
+ reasonable default values within the class. Otherwise, not-null checks must be performed
+ everywhere the code uses the dependency. One benefit of setter injection is that setter
+ methods make objects of that class amenable to reconfiguration or re-injection later.
+ Management through JMX MBeans is therefore a compelling use case for setter injection.
+
+
+ Use the DI style that makes the most sense for a particular class. Sometimes, when dealing
+ with third-party classes for which you do not have the source, the choice is made for you. For
+ example, if a third-party class does not expose any setter methods, then constructor injection
+ may be the only available form of DI.
+
+
+
+
+
+ Dependency Resolution Process
+
+ The container performs bean dependency resolution as follows:
+
+
+ • The ApplicationContext is created and initialized with configuration metadata that describes all
+ the beans. Configuration metadata can be specified by XML, Java code, or annotations.
+
+ • For each bean, its dependencies are expressed in the form of properties, constructor arguments,
+ or arguments to the static-factory method (if you use that instead of a normal constructor).
+ These dependencies are provided to the bean, when the bean is actually created.
+
+ • Each property or constructor argument is an actual definition of the value to set, or a reference
+ to another bean in the container.
+
+ • Each property or constructor argument that is a value is converted from its specified format to
+ the actual type of that property or constructor argument. By default, Spring can convert a value
+ supplied in string format to all built-in types, such as int, long, String, boolean, and so forth.
+
+ The Spring container validates the configuration of each bean as the container is created. However,
+ the bean properties themselves are not set until the bean is actually created. Beans that are
+ singleton-scoped and set to be pre-instantiated (the default) are created when the container is
+ created. Scopes are defined in Bean Scopes. Otherwise, the bean is created only when it is
+ requested. Creation of a bean potentially causes a graph of beans to be created, as the bean’s
+ dependencies and its dependencies' dependencies (and so on) are created and assigned. Note that
+
+ resolution mismatches among those dependencies may show up late — that is, on first creation of
+ the affected bean.
+
+
+ Circular dependencies
+
+ If you use predominantly constructor injection, it is possible to create an unresolvable
+ circular dependency scenario.
+
+
+ For example: Class A requires an instance of class B through constructor injection, and class B
+ requires an instance of class A through constructor injection. If you configure beans for
+ classes A and B to be injected into each other, the Spring IoC container detects this circular
+ reference at runtime, and throws a BeanCurrentlyInCreationException.
+
+
+ One possible solution is to edit the source code of some classes to be configured by setters
+ rather than constructors. Alternatively, avoid constructor injection and use setter injection
+ only. In other words, although it is not recommended, you can configure circular
+ dependencies with setter injection.
+
+
+ Unlike the typical case (with no circular dependencies), a circular dependency between bean
+ A and bean B forces one of the beans to be injected into the other prior to being fully
+ initialized itself (a classic chicken-and-egg scenario).
+
+
+
+ You can generally trust Spring to do the right thing. It detects configuration problems, such as
+ references to non-existent beans and circular dependencies, at container load-time. Spring sets
+ properties and resolves dependencies as late as possible, when the bean is actually created. This
+ means that a Spring container that has loaded correctly can later generate an exception when you
+ request an object if there is a problem creating that object or one of its dependencies — for
+ example, the bean throws an exception as a result of a missing or invalid property. This potentially
+ delayed visibility of some configuration issues is why ApplicationContext implementations by
+ default pre-instantiate singleton beans. At the cost of some upfront time and memory to create
+ these beans before they are actually needed, you discover configuration issues when the
+ ApplicationContext is created, not later. You can still override this default behavior so that singleton
+ beans initialize lazily, rather than being eagerly pre-instantiated.
+
+
+ If no circular dependencies exist, when one or more collaborating beans are being injected into a
+ dependent bean, each collaborating bean is totally configured prior to being injected into the
+ dependent bean. This means that, if bean A has a dependency on bean B, the Spring IoC container
+ completely configures bean B prior to invoking the setter method on bean A. In other words, the
+ bean is instantiated (if it is not a pre-instantiated singleton), its dependencies are set, and the
+ relevant lifecycle methods (such as a configured init method or the InitializingBean callback
+ method) are invoked.
+
+
+
+ Examples of Dependency Injection
+
+ The following example uses XML-based configuration metadata for setter-based DI. A small part of
+ a Spring XML configuration file specifies some bean definitions as follows:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ The following example shows the corresponding ExampleBean class:
+
+
+ Java
+
+
+ public class ExampleBean {
+
+
+ private AnotherBean beanOne;
+
+
+ private YetAnotherBean beanTwo;
+
+
+ private int i;
+
+
+ public void setBeanOne(AnotherBean beanOne) {
+ this.beanOne = beanOne;
+ }
+
+
+ public void setBeanTwo(YetAnotherBean beanTwo) {
+ this.beanTwo = beanTwo;
+ }
+
+
+ public void setIntegerProperty(int i) {
+ this.i = i;
+ }
+ }
+
+
+
+ Kotlin
+
+
+ class ExampleBean {
+ lateinit var beanOne: AnotherBean
+ lateinit var beanTwo: YetAnotherBean
+ var i: Int = 0
+ }
+
+
+
+ In the preceding example, setters are declared to match against the properties specified in the XML
+
+ file. The following example uses constructor-based DI:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ The following example shows the corresponding ExampleBean class:
+
+
+ Java
+
+
+ public class ExampleBean {
+
+
+ private AnotherBean beanOne;
+
+
+ private YetAnotherBean beanTwo;
+
+
+ private int i;
+
+
+ public ExampleBean(
+ AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {
+ this.beanOne = anotherBean;
+ this.beanTwo = yetAnotherBean;
+ this.i = i;
+ }
+ }
+
+
+
+ Kotlin
+
+
+ class ExampleBean(
+ private val beanOne: AnotherBean,
+ private val beanTwo: YetAnotherBean,
+ private val i: Int)
+
+
+
+ The constructor arguments specified in the bean definition are used as arguments to the
+ constructor of the ExampleBean.
+
+
+ Now consider a variant of this example, where, instead of using a constructor, Spring is told to call
+ a static factory method to return an instance of the object:
+
+
+
+
+
+
+
+
+
+
+
+
+
+ The following example shows the corresponding ExampleBean class:
+
+
+ Java
+
+
+ public class ExampleBean {
+
+
+ // a private constructor
+ private ExampleBean(...) {
+ ...
+ }
+
+
+ // a static factory method; the arguments to this method can be
+ // considered the dependencies of the bean that is returned,
+ // regardless of how those arguments are actually used.
+ public static ExampleBean createInstance (
+ AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {
+
+
+ ExampleBean eb = new ExampleBean (...);
+ // some other operations...
+ return eb;
+ }
+ }
+
+
+
+ Kotlin
+
+
+ class ExampleBean private constructor() {
+ companion object {
+ // a static factory method; the arguments to this method can be
+ // considered the dependencies of the bean that is returned,
+ // regardless of how those arguments are actually used.
+ @JvmStatic
+ fun createInstance(anotherBean: AnotherBean, yetAnotherBean: YetAnotherBean,
+ i: Int): ExampleBean {
+ val eb = ExampleBean (...)
+ // some other operations...
+ return eb
+ }
+ }
+ }
+
+ Arguments to the static factory method are supplied by elements, exactly the
+ same as if a constructor had actually been used. The type of the class being returned by the factory
+ method does not have to be of the same type as the class that contains the static factory method
+ (although, in this example, it is). An instance (non-static) factory method can be used in an
+ essentially identical fashion (aside from the use of the factory-bean attribute instead of the class
+ attribute), so we do not discuss those details here.
+
+
+ Dependencies and Configuration in Detail
+
+ As mentioned in the previous section, you can define bean properties and constructor arguments as
+ references to other managed beans (collaborators) or as values defined inline. Spring’s XML-based
+ configuration metadata supports sub-element types within its and
+ elements for this purpose.
+
+
+
+ Straight Values (Primitives, Strings, and so on)
+
+ The value attribute of the element specifies a property or constructor argument as a
+ human-readable string representation. Spring’s conversion service is used to convert these values
+ from a String to the actual type of the property or argument. The following example shows various
+ values being set:
+
+
+
+
+
+
+
+
+
+
+
+
+
+ The following example uses the p-namespace for even more succinct XML configuration:
+
+
+
+
+
+
+
+
+
+
+
+
+
+ The preceding XML is more succinct. However, typos are discovered at runtime rather than design
+
+ time, unless you use an IDE (such as IntelliJ IDEA or the Spring Tools for Eclipse) that supports
+ automatic property completion when you create bean definitions. Such IDE assistance is highly
+ recommended.
+
+
+ You can also configure a java.util.Properties instance, as follows:
+
+
+
+
+
+
+
+
+
+ jdbc.driver.className=com.mysql.jdbc.Driver
+ jdbc.url=jdbc:mysql://localhost:3306/mydb
+
+
+
+
+
+
+ The Spring container converts the text inside the element into a java.util.Properties
+ instance by using the JavaBeans PropertyEditor mechanism. This is a nice shortcut, and is one of a
+ few places where the Spring team do favor the use of the nested element over the value
+ attribute style.
+
+
+ The idref element
+
+ The idref element is simply an error-proof way to pass the id (a string value - not a reference) of
+ another bean in the container to a or element. The following
+ example shows how to use it:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ The preceding bean definition snippet is exactly equivalent (at runtime) to the following snippet:
+
+
+
+
+
+
+
+
+
+
+
+
+ The first form is preferable to the second, because using the idref tag lets the container validate at
+ deployment time that the referenced, named bean actually exists. In the second variation, no
+
+ validation is performed on the value that is passed to the targetName property of the client bean.
+ Typos are only discovered (with most likely fatal results) when the client bean is actually
+ instantiated. If the client bean is a prototype bean, this typo and the resulting exception may only
+ be discovered long after the container is deployed.
+
+
+ The local attribute on the idref element is no longer supported in the 4.0 beans
+ XSD, since it does not provide value over a regular bean reference any more.
+ Change your existing idref local references to idref bean when upgrading to the
+ 4.0 schema.
+
+
+ A common place (at least in versions earlier than Spring 2.0) where the element brings
+ value is in the configuration of AOP interceptors in a ProxyFactoryBean bean definition. Using
+ elements when you specify the interceptor names prevents you from misspelling an
+ interceptor ID.
+
+
+
+ References to Other Beans (Collaborators)
+
+ The ref element is the final element inside a or definition element.
+ Here, you set the value of the specified property of a bean to be a reference to another bean (a
+ collaborator) managed by the container. The referenced bean is a dependency of the bean whose
+ property is to be set, and it is initialized on demand as needed before the property is set. (If the
+ collaborator is a singleton bean, it may already be initialized by the container.) All references are
+ ultimately a reference to another object. Scoping and validation depend on whether you specify the
+ ID or name of the other object through the bean or parent attribute.
+
+
+ Specifying the target bean through the bean attribute of the tag is the most general form and
+ allows creation of a reference to any bean in the same container or parent container, regardless of
+ whether it is in the same XML file. The value of the bean attribute may be the same as the id
+ attribute of the target bean or be the same as one of the values in the name attribute of the target
+ bean. The following example shows how to use a ref element:
+
+
+
+
+
+
+
+ Specifying the target bean through the parent attribute creates a reference to a bean that is in a
+ parent container of the current container. The value of the parent attribute may be the same as
+ either the id attribute of the target bean or one of the values in the name attribute of the target bean.
+ The target bean must be in a parent container of the current one. You should use this bean
+ reference variant mainly when you have a hierarchy of containers and you want to wrap an
+ existing bean in a parent container with a proxy that has the same name as the parent bean. The
+ following pair of listings shows how to use the parent attribute:
+
+
+
+
+
+
+
+
+
+
+ class="org.springframework.aop.framework.ProxyFactoryBean">
+
+
+
+
+
+
+
+
+
+ The local attribute on the ref element is no longer supported in the 4.0 beans XSD,
+ since it does not provide value over a regular bean reference any more. Change
+ your existing ref local references to ref bean when upgrading to the 4.0 schema.
+
+
+
+ Inner Beans
+
+ A element inside the or elements defines an inner bean, as
+ the following example shows:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ An inner bean definition does not require a defined ID or name. If specified, the container does not
+ use such a value as an identifier. The container also ignores the scope flag on creation, because
+ inner beans are always anonymous and are always created with the outer bean. It is not possible to
+ access inner beans independently or to inject them into collaborating beans other than into the
+ enclosing bean.
+
+
+ As a corner case, it is possible to receive destruction callbacks from a custom scope — for example,
+ for a request-scoped inner bean contained within a singleton bean. The creation of the inner bean
+ instance is tied to its containing bean, but destruction callbacks let it participate in the request
+ scope’s lifecycle. This is not a common scenario. Inner beans typically simply share their containing
+ bean’s scope.
+
+
+
+ Collections
+
+ The
, , , and elements set the properties and arguments of the Java
+ Collection types List, Set, Map, and Properties, respectively. The following example shows how to
+ use them:
+
+
+
+
+
+ administrator@example.org
+ support@example.org
+ development@example.org
+
+
+
+
+
+ a list element followed by a reference
+
+
+
+
+
+
+
+
+
+
+ just some string
+
+
+
+
+
+
+
+ The value of a map key or value, or a set value, can also be any of the following elements:
+
+
+
+ bean | ref | idref | list | set | map | props | value | null
+
+
+
+ Collection Merging
+
+ The Spring container also supports merging collections. An application developer can define a
+ parent
, , or element and have child
, , or
+ elements inherit and override values from the parent collection. That is, the child collection’s
+ values are the result of merging the elements of the parent and child collections, with the child’s
+ collection elements overriding values specified in the parent collection.
+
+
+ This section on merging discusses the parent-child bean mechanism. Readers unfamiliar with
+ parent and child bean definitions may wish to read the relevant section before continuing.
+
+
+ The following example demonstrates collection merging:
+
+
+
+
+
+ administrator@example.com
+ support@example.com
+
+
+
+
+
+
+
+ sales@example.com
+ support@example.co.uk
+
+
+
+
+
+
+
+ Notice the use of the merge=true attribute on the element of the adminEmails property of the
+ child bean definition. When the child bean is resolved and instantiated by the container, the
+ resulting instance has an adminEmails Properties collection that contains the result of merging the
+ child’s adminEmails collection with the parent’s adminEmails collection. The following listing shows
+ the result:
+
+
+
+ administrator=administrator@example.com
+ sales=sales@example.com
+ support=support@example.co.uk
+
+
+
+ The child Properties collection’s value set inherits all property elements from the parent ,
+ and the child’s value for the support value overrides the value in the parent collection.
+
+
+ This merging behavior applies similarly to the
, , and collection types. In the
+ specific case of the
element, the semantics associated with the List collection type (that is,
+ the notion of an ordered collection of values) is maintained. The parent’s values precede all of the
+ child list’s values. In the case of the Map, Set, and Properties collection types, no ordering exists.
+ Hence, no ordering semantics are in effect for the collection types that underlie the associated Map,
+ Set, and Properties implementation types that the container uses internally.
+
+
+ Limitations of Collection Merging
+
+ You cannot merge different collection types (such as a Map and a List). If you do attempt to do so, an
+ appropriate Exception is thrown. The merge attribute must be specified on the lower, inherited, child
+ definition. Specifying the merge attribute on a parent collection definition is redundant and does not
+ result in the desired merging.
+
+ Strongly-typed collection
+
+ Thanks to Java’s support for generic types, you can use strongly typed collections. That is, it is
+ possible to declare a Collection type such that it can only contain (for example) String elements. If
+ you use Spring to dependency-inject a strongly-typed Collection into a bean, you can take
+ advantage of Spring’s type-conversion support such that the elements of your strongly-typed
+ Collection instances are converted to the appropriate type prior to being added to the Collection.
+ The following Java class and bean definition show how to do so:
+
+
+ Java
+
+
+ public class SomeClass {
+
+
+ private Map accounts;
+
+
+ public void setAccounts(Map accounts) {
+ this.accounts = accounts;
+ }
+ }
+
+
+
+ Kotlin
+
+
+ class SomeClass {
+ lateinit var accounts: Map
+ }
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ When the accounts property of the something bean is prepared for injection, the generics
+ information about the element type of the strongly-typed Map is available by
+ reflection. Thus, Spring’s type conversion infrastructure recognizes the various value elements as
+ being of type Float, and the string values (9.99, 2.75, and 3.99) are converted into an actual Float
+ type.
+
+
+
+ Null and Empty String Values
+
+ Spring treats empty arguments for properties and the like as empty Strings. The following XML-
+ based configuration metadata snippet sets the email property to the empty String value ("").
+
+
+
+
+
+
+
+ The preceding example is equivalent to the following Java code:
+
+
+ Java
+
+
+ exampleBean.setEmail("");
+
+
+
+ Kotlin
+
+
+ exampleBean.email = ""
+
+
+
+ The element handles null values. The following listing shows an example:
+
+
+
+
+
+
+
+
+
+
+
+ The preceding configuration is equivalent to the following Java code:
+
+
+ Java
+
+
+ exampleBean.setEmail(null);
+
+
+
+ Kotlin
+
+
+ exampleBean.email = null
+
+
+
+
+ XML Shortcut with the p-namespace
+
+ The p-namespace lets you use the bean element’s attributes (instead of nested elements)
+ to describe your property values collaborating beans, or both.
+
+
+ Spring supports extensible configuration formats with namespaces, which are based on an XML
+ Schema definition. The beans configuration format discussed in this chapter is defined in an XML
+ Schema document. However, the p-namespace is not defined in an XSD file and exists only in the
+ core of Spring.
+
+
+ The following example shows two XML snippets (the first uses standard XML format and the
+ second uses the p-namespace) that resolve to the same result:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ The example shows an attribute in the p-namespace called email in the bean definition. This tells
+ Spring to include a property declaration. As previously mentioned, the p-namespace does not have
+ a schema definition, so you can set the name of the attribute to the property name.
+
+
+ This next example includes two more bean definitions that both have a reference to another bean:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ This example includes not only a property value using the p-namespace but also uses a special
+ format to declare property references. Whereas the first bean definition uses to create a reference from bean john to bean jane, the second bean
+ definition uses p:spouse-ref="jane" as an attribute to do the exact same thing. In this case, spouse is
+ the property name, whereas the -ref part indicates that this is not a straight value but rather a
+ reference to another bean.
+
+ The p-namespace is not as flexible as the standard XML format. For example, the
+ format for declaring property references clashes with properties that end in Ref,
+ whereas the standard XML format does not. We recommend that you choose your
+ approach carefully and communicate this to your team members to avoid
+ producing XML documents that use all three approaches at the same time.
+
+
+
+ XML Shortcut with the c-namespace
+
+ Similar to the XML Shortcut with the p-namespace, the c-namespace, introduced in Spring 3.1,
+ allows inlined attributes for configuring the constructor arguments rather then nested constructor-
+ arg elements.
+
+
+ The following example uses the c: namespace to do the same thing as the from Constructor-based
+ Dependency Injection:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ The c: namespace uses the same conventions as the p: one (a trailing -ref for bean references) for
+ setting the constructor arguments by their names. Similarly, it needs to be declared in the XML file
+ even though it is not defined in an XSD schema (it exists inside the Spring core).
+
+ For the rare cases where the constructor argument names are not available (usually if the bytecode
+ was compiled without debugging information), you can use fallback to the argument indexes, as
+ follows:
+
+
+
+
+
+
+
+ Due to the XML grammar, the index notation requires the presence of the leading
+ _, as XML attribute names cannot start with a number (even though some IDEs
+ allow it). A corresponding index notation is also available for
+ elements but not commonly used since the plain order of declaration is usually
+ sufficient there.
+
+
+ In practice, the constructor resolution mechanism is quite efficient in matching arguments, so
+ unless you really need to, we recommend using the name notation throughout your configuration.
+
+
+
+ Compound Property Names
+
+ You can use compound or nested property names when you set bean properties, as long as all
+ components of the path except the final property name are not null. Consider the following bean
+ definition:
+
+
+
+
+
+
+
+
+
+ The something bean has a fred property, which has a bob property, which has a sammy property, and
+ that final sammy property is being set to a value of 123. In order for this to work, the fred property of
+ something and the bob property of fred must not be null after the bean is constructed. Otherwise, a
+ NullPointerException is thrown.
+
+
+ Using depends-on
+
+ If a bean is a dependency of another bean, that usually means that one bean is set as a property of
+ another. Typically you accomplish this with the element in XML-based configuration
+ metadata. However, sometimes dependencies between beans are less direct. An example is when a
+ static initializer in a class needs to be triggered, such as for database driver registration. The
+ depends-on attribute can explicitly force one or more beans to be initialized before the bean using
+ this element is initialized. The following example uses the depends-on attribute to express a
+ dependency on a single bean:
+
+
+
+
+
+
+
+
+ To express a dependency on multiple beans, supply a list of bean names as the value of the depends-
+ on attribute (commas, whitespace, and semicolons are valid delimiters):
+
+
+
+
+
+
+
+
+
+
+
+
+ The depends-on attribute can specify both an initialization-time dependency and, in
+ the case of singleton beans only, a corresponding destruction-time dependency.
+ Dependent beans that define a depends-on relationship with a given bean are
+ destroyed first, prior to the given bean itself being destroyed. Thus, depends-on can
+ also control shutdown order.
+
+
+
+ Lazy-initialized Beans
+
+ By default, ApplicationContext implementations eagerly create and configure all singleton beans as
+ part of the initialization process. Generally, this pre-instantiation is desirable, because errors in the
+ configuration or surrounding environment are discovered immediately, as opposed to hours or
+ even days later. When this behavior is not desirable, you can prevent pre-instantiation of a
+ singleton bean by marking the bean definition as being lazy-initialized. A lazy-initialized bean tells
+ the IoC container to create a bean instance when it is first requested, rather than at startup.
+
+
+ In XML, this behavior is controlled by the lazy-init attribute on the element, as the
+ following example shows:
+
+
+
+
+
+
+
+
+ When the preceding configuration is consumed by an ApplicationContext, the lazy bean is not
+ eagerly pre-instantiated when the ApplicationContext starts, whereas the not.lazy bean is eagerly
+ pre-instantiated.
+
+
+ However, when a lazy-initialized bean is a dependency of a singleton bean that is not lazy-
+ initialized, the ApplicationContext creates the lazy-initialized bean at startup, because it must
+ satisfy the singleton’s dependencies. The lazy-initialized bean is injected into a singleton bean
+ elsewhere that is not lazy-initialized.
+
+ You can also control lazy-initialization at the container level by using the default-lazy-init
+ attribute on the element, as the following example shows:
+
+
+
+
+
+
+
+ Autowiring Collaborators
+
+ The Spring container can autowire relationships between collaborating beans. You can let Spring
+ resolve collaborators (other beans) automatically for your bean by inspecting the contents of the
+ ApplicationContext. Autowiring has the following advantages:
+
+ • Autowiring can significantly reduce the need to specify properties or constructor arguments.
+ (Other mechanisms such as a bean template discussed elsewhere in this chapter are also
+ valuable in this regard.)
+
+ • Autowiring can update a configuration as your objects evolve. For example, if you need to add a
+ dependency to a class, that dependency can be satisfied automatically without you needing to
+ modify the configuration. Thus autowiring can be especially useful during development,
+ without negating the option of switching to explicit wiring when the code base becomes more
+ stable.
+
+
+ When using XML-based configuration metadata (see Dependency Injection), you can specify the
+ autowire mode for a bean definition with the autowire attribute of the element. The
+ autowiring functionality has four modes. You specify autowiring per bean and can thus choose
+ which ones to autowire. The following table describes the four autowiring modes:
+
+
+ Table 2. Autowiring modes
+
+ Mode Explanation
+ no (Default) No autowiring. Bean references must be defined by ref elements.
+ Changing the default setting is not recommended for larger deployments,
+ because specifying collaborators explicitly gives greater control and clarity. To
+ some extent, it documents the structure of a system.
+ byName Autowiring by property name. Spring looks for a bean with the same name as
+ the property that needs to be autowired. For example, if a bean definition is
+ set to autowire by name and it contains a master property (that is, it has a
+ setMaster(..) method), Spring looks for a bean definition named master and
+ uses it to set the property.
+ byType Lets a property be autowired if exactly one bean of the property type exists in
+ the container. If more than one exists, a fatal exception is thrown, which
+ indicates that you may not use byType autowiring for that bean. If there are no
+ matching beans, nothing happens (the property is not set).
+ constructor Analogous to byType but applies to constructor arguments. If there is not
+ exactly one bean of the constructor argument type in the container, a fatal
+ error is raised.
+
+
+ With byType or constructor autowiring mode, you can wire arrays and typed collections. In such
+ cases, all autowire candidates within the container that match the expected type are provided to
+ satisfy the dependency. You can autowire strongly-typed Map instances if the expected key type is
+ String. An autowired Map instance’s values consist of all bean instances that match the expected
+ type, and the Map instance’s keys contain the corresponding bean names.
+
+ Limitations and Disadvantages of Autowiring
+
+ Autowiring works best when it is used consistently across a project. If autowiring is not used in
+ general, it might be confusing to developers to use it to wire only one or two bean definitions.
+
+
+ Consider the limitations and disadvantages of autowiring:
+
+ • Explicit dependencies in property and constructor-arg settings always override autowiring. You
+ cannot autowire simple properties such as primitives, Strings, and Classes (and arrays of such
+ simple properties). This limitation is by-design.
+
+ • Autowiring is less exact than explicit wiring. Although, as noted in the earlier table, Spring is
+ careful to avoid guessing in case of ambiguity that might have unexpected results. The
+ relationships between your Spring-managed objects are no longer documented explicitly.
+
+ • Wiring information may not be available to tools that may generate documentation from a
+ Spring container.
+
+ • Multiple bean definitions within the container may match the type specified by the setter
+ method or constructor argument to be autowired. For arrays, collections, or Map instances, this is
+ not necessarily a problem. However, for dependencies that expect a single value, this ambiguity
+ is not arbitrarily resolved. If no unique bean definition is available, an exception is thrown.
+
+
+ In the latter scenario, you have several options:
+
+ • Abandon autowiring in favor of explicit wiring.
+
+ • Avoid autowiring for a bean definition by setting its autowire-candidate attributes to false, as
+ described in the next section.
+
+ • Designate a single bean definition as the primary candidate by setting the primary attribute of its
+ element to true.
+
+ • Implement the more fine-grained control available with annotation-based configuration, as
+ described in Annotation-based Container Configuration.
+
+
+
+ Excluding a Bean from Autowiring
+
+ On a per-bean basis, you can exclude a bean from autowiring. In Spring’s XML format, set the
+ autowire-candidate attribute of the element to false. The container makes that specific bean
+ definition unavailable to the autowiring infrastructure (including annotation style configurations
+ such as @Autowired).
+
+
+ The autowire-candidate attribute is designed to only affect type-based autowiring.
+ It does not affect explicit references by name, which get resolved even if the
+ specified bean is not marked as an autowire candidate. As a consequence,
+ autowiring by name nevertheless injects a bean if the name matches.
+
+
+ You can also limit autowire candidates based on pattern-matching against bean names. The top-
+ level element accepts one or more patterns within its default-autowire-candidates
+ attribute. For example, to limit autowire candidate status to any bean whose name ends with
+ Repository, provide a value of *Repository. To provide multiple patterns, define them in a comma-
+ separated list. An explicit value of true or false for a bean definition’s autowire-candidate attribute
+
+ always takes precedence. For such beans, the pattern matching rules do not apply.
+
+
+ These techniques are useful for beans that you never want to be injected into other beans by
+ autowiring. It does not mean that an excluded bean cannot itself be configured by using
+ autowiring. Rather, the bean itself is not a candidate for autowiring other beans.
+
+
+ Method Injection
+
+ In most application scenarios, most beans in the container are singletons. When a singleton bean
+ needs to collaborate with another singleton bean or a non-singleton bean needs to collaborate with
+ another non-singleton bean, you typically handle the dependency by defining one bean as a
+ property of the other. A problem arises when the bean lifecycles are different. Suppose singleton
+ bean A needs to use non-singleton (prototype) bean B, perhaps on each method invocation on A.
+ The container creates the singleton bean A only once, and thus only gets one opportunity to set the
+ properties. The container cannot provide bean A with a new instance of bean B every time one is
+ needed.
+
+ A solution is to forego some inversion of control. You can make bean A aware of the container by
+ implementing the ApplicationContextAware interface, and by making a getBean("B") call to the
+ container ask for (a typically new) bean B instance every time bean A needs it. The following
+ example shows this approach:
+
+ Java
+
+
+ // a class that uses a stateful Command-style class to perform some processing
+ package fiona.apple;
+
+
+ // Spring-API imports
+ import org.springframework.beans.BeansException;
+ import org.springframework.context.ApplicationContext;
+ import org.springframework.context.ApplicationContextAware;
+
+
+ public class CommandManager implements ApplicationContextAware {
+
+
+ private ApplicationContext applicationContext;
+
+
+ public Object process(Map commandState) {
+ // grab a new instance of the appropriate Command
+ Command command = createCommand();
+ // set the state on the (hopefully brand new) Command instance
+ command.setState(commandState);
+ return command.execute();
+ }
+
+
+ protected Command createCommand() {
+ // notice the Spring API dependency!
+ return this.applicationContext.getBean("command", Command.class);
+ }
+
+
+ public void setApplicationContext(
+ ApplicationContext applicationContext) throws BeansException {
+ this.applicationContext = applicationContext;
+ }
+ }
+
+ Kotlin
+
+
+ // a class that uses a stateful Command-style class to perform some processing
+ package fiona.apple
+
+
+ // Spring-API imports
+ import org.springframework.context.ApplicationContext
+ import org.springframework.context.ApplicationContextAware
+
+
+ class CommandManager : ApplicationContextAware {
+
+
+ private lateinit var applicationContext: ApplicationContext
+
+
+ fun process(commandState: Map<*, *>): Any {
+ // grab a new instance of the appropriate Command
+ val command = createCommand()
+ // set the state on the (hopefully brand new) Command instance
+ command.state = commandState
+ return command.execute()
+ }
+
+
+ // notice the Spring API dependency!
+ protected fun createCommand() =
+ applicationContext.getBean("command", Command::class.java)
+
+
+ override fun setApplicationContext(applicationContext: ApplicationContext) {
+ this.applicationContext = applicationContext
+ }
+ }
+
+
+
+ The preceding is not desirable, because the business code is aware of and coupled to the Spring
+ Framework. Method Injection, a somewhat advanced feature of the Spring IoC container, lets you
+ handle this use case cleanly.
+
+
+
+ You can read more about the motivation for Method Injection in this blog entry.
+
+
+
+
+
+ Lookup Method Injection
+
+ Lookup method injection is the ability of the container to override methods on container-managed
+ beans and return the lookup result for another named bean in the container. The lookup typically
+ involves a prototype bean, as in the scenario described in the preceding section. The Spring
+ Framework implements this method injection by using bytecode generation from the CGLIB library
+ to dynamically generate a subclass that overrides the method.
+
+ • For this dynamic subclassing to work, the class that the Spring bean container
+ subclasses cannot be final, and the method to be overridden cannot be final,
+ either.
+
+ • Unit-testing a class that has an abstract method requires you to subclass the
+ class yourself and to supply a stub implementation of the abstract method.
+ • Concrete methods are also necessary for component scanning, which requires
+ concrete classes to pick up.
+
+ • A further key limitation is that lookup methods do not work with factory
+ methods and in particular not with @Bean methods in configuration classes,
+ since, in that case, the container is not in charge of creating the instance and
+ therefore cannot create a runtime-generated subclass on the fly.
+
+
+ In the case of the CommandManager class in the previous code snippet, the Spring container
+ dynamically overrides the implementation of the createCommand() method. The CommandManager class
+ does not have any Spring dependencies, as the reworked example shows:
+
+
+ Java
+
+
+ package fiona.apple;
+
+
+ // no more Spring imports!
+
+
+ public abstract class CommandManager {
+
+
+ public Object process(Object commandState) {
+ // grab a new instance of the appropriate Command interface
+ Command command = createCommand();
+ // set the state on the (hopefully brand new) Command instance
+ command.setState(commandState);
+ return command.execute();
+ }
+
+
+ // okay... but where is the implementation of this method?
+ protected abstract Command createCommand();
+ }
+
+ Kotlin
+
+
+ package fiona.apple
+
+
+ // no more Spring imports!
+
+
+ abstract class CommandManager {
+
+
+ fun process(commandState: Any): Any {
+ // grab a new instance of the appropriate Command interface
+ val command = createCommand()
+ // set the state on the (hopefully brand new) Command instance
+ command.state = commandState
+ return command.execute()
+ }
+
+
+ // okay... but where is the implementation of this method?
+ protected abstract fun createCommand(): Command
+ }
+
+
+
+ In the client class that contains the method to be injected (the CommandManager in this case), the
+ method to be injected requires a signature of the following form:
+
+
+
+ [abstract] theMethodName(no-arguments);
+
+
+
+ If the method is abstract, the dynamically-generated subclass implements the method. Otherwise,
+ the dynamically-generated subclass overrides the concrete method defined in the original class.
+ Consider the following example:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ The bean identified as commandManager calls its own createCommand() method whenever it needs a
+ new instance of the myCommand bean. You must be careful to deploy the myCommand bean as a prototype
+ if that is actually what is needed. If it is a singleton, the same instance of the myCommand bean is
+ returned each time.
+
+
+ Alternatively, within the annotation-based component model, you can declare a lookup method
+ through the @Lookup annotation, as the following example shows:
+
+ Java
+
+
+ public abstract class CommandManager {
+
+
+ public Object process(Object commandState) {
+ Command command = createCommand();
+ command.setState(commandState);
+ return command.execute();
+ }
+
+
+ @Lookup("myCommand")
+ protected abstract Command createCommand();
+ }
+
+
+
+ Kotlin
+
+
+ abstract class CommandManager {
+
+
+ fun process(commandState: Any): Any {
+ val command = createCommand()
+ command.state = commandState
+ return command.execute()
+ }
+
+
+ @Lookup("myCommand")
+ protected abstract fun createCommand(): Command
+ }
+
+
+
+ Or, more idiomatically, you can rely on the target bean getting resolved against the declared return
+ type of the lookup method:
+
+
+ Java
+
+
+ public abstract class CommandManager {
+
+
+ public Object process(Object commandState) {
+ Command command = createCommand();
+ command.setState(commandState);
+ return command.execute();
+ }
+
+
+ @Lookup
+ protected abstract Command createCommand();
+ }
+
+ Kotlin
+
+
+ abstract class CommandManager {
+
+
+ fun process(commandState: Any): Any {
+ val command = createCommand()
+ command.state = commandState
+ return command.execute()
+ }
+
+
+ @Lookup
+ protected abstract fun createCommand(): Command
+ }
+
+
+
+ Note that you should typically declare such annotated lookup methods with a concrete stub
+ implementation, in order for them to be compatible with Spring’s component scanning rules where
+ abstract classes get ignored by default. This limitation does not apply to explicitly registered or
+ explicitly imported bean classes.
+
+
+ Another way of accessing differently scoped target beans is an ObjectFactory/
+ Provider injection point. See Scoped Beans as Dependencies.
+
+ You may also find the ServiceLocatorFactoryBean (in the
+ org.springframework.beans.factory.config package) to be useful.
+
+
+
+ Arbitrary Method Replacement
+
+ A less useful form of method injection than lookup method injection is the ability to replace
+ arbitrary methods in a managed bean with another method implementation. You can safely skip
+ the rest of this section until you actually need this functionality.
+
+
+ With XML-based configuration metadata, you can use the replaced-method element to replace an
+ existing method implementation with another, for a deployed bean. Consider the following class,
+ which has a method called computeValue that we want to override:
+
+
+ Java
+
+
+ public class MyValueCalculator {
+
+
+ public String computeValue(String input) {
+ // some real code...
+ }
+
+
+ // some other methods...
+ }
+
+ Kotlin
+
+
+ class MyValueCalculator {
+
+
+ fun computeValue(input: String): String {
+ // some real code...
+ }
+
+
+ // some other methods...
+ }
+
+
+
+ A class that implements the org.springframework.beans.factory.support.MethodReplacer interface
+ provides the new method definition, as the following example shows:
+
+
+ Java
+
+
+ /**
+ * meant to be used to override the existing computeValue(String)
+ * implementation in MyValueCalculator
+ */
+ public class ReplacementComputeValue implements MethodReplacer {
+
+
+ public Object reimplement(Object o, Method m, Object[] args) throws Throwable {
+ // get the input value, work with it, and return a computed result
+ String input = (String) args[0];
+ ...
+ return ...;
+ }
+ }
+
+
+
+ Kotlin
+
+
+ /**
+ * meant to be used to override the existing computeValue(String)
+ * implementation in MyValueCalculator
+ */
+ class ReplacementComputeValue : MethodReplacer {
+
+
+ override fun reimplement(obj: Any, method: Method, args: Array): Any {
+ // get the input value, work with it, and return a computed result
+ val input = args[0] as String;
+ ...
+ return ...;
+ }
+ }
+
+
+
+ The bean definition to deploy the original class and specify the method override would resemble
+ the following example:
+
+
+
+
+ String
+
+
+
+
+
+
+
+
+ You can use one or more elements within the element to indicate
+ the method signature of the method being overridden. The signature for the arguments is
+ necessary only if the method is overloaded and multiple variants exist within the class. For
+ convenience, the type string for an argument may be a substring of the fully qualified type name.
+ For example, the following all match java.lang.String:
+
+
+
+ java.lang.String
+ String
+ Str
+
+
+
+ Because the number of arguments is often enough to distinguish between each possible choice, this
+ shortcut can save a lot of typing, by letting you type only the shortest string that matches an
+ argument type.
+
+ 2.1.5. Bean Scopes
+
+ When you create a bean definition, you create a recipe for creating actual instances of the class
+ defined by that bean definition. The idea that a bean definition is a recipe is important, because it
+ means that, as with a class, you can create many object instances from a single recipe.
+
+
+ You can control not only the various dependencies and configuration values that are to be plugged
+ into an object that is created from a particular bean definition but also control the scope of the
+ objects created from a particular bean definition. This approach is powerful and flexible, because
+ you can choose the scope of the objects you create through configuration instead of having to bake
+ in the scope of an object at the Java class level. Beans can be defined to be deployed in one of a
+ number of scopes. The Spring Framework supports six scopes, four of which are available only if
+ you use a web-aware ApplicationContext. You can also create a custom scope.
+
+ The following table describes the supported scopes:
+
+
+ Table 3. Bean scopes
+
+ Scope Description
+
+ singleton (Default) Scopes a single bean definition to a single object instance for each
+ Spring IoC container.
+
+ prototype Scopes a single bean definition to any number of object instances.
+
+ Scope Description
+
+ request Scopes a single bean definition to the lifecycle of a single HTTP request. That
+ is, each HTTP request has its own instance of a bean created off the back of a
+ single bean definition. Only valid in the context of a web-aware Spring
+ ApplicationContext.
+
+ session Scopes a single bean definition to the lifecycle of an HTTP Session. Only valid
+ in the context of a web-aware Spring ApplicationContext.
+
+ application Scopes a single bean definition to the lifecycle of a ServletContext. Only valid
+ in the context of a web-aware Spring ApplicationContext.
+
+ websocket Scopes a single bean definition to the lifecycle of a WebSocket. Only valid in the
+ context of a web-aware Spring ApplicationContext.
+
+
+
+ As of Spring 3.0, a thread scope is available but is not registered by default. For
+ more information, see the documentation for SimpleThreadScope. For instructions
+ on how to register this or any other custom scope, see Using a Custom Scope.
+
+
+
+ The Singleton Scope
+
+ Only one shared instance of a singleton bean is managed, and all requests for beans with an ID or
+ IDs that match that bean definition result in that one specific bean instance being returned by the
+ Spring container.
+
+
+ To put it another way, when you define a bean definition and it is scoped as a singleton, the Spring
+ IoC container creates exactly one instance of the object defined by that bean definition. This single
+ instance is stored in a cache of such singleton beans, and all subsequent requests and references
+ for that named bean return the cached object. The following image shows how the singleton scope
+ works:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Spring’s concept of a singleton bean differs from the singleton pattern as defined in the Gang of
+ Four (GoF) patterns book. The GoF singleton hard-codes the scope of an object such that one and
+
+ only one instance of a particular class is created per ClassLoader. The scope of the Spring singleton
+ is best described as being per-container and per-bean. This means that, if you define one bean for a
+ particular class in a single Spring container, the Spring container creates one and only one instance
+ of the class defined by that bean definition. The singleton scope is the default scope in Spring. To
+ define a bean as a singleton in XML, you can define a bean as shown in the following example:
+
+
+
+
+
+
+
+
+
+
+
+
+ The Prototype Scope
+
+ The non-singleton prototype scope of bean deployment results in the creation of a new bean
+ instance every time a request for that specific bean is made. That is, the bean is injected into
+ another bean or you request it through a getBean() method call on the container. As a rule, you
+ should use the prototype scope for all stateful beans and the singleton scope for stateless beans.
+
+
+ The following diagram illustrates the Spring prototype scope:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ (A data access object (DAO) is not typically configured as a prototype, because a typical DAO does
+ not hold any conversational state. It was easier for us to reuse the core of the singleton diagram.)
+
+ The following example defines a bean as a prototype in XML:
+
+
+
+
+
+
+
+ In contrast to the other scopes, Spring does not manage the complete lifecycle of a prototype bean.
+
+ The container instantiates, configures, and otherwise assembles a prototype object and hands it to
+ the client, with no further record of that prototype instance. Thus, although initialization lifecycle
+ callback methods are called on all objects regardless of scope, in the case of prototypes, configured
+ destruction lifecycle callbacks are not called. The client code must clean up prototype-scoped
+ objects and release expensive resources that the prototype beans hold. To get the Spring container
+ to release resources held by prototype-scoped beans, try using a custom bean post-processor, which
+ holds a reference to beans that need to be cleaned up.
+
+
+ In some respects, the Spring container’s role in regard to a prototype-scoped bean is a replacement
+ for the Java new operator. All lifecycle management past that point must be handled by the client.
+ (For details on the lifecycle of a bean in the Spring container, see Lifecycle Callbacks.)
+
+
+ Singleton Beans with Prototype-bean Dependencies
+
+ When you use singleton-scoped beans with dependencies on prototype beans, be aware that
+ dependencies are resolved at instantiation time. Thus, if you dependency-inject a prototype-scoped
+ bean into a singleton-scoped bean, a new prototype bean is instantiated and then dependency-
+ injected into the singleton bean. The prototype instance is the sole instance that is ever supplied to
+ the singleton-scoped bean.
+
+
+ However, suppose you want the singleton-scoped bean to acquire a new instance of the prototype-
+ scoped bean repeatedly at runtime. You cannot dependency-inject a prototype-scoped bean into
+ your singleton bean, because that injection occurs only once, when the Spring container
+ instantiates the singleton bean and resolves and injects its dependencies. If you need a new
+ instance of a prototype bean at runtime more than once, see Method Injection.
+
+
+ Request, Session, Application, and WebSocket Scopes
+
+ The request, session, application, and websocket scopes are available only if you use a web-aware
+ Spring ApplicationContext implementation (such as XmlWebApplicationContext). If you use these
+ scopes with regular Spring IoC containers, such as the ClassPathXmlApplicationContext, an
+ IllegalStateException that complains about an unknown bean scope is thrown.
+
+
+
+ Initial Web Configuration
+
+ To support the scoping of beans at the request, session, application, and websocket levels (web-
+ scoped beans), some minor initial configuration is required before you define your beans. (This
+ initial setup is not required for the standard scopes: singleton and prototype.)
+
+
+ How you accomplish this initial setup depends on your particular Servlet environment.
+
+
+ If you access scoped beans within Spring Web MVC, in effect, within a request that is processed by
+ the Spring DispatcherServlet, no special setup is necessary. DispatcherServlet already exposes all
+ relevant state.
+
+
+ If you use a Servlet web container, with requests processed outside of Spring’s DispatcherServlet
+ (for example, when using JSF or Struts), you need to register the
+ org.springframework.web.context.request.RequestContextListener ServletRequestListener. This can
+ be done programmatically by using the WebApplicationInitializer interface. Alternatively, add the
+ following declaration to your web application’s web.xml file:
+
+
+ ...
+
+
+ org.springframework.web.context.request.RequestContextListener
+
+
+ ...
+
+
+
+
+ Alternatively, if there are issues with your listener setup, consider using Spring’s
+ RequestContextFilter. The filter mapping depends on the surrounding web application
+ configuration, so you have to change it as appropriate. The following listing shows the filter part of
+ a web application:
+
+
+
+
+ ...
+
+ requestContextFilter
+ org.springframework.web.filter.RequestContextFilter
+
+
+ requestContextFilter
+ /*
+
+ ...
+
+
+
+
+ DispatcherServlet, RequestContextListener, and RequestContextFilter all do exactly the same thing,
+ namely bind the HTTP request object to the Thread that is servicing that request. This makes beans
+ that are request- and session-scoped available further down the call chain.
+
+
+
+ Request scope
+
+ Consider the following XML configuration for a bean definition:
+
+
+
+
+
+
+
+ The Spring container creates a new instance of the LoginAction bean by using the loginAction bean
+ definition for each and every HTTP request. That is, the loginAction bean is scoped at the HTTP
+ request level. You can change the internal state of the instance that is created as much as you want,
+ because other instances created from the same loginAction bean definition do not see these
+ changes in state. They are particular to an individual request. When the request completes
+ processing, the bean that is scoped to the request is discarded.
+
+
+ When using annotation-driven components or Java configuration, the @RequestScope annotation can
+
+ be used to assign a component to the request scope. The following example shows how to do so:
+
+
+ Java
+
+
+ @RequestScope
+ @Component
+ public class LoginAction {
+ // ...
+ }
+
+
+
+ Kotlin
+
+
+ @RequestScope
+ @Component
+ class LoginAction {
+ // ...
+ }
+
+
+
+
+ Session Scope
+
+ Consider the following XML configuration for a bean definition:
+
+
+
+
+
+
+
+ The Spring container creates a new instance of the UserPreferences bean by using the
+ userPreferences bean definition for the lifetime of a single HTTP Session. In other words, the
+ userPreferences bean is effectively scoped at the HTTP Session level. As with request-scoped beans,
+ you can change the internal state of the instance that is created as much as you want, knowing that
+ other HTTP Session instances that are also using instances created from the same userPreferences
+ bean definition do not see these changes in state, because they are particular to an individual HTTP
+ Session. When the HTTP Session is eventually discarded, the bean that is scoped to that particular
+ HTTP Session is also discarded.
+
+
+ When using annotation-driven components or Java configuration, you can use the @SessionScope
+ annotation to assign a component to the session scope.
+
+
+ Java
+
+
+ @SessionScope
+ @Component
+ public class UserPreferences {
+ // ...
+ }
+
+ Kotlin
+
+
+ @SessionScope
+ @Component
+ class UserPreferences {
+ // ...
+ }
+
+
+
+
+ Application Scope
+
+ Consider the following XML configuration for a bean definition:
+
+
+
+
+
+
+
+ The Spring container creates a new instance of the AppPreferences bean by using the appPreferences
+ bean definition once for the entire web application. That is, the appPreferences bean is scoped at the
+ ServletContext level and stored as a regular ServletContext attribute. This is somewhat similar to a
+ Spring singleton bean but differs in two important ways: It is a singleton per ServletContext, not per
+ Spring ApplicationContext (for which there may be several in any given web application), and it is
+ actually exposed and therefore visible as a ServletContext attribute.
+
+
+ When using annotation-driven components or Java configuration, you can use the
+ @ApplicationScope annotation to assign a component to the application scope. The following
+ example shows how to do so:
+
+
+ Java
+
+
+ @ApplicationScope
+ @Component
+ public class AppPreferences {
+ // ...
+ }
+
+
+
+ Kotlin
+
+
+ @ApplicationScope
+ @Component
+ class AppPreferences {
+ // ...
+ }
+
+
+
+
+ WebSocket Scope
+
+ WebSocket scope is associated with the lifecycle of a WebSocket session and applies to STOMP over
+ WebSocket applications, see WebSocket scope for more details.
+
+ Scoped Beans as Dependencies
+
+ The Spring IoC container manages not only the instantiation of your objects (beans), but also the
+ wiring up of collaborators (or dependencies). If you want to inject (for example) an HTTP request-
+ scoped bean into another bean of a longer-lived scope, you may choose to inject an AOP proxy in
+ place of the scoped bean. That is, you need to inject a proxy object that exposes the same public
+ interface as the scoped object but that can also retrieve the real target object from the relevant
+ scope (such as an HTTP request) and delegate method calls onto the real object.
+
+
+ You may also use between beans that are scoped as singleton,
+ with the reference then going through an intermediate proxy that is serializable
+ and therefore able to re-obtain the target singleton bean on deserialization.
+
+
+ When declaring against a bean of scope prototype, every
+ method call on the shared proxy leads to the creation of a new target instance to
+ which the call is then being forwarded.
+
+ Also, scoped proxies are not the only way to access beans from shorter scopes in a
+ lifecycle-safe fashion. You may also declare your injection point (that is, the
+ constructor or setter argument or autowired field) as ObjectFactory,
+ allowing for a getObject() call to retrieve the current instance on demand every
+ time it is needed — without holding on to the instance or storing it separately.
+
+
+ As an extended variant, you may declare ObjectProvider which
+ delivers several additional access variants, including getIfAvailable and
+ getIfUnique.
+
+
+ The JSR-330 variant of this is called Provider and is used with a
+ Provider declaration and a corresponding get() call for every
+ retrieval attempt. See here for more details on JSR-330 overall.
+
+
+ The configuration in the following example is only one line, but it is important to understand the
+ “why” as well as the “how” behind it:
+
+
+
+
+
+
+
+
+ ①
+
+
+
+
+
+
+
+
+
+
+
+ ① The line that defines the proxy.
+
+ To create such a proxy, you insert a child element into a scoped bean definition
+ (see Choosing the Type of Proxy to Create and XML Schema-based configuration). Why do
+ definitions of beans scoped at the request, session and custom-scope levels require the element? Consider the following singleton bean definition and contrast it with what you
+ need to define for the aforementioned scopes (note that the following userPreferences bean
+ definition as it stands is incomplete):
+
+
+
+
+
+
+
+
+
+
+
+
+ In the preceding example, the singleton bean (userManager) is injected with a reference to the HTTP
+ Session-scoped bean (userPreferences). The salient point here is that the userManager bean is a
+ singleton: it is instantiated exactly once per container, and its dependencies (in this case only one,
+ the userPreferences bean) are also injected only once. This means that the userManager bean
+ operates only on the exact same userPreferences object (that is, the one with which it was originally
+ injected).
+
+ This is not the behavior you want when injecting a shorter-lived scoped bean into a longer-lived
+ scoped bean (for example, injecting an HTTP Session-scoped collaborating bean as a dependency
+ into singleton bean). Rather, you need a single userManager object, and, for the lifetime of an HTTP
+ Session, you need a userPreferences object that is specific to the HTTP Session. Thus, the container
+
+ creates an object that exposes the exact same public interface as the UserPreferences class (ideally
+ an object that is a UserPreferences instance), which can fetch the real UserPreferences object from
+ the scoping mechanism (HTTP request, Session, and so forth). The container injects this proxy
+ object into the userManager bean, which is unaware that this UserPreferences reference is a proxy. In
+ this example, when a UserManager instance invokes a method on the dependency-injected
+ UserPreferences object, it is actually invoking a method on the proxy. The proxy then fetches the
+ real UserPreferences object from (in this case) the HTTP Session and delegates the method
+ invocation onto the retrieved real UserPreferences object.
+
+ Thus, you need the following (correct and complete) configuration when injecting request- and
+ session-scoped beans into collaborating objects, as the following example shows:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Choosing the Type of Proxy to Create
+
+ By default, when the Spring container creates a proxy for a bean that is marked up with the
+ element, a CGLIB-based class proxy is created.
+
+
+ CGLIB proxies intercept only public method calls! Do not call non-public methods
+ on such a proxy. They are not delegated to the actual scoped target object.
+
+
+ Alternatively, you can configure the Spring container to create standard JDK interface-based
+ proxies for such scoped beans, by specifying false for the value of the proxy-target-class attribute
+ of the element. Using JDK interface-based proxies means that you do not need
+ additional libraries in your application classpath to affect such proxying. However, it also means
+ that the class of the scoped bean must implement at least one interface and that all collaborators
+ into which the scoped bean is injected must reference the bean through one of its interfaces. The
+ following example shows a proxy based on an interface:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ For more detailed information about choosing class-based or interface-based proxying, see
+ Proxying Mechanisms.
+
+ Custom Scopes
+
+ The bean scoping mechanism is extensible. You can define your own scopes or even redefine
+ existing scopes, although the latter is considered bad practice and you cannot override the built-in
+ singleton and prototype scopes.
+
+
+
+ Creating a Custom Scope
+
+ To integrate your custom scopes into the Spring container, you need to implement the
+ org.springframework.beans.factory.config.Scope interface, which is described in this section. For an
+ idea of how to implement your own scopes, see the Scope implementations that are supplied with
+ the Spring Framework itself and the Scope javadoc, which explains the methods you need to
+ implement in more detail.
+
+
+ The Scope interface has four methods to get objects from the scope, remove them from the scope,
+ and let them be destroyed.
+
+
+ The session scope implementation, for example, returns the session-scoped bean (if it does not
+ exist, the method returns a new instance of the bean, after having bound it to the session for future
+ reference). The following method returns the object from the underlying scope:
+
+
+ Java
+
+
+ Object get(String name, ObjectFactory> objectFactory)
+
+
+
+ Kotlin
+
+
+ fun get(name: String, objectFactory: ObjectFactory<*>): Any
+
+
+
+ The session scope implementation, for example, removes the session-scoped bean from the
+ underlying session. The object should be returned, but you can return null if the object with the
+ specified name is not found. The following method removes the object from the underlying scope:
+
+
+ Java
+
+
+ Object remove(String name)
+
+
+
+ Kotlin
+
+
+ fun remove(name: String): Any
+
+
+
+ The following method registers a callback that the scope should invoke when it is destroyed or
+ when the specified object in the scope is destroyed:
+
+
+ Java
+
+
+ void registerDestructionCallback(String name, Runnable destructionCallback)
+
+ Kotlin
+
+
+ fun registerDestructionCallback(name: String, destructionCallback: Runnable)
+
+
+
+ See the javadoc or a Spring scope implementation for more information on destruction callbacks.
+
+ The following method obtains the conversation identifier for the underlying scope:
+
+
+ Java
+
+
+ String getConversationId()
+
+
+
+ Kotlin
+
+
+ fun getConversationId(): String
+
+
+
+ This identifier is different for each scope. For a session scoped implementation, this identifier can
+ be the session identifier.
+
+
+
+ Using a Custom Scope
+
+ After you write and test one or more custom Scope implementations, you need to make the Spring
+ container aware of your new scopes. The following method is the central method to register a new
+ Scope with the Spring container:
+
+
+ Java
+
+
+ void registerScope(String scopeName, Scope scope);
+
+
+
+ Kotlin
+
+
+ fun registerScope(scopeName: String, scope: Scope)
+
+
+
+ This method is declared on the ConfigurableBeanFactory interface, which is available through the
+ BeanFactory property on most of the concrete ApplicationContext implementations that ship with
+ Spring.
+
+
+ The first argument to the registerScope(..) method is the unique name associated with a scope.
+ Examples of such names in the Spring container itself are singleton and prototype. The second
+ argument to the registerScope(..) method is an actual instance of the custom Scope
+ implementation that you wish to register and use.
+
+ Suppose that you write your custom Scope implementation, and then register it as shown in the
+ next example.
+
+ The next example uses SimpleThreadScope, which is included with Spring but is not
+ registered by default. The instructions would be the same for your own custom
+ Scope implementations.
+
+
+ Java
+
+
+ Scope threadScope = new SimpleThreadScope();
+ beanFactory.registerScope("thread", threadScope);
+
+
+
+ Kotlin
+
+
+ val threadScope = SimpleThreadScope()
+ beanFactory.registerScope("thread", threadScope)
+
+
+
+ You can then create bean definitions that adhere to the scoping rules of your custom Scope, as
+ follows:
+
+
+
+
+
+
+
+ With a custom Scope implementation, you are not limited to programmatic registration of the scope.
+ You can also do the Scope registration declaratively, by using the CustomScopeConfigurer class, as the
+ following example shows:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ When you place within a declaration for a FactoryBean
+ implementation, it is the factory bean itself that is scoped, not the object returned
+ from getObject().
+
+
+ 2.1.6. Customizing the Nature of a Bean
+
+ The Spring Framework provides a number of interfaces you can use to customize the nature of a
+ bean. This section groups them as follows:
+
+ • Lifecycle Callbacks
+
+ • ApplicationContextAware and BeanNameAware
+
+ • Other Aware Interfaces
+
+
+ Lifecycle Callbacks
+
+ To interact with the container’s management of the bean lifecycle, you can implement the Spring
+ InitializingBean and DisposableBean interfaces. The container calls afterPropertiesSet() for the
+
+ former and destroy() for the latter to let the bean perform certain actions upon initialization and
+ destruction of your beans.
+
+
+ The JSR-250 @PostConstruct and @PreDestroy annotations are generally considered
+ best practice for receiving lifecycle callbacks in a modern Spring application. Using
+ these annotations means that your beans are not coupled to Spring-specific
+ interfaces. For details, see Using @PostConstruct and @PreDestroy.
+
+
+ If you do not want to use the JSR-250 annotations but you still want to remove
+ coupling, consider init-method and destroy-method bean definition metadata.
+
+
+ Internally, the Spring Framework uses BeanPostProcessor implementations to process any callback
+ interfaces it can find and call the appropriate methods. If you need custom features or other
+ lifecycle behavior Spring does not by default offer, you can implement a BeanPostProcessor yourself.
+ For more information, see Container Extension Points.
+
+
+ In addition to the initialization and destruction callbacks, Spring-managed objects may also
+ implement the Lifecycle interface so that those objects can participate in the startup and shutdown
+ process, as driven by the container’s own lifecycle.
+
+
+ The lifecycle callback interfaces are described in this section.
+
+
+
+ Initialization Callbacks
+
+ The org.springframework.beans.factory.InitializingBean interface lets a bean perform
+ initialization work after the container has set all necessary properties on the bean. The
+ InitializingBean interface specifies a single method:
+
+
+
+ void afterPropertiesSet() throws Exception;
+
+
+
+ We recommend that you do not use the InitializingBean interface, because it unnecessarily couples
+ the code to Spring. Alternatively, we suggest using the @PostConstruct annotation or specifying a
+ POJO initialization method. In the case of XML-based configuration metadata, you can use the init-
+ method attribute to specify the name of the method that has a void no-argument signature. With
+ Java configuration, you can use the initMethod attribute of @Bean. See Receiving Lifecycle Callbacks.
+ Consider the following example:
+
+
+
+
+
+
+
+ Java
+
+
+ public class ExampleBean {
+
+
+ public void init() {
+ // do some initialization work
+ }
+ }
+
+ Kotlin
+
+
+ class ExampleBean {
+
+
+ fun init() {
+ // do some initialization work
+ }
+ }
+
+
+
+ The preceding example has almost exactly the same effect as the following example (which consists
+ of two listings):
+
+
+
+
+
+
+
+ Java
+
+
+ public class AnotherExampleBean implements InitializingBean {
+
+
+ @Override
+ public void afterPropertiesSet() {
+ // do some initialization work
+ }
+ }
+
+
+
+ Kotlin
+
+
+ class AnotherExampleBean : InitializingBean {
+
+
+ override fun afterPropertiesSet() {
+ // do some initialization work
+ }
+ }
+
+
+
+ However, the first of the two preceding examples does not couple the code to Spring.
+
+
+
+ Destruction Callbacks
+
+ Implementing the org.springframework.beans.factory.DisposableBean interface lets a bean get a
+ callback when the container that contains it is destroyed. The DisposableBean interface specifies a
+ single method:
+
+
+
+ void destroy() throws Exception;
+
+
+
+ We recommend that you do not use the DisposableBean callback interface, because it unnecessarily
+ couples the code to Spring. Alternatively, we suggest using the @PreDestroy annotation or specifying
+ a generic method that is supported by bean definitions. With XML-based configuration metadata,
+ you can use the destroy-method attribute on the . With Java configuration, you can use the
+
+ destroyMethod attribute of @Bean. See Receiving Lifecycle Callbacks. Consider the following
+ definition:
+
+
+
+
+
+
+
+ Java
+
+
+ public class ExampleBean {
+
+
+ public void cleanup() {
+ // do some destruction work (like releasing pooled connections)
+ }
+ }
+
+
+
+ Kotlin
+
+
+ class ExampleBean {
+
+
+ fun cleanup() {
+ // do some destruction work (like releasing pooled connections)
+ }
+ }
+
+
+
+ The preceding definition has almost exactly the same effect as the following definition:
+
+
+
+
+
+
+
+ Java
+
+
+ public class AnotherExampleBean implements DisposableBean {
+
+
+ @Override
+ public void destroy() {
+ // do some destruction work (like releasing pooled connections)
+ }
+ }
+
+
+
+ Kotlin
+
+
+ class AnotherExampleBean : DisposableBean {
+
+
+ override fun destroy() {
+ // do some destruction work (like releasing pooled connections)
+ }
+ }
+
+ However, the first of the two preceding definitions does not couple the code to Spring.
+
+
+ You can assign the destroy-method attribute of a element a special (inferred)
+ value, which instructs Spring to automatically detect a public close or shutdown
+ method on the specific bean class. (Any class that implements
+ java.lang.AutoCloseable or java.io.Closeable would therefore match.) You can
+ also set this special (inferred) value on the default-destroy-method attribute of a
+ element to apply this behavior to an entire set of beans (see Default
+ Initialization and Destroy Methods). Note that this is the default behavior with Java
+ configuration.
+
+
+
+ Default Initialization and Destroy Methods
+
+ When you write initialization and destroy method callbacks that do not use the Spring-specific
+ InitializingBean and DisposableBean callback interfaces, you typically write methods with names
+ such as init(), initialize(), dispose(), and so on. Ideally, the names of such lifecycle callback
+ methods are standardized across a project so that all developers use the same method names and
+ ensure consistency.
+
+
+ You can configure the Spring container to “look” for named initialization and destroy callback
+ method names on every bean. This means that you, as an application developer, can write your
+ application classes and use an initialization callback called init(), without having to configure an
+ init-method="init" attribute with each bean definition. The Spring IoC container calls that method
+ when the bean is created (and in accordance with the standard lifecycle callback contract described
+ previously). This feature also enforces a consistent naming convention for initialization and destroy
+ method callbacks.
+
+ Suppose that your initialization callback methods are named init() and your destroy callback
+ methods are named destroy(). Your class then resembles the class in the following example:
+
+
+ Java
+
+
+ public class DefaultBlogService implements BlogService {
+
+
+ private BlogDao blogDao;
+
+
+ public void setBlogDao(BlogDao blogDao) {
+ this.blogDao = blogDao;
+ }
+
+
+ // this is (unsurprisingly) the initialization callback method
+ public void init() {
+ if (this.blogDao == null) {
+ throw new IllegalStateException("The [blogDao] property must be set.");
+ }
+ }
+ }
+
+ Kotlin
+
+
+ class DefaultBlogService : BlogService {
+
+
+ private var blogDao: BlogDao? = null
+
+
+ // this is (unsurprisingly) the initialization callback method
+ fun init() {
+ if (blogDao == null) {
+ throw IllegalStateException("The [blogDao] property must be set.")
+ }
+ }
+ }
+
+
+
+ You could then use that class in a bean resembling the following:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ The presence of the default-init-method attribute on the top-level element attribute causes
+ the Spring IoC container to recognize a method called init on the bean class as the initialization
+ method callback. When a bean is created and assembled, if the bean class has such a method, it is
+ invoked at the appropriate time.
+
+
+ You can configure destroy method callbacks similarly (in XML, that is) by using the default-
+ destroy-method attribute on the top-level element.
+
+
+ Where existing bean classes already have callback methods that are named at variance with the
+ convention, you can override the default by specifying (in XML, that is) the method name by using
+ the init-method and destroy-method attributes of the itself.
+
+
+ The Spring container guarantees that a configured initialization callback is called immediately after
+ a bean is supplied with all dependencies. Thus, the initialization callback is called on the raw bean
+ reference, which means that AOP interceptors and so forth are not yet applied to the bean. A target
+ bean is fully created first and then an AOP proxy (for example) with its interceptor chain is applied.
+ If the target bean and the proxy are defined separately, your code can even interact with the raw
+ target bean, bypassing the proxy. Hence, it would be inconsistent to apply the interceptors to the
+ init method, because doing so would couple the lifecycle of the target bean to its proxy or
+ interceptors and leave strange semantics when your code interacts directly with the raw target
+ bean.
\ No newline at end of file
diff --git a/models/spring-ai-openai-official/src/test/script/deploy-azure-openai-models.sh b/models/spring-ai-openai-official/src/test/script/deploy-azure-openai-models.sh
new file mode 100755
index 00000000000..3bd74b4710d
--- /dev/null
+++ b/models/spring-ai-openai-official/src/test/script/deploy-azure-openai-models.sh
@@ -0,0 +1,116 @@
+#!/usr/bin/env bash
+
+# Execute this script to deploy the needed Azure OpenAI models to execute the integration tests.
+#
+# For this, you need to have Azure CLI installed: https://learn.microsoft.com/cli/azure/install-azure-cli
+#
+# Azure CLI runs on:
+# - Windows (using Windows Command Prompt (CMD), PowerShell, or Windows Subsystem for Linux (WSL)): https://learn.microsoft.com/cli/azure/install-azure-cli-windows
+# - macOS: https://learn.microsoft.com/cli/azure/install-azure-cli-macos
+# - Linux: https://learn.microsoft.com/cli/azure/install-azure-cli-linux
+# - Docker: https://learn.microsoft.com/cli/azure/run-azure-cli-docker
+#
+# Once installed, you can run the following commands to check your installation is correct:
+# az --version
+# az --help
+
+echo "Setting up environment variables..."
+echo "----------------------------------"
+PROJECT="spring-ai-open-ai-official-$RANDOM-$RANDOM-$RANDOM"
+RESOURCE_GROUP="rg-$PROJECT"
+LOCATION="eastus"
+AI_SERVICE="ai-$PROJECT"
+TAG="$PROJECT"
+
+echo "Creating the resource group..."
+echo "------------------------------"
+az group create \
+ --name "$RESOURCE_GROUP" \
+ --location "$LOCATION" \
+ --tags system="$TAG"
+
+# If you want to know the available SKUs, run the following Azure CLI command:
+# az cognitiveservices account list-skus --location "$LOCATION" -o table
+
+echo "Creating the Cognitive Service..."
+echo "---------------------------------"
+az cognitiveservices account create \
+ --name "$AI_SERVICE" \
+ --resource-group "$RESOURCE_GROUP" \
+ --location "$LOCATION" \
+ --custom-domain "$AI_SERVICE" \
+ --tags system="$TAG" \
+ --kind "OpenAI" \
+ --sku "S0"
+
+# If you want to know the available models, run the following Azure CLI command:
+# az cognitiveservices account list-models --resource-group "$RESOURCE_GROUP" --name "$AI_SERVICE" -o table
+
+echo "Deploying Embedding Models"
+echo "=========================="
+
+models=("text-embedding-ada-002" "text-embedding-3-small" "text-embedding-3-large")
+versions=("2" "1" "1")
+skus=("Standard" "Standard" "Standard")
+
+for i in "${!models[@]}"; do
+ model="${models[$i]}"
+ sku="${skus[$i]}"
+ version="${versions[$i]}"
+ echo "Deploying $model..."
+ az cognitiveservices account deployment create \
+ --name "$AI_SERVICE" \
+ --resource-group "$RESOURCE_GROUP" \
+ --deployment-name "$model" \
+ --model-name "$model" \
+ --model-version "$version"\
+ --model-format "OpenAI" \
+ --sku-capacity 1 \
+ --sku-name "$sku" || echo "Failed to deploy $model. Check SKU and region compatibility."
+done
+
+echo "Deploying Image Models"
+echo "=========================="
+
+models=("dall-e-3")
+versions=("3.0")
+skus=("Standard")
+
+for i in "${!models[@]}"; do
+ model="${models[$i]}"
+ sku="${skus[$i]}"
+ version="${versions[$i]}"
+ echo "Deploying $model..."
+ az cognitiveservices account deployment create \
+ --name "$AI_SERVICE" \
+ --resource-group "$RESOURCE_GROUP" \
+ --deployment-name "$model" \
+ --model-name "$model" \
+ --model-version "$version"\
+ --model-format "OpenAI" \
+ --sku-capacity 1 \
+ --sku-name "$sku" || echo "Failed to deploy $model. Check SKU and region compatibility."
+done
+
+echo "Storing the key and endpoint in environment variables..."
+echo "--------------------------------------------------------"
+OPENAI_API_KEY=$(
+ az cognitiveservices account keys list \
+ --name "$AI_SERVICE" \
+ --resource-group "$RESOURCE_GROUP" \
+ | jq -r .key1
+ )
+OPENAI_BASE_URL=$(
+ az cognitiveservices account show \
+ --name "$AI_SERVICE" \
+ --resource-group "$RESOURCE_GROUP" \
+ | jq -r .properties.endpoint
+ )
+
+echo "OPENAI_API_KEY=$OPENAI_API_KEY"
+echo "OPENAI_BASE_URL=$OPENAI_BASE_URL"
+
+# Once you finish the tests, you can delete the resource group with the following command:
+#echo "Deleting the resource group..."
+#echo "------------------------------"
+#az group delete --name "$RESOURCE_GROUP" --yes
diff --git a/pom.xml b/pom.xml
index abb2bc543a3..eaac072358e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -178,6 +178,7 @@
models/spring-ai-oci-genai
models/spring-ai-ollama
models/spring-ai-openai
+ models/spring-ai-openai-official
models/spring-ai-postgresml
models/spring-ai-stability-ai
models/spring-ai-transformers
@@ -272,6 +273,7 @@
3.5.6
4.3.4
1.0.0-beta.16
+ 4.5.0
1.1.0
4.37.0
1.9.25
@@ -739,6 +741,7 @@
org.springframework.ai.mistralai/**/*IT.java
org.springframework.ai.oci/**/*IT.java
org.springframework.ai.ollama/**/*IT.java
+ org.springframework.ai.openaiofficial/**/*IT.java
org.springframework.ai.postgresml/**/*IT.java
org.springframework.ai.stabilityai/**/*IT.java
org.springframework.ai.transformers/**/*IT.java
diff --git a/spring-ai-commons/src/main/java/org/springframework/ai/observation/conventions/AiProvider.java b/spring-ai-commons/src/main/java/org/springframework/ai/observation/conventions/AiProvider.java
index 88105725a69..d78522580dc 100644
--- a/spring-ai-commons/src/main/java/org/springframework/ai/observation/conventions/AiProvider.java
+++ b/spring-ai-commons/src/main/java/org/springframework/ai/observation/conventions/AiProvider.java
@@ -85,6 +85,11 @@ public enum AiProvider {
*/
OPENAI("openai"),
+ /**
+ * AI system provided by the official OpenAI SDK.
+ */
+ OPENAI_OFFICIAL("openai_official"),
+
/**
* AI system provided by Spring AI.
*/