springboot-web开发

文章目录

Web开发

1.简介

使用SpringBoot:
1)、创建SpringBoot应用,选中需要的模块。
2)、SpringBoot已经默认将这些场景配置好了,只需要在配置文件中指定少量配置就可以运行起来。
3)、自己编写业务代码。

自动配置原理?
这个场景SpringBoot帮我们配置了什么?能不能修改?能修改哪些配置?能不能扩展?xxx

xxxxAutoConfiguration:帮我们给容器中自动配置组件。
xxxxProperties:配置类来封装配置文件的内容。

2.SpringBoot对静态资源的映射规则

1.ResourceProperties

/**
 * 用于配置资源处理的属性。 
 */
@ConfigurationProperties(prefix = "spring.resources", ignoreUnknownFields = false)
public class ResourceProperties {
	//可以设置和静态资源有关的参数,缓存时间等。

2.WebMvcAutoConfiguration

@Configuration
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class,ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
	@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) {
		return new WelcomePageHandlerMapping(
				new TemplateAvailabilityProviders(applicationContext),
				applicationContext, getWelcomePage(),
				this.mvcProperties.getStaticPathPattern());
	}
	
	//配置喜欢的图标
	@Configuration
	@ConditionalOnProperty(value = "spring.mvc.favicon.enabled", matchIfMissing = true)
	public static class FaviconConfiguration implements ResourceLoaderAware {
	
		private final ResourceProperties resourceProperties;
	
		private ResourceLoader resourceLoader;
	
		public FaviconConfiguration(ResourceProperties resourceProperties) {
			this.resourceProperties = resourceProperties;
		}
	
		@Override
		public void setResourceLoader(ResourceLoader resourceLoader) {
			this.resourceLoader = resourceLoader;
		}
	
		@Bean
		public SimpleUrlHandlerMapping faviconHandlerMapping() {
			SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
			mapping.setOrder(Ordered.HIGHEST_PRECEDENCE + 1);
			//所有 **/favicon.ico
			mapping.setUrlMap(Collections.singletonMap("**/favicon.ico",faviconRequestHandler()));
			return mapping;
		}
	
		@Bean
		public ResourceHttpRequestHandler faviconRequestHandler() {
			ResourceHttpRequestHandler requestHandler = new ResourceHttpRequestHandler();
			requestHandler.setLocations(resolveFaviconLocations());
			return requestHandler;
		}
	
		private List<Resource> resolveFaviconLocations() {
			String[] staticLocations = getResourceLocations(
					this.resourceProperties.getStaticLocations());
			List<Resource> locations = new ArrayList<>(staticLocations.length + 1);
			Arrays.stream(staticLocations).map(this.resourceLoader::getResource).forEach(locations::add);
			locations.add(new ClassPathResource("/"));
			return Collections.unmodifiableList(locations);
		}
	}
1.所有 /webjars/** ,都去 classpath:/META-INF/resources/webjars/ 找资源。

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

<!‐‐引入jquery‐webjar‐‐>在访问的时候只需要写webjars下面资源的名称即可
<dependency>
    <groupId>org.webjars</groupId>
    <artifactId>jquery</artifactId>
    <version>3.5.0</version>
</dependency>

在这里插入图片描述
http://localhost:8080/webjars/jquery/3.5.0/jquery.js
在这里插入图片描述

webjars文档:http://www.webjars.org/,里面有很多静态资源都是以jar包的方式。
在这里插入图片描述

2."/**" 访问当前项目的任何资源,都去(静态资源的文件夹)找映射
"classpath:/META‐INF/resources/",
"classpath:/resources/",
"classpath:/static/",
"classpath:/public/",
"/":当前项目的根路径

http://localhost:8080/abc —> 默认去上面的静态资源文件夹里面找abc

3.欢迎页,静态资源文件夹下的所有index.html页面,被"/**"映射;

http://localhost:8080/ 找index页面

4.所有的 **/favicon.ico 都是在静态资源文件下找

3.模板引擎

JSP、Velocity、Freemarker、Thymeleaf
在这里插入图片描述SpringBoot推荐的Thymeleaf:语法更简单,功能更强大。
thymeleaf中文文档:https://pan.baidu.com/s/1VAJC22Jvx2LnvMep9f55hg 提取码:qs4d
thymeleaf中文文档:http://www.75271.com/20088.html
thymeleaf中文文档:https://www.kaifaxueyuan.com/frontend/thymeleaf3/thymeleaf-index.html

1.引入thymeleaf

因为老师使用的是1.5.9版本springboot,thymeleaf是2.1.6版本太低了,所以要提高thymeleaf版本。
<dependency> 
	<groupId>org.springframework.boot</groupId> 
	<artifactId>spring‐boot‐starter‐thymeleaf</artifactId>
</dependency> 

切换thymeleaf版本(提高thymeleaf版本,随着layout也要提高为什么呢?)
<properties> 
	<thymeleaf.version>3.0.9.RELEASE</thymeleaf.version> 
	<!‐‐ 布局功能的支持程序 thymeleaf版本3 需要layout2以上版本做支持。 ‐‐> 
	<!‐‐ thymeleaf2 layout1‐‐> 
	<thymeleaf‐layout‐dialect.version>2.2.2</thymeleaf‐layout‐dialect.version> 
</properties>

在这里插入图片描述我现在使用springboot2.0.1版本随着spring‐boot‐starter‐thymeleaf和thymeleaf‐layout‐dialect也会提升,就不用覆盖了。
在这里插入图片描述

2.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就能自动渲染。

1.导入thymeleaf的名称空间(就有语法提示了)
<html lang="en" xmlns:th="http://www.thymeleaf.org">
2.使用thymeleaf语法

success.html

<!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 th:text="${hello}">这是显示欢迎信息</div>
</body>
</html>

HelloController

@Controller
public class HelloController {
    @RequestMapping("/hello")
    public String sayHello(Map<String,String> map){
        //默认会存到request域中
        map.put("hello","你好");
        return "success";
    }
}

测试结果
在这里插入图片描述

3.语法规则

1.th:任意html属性,来替换原生属性的值

th:text,改变当前元素里面的文本内容
在thymeleaf的第10章:
在这里插入图片描述

2.表达式

在thymeleaf的第4章:

1.简单表达式
  • 变量表达式: ${…}
  • 选择变量表达式: *{…}
  • 消息表达: #{…}
  • 链接URL表达式: @{…}
  • 片段表达式: ~{…}
1.变量表达式: ${…}

获取变量值,OGNL,获取对象的属性调用方法,使用内置的基本对象。
表达式基本对象:
#ctx:上下文对象。
#vars: 上下文变量。
#locale:上下文语言环境。
#request:(仅在Web上下文中)HttpServletRequest对象。
#response:(仅在Web上下文中)HttpServletResponse对象。
#session:(仅在Web上下文中)HttpSession对象。
#servletContext:(仅在Web上下文中)ServletContext对象。

表达工具对象:
#execInfo:有关正在处理的模板的信息。
#messages:用于获取变量表达式内的外部化消息的方法,与使用#{…}语法获得消息的方法相同。
#uris:用于转义部分URL / URI的方法
#conversions:用于执行已配置的转换服务(如果有)的方法。
#dates:java.util.Date对象的方法:格式化,组件提取等。
#calendars:类似于#dates,但用于java.util.Calendar对象。
#numbers:格式化数字对象的方法。
#strings:String对象的方法:包含,startsWith,前置/追加等。
#objects:一般对象的方法。
#bools:布尔值评估的方法。
#arrays:数组方法。
#lists:列表方法。
#sets:套方法。
#maps:地图方法。
#aggregates:用于在数组或集合上创建聚合的方法。
#ids:用于处理可能重复的id属性的方法(例如,由于迭代的结果)。

2.选择变量表达式: *{…}

变量表达式不仅可以写成${…},而且还可以写成*{…}。
但是,有一个重要的区别:星号语法在选定对象而不是整个上下文上评估表达式。也就是说,只要没有选定的对象,美元和星号的语法就完全一样。

什么是选定对象?使用该th:object属性的表达式的结果。让我们在用户个人资料(userprofile.html)页面中使用一个:
在这里插入图片描述
完全等同于:
在这里插入图片描述
当然,美元和星号语法可以混合使用:
在这里插入图片描述
选择对象后,选定的对象也可以作为#object表达式变量用于美元表达式:
在这里插入图片描述
如前所述,如果尚未执行任何对象选择,则美元和星号语法是等效的。
在这里插入图片描述

3.消息表达: #{…}:获取国际化内容

众所周知,#{…}消息表达式使我们可以链接以下内容:
在这里插入图片描述
…对此:
在这里插入图片描述
但是,我们仍然没有想到的一个方面:如果消息文本不是完全静态的,会发生什么?例如,如果我们的应用程序知道随时有谁在访问该站点,而我们想按名称打招呼该怎么办?
在这里插入图片描述
这意味着我们需要在消息中添加一个参数。像这样:
在这里插入图片描述
参数是根据java.text.MessageFormat标准语法指定的,这意味着您可以按照API文档中为java.text.*包中的类指定的格式格式化数字和日期。

为了给我们的参数指定一个值,并给定一个HTTP会话属性user,我们可以拥有:
在这里插入图片描述
注意th:utext这里的使用意味着格式化的消息将不会被转义。本示例假定user.name已被转义。

可以指定几个参数,以逗号分隔。
消息密钥本身可以来自变量:
在这里插入图片描述

4.链接URL表达式: @{…}

由于URL的重要性,URL是Web应用程序模板中的一等公民,而Thymeleaf Standard Dialect为它们提供了一种特殊的语法,该@语法为:@{…}。

URL有不同类型:
	绝对网址: http://www.thymeleaf.org
	相对URL,可以是:
		相对页面: user/login.html
		上下文相关:(/itemdetails?id=3服务器中的上下文名称将自动添加)
		相对于服务器:(~/billing/processInvoice允许在同一服务器中的另一个上下文(=应用程序)中调用URL。
		相对协议网址: //code.jquery.com/jquery-2.0.3.min.js

这些表达式的实际处理以及它们到将要输出的URL的转换是通过org.thymeleaf.linkbuilder.ILinkBuilder注册到ITemplateEngine所使用的对象的接口实现来完成的。

默认情况下,该类的该接口的单个​​实现被注册org.thymeleaf.linkbuilder.StandardLinkBuilder,这对于脱机(非Web)和基于Servlet API的Web场景都足够。其他场景(例如与非ServletAPI Web框架集成)可能需要链接构建器接口的特定实现。

让我们使用这种新语法。符合th:href属性:

<!-- Will produce 'http://localhost:8080/gtvg/order/details?orderId=3' (plus rewriting) -->
<a href="details.html" th:href="@{http://localhost:8080/gtvg/order/details(orderId=${o.id})}">view</a>

<!-- Will produce '/gtvg/order/details?orderId=3' (plus rewriting) -->
<a href="details.html" th:href="@{/order/details(orderId=${o.id})}">view</a>

<!-- Will produce '/gtvg/order/3/details' (plus rewriting) -->
<a href="details.html" th:href="@{/order/{orderId}/details(orderId=${o.id})}">view</a>

这里要注意一些事情:

  • th:href是修饰符属性:处理后,它将计算要使用的链接URL,并将该值设置href为标记的属性。
  • 我们被允许对URL参数使用表达式(如您在中所见orderId=${o.id})。所需的URL参数编码操作也将自动执行。
  • 如果需要几个参数,这些将用逗号分隔:@{/order/process(execId=${execId},execType=‘FAST’)}
  • URL路径中也允许使用变量模板: @{/order/{orderId}/details(orderId=${orderId})}
  • 以/(例如:)开头的相对URL /order/details将自动以应用程序上下文名称作为前缀。
  • 如果未启用Cookie或尚不知道,则";jsessionid=…"可能会将后缀添加到相对URL中,以便保留会话。这称为URL重写,并且Thymeleaf允许您通过使用response.encodeURL(…)ServletAPI中的机制为每个URL 插入自己的重写过滤器。
  • 该th:href属性允许我们(可选)href在模板中具有有效的静态属性,以便当直接打开原型进行原型设计时,浏览器仍可导航我们的模板链接。

与消息语法(#{…})一样,URL基也可以是求值另一个表达式的结果:
在这里插入图片描述
主页的菜单
现在,我们知道了如何创建链接URL,如何在主页中为站点中的其他页面添加一个小菜单?
在这里插入图片描述
服务器根目录相对URL
可以使用其他语法来创建相对于服务器根目录的URL(而不是上下文根目录的URL),以便链接到同一服务器中的不同上下文。这些网址的指定方式如下@{~/path/to/something}

5.片段表达式: ~{…}

片段表达式是表示标记片段并将其在模板中移动的简便方法。这使我们能够复制它们,并将它们作为参数传递给其他模板,依此类推。

最常见的用途是使用th:insert或th:replace(在后面的部分中有更多关于)的片段插入:
在这里插入图片描述
但是它们可以在任何地方使用,就像其他任何变量一样:
在这里插入图片描述
在本教程的后面,将有一个完整的章节专门介绍“模板布局”,包括对片段表达式的更深入的说明。

2.文字
  • 文本文字:‘one text’,‘Another one!’,…
  • 号码文字:0,34,3.0,12.3,…
  • 布尔文字:true,false
  • 空文字: null
  • 文字标记:one,sometext,main,…
1.文本文字

文本文字只是在单引号之间指定的字符串。它们可以包含任何字符,但是您应该使用来对其中的任何单引号进行转义’。
在这里插入图片描述

2.数字文字

数字文字就是:数字。
在这里插入图片描述

3.布尔文字

布尔文字是true和false。例如:
在这里插入图片描述
在此示例中,== falsey是写在花括号外的,因此Thymeleaf负责处理。如果将其写在花括号内,则OGNL / SpringEL引擎应负责:
在这里插入图片描述

4.空文字

该null文本也可用于:
在这里插入图片描述

5.文字代币

实际上,数字,布尔值和null文字是文字标记的一种特殊情况。

这些标记允许在标准表达式中进行一些简化。它们的工作方式与文本文字(’…’)完全相同,但是它们仅允许使用字母(A-Z和a-z),数字(0-9),方括号([和]),点(.),连字符(-)和下划线(_)。因此,没有空格,没有逗号等。

好的部分?令牌不需要任何引号。因此,我们可以这样做:
在这里插入图片描述
代替:
在这里插入图片描述

3.文字操作
  • 字符串串联: +
  • 文字替换: |The name is ${name}|
1.附加文字

文本,无论是文字还是评估变量或消息表达式的结果,都可以使用+运算符轻松附加:
在这里插入图片描述

2.文字替代

文字替换可以轻松格式化包含变量值的字符串,而无需在文字后面附加’…’ + ‘…’。

这些替换项必须用竖线(|)包围,例如:
在这里插入图片描述
等效于:
在这里插入图片描述
文字替换可以与其他类型的表达式结合使用:
在这里插入图片描述
唯一的变量/消息表达式(${…},*{…},#{…})被允许内部|…|字面取代。没有其他文字(’…’),布尔/数字标记,条件表达式等。

4.算术运算
  • 二元运算符:+,-,*,/,%
  • 减号(一元运算符): -

一些算术运算也可用:+,-,*,/和%。
在这里插入图片描述
请注意,这些运算符也可以在OGNL变量表达式内部应用(在这种情况下,将由OGNL代替Thymeleaf标准表达式引擎执行):
在这里插入图片描述
请注意,其中一些运算符存在文本别名:div(/),mod(%)。

5.布尔运算
  • 二元运算符:and,or
  • 布尔否定(一元运算符): !,not
6.比较和平等
  • 比较:>,<,>=,<=(gt,lt,ge,le)
  • 等号运算符:==,!=(eq,ne)

在表达式中的值可以与进行比较>,<,>=和<=符号,以及==和!=运营商可以被用来检查是否相等(或缺乏)。请注意,XML规定,不得在属性值中使用<和>符号,因此应将其替换为<and >。
在这里插入图片描述
一个更简单的替代方法可能是使用以下某些运算符存在的文本别名:gt(>),lt(<),ge(>=),le(<=),not(!)。还有eq(==),neq/ ne(!=)。

7.条件运算符
  • 如果-则: (if) ? (then)
  • 如果-则-否则: (if) ? (then) : (else)
  • 默认: (value) ?: (defaultvalue)
1.条件表达式

条件表达式旨在仅根据两个条件的求值结果来求值(它本身就是另一个表达式)。

让我们来看一个例子片段(引入另一个属性修改器,th:class):
在这里插入图片描述
条件表达式的所有三个部分(condition,then和else)本身的表达式,这意味着它们可以是变量(${…},*{…}),消息(#{…}),网址(@{…})或文字(’…’)。

也可以使用括号嵌套条件表达式:
在这里插入图片描述
其他表达式也可以省略,在这种情况下,如果条件为false,则返回null值:
在这里插入图片描述

2.默认表达式(Elvis运算符)

一个默认的表情是一种特殊的条件值的没有那么一部分。它等效于某些语言(如Groovy)中出现的Elvis运算符,可让您指定两个表达式:如果第一个表达式的计算结果不为null,则使用第一个表达式;如果第二个表达式使用,则使用第二个表达式。

让我们在用户个人资料页面中看到它的实际效果:
在这里插入图片描述
如您所见,运算符为?:,并且仅当求*{age}值结果为null时,才在此处使用它来指定名称的默认值(在这种情况下为文字值)。因此,这等效于:
在这里插入图片描述
与条件值一样,它们可以在括号之间包含嵌套表达式:
在这里插入图片描述

8.特殊令牌
  • 无操作: _

No-Operation令牌由下划线符号(_)表示。

该标记背后的想法是指定表达式的期望结果什么也不做,即完全像可处理属性(例如th:text)根本不存在一样。

除其他可能性外,这还使开发人员可以将原型文本用作默认值。例如,代替:
在这里插入图片描述
…我们可以直接将“未经用户身份验证”用作原型文本,从设计的角度来看,这使得代码既简洁又通用:
在这里插入图片描述

3.表达式演示

success.html

<body>
    <!--th:text 会转译 -->
    <div th:text="${hello}"></div>
    <!--th:utext 不会转译 -->
    <div th:utext="${hello}"></div><hr>

    <!--th:each每一次遍历都会生成这个标签:也就是3个h1-->
    <h1 th:text="${user}" th:each="user:${users}"></h1><hr>
    <h1>
        <!--表达式内联写法,具体操作可以参照Thymeleaf文档。-->
        <span th:each="user:${users}">[[${user}]]</span>
    </h1>
</body>

HelloController

@Controller
public class HelloController {
    @RequestMapping("/hello")
    public String sayHello(Map<String,Object> map){
        //默认会存到request域中
        map.put("hello","<h1>你好<h1>");
        map.put("users", Arrays.asList("zhangsan","lisi","wangwu"));
        return "success";
    }
}

测试结果
在这里插入图片描述

4.SpringMVC自动配置

springMVC自动配置文档:https://www.springcloud.cc/spring-boot.html#boot-features-spring-mvc-auto-configuration

1.Spring MVC auto-configuration

Spring Boot为Spring MVC提供了自动配置,适用于大多数应用程序。
自动配置在Spring的默认值之上添加了以下功能:

  • 包含ContentNegotiatingViewResolverBeanNameViewResolver beans。
    自动配置了ViewResolver(视图解析器:根据方法的返回值得到视图对象(View),视图对象决定如何渲染(决定转发和重定向))
    ContentNegotiatingViewResolver:组合所有的视图解析器的。
    如何定制:我们可以自己给容器中添加一个视图解析器,自动的将其组合进来。
@SpringBootApplication
public class SpringbootThymeleafApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringbootThymeleafApplication.class, args);
    }
	//自定义视图解析器
    @Bean
    public MyResolveView myResolveView(){
        return new MyResolveView();
    }
    private static class MyResolveView implements ViewResolver {
        @Override
        public View resolveViewName(String viewName, Locale locale) throws Exception {
            return null;
        }
    }
}

在这里插入图片描述

  • 支持提供静态资源,包括对WebJars的支持( 本文档稍后介绍))。
  • 自动注册ConverterGenericConverterFormatter beans。
    Converter:转换器,public String hello(User user),类型转换使用Converter。
    Formatter 格式化器:2017.12.17 -> Date。
    在这里插入图片描述 自己添加的格式化器转换器,我们只需要放在容器中即可。
  • 支持HttpMessageConverters( 本文档稍后部分)。
    HttpMessageConverters:SpringMVC用来转换Http请求和响应的,User—Json。
    HttpMessageConverters:是从容器中确定(把自定义转换注册到容器到),获取所有的HttpMessageConverter,自己给容器中添加HttpMessageConverters,只需要将自己的组件注册容器中 (使用@Bean或者@Component)
    在这里插入图片描述
  • 自动注册MessageCodesResolver( 本文档后面部分)。
    定义错误代码生成规则。
  • 静态index.html支持。
  • 自定义Favicon支持(本文档稍后介绍)。(图标)
  • 自动使用ConfigurableWebBindingInitializer bean(本文 后面会介绍)。
    我们可以配置一个ConfigurableWebBindingInitializer来替换默认的(添加到容器)
    在这里插入图片描述在这里插入图片描述
1.我有疑问就是有的时候在配置类中注册,需要实现一个接口,有的则不需要 why?

在这里插入图片描述

2.扩展SpringMVC

配置拦截器:

<mvc:view‐controller path="/hello" view‐name="success"/> 
<mvc:interceptors> 
	<mvc:interceptor> 
		<mvc:mapping path="/hello"/> 
		<bean></bean> 
	</mvc:interceptor> 
</mvc:interceptors>

编写一个配置类(@Configuration),是WebMvcConfigurerAdapter类型,不能标注@EnableWebMvc。
既保留了所有的自动配置,也能用我们扩展的配置。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

1.扩展SpringMVC扩展配原理
  • 1、WebMvcAutoConfiguration是SpringMVC的自动配置类
  • 2、在做其他自动配置时会导入,@Import(EnableWebMvcConfiguration.class)
    在这里插入图片描述
  • 3、容器中所有的WebMvcConfigurer都会一起起作用。
  • 4、我们的配置类也会被调用。
    效果:SpringMVC的自动配置和我们的扩展配置都会起作用。

3.全面接管SpringMVC

SpringBoot对SpringMVC的自动配置不需要了,所有都是我们自己配置,所有的SpringMVC的自动配置就失效了。
我们需要在配置类中添加@EnableWebMvc即可。

//使用WebMvcConfigurerAdapter可以来扩展SpringMVC的功能 @EnableWebMvc
@EnableWebMvc
@Configuration
public class MyMvcConfig extends WebMvcConfigurerAdapter {
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        //super.addViewControllers(registry);
        //浏览器发送 /haha 请求来到 success
        registry.addViewController("/haha").setViewName("success");
    }
}
1.全面接管SpringMVC原理

为什么@EnableWebMvc自动配置就失效了?

  • 1、@EnableWebMvc的核心
    在这里插入图片描述
    2、
    在这里插入图片描述
    3、
    在这里插入图片描述4、@EnableWebMvc 将 WebMvcConfigurationSupport 组件导入进来
    5、导入的 WebMvcConfigurationSupport 只是 SpringMVC 最基本的功能

4.如何修改SpringBoot的默认配置

模式:
1、SpringBoot在自动配置组件的时候,先看容器中有没有用户自己配置的(@Bean、@Component)如果有就用用户配置的,如果没有,才自动配置。如果有些组件可以有多个(ViewResolver)将用户配置的和默认配置的组合起来。
2、在SpringBoot中会有非常多的 xxxConfigurer 帮助我们进行扩展配置(重点、重点、重点)
在这里插入图片描述扩展配置三种方式:只要是 WebMvcConfigurer 类型即可

// 使用WebMvcConfigurerAdapter可以来扩展SpringMVC的功能
// @EnableWebMvc 不要接管SpringMVC
@Configuration
public class MyMvcConfig extends WebMvcConfigurerAdapter {

    //扩展配置第一种方式
    //添加视图控制配置
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        //super.addViewControllers(registry);
        //浏览器发送 /haha 请求来到 success
        //如果是直接到页面就可以使用这种方,这样就不用因为跳转到一个页面而去下一个空方法。
        registry.addViewController("/haha").setViewName("success");
    }

    //扩展配置第二种方式
    //所有的 WebMvcConfigurer类型 组件都会一起起作用
    @Bean//将组件注册到容器
    public WebMvcConfigurerAdapter myWebMvcConfigurerAdapter(){
        return new WebMvcConfigurerAdapter() {
            @Override
            public void addViewControllers(ViewControllerRegistry registry) {
                //super.addViewControllers(registry);
                registry.addViewController("/").setViewName("login");
            }
        };
    }

    //扩展配置第三种方式
    //所有的 WebMvcConfigurer类型 组件都会一起起作用
    @Bean//将组件注册到容器
    public WebMvcConfigurer myWebMvcConfigurer(){
        return new WebMvcConfigurer(){
            @Override
            public void addViewControllers(ViewControllerRegistry registry) {
                registry.addViewController("/index.html").setViewName("login");
            }
        };
    }
}

测试结果
在这里插入图片描述3、在SpringBoot中会有很多的 xxxCustomizer 帮助我们进行定制配置

5.RestfulCRUD

1.默认访问首页

在这里插入图片描述thymeleaf映射规则(应该是 ViewResolver 映射规则)

//方式1
@Controller
public class HelloController {
    @RequestMapping({"/","/index.html"})
    public String index(){
        return "index";
    }
}

//方式2(推荐)因为没有任何数据
// 使用WebMvcConfigurerAdapter可以来扩展SpringMVC的功能
// @EnableWebMvc 不要接管SpringMVC
@Configuration
public class MyMvcConfig extends WebMvcConfigurerAdapter {
    //扩展配置第一种方式
    //添加视图控制配置
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        //super.addViewControllers(registry);
        //浏览器发送 /haha 请求来到 success
        //如果是直接到页面就可以使用这种方,这样就不用因为跳转到一个页面而去下一个空方法。
        registry.addViewController("/").setViewName("index");
        registry.addViewController("/index.html").setViewName("index");
    }
}

在这里插入图片描述

2.国际化

步骤:
1、编写国际化配置文件。
2、使用 ResourceBundleMessageSource 管理国际化资源文件 。
3、在页面使用 fmt:message 取出国际化内容。

1、编写国际化配置文件,抽取页面需要显示的国际化消息
在这里插入图片描述
在这里插入图片描述2、SpringBoot自动配置好了管理国际化资源文件的组件

@Configuration
@ConditionalOnMissingBean(value = MessageSource.class, search = SearchStrategy.CURRENT)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Conditional(ResourceBundleCondition.class)
@EnableConfigurationProperties
public class MessageSourceAutoConfiguration {

	@Bean
	@ConfigurationProperties(prefix = "spring.messages")
	public MessageSourceProperties messageSourceProperties() {
		return new MessageSourceProperties();
		/**
		 * 基名的逗号分隔列表(本质上是完全合格的类路径位置),每个都遵循ResourceBundle约定,
		 * 并轻松支持基于斜杠的位置,如果它不包含包限定符(例如“ org.mypackage”),
		 * 它将从类路径根目录中解析。
		 */
		// MessageSourceProperties 中的 messages 属性
		// 默认的国际化资源文件的基础名,放在类路径下(messages.properties)就能自动识别,上面有介绍。
		private String basename = "messages";
	}
	
	@Bean
	public MessageSource messageSource() {
		MessageSourceProperties properties = messageSourceProperties();
		ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
		if (StringUtils.hasText(properties.getBasename())) {
			//设置国际化资源文件的基础名(去掉语言国家代码的)-> login
			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;
	}
}

3、去页面获取国际化的值

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<html lang="en">
	<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" th:placeholder="#{login.username}" required="" autofocus="">
			<label class="sr-only" th:text="#{login.password}">Password</label>
			<input type="password" class="form-control" th:placeholder="#{login.password}" required="">
			<div class="checkbox mb-3">
				<label>
					<input type="checkbox" value="remember-me" > [[#{login.rememberme}]]
				</label>
			</div>
			<button class="btn btn-lg btn-primary btn-block" type="submit" th:text="#{login.signin}">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>

效果:根据浏览器语言设置的信息切换了国际化。
在这里插入图片描述
原理: 国际化Locale(区域信息对象),LocaleResolver(获取区域信息对象)。
springboot有默认的国际化配置,会根据客户端请求头中包含的信息,如果是中文就显示中文,如果是英文就是现实英文。
在这里插入图片描述

//默认的就是根据请求头带来的区域信息获取Locale进行国际化
@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;
}

4、点击链接切换国际化

<a class="btn btn-sm" th:href="@{/index.html(l=zh_CN)}">中文</a>
<a class="btn btn-sm" th:href="@{/index.html(l=en_US)}">English</a>
/** 
 * 可以在连接上携带区域信息 
 */
public class MyLocaleResolver implements LocaleResolver {
    @Override
    public Locale resolveLocale(HttpServletRequest request) {
        String l = request.getParameter("l");
        Locale locale = Locale.getDefault();
        if (!StringUtils.isEmpty(l)) {
            String[] ls = l.split("_");
            locale = new Locale(ls[0], ls[1]);
        }
        return locale;
    }

    @Override
    public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {

    }
}

// 使用WebMvcConfigurerAdapter可以来扩展SpringMVC的功能
// @EnableWebMvc 不要接管SpringMVC
@Configuration
public class MyMvcConfig extends WebMvcConfigurerAdapter {

    //扩展配置第一种方式
    //添加视图控制配置
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        //super.addViewControllers(registry);
        //浏览器发送 /haha 请求来到 success
        //如果是直接到页面就可以使用这种方,这样就不用因为跳转到一个页面而去下一个空方法。
        registry.addViewController("/").setViewName("login");
        registry.addViewController("/index.html").setViewName("login");
    }
	
	//将MyLocaleResolver注册到容器
    @Bean
    public MyLocaleResolver localeResolver(){
        return new  MyLocaleResolver();
    }
}

3.登录

开发期间模板引擎页面修改以后,要实时生效。
禁用模板引擎的缓存,页面修改完成以后ctrl+f9,重新编译。

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

登陆错误消息的显示

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

登录功能

@Controller
public class LoginController {

    @RequestMapping(value = "/user/login", method = RequestMethod.POST)
    public String login(@RequestParam("username") String username,
                        @RequestParam("password") String password,
                        HttpServletRequest request,
                        Map<String, String> map) {
        if (!StringUtils.isEmpty(username) && "123456".equals(password)) {
            //登录成功
            User user = new User();
            user.setUsername(username);
            user.setPassword(password);
            request.getSession().setAttribute("user", user);
            //这里有一个问题就是,为什么不能重定向到dashboard。要重定向到main.html
            //因为springboot有默认的映射规则,从定向后会从默认文件夹下找dashboard.html,
            //根本找不到,要想找到/templates/dashboard.html要定义 ViewResolver 的映射规则。
            return "redirect:/main.html";
            //return "dashboard";
        } else {
            map.put("msg", "用户名或密码错误!");
            return "login";
        }
    }
}

4.拦截器进行登陆检查

拦截器

/**
 * 登录检查
 */
public class LoginHandlerInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        User user = (User) request.getSession().getAttribute("user");
        if (user == null) {
            //未登录,返回登录页面
            request.setAttribute("msg", "没有权限请先登录");
            //转发到登录页面
            request.getRequestDispatcher("/index.html").forward(request, response);
            return false;
        } else {
            //放行
            return true;
        }
    }
}

注册拦截器

1.注册拦截器疑问为什么注册拦截器是添加的方式而不是@Bean方式

在这里插入图片描述

// 使用WebMvcConfigurerAdapter可以来扩展SpringMVC的功能
// @EnableWebMvc 不要接管SpringMVC
@Configuration
public class MyMvcConfig extends WebMvcConfigurerAdapter {

    //扩展配置第一种方式
    //添加视图控制配置
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        //super.addViewControllers(registry);
        //浏览器发送 /haha 请求来到 success
        //如果是直接到页面就可以使用这种方,这样就不用因为跳转到一个页面而去下一个空方法。
        registry.addViewController("/").setViewName("login");
        registry.addViewController("/index.html").setViewName("login");
        registry.addViewController("/main.html").setViewName("dashboard");
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //super.addInterceptors(registry);
        //静态资源, *.css , *.js。 
        //SpringBoot已经做好了静态资源映射(我使用的springboot2.0.1,静态资源也会被拦截掉,所以静态资源还是要放行的)
        registry.addInterceptor(new LoginHandlerInterceptor())
                .addPathPatterns("/**")//拦截任意目录下文件夹下的文件
                .excludePathPatterns("/", "/index.html", "/user/login", "/asserts/**");//放行
    }
}

5.CRUD-员工列表

实验要求:
RestfulCRUD:CRUD满足Rest风格
URI: /资源名称/资源标识 HTTP请求方式区分对资源CRUD操作

查询普通CRUD(uri来区分操作)RestfulCRUD
查询getEmpemp—GET
添加addEmp?xxxemp—POST
修改updateEmp?id=xxx&xxx=xxemp/{id}—PUT
删除deleteEmp?id=1emp/{id}—DELETE

实验的请求架构

实验功能请求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> 

2、引入公共片段 
<div th:insert="~{footer :: copy}"></div> 
~{templatename::selector}:模板名::选择器(模版名就是页面名称,这个公共片段在哪个模板页面,会经过thymeleaf解析去掉前后缀。)
~{templatename::fragmentname}:模板名::片段名 

3、默认效果:insert的公共片段在div标签中 
如果使用th:insert等属性进行引入,可以不用写~{}。
行内写法可以加上:[[~{}]];[(~{})]

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

因此,HTML片段如下所示:
在这里插入图片描述

代码中包含了3次,如下所示:
在这里插入图片描述结果:
在这里插入图片描述
可参数化的片段签名
在这里插入图片描述

1.抽取CRUD-员工列表公共页面示例

抽取topbar和sidebar
在这里插入图片描述在list.html页面引入topbar和sidebar
在这里插入图片描述

2.侧边栏选中高亮显示

在这里插入图片描述

6.CRUD-员工添加和修改合一

修改添加二合一表单

<!DOCTYPE html>
<!-- saved from url=(0052)http://getbootstrap.com/docs/4.0/examples/dashboard/ -->
<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>Dashboard Template for Bootstrap</title>
    <!-- Bootstrap core CSS -->
    <link href="asserts/css/bootstrap.min.css" th:href="@{/webjars/bootstrap/4.0.0/css/bootstrap.css}" rel="stylesheet">

    <!-- Custom styles for this template -->
    <link href="asserts/css/dashboard.css" th:href="@{/asserts/css/dashboard.css}" rel="stylesheet">
    <style type="text/css">
        /* Chart.js */

        @-webkit-keyframes chartjs-render-animation {
            from {
                opacity: 0.99
            }
            to {
                opacity: 1
            }
        }

        @keyframes chartjs-render-animation {
            from {
                opacity: 0.99
            }
            to {
                opacity: 1
            }
        }

        .chartjs-render-monitor {
            -webkit-animation: chartjs-render-animation 0.001s;
            animation: chartjs-render-animation 0.001s;
        }
    </style>
</head>

<body>
<!--引入抽取的topbar-->
<!--模板名:会使用thymeleaf的前后缀配置规则进行解析-->
<div th:replace="commons/bar::topbar"></div>

<div class="container-fluid">
    <div class="row">
        <!--引入侧边栏-->
        <div th:replace="commons/bar::#sidebar(activeUri='emps')"></div>
        <main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">

            <!--需要区分是员工修改还是添加。-->
            <form th:action="@{/emp}" method="post">
                <!--发送put请求修改员工数据-->
                <!--
                    1、SpringMVC中配置HiddenHttpMethodFilter;(SpringBoot自动配置好的)
                    2、页面创建一个post表单(因为是根据Rest风格开发)
                    3、创建一个input项,name="_method",值就是我们指定的请求方式。
                -->
                <input type="hidden" name="_mehotd" value="put" th:if="${emp!=null}">
                <input type="hidden" th:value="${emp.id}" th:if="${emp!=null}">

                <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="text" class="form-control" placeholder="zhangsan@qq.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:each="dep:${deps}" th:value="${dep.id}" th:selected="${emp!=null} ? ${emp.department.id==dep.id}">[[${dep.departmentName}]]</option>
                    </select>
                </div>
                <div class="form-group">
                    <label>Birth</label>
                    <input name="birth" type="text" class="form-control" placeholder="2020-01-01" th:value="${emp!=null} ? ${#dates.format(emp.birth,'yyyy-MM-dd HH:mm')}">
                </div>
                <button type="submit" class="btn btn-primary" th:text="${emp!=null ? '修改':'添加'}">添加</button>
            </form>
        </main>
    </div>
</div>

<!-- Bootstrap core JavaScript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
<script type="text/javascript" src="asserts/js/jquery-3.2.1.slim.min.js"
        th:src="@{/webjars/jquery/3.3.1/jquery.js}"></script>
<script type="text/javascript" src="asserts/js/popper.min.js"
        th:src="@{/webjars/popper.js/1.11.1/dist/popper.js}"></script>
<script type="text/javascript" src="asserts/js/bootstrap.min.js"
        th:src="@{/webjars/bootstrap/4.0.0/js/bootstrap.js}"></script>

<!-- Icons -->
<script type="text/javascript" src="asserts/js/feather.min.js" th:src="@{/asserts/js/feather.min.js}"></script>
<script>
    feather.replace()
</script>
</body>
</html>
@Controller
public class EmployeeController {

    @Autowired
    EmployeeDao employeeDao;

    @Autowired
    DepartmentDao departmentDao;

    /**
     *  查询所有员工返回列表页面
     * @param model
     * @return
     */
    @GetMapping("/emps")
    public String emps(Model model) {
        Collection<Employee> emps = employeeDao.getAll();
        //放在请求域中
        model.addAttribute("emps", emps);
        // thymeleaf默认就会拼串
        // classpath:/templates/xxxx.html
        return "emp/list";
    }

    /**
     * 去添加员工页面
     * @param model
     * @return
     */
    @GetMapping("/emp")
    public String toAddPage(Model model) {
        //来到添加页面,查出所有的部门,在页面显示
        Collection<Department> deps = departmentDao.getDepartments();
        model.addAttribute("deps", deps);
        return "emp/add";
    }

    /**
     *
     * 员工添加
     * SpringMVC自动将请求参数和入参对象的属性进行一一绑定,
     * 要求请求参数的名字和javaBean入参的对象里面的属性名是一样的.
     * @param employee
     * @return
     */
    @PostMapping("/emp")
    public String addEmp(Employee employee) {
        //来到员工列表页面
        //保存员工
        // redirect: 表示重定向到一个地址  /代表当前项目路径
        // forward: 表示转发到一个地址
        employeeDao.save(employee);
        return "redirect:/emps";
    }

    /**
     *  来到修改页面,查出当前员工,在页面回显
     * @return
     */
    @GetMapping("/emp/{id}")
    public String editEmp(@PathVariable("id") Integer id,Model model) {
        Employee emp = employeeDao.get(id);
        model.addAttribute("emp",emp);

        //页面要显示所有的部门列表
        Collection<Department> deps = departmentDao.getDepartments();
        model.addAttribute("deps",deps);
        //回到修改页面(add是一个修改添加二合一的页面);
        return "emp/add";
    }

    /**
     * 员工修改,需要提交员工id。
     * @param employee
     * @return
     */
    @PutMapping("/emp")
    public String updateEmp(Employee employee){
        employeeDao.save(employee);
        return "redirect:/emps";
    }
}

页面二合一遇到的问题:
怎么判断是添加还是修改?
在这里插入图片描述
日期格式问题?
提交的数据格式不对,生日期, 2017-12-12、2017/12/12、2017.12.12。
日期的格式化,SpringMVC将页面提交的值需要转换为指定的类型,2017-12-1 ->Date 类型转换,格式化。
默认日期是按照 / 的方式。(可以修改默认格式)
在这里插入图片描述
因为使用的是Rest风格开发,添加是post提交方式,修改是put提交方式,但是表单只提供2中提交方式get和post,那put提交方式怎么解决呢?
在这里插入图片描述
添加和修改页面合一开发流程
先把添加页面功能开发好,在把修改页面功能开发好后,这时候点击添加页面可能会出现问题,这样只要把添加页面调整号就完成了。

7.CRUD-员工删除

在这里插入图片描述在这里插入图片描述
list.html

<!DOCTYPE html>
<!-- saved from url=(0052)http://getbootstrap.com/docs/4.0/examples/dashboard/ -->
<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>Dashboard Template for Bootstrap</title>
    <!-- Bootstrap core CSS -->
    <link href="asserts/css/bootstrap.min.css" th:href="@{/asserts/css/bootstrap.min.css}" rel="stylesheet">

    <!-- Custom styles for this template -->
    <link href="asserts/css/dashboard.css" th:href="@{/asserts/css/dashboard.css}" rel="stylesheet">
    <style type="text/css">
        /* Chart.js */

        @-webkit-keyframes chartjs-render-animation {
            from {
                opacity: 0.99
            }
            to {
                opacity: 1
            }
        }

        @keyframes chartjs-render-animation {
            from {
                opacity: 0.99
            }
            to {
                opacity: 1
            }
        }

        .chartjs-render-monitor {
            -webkit-animation: chartjs-render-animation 0.001s;
            animation: chartjs-render-animation 0.001s;
        }
    </style>
</head>

<body>
<!--引入topbar,不应该包含div所以不能使用th:insert-->
<!--<div th:insert="~{commons/bar :: topbar}"></div>-->
<div th:replace="~{commons/bar :: topbar}"></div>
<div class="container-fluid">
    <div class="row">
        <!--引入sidebar-->
        <div th:replace="~{commons/bar :: #sidebar(activeUri='emps')}"></div>
        <main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
            <h1>
                <a class="btn btn-sm btn-success" th:href="@{/emp}">添加</a>
            </h1>
            <div class="table-responsive">
                <table class="table table-striped table-sm">
                    <thead>
                    <tr>
                        <th>ID</th>
                        <th>lastName</th>
                        <th>email</th>
                        <th>gender</th>
                        <th>department</th>
                        <th>birth</th>
                        <th>操作</th>
                    </tr>
                    </thead>
                    <tbody>
                    <tr th:each="emp : ${emps}">
                        <td th:text="${emp.id}"></td>
                        <td th:text="${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 type="submit" th:attr="delUri=@{/emp/}+${emp.id}" class="btn btn-sm btn-danger deleteBtn">删除</button>
                        </td>
                    </tr>
                    <!--提取出来删除表单-->
                    <form id="deleteForm" method="post">
                        <input type="hidden" name="_method" value="delete">
                    </form>
                    </tbody>
                </table>
            </div>
        </main>
    </div>
</div>

<!-- Bootstrap core JavaScript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
<script type="text/javascript" th:src="@{/asserts/js/jquery-3.2.1.slim.min.js}"
        src="asserts/js/jquery-3.2.1.slim.min.js"></script>
<script type="text/javascript" th:src="@{/asserts/js/popper.min.js}" src="asserts/js/popper.min.js"></script>
<script type="text/javascript" th:src="@{/asserts/js/bootstrap.min.js}" src="asserts/js/bootstrap.min.js"></script>

<!-- Icons -->
<script type="text/javascript" th:src="@{/asserts/js/feather.min.js}" src="asserts/js/feather.min.js"></script>
<script>
    feather.replace()
</script>
<script>
    $(".deleteBtn").click(function () {
        var delUri = $(this).attr("delUri");
        // 删除当前员工的
        $("#deleteForm").attr("action", delUri).submit();
    })
</script>

<!-- Graphs -->
<script type="text/javascript" th:src="@{/asserts/js/Chart.min.js}" src="asserts/js/Chart.min.js"></script>
<script>
    var ctx = document.getElementById("myChart");
    var myChart = new Chart(ctx, {
        type: 'line',
        data: {
            labels: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
            datasets: [{
                data: [15339, 21345, 18483, 24003, 23489, 24092, 12034],
                lineTension: 0,
                backgroundColor: 'transparent',
                borderColor: '#007bff',
                borderWidth: 4,
                pointBackgroundColor: '#007bff'
            }]
        },
        options: {
            scales: {
                yAxes: [{
                    ticks: {
                        beginAtZero: false
                    }
                }]
            },
            legend: {
                display: false,
            }
        }
    });
</script>
</body>
</html>

EmployeeController

@Controller
public class EmployeeController {

    @Autowired
    EmployeeDao employeeDao;

    @Autowired
    DepartmentDao departmentDao;
    /**
     * 删除员工
     * @param id
     * @return
     */
    @DeleteMapping("/emp/{id}")
    public String deleteEmp(@PathVariable("id") Integer id){
        employeeDao.delete(id);
        return "redirect:/emps";
    }
}

6.错误处理机制

1.SpringBoot默认的错误处理机制

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

在这里插入图片描述
优先响应:text/html
在这里插入图片描述

2.如果是其他客户端默认响应一个json数据

在这里插入图片描述
在这里插入图片描述

3.springboot错误处理机制原理

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

1.ErrorPageCustomizer:错误页面定制器
/**
 * 错误控制器的路径。
 */
@Value("${error.path:/error}")
private String path = "/error";
2.BasicErrorController:基本错误控制器
@Bean
@ConditionalOnMissingBean(value = ErrorController.class, search = SearchStrategy.CURRENT)
public BasicErrorController basicErrorController(ErrorAttributes errorAttributes) {
	return new BasicErrorController(errorAttributes, this.serverProperties.getError(),
			this.errorViewResolvers);
}

@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class BasicErrorController extends AbstractErrorController {
	@RequestMapping(produces = "text/html")//产生html类型的数,浏览器发送的请求来到这个方法处理。
	public ModelAndView errorHtml(HttpServletRequest request,HttpServletResponse response) {
		HttpStatus status = getStatus(request);
		Map<String, Object> model = Collections.unmodifiableMap(getErrorAttributes(
				request, isIncludeStackTrace(request, MediaType.TEXT_HTML)));
		response.setStatus(status.value());
		//去哪个页面作为错误页面,包含页面地址和页面内容。
		ModelAndView modelAndView = resolveErrorView(request, response, status, model);
		return (modelAndView == null ? new ModelAndView("error", model) : modelAndView);
	}
	
	@RequestMapping //产生json数据,其他客户端来到这个方法处理
	@ResponseBody 
	public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
		Map<String, Object> body = getErrorAttributes(request,
				isIncludeStackTrace(request, MediaType.ALL));
		HttpStatus status = getStatus(request);
		return new ResponseEntity<>(body, status);
	}
}	
3.DefaultErrorViewResolver:默认错误视图解析器
public class DefaultErrorViewResolver implements ErrorViewResolver, Ordered {
	@Override
	public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status,
			Map<String, Object> model) {
		// 得到ModelAndView	
		ModelAndView modelAndView = resolve(String.valueOf(status), 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) {
		//默认SpringBoot可以去找到一个页面 error/404.html
		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);
	}
}
4.DefaultErrorAttributes:默认错误属性
@Order(Ordered.HIGHEST_PRECEDENCE)
public class DefaultErrorAttributes implements ErrorAttributes, HandlerExceptionResolver, Ordered {
	//帮我们在页面共享信息
	@Override
	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;
	}
}
5.错误处理机制步骤

一但系统出现 4xx(客户端错误) 或者 5xx(服务器错误) 之类的错误, ErrorPageCustomizer(错误页面定制器) 就会生效定制错误的响应规则,就会来到 /error 请求,/error请求就会被 BasicErrorController(基本错误控制器) 处理。

响应页面,去哪个页面是由 DefaultErrorViewResolver(默认错误视图解析器) 解析得到的。

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

帮我们在页面共享信息是由 DefaultErrorAttributes(默认错误属性) 得到的。

@Order(Ordered.HIGHEST_PRECEDENCE)
public class DefaultErrorAttributes implements ErrorAttributes, HandlerExceptionResolver, Ordered {
	//帮我们在页面共享信息
	@Override
	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;
	}
}

2.如果定制错误响应

1.如何定制错误的页面
1.有模版引擎的情况下

有模板引擎(我是用的是Thymeleaf)的情况下,error/状态码。【将错误页面命名为 错误状态码.html 放在模板引擎文件夹(templates)里面的 error文件夹下】,发生此状态码的错误就会来到 对应的页面。

当然可以使用 4xx5xx 作为错误页面的文件名来匹配这种类型的所有错误,精确优先(优先寻找精确的 状态码.html)。
页面能获取的信息:

	timestamp:时间戳 
	status:状态码 
	error:错误提示 
	exception:异常对象 
	message:异常消息 
	errors:JSR303数据校验的错误都在这里
2.没有模版引擎的情况下

没有模板引擎(模板引擎找不到这个错误页面),就在springboot默映射规则的静态资源文件夹下找错误页面。

3.以上都没有错误页面

以上都没有错误页面,就是默认来到SpringBoot默认的错误提示页面。

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

自定义异常

public class UserNotExistException extends RuntimeException {
    public UserNotExistException(String msg){
        super(msg);
    }
}

LoginController 触发自定义异常

@Controller
public class LoginController {
    @RequestMapping("/hello")
    @ResponseBody
    public String hello(@RequestParam("user") String user){
        if("aaa".equals(user)){
           throw new UserNotExistException("用户名不存在!");
        }
        return "hello wolrd";
    }
}   

自定义异常处理器
但是没有自适应效果(根据客户端的不同响应也不同)

@ControllerAdvice //要想成为异常处理器,要加上该注解。
public class MyExceptionHandler{
    // 使用 @ExceptionHandler 注解捕获到 UserNotExistException ,
    // 将添加的数据以字符串形式返回。
    @ExceptionHandler(UserNotExistException.class)
    @ResponseBody
    public Map<String,Object> exceptionHandler(Exception e){
        Map<String,Object> map = new HashMap<>();
        map.put("code", "user.notexist");
        map.put("msg",e.getMessage());
        return map;
    }
}

测试结果
在这里插入图片描述

2.转发到/error进行自适应响应效果处理

为什么转发到 /error 就可以自适应呢? 是因为使用 BasicErrorController 处理 /error 请求.这个具体可以参照springboot错误处理机制原理。
在这里插入图片描述
转发到/error进行自适应响应效果处理

@ControllerAdvice //要想成为异常处理器,要加上该注解。
public class MyExceptionHandler{

    @ExceptionHandler(UserNotExistException.class)
    public String exceptionHandler(Exception e){
        Map<String,Object> map = new HashMap<>();
        map.put("code", "user.notexist");
        map.put("msg",e.getMessage());
        //转发到/error
        return "forward:/error";
    }
}

在这里插入图片描述
转发到/error进行自适应响应效果处理,设置响应状态码。(但是不能携带自己的数据)
如何设置设置状态码原理
在这里插入图片描述

@ControllerAdvice //要想成为异常处理器,要加上该注解。
public class MyExceptionHandler {

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

        //传入我们自己的错误状态码 4xx 5xx,否则就不会进入定制错误页面的解析流程
        /***
         * Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code");
         */
        request.setAttribute("javax.servlet.error.status_code",500 );
        Map<String, Object> map = new HashMap<>();
        map.put("code", "user.notexist");
        map.put("msg", e.getMessage());
        //转发到/error
        return "forward:/error";
    }
}

新建一个获取500.html页面

 <main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
        <h1>status:[[${status}]]</h1>
        <h2>timestamp:[[${timestamp}]]</h2>
        <h2>exception:[[${exception}]]</h2>
        <h2>message:[[${message}]]</h2>
      <!--  <h2>ext:[[${ext.code}]]</h2>
        <h2>ext:[[${ext.message}]]</h2>-->
</main>

测试结果
在这里插入图片描述

3.将我们的定制数据携带出去

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

定制数据携带出去原理:
在这里插入图片描述
定制数据携带方法

  • 完全来编写一个ErrorController的实现类【或者是编写AbstractErrorController的子类】,放在容器中。(太麻烦)
  • 页面上能用的数据,或者是json返回能用的数据都是通过 errorAttributes.getErrorAttributes 得到,容器中DefaultErrorAttributes.getErrorAttributes(),默认进行数据处理的。

自定义ErrorAttributes

@Component
public class MyErrorAttributes extends DefaultErrorAttributes {
    @Override
    public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
        Map<String, Object> map = super.getErrorAttributes(webRequest, includeStackTrace);
        //携带自定义数据
        map.put("compay", "ginger");
        return map;
    }
}

测试结果
在这里插入图片描述

如何将异常处理器中的定义信息也携带出去呢?
自定义的异常处理器

@ControllerAdvice //要想成为异常处理器,要加上该注解。
public class MyExceptionHandler {

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

        //传入我们自己的错误状态码 4xx 5xx,否则就不会进入定制错误页面的解析流程
        /***
         * Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code");
         */
        request.setAttribute("javax.servlet.error.status_code",500 );
        Map<String, Object> map = new HashMap<>();
        map.put("code", "user.notexist");
        map.put("msg", e.getMessage());

        //将自定义异常处理器中的数据存到,那为什么后面会获取到数据呢?因为是转发到 /error ,就是由	
        //BasicErrorController来处理请求。 	     
        request.setAttribute("zidingyiMap",map );
        //转发到/error
        return "forward:/error";
    }
}

MyErrorAttributes 将异常处理器也中的数据也携带出去

@Component
public class MyErrorAttributes extends DefaultErrorAttributes {
    @Override
    public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
        Map<String, Object> map = super.getErrorAttributes(webRequest, includeStackTrace);
        map.put("compay", "ginger");
        //从request域中获取,map值。
        Map<String,Object> zidingyiMap = ( Map<String,Object>) webRequest.getAttribute("zidingyiMap", 0);
        map.put("zidingyiMap", zidingyiMap);
        return map;
    }
}

测试结果
在这里插入图片描述

7.配置嵌入式Servlet容器

SpringBoot默认使用Tomcat作为嵌入式的Servlet容器。
在这里插入图片描述

1.如何定制和修改Servlet容器的相关配置

1、修改和server有关的配置(ServerProperties【也是ConfigurableServletWebServerFactory】)

server.port=8081 
server.context‐path=/crud 
server.tomcat.uri‐encoding=UTF‐8 

//通用的Servlet容器设置 
server.xxx 
//Tomcat的设置 
server.tomcat.xxx

2、编写一个ConfigurableServletWebServerFactory:嵌入式的Servlet容器的定制器,来修改Servlet容器的 配置。

@Component//一定要将这个定制器加入到容器中
public class MyWebServerFactoryCustomizer implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> {
	
	//定制嵌入式的Servlet容器相关的规则
    @Override 
    public void customize(ConfigurableServletWebServerFactory factory) {
        factory.setPort(8889);
    }
}

测试结果
在这里插入图片描述

2.注册Servlet三大组件【Servlet、Filter、Listener】

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

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

//@WebServlet("/myServlet")
public class MyServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("MyServlet....");
        response.getWriter().write("MyServlet....");
    }
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doPost(request, response);
    }
}

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

FilterRegistrationBean

public class MyFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("MyFilter....");
        chain.doFilter(request,response );
    }
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {}
    @Override
    public void destroy() {}
}

//注册过滤器
@Bean
public FilterRegistrationBean myFilter() {
    FilterRegistrationBean frb = new FilterRegistrationBean();
    frb.setFilter(new MyFilter());
    frb.setUrlPatterns(Arrays.asList("/myServlet"));
    return frb;
}

ServletListenerRegistrationBean

public class MyListener implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        System.out.println("MyListener....");
    }
    @Override
    public void contextDestroyed(ServletContextEvent sce) {

    }
}

//注册监听器
@Bean
public ServletListenerRegistrationBean myListener() {
    ServletListenerRegistrationBean slrb = new ServletListenerRegistrationBean();
    slrb.setListener(new MyListener());
    return slrb;
}

SpringBoot自动配置SpringMVC的时候,自动的注册SpringMVC的前端控制器,DIspatcherServlet, DispatcherServletAutoConfiguration中。

@Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
@ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public ServletRegistrationBean<DispatcherServlet> dispatcherServletRegistration(
		DispatcherServlet dispatcherServlet) {
	ServletRegistrationBean<DispatcherServlet> registration = new ServletRegistrationBean<>(
			dispatcherServlet,
			//默认拦截:/ 拦截所有请求,包静态资源,但是不拦截jsp请求 /* 会拦截jsp。 
			//可以通过 server.servletPath 来修改SpringMVC前端控制器默认拦截的请求路径。
			this.serverProperties.getServlet().getServletMapping());
	registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
	registration.setLoadOnStartup(
			this.webMvcProperties.getServlet().getLoadOnStartup());
	if (this.multipartConfig != null) {
		registration.setMultipartConfig(this.multipartConfig);
	}
	return registration;
}

3.替换为其他嵌入式Servlet容器

在这里插入图片描述
默认支持: Tomcat(默认使用)

引入web模块默认就是使用嵌入式的Tomcat作为Servlet容器
<dependency> 
	<groupId>org.springframework.boot</groupId> 
	<artifactId>spring‐boot‐starter‐web</artifactId> 
</dependency>

Jetty

<!‐‐ 引入web模块 ‐‐>
<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>
<!‐‐引入其他的Servlet容器‐‐>
<dependency>
    <artifactId>spring‐boot‐starter‐jetty</artifactId>
    <groupId>org.springframework.boot</groupId>
</dependency>

Undertow

<!‐‐ 引入web模块 ‐‐>
<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>
<!‐‐引入其他的Servlet容器‐‐>
<dependency>
    <artifactId>spring‐boot‐starter‐undertow</artifactId>
    <groupId>org.springframework.boot</groupId>
</dependency>

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

1、EmbeddedWebServerFactoryCustomizerAutoConfiguration:嵌入式Web服务器出厂自定义程序自动配置。

@Configuration
@EnableConfigurationProperties(ServerProperties.class)
public class EmbeddedWebServerFactoryCustomizerAutoConfiguration {

	@ConditionalOnClass({ Tomcat.class, UpgradeProtocol.class })判断当前是否引入了Tomcat依赖
	public static class TomcatWebServerFactoryCustomizerConfiguration {

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

	}

	/**
	 * Nested configuration if Jetty is being used.
	 */
	@Configuration
	@ConditionalOnClass({ Server.class, Loader.class, WebAppContext.class })
	public static class JettyWebServerFactoryCustomizerConfiguration {

		@Bean
		public JettyWebServerFactoryCustomizer jettyWebServerFactoryCustomizer(
				Environment environment, ServerProperties serverProperties) {
			return new JettyWebServerFactoryCustomizer(environment, serverProperties);
		}

	}

	/**
	 * Nested configuration if Undertow is being used.
	 */
	@Configuration
	@ConditionalOnClass({ Undertow.class, SslClientAuthMode.class })
	public static class UndertowWebServerFactoryCustomizerConfiguration {

		@Bean
		public UndertowWebServerFactoryCustomizer undertowWebServerFactoryCustomizer(
				Environment environment, ServerProperties serverProperties) {
			return new UndertowWebServerFactoryCustomizer(environment, serverProperties);
		}

	}

}

2、以TomcatServletWebServerFactory为例

@Override
	public WebServer getWebServer(ServletContextInitializer... initializers) {
		//创建一个Tomcat
		Tomcat tomcat = new Tomcat();
	    
	    //配置Tomcat的基本环节
		File baseDir = (this.baseDirectory != null ? this.baseDirectory
				: createTempDir("tomcat"));
		tomcat.setBaseDir(baseDir.getAbsolutePath());
		Connector connector = new Connector(this.protocol);
		tomcat.getService().addConnector(connector);
		customizeConnector(connector);
		tomcat.setConnector(connector);
		tomcat.getHost().setAutoDeploy(false);
		configureEngine(tomcat.getEngine());
		for (Connector additionalConnector : this.additionalTomcatConnectors) {
			tomcat.getService().addConnector(additionalConnector);
		}
		prepareContext(tomcat.getHost(), initializers);
	    //将配置好的Tomcat传入进去,返回一个 WebServer 并且启动Tomcat服务器。
		return getTomcatWebServer(tomcat);
	}

3、对嵌入式容器的配置修改是怎么生效?

ServerProperties、ConfigurableServletWebServerFactory

ConfigurableServletWebServerFactory:定制器帮我们修改了Servlet容器的配置。

容器中导入WebServerFactoryCustomizerBeanPostProcessor

5.嵌入式Servlet容器启动原理

什么时候创建嵌入式的Servlet容器工厂,什么时候获取嵌入式的Servlet容器并启动Tomcat。

获取嵌入式的Servlet容器工厂:

  • 1、SpringBoot应用启动运行run方法。

  • 2、refreshContext(context),SpringBoot刷新IOC容器【创建IOC容器对象,并初始化容器,创建容器中的每一个组件】
    Web环境默认使用的应用程序上下文的类:AnnotationConfigServletWebServerApplicationContext、
    默认情况下用于响应式Web环境的应用程序上下文的类:AnnotationConfigReactiveWebServerApplicationContext、
    在非Web环境中默认使用的应用程序上下文的类:AnnotationConfigApplicationContext。

  • 3、refresh(context),刷新刚才创建好的ioc容器。

@Override
public void refresh() throws BeansException, IllegalStateException {
	synchronized (this.startupShutdownMonitor) {
		// Prepare this context for refreshing.
		prepareRefresh();
		// Tell the subclass to refresh the internal bean factory.
		ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
		// Prepare the bean factory for use in this context.
		prepareBeanFactory(beanFactory);
		try {
			// Allows post-processing of the bean factory in context subclasses.
			postProcessBeanFactory(beanFactory);
			// Invoke factory processors registered as beans in the context.
			invokeBeanFactoryPostProcessors(beanFactory);
			// Register bean processors that intercept bean creation.
			registerBeanPostProcessors(beanFactory);
			// Initialize message source for this context.
			initMessageSource();
			// Initialize event multicaster for this context.
			initApplicationEventMulticaster();
			// Initialize other special beans in specific context subclasses.
			onRefresh();
			// Check for listener beans and register them.
			registerListeners();
			// Instantiate all remaining (non-lazy-init) singletons.
			finishBeanFactoryInitialization(beanFactory);
			// Last step: publish corresponding event.
			finishRefresh();
		}
		catch (BeansException ex) {
			if (logger.isWarnEnabled()) {
				logger.warn("Exception encountered during context initialization - " +
						"cancelling refresh attempt: " + ex);
			}
			// Destroy already created singletons to avoid dangling resources.
			destroyBeans();
			// Reset 'active' flag.
			cancelRefresh(ex);
			// Propagate exception to caller.
			throw ex;
		}
		finally {
			// Reset common introspection caches in Spring's core, since we
			// might not ever need metadata for singleton beans anymore...
			resetCommonCaches();
		}
	}
}

4、web ioc容器会创建嵌入式的Servlet容器,createWebServer()。
在这里插入图片描述
5、获取嵌入式的Servlet容器工厂
在这里插入图片描述
从ioc容器中获取 ServletWebServerFactory 组件,TomcatServletWebServerFactory创建对象,后置处理器一看是这个对象,就获取所有的定制器来先定制Servlet容器的相关配置。

6、使用容器工厂获取嵌入式的Servlet容器:this.webServer = factory.getWebServer(getSelfInitializer())。(返回TomcatWebServer)

7、嵌入式的Servlet容器创建对象并启动Servlet容器
先启动嵌入式的Servlet容器,再将ioc容器中剩下没有创建出的对象获取出来。
IOC容器启动创建嵌入式的Servlet容器
在这里插入图片描述

8.使用外置的Servlet容器

嵌入式Servlet容器,应用打成可执行的jar。
优点:简单、便携。

缺点:默认不支持JSP、优化定制比较复杂(使用定制器【ServerProperties、自定义 WebServerFactoryCustomizer】,自己编写嵌入式Servlet容器的创建工厂 【ConfigurableServletWebServerFactory】)。

外置的Servlet容器:外面安装Tomcat—应用war包的方式打包。

1.步骤

1、必须创建一个war项目(利用idea创建好目录结构)。
2、将嵌入式的Tomcat指定为provided。

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

3、必须编写一个 SpringBootServletInitializer 的子类,并调用configure方法。

public class ServletInitializer extends SpringBootServletInitializer { 
	@Override 
	protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
	 //传入SpringBoot应用的主程序 
	 return application.sources(SpringBoot04WebJspApplication.class); 
	} 
}

4、启动服务器就可以使用

2. 原理

jar包:执行SpringBoot主类的main方法,启动ioc容器,创建嵌入式的Servlet容器。
war包:启动服务器,服务器启动SpringBoot应用【SpringBootServletInitializer】,启动ioc容器。

servlet3.0(Spring注解版),第8.2.4 Shared libraries / runtimes pluggability 章节
规则:
1、服务器启动(web应用启动)会创建当前web应用里面每一个jar包里面 ServletContainerInitializer 实例。
2、 ServletContainerInitializer 的实现放在jar包的META-INF/services文件夹下,有一个名为 javax.servlet.ServletContainerInitializer的文件,内容就是 ServletContainerInitializer的 实现类的全类名。
3、还可以使用@HandlesTypes,在应用启动的时候加载我们感兴趣的类。

流程:
1、启动Tomcat
2、org\springframework\spring-web\4.3.14.RELEASE\spring-web-4.3.14.RELEASE.jar!\META- INF\services\javax.servlet.ServletContainerInitializer:Spring的web模块里面有这个文件:org.springframework.web.SpringServletContainerInitializer。
3、SpringServletContainerInitializer将@HandlesTypes(WebApplicationInitializer.class)标注的所有这个类型 的类都传入到onStartup方法的Set>;为这些WebApplicationInitializer类型的类创建实例。
4、每一个WebApplicationInitializer都调用自己的onStartup。
在这里插入图片描述
5、相当于我们的SpringBootServletInitializer的类会被创建对象,并执行onStartup方法。
6、SpringBootServletInitializer实例执行onStartup的时候会createRootApplicationContext,创建容器。

protected WebApplicationContext createRootApplicationContext(
			ServletContext servletContext) {
	//1、创建SpringApplicationBuilder		
	SpringApplicationBuilder builder = createSpringApplicationBuilder();
	StandardServletEnvironment environment = new StandardServletEnvironment();
	environment.initPropertySources(servletContext, null);
	builder.environment(environment);
	builder.main(getClass());
	ApplicationContext parent = getExistingRootWebApplicationContext(servletContext);
	if (parent != null) {
		this.logger.info("Root context already created (using as parent).");
		servletContext.setAttribute(
				WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, null);
		builder.initializers(new ParentContextApplicationContextInitializer(parent));
	}
	builder.initializers(
			new ServletContextApplicationContextInitializer(servletContext));
	builder.contextClass(AnnotationConfigServletWebServerApplicationContext.class);
	
	//调用configure方法,子类重写了这个方法,将SpringBoot的主程序类传入了进来
	builder = configure(builder);
	//使用builder创建一个Spring应用
	SpringApplication application = builder.build();
	if (application.getAllSources().isEmpty() && AnnotationUtils
			.findAnnotation(getClass(), Configuration.class) != null) {
		application.addPrimarySources(Collections.singleton(getClass()));
	}
	Assert.state(!application.getAllSources().isEmpty(),
			"No SpringApplication sources have been defined. Either override the "
					+ "configure method or add an @Configuration annotation");
	// Ensure error pages are registered
	if (this.registerErrorPageFilter) {
		application.addPrimarySources(
				Collections.singleton(ErrorPageFilterConfiguration.class));
	}
	//启动Spring应用
	return run(application);
}

7)、Spring的应用就启动并且创建IOC容器

public ConfigurableApplicationContext run(String... args) {
	StopWatch stopWatch = new StopWatch();
	stopWatch.start();
	ConfigurableApplicationContext context = null;
	Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
	configureHeadlessProperty();
	SpringApplicationRunListeners listeners = getRunListeners(args);
	listeners.starting();
	try {
		ApplicationArguments applicationArguments = new DefaultApplicationArguments(
				args);
		ConfigurableEnvironment environment = prepareEnvironment(listeners,
				applicationArguments);
		configureIgnoreBeanInfo(environment);
		Banner printedBanner = printBanner(environment);
		context = createApplicationContext();
		exceptionReporters = getSpringFactoriesInstances(
				SpringBootExceptionReporter.class,
				new Class[] { ConfigurableApplicationContext.class }, context);
		prepareContext(context, environment, listeners, applicationArguments,
				printedBanner);
		//刷新IOC容器		
		refreshContext(context);
		afterRefresh(context, applicationArguments);
		stopWatch.stop();
		if (this.logStartupInfo) {
			new StartupInfoLogger(this.mainApplicationClass)
					.logStarted(getApplicationLog(), stopWatch);
		}
		listeners.started(context);
		callRunners(context, applicationArguments);
	}
	catch (Throwable ex) {
		handleRunFailure(context, ex, exceptionReporters, listeners);
		throw new IllegalStateException(ex);
	}

	try {
		listeners.running(context);
	}
	catch (Throwable ex) {
		handleRunFailure(context, ex, exceptionReporters, null);
		throw new IllegalStateException(ex);
	}
	return context;
}

启动Servlet容器,再启动SpringBoot应用。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值