Sunday, 24 October 2021

Spring, Spring-Boot, Microservices Interview Questions and Answers

» What is a Spring Bean?

A "Spring Bean" is simply a Java object.

When Java objects are created by the Spring Container, then Spring refers to them as "Spring Beans".

The Spring container (application context) is responsible for the creation and lifecycle management of beans.

Spring Beans are created from normal Java classes .... just like Java objects.

In the early days, there was a term called "Java Beans". Spring Beans have a similar concept but Spring Beans do not follow all of the rigorous requirements of Java Beans.

» What's the use-case of IOC (Inversion of Control)?

IoC is for externalizing the construction and management of objects.

Create and manage objects

Inversion of control basically means objects are not responsible for ( or in control of ) creating their own dependencies. Control is 'inverted'. So instead of your code handling bean creation with the 'new' operator -- the framework ( the Spring container ) does the work. You create the components required by your application and the framework manages/calls them into play.

Normally in your code, you create objects like:

Employee emp = new Employee();

But using Spring it will handle the creation of objects for you. It is like outsourcing so instead of you doing the work, outsource it and someone will do the work. This is handled by spring's object factory.

Why is this configuration necessary - To make your application flexible. When using Spring's XML based configuration, you can swap in new implementations without having to recompile your source code.

» Spring container configuration

3 ways to configure a spring container:

1) XML configuration file (Most legacy apps use this)

2) Java Annotation

3) Java Source Code

Java Annotation and Java Source Code are the modern ways to configure spring container

Example: XML configuration file

Create Java Interface and Class

package com.springbasic.ioc;

public interface Sports {
	public String getInfo();
}
package com.springbasic.ioc;

public class Cricket implements Sports {

	@Override
	public String getInfo() {
		String info = "Cricket is a bat-and-ball game played between two teams of eleven players on a field at the centre of which is a 22-yard (20-metre) pitch with a wicket at each end, each comprising two bails balanced on three stumps";
		return info;
	}
}

Create Spring container i.e. applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context.xsd">
    
       
	<!-- Define your beans here -->
	<bean id="mySports" class="com.springbasic.ioc.Cricket">
	</bean>
		

</beans>

Here we have created a bean i.e. mySports and class attribute defines the implementation class name i.e. com.springbasic.ioc.Cricket

Lets create a main class now

package com.springbasic.ioc;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SportsMain {

	public static void main(String[] args) {
		// load spring configuration file
		ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

		// retrieve bean from spring container
		Sports theSport = context.getBean("mySports", Sports.class);

		// call methods on the bean
		String result = theSport.getInfo();
		System.out.println(result);
	}
}

» Spring Dependency Injection

Dependency injection is a pattern used to create instances of objects that other objects rely upon without knowing at compile time which class will be used to provide that functionality or simply the way of injecting properties to an object is called dependency injection. We have 2 ways to inject Dependency injection:

1. Constructor Injection

2. Setter/Getter Injection

More on this: Dependency Injection in Spring

» Constructor Injection:

package com.springbasic.ioc;

public interface FortuneService {
	public String getFortune();
}
package com.springbasic.ioc;

public class HappyFortuneService implements FortuneService {

	@Override
	public String getFortune() {
		return "Its you lucky day today";
	}

}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context.xsd">


	<!-- Define your beans here -->

	<!-- Define dependency -->
	<bean id="myFortune" class="com.springbasic.ioc.HappyFortuneService"></bean>

	<bean id="mySports" class="com.springbasic.ioc.Cricket">	
		<!-- Setup constructor injection -->
		<constructor-arg ref="myFortune"></constructor-arg>
	</bean>


</beans>
package com.springbasic.ioc;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SportsMain {

	public static void main(String[] args) {
		// load spring configuration file
		ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

		// retrieve bean from spring container
		Sports theSport = context.getBean("mySports", Sports.class);

		// call methods on the bean
		String result = theSport.getDailyFortune();
		System.out.println(result);
		
		context.close();
	}
}
package com.springbasic.ioc;

public interface Sports {
	public String getInfo();
	public String getDailyFortune();
}
package com.springbasic.ioc;

public class Cricket implements Sports {

	// define a private field for dependency
	private FortuneService fortuneService;
	
	// define constructor for dependency injection
	public Cricket(FortuneService theFortuneService) {
		fortuneService = theFortuneService;
	}
	
	@Override
	public String getDailyFortune() {
		// Use fortuneService to get a fortune
		return fortuneService.getFortune();
	}
	
	@Override
	public String getInfo() {
		String info = "Cricket is a bat-and-ball game played between two teams of eleven players on a field at the centre of which is a 22-yard (20-metre) pitch with a wicket at each end, each comprising two bails balanced on three stumps";
		return info;
	}	
}

So Spring will actually construct our object (private FortuneService fortuneService;), they'll pass in a dependency (FortuneService theFortuneService), and then we accept it, and then we simply assign it (fortuneService = theFortuneService;). And that's basically a Dependency Injection.

» Bean Scopes

Scope refers to the lifecycle of a bean. It tells you how long does the bean live, how many instances are created and how is the bean shared.

Default scope for Bean is Singleton.

What is Singleton?

The Spring Container creates only one instance of the bean, it's cached in memory and then all requests for that bean will return a shared reference to the same bean, so the end result is that there is only one bean and everyone will share it.

» Additional Spring Bean Scopes

Singleton: Create a single shared instance of the bean. Default scope.

Prototype: Creates a new bean instance for each container request.

Request: Scoped to an HTTP web request. Only used for web apps.

Session: Scoped to an HTTP web session. Only used for web apps.

Global-session: Scoped to a global HTTP web session. Only used for web apps.

» Health Indicators in Spring Boot

spring-boot-health-indicators

» Prototype Scope Example:

Prototype Scope: new object for each request

Example:

<beans ...>
	<bean id="myCoach" class="com.luv2code.springdemo.TrackCoach" scope="prototype">
		....
	</bean>
</beans>

» Defining init and destroy methods - Method Signatures. Special Note about init and destroy Method Signatures:

When using XML configuration, I want to provide additional details regarding the method signatures of the init-method and destroy-method.

Access modifier: The method can have any access modifier (public, protected, private)

Return type: The method can have any return type. However, "void' is most commonly used. If you give a return type just note that you will not be able to capture the return value. As a result, "void" is commonly used.

Method name: The method can have any method name.

Arguments: The method can not accept any arguments. The method should be no-arg.

» Destroy Lifecycle and Prototype Scope:

For "prototype" scoped beans, Spring does not call the destroy method.

In contrast to the other scopes, Spring does not manage the complete lifecycle of a prototype bean: the container instantiates, configures, and otherwise assembles a prototype object, and hands it to the client, with no further record of that prototype instance.

Thus, although initialization lifecycle callback methods are called on all objects regardless of scope, in the case of prototypes, configured destruction lifecycle callbacks are not called. The client code must clean up prototype-scoped objects and release expensive resources that the prototype bean(s) are holding.

This also applies to both XML configuration and Annotation-based configuration.

» When does bean actually get destroyed.

The bean is destroyed when it loses scope.

» How to create code to call the destroy method on prototype scope beans?

Spring won't manage the lifecycle of the prototype bean, so @PreDestroy (etc.) methods need to be called by your own code. Spring won't retain a reference, that means You can destroy prototype beans but custom coding is required.

» What are Java Annotations?

Java annotations are special labels/markers added to Java classes.

It provides the metadata about the class

It processed at compile time or run-time for special processing

Example:

public class Employee implements Department{
	@override	// Here @override is a Java Annotation
	public String getEmpInfo(){
		return "info";
	}
}

» Why to use Spring configuration with Annotations?

If you are working on a large project then XML configuration can be verbose.

Annotations minimizes the XML configuration.

Once you add an annotation to a class, then Spring will actually scan your Java classes for those annotations. When it finds a class that has a special Spring annotation on it, it'll automatically register that bean with the Spring container.

So instead of doing everything long hand via XML config file, Spring will just scan a bean and will register it with the Spring container.

Basically Spring will help you out here and do a lot of work for you in the background by scanning your classes.

Example:

1) Enable component scanning in spring config file (applicationContext.xml)

<beans ..>
   <context:component-scan base-package="com.demo.mypackage">
</beans>

here spring will scan com.demo.mypackage recursively

2) Add the @Component Annotation to you Java Class

@Component("mycomp") // here is the component annotation with spring bean
public class Employee implements Department{
	@override	// Here @override is a Java Annotation
	public String getEmpInfo(){
		return "info";
	}
}

It will register mycomp spring bean automatically

3) Retrieve bean from Spring Container

ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext('applicationContext.xml');

Emp e = context.getBean("mycom", Emp.class);

context.close();

» How would you connect a database with Spring boot application? Explain with simple examples

Connecting a database with a Spring Boot application involves several steps, including configuring the database connection properties, defining data models, creating repositories, and optionally, setting up the database schema. Here is a simple example using a MySQL database.

Step 1: Add Dependencies

First, add the necessary dependencies to your `pom.xml` file for Spring Boot and MySQL.

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>

Step 2: Configure Database Properties

Add your database connection properties to the `application.properties` file.

properties

spring.datasource.url=jdbc:mysql://localhost:3306/mydatabase
spring.datasource.username=myuser
spring.datasource.password=mypassword
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5Dialect

Step 3: Define a Data Model

Create an entity class that maps to a database table.
//java
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private String email;

    // Getters and Setters
}

Step 4: Create a Repository Interface

Create a repository interface for the `User` entity.

//java
import org.springframework.data.jpa.repository.JpaRepository;

public interface UserRepository extends JpaRepository<User, Long> {
}

Step 5: Create a Service Class

Create a service class to handle business logic.

//java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

    public List<User> getAllUsers() {
        return userRepository.findAll();
    }

    public User getUserById(Long id) {
        return userRepository.findById(id).orElse(null);
    }

    public User saveUser(User user) {
        return userRepository.save(user);
    }

    public void deleteUser(Long id) {
        userRepository.deleteById(id);
    }
}

Step 6: Create a Controller Class

Create a controller class to handle HTTP requests.

//java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/users")
public class UserController {

    @Autowired
    private UserService userService;

    @GetMapping
    public List<User> getAllUsers() {
        return userService.getAllUsers();
    }

    @GetMapping("/{id}")
    public User getUserById(@PathVariable Long id) {
        return userService.getUserById(id);
    }

    @PostMapping
    public User createUser(@RequestBody User user) {
        return userService.saveUser(user);
    }

    @DeleteMapping("/{id}")
    public void deleteUser(@PathVariable Long id) {
        userService.deleteUser(id);
    }
}

Step 7: Run the Application

Run your Spring Boot application. Spring Boot will automatically configure the data source and JPA based on the properties you defined in the `application.properties` file.

» How would you do validation in spring boot? What are the different ways? Explain with simple example

Validation in Spring Boot can be handled using various methods. The most common approaches include using Hibernate Validator (JSR-380), custom validators, and integrating third-party validation libraries. Here's a detailed explanation of each method with simple examples.

Method 1: Using Hibernate Validator (JSR-380)

Hibernate Validator is the reference implementation of the Bean Validation API. It provides a simple way to validate user input.

Step 1: Add Dependencies

Add the necessary dependencies to your `pom.xml` file.

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-validation</artifactId>
    </dependency>
</dependencies>

Step 2: Create a Model Class

Define a model class with validation annotations.

//java
import javax.validation.constraints.Email;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Size;

public class User {
    @NotEmpty(message = "Name is required")
    @Size(min = 2, max = 30, message = "Name must be between 2 and 30 characters")
    private String name;

    @NotEmpty(message = "Email is required")
    @Email(message = "Email should be valid")
    private String email;

    // Getters and Setters
}

Step 3: Create a Controller

Use the `@Valid` annotation to trigger validation.

//java
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import org.springframework.validation.BindingResult;

@RestController
@RequestMapping("/users")
public class UserController {

    @PostMapping
    public String createUser(@Valid @RequestBody User user, BindingResult result) {
        if (result.hasErrors()) {
            return result.getAllErrors().toString();
        }
        return "User is valid";
    }
}

Method 2: Custom Validator

Create a custom validator for more complex validation logic.

Step 1: Create a Custom Annotation

//java
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Constraint(validatedBy = AgeValidator.class)
@Target({ ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface ValidAge {
    String message() default "Invalid age";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

Step 2: Create the Validator Class

//java
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

public class AgeValidator implements ConstraintValidator<ValidAge, Integer> {
    @Override
    public void initialize(ValidAge age) {}

    @Override
    public boolean isValid(Integer age, ConstraintValidatorContext context) {
        return age != null && age >= 18 && age <= 60;
    }
}

Step 3: Apply the Custom Annotation

//java
public class User {
    @ValidAge
    private Integer age;

    // Other fields, getters and setters
}

Method 3: Integrating Third-Party Validation Libraries

You can also integrate third-party libraries such as Apache Commons Validator or Google Guava for specific validation needs.

Example: Using Apache Commons Validator

Add the dependency to your `pom.xml` file.

<dependency>
    <groupId>commons-validator</groupId>
    <artifactId>commons-validator</artifactId>
    <version>1.7</version>
</dependency>

Usage Example

//java
import org.apache.commons.validator.routines.EmailValidator;

public class User {
    private String email;

    public boolean isValidEmail() {
        EmailValidator validator = EmailValidator.getInstance();
        return validator.isValid(email);
    }

    // Other fields, getters and setters
}

» What are Microservices?

Microservices is a way to split your application into set of smaller, interconnected services instead of building a single monolithic application.

Each microservices has its own architecture consisting of its own business logic.

Its beneficial for you as you can test these services as their own and different developer teams on their own or all of them can be released individually to production at a time.

» What is Spring Cloud Config Server?

Spring cloud Config Server is a common microservice that will be talking with all other microservices. i.e. It provides server-side and client-side support for externalized configuration.

All the common configuration will be placed in Spring Cloud Config Server microservice.

Maven users can add the below dependency into the pom.xml file.

<dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-config-server</artifactId>
</dependency>

More on this: https://www.tutorialspoint.com/spring_boot/spring_boot_cloud_configuration_server.htm

» What is Load Balancing?

Load balancing improves the distribution of workloads across multiple Microservices or you can say Computing Resources

Consider you have a microservice i.e. ProductService running on multiple instances and you are getting thousands of request for ProductService. In this case you can divide the request load among these services by using multiple running instances.

There are two types of load balancer: 1) Client Side and 2) Server Side

Client Side Load Balancer: The load balancing decision resides with the client itself. The client can take the help of a naming server (eg. Netflix Eureka) to get the list of registered backend server instances, and then route the request to one of these backend instances using client-side load balancing libraries like Netflix Ribbon.

Advantages of client-side load balancing:

- No more single point of failure as in the case of the traditional load balancer approach.

- Reduced cost as the need for server-side load balancer goes away.

- Less network latency as the client can directly invoke the backend servers removing an extra hop for the load balancer.

Server Side Load Balancer: All backend server instances are registered with a central load balancer. A client requests this load balancer which then routes the request to one of the server instances using various algorithms like round-robin. AWS ELB is a prime example of server-side load-balancing that registers multiple EC2 instances launched in its auto-scaling group and then routes the client requests to one of the EC2 instances.

Advantages of server-side load balancing:

- Simple client configuration: only need to know the load-balancer address.

- Clients can be untrusted: all traffic goes through the load-balancer where it can be looked at.

- Clients are not aware of the backend servers.

» What Ribbon Load Balancer?

It is a client side load balancer that gives you a lot of control over the behaviour of HTTP and TCP client.

Dependency: spring-cloud-starter-netflix-ribbon

When we use Feign, the Ribbon also applies. Feign is a client for calling other microservices. You need to add dependency of it in pom.xml

» What Eureka Naming Server?

Eureka Naming Server is an application that holds information about all client service applications.

Each microservice registers itself with Eureka Naming Server.

The naming server registers the client services with their port numbers and ip addresses.

It is also known as Discovery Server.

It is REST-based server that is used in AWS cloud services for load balancing and fallover for middle tier services

» What is API Gateway?

An API gateway is an API management tool that sits between a client and a collection of backend services.

It takes care of invites or requests, matching them to the suitable stations or services for request/call processing, and sends them back to the target resource.

It implements some of the common features like:

Authentication and Authorization

Rate Limit: If you want to execute certain no. of request (Ex. 100 per hour) for that we can use Rate Limit.

Fault Tolerance: If your microservice is dependent on another microservice and that microservice is not up and running so in this kind of situation we need to provide a default output to the calling microservice that is what Fault Tolerance does.

Service Aggregation: Suppose a client application wants to use your multiple APIs i.e. 10 or 100. In this case you can't provide an endpoint of all the APIs to client application instead you will just provide one endpoint and internally you can connect with you APIs and provide only single implementation to client side application.

» How can you optimize the startup time of app?

Optimizing the startup time of a Spring Boot application is essential for ensuring a smooth user experience. Here are some simple ways to optimize startup time:

  • Reduce Dependencies:

    - Minimize the number of dependencies your application relies on. Each dependency adds to the application's startup time, as Spring Boot needs to initialize and configure them. - Review your pom.xml or build.gradle file and remove any unnecessary dependencies.

  • Lazy Initialization:

    - Utilize lazy initialization to defer the loading of beans until they are actually needed. This can significantly reduce the startup time by preventing unnecessary bean creation during application startup. - Use @Lazy annotation on beans that don't need to be initialized eagerly.

  • Profile Specific Configuration:

    - Utilize Spring profiles to load only the necessary configuration based on the environment (e.g., development, production). - Avoid loading unnecessary configurations and beans for each environment, which can help reduce startup time.

  • Optimize Auto-Configuration:

    - Spring Boot's auto-configuration feature automatically configures beans based on the classpath and the presence of certain dependencies. - Review and optimize auto-configuration to only include necessary configurations for your application. Exclude auto-configurations that are not required.

  • Use Spring Initializr wisely:

    - When creating a Spring Boot project using Spring Initializr, choose only the dependencies you need. Avoid selecting unnecessary dependencies, as they can increase the startup time of your application.

  • Enable Spring Boot DevTools (for development):

    - Spring Boot DevTools provide features like automatic application restarts and live reload during development. - While DevTools may slightly increase startup time in development mode, they can significantly improve developer productivity and overall development speed.

Example: Suppose you have a Spring Boot application that has multiple dependencies and beans that are initialized eagerly. To optimize its startup time:

  • Review the pom.xml file and remove any unnecessary dependencies that are not being used in the application.
  • Identify beans that can be lazily initialized and annotate them with @Lazy.
  • Use Spring profiles to load only the necessary configurations based on the environment.
  • Review auto-configuration classes and exclude any unnecessary auto-configurations.
  • When using Spring Initializr to create a new project, only select the dependencies that your application needs.
  • During development, enable Spring Boot DevTools to take advantage of its features for faster development iterations.

By implementing these optimizations, you can significantly reduce the startup time of your Spring Boot application, providing a better user experience and faster application responsiveness.

» Explain server-side caching and client-side caching

Caching is a technique used to store frequently accessed data in a temporary storage area, allowing for faster access and reduced load on the underlying systems. Caching can be implemented both on the server side and the client side, each serving different purposes and employing various techniques.

Server-Side Caching

Server-side caching stores data on the server to reduce the time and resources needed to generate responses to client requests. It is particularly useful for reducing database load and improving the performance of web applications.

Key Techniques for Server-Side Caching:

1. In-Memory Caching: This technique involves storing data in the server's memory for quick access. Examples include using caches like Ehcache or Guava Cache.

// Example using Guava Cache
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;

public class CacheExample {
    private LoadingCache<String, String> cache;

    public CacheExample() {
        cache = CacheBuilder.newBuilder()
                .maximumSize(100)
                .build(
                    new CacheLoader<String, String>() {
                        public String load(String key) {
                            return getDataFromDatabase(key);
                        }
                    });
    }

    private String getDataFromDatabase(String key) {
        // Simulate database access
        return "Data for " + key;
    }

    public String getData(String key) {
        return cache.getUnchecked(key);
    }
}

2. Distributed Caching: Distributed caching involves storing cache data across multiple servers. This is useful for large-scale applications. Examples include using Redis or Memcached.

// Example using Redis
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

@Service
public class RedisCacheExample {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    public void cacheData(String key, String value) {
        redisTemplate.opsForValue().set(key, value);
    }

    public String getData(String key) {
        return (String) redisTemplate.opsForValue().get(key);
    }
}

3. HTTP Caching: This involves caching responses at the server level using HTTP headers like Cache-Control and ETag.

// Example setting Cache-Control header in Spring Boot
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletResponse;

@RestController
public class CacheController {

    @GetMapping("/cached-endpoint")
    public String cachedEndpoint(HttpServletResponse response) {
        response.setHeader("Cache-Control", "max-age=3600");
        return "Cached response";
    }
}

» Client-Side Caching

Client-side caching stores data on the client's browser or device, reducing the need to fetch data from the server on every request. This improves the user experience by decreasing load times.

Key Techniques for Client-Side Caching:

1. Browser Caching: This involves using HTTP headers to instruct the browser to cache certain resources (e.g., images, stylesheets, scripts).

// Example setting Cache-Control header for browser caching
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletResponse;

@RestController
public class BrowserCacheController {

    @GetMapping("/static-resource")
    public void staticResource(HttpServletResponse response) {
        response.setHeader("Cache-Control", "max-age=86400");
        response.setContentType("text/plain");
        response.getWriter().write("This is a static resource.");
    }
}

2. Service Workers: Service workers are a feature of modern web browsers that allow for caching of resources and handling network requests. They are commonly used in Progressive Web Apps (PWAs).

// Example of a basic service worker script
self.addEventListener('install', function(event) {
    event.waitUntil(
        caches.open('v1').then(function(cache) {
            return cache.addAll([
                '/',
                '/index.html',
                '/styles.css',
                '/script.js',
                '/image.png'
            ]);
        })
    );
});

self.addEventListener('fetch', function(event) {
    event.respondWith(
        caches.match(event.request).then(function(response) {
            return response || fetch(event.request);
        })
    );
});

3. Local Storage and Session Storage: These are browser-based storage options that allow for storing data on the client-side, useful for caching small amounts of data.

// Example using local storage in JavaScript
localStorage.setItem('username', 'john_doe');
const username = localStorage.getItem('username');

» What is role of Actuator in monitoring startup time?

Spring Boot Actuator is a set of features that can track and monitor startup information for Spring Boot applications. It can help you understand the application's startup sequence, context lifecycle, and time spent during startup phases.

Actuator exposes operational information about the running application, such as:

Health Provides an endpoint to monitor the application's health status

Metrics Monitors key metrics such as the response time and number of requests

Logging Retrieves log files, which is useful in debugging

Auditing Tracks users' actions

Actuator uses HTTP endpoints or JMX beans to enable interaction.

To enable Spring Boot Actuator, you can:

1. Add the spring-boot-starter-actuator dependency to your POM

2. Add the spring-boot-starter-web dependency

3. Set the configuration property in your application.properties file

Actuator allows you to monitor your application's performance and resource usage with helpful metrics, such as memory usage, request rates, and response times. This information can help you diagnose issues and optimize your application's performance.

» Explain how classpath scanning mechanism affects startup time

Classpath scanning is a mechanism that Spring uses to detect classes that need to be managed by the Spring container. This is done by scanning the classpath for classes that are annotated with the @Component, @Service, @Repository, or @Controller annotations.

The classpath scanning mechanism can have a significant impact on startup time, especially for large applications with a large number of classes. This is because Spring needs to scan the entire classpath for each class that it needs to manage.

There are a few things that you can do to reduce the impact of classpath scanning on startup time:

Use the @ComponentScan annotation to specify the packages that Spring should scan. This will prevent Spring from scanning the entire classpath, which can save a significant amount of time.

Use custom filters to exclude classes from being scanned. This can be useful for excluding classes that are not needed by Spring, such as test classes or classes that are only used in certain environments.

Use a Spring Boot starter project: Spring Boot starter projects include a number of features that can help to reduce startup time, including classpath scanning optimization.

» What are some tools/techniques to analyze startup time?

1. Spring Boot Actuator:

- Utilize Actuator's /metrics and /health endpoints to gather metrics and health status during startup. - Actuator provides insights into bean initialization, application context creation, and other startup-related metrics.

2. Application Profilers:

- Use profiling tools like YourKit, VisualVM, or JProfiler to profile the application during startup. - Profilers help identify hotspots, memory usage, and thread activity that contribute to startup time.

3. Logging:

- Enable debug logging during startup to capture detailed logs of bean initialization, component scanning, and other startup processes. - Analyze log messages to identify bottlenecks, errors, or delays during startup.

4. Application Monitoring Tools:

- Use application monitoring tools like New Relic, Datadog, or AppDynamics to monitor application performance in real-time. - These tools provide insights into CPU usage, memory consumption, and response times during startup.

5. Spring Boot DevTools:

- Enable DevTools to automatically restart the application when changes are detected during development. - DevTools provide quick feedback on startup time improvements by reducing the need for manual restarts.

6. Custom Timing Metrics:/

- Instrument the application code with custom timing metrics to measure the duration of specific startup tasks. - Use libraries like Micrometer or Spring Boot's MeterRegistry to record and report custom metrics.

7. Benchmarking Tools:

- Use benchmarking tools like JMH (Java Microbenchmark Harness) to benchmark specific startup tasks or components. - Benchmarking helps identify performance bottlenecks and evaluate the impact of optimizations.

8. Static Code Analysis:

- Perform static code analysis using tools like SonarQube or Checkstyle to identify potential code smells or anti-patterns that may impact startup time. - Addressing code quality issues can improve overall application performance, including startup time.

» What are Fault Tolerance and Circuit Breaker?

Fault Tolerance:

Think of a power generator at a hospital. If there's a sudden power outage, the generator kicks in to supply electricity, ensuring that critical operations like surgeries can continue without interruption. Here, the generator acts as a backup to ensure the hospital remains operational even if the primary power source fails.

Fault tolerance is like having a backup plan. If one part of a system fails or has a problem, another part can step in to keep things running smoothly.

Circuit Breaker:

Imagine you're using multiple appliances in your kitchen, such as a toaster, microwave, and blender. If all these appliances are running simultaneously and draw too much power, it could overload the circuit. A circuit breaker in your home's electrical panel detects this overload and "trips," cutting off the power to prevent overheating or electrical fires. Once the issue is resolved, you can reset the circuit breaker to restore power safely.

A circuit breaker is like a safety switch in your home's electrical panel. If there's a sudden surge of electricity or a fault in the wiring, the circuit breaker "trips" or "breaks" the circuit, cutting off the power to prevent damage or fires.

So, fault tolerance ensures that if one component fails, other components can continue to operate, maintaining system availability and reliability. On the other hand, a circuit breaker mechanism helps in preventing system overload or failures by "breaking" or "tripping" when certain thresholds are exceeded, thereby protecting the system and allowing it to recover gracefully without causing widespread failures or outages.

» What is the purpose of Spring Boot's @ConfigurationProperties annotation?

The @ConfigurationProperties annotation in Spring Boot is used to bind external configuration properties (e.g., from properties files, YAML files, environment variables) to a Java object. This allows for type-safe and structured configuration management.

Key Features:

Grouping Properties: It can bind hierarchical properties to a POJO (Plain Old Java Object) class, making it easier to manage and group related configurations.

Type Safety: Provides type safety by binding properties directly to strongly-typed fields in a POJO.

Nested Properties: Supports nested properties, allowing for complex configuration structures.

Ease of Use: Simplifies the management of multiple properties by binding them all to a single class.

How to Use @ConfigurationProperties

Step 1: Create a POJO Class: Define a class with fields corresponding to the properties you want to bind. Use standard getter and setter methods.

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Component
@ConfigurationProperties(prefix = "app")
public class AppConfig {
    private String name;
    private String description;

    // Getters and Setters
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }
}

Step 2: Define Properties in application.properties or application.yml:

# application.properties

app.name=MyApp
app.description=This is my application

Step 3: Access Configuration: You can now inject AppConfig wherever you need to access these properties.

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class AppController {

    @Autowired
    private AppConfig appConfig;

    @GetMapping("/config")
    public String getConfig() {
        return "Name: " + appConfig.getName() + ", Description: " + appConfig.getDescription();
    }
}

How does it differ from @Value annotation?

@Value Annotation:

Single Property Binding: The @Value annotation is used to inject a single property directly into a field.

Direct Injection: It directly injects the value from the properties file, environment variable, or system property.

No Type Safety for Complex Structures: Not ideal for binding complex nested properties or multiple related properties.

Example:

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class AppConfig {

    @Value("${app.name}")
    private String name;

    @Value("${app.description}")
    private String description;

    // Getters and Setters
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }
}

Summary of Differences

Purpose:
@ConfigurationProperties: Designed for binding multiple properties to a structured object (POJO). Ideal for complex configurations and nested properties.
@Value: Used for injecting individual property values directly into fields.

Use Case:
@ConfigurationProperties: Use when you have a group of related properties that you want to manage together.
@Value: Use when you need to inject a single property.

Type Safety:
@ConfigurationProperties: Provides type safety and supports complex property structures.
@Value: Suitable for simple, single property injections without hierarchical binding.

» Explain the purpose of Spring Boot profiles

Spring Boot profiles provide a way to segregate parts of your application configuration and make it available only in certain environments. This is useful for managing different configurations for different stages of the application lifecycle, such as development, testing, and production.

Key Features and Benefits:

Environment-Specific Configuration: Profiles allow you to define environment-specific properties. For example, you can have separate configurations for development, testing, and production environments.

Dynamic Configuration: You can activate different profiles at runtime, enabling dynamic switching of configurations without changing the codebase.

Property Overriding: Profiles can override default properties defined in the application configuration. This means you can have a base configuration and override specific values as needed for each profile.

Conditional Beans: Profiles can be used to conditionally create beans using the @Profile annotation, allowing for environment-specific bean creation.

How to Use Spring Boot Profiles

Step 1: Define Profile-Specific Properties: Create profile-specific property files like application-dev.properties, application-test.properties, and application-prod.properties.

# application-dev.properties
app.name=MyApp (Development)
app.description=This is my development environment



# application-test.properties
app.name=MyApp (Test)
app.description=This is my test environment



# application-prod.properties
app.name=MyApp
app.description=This is my production environment

Step 2: Activate a Profile: You can activate a profile by setting the spring.profiles.active property. This can be done in several ways:

1. Via Command Line: Use the --spring.profiles.active argument.

$ java -jar myapp.jar --spring.profiles.active=dev

2. Via Environment Variable: Set the environment variable SPRING_PROFILES_ACTIVE.

export SPRING_PROFILES_ACTIVE=dev

3. Via application.properties: Set the property in your application.properties file.

spring.profiles.active=dev

Using @Profile Annotation for Conditional Beans

You can use the @Profile annotation to define beans that should only be created under certain profiles.

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;

@Configuration
public class AppConfig {

    @Bean
    @Profile("dev")
    public MyService devMyService() {
        return new MyService("Development Service");
    }

    @Bean
    @Profile("prod")
    public MyService prodMyService() {
        return new MyService("Production Service");
    }
}

» How to expose an API in Spring Boot without using @RestController?

To explose an API without @RestController use the @Controller annotation. Use @ResponseBody on the methods that should return a response body directly.

Example:

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class MyController {

    @GetMapping("/hello")
    @ResponseBody
    public String sayHello() {
        return "Hello, World!";
    }
}

Using @Controller and @ResponseBody is a viable alternative to @RestController for exposing APIs in a Spring Boot application.

» EnableConfigServer in Spring Boot

The `@EnableConfigServer` annotation in Spring Boot is used to create a central configuration server. This server provides a way to manage and serve configuration properties for multiple applications from a single, centralized location. By using this server, you can ensure that all your applications have consistent configuration settings, which simplifies the management and updating of these settings across different environments such as development, testing, and production.

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;

@SpringBootApplication
@EnableConfigServer
public class ConfigServerApplication {

    public static void main(String[] args) {
        SpringApplication.run(ConfigServerApplication.class, args);
    }
}

The @EnableConfigServer annotation turns your Spring Boot application into a Spring Cloud Config Server. This server can serve configuration properties to multiple client applications.

» How do you create microservices in in Spring boot?

In today's digital era, microservices architecture offers scalability and flexibility. Let's explore how to create a simple e-commerce app using Spring Boot with three microservices: Product, Order, and User.

Setup Spring Boot Projects

Generate three Spring Boot projects using Spring Initializr: product-service, order-service, and user-service.

Define Dependencies

Add Spring Web, Spring Data JPA, and H2 Database dependencies to each project's pom.xml.

Create Microservice Components

Product Service

Create a Product entity, repository, and controller for CRUD operations.

@Entity
public class Product {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private String description;
    private BigDecimal price;
    
    // Getters and Setters
}

Repository: Create a ProductRepository interface extending JpaRepository.

@Repository
public interface ProductRepository extends JpaRepository<Product, Long> {
}

Controller: Implement a RESTful API to fetch products.

@RestController
@RequestMapping("/api/products")
public class ProductController {
    @Autowired
    private ProductRepository productRepository;

    @GetMapping
    public List<Product> getAllProducts() {
        return productRepository.findAll();
    }
}

Order Service: Similar to the Product Service, create entities (Order, OrderItem) and corresponding repositories and controllers.

User Service: Define a User entity and implement CRUD operations using repositories and controllers.

Configure Service Communication:

Use Spring Cloud Eureka for service registration and discovery. Each microservice registers itself with the Eureka server and looks up dependencies via service names.

Implement Service Interactions:

In the Order Service, when creating an order, call the Product Service to verify product availability and User Service to validate user details.

Dockerize Microservices

Create Dockerfile for each microservice to containerize them. Use docker-compose to manage multi-container Docker applications, defining services, networks, and volumes.

Implement Circuit Breakers

Use Spring Cloud Circuit Breaker (Hystrix) to handle failures gracefully between service interactions.

Build and Deploy

Build Docker images for each microservice and deploy them to a container orchestration platform like Kubernetes or cloud platforms (AWS, Azure, Google Cloud).

» Explain the components (entities, repositories, controllers) for each microservice. Explain how service communication is implemented using Spring Cloud Eureka and service interactions between microservices.

Components of Microservices:

Entities:

Think of entities as the blueprint for your data. In our e-commerce example, each microservice has its data structure:

Product Service: Defines what a product looks like (e.g., name, price).

Order Service: Defines what an order looks like (e.g., items, customer details).

User Service: Defines what a user looks like (e.g., username, email).

Repositories:

Repositories are like specialized libraries where you can save, retrieve, and manage your data.

Product Repository: This is where you'd save, fetch, or delete product information.

Order Repository: This is where you'd handle saving and fetching order details.

User Repository: This manages user-related tasks like saving user details.

Controllers:

Controllers act as traffic controllers for your application, deciding what to do when you receive a request.

Product Controller: When someone wants product info, this controller handles it.

Order Controller: This manages requests related to orders, like placing a new order.

User Controller: When someone needs user details or wants to create a new user, this controller takes care of it.

Service Communication using Spring Cloud Eureka:

Imagine you have three friends, and instead of calling their names whenever you want something, you use a directory or a phonebook to find them easily. Spring Cloud Eureka acts like that phonebook for your microservices.

Registering Services: Each microservice registers itself with Eureka when it starts up. It's like adding your friend's contact details to the phonebook.

Discovering Services: If one microservice needs to talk to another, instead of hardcoding the location, it asks Eureka where the other service is. This way, it can find and communicate with other services without guessing their locations.

Service Interactions Between Microservices:

Imagine you're buying a product online. Multiple things need to happen behind the scenes:

You browse products (Product Service).

You add products to the cart and place an order (Order Service).

You might need to log in or create an account (User Service).

These services don't work in isolation; they talk to each other:

Order Service might ask the Product Service if a product is available when you try to buy it.

When creating an order, the Order Service might check with the User Service to ensure you're a valid user.

This interaction is crucial because each microservice handles its specialized task, but they work together to provide a seamless experience for you, the user. And Spring Cloud Eureka ensures they find and communicate with each other easily.

» Event-Driven Architecture (EDA) in Java

Event-Driven Architecture (EDA) is a software design pattern where the flow of the application is determined by events such as user actions, sensor outputs, or messages from other systems.In EDA, the communication between components is based on events rather than direct method calls or API requests. This allows for loose coupling between different parts of the system and supports asynchronous processing of events.

Components of Event-Driven Architecture:

Event: An occurrence or state change that triggers a response from the system.

Event Producer: Generates and publishes events when certain conditions or actions occur.

Event Consumer: Listens for specific types of events and reacts accordingly.

Example: Consider an online retail application that uses Event-Driven Architecture for its order processing system

Event: Placing an order on the website.

Event Producer: When a customer places an order, the application generates an OrderPlacedEvent containing details like order ID, items purchased, and customer information.

Event Consumers: Different parts of the system can react to this event:

Inventory Service: Listens for OrderPlacedEvent to update the stock levels of items.

Notification Service: Sends an email or SMS to the customer about the order confirmation.

Billing Service: Generates an invoice and processes payment based on the order details.

Example in Java using Spring Boot and Kafka:

// DEFINE EVENT

public class OrderPlacedEvent {
    private String orderId;
    private List<Item> items;
    private Customer customer;
    // getters and setters
}
// EVENT PRODUCER

@RestController
@RequestMapping("/orders")
public class OrderController {
    
    @Autowired
    private KafkaTemplate<String, OrderPlacedEvent> kafkaTemplate;

    @PostMapping("/place")
    public ResponseEntity<String> placeOrder(@RequestBody Order order) {
        // Logic to create order and generate event
        OrderPlacedEvent event = new OrderPlacedEvent(order.getId(), order.getItems(), order.getCustomer());
        kafkaTemplate.send("order-topic", event);
        return ResponseEntity.ok("Order placed successfully!");
    }
}
// EVENT CONSUMERS

// 1) INVENTORY SERVICE CONSUMER

@Service
@KafkaListener(topics = "order-topic")
public class InventoryService {
    
    @KafkaHandler
    public void handleOrderPlacedEvent(OrderPlacedEvent event) {
        // Logic to update inventory based on event details
        System.out.println("Updating inventory for order: " + event.getOrderId());
    }
}


// NOTIFICATION SERVICE CONSUMER

@Service
@KafkaListener(topics = "order-topic")
public class NotificationService {
    
    @KafkaHandler
    public void handleOrderPlacedEvent(OrderPlacedEvent event) {
        // Logic to send notification to customer
        System.out.println("Sending notification to customer: " + event.getCustomer().getEmail());
    }
}

» Are enums Singleton in Java?

Yes, enums in Java are effectively singletons because they ensure only one instance of each enum constant exists within the JVM. This inherent feature makes enums a recommended choice when you need a singleton instance with a predefined set of values.

When you declare an enum type, Java ensures that only one instance of each enum constant exists within the JVM for that enum type. This makes enums a convenient and safe way to implement the Singleton pattern in Java.

Why Enums are Singleton?

Single Instances: Each enum constant is instantiated once when the enum type is loaded by the JVM. For example, if you have an enum Color with constants RED, GREEN, and BLUE, there will be only one instance of RED, GREEN, and BLUE throughout your application.

Instance Control: You cannot create instances of an enum using new, ensuring that the instances are controlled and limited to those defined by the enum constants.

Thread Safety: Enums are inherently thread-safe due to their static nature. The instances are created statically when the enum class is initialized.

public enum Color {
    RED,
    GREEN,
    BLUE
}

Here, Color.RED, Color.GREEN, and Color.BLUE are instances of the Color enum. They are singletons because Java guarantees that each enum constant is instantiated only once.

» Why do we use readResolve in singletons

When you serialize and then deserialize a Singleton object in Java, the default behavior of deserialization is to create a new instance of the class. This breaks the Singleton pattern because now you have multiple instances of what should be a single instance class.

How readResolve() Solves This Problem?

By implementing the readResolve() method in your Singleton class, you can control what object should be returned after deserialization. This method allows you to enforce that only the original Singleton instance is returned, regardless of how many times the Singleton object is serialized and deserialized.

readResolve() is invoked by the serialization mechanism after the object has been deserialized. You can use this method to return the existing Singleton instance, thereby preserving the Singleton pattern.

import java.io.Serializable;

public class Singleton implements Serializable {
    private static final long serialVersionUID = 1L;
    
    // Private constructor to prevent instantiation from outside
    private Singleton() {}

    private static class SingletonHelper {
        private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance() {
        return SingletonHelper.INSTANCE;
    }
    
    // Method to ensure Singleton integrity during deserialization
    protected Object readResolve() {
        return getInstance(); // Returns the existing instance
    }
}

Private Constructor: Prevents instantiation of the Singleton class from outside.

SingletonHelper Class: Nested static class that holds the Singleton instance.

getInstance() Method: Provides access to the Singleton instance.

readResolve() Method: Overrides the default deserialization behavior. It returns the existing Singleton instance instead of allowing the creation of a new one.

» How to create an immutable class in Java?

Creating an immutable class in Java ensures that once an object is created, its state cannot be changed.

package com.example.immutableclass;

import java.util.Collections;
import java.util.List;
import java.util.ArrayList;


public final class ImmutableClassExample {
    private final int id;
    private final String name;
    private final List<String> emails; // Mutable field

    // Constructor initializes all fields
    public ImmutableClassExample(int id, String name, List<String> emails) {
        this.id = id;
        this.name = name;
        if (emails == null) {
            this.emails = Collections.emptyList(); // Immutable empty list
        } else {
            this.emails = new ArrayList<>(emails); // Defensive copy
        }
    }

    // Getter methods for all fields, no setters
    public int getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    // Return a defensive copy of mutable field
    public List<String> getEmails() {
        return new ArrayList<>(emails);
    }

public static void main(String[] args) {
	
	// Example usage
    List<String> emails = new ArrayList<>();
    emails.add("john.doe@example.com");
    emails.add("jane.smith@example.com");

	
    ImmutableClassExample immutableObj = new ImmutableClassExample(1, "John Doe", emails);
    System.out.println("ID: " + immutableObj.getId());
    System.out.println("Name: " + immutableObj.getName());
    System.out.println("Emails: " + immutableObj.getEmails());
    }
}

final Class: Prevents subclassing.

private final Fields: Ensures fields cannot be changed after object creation.

Constructor: Initializes all fields, including making a defensive copy of any mutable objects passed to it.

No Setter Methods: No methods that modify the state of the object.

Getter Methods: Provide read-only access to fields, with defensive copies of any mutable fields.

Creating immutable classes in Java involves making the class final, using private final fields, initializing all fields via the constructor, and ensuring no setter methods are provided. This guarantees that objects of the class cannot be modified after creation, promoting better code reliability and thread safety in Java applications.

No comments:

Post a Comment