Spring Boot application structure
Microservices, if exposed as web services, either JSON or XML, need to have the corresponding HTTP server capabilities. As well as managing resources and connections to databases, they may consume other services, or even publish data through messages queues. If you have worked with these kinds of applications before, you may remember how complex they were in order to configure and set up with probably dozens of XML files and properties and in many cases a lot of boilerplate code that initialized many of these systems. Spring, through Spring Boot, provides a framework that will drastically reduce the boilerplate code and autoconfigure most of the system that we need to use. Let's review the different components that we will use to create microservices.
Creating an application object The @SpringBootApplication annotation provides a convenient way to bootstrap a Spring application that can be started from a main() method. In many situations, you can just delegate to the static runApplication method:
@SpringBootApplication
class Chapter2Application
fun main(args: Array<String>)
{
runApplication<Chapter2Application>(*args)
}
You can see that we have used the @SpringBootApplication annotation for marking a class that we will use as our application context. We will learn more about the Spring Application Context shortly. When a Spring Boot application starts it will autoconfigure all the required systems; if it is a web app, it will start a web application.
Spring Boot provides an embedded Tomcat server that will start when our application starts. This is a fully functional server, so it's not required to deploy our software in any application server. However, Spring allows us to do it. We will discuss more on this in the section: Packing and running a Spring Boot application. The application will also configure and run any other system that we need, such as a connection pool to a database or a queue.
Defining Spring application context
Every Spring application requires a context, a place where every component is registered. We could think about it like a central directory of object instances created by our application. When we use the Spring Framework and create something, for example, a connection pool, it gets registered in our context or when we create our own components they will be registered as well. So, if in another part of the application we require that component, instead of creating it again we can just access it. However, this provides more advanced features. If we want, for example, to register a controller that handles HTTP requests, as we do in our example, we could just do it anywhere in our classes. Later, the application could use the component scan to find what controllers we have and wire them to web interfaces, without requiring any configuration. Let's understand the component scan better in the following section.
Understanding the component scan Let's go back to our example
package com.microservices
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.stereotype.Controller
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestMethod
import org.springframework.web.bind.annotation.ResponseBody
@SpringBootApplication
class Chapter2Application
@Controller
class FirstController(val exampleService: ExampleService) {
@RequestMapping(value = "/user/{name}", method = arrayOf(RequestMethod.GET))
@ResponseBody
fun hello(@PathVariable name: String) = exampleService.getHello(name)
}
fun main(args: Array<String>) {
runApplication<Chapter2Application>(*args)
}
We add a controller class, then when our application starts we can see in the log: RequestMappingHandlerMapping : Mapped "{[/user/{name}],methods=[GET]}" onto public java.lang.String com.microservices.FirstController.hello(java.lang.String) How has the application found our controller and wired into a request mapping? When the Spring Boot application starts, it will scan all the classes and packages underneath the application context class recursively, and if any class is annotated as a component it will create an instance of it and add it to the Spring Boot application context. This feature is named as the component scan. Spring Components instances are named beans, so basically we can say that our context is a collection of beans. Later on, when the Spring Boot application starts, if it's a web application, it will get any class annotated with @Controller that is on the context and create a mapping that will get the request coming to our microservice to that class.
Using components
The component scan, while scanning all our packages, needs to understand if any of the classes it found should be added to the spring context as a bean, in order to know which classes need to be added, it will check if the class is annotated with the annotation @Component. In IntelliJ, we can navigate to a class holding control and click on a class name, for example, if we go to our example, hold control, and click on the @Controller. To visualize this, we can just view the source code of the @Controller annotation: @Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Controller
{
String value() default "";
}
The @Controller annotation uses the @Component annotation, so the component scan will create a bean in the context that will be available to the Spring Framework for any class annotated with @Controller. Many other Spring classes as well are components, such as @Service that we have used before.
Autowiring
Part of our application may use other parts, for example, a component uses another. If we look at our example, we can see that we created a service named ExampleService and that our Controller uses it as part of the constructor. When the component scan finds our controller, because is annotated with @Controller, it will discover that our class has a constructor that receives parameters. The constructor parameter is a class that is already in the Spring context since it is as annotated with @Service, so the component scan will get the bean on the context and sends it as a parameter to the constructor of our controller. But sometimes, we may just have this as a parameter within our class, not as a parameter in the constructor, so we can use the @Autowired annotation instead:
package com.microservices
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.stereotype.Controller
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestMethod
import org.springframework.web.bind.annotation.ResponseBody
@Controller
class FirstController {
@Autowired
lateinit var service: ExampleService
@RequestMapping(value = "/user/{name}", method = arrayOf(RequestMethod.GET))
@ResponseBody
fun hello(@PathVariable name: String) = service.getHello(name)
}
In Kotlin, when we declare something as lateinit we just say that this property will be initialized after the constructor. The @Authowired will take care of this in this example. This will have the same results as declaring the service in the constructor, sometimes we may choose this option, especially when we start to have a lot of components and we're not wanting to have all of then passed as parameters in the constructor of the class.
This is called dependency injection and is part of Spring Inversion of Control (IoC) that allows more advanced users; for example, we could create an interface for our service: package com.microservices
interface ServiceInterface {
fun getHello(name : String) : String
}
Then, we could modify the service to use that interface:
package com.microservices
import org.springframework.stereotype.Service
@Service
class ExampleService : ServiceInterface {
override fun getHello(name : String) = "hello $name"
}
Finally, we could change our controller to be autowired to our interface, ServiceInterface, not to the ExampleService
class: @Controller
class FirstController {
@Autowired
lateinit var service: ServiceInterface
@RequestMapping(value = "/user/{name}", method = arrayOf(RequestMethod.GET))
@ResponseBody
fun hello(@PathVariable name: String) = service.getHello(name)
}
What are the benefits of this? Hiding implementation details, since we are not directly showing how our service works. Decoupling, because tomorrow we can change our service and a new implementation will be used without affecting the consumers of the service. Easily handling changes. Spring allows us through Spring Configuration to change which services we use, without changing our code.
Note : will further optimize it.
No comments:
Post a Comment