CS F213 Objected Oriented Programming Labsheet 13

Java Spring Boot

Spring Boot is an open source Java-based framework used to create a micro Service. It provides a good platform for Java developers to develop a stand-alone and production-grade spring application that you can just run. You can get started with minimum configurations without the need for an entire Spring configuration setup.

How does it work?

Spring Boot automatically configures your application based on the dependencies you have added to the project by using @EnableAutoConfiguration annotation. For example, if MySQL database is on your classpath, but you have not configured any database connection, then Spring Boot auto-configures an in-memory database.

The entry point of the spring boot application is the class contains @SpringBootApplication annotation and the main method.

Spring Boot automatically scans all the components included in the project by using @ComponentScan annotation.

Spring Boot Starters

Handling dependency management is a difficult task for big projects. Spring Boot resolves this problem by providing a set of dependencies for developers convenience.

  • Spring Boot Starter Actuator dependency is used to monitor and manage your application.

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
  • Spring Boot Starter Security dependency is used for Spring Security.

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-security</artifactId>
</dependency>
  • Spring Boot Starter web dependency is used to write a Rest Endpoints.

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

How to setup?

Create and Setup Spring Boot Project in Spring Tool Suite

  • Go to https://start.spring.io/

  • Select Maven as Project builder

  • Select Java as language

  • Name the project something if you want and let other fields untouched

  • Now click on 'Add Dependencies' and add 'Spring web'

  • Click on 'Generate' to download the .zip file

Install and run Your First Spring Boot Application in IntelliJ IDEA

You can skip most of the above steps and use Intellij IDEA to directly start a Spring Boot project as well

  • Go to https://www.jetbrains.com/idea/download/

  • Download and install the IDE on your system

  • Extract the downloaded Spring boot zip file

  • Click on Open an existing project in IntelliJ and browse to the pom.xml file

  • Click on import changes on prompt and wait for the project to sync.

How to run?

Go to src->main->java->com.example.demo->DemoApplication.java

package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class DemoApplication {

	public static void main(String[] args) {

		SpringApplication.run(DemoApplication.class, args);
	}
}

To run this application now Right-click on the DemoApplication.java > Run “DemoApplication.main()”

Or, you can click the 'Play' button on the toolbar to start the application.

Write a Rest Endpoint

Create a new file in the src folder (call it MainController.java for example) and follow the steps shown below −

  • Firstly, add the @RestController annotation at the top of the class.

  • Now, write a Request URI method with @RequestMapping annotation.

  • Then, the Request URI method should return the Hello World string

So now the main file will look like :

package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
@RestController
public class DemoApplication {
	public static void main(String[] args) {
		SpringApplication.run(DemoApplication.class, args);
	}
	@RequestMapping(value = "/")
	public String hello() {
		return "Hello World";
	}
}

Now run the project and open a browser and type localhost:8080 to see the output of your endpoint.

Now let's create a new endpoint in a different path say /test. Have a look at the following code:

@RequestMapping(
			method = { RequestMethod.GET },
			value = { "/test" })

	public String info()
	{
		String str2
				= "<html><body><font color=\"green\">"
				+ "<h2>THIS IS A TESTING CODE"
				+ "</h2></font></body></html>";
		return str2;
	}

Add this snippet to the MainController.java file and rerun the project. After that go to http://localhost:8080/test and observe the output

Note: The default port of the Tomcat server is 8080 and can be changed in the application.properties file. Example: server.port=7000

By following the above steps, we have created a simple RESTful route with some messages in it. In order to make a more complex application, more RESTful routes are added to perform the CRUD operations on the server.

Spring Boot Annotations

Spring Boot Annotations are a form of metadata that provides data about a spring application. Spring Boot is built on the top of the spring and contains all the features of spring. Annotations are used to provide supplemental information about a program.

Spring annotations present in the org.springframework.boot.autoconfigure and org.springframework.boot.autoconfigure.condition packages are commonly known as Spring Boot annotations.

  • @SpringBootApplication Annotation : This annotation is used to mark the main class of a Spring Boot application. It encapsulates @SpringBootConfiguration, @EnableAutoConfiguration, and @ComponentScan annotations with their default attributes.

  • @Controller Annotation: This annotation provides Spring MVC features. It is used to create Controller classes and simultaneously it handles the HTTP requests. Generally we use @Controller annotation with @RequestMapping annotation to map HTTP requests with methods inside a controller class.

  • @RestController Annotation: This annotation is used to handle REST APIs such as GET, PUT, POST, DELETE etc. and also used to create RESTful web services using Spring MVC. It encapsulates @Controller annotation and @ResponseBody annotation with their default attributes.

  • @RequestMapping Annotation: This annotation is used to map the HTTP requests with the handler methods inside the controller class.

Example

//Java program to demonstrate Request Handling annotations 
@RestController
public class MyController{ 
	@RequestMapping(value=" ",method=RequestMapping.GET) 
	public String test(){ 
		//insert code here 
	} 
}

For handling specific HTTP requests we can use

@GetMapping @PutMapping @PostMapping @PatchMapping @DeleteMapping

NOTE : We can manually use GET, POST, PUT and DELETE annotations along with the path as well as we can use @RequestMapping annotation along with the method for all the above handler requests

  • @RequestBody Annotation: This annotation is used to convert HTTP requests from incoming JSON format to domain objects directly from the request body. Here, the method parameter binds with the body of the HTTP request.

  • @ResponseBody Annotation: This annotation is used to convert the domain object into HTTP request in the form of JSON or any other text. Here, the return type of the method binds with the HTTP response body.

Typical Layout

We are going to create packages to make sure we use them to make our file tree better to read and interpret. We’ll mostly use the following packages and create classes and interfaces in these packages

  1. controller: It will contain all classes and interfaces related to controllers.

  2. dao: It will contain all the repositories-related interfaces and classes.

  3. service: It will contain all the business logic-related interfaces and classes.

  4. model: It will contain all the models in form of classes.

  5. exceptions: It will contain all the custom exceptions

Building RESTful Web Services

Let's create a package called controller and model. In the model package we will create a new class Product.java with Name, ID, getter and setter methods.

package com.example.demo.model;

public class Product {
    private String id;
    private String name;

    public String getId() {
        return id;
    }
    public void setId(String id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

Now we will create four APIs in controller package with ProductServiceController.java

GET API -

The default HTTP request method is GET. This method does not require any Request Body. You can send request parameters and path variables to define the custom or dynamic URL.

The sample code to define the HTTP GET request method is shown below. In this example, we used HashMap to store the Product. Note that we used a Product.java class as the product to be stored.

Here, the request URI is /products and it will return the list of products from HashMap repository. The controller class file is given below that contains GET method REST Endpoint.

package com.example.demo.controller;
import java.util.HashMap;
import java.util.Map;
import com.example.demo.model.Product;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ProductServiceController {
   private static Map<String, Product> productRepo = new HashMap<>();
   static {
      Product honey = new Product();
      honey.setId("1");
      honey.setName("Honey");
      productRepo.put(honey.getId(), honey);
      
      Product almond = new Product();
      almond.setId("2");
      almond.setName("Almond");
      productRepo.put(almond.getId(), almond);
   }
   @RequestMapping(value = "/products")
   public ResponseEntity<Object> getProduct() {
      return new ResponseEntity<>(productRepo.values(), HttpStatus.OK);
   }
}

For the rest three APIs only the main code will be given, the header packages are same as GET API

POST API

The HTTP POST request is used to create a resource. This method contains the Request Body. We can send request parameters and path variables to define the custom or dynamic URL.

The following example shows the sample code to define the HTTP POST request method. In this example, we used HashMap to store the Product, where the product is a Product.java class.

Here, the request URI is /products, and it will return the String after storing the product in the HashMap repository.

@RestController
public class ProductServiceController {
   private static Map<String, Product> productRepo = new HashMap<>();
   
   @RequestMapping(value = "/products", method = RequestMethod.POST)
   public ResponseEntity<Object> createProduct(@RequestBody Product product) {
      productRepo.put(product.getId(), product);
      return new ResponseEntity<>("Product is created successfully", HttpStatus.CREATED);
   }
}

PUT API

The HTTP PUT request is used to update the existing resource. This method contains a Request Body. We can send request parameters and path variables to define the custom or dynamic URL.

The example given below shows how to define the HTTP PUT request method. In this example, we used HashMap to update the existing Product, where the product is a Product.java class.

Here the request URI is /products/{id} which will return the String after a the product into a HashMap repository. Note that we used the Path variable {id} which defines the products ID that needs to be updated.

@RestController
public class ProductServiceController {
   private static Map<String, Product> productRepo = new HashMap<>();
   
   @RequestMapping(value = "/products/{id}", method = RequestMethod.PUT)
   public ResponseEntity<Object> updateProduct(@PathVariable("id") String id, @RequestBody Product product) { 
      productRepo.remove(id);
      product.setId(id);
      productRepo.put(id, product);
      return new ResponseEntity<>("Product is updated successsfully", HttpStatus.OK);
   }   
}

DELETE API

The HTTP Delete request is used to delete the existing resource. This method does not contain any Request Body. We can send request parameters and path variables to define the custom or dynamic URL.

The example given below shows how to define the HTTP DELETE request method. In this example, we used HashMap to remove the existing product, which is a Product.java class.

The request URI is /products/{id} and it will return the String after deleting the product from HashMap repository. We used the Path variable {id} which defines the products ID that needs to be deleted.


@RestController
public class ProductServiceController {
   private static Map<String, Product> productRepo = new HashMap<>();
   
   @RequestMapping(value = "/products/{id}", method = RequestMethod.DELETE)
   public ResponseEntity<Object> delete(@PathVariable("id") String id) { 
      productRepo.remove(id);
      return new ResponseEntity<>("Product is deleted successsfully", HttpStatus.OK);
   }
}

Combine all the APIs into one file ProductServiceController.java

How to check your APIs

Install Postman

  • Go to https://www.postman.com/downloads/

  • Download and Install it on your machine

  • Run your Spring boot project

  • Go to new and select HTTP

Test APIs

  • Send GET from the dropdown menu and type http://localhost:8080/products in the url

  • Click on Send

If everything is followed, the following output will be received

[
    {
        "id": "1",
        "name": "Honey"
    },
    {
        "id": "2",
        "name": "Almond"
    }
]

This is what we added in our HashMap at the very start.

  • To check POST API, create a new request

  • Select Post from the dropdown and go to the Body tab then raw to give your request a body.

  • Let's give a new Product in JSON format

{
    "id":"3",
    "name":"Tomato"
}
  • put the same URL http://localhost:8080/products in the URL box and click send

  • The output will be Product is created successfully

Try performing GET again to see the added product.

  • To check PUT API, again create a new request and select PUT from menu, and type http://localhost:8080/products/3 in url section where 3 is the id of product we want to update

  • In the body, again in raw->Json type the name of updated product { "name":"Potato" }

  • Click 'Send'

  • You should get Product is updated successsfully

Now if you use GET request, you will get the following output

[
    {
        "id": "1",
        "name": "Honey"
    },
    {
        "id": "2",
        "name": "Almond"
    },
    {
        "id": "3",
        "name": "Potato"
    }
]
  • To check Delete API create a new request of Delete type and type http://localhost:8080/products/1 where 1 is the ID of the product you want to delete

  • Click on 'Send'

  • You should get Product is deleted successsfully

Conclusion

Now you know how you can create different RESTful APIs. Here we used 2 end points one was /products and another was /products/{id}. This was because we needed id parameter to implement update and delete functionality. Similarly you can create two endpoints say /borrow and /return for a library management system and implement different APIs in that. This is basically how CRUD operations happen in JAVA Spring boot.

Further we can connect it to a database, so for that say we are implementing POST API then we need to create a new row in the table of Products in our database everytime. Also we can make a front-end through HTML/CSS or Java Swing and connect it to Spring boot backend which we made so as to perform these CRUD operations on our browser instead of Postman.

Java Optional Class

Java’s Optional class was introduced in 2014 to allow developers to represent the empty state. In Java we create an Optional with a specific state. At the point of creation we either give it an object, or don’t give it an object.

Here’s how to create an Optional which contains an object.

Glasses glasses = new Glasses();
Optional<Glasses> glassesOptional = Optional.of(glasses);

Creating an Optional which doesn’t contain an object is even simpler.

Optional<Glasses> glassesOptional = Optional.empty()

Need of Optional

You’re coding a REST API. Right now, you need to create a Java method which searches by the guitarist’s last name.

You add this method to the GuitaristService class. Right now it only supports a single guitarist. It looks up the guitarist and returns it, if found.

public Guitarist findByLastName(String lastName) {
    if (lastName.equalsIgnoreCase("Hendrix")) {
        return new Guitarist("Jimi", "Hendrix", "Purple Haze");
    }
    return null;
}

But what if the guitarist doesn’t exist? In this scenario, you think the best thing to do is to return null.

So a chance of NullPointerException is there. Even worse would be if we had to access some fields of the guitarist object. Since the guitarist is null, Java throws a NullPointerException and whoever is calling our REST API gets a 500 error.

Instead of thinking hard to handle null, we can use the Optional class.

public Optional<Guitarist> findByLastName(String lastName) {
    if (lastName.equalsIgnoreCase("Hendrix")) {
        return Optional.of(new Guitarist("Jimi", "Hendrix", "Purple Haze"));
    }
    return Optional.empty();
}
  • If lastName is Hendrix, we construct a Guitarist object and pass it to Optional.of. This creates an Optional containing the Guitarist, which we return from the method.

  • If lastName is anything else, we return Optional.empty(). This creates an empty Optional, which will force whoever is calling the method to handle the guitarist not found scenario, as you’ll see shortly.

How to handle an Optional returned by a method?

// returns Optional with value
Optional<Guitarist> lookupResult = guitaristService.findByLastName("Hendrix");
lookupResult.ifPresent(guitarist -> System.out.println(guitarist.getSignatureSong()));

The ifPresent method takes a consumer function, passed as a lambda expression. The function gets executed if the Optional contains a value. That’s perfect for when we want to handle the value in some way. And we don’t even have to use an if statement.

Another way can be

// returns empty Optional
Optional<Guitarist> lookupResult = guitaristService.findByLastName("Page");
lookupResult.ifPresentOrElse(
        guitarist -> System.out.println(guitarist.getSignatureSong()),
        () -> System.out.println("Guitarist not found!")
);

Substituting a default value

Guitarist defaultGuitarist = new Guitarist("Ed", "Sheeran", "The A team");
// returns empty Optional
Guitarist guitarist = guitaristService.findByLastName("Page").orElse(defaultGuitarist);
System.out.println("Recommended listening: " + guitarist.getSignatureSong());

Java Streams

Stream represents a sequence of objects from a source, which supports aggregate operations. It is a new abstract layer introduced in Java 8. Using stream, you can process data in a declarative way.

Generating Streams in Java

Collection interface has two methods to generate a Stream.

  • stream() − Returns a sequential stream considering collection as its source.

  • parallelStream() − Returns a parallel Stream considering collection as its source.

Important Operations

  1. forEach Method: Stream has provided a new method 'forEach' to iterate each element of the stream. The following code segment shows how to print 10 random numbers using forEach.

Random random = new Random();
random.ints().limit(10).forEach(System.out::println);

2.map Method: The 'map' method is used to map each element to its corresponding result. The following code segment prints unique squares of numbers using map.

List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);

//get list of unique squares
List<Integer> squaresList = numbers.stream().map( i -> i*i).distinct().collect(Collectors.toList());
  1. filter Method: The 'filter' method is used to eliminate elements based on a criteria. The following code segment prints a count of empty strings using filter.

List<String>strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");

//get count of empty string
int count = strings.stream().filter(string -> string.isEmpty()).count();
  1. limit Method: The 'limit' method is used to reduce the size of the stream. The following code segment shows how to print 10 random numbers using limit.

Random random = new Random();
random.ints().limit(10).forEach(System.out::println);
  1. sorted Method: The 'sorted' method is used to sort the stream. The following code segment shows how to print 10 random numbers in a sorted order.

Random random = new Random();
random.ints().limit(10).sorted().forEach(System.out::println);
  1. Parallel Processing: parallelStream is the alternative of stream for parallel processing. Take a look at the following code segment that prints a count of empty strings using parallelStream.

List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
//get count of empty string
long count = strings.parallelStream().filter(string -> string.isEmpty()).count();

It is very easy to switch between sequential and parallel streams.

  1. Collectors: Collectors are used to combine the result of processing on the elements of a stream. Collectors can be used to return a list or a string.

List<String>strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
List<String> filtered = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList());

System.out.println("Filtered List: " + filtered);
String mergedString = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.joining(", "));
System.out.println("Merged String: " + mergedString);

8.Statistics: With Java 8, statistics collectors are introduced to calculate all statistics when stream processing is being done.

List numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);

IntSummaryStatistics stats = numbers.stream().mapToInt((x) -> x).summaryStatistics();

System.out.println("Highest number in List : " + stats.getMax());
System.out.println("Lowest number in List : " + stats.getMin());
System.out.println("Sum of all numbers : " + stats.getSum());
System.out.println("Average of all numbers : " + stats.getAverage());

Example

// Java program to demonstrate 
// the use of stream in java
import java.util.*;
import java.util.stream.*;

class Demo {
	public static void main(String args[])
	{
		// create a list of integers
		List<Integer> number = Arrays.asList(2, 3, 4, 5); 

		// demonstration of map method
		List<Integer> square 
		= number.stream()
			.map(x -> x * x)
			.collect(Collectors.toList());

		// create a list of String
		List<String> names = Arrays.asList(
			"Reflection", "Collection", "Stream");

		// demonstration of filter method
		List<String> result
		= names.stream()
			.filter(s -> s.startsWith("S"))
			.collect(Collectors.toList());
	
		System.out.println(result);

		// demonstration of sorted method
		List<String> show 
		= names.stream()
			.sorted()
			.collect(Collectors.toList());
	
		System.out.println(show);

		// create a list of integers
		List<Integer> numbers
			= Arrays.asList(2, 3, 4, 5, 2);

		// collect method returns a set
		Set<Integer> squareSet
		= numbers.stream()
			.map(x -> x * x)
			.collect(Collectors.toSet());
	
		System.out.println(squareSet);

		// demonstration of forEach method
		number.stream()
			.map(x -> x * x)
			.forEach(y -> System.out.println(y));

		// demonstration of reduce method
		int even 
		= number.stream()
			.filter(x -> x % 2 == 0)
			.reduce(0, (ans, i) -> ans + i);

		System.out.println(even);
	}
}

Output

[4, 9, 16, 25]
[Stream]
[Collection, Reflection, Stream]
[16, 4, 9, 25]
4
9
16
25
6

Last updated