Introduction
The Spring Framework is a very robust framework, released in 2002. Its core features can be applied to plain Java applications or extended to complex, modern web applications.
As it's constantly being updated and is following new architectural and programming paradigms, it offers support for many other frameworks that work hand-in-hand with it.
With such a vast array of functionalities, it's only normal that it introduces us to some new annotations, which are a key part of developing Spring applications.
Spring's configuration is fully customizable, which was originally done through XML configuration files. However, this approach has become outdated, and most people nowadays resort to annotation configuration.
That being said, this series of articles aims to unravel the options you as a developer have to configure and use the Spring framework:
- Spring Annotations: @RequestMapping and its Variants
- Spring Annotations: Core Annotations
- Spring Annotations: Spring Cloud
- Spring Annotations: Testing Annotations
Spring Cloud Annotations
Spring Cloud is a great extension to the already robust Spring Framework. It allows developers to effortlessly and painlessly build common patterns when it comes to cloud-based architecture such as setting up circuit breakers, discovery clients, routing etc.
We've already published several articles covering some of these topics, so if you'd like to see these annotations on practical hands-on examples, these are a great start:
- Spring Cloud: Service Discovery with Eureka
- Spring Cloud: Routing with Zuul and Gateway
- Spring Cloud: Hystrix
- Spring Cloud: Turbine
- Spring Cloud Stream with RabbitMQ: Message-Driven Microservices
@EnableConfigServer
Spring Cloud introduces us to several useful tools, each needing some sort of configuration.
If we're using multiple tools, it would be logical to store all configuration details in a single place, similar to an application.properties
file.
To do this, we annotate a class with the @EnableConfigServer
annotation, applied on class-level:
@SpringBootApplication
@EnableConfigServer
public class SomeApplication {
public static void main(String[] args) {
SpringApplication.run(SomeApplication.class, args);
}
}
This annotation tells Spring where to search for configurations and by creating a centralized server configuration like this, other applications can talk to it via it's port - which by default is 8080
.
@EnableEurekaServer
When developing a project with a microservice architecture, especially when there's numerous services that should be working together, we're faced with a problem. The way these services talk to each other.
We could hardcode all the names and ports in a property file but this is bad practice and isn't scalable at all. Even if we did, what happens when there are multiple instances of a service? Which one responds to the request?
To solve these, we can rely on Service Discovery via Eureka.
Very similar to the @EnableConfigServer
annotation, we'll make a Eureka server by annotating a class with @EnableEurekaServer
, which can now be used to lookup other services registered to it and manage them:
@SpringBootApplication
@EnableEurekaServer
public class SomeApplication {
public static void main(String[] args) {
SpringApplication.run(SomeApplication.class, args);
}
}
@EnableEurekaClient
A Eureka Server is nothing without its services. Each service that we want to put on the radar for our server should be annotated with the @EnableEurekaClient
annotation.
As an alternative, we could use the @EnableDiscoveryClient
annotation, which comes from spring-cloud-commons
. Though, if you know the specific implementation you'll be using, it's better to be specific. If you're using the more genetic @EnableDiscoveryClient
annotation, Spring will choose the implementation based on the .jar
s present in the classpath.
@SpringBootApplication
@EnableEurekaClient
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
@EnableDiscoveryClient
The default discovery client annotation which marks a class as a service that should be put on the radar for a server.
@EnableCircuitBreaker
Again, when working with microservices, we're faced with a big problem. It's common for services to work with other services to complete certain requests.
Let's say Service A calls Service B, which relies on Service C to complete a request. Now let's say Service C completely fails due to a network error or an overload. What happens next is a cascading error in the flow of logic coming back up to Service A.
What we can do to rectify this is implement circuit breakers. Each service should have a circuit breaking point, and if something wrong occurs, they "open their circuits" so that the problem doesn't transpose onto other services. In this case, the circuit breaker of our choice isolates the failing service so that other services can't call it and fail as well.
The @EnableCircuitBreaker
annotation is applied on class level, for each and every service in our microservice architecture:
@SpringBootApplication
@EnableDiscoveryClient
@EnableCircuitBreaker
public class ServiceA {
public static void main(String[] args) {
SpringApplication.run(ServiceA.class, args);
}
}
@SpringBootApplication
@EnableDiscoveryClient
@EnableCircuitBreaker
public class ServiceB {
public static void main(String[] args) {
SpringApplication.run(ServiceB.class, args);
}
}
@SpringBootApplication
@EnableDiscoveryClient
@EnableCircuitBreaker
public class ServiceC {
public static void main(String[] args) {
SpringApplication.run(ServiceC.class, args);
}
}
The circuit breaker pattern in Spring is implemented through Spring Cloud: Hystrix.
@HystrixCommand
For the circuit breaker pattern to work fully, we can't only annotate classes. Since in almost all situations we can "expect" that a method may be risky, in the sense that it might fail when called, we mark them with @HystrixCommand
. Alongside the annotation, we can also add a flag that points to a different method to run if the original one fails:
Check out our hands-on, practical guide to learning Git, with best-practices, industry-accepted standards, and included cheat sheet. Stop Googling Git commands and actually learn it!
@GetMapping(value = "/personalized/{id}")
@HystrixCommand(fallbackMethod = "recommendationFallback")
public Product[] personalized(@PathVariable int id) {
Product[] result = restTemplate.getForObject("http://recommendation-service/recommendations", Product[].class);
return result;
}
public Product[] recommendationFallback(int id) {
System.out.println("=======recommendationFallback=========" + id);
return new Product[0];
}
@RibbonClient
Ribbon works as a load-balancer on the client side and gives you control over the HTTP and TCP clients.
In most cases, when using a discovery client such as Eureka, you don't need to use this annotation as it's applied by default. In the vast majority of cases the default options are good enough to take care of load balancing, but if you require to tweak them, you can do so:
@Configuration
@RibbonClient(name = "custom", configuration = CustomConfiguration.class)
public class SomeConfiguration {
}
The CustomConfiguration
class should also be a @Configuration
annotated class with the custom Ribbon settings set-up as beans.
@LoadBalanced
The @LoadBalanced
annotation is used to mark RestTemplate
s that should work with the RibbonLoadBalancerClient
when interacting with your services:
@RestController
@RibbonClient(name = "custom", configuration = CustomConfiguration.class)
public class SomeApplication {
@LoadBalanced
@Bean
RestTemplate restTemplate(){
return new RestTemplate();
}
// ...
}
This annotation basically allows the RestTemplate
to leverage its built-in support for load balancing.
Conclusion
The Spring framework is a powerful and robust framework which really changed the game when it comes to developing web-applications. Amongst it's myriad of projects, the Spring Cloud module is a great extension of the original framework.