- Published on
Java Interview Preparation - Spring Boot
- Authors
- Name
- Rahul Neelakantan
What is Spring and Spring Boot?
Spring is a powerful and flexible framework used for building Java applications. It provides comprehensive infrastructure support for developing robust Java applications, enabling developers to focus on application-level business logic, without unnecessary ties to specific deployment environments.
Spring Boot, on the other hand, is a project built on top of the Spring Framework. It provides a simpler and faster way to set up, configure, and run both simple and web-based applications. It is a Spring module which provides RAD (Rapid Application Development) feature to the Spring Framework.
Spring Boot automatically configures your application based on the dependencies you have added to the project by using @EnableAutoConfiguration annotation. It simplifies the manual configuration of application.
How does Spring handle IoC (Inversion of Control)?
Spring Framework handles Inversion of Control (IoC) through a mechanism known as Dependency Injection (DI). This is a design pattern that allows for loose coupling of code, making it more modular, easier to test, and more maintainable.
In traditional programming, the flow of control is managed by a central piece of code and it's responsible for managing the creation and lifecycle of objects. This leads to tightly coupled and hard-to-test code.
In contrast, with IoC, the control is inverted — the framework (in this case, Spring) takes care of instantiating and wiring together the necessary objects. The objects do not need to know about each other's existence. They just declare their dependencies, and Spring injects these dependencies when it creates the beans. This is done via XML configuration or annotations (@Autowired, @Inject, @Resource).
So, in Spring, you simply define your beans and their dependencies, and Spring takes care of the rest. This allows you to focus on the business logic of your application, rather than the boilerplate instantiation and setup code.
What is Bean life cycle in Spring?
Instantiation: The Spring container finds the bean's definition from the XML file and instantiates the bean.
Populate properties: Spring populates all of the properties as specified in the bean definition (like property, constructor argument values).
Set Bean Name: If the bean implements
BeanNameAware
, Spring passes the bean's ID tosetBeanName()
method.Set Bean Factory: If Bean implements
BeanFactoryAware
, Spring passes the beanfactory tosetBeanFactory()
method.Pre-Initialization - BeanPostProcessors: If there are any BeanPostProcessors associated, Spring calls their
postProcessBeforeInitialization()
methods.Initialize beans: If the bean implements
InitializingBean
, itsafterPropertiesSet()
method is called. If the bean has ainit-method
declaration, the specified initialization method is called.Post-Initialization - BeanPostProcessors: If there are any BeanPostProcessors associated, Spring calls their
postProcessAfterInitialization()
methods.Ready to use: Now, the bean is ready to be used by the application.
Destroy: If the bean implements
DisposableBean
, it will call thedestroy()
method. If the bean has adestroy-method
declaration, the specified method is called when the bean is no longer needed and the application is shut down or the Spring container is closed.
This lifecycle is managed by the Spring IoC container. The container ensures that all dependencies are injected before the bean is put into use, and it also manages the cleanup of the bean at the end of its life.
What is the use of BeanNameAware interface in Spring?
BeanNameAware
is an interface in Spring Framework that beans can implement to be aware of their own bean name in the Spring application context.
The primary use case for BeanNameAware
is when a bean needs to know its own name for some internal logic or for some other beans that need to retrieve it by name.
For example, if a bean is used in multiple contexts and it needs to behave slightly differently based on where it's used, it can use BeanNameAware
to get its context name and adjust its behavior accordingly.
Another use case could be for logging or debugging purposes. If a bean logs its activities for auditing, it might be useful to include the bean name in the log entries.
Remember, it's not often that you'll need to use BeanNameAware
. It's there for those edge cases where a bean needs to be aware of its own identity within the Spring container. In most cases, beans should be designed to be unaware of their own configuration and container details to promote loose coupling and maintainability.
What is use of BeanFactoryAware interface in Spring?
BeanFactoryAware
is an interface in Spring Framework that beans can implement to be aware of their owning BeanFactory. We can then use it to manipulate the BeanFactory or to access other beans from the BeanFactory. When a bean implements BeanFactoryAware
, Spring will automatically inject the BeanFactory into the bean before it's put into use. This allows the bean to interact with the BeanFactory and access other beans in the container.
How to setup multiple ApplicationContext in Spring Boot?
- Create a parent context: This is the main
ApplicationContext
that will be used to bootstrap the other contexts.
@SpringBootApplication
public class ParentApplication {
@Bean
public ParentBean parentBean() {
return new ParentBean();
}
public static void main(String[] args) {
SpringApplication.run(ParentApplication.class, args);
}
}
- Create child contexts: These are the additional
ApplicationContexts
that you want to create. You can bootstrap them manually usingSpringApplicationBuilder
.
public class ChildApplication {
@Bean
public ChildBean childBean() {
return new ChildBean();
}
public static void main(String[] args) {
new SpringApplicationBuilder(ChildApplication.class)
.parent(ParentApplication.class)
.run(args);
}
}
In this example, ChildApplication
is a separate ApplicationContext
that has ParentApplication
as its parent. Beans defined in ParentApplication
are available in ChildApplication
, but not vice versa.
Pre and Post Initialization in Spring Bean
In Spring, you can perform actions before and after a bean is initialized using BeanPostProcessor
. This interface provides callback methods that you can implement to perform custom initialization logic on all beans or specific beans.
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;
@Component
public class CustomBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof MyBean) {
MyBean myBean = (MyBean) bean;
myBean.setProperty(myBean.getProperty().toUpperCase());
}
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
// We can leave this method empty if we don't want to do anything after initialization
return bean;
}
}
In this example, CustomBeanPostProcessor
checks if the bean is an instance of MyBean
. If it is, it changes the property of MyBean
to uppercase. This happens before the bean's initialization methods are called.
Purpose of @PostConstruct and BeanPostProcessor in Spring
The @PostConstruct
annotation and BeanPostProcessor
interface serve different purposes in Spring and can be used in different scenarios.
@PostConstruct
: This annotation is used on a method that needs to be executed after dependency injection is done to perform any initialization. This method will be executed only once during the life cycle of the bean.BeanPostProcessor
: This interface defines callback methods that you can implement to provide your own instantiation logic, dependency-resolution logic, etc. You can also post-process an object instance after it is created and default initialization has been performed.
The BeanPostProcessor
interface gives you a way to interact with new bean instances before and after their initialization callbacks. It allows for both custom modification of new bean instances (for example, checking for marker interfaces or wrapping them with proxies) and suppression of initialization.
In the code excerpt you provided, the CustomBeanPostProcessor
is used to modify the properties of the MyBean
instance before it's initialized. This kind of modification isn't possible with @PostConstruct
because @PostConstruct
is called after the bean is fully initialized.
So, while @PostConstruct
is useful for bean-specific initialization logic, BeanPostProcessor
provides a way to apply common logic to multiple beans in a more flexible way.
Explain @PostConstruct and @PreDestroy annotations in Spring
@PostConstruct
and @PreDestroy
are lifecycle annotations in Spring that allow you to define methods that should be executed after bean creation and before bean destruction, respectively.
@Component
public class MyBean {
@PostConstruct
public void init() {
// initialization logic here
}
@PreDestroy
public void destroy() {
// cleanup logic here
}
}
Explain the concept of AOP (Aspect-Oriented Programming) in Spring
Aspect-Oriented Programming (AOP) is a programming paradigm that aims to increase modularity by allowing the separation of cross-cutting concerns. In traditional programming, concerns such as logging, security, transaction management, etc., are scattered throughout the codebase, making it harder to maintain and test.
AOP provides a way to modularize these cross-cutting concerns by defining aspects, which encapsulate behavior that cuts across multiple classes. Aspects are then applied to the target classes using pointcuts, which define where the aspect should be applied.
In Spring, AOP is implemented using proxies. When a bean is created, Spring wraps it in a proxy that intercepts method calls. These proxies can apply aspects before, after, or around the target method execution.
For example, you can define an aspect that logs the method execution time for all methods annotated with @LogExecutionTime
. This aspect can be applied to multiple classes without modifying their code.
@Aspect
@Component
public class LoggingAspect {
@Around("@annotation(LogExecutionTime)")
public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
Object proceed = joinPoint.proceed();
long executionTime = System.currentTimeMillis() - startTime;
System.out.println(joinPoint.getSignature() + " executed in " + executionTime + "ms");
return proceed;
}
}
In this example, the LoggingAspect
logs the execution time of methods annotated with @LogExecutionTime
. The @Around
advice intercepts the method execution, logs the time, and then proceeds with the method execution.
AOP is a powerful tool for separating concerns and improving the modularity of your codebase. It allows you to focus on the core business logic of your application while keeping cross-cutting concerns separate and reusable.
Bean Container in Spring
The Bean Container, also known as the ApplicationContext in Spring, is the central interface within a Spring application for providing configuration information to the application.
The ApplicationContext includes all functionality of the BeanFactory (another type of bean container), which is generally used for simpler applications, and adds more enterprise-specific functionality. This includes the ability to resolve textual messages from a properties file and the ability to publish application events to interested event listeners.
In summary, the Bean Container or ApplicationContext is responsible for instantiating, configuring, and assembling beans by reading configuration metadata from XML, Java annotations, and/or Java code in the configuration files.
Bean Factory vs Application Context in Spring
BeanFactory
and ApplicationContext
are both interfaces for a Spring container that is responsible for instantiating, configuring, and managing beans. However, they have some differences:
BeanFactory
: This is the root interface for accessing a Spring bean container. It provides basic Inversion of Control (IoC) and Dependency Injection (DI) features.BeanFactory
is used for simple applications where memory consumption might be a concern, as it is less resource-intensive.ApplicationContext
: This is a more advanced interface that builds onBeanFactory
. It adds more enterprise-specific features, such as the ability to resolve textual messages from a properties file, support for internationalization, event propagation, and various application-layer specific contexts such asWebApplicationContext
for web applications.
In general, ApplicationContext
is preferred over BeanFactory
for most applications due to its extended functionality. The extra memory consumption of ApplicationContext
is usually negligible in comparison to the added value.
If same interface is used by multiple beans, how to resolve the ambiguity?
When multiple beans implement the same interface, Spring can't determine which bean to inject by default. To resolve this ambiguity, you can use the @Qualifier
annotation along with @Autowired
to specify the bean name to be injected.
Spring Bean Scopes
In Spring, beans can have different scopes which determine their lifecycle and how they are shared among other beans. Here are the main scopes:
Singleton (default): Only one instance of the bean is created per Spring IoC container. This single instance is shared and used by all other beans that require it.
Prototype: A new instance of the bean is created each time it is requested from the container. This is useful when each request needs its own independent instance of the bean.
Request: A new bean is created for each HTTP request. This scope is only valid in the context of a web-aware Spring
ApplicationContext
.Session: A new bean is created for each HTTP session. Like the request scope, this scope is also only valid in the context of a web-aware Spring
ApplicationContext
.Application: A single bean is created for the lifecycle of a Servlet
ServletContext
. This scope is also only valid in the context of a web-aware SpringApplicationContext
.Websocket: A single bean is created for the lifecycle of a WebSocket. This is only valid in the context of a web-aware Spring
ApplicationContext
.
You can specify the scope of a bean using the @Scope
annotation:
@Component
@Scope("prototype")
public class MyPrototypeBean {
// ...
}
In this example, MyPrototypeBean
is a prototype scoped bean, so a new instance will be created each time it is requested from the container.
Method A and Method B are in the same class. Method A is calling Method B. Both methods are transactional.
In Spring, the @Transactional
annotation is used to define the scope of a single database transaction. The database transaction happens inside the scope of a persistence context.
When you have two methods, let's say methodA
and methodB
, and methodA
is annotated with @Transactional
and it calls methodB
:
If
methodB
is in the same class asmethodA
: The@Transactional
annotation has no effect onmethodB
, because it's called insidemethodA
which is already running within a transaction. SomethodB
will participate inmethodA
's transaction.If
methodB
is in a different class and it's also annotated with@Transactional
: Spring will check the propagation setting of the@Transactional
annotation inmethodB
. By default, the propagation setting isREQUIRED
, which meansmethodB
will join the ongoing transaction started inmethodA
.
Here's an example:
@Service
public class ServiceA {
@Autowired
private ServiceB serviceB;
@Transactional
public void methodA() {
// some code...
serviceB.methodB();
}
}
@Service
public class ServiceB {
@Transactional
public void methodB() {
// some code...
}
}
In this example, when methodA
is called, it starts a new transaction. Then it calls methodB
, which joins the ongoing transaction because its propagation setting is REQUIRED
. If the propagation setting was REQUIRES_NEW
, methodB
would start a new transaction, suspending the current one.
Different types of transaction propagation in Spring
The @Transactional
annotation in Spring supports several propagation settings that determine how transactions relate to each other. Here are the different propagation settings:
REQUIRED (default): Supports the current transaction if one exists; otherwise, it starts a new one.
SUPPORTS: Supports the current transaction if one exists; otherwise, executes non-transactionally.
MANDATORY: Supports the current transaction if one exists; otherwise, it throws an exception.
REQUIRES_NEW: Creates a new transaction, suspending the current transaction if one exists.
NOT_SUPPORTED: Executes non-transactionally, suspending the current transaction if one exists.
NEVER: Executes non-transactionally, throwing an exception if a transaction exists.
NESTED: Executes within a nested transaction if a current transaction exists; otherwise, it behaves like
REQUIRED
.
You can specify the propagation setting in the @Transactional
annotation like this:
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void someMethod() {
// ...
}
In this example, someMethod
will always run in a new transaction, suspending the current transaction if one exists.
Can you elaborate more on REQUIRES_NEW transaction propagation?
When the propagation setting is REQUIRES_NEW
, Spring will start a brand new transaction for the annotated method, and the current transaction (if one exists) will be suspended until the new transaction completes.
This is useful when you want the annotated method to always run in a new, independent transaction, separate from any existing transaction.
Here's an example:
@Service
public class ServiceA {
@Autowired
private ServiceB serviceB;
@Transactional
public void methodA() {
// some code...
serviceB.methodB();
}
}
@Service
public class ServiceB {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void methodB() {
// some code...
}
}
When methodB
is annotated with Propagation.REQUIRES_NEW
, it starts a new, separate transaction. If methodB
encounters a runtime exception and its transaction is rolled back, it does not affect methodA
's transaction.
This is because the REQUIRES_NEW
propagation setting suspends the current transaction before starting a new one. So methodA
's transaction and methodB
's transaction are independent of each other.
In other words, even if methodB
fails and its transaction is rolled back, methodA
's transaction can still commit successfully, because they are in separate transactions.
When does @Transactional annotation not work in Spring?
The @Transactional
annotation in Spring may not work as expected in the following scenarios:
Calling a method within the same class: Spring's transaction management is proxy-based. When you call a method annotated with
@Transactional
from within the same class, Spring's transactional proxy is bypassed, and the annotation has no effect.Exception handling: By default, Spring only rolls back the transaction for unchecked exceptions (i.e., subclasses of
RuntimeException
). If a method throws a checked exception, the transaction is not rolled back. You can customize this behavior using therollbackFor
andnoRollbackFor
attributes of the@Transactional
annotation.Transaction propagation: If you have a method running within a transaction, and it calls another method with
Propagation.REQUIRES_NEW
, a new transaction will be started, and the original transaction will be suspended. If the inner transaction fails, it won't affect the outer transaction.Incorrect configuration: If the Spring configuration is not set up correctly,
@Transactional
might not work. For example, if you forget to enable transaction management with@EnableTransactionManagement
in your configuration, the@Transactional
annotations will be ignored.Non-public methods:
@Transactional
will not work on methods that are not public. It will also not work onstatic
methods.Using
this
to reference the method: If you usethis
to reference the method, the transactional aspect will not be weaved in, and the annotation will not work.