Learnitweb

Java Based Container Configuration

1. Introduction

In this tutorial, we’ll discuss the Java based container configuration in Spring.

Two central artifacts in Spring’s Java configuration support are @Configuration annotated classes and @Bean annotated classes.

2. @Bean annotation

The @Bean annotation signifies that a method creates, configures, and initializes a new object to be managed by the Spring IoC container. The @Bean annotation plays the same role as the element.

@Bean annotated methods are most often used with @Configuration beans.

A class annotated with @Configuration indicates that its main purpose is to serve as a source of bean definitions. @Configuration classes let inter-bean dependencies be defined by calling other @Bean methods in the same class.

Here is an example:

@Configuration
public class AppConfig {

	@Bean
	public MyServiceImpl myService() {
		return new MyServiceImpl();
	}
}

In common scenarios, @Bean methods are to be declared within @Configuration classes. This ensures that the entire class is processed and cross-method references therefore get redirected to the container’s lifecycle management.

This ensures that the @Bean method is not inadvertently called through a standard Java method call, helping to minimize subtle and difficult-to-detect bugs. When @Bean methods are declared within classes that not annotated with @Configuration, they are referred to as being processed in a ‘lite’ mode.

A direct Java call to such a method will bypass the container’s interception, behaving like a regular method call. This results in a new instance being created each time, instead of reusing an existing singleton (or scoped) instance of the bean. Such a @Bean method therefore never needs to invoke other @Bean methods.

2. @Configuration annotation

@Configuration is used on a class and indicates that an object is a source of bean definitions. @Configuration classes declare beans through @Bean-annotated methods. You can also use calls to @Bean methods within @Configuration classes to establish dependencies between beans.

2.1 Injecting Inter-bean Dependencies

When one bean depends on another, you can easily express this dependency by having one @Bean method call another, as demonstrated in the following example:

@Configuration
public class AppConfig {

	@Bean
	public BeanOne beanOne() {
		return new BeanOne(beanTwo());
	}

	@Bean
	public BeanTwo beanTwo() {
		return new BeanTwo();
	}
}

In the preceding example, beanOne receives a reference to beanTwo through constructor injection. This approach to defining inter-bean dependencies is effective only when the @Bean method is declared within a @Configuration class. Inter-bean dependencies cannot be specified using plain @Component classes.

2.2 @Configuration and CGLIB

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

@Configuration
public class AppConfig {

	@Bean
	public MyService myService1() {
		MyServiceImpl myService = new MyServiceImpl();
		myService.setMyDao(myDao());
		return myService;
	}

	@Bean
	public MyService myService2() {
		MyServiceImpl myService = new MyServiceImpl();
		myService.setMyDao(myDao());
		return myService;
	}

	@Bean
	public MyDao myDao() {
		return new MyDaoImpl();
	}
}

myDao() has been called once in myService1() and once in myService2(). Since this method creates and returns a new instance of MyDaoImpl, you would typically expect to have two separate instances (one for each service). This is going to be an issue as Spring instantiated beans have ‘singleton’ scope.
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. The behavior could be different according to the scope of the bean. CGLIB dynamically adds features at startup-time this leads to few restrictions. In particular, @Configuration classes can not be final. However, default constructors are allowed, including the use of @Autowired.

To avoid the limitations imposed by CGLIB, you can declare your @Bean methods in non-@Configuration classes, such as plain @Component classes. Alternatively, you can annotate your configuration class with @Configuration(proxyBeanMethods = false). In this setup, cross-method calls between @Bean methods are not intercepted, so you must rely solely on constructor or method-level dependency injection.

2.3 @Import annotation

The @Import annotation allows for loading @Bean definitions from another configuration class.

@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();
	}
}

Now, instead of specifying both ConfigA.class and ConfigB.class when creating the context, you only need to explicitly provide ConfigB, as illustrated in the following example:

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);
}

This approach simplifies container instantiation by requiring you to handle only one class, rather than remembering and managing a potentially large number of @Configuration classes during setup.

3. Conclusion

In conclusion, Java-based container configuration in Spring provides a robust and flexible approach to defining beans and their dependencies. By leveraging annotations such as @Configuration and @Bean, developers can create maintainable and easily readable configuration classes. This method also allows for fine-grained control over bean instantiation and lifecycle management. Understanding and utilizing these Java-based configuration techniques enable the creation of scalable, efficient, and well-organized Spring applications, ensuring that your codebase remains clean and manageable as it grows.