Spring Boot behind the curtains

If you already set up a Spring MVC application earlier, you may be used to writing at least a small portion of XML or a handful of Java annotation configuration classes.

Initialization steps are typically as follows:

  1. Initializing the DispatcherServlet of Spring MVC.
  2. Setting up an encoding filter to ensure that client requests are encoded correctly.
  3. Setting up a view resolver to tell Spring where to find our views and in which dialect they are written (jsp, Thymeleaf templates, and so on).
  4. Configuring static resources locations (css, js).
  5. Configuring supported locales and resource bundles.
  6. Configuring a multipart resolver for file uploads to work.
  7. Including tomcat or jetty to run our application on a web server.
  8. Setting up the error pages (For example 404).

However, Spring Boot handles all that work for us. Because this configuration is typically up to your application, you can come up with an unlimited amount of combinations.

Spring boot, in a way, is an opinionated Spring project configurator. It is based on conventions and will enforce them on your project by default.

The dispatcher and multipart configuration

Let's see what happens behind the curtains.

We will use the default Spring Boot configuration file that was created for us and put it in the debug mode. Add the following line to src/main/resources/application.properties:

debug=true

Now, if we launch our application again we'll see Spring Boot's autoconfiguration report. It is divided into two parts: positive matches, which list all autoconfigurations that are used by our application; and negative matches, which are Spring Boot autoconfigurations whose requirements weren't met when the application started:

=========================
AUTO-CONFIGURATION REPORT
=========================


Positive matches:
-----------------

  DispatcherServletAutoConfiguration
      - @ConditionalOnClass classes found: org.Springframework.web.servlet.DispatcherServlet (OnClassCondition)
      - found web application StandardServletEnvironment (OnWebApplicationCondition)

  EmbeddedServletContainerAutoConfiguration
      - found web application StandardServletEnvironment (OnWebApplicationCondition)

  ErrorMvcAutoConfiguration
      - @ConditionalOnClass classes found: javax.servlet.Servlet,org.springframework.web.servlet.DispatcherServlet (OnClassCondition)
      - found web application StandardServletEnvironment (OnWebApplicationCondition)

  HttpEncodingAutoConfiguration
      - @ConditionalOnClass classes found: org.springframework.web.filter.CharacterEncodingFilter (OnClassCondition)
      - matched (OnPropertyCondition)


<Input trimmed>

Let's take a closer look at DispatcherServletAutoConfiguration:

/**
* {@link EnableAutoConfiguration Auto-configuration} for the Spring
* {@link DispatcherServlet}. Should work for a standalone application where an embedded
* servlet container is already present and also for a deployable application using
* {@link SpringBootServletInitializer}.
*
* @author Phillip Webb
* @author Dave Syer
*/
@Order(Ordered.HIGHEST_PRECEDENCE)
@Configuration
@ConditionalOnWebApplication
@ConditionalOnClass(DispatcherServlet.class)
@AutoConfigureAfter(EmbeddedServletContainerAutoConfiguration.class)
public class DispatcherServletAutoConfiguration {

    /*
    * The bean name for a DispatcherServlet that will be mapped to the root URL "/"
    */
    public static final String DEFAULT_DISPATCHER_SERVLET_BEAN_NAME = "dispatcherServlet";

    /*
    * The bean name for a ServletRegistrationBean for the DispatcherServlet "/"
    */
    public static final String DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME = "dispatcherServletRegistration";

    @Configuration
    @Conditional(DefaultDispatcherServletCondition.class)
    @ConditionalOnClass(ServletRegistration.class)
    protected static class DispatcherServletConfiguration {

        @Autowired
        private ServerProperties server;

        @Autowired(required = false)
        private MultipartConfigElement multipartConfig;

        @Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
        public DispatcherServlet dispatcherServlet() {
            return new DispatcherServlet();
        }

        @Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
        public ServletRegistrationBean dispatcherServletRegistration() {
            ServletRegistrationBean registration = new ServletRegistrationBean(
                    dispatcherServlet(), this.server.getServletMapping());
            registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
            if (this.multipartConfig != null) {
                registration.setMultipartConfig(this.multipartConfig);
            }
            return registration;
        }

        @Bean
        @ConditionalOnBean(MultipartResolver.class)
        @ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)
        public MultipartResolver multipartResolver(MultipartResolver resolver) {
            // Detect if the user has created a MultipartResolver but named it incorrectly
            return resolver;
        }

    }

    @Order(Ordered.LOWEST_PRECEDENCE - 10)
    private static class DefaultDispatcherServletCondition extends SpringBootCondition {

        @Override
        public ConditionOutcome getMatchOutcome(ConditionContext context,
                AnnotatedTypeMetadata metadata) {
            ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
            ConditionOutcome outcome = checkServlets(beanFactory);
            if (!outcome.isMatch()) {
                return outcome;
            }
            return checkServletRegistrations(beanFactory);
        }

    }
}

This is a typical Spring Boot configuration class:

  • It is annotated with @Configuration like any other Spring configuration class.
  • It typically declares its priority level with the @Order annotation. You can see that DispatcherServletAutoConfiguration needs to be configured first.
  • It can also contain hints such as @AutoConfigureAfter or @AutoConfigureBefore to further refine the order in which configurations are processed.
  • It is enabled under certain conditions. With @ConditionalOnClass(DispatcherServlet.class), this particular configuration ensures that our classpath contains DispatcherServlet, which is a good indication that Spring MVC is in the classpath and the user certainly wants to bootstrap it.

This file also contains classic bean declarations for the Spring MVC dispatcher servlet and a multipart resolver. The whole Spring MVC configuration is broken into multiple files.

It is also worth noting that these beans obey certain rules to check whether are active. The ServletRegistrationBean function will be enabled under the @Conditional(DefaultDispatcherServletCondition.class) condition, which is a bit complex but checks whether you already have a dispatcher servlet registered in your own configuration.

The MultipartResolver function will become active only if the condition @ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME) is met, for example, if we didn't declare it ourselves.

This means Spring boot only gives you a hand in configuring your application according to common use cases. However, at any point, you can override these defaults and declare your own configuration.

So, the DispatcherServletAutoConfiguration class explains why we have a dispatcher servlet and a multipart resolver.

The view resolver, static resources, and locale configuration

Another very relevant piece of configuration is WebMvcAutoConfiguration. It declares the view resolver, the locale resolver, and the location of our static resources. The view resolver is as follows:

@Configuration
@Import(EnableWebMvcConfiguration.class)
@EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })
public static class WebMvcAutoConfigurationAdapter extends WebMvcConfigurerAdapter {

  @Value("${spring.view.prefix:}")
  private String prefix = "";

  @Value("${spring.view.suffix:}")
  private String suffix = "";

  @Bean
  @ConditionalOnMissingBean(InternalResourceViewResolver.class)
  public InternalResourceViewResolver defaultViewResolver() {
    InternalResourceViewResolver resolver = new InternalResourceViewResolver();
    resolver.setPrefix(this.prefix);
    resolver.setSuffix(this.suffix);
    return resolver;
  }
}

The view resolver configuration is really typical. What's really interesting here is the use of configuration properties to allow users to customize it.

What it says is "I will look for two variables in the user's application.properties called spring.view.prefix and spring.view.suffix". This is a very handy way to set up the view resolver with only two lines in our configuration.

Keep this in mind for the next chapter. For now, we will just stroll through Spring Boot's code.

Regarding static resources, this configuration includes the following lines:

private static final String[] CLASSPATH_RESOURCE_LOCATIONS = {
    "classpath:/META-INF/resources/", "classpath:/resources/",
    "classpath:/static/", "classpath:/public/" };

private static final String[] RESOURCE_LOCATIONS;
static {
  RESOURCE_LOCATIONS = new String[CLASSPATH_RESOURCE_LOCATIONS.length
      + SERVLET_RESOURCE_LOCATIONS.length];
  System.arraycopy(SERVLET_RESOURCE_LOCATIONS, 0, RESOURCE_LOCATIONS, 0,
      SERVLET_RESOURCE_LOCATIONS.length);
  System.arraycopy(CLASSPATH_RESOURCE_LOCATIONS, 0, RESOURCE_LOCATIONS,
      SERVLET_RESOURCE_LOCATIONS.length, CLASSPATH_RESOURCE_LOCATIONS.length);
}

@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
  if (!this.resourceProperties.isAddMappings()) {
    logger.debug("Default resource handling disabled");
    return;
  }

  Integer cachePeriod = this.resourceProperties.getCachePeriod();
  if (!registry.hasMappingForPattern("/webjars/**")) {
    registry.addResourceHandler("/webjars/**")
        .addResourceLocations("classpath:/META-INF/resources/webjars/")
        .setCachePeriod(cachePeriod);
  }
  if (!registry.hasMappingForPattern("/**")) {
    registry.addResourceHandler("/**")
        .addResourceLocations(RESOURCE_LOCATIONS)
        .setCachePeriod(cachePeriod);
  }
}

The declaration of resource locations is a bit convoluted but we can still understand two things:

  • Any resource accessed with the "webjar" prefix will be resolved inside the classpath inside the classpath. This will allow us to use prepackaged JavaScript dependencies from Maven central.
  • Our static resources can reside in any of the locations after our classpath /META-INF/resources/, /resources/, /static/, or /public/.

Tip

WebJars are JAR packages of client JavaScript libraries available on Maven central. They include a Maven project file, which allows for transitive dependencies and works in all JVM-based applications. WebJars are an alternative to JavaScript package managers such as bower or npm. They are great for applications that require just a few JavaScript libraries. Find the list of available WebJars on www.webjars.org.

There is also a part of this file that is dedicated to locale management:

@Bean
@ConditionalOnMissingBean(LocaleResolver.class)
@ConditionalOnProperty(prefix = "spring.mvc", name = "locale")
public LocaleResolver localeResolver() {
  return new FixedLocaleResolver(
      StringUtils.parseLocaleString(this.mvcProperties.getLocale()));
}

This default locale resolver handles only one locale and allows us to define it via the spring.mvc.locale configuration property.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.149.228.138