概述
web开发
- 一、简介
- 二、SpringBoot对静态资源的映射规则
- 1、所有/webjars/**,都去classpath:/META-INF/resources/webjars/找资源;webjars:以jar包的方式引入静态资源;
- 2、“/**”访问当前项目的任何资源(静态资源的文件夹)
- 3、欢迎页:静态资源文件夹下所有index.html页面;被"/**"映射;
- 4、所有的**/favicon.ico 都是在静态资源文件下找;
- 三、模板引擎
- 1、引入thymeleaf;
- 2、thymeleaf使用&语法
- 3、语法规则
- 四、SpringMVC自动配置
- 1、SpringMVC autoConfiguration
- 2、扩展SpringMVC
- 3、全面接管SpringMVC
- 五、如何修改springboot的默认配置
- 六、RestfulCRUD
- 1、默认访问页面
- 2、国际化
- 3、登录
- 4、拦截器实现登录检测
- 5、CRUD员工列表
- 6、CRUD-员工添加
- 7.CRUD员工修改
- 8.CRUD员工删除
- 七、错误处理机制
- 1.SpringBoot默认的错误处理机制
- 2.如何定制错误响应:
- 1. 如何定制错误的页面;
- 2. 如何定制错误的JSON数据;
- 3. 将我们的定制数据携带出去
- 八、配置嵌入式servlet容器
- 1.如何定制和修改Servlet容器的相关配置:
- 2.注册Servlet三大组件【Servlet、Filter、Listener】
- 3.替换其他的嵌入式Servlet容器
- 4.嵌入式Servlet自动配置原理
- 5、嵌入式Servlet容器启动原理
- 九、使用外置的servlet容器
- 1、原理
- 2、规则
- 3、流程
一、简介
使用SpringBoot:
1.创建SpringBoot应用,选中我们需要的模块;
2.SpringBoot已经默认将这些场景配置好了,只需要在配置文件中指定少量配置就可以运行起来;
3.自己编写业务代码
自动配置原理?
这个场景SpringBoot帮我们配置了什么?能不能修改?能不能扩展?…
XXXAutoConfiguration:帮我们给容器中自动配置组件
XXXProperties:配置类来封装配置文件的内容
二、SpringBoot对静态资源的映射规则
@ConfigurationProperties("spring.web")
public class WebProperties {
//可以设置和资源有关的参数
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
if (!this.resourceProperties.isAddMappings()) {
logger.debug("Default resource handling disabled");
return;
}
addResourceHandler(registry, "/webjars/**", "classpath:/META-INF/resources/webjars/");
addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> {
registration.addResourceLocations(this.resourceProperties.getStaticLocations());
if (this.servletContext != null) {
ServletContextResource resource = new ServletContextResource(this.servletContext, SERVLET_LOCATION);
registration.addResourceLocations(resource);
}
});
}
//配置欢迎页映射
@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;
}
1、所有/webjars/**,都去classpath:/META-INF/resources/webjars/找资源;webjars:以jar包的方式引入静态资源;
webjars官网
http://localhost:8080/webjars/github-com-jquery-jquery/3.4.1/ajax.js
<!-- 引入jquery-webjars:在访问时只需要写webjars下面资源的名称即可 -->
<dependency>
<groupId>org.webjars.npm</groupId>
<artifactId>github-com-jquery-jquery</artifactId>
<version>3.4.1</version>
</dependency>
2、“/**”访问当前项目的任何资源(静态资源的文件夹)
classpath:/META-INF/resources/",
“classpath:/resources/”,
“classpath:/static/”,
“classpath:/public/”,
“classpath:/**” 当前项目的根路径
localhost:8080/abc === 去静态资源文件夹中找abc
3、欢迎页:静态资源文件夹下所有index.html页面;被"/**"映射;
localhost:8080 找index页面
4、所有的**/favicon.ico 都是在静态资源文件下找;
三、模板引擎
jsp、Velocity、Freemarker、Thymeleaf
SpringBoot推荐的Thymeleaf:语法更简单,功能更强大;
1、引入thymeleaf;
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<properties>
<java.version>1.8</java.version>
<!--切换thymeleaf版本-->
<thymeleaf.version>3.0.2.RELEASE</thymeleaf.version>
<!--布局功能支持程序 thymeleaf3主程序 layout2以上版本-->
<!--thymeleaf2 layout-->
<thymeleaf-layout-dialect.version>2.1.1</thymeleaf-layout-dialect.version>
</properties>
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/,thyemleaf就能自动渲染;
使用:
- 导入thymeleaf的名称空间
<html lang="en" xmlns:th="http://www.thymeleaf.org">
- 使用thymeleaf语法;
<!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>
3、语法规则
- th:text;改变当前元素里面的文本内容;
th:任意HTML属性;来替换原生属性的值;
序号 | 特征 | 描述 | 属性 |
---|---|---|---|
1 | Fragment inclusion | 片段包含:jsp:include | th:insert ;th:replace |
2 | Fragment iteration | 遍历: c:forEach | th:each |
3 | Conditional evaluation | 条件判断: c:if | th:if ;th:unless ;th:switch ;th:case |
4 | Local variable definition | 声明变量: c:set | th:object ;th:with |
5 | General attribute modification | 任意属性修改,支持prepend,append | th:attr ;th:attrprepend ;th:attrappend |
6 | Specific attribute modification | 修改指定属性默认值 | th:value ;th:herf ; th:src … |
7 | Text(tag body modification) | 修改标签内容 | th:text(转义特殊字符) ;th:utext(不转义特殊字符) |
8 | Franment specification | 声明片段 | th:fragment |
9 | Fragment removal | th:remove |
- 表达式
- 表达式语法:
- 变量表达式: ${…}
- 获取对象的属性、调用方法
- 使用 内置的基本对象
- #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.
- 内置的工具对象
- #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).
- 选择表达式: *{…}:和
${}
在功能上是一样的;补充:配合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>
- 获取国际化内容: #{…}
- 定义URL连接: @{…}
<a href="details.html" th:href="@{http://localhost:8080/gtvg/order/details(orderId=${o.id})}">view</a>
- 片段引用表达式: ~{…}
<div th:insert="~{commons :: main}">...</div>
- 变量表达式: ${…}
- 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(Bool运算):
- 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: _
四、SpringMVC自动配置
1、SpringMVC autoConfiguration
spring-mvc文档说明
Spring Boot 自动配置好了SpringMVC.
以下是Springboot对SpringMVC的默认配置:
-
Inclusion of ContentNegotiatingViewResolver and BeanNameViewResolver beans.
- 自动配置了ViewResolver(视图解析器:根据方法的返回值得到视图对象*(View),视图对象决定如何渲染(转发?重定向?))
- ContentNegotiatingViewResolver :组合所有的视图解析器;
- 如何定制:我们可以自己给容器中添加一个视图解析器;自动的将其组合进来;
-
Support for serving static resources, including support for WebJars (covered later in this document).静态资源文件夹路径和webjars
-
自动注册了 Converter, GenericConverter, 和 Formatter beans.
- Converter:类型转换器
- Formatter :格式化器(例如时间日期格式化)
- 自己添加的格式化转换器,我们只需要放在容器中即可
-
Support for HttpMessageConverters (covered later in this document).
- HttpMessageConverters :SpringMVC用来转换http请求和响应的;User–>json
- HttpMessageConverters 是从容器中确定;获取所有的HttpMessageConverters ;自己给容器中添加HttpMessageConverter,只需要自己将HttpMessageConverter注册到容器中(@Bean,@Component)
-
Automatic registration of MessageCodesResolver (covered later in this document).定义错误代码生成规则的
-
Static index.html support.静态首页访问
-
Automatic use of a ConfigurableWebBindingInitializer bean (covered later in this document).我们可以配置一个ConfigurableWebBindingInitializer来替换默认的;(添加到容器中)
初始化WebDataBinder;
请求数据======JavaBean
org.springframework.boot.autoconfigure.web:web的所有自动场景
If you want to keep those Spring Boot MVC customizations and make more MVC customizations (interceptors, formatters, view controllers, and other features), you can add your own @Configuration
class of type WebMvcConfigurer but without @EnableWebMvc
.If you want to provide custom instances of RequestMappingHandlerMapping, RequestMappingHandlerAdapter, or ExceptionHandlerExceptionResolver, and still keep the Spring Boot MVC customizations, you can declare a bean of type WebMvcRegistrations and use it to provide custom instances of those components.
If you want to take complete control of Spring MVC, you can add your own @Configuration
annotated with @EnableWebMvc
, or alternatively add your own @Configuration-annotated
DelegatingWebMvcConfiguration as described in the Javadoc of @EnableWebMvc
.
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),是WebMvcConfigurer 类型;不能标注@EnableWebMvc
既保留了所有的自动配置,也能用我们扩展的配置;
//使用WebMvcConfigurer扩展SpringMVC的功能
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
//浏览器发送/abc请求来到success页面
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/abc").setViewName("success");
}
}
原理:
1. WebMvcAutoConfiguration
是SpringMVC的自动配置类
2. 在做其他自动配置时会导入@Import(EnableWebMvcConfiguration.class)
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(WebProperties.class)
public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware {
//从容器中获取所有的WebMvcAutoConfiguration
@Autowired(required = false)
public void setConfigurers(List<WebMvcConfigurer> configurers) {
if (!CollectionUtils.isEmpty(configurers)) {
this.configurers.addWebMvcConfigurers(configurers);
//一个参考实现;将所有的WebMvcAutoConfiguration相关配置都来一起调用
//public void addViewControllers(ViewControllerRegistry registry) {
//Iterator var2 = this.delegates.iterator();
//while(var2.hasNext()) {
//WebMvcConfigurer delegate = (WebMvcConfigurer)var2.next();
// delegate.addViewControllers(registry);
// }
// }
}
}
}
- 容器中所有的
WebMvcAutoConfiguration
都会一起起作用; - 我们的配置类也会被调用;
效果:SpringMVC的自动配置和我们的扩展配置都会起作用;
3、全面接管SpringMVC
SpringBoot对springMVC的自动配置不需要了,所有都是我们自己配置;所有的SpringMVC的自动配置都有失效了;
我们需要在配置类中添加@EnableWebMvc
即可
//使用WebMvcConfigurer扩展SpringMVC的功能
@Configuration
@EnableWebMvc
public class MyMvcConfig implements WebMvcConfigurer {
//浏览器发送/abc请求来到success页面
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/abc").setViewName("success");
}
}
原理:
为什么@EnableWebMvc自动配置失效?
- EnableWebMvc 的核心
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({DelegatingWebMvcConfiguration.class})
public @interface EnableWebMvc {
}
@Configuration(
proxyBeanMethods = false
)
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
@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 {}
@EnableWebMvc
将WebMvcConfigurationSupport组件导入进来;- 导入WebMvcConfigurationSupport只是SpringMVC最基本的功能;
五、如何修改springboot的默认配置
模式:
1. Springboot在自动配置很多组件的时候,先看容器中有没有用户自己配置的(@Bean,@Component),如果有就用用户配置的,如果没有才自动配置;如果有些组件可以有多个(ViewResolver)将用户配置和自己默认的结合起来;
2. 在springboot中会有非常多的XXXConfigurer帮助我们进行扩展配置;
3. 在SpringBoot中会有很多WebServerFactoryCustomizer帮助我们进行定制配置;
六、RestfulCRUD
1、默认访问页面
//在templates下面寻找index.html
// @RequestMapping({"/","/index"})
// public String index(){
// return "index";
// }
//所有的WebMvcConfigurer都会一起起作用
@Bean//将组件注册到容器
public WebMvcConfigurer WebMvcConfigurer(){
WebMvcConfigurer webMvcConfigurer = new WebMvcConfigurer(){
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("login");
registry.addViewController("/index.html").setViewName("login");
}
};
return webMvcConfigurer;
}
2、国际化
- 编写国际化配置文件;
- 使用resourceBundleMessageSource管理国际化资源文件;
- 在页面使用fmt:message取出国际化内容;
步骤:
1. 编写国际化配置文件,抽取页面需要显示的国际化消息
2. springboot自动配置好了管理国际化资源文件的组件;
@Configuration(
proxyBeanMethods = false
)
@ConditionalOnMissingBean(
name = {"messageSource"},
search = SearchStrategy.CURRENT
)
@AutoConfigureOrder(-2147483648)
@Conditional({MessageSourceAutoConfiguration.ResourceBundleCondition.class})
@EnableConfigurationProperties
public class MessageSourceAutoConfiguration {
//我们的配置文件可以直接放置到类路径下叫message.properties
private String basename = "messages";
@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;
}
}
spring.messages.basename=i18n.login
- 去页面获取国际化的值
<!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" th:href="@{/webjars/bootstrap/4.0.0/css/bootstrap.css}" rel="stylesheet">
<!-- Custom styles for this template -->
<link href="asserts/css/signin.css" th:href="@{/asserts/css/signin.css}" rel="stylesheet">
</head>
<body class="text-center">
<form class="form-signin" action="dashboard.html" th:action="@{/user/login}" method="post">
<img class="mb-4" th:src="@{/asserts/img/bootstrap-solid.svg}" 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>
<!--判断-->
<p style="color: red" th:text="${msg}" th:if="${not #strings.isEmpty(msg)}"></p>
<label class="sr-only" th:text="#{login.username}">Username</label>
<input type="text" name="username" class="form-control" placeholder="Username" th:placeholder="#{login.username}" required="" autofocus="">
<label class="sr-only" th:text="#{login.password}">Password</label>
<input type="password" name="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" th:href="@{/index.html(l='zh_CN')}">中文</a>
<a class="btn btn-sm" th:href="@{/index.html(l='en_US')}">English</a>
</form>
</body>
</html>
效果:根据浏览器语言设置的信息切换了国际化;
原理:
- 国际化Locale(区域信息对象);localeResolver (获取区域信息对象)
@Override
@Bean
@ConditionalOnMissingBean(name = DispatcherServlet.LOCALE_RESOLVER_BEAN_NAME)
public LocaleResolver localeResolver() {
if (this.webProperties.getLocaleResolver() == WebProperties.LocaleResolver.FIXED) {
return new FixedLocaleResolver(this.webProperties.getLocale());
}
AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver();
localeResolver.setDefaultLocale(this.webProperties.getLocale());
return localeResolver;
}
- 点击链接切换国际化
/**
* 可以在连接上携带区域信息
*/
public class MyLocaleResolver implements LocaleResolver {
@Override
public Locale resolveLocale(HttpServletRequest request) {
String localeString = request.getParameter("l");
Locale locale = Locale.getDefault();
if (StringUtils.hasLength(localeString)) {
String[] split = localeString.split("_");
locale = new Locale(split[0],split[1]);
}
return locale;
}
@Override
public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
}
}
@Bean
public LocaleResolver localeResolver(){
return new MyLocaleResolver();
}
3、登录
开发期间模板引擎修改以后,要实时生效:
- 禁用模板引擎的缓存
spring.thymeleaf.cache=false
; - 按CTRL+F9重新编译;
登录错误消息的表示:
<p style="color: red" th:text="${msg}" th:if="${not #strings.isEmpty(msg)}"></p>
- 防止表单重复提交:使用重定向;
//首先进行视图映射,将/main.html映射到实际的页面dashboard.html
registry.addViewController("/main.html").setViewName("dashboard");
@PostMapping("/user/login")
//@RequestMapping(value = "/user/login",method = RequestMethod.POST)
public String login(@RequestParam String username,
@RequestParam String password,
Map<String, Object> map){
if (StringUtils.hasLength(username) && "12345".equals(password)){
//登录成功,防止表单重复提交,重定向到目标页面
return "redirect:main.html";
}else {
//登录失败
map.put("msg","用户名密码错误");
return "login";
}
}
4、拦截器实现登录检测
//验证用户登录的拦截器
public class LoginHandlerInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
final Object loginUser = request.getSession().getAttribute("loginUser");
if (loginUser == null){
//未登录,返回登录页面
request.setAttribute("msg","没有权限,请先登录");
request.getRequestDispatcher("/index.html").forward(request,response);
return false;
}else {
//以登录,放行请求
return true;
}
}
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
/*
* 静态资源 **.css **.js
* SpringBoot已经做好静态资源映射,不需要再配置也可以进行访问
* */
registry
.addInterceptor(new LoginHandlerInterceptor())
.addPathPatterns("/**")
.excludePathPatterns("/index.html","/","/user/login");
}
5、CRUD员工列表
实验要求:
- RestfulCRUD:CRUD满足Rest风格;
URI:/资源名称/资源标识 HTTP请求方式区分对资源CRUD操作
普通CRUD(uri来区分操作) | RestfulCRUD | |
---|---|---|
查询 | getEmp | emp----GET |
添加 | addEmp?xx | emp----POST |
修改 | updateEmp?id=xxx&xxx=xxx | emp/{id}----PUT |
删除 | deleteEmp?id=1 | emp/{id}----DELETE |
- 实验的请求架构:
请求URI | 请求方式 | |
---|---|---|
查询所有员工 | emps | GET |
查询某个员工(来到修改页面) | emp/1 | GET |
来到添加页面 | emp | GET |
添加员工 | emp | POST |
来到修改页面(查出员工进行信息回显) | emp/1 | GET |
修改员工 | emp | PUT |
删除员工 | emp/1 | DELETE |
- 员工列表
thymeleaf公共页面抽取
1.抽取公共片段
<div th:fragment="copy">
© 2011 The Good Thymes Virtual Grocery
</div>
2.引入公共片段
<div th:insert="~{footer :: copy}"></div>
~{templatename::selector}:模板名::选择器
~{templatename::fragmentname}:模板名::片段名
3.默认效果:
insert的功能片段在div标签中
三种引入功能片段的th属性:
th:insert:将公共片段整个插入到声明引入的元素中
th:replace:将声明引入的元素替换为公共片段
th:include:将被引入的片段的内容包含进这个标签中
1.公共片段
<footer th:fragment="copy">
© 2011 The Good Thymes Virtual Grocery
</footer>
2.引入方式
<div th:insert="footer :: copy"></div>
<div th:replace="footer :: copy"></div>
<div th:include="footer :: copy"></div>
3.效果
<div>
<footer>
© 2011 The Good Thymes Virtual Grocery
</footer>
</div>
<footer>
© 2011 The Good Thymes Virtual Grocery
</footer>
<div>
© 2011 The Good Thymes Virtual Grocery
</div>
6、CRUD-员工添加
<form th:action="@{/emp}" method="post">
<div class="form-group">
<label>LastName</label>
<input name="lastName" type="text" class="form-control" placeholder="zhangsan">
</div>
<div class="form-group">
<label>Email</label>
<input name="email" 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>
<!--提交的是部门的id-->
<select class="form-control" name="department.id">
<option 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">
</div>
<button type="submit" class="btn btn-primary">添加</button>
</form>
提交的数据格式不对:生日、日期;
2017-12-12;2017/12/12;2017.12.12
日期格式化:SpringMVC将页面提交的值需要转换为指定的类型;
2017-12-12-------------->类型转换,格式化;
默认日期是按照/的方式;
@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;
}
spring.mvc.format.date=yyyy-MM-dd HH:mm
7.CRUD员工修改
<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 HH:mm')}">
</div>
<button type="submit" class="btn btn-primary" th:text="${emp!=null}?'修改':'添加'">添加</button>
</form>
8.CRUD员工删除
1.自定义button属性del_uri,传入删除的uri值
<button th:attr="del_uri=@{/emp/}+${emp.id}" class="btn btn-sm btn-danger deleteBtn">删除</button>
2.按照spring规范定义一个删除的表单,表单中只有一个隐藏属性input设置方法为_method,value=delete
<form id="deleteEmpForm" method="post">
<input type="hidden" name="_method" value="delete"/>
</form>
3.在JavaScript中编写提交逻辑
<script>
$(".deleteBtn").click(function(){
//删除当前员工的
$("#deleteEmpForm").attr("action",$(this).attr("del_uri")).submit();
return false;
});
</script>
七、错误处理机制
1.SpringBoot默认的错误处理机制
默认效果:
- 浏览器,返回一个默认的错误页面;
浏览器发送的数据:
- 如果是其他客户端访问,默认返回一个JSON数据;
{
"timestamp": "2022-03-06T14:32:47.196+00:00",
"status": 404,
"error": "Not Found",
"path": "/dmserver/"
}
其他客户端发送的数据:
原理:
可以参照ErrorMvcAutoConfiguration
;错误处理的自动配置;
给容器中添加了以下组件
- DefaultErrorAttributes:
//帮我们在页面共享数据
private Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
Map<String, Object> errorAttributes = new LinkedHashMap();
errorAttributes.put("timestamp", new Date());
this.addStatus(errorAttributes, webRequest);
this.addErrorDetails(errorAttributes, webRequest, includeStackTrace);
this.addPath(errorAttributes, webRequest);
return errorAttributes;
}
- BasicErrorController:
@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class BasicErrorController extends AbstractErrorController {
//产生HTML类型的数据,浏览器发送请求来到这个方法处理
@RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
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 = resolveErrorView(request, response, status, model);
return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
}
//产生JSON类型的数据,其他类型客户端发送请求到这个方法处理
@RequestMapping
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);
}
}
- ErrorPageCustomizer:
//系统出现错误以后来到error请求进行处理;(web.xml注册的错误页面规则)
@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) {
//默认SpringBoot可以去找到一个页面? 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;
}
2.如何定制错误响应:
1. 如何定制错误的页面;
- **有模板引擎的情况下;error/状态码;**【将错误页面命名为错误状态码.HTML放在模板引擎文件夹里面的error文件夹下】,发生此状态的错误就会来到对应的页面;
我们可以使用4XX和5XX作为错误页面的文件名称来匹配这种类型的所有错误,精确有限(优先寻找精确的状态码.html);
页面能获取的信息:
timestamp:时间戳
status:状态码
error:错误提示
exception:异常对象
message:异常消息
errors:JSR303数据校验的错误都在这里
- 没有模板引擎(模板引擎找不到这个错误页面),静态资源文件夹下找;
- 以上都没有错误页面,就是默认来到SpringBoot默认的错误提示页面;
2. 如何定制错误的JSON数据;
- 自定义异常处理&返回定制JSON数据
//没有自适应效果,浏览器和客户端返回都是JSON
@ControllerAdvice
public class MyExceptionHandler {
@ResponseBody
@ExceptionHandler(UserNotExitsException.class)
public Map<String,Object> handlerException(Exception e){
Map<String, Object> map = new HashMap<>();
map.put("code","user.not.exist");
map.put("message",e.getMessage());
return map;
}
}
- 转发到/error进行自适应响应效果处理
@ExceptionHandler(UserNotExitsException.class)
public String handlerException(Exception e, HttpServletRequest request){
Map<String, Object> map = new HashMap<>();
//传入我们自己的错误状态码,否则就不会进入定制的错误流程的解析页面
//Integer status = (Integer)this.getAttribute(requestAttributes, "javax.servlet.error.status_code");
request.setAttribute("javax.servlet.error.status_code",500);
map.put("code","user.not.exist");
map.put("message",e.getMessage());
//转发到/error
return "forward:/error";
}
3. 将我们的定制数据携带出去
出现错误以后,回来到/error请求,会被BasicErrorController
处理,响应出去可以获取的数据是由getErrorAttributes
得到的(是AbstractErrorController
(ErrorAttributes
)规定的方法);
- 完全编写一个ErrorController的实现类【或者是编写AbstractErrorController的子类】,放在容器中;
- 页面上能用的数据,或者是JSON返回能用的数据都是通过
this.errorAttributes.getErrorAttributes
得到;容器中的DefaultErrorAttributes.getErrorAttributes();默认进行数据处理的;自定义ErrorAttributes如下:
@Component
//在容器中加入我们自己定义的errorAttributes
public class MyErrorAttributes extends DefaultErrorAttributes {
@Override
public Map<String, Object> getErrorAttributes(WebRequest webRequest, ErrorAttributeOptions options) {
final Map<String, Object> attributes = super.getErrorAttributes(webRequest, options);
attributes.put("company","heym");
return attributes;
}
}
最终的效果:响应是自适应的,可以通过定制ErrorAttributes改变需要返回的内容。
八、配置嵌入式servlet容器
SpringBoot默认使用tomcat作为servlet容器;
1.如何定制和修改Servlet容器的相关配置:
- 修改和server有关的配置(ServerProperties);
server.port=8081
server.servlet.context-path=/crud
server.tomcat.uri-encoding=utf-8
#通用的Servlet容器配置
server.xxx
#tomcat的设置
server.tomcat.xxx
- 编写一个WebServerFactoryCustomizer:嵌入式的Servlet容器的定制器;来修改servlet容器的配置;
//定制Servlet容器相关的规则
@Bean
public WebServerFactoryCustomizer<ConfigurableWebServerFactory> webServerFactoryCustomizer(){
return factory -> factory.setPort(8083);
}
2.注册Servlet三大组件【Servlet、Filter、Listener】
SpringBoot默认是以jar包的方式启动嵌入式的Servlet容器来启动SpringBoot的web应用,没有web.xml文件;注册三大组件用以下方式:
ServletRegistrationBean:
@Bean
public ServletRegistrationBean<MyServlet> servletRegistrationBean(){
final ServletRegistrationBean<MyServlet> registrationBean = new ServletRegistrationBean<>(new MyServlet(), "/myServlet");
return registrationBean;
}
FilterRegistrationBean:
@Bean
public FilterRegistrationBean<MyFilter> filterRegistrationBean(){
final FilterRegistrationBean<MyFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new MyFilter());
registrationBean.setUrlPatterns(Arrays.asList("/hello","/myServlet"));
return registrationBean;
}
ServletListenerRegistrationBean:
@Bean
public ServletListenerRegistrationBean<MyListener> listenerRegistrationBean(){
return new ServletListenerRegistrationBean<>(new MyListener());
}
SpringBoot帮我们自动注册SpringMVC的前端控制器:DispatcherServlet:
@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
//可以通过server.servletPath
registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
registration.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());
multipartConfig.ifAvailable(registration::setMultipartConfig);
return registration;
}
3.替换其他的嵌入式Servlet容器
默认支持:
Tomcat(默认使用)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
Jetty:
<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>
Undertow:
<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-undertow</artifactId>
<groupId>org.springframework.boot</groupId>
</dependency>
4.嵌入式Servlet自动配置原理
EmbeddedWebServerFactoryCustomizerAutoConfiguration:嵌入式容器自动配置:
@Configuration(proxyBeanMethods = false)
class ServletWebServerFactoryConfiguration {
@Configuration(proxyBeanMethods = false)
//判断当前是否引入Tomcat依赖
@ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })
//判断容器中有没有用户自己定义的ServletWebServerFactory;嵌入式的Servlet容器工厂;作用是创建Servlet容器
@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
static class EmbeddedTomcat {
@Bean
TomcatServletWebServerFactory tomcatServletWebServerFactory(
ObjectProvider<TomcatConnectorCustomizer> connectorCustomizers,
ObjectProvider<TomcatContextCustomizer> contextCustomizers,
ObjectProvider<TomcatProtocolHandlerCustomizer<?>> protocolHandlerCustomizers) {
TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
factory.getTomcatConnectorCustomizers()
.addAll(connectorCustomizers.orderedStream().collect(Collectors.toList()));
factory.getTomcatContextCustomizers()
.addAll(contextCustomizers.orderedStream().collect(Collectors.toList()));
factory.getTomcatProtocolHandlerCustomizers()
.addAll(protocolHandlerCustomizers.orderedStream().collect(Collectors.toList()));
return factory;
}
}
}
- ServletWebServerFactory(嵌入式Servlet容器工厂);
@FunctionalInterface
public interface ServletWebServerFactory {
WebServer getWebServer(ServletContextInitializer... initializers);
}
2. 以TomcatServletWebServerFactory为例
@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
if (this.disableMBeanRegistry) {
Registry.disableRegistry();
}
//创建一个Tomcat
Tomcat tomcat = new Tomcat();
//配置Tomcat的基本环境
File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
tomcat.setBaseDir(baseDir.getAbsolutePath());
for (LifecycleListener listener : this.serverLifecycleListeners) {
tomcat.getServer().addLifecycleListener(listener);
}
Connector connector = new Connector(this.protocol);
connector.setThrowOnFailure(true);
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传入进去,返回一个web容器对象,并且启动tomcat服务器
return getTomcatWebServer(tomcat);
}
- 我们对嵌入式容器的配置修改时怎么生效的?
ServerProperties、WebServerFactoryCustomizer
@Configuration(proxyBeanMethods = false)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@ConditionalOnClass(ServletRequest.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(ServerProperties.class)
@Import({
//导入BeanPostProcessorsRegistrar:给容器中导入一些组件
//导入了WebServerFactoryCustomizerBeanPostProcessor;
//后置处理器:bean初始化前后(创建完对象,还没有属性赋值)执行初始化工作
ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
public class ServletWebServerFactoryAutoConfiguration {
}
- 容器中导入了WebServerFactoryCustomizerBeanPostProcessor
//初始化之前
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
//如果当前初始化的是一个WebServerFactory类型的组件
if (bean instanceof WebServerFactory) {
postProcessBeforeInitialization((WebServerFactory) bean);
}
return bean;
}
private void postProcessBeforeInitialization(WebServerFactory webServerFactory) {
//获取所有的定制器,调用每一个定制器的customize方法来给Servlet容器进行属性赋值;
//getCustomizers:从容器中获取所有这类型的组件
//定制Servlet容器,给容器中添加一个WebServerFactoryCustomizer类型的组件;
LambdaSafe.callbacks(WebServerFactoryCustomizer.class, getCustomizers(), webServerFactory)
.withLogger(WebServerFactoryCustomizerBeanPostProcessor.class)
.invoke((customizer) -> customizer.customize(webServerFactory));
}
步骤:
- ServletWebServerFactoryAutoConfiguration类根据导入情况,给容器添加相应的WebServletWebServerFactory【TomcatServletWebServerFactory】;
- 容器中某个组件要创建对象就会惊动后置处理器WebServerFactoryCustomizerBeanPostProcessor;只要是嵌入式的servlet工厂,后置处理器就工作;
- 后置处理器,从容器中获取所有的WebServerFactoryCustomizer,调用定制器的定制方法;
5、嵌入式Servlet容器启动原理
什么时候创建嵌入式的Servlet容器工厂?什么时候获取嵌入式的servlet容器并启动Tomcat?
获取嵌入式的servlet的容器工厂:
- SpringBoot应用启动运行run方法;
- refreshContext(context);SpringBoot刷新IOC容器【创建IOC容器并初始化容器】
- refresh(context);刷新刚才创建好的IOC容器;
- onRefresh();servlet的IOC容器重写了onRefresh方法;
- servlet的IOC创建嵌入式的服务器对象;createWebServer;
- 获取web容器工厂;
ServletWebServerFactory factory = getWebServerFactory();
从IOC容器获取ServletWebServerFactory 组件;TomcatServletWebServerFactory创建对象,后置处理器就获取所有的定制器来先定制Servlet容器的相关配置; - 使用容器工厂获取嵌入式的servlet容器;
this.webServer = factory.getWebServer(getSelfInitializer());
- 嵌入式的servlet容器创建对象并启动servlet容器;
先启动嵌入式的servlet容器,再将IOC容器下剩下没有创建的对象获取出来;
九、使用外置的servlet容器
嵌入式Servlet容器:应用打成可执行的jar
- 优点:简单、便捷
- 缺点:默认不支持jsp、优化定制比较复杂(使用定制器【ServerProperties、自定义ConfigurableWebServerFactory】、自己编写嵌入式Servlet容器的创建工厂ServletWebServerFactory );
外置的Servlet容器:外面安装Tomcat----应用war包的方式打包:
步骤:
- 必须创建一个war项目(利用idea创建目录结构);
- 将嵌入式的tomcat指定为
provided
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
- 必须编写一个SpringBootServletInitializer 的子类,并调用configure方法
public class ServletInitializer extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
//传入SpringBoot应用主程序
return application.sources(SpringBootWebJspApplication.class);
}
}
- 启动服务器即可;
1、原理
jar包:执行SpringBoot主类的main方法,启动IOC容器,创建嵌入式的Servlet容器;
war包:启动服务器,服务器启动SpringBoot应用【SpringBootServletInitializer 】,启动IOC容器;
servlet3.0(Spring注解版):
8.2.4 Shared libraries/ runtimes pluggability;
2、规则
- 服务启动(web应用启动)会创建当前的web应用里面每一个jar包里面ServletContainerInitializer实例;
- ServletContainerInitializer的实现放在META-INF/services文件夹下,有一个名为javax.servlet.ServletContainerInitializer的文件,内容就是ServletContainerInitializer的实现类的全类名;
- 还可以使用@HandlesTypes,在应用启动的时候加载我们感兴趣的类;
3、流程
- 启动Tomcat;
orgspringframeworkspring-web5.3.16spring-web-5.3.16.jar!META-INFservicesjavax.servlet.ServletContainerInitializer
,Spring的web模块里面有这个文件:org.springframework.web.SpringServletContainerInitializer;- SpringServletContainerInitializer将@HandlesTypes({WebApplicationInitializer.class})标注的所有这个类型的类都传入到onStartup方法的Set<Class<?>>;为这些WebApplicationInitializer类型的类创建实例;
- 每一个WebApplicationInitializer都调用自己的onStartup;
- 相当于我们的
SpringBootServletInitializer
类会被创建对象,并执行onStartup方法; SpringBootServletInitializer
实例执行onStartup的时候会创建createRootApplicationContext
容器;
protected WebApplicationContext createRootApplicationContext(ServletContext servletContext) {
//1.创建SpringApplicationBuilder
SpringApplicationBuilder builder = this.createSpringApplicationBuilder();
builder.main(this.getClass());
ApplicationContext parent = this.getExistingRootWebApplicationContext(servletContext);
if (parent != null) {
this.logger.info("Root context already created (using as parent).");
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, (Object)null);
builder.initializers(new ApplicationContextInitializer[]{new ParentContextApplicationContextInitializer(parent)});
}
builder.initializers(new ApplicationContextInitializer[]{new ServletContextApplicationContextInitializer(servletContext)});
builder.contextFactory((webApplicationType) -> {
return new AnnotationConfigServletWebServerApplicationContext();
});
//调用了configure,子类重写了这个方法,将SpringBoot的主程序类传入进来
builder = this.configure(builder);
builder.listeners(new ApplicationListener[]{new SpringBootServletInitializer.WebEnvironmentPropertySourceInitializer(servletContext)});
//使用builder创建一个Spring应用
SpringApplication application = builder.build();
if (application.getAllSources().isEmpty() && MergedAnnotations.from(this.getClass(), SearchStrategy.TYPE_HIERARCHY).isPresent(Configuration.class)) {
application.addPrimarySources(Collections.singleton(this.getClass()));
}
Assert.state(!application.getAllSources().isEmpty(), "No SpringApplication sources have been defined. Either override the configure method or add an @Configuration annotation");
if (this.registerErrorPageFilter) {
application.addPrimarySources(Collections.singleton(ErrorPageFilterConfiguration.class));
}
application.setRegisterShutdownHook(false);
//启动spring应用
return this.run(application);
}
- Spring应用创建并启动IOC容器;
public ConfigurableApplicationContext run(String... args) {
long startTime = System.nanoTime();
DefaultBootstrapContext bootstrapContext = createBootstrapContext();
ConfigurableApplicationContext context = null;
configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment);
context = createApplicationContext();
context.setApplicationStartup(this.applicationStartup);
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
//刷新IOC容器
refreshContext(context);
afterRefresh(context, applicationArguments);
Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);
}
listeners.started(context, timeTakenToStartup);
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, listeners);
throw new IllegalStateException(ex);
}
try {
Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
listeners.ready(context, timeTakenToReady);
}
catch (Throwable ex) {
handleRunFailure(context, ex, null);
throw new IllegalStateException(ex);
}
return context;
}
启动Servlet容器,在启动SpringBoot应用;
最后
以上就是搞怪糖豆为你收集整理的四、SpringBoot与Web开发一、简介二、SpringBoot对静态资源的映射规则三、模板引擎四、SpringMVC自动配置五、如何修改springboot的默认配置六、RestfulCRUD七、错误处理机制八、配置嵌入式servlet容器九、使用外置的servlet容器的全部内容,希望文章能够帮你解决四、SpringBoot与Web开发一、简介二、SpringBoot对静态资源的映射规则三、模板引擎四、SpringMVC自动配置五、如何修改springboot的默认配置六、RestfulCRUD七、错误处理机制八、配置嵌入式servlet容器九、使用外置的servlet容器所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复