From e44be0b1a14b852129bc483948219e73eb0ab507 Mon Sep 17 00:00:00 2001 From: Soby Chacko Date: Tue, 13 Aug 2024 12:01:33 -0400 Subject: [PATCH] Add Batching strategy for embedding documents - When embedding documents, allow batching the documents using some criteria. - `BatchingStrategy` interface with a `TokenCountBatchingStrategy` implementation that uses the openai max input token size of 8191 as the default. - Add a default method in EmbeddingModel to embed document using this new batching strategy. - Change `MilvusVectorStore` to make use of this new batching API. - Adding unit tests for `TokenCountBatchingStrategy`. - Adding openai integration test to call the embed API that uses batching. Resolves https://github.com/spring-projects/spring-ai/issues/1214 Other vector stores will be updated seperately --- .../ai/openai/embedding/EmbeddingIT.java | 34 +- .../src/test/resources/text_source.txt | 4124 +++++++++++++++++ .../ai/embedding/BatchingStrategy.java | 39 + .../ai/embedding/EmbeddingModel.java | 37 + .../embedding/TokenCountBatchingStrategy.java | 105 + .../tokenizer/JTokkitTokenCountEstimator.java | 12 +- .../TokenCountBatchingStrategyTests.java | 57 + .../MilvusVectorStoreAutoConfiguration.java | 21 +- .../ai/vectorstore/MilvusVectorStore.java | 76 +- .../MilvusEmbeddingDimensionsTests.java | 8 +- .../ai/vectorstore/MilvusVectorStoreIT.java | 3 +- .../MilvusVectorStoreObservationIT.java | 4 +- 12 files changed, 4471 insertions(+), 49 deletions(-) create mode 100644 models/spring-ai-openai/src/test/resources/text_source.txt create mode 100644 spring-ai-core/src/main/java/org/springframework/ai/embedding/BatchingStrategy.java create mode 100644 spring-ai-core/src/main/java/org/springframework/ai/embedding/TokenCountBatchingStrategy.java create mode 100644 spring-ai-core/src/test/java/org/springframework/ai/embedding/TokenCountBatchingStrategyTests.java diff --git a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/embedding/EmbeddingIT.java b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/embedding/EmbeddingIT.java index b15471c6519..079990e1a0c 100644 --- a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/embedding/EmbeddingIT.java +++ b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/embedding/EmbeddingIT.java @@ -1,5 +1,5 @@ /* - * Copyright 2023 - 2024 the original author or authors. + * Copyright 2023-2024 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. @@ -18,23 +18,33 @@ 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.openai.OpenAiEmbeddingModel; import org.springframework.ai.openai.OpenAiEmbeddingOptions; import org.springframework.ai.openai.OpenAiTestConfiguration; +import org.springframework.ai.openai.api.OpenAiApi; import org.springframework.ai.openai.testutils.AbstractIT; 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; @SpringBootTest(classes = OpenAiTestConfiguration.class) @EnabledIfEnvironmentVariable(named = "OPENAI_API_KEY", matches = ".+") class EmbeddingIT extends AbstractIT { + private Resource resource = new DefaultResourceLoader().getResource("classpath:text_source.txt"); + @Autowired private OpenAiEmbeddingModel embeddingModel; @@ -53,6 +63,28 @@ void defaultEmbedding() { assertThat(embeddingModel.dimensions()).isEqualTo(1536); } + @Test + void embeddingBatchDocuments() throws Exception { + assertThat(embeddingModel).isNotNull(); + List embedded = this.embeddingModel.embed( + List.of(new Document("Hello world"), new Document("Hello Spring"), new Document("Hello Spring AI!")), + OpenAiEmbeddingOptions.builder().withModel(OpenAiApi.DEFAULT_EMBEDDING_MODEL).build(), + new TokenCountBatchingStrategy()); + assertThat(embedded.size()).isEqualTo(3); + embedded.forEach(embedding -> assertThat(embedding.length).isEqualTo(this.embeddingModel.dimensions())); + } + + @Test + void embeddingBatchDocumentsThatExceedTheLimit() throws Exception { + assertThat(embeddingModel).isNotNull(); + String contentAsString = resource.getContentAsString(StandardCharsets.UTF_8); + assertThatThrownBy(() -> { + embeddingModel.embed(List.of(new Document("Hello World"), new Document(contentAsString)), + OpenAiEmbeddingOptions.builder().withModel(OpenAiApi.DEFAULT_EMBEDDING_MODEL).build(), + new TokenCountBatchingStrategy()); + }).isInstanceOf(IllegalArgumentException.class); + } + @Test void embedding3Large() { diff --git a/models/spring-ai-openai/src/test/resources/text_source.txt b/models/spring-ai-openai/src/test/resources/text_source.txt new file mode 100644 index 00000000000..5f777418da0 --- /dev/null +++ b/models/spring-ai-openai/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/spring-ai-core/src/main/java/org/springframework/ai/embedding/BatchingStrategy.java b/spring-ai-core/src/main/java/org/springframework/ai/embedding/BatchingStrategy.java new file mode 100644 index 00000000000..4f73cab0684 --- /dev/null +++ b/spring-ai-core/src/main/java/org/springframework/ai/embedding/BatchingStrategy.java @@ -0,0 +1,39 @@ +/* + * Copyright 2024 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.embedding; + +import java.util.List; + +import org.springframework.ai.document.Document; + +/** + * Contract for batching {@link Document} objects so that the call to embed them could be + * optimized. + * + * @author Soby Chacko + * @since 1.0.0 + */ +public interface BatchingStrategy { + + /** + * {@link EmbeddingModel} implementations can call this method to optimize embedding + * tokens. The incoming collection of {@link Document}s are split into su-batches. + * @param documents to batch + * @return a list of sub-batches that contain {@link Document}s. + */ + List> batch(List documents); + +} diff --git a/spring-ai-core/src/main/java/org/springframework/ai/embedding/EmbeddingModel.java b/spring-ai-core/src/main/java/org/springframework/ai/embedding/EmbeddingModel.java index aefc30b4e50..874fadfed81 100644 --- a/spring-ai-core/src/main/java/org/springframework/ai/embedding/EmbeddingModel.java +++ b/spring-ai-core/src/main/java/org/springframework/ai/embedding/EmbeddingModel.java @@ -19,10 +19,18 @@ import org.springframework.ai.model.Model; import org.springframework.util.Assert; +import java.util.ArrayList; import java.util.List; /** * EmbeddingModel is a generic interface for embedding models. + * + * @author Mark Pollack + * @author Christian Tzolov + * @author Josh Long + * @author Soby Chacko + * @since 1.0.0 + * */ public interface EmbeddingModel extends Model { @@ -61,6 +69,35 @@ default List embed(List texts) { .toList(); } + /** + * Embeds a batch of {@link Document}s into vectors based on a + * {@link BatchingStrategy}. + * @param documents list of {@link Document}s. + * @param options {@link EmbeddingOptions}. + * @param batchingStrategy {@link BatchingStrategy}. + * @return a list of float[] that represents the vectors for the incoming + * {@link Document}s. + */ + default List embed(List documents, EmbeddingOptions options, BatchingStrategy batchingStrategy) { + Assert.notNull(documents, "Documents must not be null"); + List embeddings = new ArrayList<>(); + + List> batch = batchingStrategy.batch(documents); + + for (List subBatch : batch) { + List texts = subBatch.stream().map(Document::getContent).toList(); + EmbeddingRequest request = new EmbeddingRequest(texts, options); + EmbeddingResponse response = this.call(request); + for (int i = 0; i < subBatch.size(); i++) { + Document document = subBatch.get(i); + float[] output = response.getResults().get(i).getOutput(); + embeddings.add(output); + document.setEmbedding(output); + } + } + return embeddings; + } + /** * Embeds a batch of texts into vectors and returns the {@link EmbeddingResponse}. * @param texts list of texts to embed. diff --git a/spring-ai-core/src/main/java/org/springframework/ai/embedding/TokenCountBatchingStrategy.java b/spring-ai-core/src/main/java/org/springframework/ai/embedding/TokenCountBatchingStrategy.java new file mode 100644 index 00000000000..e322f52cb7d --- /dev/null +++ b/spring-ai-core/src/main/java/org/springframework/ai/embedding/TokenCountBatchingStrategy.java @@ -0,0 +1,105 @@ +/* + * Copyright 2024 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.embedding; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.ai.document.ContentFormatter; +import org.springframework.ai.document.Document; +import org.springframework.ai.document.MetadataMode; +import org.springframework.ai.tokenizer.JTokkitTokenCountEstimator; +import org.springframework.ai.tokenizer.TokenCountEstimator; + +import com.knuddels.jtokkit.api.EncodingType; + +/** + * Token count based strategy implementation for {@link BatchingStrategy}. Using openai + * max input token as the default: + * https://platform.openai.com/docs/guides/embeddings/embedding-models. + * + * @author Soby Chacko + * @since 1.0.0 + */ +public class TokenCountBatchingStrategy implements BatchingStrategy { + + /** + * Using openai upper limit of input token count as the default. + */ + private static final int MAX_INPUT_TOKEN_COUNT = 8191; + + private final TokenCountEstimator tokenCountEstimator; + + private final int maxInputTokenCount; + + private final ContentFormatter contentFormater; + + private final MetadataMode metadataMode; + + public TokenCountBatchingStrategy() { + this(EncodingType.CL100K_BASE, MAX_INPUT_TOKEN_COUNT); + } + + /** + * @param encodingType {@link EncodingType} + * @param maxInputTokenCount upper limit for input tokens + */ + public TokenCountBatchingStrategy(EncodingType encodingType, int maxInputTokenCount) { + this(encodingType, maxInputTokenCount, Document.DEFAULT_CONTENT_FORMATTER, MetadataMode.NONE); + } + + /** + * @param encodingType {@link EncodingType} + * @param maxInputTokenCount upper limit for input tokens + * @param contentFormatter {@link ContentFormatter} + * @param metadataMode {@link MetadataMode} + */ + public TokenCountBatchingStrategy(EncodingType encodingType, int maxInputTokenCount, + ContentFormatter contentFormatter, MetadataMode metadataMode) { + this.tokenCountEstimator = new JTokkitTokenCountEstimator(encodingType); + this.maxInputTokenCount = (int) Math.round(maxInputTokenCount - (maxInputTokenCount * .1)); + this.contentFormater = contentFormatter; + this.metadataMode = metadataMode; + } + + @Override + public List> batch(List documents) { + List> batches = new ArrayList<>(); + int currentSize = 0; + List currentBatch = new ArrayList<>(); + + for (Document document : documents) { + int tokenCount = this.tokenCountEstimator + .estimate(document.getFormattedContent(this.contentFormater, this.metadataMode)); + if (tokenCount > this.maxInputTokenCount) { + throw new IllegalArgumentException( + "Tokens in a single document exceeds the maximum number of allowed input tokens"); + } + if (currentSize + tokenCount > maxInputTokenCount) { + batches.add(currentBatch); + currentBatch.clear(); + currentSize = 0; + } + currentBatch.add(document); + currentSize += tokenCount; + } + if (!currentBatch.isEmpty()) { + batches.add(currentBatch); + } + return batches; + } + +} diff --git a/spring-ai-core/src/main/java/org/springframework/ai/tokenizer/JTokkitTokenCountEstimator.java b/spring-ai-core/src/main/java/org/springframework/ai/tokenizer/JTokkitTokenCountEstimator.java index 1edda8244bb..8a1dc60aa01 100644 --- a/spring-ai-core/src/main/java/org/springframework/ai/tokenizer/JTokkitTokenCountEstimator.java +++ b/spring-ai-core/src/main/java/org/springframework/ai/tokenizer/JTokkitTokenCountEstimator.java @@ -21,23 +21,27 @@ import com.knuddels.jtokkit.api.EncodingType; import org.springframework.ai.model.Media; -import org.springframework.ai.model.Content; import org.springframework.ai.model.MediaContent; import org.springframework.util.CollectionUtils; /** + * Estimates the number of tokens in a given text or message using the JTokkit encoding + * library. + * * @author Christian Tzolov + * @author Soby Chacko + * @since 1.0.0 */ public class JTokkitTokenCountEstimator implements TokenCountEstimator { private final Encoding estimator; public JTokkitTokenCountEstimator() { - this.estimator = Encodings.newLazyEncodingRegistry().getEncoding(EncodingType.CL100K_BASE); + this(EncodingType.CL100K_BASE); } - public JTokkitTokenCountEstimator(Encoding tokenEncoding) { - this.estimator = tokenEncoding; + public JTokkitTokenCountEstimator(EncodingType tokenEncodingType) { + this.estimator = Encodings.newLazyEncodingRegistry().getEncoding(tokenEncodingType); } @Override diff --git a/spring-ai-core/src/test/java/org/springframework/ai/embedding/TokenCountBatchingStrategyTests.java b/spring-ai-core/src/test/java/org/springframework/ai/embedding/TokenCountBatchingStrategyTests.java new file mode 100644 index 00000000000..f809ccf27d2 --- /dev/null +++ b/spring-ai-core/src/test/java/org/springframework/ai/embedding/TokenCountBatchingStrategyTests.java @@ -0,0 +1,57 @@ +/* + * Copyright 2024-2024 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.embedding; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.List; + +import org.junit.jupiter.api.Test; + +import org.springframework.ai.document.Document; +import org.springframework.core.io.DefaultResourceLoader; +import org.springframework.core.io.Resource; + +/** + * Basic unit test for {@link TokenCountBatchingStrategy}. + * + * @author Soby Chacko + */ +public class TokenCountBatchingStrategyTests { + + @Test + void batchEmbeddingHappyPath() { + TokenCountBatchingStrategy tokenCountBatchingStrategy = new TokenCountBatchingStrategy(); + List> batch = tokenCountBatchingStrategy.batch( + List.of(new Document("Hello world"), new Document("Hello Spring"), new Document("Hello Spring AI!"))); + assertThat(batch.size()).isEqualTo(1); + assertThat(batch.get(0).size()).isEqualTo(3); + } + + @Test + void batchEmbeddingWithLargeDocumentExceedsMaxTokenSize() throws IOException { + Resource resource = new DefaultResourceLoader().getResource("classpath:text_source.txt"); + String contentAsString = resource.getContentAsString(StandardCharsets.UTF_8); + TokenCountBatchingStrategy tokenCountBatchingStrategy = new TokenCountBatchingStrategy(); + assertThatThrownBy(() -> { + tokenCountBatchingStrategy.batch(List.of(new Document(contentAsString))); + }).isInstanceOf(IllegalArgumentException.class); + } + +} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/milvus/MilvusVectorStoreAutoConfiguration.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/milvus/MilvusVectorStoreAutoConfiguration.java index 3eaf1c5b856..ec789bed87f 100644 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/milvus/MilvusVectorStoreAutoConfiguration.java +++ b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/milvus/MilvusVectorStoreAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2023 - 2024 the original author or authors. + * Copyright 2023-2024 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. @@ -15,15 +15,14 @@ */ package org.springframework.ai.autoconfigure.vectorstore.milvus; -import java.util.concurrent.TimeUnit; - import io.micrometer.observation.ObservationRegistry; import io.milvus.client.MilvusServiceClient; import io.milvus.param.ConnectParam; import io.milvus.param.IndexType; import io.milvus.param.MetricType; - +import org.springframework.ai.embedding.BatchingStrategy; import org.springframework.ai.embedding.EmbeddingModel; +import org.springframework.ai.embedding.TokenCountBatchingStrategy; import org.springframework.ai.vectorstore.MilvusVectorStore; import org.springframework.ai.vectorstore.MilvusVectorStore.MilvusVectorStoreConfig; import org.springframework.ai.vectorstore.observation.VectorStoreObservationConvention; @@ -35,9 +34,12 @@ import org.springframework.context.annotation.Bean; import org.springframework.util.StringUtils; +import java.util.concurrent.TimeUnit; + /** * @author Christian Tzolov * @author Eddú Meléndez + * @author Soby Chacko */ @AutoConfiguration @ConditionalOnClass({ MilvusVectorStore.class, EmbeddingModel.class }) @@ -51,10 +53,17 @@ PropertiesMilvusServiceClientConnectionDetails milvusServiceClientConnectionDeta return new PropertiesMilvusServiceClientConnectionDetails(properties); } + @Bean + @ConditionalOnMissingBean(BatchingStrategy.class) + BatchingStrategy milvusBatchingStrategy() { + return new TokenCountBatchingStrategy(); + } + @Bean @ConditionalOnMissingBean public MilvusVectorStore vectorStore(MilvusServiceClient milvusClient, EmbeddingModel embeddingModel, - MilvusVectorStoreProperties properties, ObjectProvider observationRegistry, + MilvusVectorStoreProperties properties, BatchingStrategy batchingStrategy, + ObjectProvider observationRegistry, ObjectProvider customObservationConvention) { MilvusVectorStoreConfig config = MilvusVectorStoreConfig.builder() @@ -67,7 +76,7 @@ public MilvusVectorStore vectorStore(MilvusServiceClient milvusClient, Embedding .build(); return new MilvusVectorStore(milvusClient, embeddingModel, config, properties.isInitializeSchema(), - observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP), + batchingStrategy, observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP), customObservationConvention.getIfAvailable(() -> null)); } diff --git a/vector-stores/spring-ai-milvus-store/src/main/java/org/springframework/ai/vectorstore/MilvusVectorStore.java b/vector-stores/spring-ai-milvus-store/src/main/java/org/springframework/ai/vectorstore/MilvusVectorStore.java index 08e68194a6e..db182d6d87b 100644 --- a/vector-stores/spring-ai-milvus-store/src/main/java/org/springframework/ai/vectorstore/MilvusVectorStore.java +++ b/vector-stores/spring-ai-milvus-store/src/main/java/org/springframework/ai/vectorstore/MilvusVectorStore.java @@ -1,5 +1,5 @@ /* - * Copyright 2023 - 2024 the original author or authors. + * Copyright 2023-2024 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. @@ -15,29 +15,7 @@ */ package org.springframework.ai.vectorstore; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.stream.Collectors; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.ai.document.Document; -import org.springframework.ai.embedding.EmbeddingModel; -import org.springframework.ai.model.EmbeddingUtils; -import org.springframework.ai.observation.conventions.VectorStoreProvider; -import org.springframework.ai.observation.conventions.VectorStoreSimilarityMetric; -import org.springframework.ai.vectorstore.filter.FilterExpressionConverter; -import org.springframework.ai.vectorstore.observation.AbstractObservationVectorStore; -import org.springframework.ai.vectorstore.observation.VectorStoreObservationContext; -import org.springframework.ai.vectorstore.observation.VectorStoreObservationConvention; -import org.springframework.beans.factory.InitializingBean; -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; - import com.alibaba.fastjson.JSONObject; - import io.micrometer.observation.ObservationRegistry; import io.milvus.client.MilvusServiceClient; import io.milvus.common.clientenum.ConsistencyLevelEnum; @@ -64,9 +42,33 @@ import io.milvus.param.index.DropIndexParam; import io.milvus.response.QueryResultsWrapper.RowRecord; import io.milvus.response.SearchResultsWrapper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.ai.document.Document; +import org.springframework.ai.embedding.BatchingStrategy; +import org.springframework.ai.embedding.EmbeddingModel; +import org.springframework.ai.embedding.EmbeddingOptionsBuilder; +import org.springframework.ai.embedding.TokenCountBatchingStrategy; +import org.springframework.ai.model.EmbeddingUtils; +import org.springframework.ai.observation.conventions.VectorStoreProvider; +import org.springframework.ai.observation.conventions.VectorStoreSimilarityMetric; +import org.springframework.ai.vectorstore.filter.FilterExpressionConverter; +import org.springframework.ai.vectorstore.observation.AbstractObservationVectorStore; +import org.springframework.ai.vectorstore.observation.VectorStoreObservationContext; +import org.springframework.ai.vectorstore.observation.VectorStoreObservationConvention; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; /** * @author Christian Tzolov + * @author Soby Chacko */ public class MilvusVectorStore extends AbstractObservationVectorStore implements InitializingBean { @@ -104,6 +106,8 @@ public class MilvusVectorStore extends AbstractObservationVectorStore implements private final boolean initializeSchema; + private final BatchingStrategy batchingStrategy; + /** * Configuration for the Milvus vector store. */ @@ -134,7 +138,6 @@ public static Builder builder() { * {@return the default config} */ public static MilvusVectorStoreConfig defaultConfig() { - return builder().build(); } @@ -252,20 +255,25 @@ public MilvusVectorStoreConfig build() { public MilvusVectorStore(MilvusServiceClient milvusClient, EmbeddingModel embeddingModel, boolean initializeSchema) { - this(milvusClient, embeddingModel, MilvusVectorStoreConfig.defaultConfig(), initializeSchema); + this(milvusClient, embeddingModel, MilvusVectorStoreConfig.defaultConfig(), initializeSchema, + new TokenCountBatchingStrategy()); + } + + public MilvusVectorStore(MilvusServiceClient milvusClient, EmbeddingModel embeddingModel, boolean initializeSchema, + BatchingStrategy batchingStrategy) { + this(milvusClient, embeddingModel, MilvusVectorStoreConfig.defaultConfig(), initializeSchema, batchingStrategy); } public MilvusVectorStore(MilvusServiceClient milvusClient, EmbeddingModel embeddingModel, - MilvusVectorStoreConfig config, boolean initializeSchema) { - this(milvusClient, embeddingModel, config, initializeSchema, ObservationRegistry.NOOP, null); + MilvusVectorStoreConfig config, boolean initializeSchema, BatchingStrategy batchingStrategy) { + this(milvusClient, embeddingModel, config, initializeSchema, batchingStrategy, ObservationRegistry.NOOP, null); } public MilvusVectorStore(MilvusServiceClient milvusClient, EmbeddingModel embeddingModel, - MilvusVectorStoreConfig config, boolean initializeSchema, ObservationRegistry observationRegistry, - VectorStoreObservationConvention customObservationConvention) { + MilvusVectorStoreConfig config, boolean initializeSchema, BatchingStrategy batchingStrategy, + ObservationRegistry observationRegistry, VectorStoreObservationConvention customObservationConvention) { super(observationRegistry, customObservationConvention); - this.initializeSchema = initializeSchema; Assert.notNull(milvusClient, "MilvusServiceClient must not be null"); @@ -274,6 +282,7 @@ public MilvusVectorStore(MilvusServiceClient milvusClient, EmbeddingModel embedd this.milvusClient = milvusClient; this.embeddingModel = embeddingModel; this.config = config; + this.batchingStrategy = batchingStrategy; } @Override @@ -286,15 +295,16 @@ public void doAdd(List documents) { List metadataArray = new ArrayList<>(); List> embeddingArray = new ArrayList<>(); + // TODO: Need to customize how we pass the embedding options + this.embeddingModel.embed(documents, EmbeddingOptionsBuilder.builder().build(), this.batchingStrategy); + for (Document document : documents) { - float[] embedding = this.embeddingModel.embed(document); - document.setEmbedding(embedding); docIdArray.add(document.getId()); // Use a (future) DocumentTextLayoutFormatter instance to extract // the content used to compute the embeddings contentArray.add(document.getContent()); metadataArray.add(new JSONObject(document.getMetadata())); - embeddingArray.add(EmbeddingUtils.toList(embedding)); + embeddingArray.add(EmbeddingUtils.toList(document.getEmbedding())); } List fields = new ArrayList<>(); diff --git a/vector-stores/spring-ai-milvus-store/src/test/java/org/springframework/ai/vectorstore/MilvusEmbeddingDimensionsTests.java b/vector-stores/spring-ai-milvus-store/src/test/java/org/springframework/ai/vectorstore/MilvusEmbeddingDimensionsTests.java index 9db458ae338..132d3673ea0 100644 --- a/vector-stores/spring-ai-milvus-store/src/test/java/org/springframework/ai/vectorstore/MilvusEmbeddingDimensionsTests.java +++ b/vector-stores/spring-ai-milvus-store/src/test/java/org/springframework/ai/vectorstore/MilvusEmbeddingDimensionsTests.java @@ -25,6 +25,7 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.ai.embedding.EmbeddingModel; +import org.springframework.ai.embedding.TokenCountBatchingStrategy; import org.springframework.ai.vectorstore.MilvusVectorStore.MilvusVectorStoreConfig; import static org.assertj.core.api.Assertions.assertThat; @@ -56,7 +57,8 @@ public void explicitlySetDimensions() { .withEmbeddingDimension(explicitDimensions) .build(); - var dim = new MilvusVectorStore(milvusClient, embeddingModel, config, true).embeddingDimensions(); + var dim = new MilvusVectorStore(milvusClient, embeddingModel, config, true, new TokenCountBatchingStrategy()) + .embeddingDimensions(); assertThat(dim).isEqualTo(explicitDimensions); verify(embeddingModel, never()).dimensions(); @@ -68,7 +70,7 @@ public void embeddingModelDimensions() { MilvusVectorStoreConfig config = MilvusVectorStoreConfig.builder().build(); - var dim = new MilvusVectorStore(milvusClient, embeddingModel, config ,true) + var dim = new MilvusVectorStore(milvusClient, embeddingModel, config ,true, new TokenCountBatchingStrategy()) .embeddingDimensions(); assertThat(dim).isEqualTo(969); @@ -82,7 +84,7 @@ public void fallBackToDefaultDimensions() { when(embeddingModel.dimensions()).thenThrow(new RuntimeException()); var dim = new MilvusVectorStore(milvusClient, embeddingModel, - MilvusVectorStoreConfig.builder().build() ,true) + MilvusVectorStoreConfig.builder().build() ,true, new TokenCountBatchingStrategy()) .embeddingDimensions(); assertThat(dim).isEqualTo(MilvusVectorStore.OPENAI_EMBEDDING_DIMENSION_SIZE); diff --git a/vector-stores/spring-ai-milvus-store/src/test/java/org/springframework/ai/vectorstore/MilvusVectorStoreIT.java b/vector-stores/spring-ai-milvus-store/src/test/java/org/springframework/ai/vectorstore/MilvusVectorStoreIT.java index ab340b55fa0..53bf847fbae 100644 --- a/vector-stores/spring-ai-milvus-store/src/test/java/org/springframework/ai/vectorstore/MilvusVectorStoreIT.java +++ b/vector-stores/spring-ai-milvus-store/src/test/java/org/springframework/ai/vectorstore/MilvusVectorStoreIT.java @@ -34,6 +34,7 @@ import org.springframework.ai.document.Document; import org.springframework.ai.embedding.EmbeddingModel; +import org.springframework.ai.embedding.TokenCountBatchingStrategy; import org.springframework.ai.openai.OpenAiEmbeddingModel; import org.springframework.ai.openai.api.OpenAiApi; import org.springframework.ai.vectorstore.MilvusVectorStore.MilvusVectorStoreConfig; @@ -265,7 +266,7 @@ public VectorStore vectorStore(MilvusServiceClient milvusClient, EmbeddingModel .withIndexType(IndexType.IVF_FLAT) .withMetricType(metricType) .build(); - return new MilvusVectorStore(milvusClient, embeddingModel, config, true); + return new MilvusVectorStore(milvusClient, embeddingModel, config, true, new TokenCountBatchingStrategy()); } @Bean diff --git a/vector-stores/spring-ai-milvus-store/src/test/java/org/springframework/ai/vectorstore/MilvusVectorStoreObservationIT.java b/vector-stores/spring-ai-milvus-store/src/test/java/org/springframework/ai/vectorstore/MilvusVectorStoreObservationIT.java index 0e3627e01f7..e10a1174cc8 100644 --- a/vector-stores/spring-ai-milvus-store/src/test/java/org/springframework/ai/vectorstore/MilvusVectorStoreObservationIT.java +++ b/vector-stores/spring-ai-milvus-store/src/test/java/org/springframework/ai/vectorstore/MilvusVectorStoreObservationIT.java @@ -25,6 +25,7 @@ import org.junit.jupiter.api.Test; import org.springframework.ai.document.Document; import org.springframework.ai.embedding.EmbeddingModel; +import org.springframework.ai.embedding.TokenCountBatchingStrategy; import org.springframework.ai.observation.conventions.VectorStoreProvider; import org.springframework.ai.openai.OpenAiEmbeddingModel; import org.springframework.ai.openai.api.OpenAiApi; @@ -158,7 +159,8 @@ public VectorStore vectorStore(MilvusServiceClient milvusClient, EmbeddingModel .withIndexType(IndexType.IVF_FLAT) .withMetricType(MetricType.COSINE) .build(); - return new MilvusVectorStore(milvusClient, embeddingModel, config, true, observationRegistry, null); + return new MilvusVectorStore(milvusClient, embeddingModel, config, true, new TokenCountBatchingStrategy(), + observationRegistry, null); } @Bean