Before we talk about Spring or Spring Boot, first we need to understand about "SERVLET" and "Servlet Container"
Provide foundation for building web applications.
Servlet is a Java Class, which handles client request, process it and return the response.
And Servlet Container are the ones which manages the Servlets.
above we can see serverlet mapping specifying what all URLS serverlet 1 will handle
Spring Framework (MVC), solve challenges which exists with Servlets.
Removal of web.xml
this web.ml over the time becomes too big and becomes very difficult to manage and understand.
Spring framework introduced Annotations based configuration.
Inversion of Control (loC)
Servlets depends on Servlet container to create object and maintain its lifecycle.
loC is more flexible way to manage object dependencies and its lifecycle (though Dependency injection)
Makes Unit Testing much easier (due to loose coupling)
Solves Difficult to manage REST APIs
Handling different HTTP methods, request parameters, path mapping make code little difficult to understand.
Spring MVC provides an organised approach to handle the requests and its easy to build RESTful APls.
The another important feature of Spring framework is lot of INTEGRATION available with other frameworks.
This allow Developers to choose different combination of technologies and framework which best fits their requirements like:
Integration with Unit testing framework like Junit or Mockito.
Integration with Data Access framework like Hibernate, JDBC, JPA etc.
Integration with Asynchronous programming.
Similar way, it has different integration available for:
Caching
Messaging
Security etc.
SIMPLE SPRING MVC APPLICATION (NOT SPRING BOOT)
Spring Boot, solve challenges which exists with Spring MVC.
Dependency Management: No need for adding different dependencies separately and also the compatible version headache.WE DONT ADD ANY VERSIONS in pom.xml
Auto Configuration: No need for separately configuring "DispatcherServlet", "AppConfig","EnableWebMvc", "ComponentScan". Spring boot add internally by-default. The component scan will start from main method. @SpringbootApplication is a composite config annotation which handles all configurations
**Embedded Server:**In traditional Spring MVC application, we need to build a WAR file, which is a packaged file containing your application's classes, JSP pages, configuration files, and dependencies.Then we need to deploy this WAR file to a servlet container like Tomcat.But in Spring boot, Servlet container is already embedded, we don't have to do all this stuff.
So, what is Spring boot?
It provides a quick way to create a production ready application.
It is based on Spring framework.
It support "Convention over Configuration".
Use default values for configuration, and if developer don't want to go with convention(the way something is done), they can override it.
It also help to run an application as quick as possible.
Spring boot layered architecture
DTO pacakge specifies the format of Data Transfer Objects (Request DTO/ Response DTO)
Utility pacakge container helper classes / functions which are common among classes
Entity pacakage contain POJOS which map to DB tables (ORM)
Configuration pacakge contains application.properties
Maven
It's a project management tool. Helps developers with:
Build generation
Dependency resolution
Documentation etc.
Maven uses POM (Project Object Model) to achieve this.
- When "maven" command is given, it looks for "pom.ml" in the current directory & get needed configuration.
MAVEN BUILD LIFECYCLE
The <build/> section im pom.xml corresponds with build phases and tasks - phases/tasks can be added or removed
Maven already has Validate phase defined and its goal, but if we want to add more goals or task, then we can use <build> element. And add the goal to specific phase.
-
mvn compile: It will validate and compile your code and put it under ${project.basedir}/target/classes
Bean Lifecycle
Dependency Injection
Different ways of Injection and which one is better?
Field Injection
above @Lazy means that bean created only when used like its being done with @Autowired.(In above screenshot first the constructor object gets printed then Order constructor)
DISADVANTAGE:
Cannot make filed injected fields immutable
How to test this ?
Chances of null pointer exceptions
The above throws a
NullPointerException
because theOrder
object is not properly initialized when we manually create an instance of theUser
class using thenew
keyword. In Spring, dependency injection is managed by the Spring container, and we you bypass the container by manually instantiating the class, the dependencies are not injected.User userObj = new User(); userObj.process();
When we manually create an instance of
User
usingnew User()
, Spring does not get a chance to inject theOrder
dependency.
Below is the corrected code with proper dependency injection:
Order Class:
@Component public class Order { public void process() { System.out.println("Order processed"); } }
User Class:
@Component public class User { private final Order order; @Autowired public User(Order order) { this.order = order; System.out.println("User initialized"); } public void process() { order.process(); } }
Main Application Class:
@SpringBootApplication public class Application { public static void main(String[] args) { ApplicationContext context = SpringApplication.run(Application.class, args); User userObj = context.getBean(User.class); userObj.process(); } }
Explanation
Order Class:
- The
Order
class is annotated with@Component
, making it a Spring-managed bean.
- The
User Class:
The
User
class is also annotated with@Component
.The
Order
dependency is injected via the constructor, which is annotated with@Autowired
. This ensures that Spring injects theOrder
bean when it creates an instance ofUser
.(Avoiding field injection)
Main Application Class:
The
Application
class is annotated with@SpringBootApplication
, which is a convenience annotation that adds@Configuration
,@EnableAutoConfiguration
, and@ComponentScan
.The
ApplicationContext
is obtained by runningSpringApplication.run
(Application.class, args)
.The
User
bean is retrieved from the Spring context usingcontext.getBean(User.class)
, ensuring that all dependencies are properly injected by Spring.
Setter Injection
-
Advantage:
Dependency can be changed any time after the object creation (as object can not be marked as final).
Ease of testing, as we can pass mock object in the dependency easily.
-
Disadvantage:
Field Can not be marked as final. (We can not make it immutable).
Difficult to read and maintained, as per standard, object should be initialized during object creation, so this might create code readability issue.
Constructor Injection (Most preferred)
In Spring Boot, dependency injection can be achieved in several ways, including using the @Autowired
annotation and constructor injection. Constructor injection is a preferred method for dependency injection in Spring Boot because it promotes immutability and makes your code easier to test.
Dependency get resolved at the time of initialization of the Object itself.
Its recommended to use
When only 1 constructor is present, then using @Autowired on constructor is not mandatory.(from Spring version 4.3)
Why Constructor Injection?
Immutability: By using constructor injection, you can make the fields
final
, which ensures that they are not modified after the object is created.Testing: Constructor injection makes it easier to write unit tests. You can create instances of the class and pass mock dependencies directly to the constructor.
Simplicity: It removes the need for the
@Autowired
annotation, making the code cleaner and more straightforward.Fail Fast: If there is any missing dependency, it will fail during compilation itself, rather than tailing during run Time.
Common Issues with DI
Circular Dependecy
- First and foremost, can we refactor the code and remove this cycle dependency:
For example, common code in which both are dependent, can be taken out to separate class. This way we can break the circular dependency.
- Using @Lazy on @Autowired annotation .
Spring will create proxy bean instead of creating the bean instance immediately during application startup.
- We can resolve circular dependecy using @PostConstruct as well
Unsatisfied Dependecy
An "unsatisfied dependency" problem in Spring occurs when the Spring container cannot find a suitable bean to inject into a class. This typically results in exceptions such as NoSuchBeanDefinitionException
or UnsatisfiedDependencyException
. Understanding the causes and solutions for this issue is crucial for developing robust Spring applications.
Common Causes of Unsatisfied Dependency Problems
Missing Bean Definition:
- The required bean is not defined in the Spring context.
Incorrect Bean Type:
- The bean type does not match the expected type in the dependent class.
Ambiguous Dependencies:
- Multiple beans of the same type are available, and Spring cannot decide which one to inject.
Circular Dependencies:
- Two or more beans depend on each other, causing a circular reference.
Ways to Mitigate Unsatisfied Dependency Problems
1. Ensure Bean Definitions
Component Scanning: Ensure that your classes are annotated with appropriate Spring annotations like
@Component
,@Service
,@Repository
, or@Controller
, and that they are within the packages scanned by Spring.Configuration Classes: Define beans explicitly in configuration classes using
@Configuration
and@Bean
.@Configuration public class AppConfig { @Bean public MyService myService() { return new MyService(); } }
2. Match Bean Types
- Check Bean Types: Ensure that the type of the bean being injected matches the type expected by the dependent class.
3. Resolve Ambiguous Dependencies
- @Primary Annotation: Use
@Primary
to mark one bean as the primary candidate when multiple beans of the same type exist.
- @Qualifier Annotation: Use
@Qualifier
to specify which bean to inject when multiple beans of the same type exist.
Dynamically Initialized Beans | Value Annotation
4. Avoid Circular Dependencies
Refactor Code: Refactor your code to break circular dependencies. This might involve redesigning your classes or using setter injection instead of constructor injection.
Java@Component public class A { private B b; @Autowired public void setB(B b) { this.b = b; } } @Component public class B { private A a; @Autowired public void setA(A a) { this.a = a; } }
Use
@Lazy
: Use@Lazy
to delay the initialization of a bean until it is needed, which can help resolve circular dependencies.@Component public class A { private final B b; @Autowired public A(@Lazy B b) { this.b = b; } } @Component public class B { private final A a; @Autowired public B(@Lazy A a) { this.a = a; } }
Annotations
@Component: tells Spring that, you have to manage this class or bean.
@Autowired: tells Spring to resolve and add this object dependency.Look for a bean of thr required type and if found INJECT IT
@Controller : Indicates that class in responsible for handling incoming HTTP Requests
@RestController: Nothing but controller plus responseBody annotation
@RequestMapping: what all apis is this controller hosting
@<Verb>Mapping ex. @GetMapping: nothing but request mapping with http method
@RequestParam: Used to bind, request parameter to controller method parameter.The framework automatically performs type conversion from the request parameter's string representation to
the specified type.
@PathVariable: Used to extract values from the path of the URL and help to bind it to controller method parameter.
@RequestBody: Bind the body of HTTP request (typically JSON) to controller method parameter (java object).
ResponseEntity: It represents the entire HTTP response.
Header, status, response body etc.
How to do type conversion of request paramer string into Custom Object types?:
PROPERTY EDITOR