Refresher: /spring-boot/concepts/#ioc-and-di
Spring always finds Beans and adds them to ApplicationContext/Proxy, the @ComponentScan
must specify scope to scan for bean definitions otherwise it results in a compile-time error if a bean is autowired somewhere in our application code and not scanned under component scan.
Multiple beans of the same type can exist but each bean in Spring context has a unique name (globally, and not only for its type). If multiple bean of the same name exist, one overrides the other. Their unique name also helps us to identify them while autowiring.
The default bean scope in Spring is Singleton (only one instance of a Spring bean having a specific name will be created and injected into the proxy). Multiple beans of the same type may exist but singleton property in Spring is based on their name (bean names have to be globally unique).
For dependency to eliglible for autowiring, it needs to have a @Component
or equivalent annotation on the implementing concrete class (and not interface
) otherwise autowiring will fail with error No qualifying bean of type [...] found for dependency...
Don’t create objects having @Autowired
dependencies with new
keyword, Spring won’t know that these object exists and won’t be able to Autowire the dependency into it, making its value to be null
and its a Runtime Exception.
It is a good practice never to use new
to create beans in Spring.
class MyService{
@Autowired
MyRepository repo;
}
class MyController{
MyService service = new MyService(); // Spring can't inject MyRepository bean in MyService
}
Reference: https://www.baeldung.com/spring-autowired-field-null
Points to remember:
@Component
or equivalent is always placed on the concrete class, where the Bean is actually formed; otherwise errorinterface
but only when atleast one of its implementation’s Bean is present; otherwise errorWhat happens when two Concrete classes exists for a single interface that we autowire? Error, Spring can’t decide which impl to autowire in this case.
interface AutoService { }
@Service
class ServiceA implements AutoService { }
@Service
class ServiceB implements AutoService { }
class Main{
@Autowired
private AutoService autoService; // compile-time error
}
@Autowired
field’s name@Service("")
along with @Qualifier("")
to specify more explicitly@Primary
on the class which should always be autowired in case of ambiguity (if there are multiple beans of the same type) unless specified otherwise// 1. specify service name as @Autowired field's name to select which bean to use
class Main{
@Autowired
private AutoService serviceA; // instance of ServiceA is injected
}
// 2. use @Service("") along with @Qualifier("") to select which bean to use
@Service("foo")
class ServiceA implements AutoService { }
@Service("bar")
class ServiceB implements AutoService { }
class Main{
@Autowired
@Qualifier("bar")
private AutoService autoService; // ServiceB is injected
}
// 3. use @Primary to indicate a preferred impl (if multiple impl are present and there is ambiguity, this bean impl will be used)
@Service
@Primary
class ServiceA implements AutoService { }
@Service
class ServiceB implements AutoService { }
class Main{
@Autowired
private AutoService autoService; // ServiceA is injected
}
// if both @Primary and @Qualifier mechanisms are present and causing ambiguity, then @Qualifier takes precedence (ofc as its more explicit and closer to usage (autowiring location))
The same three strategies above can be used to autowire a superclass type if multiple subclasses of it are present and there is ambiguity on which subclass instance Spring should autowire. Read below section on Bean Selection for detail.
Reference: https://www.baeldung.com/spring-autowire
interfaces
can’t have @Component
or equivalent annotations on them, but there is an exception: the JPA Repositories, they can have them on interface, although it is not required at all:
@Repository
public interface UserRepository extends JpaRepository<User,Long> {
User findByEmail(String email);
}
Actually, interface
can also have a @Component
annotation (useless though) if the implementing class also has it, that is what is happening here. Keeping interfaces free of any such framework specific annotations is a good practice.
The usual way we have beans is using annotation like @Component
and the name of the bean is always taken as lowercase in this case:
@Component
class Foobar{} // default naming; a bean named "foobar" (notice the lowercase) is created
@Component("Baz")
class Foobar{} // explicit naming; a bean named "Baz" is created (no case conversion since its explicit)
We can create custom beans other than the @Component
mechanism, with @Configuration
class and @Bean
method annotations:
@Configuration
class MyCustomBeans{
@Bean
public ServiceA serviceABean(String s, int n){ // notice return type
return new ServiceA("foo", 99); // we can call any constructor we want here
}
// dependency on the existence of another bean
@Bean
public ServiceC serviceCBean(ServiceB s) { // Spring will automatically init ServiceB Bean before this and use that to init ServiceC here
return new ServiceC();
}
}
The bean name can be specified in the @Bean
annotation and by default the method name is taken as the bean name unless explicitly specified, no restriction of lowercase names here since we can specify anything explicitly here:
@Bean
public Demo foo(){ // bean name is "foo" (default is method name)
return new Demo();
}
@Bean("Bar") // bean name is "Bar", explicitly specified bean name
public Demo foo(){
return new Demo();
}
The Beans created above will automatically be injected in Spring Proxy since this is a @Configuration
class.
Remember that all beans have a unique name globally in the Spring context, hence each and every bean can be identified using its name and as a result, they are overridden too using their names be it same type or not.
Points to remember regarding overriding of beans:
// configuration class
@Bean("foobar")
public Foobar getFoobar(){
return new Foobar();
}
@Bean("foobar")
public Foobar getFoobarBean(){
return new Foobar();
}
// this doesn't cause any application startup errors but the below bean overrides the above one since they have the same name
@Bean("foobar")
public Test getTestBean(){
return new Test();
}
// this will still override above beans even if it is of diff type, since it has the same name (no startup errors here too)
// only one bean named "foobar" is created for the above three methods
Usage: it is often used to override classes (beans) imported from a JAR dependency with custom bean implementation. Interview Question
We can also completely avoid bean overriding in certain scenarios using bean selection mechanisms provided by Spring.
If multiple beans of the same type (interface or superclass) are present and even if they have diff names we can use @Primary
on one of the bean or use @Qualifier("")
to specify name of bean to use while autowiring (in this we need to modify existing code though) to prefer a particular bean making things more explicit.
// Beans present of type Foobar are - foobar, foo, bar (all subclasses of Foobar class)
class Service{
@Autowired
Foobar fb; // ambiguous; application startup failure: required a single bean, but 2 were found
}
// resolve the above error just like we resolved ambiguity in interface autowiring: Autowired field name, @Primary, @Qualifier annotations
We can dynamically include beans in the following ways instead of relying on static code:
@Value
) and use if-else in @Bean
method to include the wanted bean@ConditionalOnProperty
), etc.@Profile
)@Configuration
class AppConfig{
@Value("${pet.isDog}")
boolean flag;
@Bean
public Pet animal(){
if(flag == true){
return new Dog();
} else {
return new Cat();
}
}
}
// below 2 ways be used on @Bean annotation too instead of @Component
@Component
@ConditionalOnProperty(prefix="foobar", name="enabled", havingValue="true", matchIfMissing=false)
class Foobar{ }
@Component
@Profile("qa")
class Foobar{ }
All singleton beans are created and injected into the Application Context during startup. To cut down startup time, we can make it so such that beans are only initialized and injected just before they are used in the code.
spring.main.lazy-initialization=true
@Configuration
class that has bean definitions or on individual @Bean
@Lazy // specify here; applies to all beans in the class
@Configuration
public class MyCustomBeans{
@Lazy // or here
@Bean
ServiceA serviceBeanCreator(){
return new ServiceA();
}
}
We can also make all beans lazy using the properties file and do @Lazy(false) on specific beans to make only them load at startup.
Reference: https://www.baeldung.com/spring-boot-lazy-initialization
So far, the most popular way is to autowire/inject a Bean to a field. But it is Relection based and slower than the other two ways. We can also do setter and constructor based injections (recommended):
class Main{
@Autowired
private AutoService autoService; // field-based injection; injects AutoService bean on the field
}
----------------------------------------------------
class Main{
private AutoService autoService;
@Autowired // Spring calls this setter automatically just after constructor call; also passes AutoService Bean if it exists
private mySetter(AutoService autoService){ // setter-based injection; injects AutoService bean on the setter's param
// any custom logic
this.autoService = autoService;
}
}
----------------------------------------------------
class Main{
private AutoService autoService;
@Autowired // Spring will use this constructor by default when creating Bean of Main
private Main(AutoService autoService){ // constructor-based injection; injects AutoService bean on the constructor's param
this.autoService = autoService;
}
}
Reference: https://stackoverflow.com/a/56089288
Specify scopes on classes using @Scope
annotation on them. Ex - @Scope("prototype")
or with constant using @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
.
ctx.getBean("Foobar")
or on every bean usage in another bean.@RequestScope
): only a single instance will be created laziliy and available during the complete lifecycle of an HTTP Request.@SessionScope
): only a single instance will be created laziliy and available during the complete lifecycle of an HTTP Session (they start on API endpoint call and end after timeout or manually closed).@ApplicationScope
): only a single instance will be created laziliy and available during the complete lifecycle of ServletContext
(multiple IoC share a single instance of such bean).If a singleton bean Controller
(eager init) has a dependency upon another bean User
(request scoped; lazy init), how does Spring Proxy get init without User
bean being present? Answer - application startup failure error if we don’t configure proxyMode
manually.
@Component
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS) // configure proxyMode for lazy bean
public class User{ }
This inserts a dummy (proxy) bean during application startup and later when a HTTP request is made and an actual bean is init and available, it is replaced.
References:
init()
method is called@PostConstruct
annotated method is called@PreDestroy
annotated method is calleddestroy()
method is calledReference: https://javatechonline.com/spring-bean-life-cycle-method-examples/