Mocking is an essential part of unit testing, and the Mockito library makes it easy to write clean and intuitive unit tests for your Java code.
Get started with mocking and improve your application tests using our Mockito guide:
Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.
Get started with understanding multi-threaded applications with our Java Concurrency guide:
Spring 5 added support for reactive programming with the Spring WebFlux module, which has been improved upon ever since. Get started with the Reactor project basics and reactive programming in Spring Boot:
Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.
But these can also be overused and fall into some common pitfalls.
To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:
Get started with Spring and Spring Boot, through the Learn Spring course:
>> LEARN SPRINGExplore Spring Boot 3 and Spring 6 in-depth through building a full REST API with the framework:
Yes, Spring Security can be complex, from the more advanced functionality within the Core to the deep OAuth support in the framework.
I built the security material as two full courses - Core and OAuth, to get practical with these more complex scenarios. We explore when and how to use each feature and code through it on the backing project.
You can explore the course here:
Spring Data JPA is a great way to handle the complexity of JPA with the powerful simplicity of Spring Boot.
Get started with Spring Data JPA through the guided reference course:
Refactor Java code safely β and automatically β with OpenRewrite.
Refactoring big codebases by hand is slow, risky, and easy to put off. Thatβs where OpenRewrite comes in. The open-source framework for large-scale, automated code transformations helps teams modernize safely and consistently.
Each month, the creators and maintainers of OpenRewrite at Moderne run live, hands-on training sessions β one for newcomers and one for experienced users. Youβll see how recipes work, how to apply them across projects, and how to modernize code with confidence.
Join the next session, bring your questions, and learn how to automate the kind of work that usually eats your sprint time.
1. Introduction
In this article, we will get acquainted with Zookeeper and how itβs used for Service Discovery which is used as a centralized knowledge about services in the cloud.
Spring Cloud Zookeeper provides Apache Zookeeper integration for Spring Boot apps through autoconfiguration and binding to the Spring Environment.
2. Service Discovery Setup
We will create two apps:
- An app that will provide a service (referred to in this article as the Service Provider)
- An app that will consume this service (called the Service Consumer)
Apache Zookeeper will act as a coordinator in our service discovery setup. Apache Zookeeper installation instructions are available at the following link.
3. Service Provider Registration
We will enable service registration by adding the spring-cloud-starter-zookeeper-discovery dependency and using the annotation @EnableDiscoveryClient in the main application.
Below, we will show this process step-by-step for the service that returns βHello World!β in a response to GET requests.
3.1. Maven Dependencies
First, letβs add the required spring-cloud-starter-zookeeper-discovery, spring-web, spring-cloud-dependencies and spring-boot-starter dependencies to our pom.xml file:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.2.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.1.14.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR4</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
3.2. Service Provider Annotations
Next, we will annotate our main class with @EnableDiscoveryClient. This will make the HelloWorld application discovery-aware:
@SpringBootApplication
@EnableDiscoveryClient
public class HelloWorldApplication {
public static void main(String[] args) {
SpringApplication.run(HelloWorldApplication.class, args);
}
}
And a simple controller:
@GetMapping("/helloworld")
public String helloWorld() {
return "Hello World!";
}
3.3. YAML Configurations
Now let us create a YAML Application.yml file that will be used for configuring the application log level and informing Zookeeper that the application is discovery-enabled.
The name of the application with which gets registered to Zookeeper is the most important. Later in the service consumer, a feign client will use this name during the service discovery:
spring:
application:
name: HelloWorld
cloud:
zookeeper:
discovery:
enabled: true
logging:
level:
org.apache.zookeeper.ClientCnxn: WARN
The spring boot application looks for zookeeper on default port 2181. If zookeeper is located somewhere else, the configuration needs to be added:
spring:
cloud:
zookeeper:
connect-string: localhost:2181
4. Service Consumer
Now we will create a REST service consumer and registered it using Spring Netflix Feign Client.
4.1. Maven Dependency
First, letβs add the required spring-cloud-starter-zookeeper-discovery, spring-web, spring-cloud-dependencies, spring-boot-starter-actuator and spring-cloud-starter-feign dependencies to our pom.xml file:
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<version>2.2.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR4</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
4.2. Service Consumer Annotations
As with the service provider, we will annotate the main class with @EnableDiscoveryClient to make it discovery-aware:
@SpringBootApplication
@EnableDiscoveryClient
public class GreetingApplication {
public static void main(String[] args) {
SpringApplication.run(GreetingApplication.class, args);
}
}
4.3. Discover Service With Feign Client
We will use the Spring Cloud Feign Integration, a project by Netflix that lets you define a declarative REST Client. We declare how the URL looks like and feign takes care of connecting to the REST service.
The Feign Client is imported via the spring-cloud-starter-feign package. We will annotate a @Configuration with @EnableFeignClients to make use of it within the application.
Finally, we annotate an interface with @FeignClient(βservice-nameβ) and auto-wire it into our application for us to access this service programmatically.
Here in the annotation @FeignClient(name = βHelloWorldβ), we refer to the service-name of the service producer we previously created.
@Configuration
@EnableFeignClients
@EnableDiscoveryClient
public class HelloWorldClient {
@Autowired
private TheClient theClient;
@FeignClient(name = "HelloWorld")
interface TheClient {
@RequestMapping(path = "/helloworld", method = RequestMethod.GET)
@ResponseBody
String helloWorld();
}
public String HelloWorld() {
return theClient.HelloWorld();
}
}
4.4. Controller Class
The following is the simple service controller class that will call the service provider function on our feign client class to consume the service (whose details are abstracted through service discovery) via the injected interface helloWorldClient object and displays it in response:
@RestController
public class GreetingController {
@Autowired
private HelloWorldClient helloWorldClient;
@GetMapping("/get-greeting")
public String greeting() {
return helloWorldClient.helloWorld();
}
}
4.5. YAML Configurations
Next, we create a YAML file Application.yml very similar to the one used before. That configures the applicationβs log level:
logging:
level:
org.apache.zookeeper.ClientCnxn: WARN
The application looks for the Zookeeper on default port 2181. If Zookeeper is located somewhere else, the configuration needs to be added:
spring:
cloud:
zookeeper:
connect-string: localhost:2181
5. Testing the Setup
The HelloWorld REST service registers itself with Zookeeper on deployment. Then the Greeting service acting as the service consumer calls the HelloWorld service using the Feign client.
Now we can build and run these two services.
Finally, weβll point our browser to http://localhost:8083/get-greeting, and it should display:
Hello World!
6. Conclusion
In this article, we have seen how to implement service discovery using Spring Cloud Zookeeper and we registered a service called HelloWorld within Zookeeper server to be discovered and consumed by the Greeting service using a Feign Client without knowing its location details.
