Spring Boot学习教程(三)

280 阅读16分钟

Spring Boot学习教程(三)

4. Spring Boot与Web开发

4.1 使用Spring Boot进行Web的简介

使用Spring Boot进行Web开发的三部曲:

  1. 创建Spring Boot应用,选中我们需要的模块(Web、数据库等)
  2. Spring Boot已经默认将这些场景都配置好了,只需要在配置文件中指定少量配置就可以运行了
  3. 自己编写业务代码

在这个过程中,我们需要去思考的问题有: 这个场景里,Spring Boot帮我们配置了什么? 我们需不需要/能不能进行配置的修改? 如果能修改,需要/能够修改哪些配置? 我们能不能进行配置的扩展?


4.2 Spring Boot对静态资源的映射规则


@ConfigurationProperties(prefix = "spring.resources", ignoreUnknownFields = false)
public class ResourceProperties {
//可以设置和静态资源资源有关的参数,如:缓存时间等



@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
	if (!this.resourceProperties.isAddMappings()) {
		logger.debug("Default resource handling disabled");
		return;
	}
	Duration cachePeriod = this.resourceProperties.getCache().getPeriod();
	CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl();
	if (!registry.hasMappingForPattern("/webjars/**")) {
		customizeResourceHandlerRegistration(registry.addResourceHandler("/webjars/**")
				.addResourceLocations("classpath:/META-INF/resources/webjars/")
				.setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
	}
	String staticPathPattern = this.mvcProperties.getStaticPathPattern();
	if (!registry.hasMappingForPattern(staticPathPattern)) {
		customizeResourceHandlerRegistration(registry.addResourceHandler(staticPathPattern)
				.addResourceLocations(getResourceLocations(this.resourceProperties.getStaticLocations()))
				.setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
	}
}

//配置欢迎页(首页)映射
@Bean
public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext,
		FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) {
	WelcomePageHandlerMapping welcomePageHandlerMapping = new WelcomePageHandlerMapping(
			new TemplateAvailabilityProviders(applicationContext), applicationContext, getWelcomePage(),
			this.mvcProperties.getStaticPathPattern());
	welcomePageHandlerMapping.setInterceptors(getInterceptors(mvcConversionService, mvcResourceUrlProvider));
	welcomePageHandlerMapping.setCorsConfigurations(getCorsConfigurations());
	return welcomePageHandlerMapping;
}


4.2.1 规则1:所有的/webjars/**,都去classpath:/META-INF/resources/webjars/下获取资源

webjars :以jar包的方式引入静态资源

webjars官网

<!--引入jquery-webjar-->
<!--要访问时只需要写webjars下面资源的名称即可-->

<dependency>
    <groupId>org.webjars</groupId>
    <artifactId>jquery</artifactId>
    <version>3.5.1</version>
</dependency>

0nXmKe.png

0nXubd.png

所以访问路径是:

localhost:8080/webjars/jquery/3.5.1/jquery.js

运行结果:

0njO6U.png

4.2.2 规则2:"/**":访问当前项目的任何资源,如果没有被处理,则默认去到静态资源路径中获取静态资源:

静态资源路径包括:

"classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/" "/"当前项目的根路径

举个例子:

0l961J.png

4.2.3 规则3:欢迎页:静态资源文件夹下的所有index.html页面(被"/**"映射)

当我们访问 localhost:8080/ 时,Spring Boot会帮我们默认获取 index.html 页面

4.2.4 规则4:favicon.ico:放在静态资源文件夹下

favicon.ico是网页标签页左侧显示的小图标


<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>终于能显示图标啦!</title>
    <link rel="shortcut icon" href="favicon.ico" type="image/x-icon"/>
</head>
<body>
    <h1>晚安!倚欧!</h1>
</body>
</html>

tips:如果浏览器没有显示图标,可以使用ctrl+f5进行刷新(会清空缓存)

4.2.5 规则5:允许在配置文件中修改静态资源访问路径


spring.resources.static-locations=classpath:/hello/,classpath:/taotao/


4.3 模板引擎

4.3.1 什么是模板引擎?

模板引擎是为了使用户界面与业务数据(内容)分离而产生的,它可以生成特定格式的文档,用于网站的模板引擎就会生成一个标准的文档 就是将模板文件和数据通过模板引擎生成一个HTML代码,如图所示:

0uTILn.png

Spring Boot推荐使用的模板引擎—— Thymeleaf

4.3.1.1 引入Thymeleaf

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>

4.3.1.2 Thymeleaf的使用&语法
如何使用Thymeleaf模板引擎:

@ConfigurationProperties(prefix = "spring.thymeleaf")
public class ThymeleafProperties {

	private static final Charset DEFAULT_ENCODING = StandardCharsets.UTF_8;

	public static final String DEFAULT_PREFIX = "classpath:/templates/";
        //默认前缀

	public static final String DEFAULT_SUFFIX = ".html";
        //默认后缀

也就是说,只要我们把HTML页面放在classpath:/templates/目录下,thymeleaf就能自动进行渲染


Thymeleaf的相关语法:
  1. 导入thymeleaf的名称空间

<html lang="en" xmlns:th="http://www.thymeleaf.org">

  1. 使用thymeleaf语法
<!--success.html-->

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="shortcut icon" href="favicon.ico" type="image/x-icon"/>
</head>
<body>
    <h1>成功!</h1>
    <div th:text="${hello}">这是显示欢迎信息</div>
</body>
</html>

  1. 语法规则

1) th:任意html属性:替换原生属性的值 th:text——改变当前元素中的文本内容


<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">

<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1>成功</h1>
    <!--th:text: 讲div中的文本内容设置为指定的值(以${}的形式指定值)-->
    <div id="div01" class="myDiv" th:id="${hello}" th:class="${hello}" th:text="${hello}">这是显示欢迎信息</div>
</body>
</html>

运行后查看网页源代码:

0KSWWR.png

th语法汇总: 0Kpv4J.png

2) 表达式


# Simple expressions:
    Variable Expressions: ${...}
    # 获取变量值,在底层是OGNL表达式
        # 1. 获取对象的属性、调用方法
        # 2. 使用内置的几本对象
            #ctx: the context object.
            #vars: the context variables.
            #locale: the context locale.
            #request: (only in Web Contexts) the HttpServletRequest object.
            #response: (only in Web Contexts) the HttpServletResponse object.
            #session: (only in Web Contexts) the HttpSession object.
            #servletContext: (only in Web Contexts) the ServletContext object.
        # 3. 内置的工具对象
            #execInfo: information about the template being processed.
            #messages: methods for obtaining externalized messages inside variables expressions, in the same way as they would be obtained using #{…} syntax.
            #uris: methods for escaping parts of URLs/URIs
            #conversions: methods for executing the configured conversion service (if any).
            #dates: methods for java.util.Date objects: formatting, component extraction, etc.
            #calendars: analogous to #dates, but for java.util.Calendar objects.
            #numbers: methods for formatting numeric objects.
            #strings: methods for String objects: contains, startsWith, prepending/appending, etc.
            #objects: methods for objects in general.
            #bools: methods for boolean evaluation.
            #arrays: methods for arrays.
            #lists: methods for lists.
            #sets: methods for sets.
            #maps: methods for maps.
            #aggregates: methods for creating aggregates on arrays or collections.
            #ids: methods for dealing with id attributes that might be repeated (for example, as a result of an iteration).
    Selection Variable Expressions: *{...}
    # 选择表达式:和${}在功能上是一样的,不过有一个补充用法:
    配合th:object="${session.user}:"进行使用
    Message Expressions: #{...}
    # 获取国际化内容
    Link URL Expressions: @{...}
    # 定义URL
    # 举个例子:
    # @{/order/process(execId=${execId},execType='FAST')}
    Fragment Expressions: ~{...}
    # 片段引用表达式

# Literals
# 字面量
    Text literals: 'one text', 'Another one!',…
    Number literals: 0, 34, 3.0, 12.3,…
    Boolean literals: true, false
    Null literal: null
    Literal tokens: one, sometext, main,…

# Text operations:
# 文本操作
    String concatenation: +
    Literal substitutions: |The name is ${name}|

# Arithmetic operations:
# 数学运算
    Binary operators: +, -, *, /, %
    Minus sign (unary operator): -

# Boolean operations:
# 布尔运算
    Binary operators: and, or
    Boolean negation (unary operator): !, not

# Comparisons and equality:
# 比较运算
    Comparators: >, <, >=, <= (gt, lt, ge, le)
    Equality operators: ==, != (eq, ne)

# Conditional operators:
# 条件语句
    If-then: (if) ? (then)
    If-then-else: (if) ? (then) : (else)
    Default: (value) ?: (defaultvalue)

# Special tokens:
# 特殊操作符
    No-Operation: _
    # 不进行操作


  • {...}的补充用法(配合th:object="${session.user}:"进行使用)的示例:

<div th:object="${session.user}">
    <p>Name: <span th:text="*{firstName}">Sebastian</span>.</p>
    <p>Surname: <span th:text="*{lastName}">Pepper</span>.</p>
    <p>Nationality: <span th:text="*{nationality}">Saturn</span>.</p>
</div>

<!--上面的代码等价于下面的代码-->

<div>
  <p>Name: <span th:text="${session.user.firstName}">Sebastian</span>.</p>
  <p>Surname: <span th:text="${session.user.lastName}">Pepper</span>.</p>
  <p>Nationality: <span th:text="${session.user.nationality}">Saturn</span>.</p>
</div>

来看看完整的一个代码实例吧!

<!--success.html-->

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="shortcut icon" href="favicon.ico" type="image/x-icon"/>
</head>
<body>
    <h1>成功!</h1>
    <div th:text="${hello}">这是显示欢迎信息</div>
    <br>
    <hr/>
    <div th:text="${hello}"></div>
    <br>
    <div th:utext="${hello}"></div>
    <hr/>

<h4 th:text="${user}" th:each="user:${users}"></h4>
<hr/>
<h4>
    <span th:each="user:${users}">[[${user}]]</span>
</h4>
</body>
</html>

//HelloController.java

package com.taotao.springboot.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.Arrays;
import java.util.Map;

@Controller
public class HelloController {

    @ResponseBody
    @RequestMapping("/hello")
    public String hello(){
        return "Hello World!";
    }

    @RequestMapping("/success")
    public String success(Map<String,Object> map){
        map.put("hello","<h1>你好</h1>");
        map.put("users", Arrays.asList("涛涛","针不戳","!!!"));
        return "success";
    }
}

运行结果:

01XjSA.png

4.4 SpringMVC的自动配置原理

官方文档关于SpringMVC

4.4.1 SpringMVC autoconfiguration

Spring Boot已经自动配置好了SpringMVC,并提供了大多数场景的自动配置

以下是Spring Boot对SpringMVC的默认配置:

  • 自动配置了 ViewResolver (视图解析器:根据方法的返回值得到视图对象(View),视图对象决定如何渲染)

    • ContentNegotiatingViewResolver :组合所有的视图解析器
    • 如何定制:我们可以自己向容器中添加一个视图解析器,然后ContentNegotiatingViewResolver就会自动地将其组合起来 不信你看: 01jNm6.png
  • 静态资源文件夹路径( webjars

  • 静态首页访问

  • favicon.ico 访问

  • 自动注册了 Converter, GenericConverter , 和 Formatter 这些bean

    • Converter:转化器(类型转换)
    • Formatter:格式化器
    
    @Bean
    @Override
    public FormattingConversionService mvcConversionService() {
    	Format format = this.mvcProperties.getFormat();
    	WebConversionService conversionService = new WebConversionService(new DateTimeFormatters()
    			.dateFormat(format.getDate()).timeFormat(format.getTime()).dateTimeFormat(format.getDateTime()));
    	addFormatters(conversionService);
    	return conversionService;
    }
    
    
    • 想要自己添加格式化转化器,只需要将其添加到容器中放在
    
    @Override
    public void addFormatters(FormatterRegistry registry) {
    	ApplicationConversionService.addBeans(registry, this.beanFactory);
    }
    
    
    
    public static void addBeans(FormatterRegistry registry, ListableBeanFactory beanFactory) {
    	Set<Object> beans = new LinkedHashSet<>();
    	beans.addAll(beanFactory.getBeansOfType(GenericConverter.class).values());
    	beans.addAll(beanFactory.getBeansOfType(Converter.class).values());
    	beans.addAll(beanFactory.getBeansOfType(Printer.class).values());
    	beans.addAll(beanFactory.getBeansOfType(Parser.class).values());
    	for (Object bean : beans) {
    		if (bean instanceof GenericConverter) {
    			registry.addConverter((GenericConverter) bean);
    		}
    		else if (bean instanceof Converter) {
    			registry.addConverter((Converter<?, ?>) bean);
    		}
    		else if (bean instanceof Formatter) {
    			registry.addFormatter((Formatter<?>) bean);
    		}
    		else if (bean instanceof Printer) {
    			registry.addPrinter((Printer<?>) bean);
    		}
    		else if (bean instanceof Parser) {
    			registry.addParser((Parser<?>) bean);
    		}
    	}
    }
    
    
  • HttpMessageConverter :SpringMVC用来转换Http请求和响应。例如,可以将对象自动转换为JSONXML

它是从容器中确定并获取获取所有的HttpMessageConverter


import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.context.annotation.*;
import org.springframework.http.converter.*;

@Configuration(proxyBeanMethods = false)
public class MyConfiguration {

    @Bean
    public HttpMessageConverters customConverters() {
        HttpMessageConverter<?> additional = ...
        HttpMessageConverter<?> another = ...
        return new HttpMessageConverters(additional, another);
    }
}

自己给容器中添加HttpMessageConverter,只需要将自己的组件注册在容器中(@Bean,@Component)


import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.context.annotation.*;
import org.springframework.http.converter.*;

@Configuration(proxyBeanMethods = false)
public class MyConfiguration {

    @Bean
    public HttpMessageConverters customConverters() {
        HttpMessageConverter<?> additional = ...
        HttpMessageConverter<?> another = ...
        return new HttpMessageConverters(additional, another);
    }
}

  • Automatic registration of MessageCodesResolver:定义错误代码生成规则

  • ConfigurableWebBindingInitializer bean: Spring MVC使用WebBindingInitializer来为特定请求初始化WebDataBinder 如果创建一个自己的ConfigurableWebBindingInitializer添加到容器中,Spring Boot会自动配置SpringMVC来使用它(替换掉默认的)


org.springframework.boot.autoconfigure.web:web的所有配置场景


4.4.2 扩展SpringMVC

SpringMVC 自带的自动配置是不够用的,所以常常需要我们自己对其进行拓展;拓展SpringMVC的配置:

编写一个配置类 (@Configuration) ,来实现 WebMvcConfigurer 接口,且不能标注 @EnableWebMvc

这样子可以既保留所有的自动配置,也能使用我们自己编写的拓展配置


//使用WebMvcConfigurer可以来扩展SpringMVC的功能
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        //浏览器发送 /taotao 请求来到 success
        registry.addViewController("/taotao").setViewName("success");
    }
}

来稍微看看他的原理:

  1. WebMvcAutoConfigurationSpringMVC 的自动配置类
  2. 在做其他自动配置时会导入 @Import(EnableWebMvcConfiguration.class)

@Configuration(proxyBeanMethods = false)
	public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware {



@Configuration(proxyBeanMethods = false)
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {

	private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();


    //从容器中获取所有的WebMvcConfigurer
	@Autowired(required = false)
	public void setConfigurers(List<WebMvcConfigurer> configurers) {
		if (!CollectionUtils.isEmpty(configurers)) {
			this.configurers.addWebMvcConfigurers(configurers);
		}
	}



//一个参考实现
//将所有的WebMvcConfiguration相关配置都来一起调用

@Override
protected void addViewControllers(ViewControllerRegistry registry) {
    this.configurers.addViewControllers(registry);
}

  1. 容器中所有的 WebMvcConfiguration 都会一起起作用
  2. 我们自己的配置类也会被调用

效果:SpringMVC 的自动配置和我们的扩展配置都会起作用

4.4.3 全面接管SpringMVC

假如Spring Boot不需要所有的 SpringMVC 自动配置了,想要所有配置都由我们自己来手动配置,想要所有的 SpringMVC 的自动配置都失效,这就是对 SpringMVC 的全面接管,想要实现对 SpringMVC 的全面接管,我们只要在配置类中添加 @EnableWebMvc 注解即可


@EnableWebMvc
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {


    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/taotao").setViewName("success");
        registry.addViewController("/").setViewName("login");
        registry.addViewController("/index.html").setViewName("login");
        registry.addViewController("/main.html").setViewName("dashboard");
    }

为什么在配置类中添加 @EnableWebMvc 后自动配置就失效了呢?我们来看看其中的原理:

  1. @EnableWebMvc核心源码:

@Import(DelegatingWebMvcConfiguration.class)
public @interface EnableWebMvc {
}

  1. DelegatingWebMvcConfiguration类部分源码:

@Configuration(proxyBeanMethods = false)
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {

  1. WebMvcAutoConfiguration类部分源码:

@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
//当容器中没有这个组件的时候,这个自动配置类才会生效
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
		ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {

  1. @EnableWebMvc 注解将 WebMvcConfigurationSupport 组件导入了进来

  2. 导入的 WebMvcConfigurationSupport 只是 SpringMVC 最基本的功能



4.4.4 修改Spring Boot的默认配置

所介绍的修改默认配置的模式是一个统一的模式,不只是适用于web模块;这个模式具体是这样的:

  1. Spring Boot在自动配置很多组件的时候,先看容器中有没有用户自己配置( @Bean、@Component ),如果有,就用用户所配置的组件,否则就会自动配置 ( @xxxConditional )。如果有些组件可以有多个(比如 ViewResolver ),它就会将用户配置的组件和他自己自动配置的组合起来;
  2. 在Spring Boot中会有许多的 xxxConfigurer ,帮助我们进行扩展配置
  3. 在Spring Boot中会有很多的 xxxCustomizer 帮助我们进行定制配置

4.5 RestfulCRUD——实验实战

A.设置默认访问首页

B.国际化

使用Spring Boot实现国际化的步骤:

  1. 编写国际化配置文件,抽取页面需要显示的国际化消息

08GUm9.png

08GDfK.png

08G66e.png

08GcOH.png


  1. Spring Boot自动配置好了管理国际化资源文件的组件

    @Bean
	@ConfigurationProperties(prefix = "spring.messages")
	public MessageSourceProperties messageSourceProperties() {
		return new MessageSourceProperties();
	}


    @Bean
	public MessageSource messageSource(MessageSourceProperties properties) {
		ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
        //设国际化资源的基础名(去掉"语言—_国家"代码的)
		if (StringUtils.hasText(properties.getBasename())) {
			messageSource.setBasenames(StringUtils
					.commaDelimitedListToStringArray(StringUtils.trimAllWhitespace(properties.getBasename())));
		}
		if (properties.getEncoding() != null) {
			messageSource.setDefaultEncoding(properties.getEncoding().name());
		}
		messageSource.setFallbackToSystemLocale(properties.isFallbackToSystemLocale());
		Duration cacheDuration = properties.getCacheDuration();
		if (cacheDuration != null) {
			messageSource.setCacheMillis(cacheDuration.toMillis());
		}
		messageSource.setAlwaysUseMessageFormat(properties.isAlwaysUseMessageFormat());
		messageSource.setUseCodeAsDefaultMessage(properties.isUseCodeAsDefaultMessage());
		return messageSource;
	}


public class MessageSourceProperties {

	/**
	 * Comma-separated list of basenames (essentially a fully-qualified classpath
	 * location), each following the ResourceBundle convention with relaxed support for
	 * slash based locations. If it doesn't contain a package qualifier (such as
	 * "org.mypackage"), it will be resolved from the classpath root.
	 */
	private String basename = "messages";

  1. 去页面获取国际化的值 用 #{} 的语法进行赋值

<!--login.html-->

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
	<head>
		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
		<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
		<meta name="description" content="">
		<meta name="author" content="">
		<title>Signin Template for Bootstrap</title>
		<!-- Bootstrap core CSS -->
		<link href="asserts/css/bootstrap.min.css" rel="stylesheet">
		<!-- Custom styles for this template -->
		<link href="asserts/css/signin.css" rel="stylesheet">
	</head>

	<body class="text-center">
		<form class="form-signin" action="dashboard.html">
			<img class="mb-4" src="asserts/img/bootstrap-solid.svg" alt="" width="72" height="72">
			<h1 class="h3 mb-3 font-weight-normal" th:text="#{login.tip}">Please sign in</h1>
			<label class="sr-only" th:text="#{login.username}">Username</label>
			<input type="text" class="form-control" placeholder="Username" th:placeholder="#{login.username}" required="" autofocus="">
			<label class="sr-only" th:text="#{login.password}">Password</label>
			<input type="password" class="form-control" placeholder="Password" th:placeholder="#{login.password}" required="">
			<div class="checkbox mb-3">
				<label>
          <input type="checkbox" value="remember-me"/> [[#{login.remember}]]
        </label>
			</div>
			<button class="btn btn-lg btn-primary btn-block" type="submit" th:text="#{login.btn}">Sign in</button>
			<p class="mt-5 mb-3 text-muted">© 2017-2018</p>
			<a class="btn btn-sm">中文</a>
			<a class="btn btn-sm">English</a>
		</form>
	</body>
</html>

效果:根据浏览器语言设置的信息实现国际化的语言切换

0UMckR.png

0UMWp6.png

0UMh6O.png


国际化的升级版:用户可以通过点击链接来切换页面所用的语言来实现国际化

首先了解一下请求头:

0UQp7j.png

0UQPNn.png

升级版国际化的实现原理: locale(区域信息对象) localeResolver(获取区域信息对象)


//WebMvcAutoConfiguration.java

@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(prefix = "spring.mvc", name = "locale")
public LocaleResolver localeResolver() {
	if (this.mvcProperties.getLocaleResolver() == WebMvcProperties.LocaleResolver.FIXED) {
		return new FixedLocaleResolver(this.mvcProperties.getLocale());
	}
	AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver();
	localeResolver.setDefaultLocale(this.mvcProperties.getLocale());
	return localeResolver;
}

默认是根据请求头带来的区域信息获取 Locale 进行国际化


//AcceptHeaderLocaleResolver.java

    @Override
	public Locale resolveLocale(HttpServletRequest request) {
		Locale defaultLocale = getDefaultLocale();
		if (defaultLocale != null && request.getHeader("Accept-Language") == null) {
			return defaultLocale;
		}
		Locale requestLocale = request.getLocale();
        //从请求头中获取区域信息
		List<Locale> supportedLocales = getSupportedLocales();
		if (supportedLocales.isEmpty() || supportedLocales.contains(requestLocale)) {
			return requestLocale;
		}
		Locale supportedLocale = findSupportedLocale(request, supportedLocales);
		if (supportedLocale != null) {
			return supportedLocale;
		}
		return (defaultLocale != null ? defaultLocale : requestLocale);
	}

C.登录

模板引擎页面修改后想要它实时生效,我们需要两步:

  1. 禁用模板引擎的缓存
# application.properties

# 禁用缓存
spring.thymeleaf.cache=false

  1. 页面修改完成后重新编译(Ctrl+F9)

登录错误消息的显示:


<p style="color: #bd2130" th:text="${msg}" th:if="${not #strings.isEmpty(msg)}"></p>

当我们刷新页面时,浏览器会出现这样的提示:

0UQGjO.png

为了防止重复提交表单,我们需要进行重定向


if(!StringUtils.isEmpty(username) && "123456".equals(password)){
    session.setAttribute("loginUser",username);
    return "redirect:/main.html";
}

D.拦截器进行登录检查

拦截器:


/**
 * 实现登录检查,没有登录的用户就不能访问后台的主页,也不能对员工进行增删改查*/
public class LoginHandlerInterceptor implements HandlerInterceptor {
    //目标方法执行之前
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        Object user = request.getSession().getAttribute("loginUser");
        if(user == null){
            //未登录,返回登录页面
            request.setAttribute("msg","没有权限,请先登录");
            request.getRequestDispatcher("index.html").forward(request,response);
            return false;
        }else{
            //已登录,放行请求
            return true;
        }
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

    }
}


注册拦截器:


@Override
public void addViewControllers(ViewControllerRegistry registry) {
    registry.addViewController("/taotao").setViewName("success");
    registry.addViewController("/").setViewName("login");
    registry.addViewController("/index.html").setViewName("login");
    registry.addViewController("/main.html").setViewName("dashboard");
}


//注册拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(new LoginHandlerInterceptor()).addPathPatterns("/**").excludePathPatterns(
            "/index.html", "/", "/user/login", "/webjars/**", "/asserts/**", "/resources/**");
}

E.CRUD-员工列表

  1. Restful-CRUD:CRUD要满足Rest风格

普通的CRUD和Restful-CRUD 的对比:

操作普通的CRUD(用URI来区分操作)Restful-CRUD
查询发送getEmp请求直接发送emp请求(以GET方式发送)
添加发送addEmp请求emp(以POST方式发送)
修改发送updateEmp请求emp(以PUT方式发送)
删除发送deleteEmp请求emp(以DELETE方式发送)
  1. 实验的请求架构
操作请求URI请求方式
查询所有员工empsGET
查询某一个员工emp/1GET
来到添加页面empGET
添加员工empPOST
来到修改页面(查出员工后进行信息回显)emp/1GET
修改员工empPUT
删除员工emp/1DELETE
  1. 员工列表

thymeleaf公共页面元素抽取

    1. 抽取公共片段

<div th:fragment="copy">
    &copy; 2011 The Good Thymes Virtual Grocery
</div>

    1. 引入公共片段
<!--选择器-->

<div id="copy-section">
  &copy; 2011 The Good Thymes Virtual Grocery
</div>


<body>
  <div th:insert="~{footer :: #copy-section}"></div>
</body>


<!--模板名-->
<div th:insert="~{footer :: copy}"></div>

~{templatename::selector} :模板名::选择器 ~{templatename::fragmentname} :模板名::片段名

    1. 默认效果 insert 的功能片段的 div 标签中 如果使用 th:insert 等属性进行引入的话,就可以不写 ~{} 不过使用行内写法的话就得写上 ~{},就像这样: [[~{}]]; [(~{})];

三种引入功能片段的th属性: th:insert: 将公共片段整个插入到声明引入的元素中 th:replace: 将声明引入的元素替换为公共片段 th:include: 将被引入的片段的内容包含进这个标签中


<footer th:fragment="copy">
  &copy; 2011 The Good Thymes Virtual Grocery
</footer>


<body>

  <div th:insert="footer :: copy"></div>
  <div th:replace="footer :: copy"></div>
  <div th:include="footer :: copy"></div>
  
</body>

结果:


<body>

  <div>
    <footer>
      &copy; 2011 The Good Thymes Virtual Grocery
    </footer>
  </div>

  <footer>
    &copy; 2011 The Good Thymes Virtual Grocery
  </footer>

  <div>
    &copy; 2011 The Good Thymes Virtual Grocery
  </div>
  
</body>

引入片段的时候传入参数:


<nav class="col-md-2 d-none d-md-block bg-light sidebar" id="sidebar">
    <div class="sidebar-sticky">
        <ul class="nav flex-column">
            <li class="nav-item">
                <a class="nav-link active" th:class="${activeUri=='main.html'?'nav-link active':'nav-link'}" href="#" th:href="@{/main.html}">
                    <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-home">
                        <path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path>
                        <polyline points="9 22 9 12 15 12 15 22"></polyline>
                    </svg>
                    Dashboard <span class="sr-only">(current)</span>
                </a>
            </li>
        </ul>
    </div>
</nav>

F.员工添加

添加页面


<form>
    <div class="form-group">
        <label>LastName</label>
        <input type="text" class="form-control" placeholder="zhangsan">
    </div>
    <div class="form-group">
        <label>Email</label>
        <input type="email" class="form-control" placeholder="zhangsan@atguigu.com">
    </div>
    <div class="form-group">
        <label>Gender</label><br/>
        <div class="form-check form-check-inline">
            <input class="form-check-input" type="radio" name="gender"  value="1">
            <label class="form-check-label"></label>
        </div>
        <div class="form-check form-check-inline">
            <input class="form-check-input" type="radio" name="gender"  value="0">
            <label class="form-check-label"></label>
        </div>
    </div>
    <div class="form-group">
        <label>department</label>
        <select class="form-control">
            <option>1</option>
            <option>2</option>
            <option>3</option>
            <option>4</option>
            <option>5</option>
        </select>
    </div>
    <div class="form-group">
        <label>Birth</label>
        <input type="text" class="form-control" placeholder="zhangsan">
    </div>
    <button type="submit" class="btn btn-primary">添加</button>
</form>

在添加员工的操作过程中,容易产生一个问题:提交的数据格式不对; 以日期为例,下面三种日期格式都是允许的,但是对于页面来说,我们只允许一种格式的数据能成功提交: 2000-05-04 2000/05/04 2000.05.04 于是,我们就需要对提交的日期数据格式化,SpringMVC需要将页面提交的值转换为我们需要的类型 默认日期是按照类似于2000/05/04的格式显示的,所以我们需要通过类型转换+格式化,来将2000-05-04作为日期的格式

直接修改配置文件的配置


spring.mvc.format.date=yyyy-MM-dd

G.员工修改

修改添加二合一表单


<form th:action="@{/emp}" method="post">
    <!--发送put请求修改员工数据-->
    <!--1. SpringMVC中配置HiddenHttpMethodFilter;(SpringBoot自动配置好的)
    2. 页面创建一个post表单
    3. 创建一个input项,name="_method"值就是我们指定的请求方式
    -->
    <input type="hidden" name="_method" value="put" th:if="${emp!=null}"/>
    <input type="hidden" name="id" th:if="${emp!=null}" th:value="${emp.id}"/>
    <div class="form-group">
        <label>LastName</label>
        <input name="lastName" type="text" class="form-control" placeholder="zhangsan" th:value="${emp!=null}?${emp.lastName}">
    </div>
    <div class="form-group">
        <label>Email</label>
        <input name="email" type="email" class="form-control" placeholder="zhangsan@atguigu.com" th:value="${emp!=null}?${emp.email}">
    </div>
    <div class="form-group">
        <label>Gender</label><br/>
        <div class="form-check form-check-inline">
            <input class="form-check-input" type="radio" name="gender"  value="1" th:checked="${emp!=null}?${emp.gender==1}">
            <label class="form-check-label"></label>
        </div>
        <div class="form-check form-check-inline">
            <input class="form-check-input" type="radio" name="gender"  value="0" th:checked="${emp!=null}?${emp.gender==0}">
            <label class="form-check-label"></label>
        </div>
    </div>
    <div class="form-group">
        <label>department</label>
        <!--提交的是部门的id-->
        <select class="form-control" name="department.id">
            <option th:selected="${emp!=null}?${dept.id == emp.department.id}" th:value="${dept.id}" th:each="dept:${depts}" th:text="${dept.departmentName}">1</option>
        </select>
    </div>
    <div class="form-group">
        <label>Birth</label>
        <input name="birth" type="text" class="form-control" placeholder="zhangsan" th:value="${emp!=null}?${#dates.format(emp.birth, 'yyyy-MM-dd')}" th:placeholder="${'2000-05-04'}">
    </div>
    <button type="submit" class="btn btn-primary" th:text="${emp!=null}?'修改':'添加'">添加</button>
</form>

H.员工删除

员工删除页面


<tr th:each="emp:${emps}">
    <td th:text="${emp.id}"></td>
    <td>[[${emp.lastName}]]</td>
    <td th:text="${emp.email}"></td>
    <td th:text="${emp.gender}==0?'女':'男'"></td>
    <td th:text="${emp.department.departmentName}"></td>
    <td th:text="${#dates.format(emp.birth, 'yyyy-MM-dd HH:mm')}"></td>
    <td>
        <a class="btn btn-sm btn-primary" th:href="@{/emp/}+${emp.id}">编辑</a>

        <button th:attr="del_uri=@{/emp/}+${emp.id}" class="btn btn-sm btn-danger deleteBtn">删除</button>
    </td>
</tr>


4.6 错误处理机制

4.6.1 Spring Boot默认的错误处理机制

默认效果:

  1. 浏览器会返回一个默认的错误页面

0Y6Axg.png

浏览器发送请求的请求头:

0Yv1fK.png


  1. 如果是其他客户端进行访问,则默认响应一个json数据

0YvJ6e.png

客户端发送请求的请求头:

0YvBff.png


原理:可以参照ErrorMvcAutoConfiguration(错误处理的自动配置) 它给容器中添加了以下组件:

  • DefaultErrorAttributes

//帮我们在页面共享信息

@Override
	@Deprecated
	public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
		Map<String, Object> errorAttributes = new LinkedHashMap<>();
		errorAttributes.put("timestamp", new Date());
		addStatus(errorAttributes, webRequest);
		addErrorDetails(errorAttributes, webRequest, includeStackTrace);
		addPath(errorAttributes, webRequest);
		return errorAttributes;
	}

  • BasicErrorController
//处理默认/error请求

@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class BasicErrorController extends AbstractErrorController {

    @RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
    //产生html类型的数据;浏览器发送的请求到这个方法处理
	public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
		HttpStatus status = getStatus(request);
		Map<String, Object> model = Collections
				.unmodifiableMap(getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.TEXT_HTML)));
		response.setStatus(status.value());
        //去哪个页面作为错误页面;modelAndView包含页面地址和页面内容
		ModelAndView modelAndView = resolveErrorView(request, response, status, model);
		return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
	}

	@RequestMapping
    //产生json数据;其他客户端发送的请求到这个方法处理
	public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
		HttpStatus status = getStatus(request);
		if (status == HttpStatus.NO_CONTENT) {
			return new ResponseEntity<>(status);
		}
		Map<String, Object> body = getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.ALL));
		return new ResponseEntity<>(body, status);
	}
}


public static final String TEXT_HTML_VALUE = "text/html";

  • ErrorPageCustomizer
//系统出现错误以后来到error请求进行处理

@Value("${error.path:/error}")
private String path = "/error";

  • DefaultErrorViewResolver

@Override
	public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
		ModelAndView modelAndView = resolve(String.valueOf(status.value()), model);
		if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
			modelAndView = resolve(SERIES_VIEWS.get(status.series()), model);
		}
		return modelAndView;
	}

	private ModelAndView resolve(String viewName, Map<String, Object> model) {
        //默认Spring Boot可以去找到一个error/404页面
		String errorViewName = "error/" + viewName;

        //假如模板引擎可以解析这个页面地址,那么就用模板引擎进行解析
		TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName,
				this.applicationContext);
		if (provider != null) {
            //模板引擎可用的情况下返回到errorViewName指定的视图地址
			return new ModelAndView(errorViewName, model);
		}
        //当模板引擎不可用时,就在静态资源文件夹下找errorViewName对应的页面(error/404.html)
		return resolveResource(errorViewName, model);
	}


错误处理的流程如下:

一旦系统出现4xx(客户端错误)或5xx(服务器错误)之类的错误, ErrorPageCustomizer 就会生效(定制错误的响应规则),就会来到 /error请求,然后就会被 BasicErrorController 处理;这一步会分为两种情况:

  1. 响应页面:(去哪个页面是由 DefaultErrorViewResolver 解析得到的)

protected ModelAndView resolveErrorView(HttpServletRequest request, HttpServletResponse response, HttpStatus status,
		Map<String, Object> model) {
            //所有的ErrorViewResolver得到modelAndView
	for (ErrorViewResolver resolver : this.errorViewResolvers) {
		ModelAndView modelAndView = resolver.resolveErrorView(request, status, model);
		if (modelAndView != null) {
			return modelAndView;
		}
	}
	return null;
}

  1. 响应Json数据

4.6.2 如何定制错误响应

  1. 如何定制错误的页面
  • 有模板引擎的情况下: 将错误页面命名为: 错误状态码/html(例:404/html) ,并放在模板引擎文件夹中的 error 文件夹下 当发生此状态码的错误时便显示该对应页面 另外,我们还可以使用 4xx5xx 作为错误页面的文件名来匹配这种类型的所有错误 不过,会优先寻找 精确的状态码.html 文件 页面能获取的信息: timestamp:时间戳 status:状态码 error:错误提示 exception:异常对象 message:异常信息 errors:JSR303数据校验的错误

  • 没有模板引擎的情况下:(也就相当于模板引擎找不到这个错误页面) 默认在静态资源文件下找

  • 假如模板引擎和静态资源文件下都没有错误页面,那就会默认来到Spring Boot默认的错误提示页面

源码如下:


@Override
public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response)
		throws Exception {
	if (response.isCommitted()) {
		String message = getMessage(model);
		logger.error(message);
		return;
	}
	response.setContentType(TEXT_HTML_UTF8.toString());
	StringBuilder builder = new StringBuilder();
	Object timestamp = model.get("timestamp");
	Object message = model.get("message");
	Object trace = model.get("trace");
	if (response.getContentType() == null) {
		response.setContentType(getContentType());
	}
	builder.append("<html><body><h1>Whitelabel Error Page</h1>").append(
			"<p>This application has no explicit mapping for /error, so you are seeing this as a fallback.</p>")
			.append("<div id='created'>").append(timestamp).append("</div>")
			.append("<div>There was an unexpected error (type=").append(htmlEscape(model.get("error")))
			.append(", status=").append(htmlEscape(model.get("status"))).append(").</div>");
	if (message != null) {
		builder.append("<div>").append(htmlEscape(message)).append("</div>");
	}
	if (trace != null) {
		builder.append("<div style='white-space:pre-wrap;'>").append(htmlEscape(trace)).append("</div>");
	}
	builder.append("</body></html>");
	response.getWriter().append(builder.toString());
}

  1. 如何定制错误的json数据
  • 一、自定义异常处理&返回定制的json数据

@ControllerAdvice
public class MyExceptionHandler {

    //浏览器和客户端返回的都是json数据
    @ResponseBody
    @ExceptionHandler(UserNotExistException.class)
    public Map<String, Object> handleException(Exception e){

        Map<String,Object> map = new HashMap<>();
        map.put("code","user.notexist");
        map.put("message",e.getMessage());

        return map;
    }
}

//没有自适应效果,也就是说浏览器和客户端访问都是json数据,因为这一块被写死了

  • 二、转发到 /error 进行自适应响应效果处理

@ExceptionHandler(UserNotExistException.class)
public String handleException(Exception e, HttpServletRequest request){

    Map<String,Object> map = new HashMap<>();
    //应该传入自己的错误状态码,否则举不会进入定制错误页面的解析流程
    /*Integer statusCode = (Integer) request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);*/
    request.setAttribute("javax.servlet.error.status_code",500);

    //这俩的效果没办法显示出来,需要改进
    map.put("code","user.notexist");
    map.put("message",e.getMessage());

    //转发到/error
    return "forward:/error";
}

  • 三、将我们的定制数据携带出去

出现错误以后,会来到 /error 请求,会被 BasicErrorController 处理,响应出去可以获取的数据是由 getErrorAttributes 得到的(是 AbstractErrorController (实现的是 ErrorController 接口)规定的方法)

方法1:直接编写一个 ErrorController 的实现类(或者直接编写 AbstractErrorController 的子类),放在容器中 方法2:页面上能用的数据(或者json返回的能用的数据)都是通过errorAttributes.getErrorAttributes得到的 容器中 DefaultErrorAttributes() 默认来进行数据处理

自定义 ErrorAttributes :


//向容器中加入我们自己定义的ErrorAttributes
@Component
public class MyErrorAttributes extends DefaultErrorAttributes {

    //返回值的map就是页面和json能获取的所有字段
    @Override
    public Map<String, Object> getErrorAttributes(WebRequest webRequest, ErrorAttributeOptions options) {
        Map<String, Object> map = super.getErrorAttributes(webRequest, options);
        map.put("company","taotao");


        //我们的异常处理器携带的数据
        Map<String,Object> ext = (Map<String, Object>) webRequest.getAttribute("ext", 0);
        map.put("ext",ext);
        return map;
    }
}

最终的效果:响应是自适应的,可以通过定制 ErrorAttributes 改变需要返回的内容

4.7 配置嵌入式Servlet容器

Spring Boot默认使用的是嵌入式的 Servlet 容器(Tomcat

0tgSJ0.png

需要考虑的问题:

  • 问题1: 如何定制和修改Servlet容器的相关配置?

    • 修改和Server有关的配置(ServerProperties)
    server.port=8081
    server.context-path=/crud
    
    server.tomcat.uri-encoding=UTF-8
    
    //通用的Servlet容器设置
    server.xxx
    //Tomcat的设置
    server.tomcat.xxx
    
    • 编写一个WebServerFactoryCustomizer:嵌入式Servlet容器的定制器,来修改Servlet容器的配置
    
    @Bean
    public WebServerFactoryCustomizer<ConfigurableWebServerFactory> webServerFactoryWebServerFactoryCustomizer(){
        return new WebServerFactoryCustomizer<ConfigurableWebServerFactory>() {
            @Override
            public void customize(ConfigurableWebServerFactory factory) {
                factory.setPort(8083);
            }
        };
    }
    

4.7.1 注册Servlet三大组件(Servlet、Filter、Listener)

由于Spring Boot默认是以jar包的方式启动嵌入式的Servlet容器来启东Spring Boot的Web应用,没有web.xml文件

注册三大组件用以下方式:

ServletRegistrationBean


//注册三大组件
@Bean
public ServletRegistrationBean myServlet(){
    ServletRegistrationBean registrationBean = new ServletRegistrationBean(new MyServlet(),"/myServlet");
    return registrationBean;
}

FilterRegistrationBean


@Bean
public FilterRegistrationBean myFilter(){
    FilterRegistrationBean registrationBean = new FilterRegistrationBean();
    registrationBean.setFilter(new MyFilter());
    registrationBean.setUrlPatterns(Arrays.asList("/hello","/myServlet"));
    return registrationBean;
}


ServletListenerRegistrationBean


@Bean
public ServletListenerRegistrationBean<MyListener> myListener(){
    ServletListenerRegistrationBean<MyListener> registrationBean = new ServletListenerRegistrationBean<>(new MyListener());
    return registrationBean;
}

Spring Boot帮我们自动配置SpringMVC的时候,自动注册了SpringMVC的前端控制器(DispatcherServlet


@Configuration(proxyBeanMethods = false)
@Conditional(DispatcherServletRegistrationCondition.class)
@ConditionalOnClass(ServletRegistration.class)
@EnableConfigurationProperties(WebMvcProperties.class)
@Import(DispatcherServletConfiguration.class)
protected static class DispatcherServletRegistrationConfiguration {

	@Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
	@ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
	public DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet,
			WebMvcProperties webMvcProperties, ObjectProvider<MultipartConfigElement> multipartConfig) {
		DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet,
				webMvcProperties.getServlet().getPath());
                //默认拦截: /  所有请求(包括静态资源),但是不拦截jsp请求(/*才会拦截jsp请求)
                //可以通过配置文件中的spring.mvc.servlet.path来修改SpringMVC前端控制器默认拦截的请求路径

		registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
		registration.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());
		multipartConfig.ifAvailable(registration::setMultipartConfig);
		return registration;
	}
}

4.7.2 使用其他Servlet容器

  • 问题2: Spring Boot能不能支持其他的Servlet容器?

Jetty(适用于长连接场景) Undertow(不支持jsp) Spring Boot默认支持切换Jetty和Undertow

0NKT4P.png

默认支持: Tomcat(默认使用) Undertow Jetty


<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>


<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <exclusion>
            <artifactId>spring-boot-starter-tomcat</artifactId>
            <groupId>org.springframework.boot</groupId>
        </exclusion>
    </exclusions>
</dependency>


<dependency>
    <artifactId>spring-boot-starter-jetty</artifactId>
    <groupId>org.springframework.boot</groupId>
</dependency>


<dependency>
    <artifactId>spring-boot-starter-undertow</artifactId>
    <groupId>org.springframework.boot</groupId>
</dependency>

4.7.3 嵌入式Servlet容器自动配置原理

EmbeddedWebServerFactoryCustomizerAutoConfiguration:嵌入式的Servlet容器自动配置


@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication
@EnableConfigurationProperties(ServerProperties.class)
public class EmbeddedWebServerFactoryCustomizerAutoConfiguration {

	/**
	 * Nested configuration if Tomcat is being used.
	 */
	@Configuration(proxyBeanMethods = false)
	@ConditionalOnClass({ Tomcat.class, UpgradeProtocol.class })//判断当前是否引入了Tomcat依赖
	public static class TomcatWebServerFactoryCustomizerConfiguration {

		@Bean
		public TomcatWebServerFactoryCustomizer tomcatWebServerFactoryCustomizer(Environment environment,
				ServerProperties serverProperties) {
			return new TomcatWebServerFactoryCustomizer(environment, serverProperties);
		}

	}

4.8 使用外置的Servlet容器

4.8.1 操作

嵌入式Servlet容器:(应用打包为可执行的Jar包) 优点:简单便携 缺点:默认不支持JSP,优化&定制会比较复杂

使用外置的Servlet容器:在外部安装一个Tomcat环境,应用以war包的方式打包

步骤:

  1. 先创建一个War项目

  2. 将嵌入式的Tomcat指定为provided


<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-tomcat</artifactId>
    <scope>provided</scope>
</dependency>

  1. 需要编写一个SpringBootServletInitializer的子类,并调用configure方法

public class ServletInitializer extends SpringBootServletInitializer {

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        //传入Spring Boot应用的主程序
        return application.sources(SpringBoot04WebJspApplication.class);
    }

}

  1. 启动服务器,就可以开始使用了