我是靠谱客的博主 搞怪糖豆,最近开发中收集的这篇文章主要介绍四、SpringBoot与Web开发一、简介二、SpringBoot对静态资源的映射规则三、模板引擎四、SpringMVC自动配置五、如何修改springboot的默认配置六、RestfulCRUD七、错误处理机制八、配置嵌入式servlet容器九、使用外置的servlet容器,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

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官网
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就能自动渲染;
使用:

  1. 导入thymeleaf的名称空间
<html lang="en" xmlns:th="http://www.thymeleaf.org">
  1. 使用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、语法规则

  1. th:text;改变当前元素里面的文本内容;
    th:任意HTML属性;来替换原生属性的值;
序号特征描述属性
1Fragment inclusion片段包含:jsp:includeth:insert ;th:replace
2Fragment iteration遍历: c:forEachth:each
3Conditional evaluation条件判断: c:ifth:if ;th:unless ;th:switch ;th:case
4Local variable definition声明变量: c:setth:object ;th:with
5General attribute modification任意属性修改,支持prepend,appendth:attr ;th:attrprepend ;th:attrappend
6Specific attribute modification修改指定属性默认值th:value ;th:herf ; th:src …
7Text(tag body modification)修改标签内容th:text(转义特殊字符) ;th:utext(不转义特殊字符)
8Franment specification声明片段th:fragment
9Fragment removalth:remove
  1. 表达式
  • 表达式语法:
    • 变量表达式: ${…}
      1. 获取对象的属性、调用方法
      2. 使用 内置的基本对象
        • #ctx : the context object.
        • #vars: the context variables.
        • #locale : the context locale.
        • #request : (only in Web Contexts) the HttpServletRequest object.
        • #response : (only in Web Contexts) the HttpServletResponse object.
        • #session : (only in Web Contexts) the HttpSession object.
        • #servletContext : (only in Web Contexts) the ServletContext object.
      3. 内置的工具对象
        • #execInfo : information about the template being processed.
        • #messages : methods for obtaining externalized messages inside variables expressions, in the same way as they would be obtained using #{…} syntax.
        • #uris : methods for escaping parts of URLs/URIs
        • #conversions : methods for executing the configured conversion service (if any).
        • #dates : methods for java.util.Date objects: formatting, component extraction, etc.
        • #calendars : analogous to #dates , but for java.util.Calendar objects.
        • #numbers : methods for formatting numeric objects.
        • #strings : methods for String objects: contains, startsWith, prepending/appending, etc.
        • #objects : methods for objects in general.
        • #bools : methods for boolean evaluation.
        • #arrays : methods for arrays.
        • #lists : methods for lists.
        • #sets : methods for sets.
        • #maps : methods for maps.
        • #aggregates : methods for creating aggregates on arrays or collections.
        • #ids : methods for dealing with id attributes that might be repeated (for example, as a result of an iteration).
    • 选择表达式: *{…}:和${}在功能上是一样的;补充:配合 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);
       			// }
       			// }
        	}
    	}
	}
  1. 容器中所有的WebMvcAutoConfiguration都会一起起作用;
  2. 我们的配置类也会被调用;
    效果: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自动配置失效?

  1. 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 {}
  1. @EnableWebMvc将WebMvcConfigurationSupport组件导入进来;
  2. 导入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、国际化

  1. 编写国际化配置文件;
  2. 使用resourceBundleMessageSource管理国际化资源文件;
  3. 在页面使用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
  1. 去页面获取国际化的值
    修改文字编码设置
<!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;
		}
  1. 点击链接切换国际化
/**
 * 可以在连接上携带区域信息
 */
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、登录

开发期间模板引擎修改以后,要实时生效:

  1. 禁用模板引擎的缓存
    spring.thymeleaf.cache=false
  2. 按CTRL+F9重新编译;

登录错误消息的表示:

<p style="color: red" th:text="${msg}" th:if="${not #strings.isEmpty(msg)}"></p>
  1. 防止表单重复提交:使用重定向;
	//首先进行视图映射,将/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员工列表

实验要求:

  1. RestfulCRUD:CRUD满足Rest风格;
    URI:/资源名称/资源标识 HTTP请求方式区分对资源CRUD操作
普通CRUD(uri来区分操作)RestfulCRUD
查询getEmpemp----GET
添加addEmp?xxemp----POST
修改updateEmp?id=xxx&xxx=xxxemp/{id}----PUT
删除deleteEmp?id=1emp/{id}----DELETE
  1. 实验的请求架构:
请求URI请求方式
查询所有员工empsGET
查询某个员工(来到修改页面)emp/1GET
来到添加页面empGET
添加员工empPOST
来到修改页面(查出员工进行信息回显)emp/1GET
修改员工empPUT
删除员工emp/1DELETE
  1. 员工列表
    thymeleaf公共页面抽取
1.抽取公共片段
<div th:fragment="copy">
      &copy; 2011 The Good Thymes Virtual Grocery
</div>
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">
  &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>
     &copy; 2011 The Good Thymes Virtual Grocery
   </footer>
 </div>
 
 <footer>
   &copy; 2011 The Good Thymes Virtual Grocery
 </footer>
 
 <div>
   &copy; 2011 The Good Thymes Virtual Grocery
 </div>

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默认的错误处理机制

默认效果:

  1. 浏览器,返回一个默认的错误页面;
    在这里插入图片描述
    浏览器发送的数据:
    在这里插入图片描述
  2. 如果是其他客户端访问,默认返回一个JSON数据;
{
    "timestamp": "2022-03-06T14:32:47.196+00:00",
    "status": 404,
    "error": "Not Found",
    "path": "/dmserver/"
}

其他客户端发送的数据:
在这里插入图片描述
原理:
可以参照ErrorMvcAutoConfiguration;错误处理的自动配置;
给容器中添加了以下组件

  1. 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;
    }
  1. 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);
	}
}
  1. ErrorPageCustomizer:
	//系统出现错误以后来到error请求进行处理;(web.xml注册的错误页面规则)
	@Value("${error.path:/error}")
	private String path = "/error";
  1. 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数据;

  1. 自定义异常处理&返回定制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;
    }
}
  1. 转发到/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得到的(是AbstractErrorControllerErrorAttributes)规定的方法);

  1. 完全编写一个ErrorController的实现类【或者是编写AbstractErrorController的子类】,放在容器中;
  2. 页面上能用的数据,或者是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容器的相关配置:

  1. 修改和server有关的配置(ServerProperties);
server.port=8081
server.servlet.context-path=/crud
server.tomcat.uri-encoding=utf-8

#通用的Servlet容器配置
server.xxx
#tomcat的设置
server.tomcat.xxx
  1. 编写一个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;
		}

	}
}
  1. 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);
	}
  1. 我们对嵌入式容器的配置修改时怎么生效的?
    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 {
}
  1. 容器中导入了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的容器工厂:

  1. SpringBoot应用启动运行run方法;
  2. refreshContext(context);SpringBoot刷新IOC容器【创建IOC容器并初始化容器】
  3. refresh(context);刷新刚才创建好的IOC容器;
  4. onRefresh();servlet的IOC容器重写了onRefresh方法;
  5. servlet的IOC创建嵌入式的服务器对象;createWebServer;
  6. 获取web容器工厂;ServletWebServerFactory factory = getWebServerFactory();从IOC容器获取ServletWebServerFactory 组件;TomcatServletWebServerFactory创建对象,后置处理器就获取所有的定制器来先定制Servlet容器的相关配置;
  7. 使用容器工厂获取嵌入式的servlet容器;this.webServer = factory.getWebServer(getSelfInitializer());
  8. 嵌入式的servlet容器创建对象并启动servlet容器;
    先启动嵌入式的servlet容器,再将IOC容器下剩下没有创建的对象获取出来;

九、使用外置的servlet容器

嵌入式Servlet容器:应用打成可执行的jar

  • 优点:简单、便捷
  • 缺点:默认不支持jsp、优化定制比较复杂(使用定制器【ServerProperties、自定义ConfigurableWebServerFactory】、自己编写嵌入式Servlet容器的创建工厂ServletWebServerFactory );

外置的Servlet容器:外面安装Tomcat----应用war包的方式打包:
步骤:

  1. 必须创建一个war项目(利用idea创建目录结构);
  2. 将嵌入式的tomcat指定为provided
 <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-tomcat</artifactId>
     <scope>provided</scope>
 </dependency>
  1. 必须编写一个SpringBootServletInitializer 的子类,并调用configure方法
public class ServletInitializer extends SpringBootServletInitializer {

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
    	//传入SpringBoot应用主程序
        return application.sources(SpringBootWebJspApplication.class);
    }

}
  1. 启动服务器即可;

1、原理

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

servlet3.0(Spring注解版):
8.2.4 Shared libraries/ runtimes pluggability;

2、规则

  1. 服务启动(web应用启动)会创建当前的web应用里面每一个jar包里面ServletContainerInitializer实例;
  2. ServletContainerInitializer的实现放在META-INF/services文件夹下,有一个名为javax.servlet.ServletContainerInitializer的文件,内容就是ServletContainerInitializer的实现类的全类名;
  3. 还可以使用@HandlesTypes,在应用启动的时候加载我们感兴趣的类;

3、流程

  1. 启动Tomcat;
  2. orgspringframeworkspring-web5.3.16spring-web-5.3.16.jar!META-INFservicesjavax.servlet.ServletContainerInitializer,Spring的web模块里面有这个文件:org.springframework.web.SpringServletContainerInitializer
  3. SpringServletContainerInitializer将@HandlesTypes({WebApplicationInitializer.class})标注的所有这个类型的类都传入到onStartup方法的Set<Class<?>>;为这些WebApplicationInitializer类型的类创建实例;
  4. 每一个WebApplicationInitializer都调用自己的onStartup;在这里插入图片描述
  5. 相当于我们的SpringBootServletInitializer类会被创建对象,并执行onStartup方法;
  6. 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);
    }
  1. 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容器所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(60)

评论列表共有 0 条评论

立即
投稿
返回
顶部