Week Spring on Habré, apparently, is open. I would like to say thanks to the translator and commentators of the article “Why I hate Spring” , which, despite the strong negative message in the title, caused a number of interesting discussions, as well as those who responded to my previous article How to write for Spring in 2017 . Thanks largely to the comments on the previous article, this one appeared.
This time we will plunge into the depths of the Spring framework, expose its magic, see how the basic web application looks from the inside, and figure out what the same task and how Spring Boot solves.
In the comments to the previous article, several people very rightly pointed out that the example of Hello World on Spring is still not very revealing. Spring, especially with the use of Spring Boot, gives a feeling of simplicity and omnipotence, but misunderstanding of the basics and insides of the framework leads to a great danger of getting a trace on the log. Well, in order to dispel a little the feeling of the full magic of what is happening, today we will take the application from the previous article and analyze how and what happens inside the framework and what problems we are protected from by the Boot. The target audience is still novice developers, but with some experience and basic knowledge of Java and Spring. Although, perhaps, Spring experienced users will also find it interesting to refresh their knowledge of what is going on under the hood.
Let's begin to tear the covers from the most basic concepts of Spring. A bean is nothing but the most common object. The only difference is that it is accepted to call the objects that are controlled by Spring and live inside its DI container as bins. Bin is almost everything in Spring - services, controllers, repositories, in fact, the entire application consists of a set of beans. They can be registered, received as dependencies, proxied, mock, etc.
The key and fundamental mechanism of Spring. It looks very simple, but inside it provides a lot of mechanisms for fine-tuning dependencies. In fact, any Spring application is a collection of bins connected together via a DI container.
Very often, when discussing Spring, the argument sounds that you can easily replace it with any lightweight DI container (Guice, for example) and get the same, but easier and simpler. And here it is very important to understand - the value of Spring DI is not in the very fact of its existence, but in its fundamental nature. All the libraries in the Spring ecosystem, in fact, simply register their bins in this container (including Spring itself) - and application developers will be able to get the necessary components through dependency injection. A simple example: when using Spring Security OAuth, if you configure the OAuth parameters in application.properties
, then Spring Security will provide an OAuth2RestTemplate
bin which we can simply inject into our code. And when you access the external API, this bin will know where and how to go to get the OAuth token, how to update it, where to add it to the request, etc. So the value of DI here is that it is just a mechanism for communication between our code and Spring Security. And by simply replacing the DI implementation with Guice, you cannot ensure that Spring Security also starts using it. And if in this new DI there is no integration with all the libraries of Spring, then its value drops dramatically.
Another very important point that many miss when discussing a DI container is that the use of dependency injection does not imply the creation of interfaces for each component. This is a very simple idea, but I have seen many times that because of its simplicity, it is not always obvious. Moreover, the creation of an interface, if it has only one implementation, is considered bad practice. Those. classes themselves may well be DI objects. Moreover, the lack of an interface does not even prevent them from getting wet in the tests, since Mockito, for example, is quite able to mock classes.
Presented by the ApplicationContext
interface. In essence, it is the Spring application itself. The context also provides the ability to respond to various events that occur within the application, to manage the life cycle of the beans (create as a singleton or for each request, for example).
So, if an application is a set of beans, in order for it to work, we need to describe this set.
A configuration is simply a description of the available bins. Spring provides several options for describing the set of beans that will form an application. The historical option is through a set of xml files . Nowadays, Java annotations have replaced it. Spring Boot is built on annotations a little more than completely, and most modern libraries in principle can also be configured via annotations. In its third generation, the configuration of the beans came to the functional registration approach ( functional bean registration ), which will become one of the important new features of the upcoming Spring 5.
A typical configuration class might look like this:
@Configuration class PaymentsServiceConfiguration { @Bean public PaymentProvider paymentProvider() { return new PayPalPaymentProvider(); } @Bean public PaymentService paymentService(PaymentProvider paymentProvider) { return new PaymentService(paymentProvider); } }
This configuration defines two bins, and the second depends on the first. And here Spring will come into play - when we ask for the PaymentProvider
instance - Spring will find it in context and provide it to us.
The configuration does not necessarily describe in one huge file, you can split it into several and combine them using @Import
annotations.
An important component of the Spring Framework, another approach to simplifying application configuration. The idea is very simple - if we know that our MyCoolComponent
class should register a bean with the name myCoolComponent
, why write @Bean MyCoolComponent myCoolComponent(dependencies...) { return new MyCoolComponent(dependencies...); }
every time @Bean MyCoolComponent myCoolComponent(dependencies...) { return new MyCoolComponent(dependencies...); }
@Bean MyCoolComponent myCoolComponent(dependencies...) { return new MyCoolComponent(dependencies...); }
? Why not just give Spring – y the machine to register and create a bin based on the desired class? This task is solved by scanning the components. Those. if we declare our class as
@Component class MyCoolComponent { MyCoolComponent(dependencies...) { } }
and allow scanning of components - then Spring will create and register a bin with the name myCoolComponent
, using the class constructor and injecting all dependencies there.
Be careful when scanning components. in fact, it implicitly changes the context of the application. For example, if we have an interface and two implementations - and @Component
is specified on each, then when trying to inject a dependency on the interface, Spring will throw an exception that there are two bins that satisfy the query.
So, the things you need to remember: the Spring application, described by the ApplicationContext
interface, is a set of objects ( bins ) managed by the DI container. Configuration of a set of beans is done using configuration classes (annotation @Configuration
), which can be combined using imports (annotation @Import
).
Now go to the next part. Suppose we need to configure a connection to a MySQL database. If we want to use Spring Data JPA with Hibernate as a provider, we will need to configure several bins - EntityManagerFactory
(main JPA class), DataSource
to connect directly to the database via the JDBC driver, etc. But on the other hand, if we do it every time and, in fact, do the same thing - why not automate it? Let's say if we specified the connection string to the database and added a dependency on the MySQL driver - why not automatically create all the necessary bins for working with MySQL? This is what Spring Boot does. In essence, Spring Boot is just a set of configuration classes that create the necessary beans in the context. Similarly, they can be created by hand, just Boot automates it.
The important concept of Spring Boot is autoconfiguration. In essence, this is just a set of configuration classes that create and register specific beans in an application. By and large, even the Embedded Servlet Container itself is just another bin that can be configured! A couple of important points that are important to know about autoconfiguration:
@EnableAutoConfiguration
annotationapplication.properties
and so on.DataSource
bin, the autoconfiguration will not overlap itThe logic for registering beans is controlled by a set of @ConditionalOn*
annotations. You can specify that a bean is created if there is a class in the classpath ( @ConditionalOnClass
), an existing bin ( @ConditionalOnBean
), no bin ( @ConditionalOnMissingBean
), etc.
Spring Boot makes extensive use of these annotations in order to remain as inconspicuous as possible and not override user configurations.
Now, having in stock the basic theoretical knowledge, let's look at what happens when you start the application.
So, our application includes such code:
@SpringBootApplication public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }
Let's look at what happens here in steps.
DemoApplication
classThis class is marked with the @SpringBootApplication
annotation, which is a meta annotation , i.e. in fact, it is an alias for several annotations:
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan
.Those. the presence of @SpringBootApplication
includes scanning components, autoconfiguration, and shows various Spring components (for example, integration tests) that this is a Spring Boot application
This is just a helper that does a couple of things — using the list of provided configurations (and the DemoApplication
class itself is a configuration, see above) creates an ApplicationContext
, configures it, displays a banner in the console, and notes the start time of the application, etc. It can be replaced with manual creation of the context: new AnnotationConfigApplicationContext(DemoApplication.class)
. As the name implies, this is an application context that is configured using annotations. However, this context does not know anything about the embedded servlet container, and it definitely does not know how to launch itself. His successor, already from Spring Boot - AnnotationConfigEmbeddedWebApplicationContext
is fully capable of doing this, and if we write in the main
method simply
@SpringBootApplication public class DemoApplication { public static void main(String[] args) throws InterruptedException { ApplicationContext applicationContext = new AnnotationConfigEmbeddedWebApplicationContext(DemoApplication.class); } }
Then we get exactly the same running application, because The AnnotationConfigEmbeddedWebApplicationContext
class will find an EmbeddedServletContainerFactory
bin in the context and create and run the embedded container through it. Please note that all this works within the framework of a common DI container, that is, you can implement this class yourself.
This annotation includes autoconfiguration. And here, perhaps, the key moment in dispelling Spring magic. Here is how this annotation is declared:
... @Import(EnableAutoConfigurationImportSelector.class) public @interface EnableAutoConfiguration { ... }
Those. This is the most common configuration import , which we talked about above. The class EnableAutoConfigurationImportSelector
(and its successor in Spring Boot 1.5+ - AutoConfigurationImportSelector
) is just a configuration that adds several beans to the context. However, this class has one subtlety - it does not declare the bins itself, but uses the so-called factories .
The class EnableAutoConfigurationImportSelector
looks into the spring.factories
file and loads from there a list of values that are the names of the (auto) configuration classes that Spring Boot imports .
A piece of the spring.factories
file (it is in the META-INF
folder inside spring-boot-autoconfigure.<version>.jar
), which we now need is this:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\ ... (100 lines) org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration
Those. The @EnableAutoConfiguration
annotation simply imports all of the listed configurations to provide the required beans to the application context.
In fact, it can be replaced with manual import of the desired configurations:
@Import({ org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration.class, org.springframework.boot.autoconfigure.aop.AopAutoConfiguration.class, ...}) public class DemoApplication { ... }
However, the peculiarity is that Spring Boot tries to apply all configurations (there are about a hundred of them). I think the attentive reader already has a couple of questions that should be clarified.
"But this is slow!" Both yes and no - there are no exact numbers at hand, but the autoconfiguration process itself is very fast (on the order of hundreds of milliseconds on an abstract machine in a vacuum)
RabbitAutoConfiguration
) if I do not use it?". The presence of autoconfiguration does not mean that a bin will be created. Autoconfigurational classes actively use @ConditionalOnClass
annotations, and in most cases the configuration will not do anything and will not create (see "Conditions and procedure for registering bins" above).At the heart of the “magic” of Spring Boot there is nothing magical, it uses completely basic concepts from the Spring Framework. In short, the process can be described as:
@SpringBootApplication
includes scanning components and auto-configuration via the @EnableAutoConfiguration
annotation@EnableAutoConfiguration
imports the class EnableAutoConfigurationImportSelector
EnableAutoConfigurationImportSelector
loads the configuration list from the META-INF/spring.factories
AnnotationConfigEmbeddedWebApplicationContext
searches in the same DI container for the factory to run the embedded servlet containerIt may look difficult, but for the most part, application developers do not need to get into the interior of an autoconfiguration unless it is about supporting auto-configuration for their library.
In the case where something goes wrong, Spring Boot allows you to run the autoconfiguration diagnostics and see which bins were created. To see this information, you need to run the application with the key - --debug
.
java -jar my-app.jar --debug
In response, Spring will issue a detailed Auto-configuration report :
========================= AUTO-CONFIGURATION REPORT ========================= Positive matches: ----------------- DataSourceAutoConfiguration matched: - @ConditionalOnClass found required classes 'javax.sql.DataSource', 'org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType'; @ConditionalOnMissingClass did not find unwanted class (OnClassCondition) DataSourceAutoConfiguration#dataSourceInitializer matched: - @ConditionalOnMissingBean (types: org.springframework.boot.autoconfigure.jdbc.DataSourceInitializer; SearchStrategy: all) did not find any beans (OnBeanCondition) ... Negative matches: ----------------- ActiveMQAutoConfiguration: Did not match: - @ConditionalOnClass did not find required classes 'javax.jms.ConnectionFactory', 'org.apache.activemq.ActiveMQConnectionFactory' (OnClassCondition) ...
The line in Positive / Negative matches will be for each autoconfiguration applied , moreover, Boot will tell you why a particular bin has been created (that is, it will indicate which of the registration conditions have been met).
Spring Boot Actuator is a powerful diagnostic tool for a running application that can provide a lot of useful analytics (moreover, a set of these metrics can be easily expanded from within the application).
After adding Actuator to the project, Spring Boot publishes a list of available bins via the URL http://localhost:8080/beans
. This list is also available via JMX (Java Management Extensions), and the latest version of Intellij IDEA can display all application beans directly from the launch window.
Spring still remains large and not the easiest framework, but this is the price of high-level abstractions that it provides. And although it’s not necessary to know all the subtleties of the framework’s work in daily development, knowing how it works from the inside is still useful. I hope that this article helped to understand the importance and value of Spring precisely as an ecosystem and removed a bit of "magic" in what is happening, especially when using Spring Boot. My advice - do not be afraid to go deeper into the framework of the framework, read the source code and documentation, since they are almost reference in Spring — in my opinion.
It is also worth noting that in Spring 5 , which is being prepared for release in September, there will be several new concepts aimed at creating simple applications and lowering the level of "magic" (although, as we found out, there is not much magic there). One of the concepts is Functional Bean Registration , which allows you to register bins in context using functions, or even using quite good DSL on Kotlin (and Spring 5 will add a lot of good to support Kotlin). The next, but even more important thing, is the combination of the Functional Web Framework and the WebFlux (reactive web framework) , which allows you to create web applications at all without dependencies on Spring MVC and run them without servlet containers. An application can work without context of applications and DI, and is described simply as a set of request -> response
functions. About this you can read a little more here (in English).
Source: https://habr.com/ru/post/334448/
All Articles