Spring configuration with java

Composing Java-based Configurations

Spring’s Java-based configuration feature lets you compose annotations, which can reduce the complexity of your configuration.

Using the @Import Annotation

Much as the element is used within Spring XML files to aid in modularizing configurations, the @Import annotation allows for loading @Bean definitions from another configuration class, as the following example shows:

@Configuration public class ConfigA < @Bean public A a() < return new A(); >> @Configuration @Import(ConfigA.class) public class ConfigB < @Bean public B b() < return new B(); >>
@Configuration class ConfigA < @Bean fun a() = A() >@Configuration @Import(ConfigA::class) class ConfigB

Now, rather than needing to specify both ConfigA.class and ConfigB.class when instantiating the context, only ConfigB needs to be supplied explicitly, as the following example shows:

public static void main(String[] args) < ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigB.class); // now both beans A and B will be available. A a = ctx.getBean(A.class); B b = ctx.getBean(B.class); >
import org.springframework.beans.factory.getBean fun main() < val ctx = AnnotationConfigApplicationContext(ConfigB::class.java) // now both beans A and B will be available. val a = ctx.getBean() val b = ctx.getBean() >

This approach simplifies container instantiation, as only one class needs to be dealt with, rather than requiring you to remember a potentially large number of @Configuration classes during construction.

As of Spring Framework 4.2, @Import also supports references to regular component classes, analogous to the AnnotationConfigApplicationContext.register method. This is particularly useful if you want to avoid component scanning, by using a few configuration classes as entry points to explicitly define all your components.

Injecting Dependencies on Imported @Bean Definitions

The preceding example works but is simplistic. In most practical scenarios, beans have dependencies on one another across configuration classes. When using XML, this is not an issue, because no compiler is involved, and you can declare ref=»someBean» and trust Spring to work it out during container initialization. When using @Configuration classes, the Java compiler places constraints on the configuration model, in that references to other beans must be valid Java syntax.

Fortunately, solving this problem is simple. As we already discussed, a @Bean method can have an arbitrary number of parameters that describe the bean dependencies. Consider the following more real-world scenario with several @Configuration classes, each depending on beans declared in the others:

@Configuration public class ServiceConfig < @Bean public TransferService transferService(AccountRepository accountRepository) < return new TransferServiceImpl(accountRepository); >> @Configuration public class RepositoryConfig < @Bean public AccountRepository accountRepository(DataSource dataSource) < return new JdbcAccountRepository(dataSource); >> @Configuration @Import() public class SystemTestConfig < @Bean public DataSource dataSource() < // return new DataSource >> public static void main(String[] args) < ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class); // everything wires up across configuration classes. TransferService transferService = ctx.getBean(TransferService.class); transferService.transfer(100.00, "A123", "C456"); >
import org.springframework.beans.factory.getBean @Configuration class ServiceConfig < @Bean fun transferService(accountRepository: AccountRepository): TransferService < return TransferServiceImpl(accountRepository) >> @Configuration class RepositoryConfig < @Bean fun accountRepository(dataSource: DataSource): AccountRepository < return JdbcAccountRepository(dataSource) >> @Configuration @Import(ServiceConfig::class, RepositoryConfig::class) class SystemTestConfig < @Bean fun dataSource(): DataSource < // return new DataSource >> fun main() < val ctx = AnnotationConfigApplicationContext(SystemTestConfig::class.java) // everything wires up across configuration classes. val transferService = ctx.getBean() transferService.transfer(100.00, "A123", "C456") >

There is another way to achieve the same result. Remember that @Configuration classes are ultimately only another bean in the container: This means that they can take advantage of @Autowired and @Value injection and other features the same as any other bean.

Читайте также:  Java update list elements

Make sure that the dependencies you inject that way are of the simplest kind only. @Configuration classes are processed quite early during the initialization of the context, and forcing a dependency to be injected this way may lead to unexpected early initialization. Whenever possible, resort to parameter-based injection, as in the preceding example.

Also, be particularly careful with BeanPostProcessor and BeanFactoryPostProcessor definitions through @Bean . Those should usually be declared as static @Bean methods, not triggering the instantiation of their containing configuration class. Otherwise, @Autowired and @Value may not work on the configuration class itself, since it is possible to create it as a bean instance earlier than AutowiredAnnotationBeanPostProcessor .

The following example shows how one bean can be autowired to another bean:

@Configuration public class ServiceConfig < @Autowired private AccountRepository accountRepository; @Bean public TransferService transferService() < return new TransferServiceImpl(accountRepository); >> @Configuration public class RepositoryConfig < private final DataSource dataSource; public RepositoryConfig(DataSource dataSource) < this.dataSource = dataSource; >@Bean public AccountRepository accountRepository() < return new JdbcAccountRepository(dataSource); >> @Configuration @Import() public class SystemTestConfig < @Bean public DataSource dataSource() < // return new DataSource >> public static void main(String[] args) < ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class); // everything wires up across configuration classes. TransferService transferService = ctx.getBean(TransferService.class); transferService.transfer(100.00, "A123", "C456"); >
import org.springframework.beans.factory.getBean @Configuration class ServiceConfig < @Autowired lateinit var accountRepository: AccountRepository @Bean fun transferService(): TransferService < return TransferServiceImpl(accountRepository) >> @Configuration class RepositoryConfig(private val dataSource: DataSource) < @Bean fun accountRepository(): AccountRepository < return JdbcAccountRepository(dataSource) >> @Configuration @Import(ServiceConfig::class, RepositoryConfig::class) class SystemTestConfig < @Bean fun dataSource(): DataSource < // return new DataSource >> fun main() < val ctx = AnnotationConfigApplicationContext(SystemTestConfig::class.java) // everything wires up across configuration classes. val transferService = ctx.getBean() transferService.transfer(100.00, "A123", "C456") >
Constructor injection in @Configuration classes is only supported as of Spring Framework 4.3. Note also that there is no need to specify @Autowired if the target bean defines only one constructor.

In the preceding scenario, using @Autowired works well and provides the desired modularity, but determining exactly where the autowired bean definitions are declared is still somewhat ambiguous. For example, as a developer looking at ServiceConfig , how do you know exactly where the @Autowired AccountRepository bean is declared? It is not explicit in the code, and this may be just fine. Remember that the Spring Tools for Eclipse provides tooling that can render graphs showing how everything is wired, which may be all you need. Also, your Java IDE can easily find all declarations and uses of the AccountRepository type and quickly show you the location of @Bean methods that return that type.

Читайте также:  Accessing databases with java

In cases where this ambiguity is not acceptable and you wish to have direct navigation from within your IDE from one @Configuration class to another, consider autowiring the configuration classes themselves. The following example shows how to do so:

@Configuration public class ServiceConfig < @Autowired private RepositoryConfig repositoryConfig; @Bean public TransferService transferService() < // navigate 'through' the config class to the @Bean method! return new TransferServiceImpl(repositoryConfig.accountRepository()); >>

Источник

Using the @Configuration annotation

@Configuration is a class-level annotation indicating that an object is a source of bean definitions. @Configuration classes declare beans through @Bean -annotated methods. Calls to @Bean methods on @Configuration classes can also be used to define inter-bean dependencies. See Basic Concepts: @Bean and @Configuration for a general introduction.

Injecting Inter-bean Dependencies

When beans have dependencies on one another, expressing that dependency is as simple as having one bean method call another, as the following example shows:

@Configuration public class AppConfig < @Bean public BeanOne beanOne() < return new BeanOne(beanTwo()); >@Bean public BeanTwo beanTwo() < return new BeanTwo(); >>
@Configuration class AppConfig

In the preceding example, beanOne receives a reference to beanTwo through constructor injection.

This method of declaring inter-bean dependencies works only when the @Bean method is declared within a @Configuration class. You cannot declare inter-bean dependencies by using plain @Component classes.

Lookup Method Injection

As noted earlier, lookup method injection is an advanced feature that you should use rarely. It is useful in cases where a singleton-scoped bean has a dependency on a prototype-scoped bean. Using Java for this type of configuration provides a natural means for implementing this pattern. The following example shows how to use lookup method injection:

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(); >
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.setState(commandState) return command.execute() >// okay. but where is the implementation of this method? protected abstract fun createCommand(): Command >

By using Java configuration, you can create a subclass of CommandManager where the abstract createCommand() method is overridden in such a way that it looks up a new (prototype) command object. The following example shows how to do so:

@Bean @Scope("prototype") public AsyncCommand asyncCommand() < AsyncCommand command = new AsyncCommand(); // inject dependencies here as required return command; >@Bean public CommandManager commandManager() < // return new anonymous implementation of CommandManager with createCommand() // overridden to return a new prototype Command object return new CommandManager() < protected Command createCommand() < return asyncCommand(); >> >
@Bean @Scope("prototype") fun asyncCommand(): AsyncCommand < val command = AsyncCommand() // inject dependencies here as required return command >@Bean fun commandManager(): CommandManager < // return new anonymous implementation of CommandManager with createCommand() // overridden to return a new prototype Command object return object : CommandManager() < override fun createCommand(): Command < return asyncCommand() >> >

Further Information About How Java-based Configuration Works Internally

Consider the following example, which shows a @Bean annotated method being called twice:

@Configuration public class AppConfig < @Bean public ClientService clientService1() < ClientServiceImpl clientService = new ClientServiceImpl(); clientService.setClientDao(clientDao()); return clientService; >@Bean public ClientService clientService2() < ClientServiceImpl clientService = new ClientServiceImpl(); clientService.setClientDao(clientDao()); return clientService; >@Bean public ClientDao clientDao() < return new ClientDaoImpl(); >>
@Configuration class AppConfig < @Bean fun clientService1(): ClientService < return ClientServiceImpl().apply < clientDao = clientDao() >> @Bean fun clientService2(): ClientService < return ClientServiceImpl().apply < clientDao = clientDao() >> @Bean fun clientDao(): ClientDao < return ClientDaoImpl() >>

clientDao() has been called once in clientService1() and once in clientService2() . Since this method creates a new instance of ClientDaoImpl and returns it, you would normally expect to have two instances (one for each service). That definitely would be problematic: In Spring, instantiated beans have a singleton scope by default. This is where the magic comes in: All @Configuration classes are subclassed at startup-time with CGLIB . In the subclass, the child method checks the container first for any cached (scoped) beans before it calls the parent method and creates a new instance.

Читайте также:  Python raise exception with error message
The behavior could be different according to the scope of your bean. We are talking about singletons here.

It is not necessary to add CGLIB to your classpath because CGLIB classes are repackaged under the org.springframework.cglib package and included directly within the spring-core JAR.

There are a few restrictions due to the fact that CGLIB dynamically adds features at startup-time. In particular, configuration classes must not be final. However, any constructors are allowed on configuration classes, including the use of @Autowired or a single non-default constructor declaration for default injection.

If you prefer to avoid any CGLIB-imposed limitations, consider declaring your @Bean methods on non- @Configuration classes (for example, on plain @Component classes instead) or by annotating your configuration class with @Configuration(proxyBeanMethods = false) . Cross-method calls between @Bean methods are then not intercepted, so you have to exclusively rely on dependency injection at the constructor or method level there.

Apache®, Apache Tomcat®, Apache Kafka®, Apache Cassandra™, and Apache Geode™ are trademarks or registered trademarks of the Apache Software Foundation in the United States and/or other countries. Java™, Java™ SE, Java™ EE, and OpenJDK™ are trademarks of Oracle and/or its affiliates. Kubernetes® is a registered trademark of the Linux Foundation in the United States and other countries. Linux® is the registered trademark of Linus Torvalds in the United States and other countries. Windows® and Microsoft® Azure are registered trademarks of Microsoft Corporation. “AWS” and “Amazon Web Services” are trademarks or registered trademarks of Amazon.com Inc. or its affiliates. All other trademarks and copyrights are property of their respective owners and are only mentioned for informative purposes. Other names may be trademarks of their respective owners.

Источник

Оцените статью