Springboot第四章 web开发
1、简介
使用SpringBoot;
1)、创建SpringBoot应用,选中我们需要的模块;
2)、SpringBoot已经默认将这些场景配置好了,只需要在配置文件中指定少量配置就可以运行起来
3)、自己编写业务代码;
自动配置原理?
这个场景SpringBoot帮我们配置了什么?能不能修改?能修改哪些配置?能不能扩展?xxx
xxxxAutoConfiguration:帮我们给容器中自动配置组件;
xxxxProperties:配置类来封装配置文件的内容;
webjars
webjars 就是把静态资源打成jar包,然后通过maven 引入
webjars 的查找可以参考网站 https://www.webjars.org 找到引入maven 就行了
Springboot静态资源映射规则
web 的所有自动配置都是在 WebMvcAtuoConfiguraction 类中配置的
@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();
//静态文件夹映射,一个请求,springboot 会先找有没有对应的action ,如果没有就找静态文件
if (!registry.hasMappingForPattern(staticPathPattern)) {
customizeResourceHandlerRegistration(
registry.addResourceHandler(staticPathPattern)
.addResourceLocations(getResourceLocations(
this.resourceProperties.getStaticLocations()))
.setCachePeriod(getSeconds(cachePeriod))
.setCacheControl(cacheControl));
}
}
这个默认的映射是有配置默认路径的,
*/
@ConfigurationProperties(prefix = "spring.resources", ignoreUnknownFields = false)
public class ResourceProperties {
private static final String[] CLASSPATH_RESOURCE_LOCATIONS = {
"classpath:/META-INF/resources/", "classpath:/resources/",
"classpath:/static/", "classpath:/public/" };
还有根目录下查找
//欢迎页映射
@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);
mapping.setUrlMap(Collections.singletonMap("**/favicon.ico",
faviconRequestHandler()));
return mapping;
}
static String[] getResourceLocations(String[] staticLocations) {
String[] locations = new String[staticLocations.length
+ SERVLET_LOCATIONS.length];
System.arraycopy(staticLocations, 0, locations, 0, staticLocations.length);
System.arraycopy(SERVLET_LOCATIONS, 0, locations, staticLocations.length,
SERVLET_LOCATIONS.length);
return locations;
}
这个方法可以获取本地的静态资源, 就是/下面的惊天资源文件
==1)、所有 /webjars/** ,都去 classpath:/META-INF/resources/webjars/ 找资源;==
webjars:以jar包的方式引入静态资源;
==2)、"/**" 访问当前项目的任何资源,都去(静态资源的文件夹)找映射==
"classpath:/META-INF/resources/",
"classpath:/resources/",
"classpath:/static/",
"classpath:/public/"
"/":当前项目的根路径
localhost:8080/abc === 去静态资源文件夹里面找abc
==3)、欢迎页; 静态资源文件夹下的所有index.html页面;被"/**"映射;==
localhost:8080/ 找index页面
==4)、所有的 **/favicon.ico 都是在静态资源文件下找;==
我们可以通过指定文件夹为静态文件夹
spring.mvc.static-path-pattern= 指定之后springboot 默认的静态文件夹不再生效
3、模板引擎
JSP、Velocity、Freemarker、Thymeleaf
SpringBoot推荐的Thymeleaf;
语法更简单,功能更强大;
1、引入thymeleaf;
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
2.1.6
</dependency>
切换thymeleaf版本
<properties>
<thymeleaf.version>3.0.9.RELEASE</thymeleaf.version>
<!-- 布局功能的支持程序 thymeleaf3主程序 layout2以上版本 -->
<!-- thymeleaf2 layout1-->
<thymeleaf-layout-dialect.version>2.2.2</thymeleaf-layout-dialect.version>
</properties>
2、Thymeleaf使用
1. 引入
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
2.1.6
</dependency>
切换thymeleaf版本
<properties>
<thymeleaf.version>3.0.9.RELEASE</thymeleaf.version>
<!-- 布局功能的支持程序 thymeleaf3主程序 layout2以上版本 -->
<!-- thymeleaf2 layout1-->
<thymeleaf-layout-dialect.version>2.2.2</thymeleaf-layout-dialect.version>
</properties>
sprinboot 有自动配置thymeleaf 的autoconfig
在springboot -autoconfig 包下
thymelef springbot 提供给的自动配置规则,都在 ThymeleafProperties 类中
blic 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";
/**
* Whether to check that the template exists before rendering it.
*/
private boolean checkTemplate = true;
/**
* Whether to check that the templates location exists.
*/
private boolean checkTemplateLocation = true;
/**
* Prefix that gets prepended to view names when building a URL.
*/
private String prefix = DEFAULT_PREFIX;
/**
* Suffix that gets appended to view names when building a URL.
*/
private String suffix = DEFAULT_SUFFIX;
/**
* Template mode to be applied to templates. See also Thymeleaf's TemplateMode enum.
*/
private String mode = "HTML";
/**
* Template files encoding.
*/
private Charset encoding = DEFAULT_ENCODING;
/**
* Whether to enable template caching.
*/
private boolean cache = true;
/**
* Order of the template resolver in the chain. By default, the template resolver is
* first in the chain. Order start at 1 and should only be set if you have defined
* additional "TemplateResolver" beans.
*/
private Integer templateResolverOrder;
/**
* Comma-separated list of view names (patterns allowed) that can be resolved.
*/
private String[] viewNames;
/**
* Comma-separated list of view names (patterns allowed) that should be excluded from
* resolution.
*/
private String[] excludedViewNames;
/**
* Enable the SpringEL compiler in SpringEL expressions.
*/
private boolean enableSpringElCompiler;
使用thymelef 引擎模板 ,只用把thymelef 模板放在resuores 文件夹下, 就thymelef 就可一进行渲染
3、语法规则
1)、th:text;改变当前元素里面的文本内容;
th:任意html属性;来替换原生属性的值
表达式
Simple expressions:(表达式语法)
Variable Expressions: ${...}:获取变量值;OGNL;
1)、获取对象的属性、调用方法
2)、使用内置的基本对象:
#ctx : the context object.
#vars: the context variables.
#locale : the context locale.
#request : (only in Web Contexts) the HttpServletRequest object.
#response : (only in Web Contexts) the HttpServletResponse object.
#session : (only in Web Contexts) the HttpSession object.
#servletContext : (only in Web Contexts) the ServletContext object.
${param.foo} // Retrieves a String[] with the values of
request parameter 'foo'
${param.size()}
${param.isEmpty()}
${param.containsKey('foo')}
...
3)、内置的一些工具对象:
#execInfo : information about the template being processed.
#messages : methods for obtaining externalized messages inside variables expressions, in the same way as they would be obtained using #{…} syntax.
#uris : methods for escaping parts of URLs/URIs
#conversions : methods for executing the configured conversion service (if any).
#dates : methods for java.util.Date objects: formatting, component extraction, etc.
#calendars : analogous to #dates , but for java.util.Calendar objects.
#numbers : methods for formatting numeric objects.
#strings : methods for String objects: contains, startsWith, prepending/appending, etc.
#objects : methods for objects in general.
#bools : methods for boolean evaluation.
#arrays : methods for arrays.
#lists : methods for lists.
#sets : methods for sets.
#maps : methods for maps.
#aggregates : methods for creating aggregates on arrays or collections.
#ids : methods for dealing with id attributes that might be repeated (for example, as a result of an iteration).
Selection Variable Expressions: *{...}:选择表达式:和${}在功能上是一样;
补充:配合 th:object="${session.user}:
<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>
Message Expressions: #{...}:获取国际化内容
Link URL Expressions: @{...}:定义URL;
@{/order/process(execId=${execId},execType='FAST')}
Fragment Expressions: ~{...}:片段引用表达式
<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:(布尔运算)
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: _
行内写法
<p>Hello, [[${session.user.name}]]!</p> 或者
<p>The message is "[(${msg})]"</p>
SpringMvc 自动配置
1. Spring MVC auto-configuration
以下是SpringBoot对SpringMVC的默认配置:==(WebMvcAutoConfiguration)==
1) Inclusion of ContentNegotiatingViewResolver
and BeanNameViewResolver
beans.
-
自动配置了ViewResolver(视图解析器:根据方法的返回值得到视图对象(View),视图对象决定如何渲染(转发?重定向?))
-
@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; }
这个localeResolver 本地解析器,根据字面意思就是解析请求头的国际化问题的在他new 的AcceptHeaderLocaleResolver 类中就有说明,调用this.mvcProperties这个说明可以在配置文件中进行配置
-
ContentNegotiatingViewResolver:组合所有的视图解析器的;
-
@Bean @ConditionalOnBean(ViewResolver.class) @ConditionalOnMissingBean(name = "viewResolver", value = ContentNegotiatingViewResolver.class) public ContentNegotiatingViewResolver viewResolver(BeanFactory beanFactory) { ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver(); resolver.setContentNegotiationManager( beanFactory.getBean(ContentNegotiationManager.class)); // ContentNegotiatingViewResolver uses all the other view resolvers to locate // a view so it should have a high precedence resolver.setOrder(Ordered.HIGHEST_PRECEDENCE); return resolver; }
创建了ContentNegotiatingViewResolver 对象,这个是它里面的init方法
-
@Override protected void initServletContext(ServletContext servletContext) { Collection<ViewResolver> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(obtainApplicationContext(), ViewResolver.class).values(); if (this.viewResolvers == null) { this.viewResolvers = new ArrayList<>(matchingBeans.size()); for (ViewResolver viewResolver : matchingBeans) { if (this != viewResolver) { this.viewResolvers.add(viewResolver); } } } else { for (int i = 0; i < this.viewResolvers.size(); i++) { ViewResolver vr = this.viewResolvers.get(i); if (matchingBeans.contains(vr)) { continue; } String name = vr.getClass().getName() + i; obtainApplicationContext().getAutowireCapableBeanFactory().initializeBean(vr, name); } } if (this.viewResolvers.isEmpty()) { logger.warn("Did not find any ViewResolvers to delegate to; please configure them using the " + "'viewResolvers' property on the ContentNegotiatingViewResolver"); } AnnotationAwareOrderComparator.sort(this.viewResolvers); this.cnmFactoryBean.setServletContext(servletContext); }
他会使用BeanFactoryUtils 工具类,把容器中的viewResolver 都给添加到集合中,然后进行注册使用
-
==如何定制:我们可以自己给容器中添加一个视图解析器;自动的将其组合进来;==
/**
* @author ZZQ
* @date 2018/9/1
* @description 自定义视图解析器
*/
@Bean
public ViewResolver MyViewResolver(){
return new MyViewResolver() ;
}
public static class MyViewResolver implements ViewResolver {
@Override
public View resolveViewName(String viewName, Locale locale) throws Exception {
return null;
}
}
大家知道所有请求一定经过前段控制器,然后发配给不同的处理器,我们在这里打断点
由此可见如果我们想添加视图解析器,我们自己定义的视图解析器我们直接把他放入ioc 容器中就可以了
2)Support for serving static resources, including support for WebJars (see below).静态资源文件夹路径,webjars
@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));
}
}
private Optional<Resource> getWelcomePage() {
String[] locations = getResourceLocations(
this.resourceProperties.getStaticLocations());
return Arrays.stream(locations).map(this::getIndexHtml)
.filter(this::isReadable).findFirst();
}
public String[] getStaticLocations() {
return this.staticLocations;
}
private static final String[] CLASSPATH_RESOURCE_LOCATIONS = {
"classpath:/META-INF/resources/", "classpath:/resources/",
"classpath:/static/", "classpath:/public/" };
/**
* Locations of static resources. Defaults to classpath:[/META-INF/resources/,
* /resources/, /static/, /public/].
*/
private String[] staticLocations = CLASSPATH_RESOURCE_LOCATIONS;
*/
@ConfigurationProperties(prefix = "spring.resources", ignoreUnknownFields = false)
3)Static index.html
support. 静态首页访问
private Resource getIndexHtml(String location) {
return this.resourceLoader.getResource(location + "index.html");
}
4)Custom Favicon
support (see below). favicon.ico
自定义图标
@Bean
public SimpleUrlHandlerMapping faviconHandlerMapping() {
SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
mapping.setOrder(Ordered.HIGHEST_PRECEDENCE + 1);
mapping.setUrlMap(Collections.singletonMap("**/favicon.ico",
faviconRequestHandler()));
return mapping;
}
默认放在静态资源文件夹下就可以了,ConditionalOnProperty 也 就是说我们可以禁用这个东西在properties中,默认是true
5)自动注册了 of Converter
, GenericConverter
, Formatter
beans.
-
Converter:转换器; public String hello(User user):类型转换使用Converter
-
Formatter
格式化器; 2017.12.17===Date;
@Override
public void addFormatters(FormatterRegistry registry) {
for (Converter<?, ?> converter : getBeansOfType(Converter.class)) {
registry.addConverter(converter);
}
for (GenericConverter converter : getBeansOfType(GenericConverter.class)) {
registry.addConverter(converter);
}
for (Formatter<?> formatter : getBeansOfType(Formatter.class)) {
registry.addFormatter(formatter);
}
}
分析格式化器是那种格式化器,然后注册
@Bean
@ConditionalOnProperty(prefix = "spring.mvc", name = "date-format")//在文件中配置日期格式化的规则
public Formatter<Date> dateFormatter() {
return new DateFormatter(this.mvcProperties.getDateFormat());//日期格式化组件
}
当配置文件没有配置的时候会默认按照springboot 的如果配置按照自己的
spring.mvc.date-format=
6)Support for HttpMessageConverters
(see below).
-
HttpMessageConverter:SpringMVC用来转换Http请求和响应的;User---Json;
-
HttpMessageConverters
是从容器中确定;获取所有的HttpMessageConverter;==自己给容器中添加HttpMessageConverter,只需要将自己的组件注册容器中(@Bean,@Component)==
7)Automatic registration of
MessageCodesResolver
(see below).定义错误代码生成规则 -
Automatic use of a
ConfigurableWebBindingInitializer
bean (see below).==我们可以配置一个ConfigurableWebBindingInitializer来替换默认的;(添加到容器)==
@Override
protected ConfigurableWebBindingInitializer getConfigurableWebBindingInitializer() {
try {
return this.beanFactory.getBean(ConfigurableWebBindingInitializer.class);
}
catch (NoSuchBeanDefinitionException ex) {
return super.getConfigurableWebBindingInitializer();
}
}
上面写着如若没有就在父类中找一个
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==; 既保留了所有的自动配置,也能用我们扩展的配置;
package com.zzq.springboot04restfulcrud.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.*;
/**
* @author ZZQ
* @date 2018/9/1 20:46
*/
// 添加@EnableWebMvc 可以全面接管mvc springboot 的任何配置都不生效
//配置类
//@EnableWebMvc
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
//ctrl + o 实现父类中的方法
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/login").setViewName("index");
}
}
原理
1)、WebMvcAutoConfiguration是SpringMVC的自动配置类
2)、在做其他自动配置时会导入;@Import(EnableWebMvcConfiguration.class)
@Configuration
@Import(EnableWebMvcConfiguration.class)
@EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })
@Order(0)
public static class WebMvcAutoConfigurationAdapter
implements WebMvcConfigurer, ResourceLoaderAware {
EnableWebMvcConfiguration 把automvc config 需要的配置都给初始化进来比如
@Bean
@Override
public FormattingConversionService mvcConversionService() {
WebConversionService conversionService = new WebConversionService(
this.mvcProperties.getDateFormat());
addFormatters(conversionService);
return conversionService;
}
@Bean
@Override
public Validator mvcValidator() {
if (!ClassUtils.isPresent("javax.validation.Validator",
getClass().getClassLoader())) {
return super.mvcValidator();
}
return ValidatorAdapter.get(getApplicationContext(), getValidator());
}
@Override
protected void configureHandlerExceptionResolvers(
List<HandlerExceptionResolver> exceptionResolvers) {
super.configureHandlerExceptionResolvers(exceptionResolvers);
if (exceptionResolvers.isEmpty()) {
addDefaultHandlerExceptionResolvers(exceptionResolvers);
}
if (this.mvcProperties.isLogResolvedException()) {
for (HandlerExceptionResolver resolver : exceptionResolvers) {
if (resolver instanceof AbstractHandlerExceptionResolver) {
((AbstractHandlerExceptionResolver) resolver)
.setWarnLogCategory(resolver.getClass().getName());
}
}
}
}
3)、容器中所有的WebMvcConfigurer都会一起起作用;
4)、我们的配置类也会被调用;
效果:SpringMVC的自动配置和我们的扩展配置都会起作用;
3、全面接管SpringMVC;
就是不用springboot 给我们配置的东西,把springmvc 还原到最初,我们自己配置springmvc
**我们需要在配置类中添加@EnableWebMvc即可;* 为什么? 我们先来看webMvcAutoConfiguraction 中的配置
@Configuration
//标志为配置类
@ConditionalOnWebApplication(type = Type.SERVLET)
//当这个是一个webapp的时候这个类起作用个
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
//限制为web环境
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
//当容器中没有发现WebMvcConfigurationSupport 类的时候才开始自动给配置
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
//优先级 都是取integer的max 值,然后进行加减操作
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class,
ValidationAutoConfiguration.class })
//这个是表明顺序的,这个类初始化之后,然后接着初始化前端控制器配置,还有ValidationAuto自动绑定字段的
public class WebMvcAutoConfiguration {
我在注释上已经说明了, 也就是如果容器中发现了WebMvcConfigurationSupport 或者他的子类,webmvcautoconfiguraction 就失效了
因此我们呢只用标注一个注解
@EnableWebMvc
在我们的配置类上, 这样springboot 为我们提供的springmvc 的所有配置都失效
package com.zzq.springboot04restfulcrud.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.*;
/**
* @author ZZQ
* @date 2018/9/1 20:46
*/
// 添加@EnableWebMvc 可以全面接管mvc springboot 的任何配置都不生效
//配置类
//@EnableWebMvc
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
//ctrl + o 实现父类中的方法
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/login").setViewName("index");
}
}
@EnableWebMvc
点开这个注解
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(DelegatingWebMvcConfiguration.class)
public @interface EnableWebMvc {
}
发现导入了一个DelegatingWebMvcConfiguraction 这个类
@Configuration
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
这个类继承WebMvcConfiguractionSupport 上文说到如果容器中有这个类, webmvcautoconfiguraction 就会不加载,不配置, 这就是原理
5、如何修改SpringBoot的默认配置
模式:
1)、SpringBoot在自动配置很多组件的时候,先看容器中有没有用户自己配置的(@Bean、@Component)如果有就用用户配置的,如果没有,才自动配置;如果有些组件可以有多个(ViewResolver)将用户配置的和自己默认的组合起来;
2)、在SpringBoot中会有非常多的xxxConfigurer帮助我们进行扩展配置
3)、在SpringBoot中会有很多的xxxCustomizer帮助我们进行定制配置
6、RestfulCRUD
1)、默认访问首页
//使用WebMvcConfigurerAdapter可以来扩展SpringMVC的功能
//@EnableWebMvc 不要接管SpringMVC
@Configuration
public class MyMvcConfig extends WebMvcConfigurerAdapter {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
// super.addViewControllers(registry);
//浏览器发送 /atguigu 请求来到 success
registry.addViewController("/atguigu").setViewName("success");
}
//所有的WebMvcConfigurerAdapter组件都会一起起作用
@Bean //将组件注册在容器
public WebMvcConfigurerAdapter webMvcConfigurerAdapter(){
WebMvcConfigurerAdapter adapter = new WebMvcConfigurerAdapter() {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("login");
registry.addViewController("/index.html").setViewName("login");
}
};
return adapter;
}
}
2)、国际化
1)、编写国际化配置文件;
2)、使用ResourceBundleMessageSource管理国际化资源文件
3)、在页面使用fmt:message取出国际化内容
步骤:
1)、编写国际化配置文件,抽取页面需要显示的国际化消息
2)、SpringBoot自动配置好了管理国际化资源文件的组件;
@ConfigurationProperties(prefix = "spring.messages")
public class MessageSourceAutoConfiguration {
/**
* Comma-separated list of basenames (essentially a fully-qualified classpath
* location), each following the ResourceBundle convention with relaxed support for
* slash based locations. If it doesn't contain a package qualifier (such as
* "org.mypackage"), it will be resolved from the classpath root.
*/
private String basename = "messages";
//我们的配置文件可以直接放在类路径下叫messages.properties;
@Bean
public MessageSource messageSource() {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
if (StringUtils.hasText(this.basename)) {
//设置国际化资源文件的基础名(去掉语言国家代码的)
messageSource.setBasenames(StringUtils.commaDelimitedListToStringArray(
StringUtils.trimAllWhitespace(this.basename)));
}
if (this.encoding != null) {
messageSource.setDefaultEncoding(this.encoding.name());
}
messageSource.setFallbackToSystemLocale(this.fallbackToSystemLocale);
messageSource.setCacheSeconds(this.cacheSeconds);
messageSource.setAlwaysUseMessageFormat(this.alwaysUseMessageFormat);
return messageSource;
}
3)、去页面获取国际化的值;
<!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">
<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>
<label class="sr-only" th:text="#{login.username}">Username</label>
<input type="text" class="form-control" placeholder="Username" th:placeholder="#{login.username}" required="" autofocus="">
<label class="sr-only" th:text="#{login.password}">Password</label>
<input type="password" class="form-control" placeholder="Password" th:placeholder="#{login.password}" required="">
<div class="checkbox mb-3">
<label>
<input type="checkbox" value="remember-me"/> [[#{login.remember}]]
</label>
</div>
<button class="btn btn-lg btn-primary btn-block" type="submit" th:text="#{login.btn}">Sign in</button>
<p class="mt-5 mb-3 text-muted">© 2017-2018</p>
<a class="btn btn-sm">中文</a>
<a class="btn btn-sm">English</a>
</form>
</body>
</html>
效果:根据浏览器语言设置的信息切换了国际化;
@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)、点击链接切换国际化
/**
* 可以在连接上携带区域信息
*/
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[] split = l.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>
4)、拦截器进行登陆检查
/**
* 登陆检查,
*/
public class LoginHandlerInterceptor implements HandlerInterceptor {
//目标方法执行之前
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
Object user = request.getSession().getAttribute("loginUser");
if(user == null){
//未登陆,返回登陆页面
request.setAttribute("msg","没有权限请先登陆");
request.getRequestDispatcher("/index.html").forward(request,response);
return false;
}else{
//已登陆,放行请求
return true;
}
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
注册拦截器
//所有的WebMvcConfigurerAdapter组件都会一起起作用
@Bean //将组件注册在容器
public WebMvcConfigurerAdapter webMvcConfigurerAdapter(){
WebMvcConfigurerAdapter adapter = new WebMvcConfigurerAdapter() {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
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已经做好了静态资源映射
registry.addInterceptor(new LoginHandlerInterceptor()).addPathPatterns("/**")
.excludePathPatterns("/index.html","/","/user/login");
}
};
return adapter;
}
RestfulCRUD 注意点
1.写get post 请求是没问题了,呢么delete put 请求怎么写?
springmvc 提供了一个过滤器 springboot 中式自动配置 的HiddenHttpMethodFilter
public class HiddenHttpMethodFilter extends OncePerRequestFilter {
private static final List<String> ALLOWED_METHODS =
Collections.unmodifiableList(Arrays.asList(HttpMethod.PUT.name(),
HttpMethod.DELETE.name(), HttpMethod.PATCH.name()));
/** Default method parameter: {@code _method} */
public static final String DEFAULT_METHOD_PARAM = "_method";
private String methodParam = DEFAULT_METHOD_PARAM;
他这里会自动接受_method 命名的input 表单,然后会绑定参数, input表单的value 填写 什么? 这里有说明
public enum HttpMethod {
GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE;
7、错误处理机制
1)、SpringBoot默认的错误处理机制
默认效果:
1)、浏览器,返回一个默认的错误页面
//TODO 这个以后回过头进行
8.配置嵌入式servlet容器
springboot默认使用嵌入式tomcat servlet 容器
1)、如何定制和修改Servlet容器的相关配置;
1、修改和server有关的配置(ServerProperties【也是EmbeddedServletContainerCustomizer】);
server.port=8081
server.context-path=/crud
server.tomcat.uri-encoding=UTF-8
//通用的Servlet容器设置
server.xxx
//Tomcat的设置
server.tomcat.xxx
@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerProperties {
2、编写一个EmbeddedServletContainerCustomizer:嵌入式的Servlet容器的定制器;来修改Servlet容器的配置
如果你是2.0 一下的springboot 你可以用EmbeddedServletContainerCustomizer 但是如果是2.0 或者以上你根本就找不到这个接口他被代替了
https://blog.csdn.net/Hard__ball/article/details/81281898
用这个类
WebServerFactoryCustomizer
使用此函数式接口进行定义servlet 的配置
@Bean
public WebServerFactoryCustomizer webServerFactoryCustomizer(){
WebServerFactoryCustomizer<ConfigurableWebServerFactory> w = factory ->{
factory.setPort(8886);
};
return w ;
}
配置文件和我们注入ioc 容器中的webserverFactoryCustomizer 接口有什么关系 >?
在Server Properties 中的某一个方法右击Find Usages 可以看到他被谁调用,
把serverpropertis 中配置的东西全部搬到factory 中 ConfigurableServletWebServerFactory
我们我自己继承 WebServerFactoryCustomizer 然后set 其实道理一样的、
spring2.0 tomcat 必须使用8.5 以上, jdk1.8 ,spring2.0 和1.5 是不一样的, 好多, 比如这个配置
2)、注册Servlet三大组件【Servlet、Filter、Listener】
三大组件代码
package com.zzq.springboot04restfulcrud.servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author ZZQ
* @date 2018/9/3 11:18
*/
public class MyServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doGet(req,resp);
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().write("Over!");
}
}
package com.zzq.springboot04restfulcrud.listener;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.ServletRequestListener;
/**
* @author ZZQ
* @date 2018/9/3 11:29
* Listenter 可以监听session request context
*/
public class MyListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
System.out.println("ServletContextListener.......contextInitialized");
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("ServletContextListener.......contextDestroyed");
}
}
package com.zzq.springboot04restfulcrud.filter;
import javax.servlet.*;
import javax.sound.midi.Soundbank;
import java.io.IOException;
import java.net.SocketAddress;
/**
* @author ZZQ
* @date 2018/9/3 11:22
*/
public class MyFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("init...............");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("doFilter...............");
chain.doFilter(request,response);
}
@Override
public void destroy() {
System.out.println("destroy......");
}
}
对应配置类代码
package com.zzq.springboot04restfulcrud.config;
import com.zzq.springboot04restfulcrud.filter.MyFilter;
import com.zzq.springboot04restfulcrud.listener.MyListener;
import com.zzq.springboot04restfulcrud.servlet.MyServlet;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletListenerRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Arrays;
/**
* @author ZZQ
* @date 2018/9/3 11:20
* 注册Serlvet 三大组件
*/
@Configuration
public class MyServletConfig {
@Bean
public ServletRegistrationBean servletRegistrationBean(){
return new ServletRegistrationBean(new MyServlet(),"/MyServlet");
}
@Bean
public FilterRegistrationBean FilterRegistrationBean (){
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
filterRegistrationBean.setFilter(new MyFilter());
filterRegistrationBean.setUrlPatterns(Arrays.asList("/MyServlet"));
return filterRegistrationBean;
}
public ServletListenerRegistrationBean servletListenerRegistrationBean(){
return new ServletListenerRegistrationBean(new MyListener());
}
}
这种方式最好的例子就式springboot 帮我们自动注册springmvc 的时候自动帮我们注册了前端控制器,我们看下
@Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
@ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public DispatcherServletRegistrationBean dispatcherServletRegistration(
DispatcherServlet dispatcherServlet) {
//创建一个ServletRegistrationBean
DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(
//传入springmvc 的控制器 // 拦截的路径
dispatcherServlet, this.serverProperties.getServlet().getPath());
//拦截器的名字
registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
//加载时机(优先级)
registration.setLoadOnStartup(
this.webMvcProperties.getServlet().getLoadOnStartup());
if (this.multipartConfig != null) {
registration.setMultipartConfig(this.multipartConfig);
}
return registration;
}
我们可以看到注册的Servlet 的默认名字式dispatcherServlet ,
//默认拦截: / 所有请求;包静态资源,但是不拦截jsp请求; /*会拦截jsp
//可以通过server.servletPath来修改SpringMVC前端控制器默认拦截的请求路径
3)、替换为其他嵌入式Servlet容器
jetty (长链接)
undertow (不支持jsp)
ConfigurableServletWebServerFactory 我们在配置servlet 的配置的时候使用WebServerFactoryCustomizer
,使用过这个类,翻译过来就式可配置的web servlet 工厂 ,工厂就应该有很多的servlet 在里面我们看他的继承关系
这是他默认支持的所有的servlet 容器
工厂的作用就式创建不同的嵌入式容器的
Tomcat(默认使用)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
引入web模块默认就是使用嵌入式的Tomcat作为Servlet容器;
</dependency>
首先为什么会引入嵌入式tomcat ? 是因为我们引入了 web starter 我们用idea 的pom 文件分析视图然后把tomcat starter 右击个排除掉,然后直接引入
<dependency>
<artifactId>spring-boot-starter-undertow</artifactId>
<groupId>org.springframework.boot</groupId>
</dependency>
<dependency>
<artifactId>spring-boot-starter-jetty</artifactId>
<groupId>org.springframework.boot</groupId>
</dependency>
4)嵌入式Servlet 容器自动配置原理
这个方面在springboot 1.5 到2.0 之间的改进还是有点大的,我讲解2.0 ,直接对着源码看
springboot 的所有的自动配置都是在autoconfugure 下面的,
第一个
EmbeddedWebServerFactoryCustomizerAutoConfiguration
这个类就式一个自动配置类,翻译过来就式嵌入式web 服务器工厂定制自动配置 ,他就式自动的定制一个webserver
在2.0 中 有三个内部类,和1.5是不一样的,不信可以自己看看1.5去,这三个类中的方法实现其实都是差不多的,我们拿tomcat来说
@Configuration
@EnableConfigurationProperties(ServerProperties.class)
public class EmbeddedWebServerFactoryCustomizerAutoConfiguration {
@ConditionalOnClass({ Tomcat.class, UpgradeProtocol.class })
public static class TomcatWebServerFactoryCustomizerConfiguration {
@Bean
public TomcatWebServerFactoryCustomizer tomcatWebServerFactoryCustomizer(
Environment environment, ServerProperties serverProperties) {
return new TomcatWebServerFactoryCustomizer(environment, serverProperties);
}
}
@Configuration 不用说了标注他是一个配置类, 会被spring自动扫描到ioc容器总
@EnableConfiguractionProperties 看名字就知道意思,他的意思是,允许那个类注入到容器中,就是标注之后可以把类实例化对象放入容器中,他这里放的ServerProperties 也就是server 的配置i文件, 对应的config类
@ConditionalOnClass 如果容器中有Tomcat 这个对象 Upgradeprotocol 是干嘛的?
通过分析发现他是用来和http 打交道的,因此http web 中常用的,因此他有可能可以来标注web环境,也就是说如果是web环境有有http请求的环境,这个类就会自动的配置
调用了构造器方法TomcatWebServerFactoryCustomizer() 里面有两个参数一个参数 environment serverProperties
注释
* Interface representing the environment in which the current application is running.
* Models two key aspects of the application environment: <em>profiles</em> and
* <em>properties</em>. Methods related to property access are exposed via the
* {@link PropertyResolver} superinterface.
environment 记录的是appliction 运行的环境信息, 然后serverproperties 是我们配置的参数
TomcatWebServerFactoryCustomizer
这个类是用来定制tomcat 的,
public TomcatWebServerFactoryCustomizer(Environment environment,
ServerProperties serverProperties) {
this.environment = environment;
this.serverProperties = serverProperties;
}
把对应的参数serverpropert 还设有环境变量设置进去 ,然后返回到ioc容器中
看他的方法, 注入的serverproperties 还有定制静态资源,定制minThreads 定制timeout ,,随便选一个方法
private int determineMaxHttpHeaderSize() {
return (this.serverProperties.getMaxHttpHeaderSize() > 0)
? this.serverProperties.getMaxHttpHeaderSize()
: this.serverProperties.getTomcat().getMaxHttpHeaderSize();
}
可以看出他的定制都是把,我们传入的serverproperties 给自己内部维护的serverproperties
ServerProperties
2.0 serverproperties 中放了三个springboot 默认支持的配置 servlet 容器,应该说是容器对象
嵌入式servlet嵌入式原理
1. 从主程序开始看
public static void main(String[] args) {
SpringApplication.run(SpringBoot04RestfulcrudApplication.class, args);
System.out.println("...");
}
public static ConfigurableApplicationContext run(Class<?> primarySource,
String... args) {
return run(new Class<?>[] { primarySource }, args);
}
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);
//创建ioc容器
context = createApplicationContext();
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
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;
}
点开、createApplicationContext 方法
protected ConfigurableApplicationContext createApplicationContext() {
Class<?> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {
switch (this.webApplicationType) {
case SERVLET:
contextClass = Class.forName(DEFAULT_WEB_CONTEXT_CLASS);
break;
case REACTIVE:
contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
break;
default:
contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
}
}
catch (ClassNotFoundException ex) {
throw new IllegalStateException(
"Unable create a default ApplicationContext, "
+ "please specify an ApplicationContextClass",
ex);
}
}
return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}
他会根据不同的环境创建不同的ioc容器
然后他调用了一个 refreshContext(context);方法吧创建的ioc容器传递进去
private void refreshContext(ConfigurableApplicationContext context) {
refresh(context);
if (this.registerShutdownHook) {
try {
context.registerShutdownHook();
}
catch (AccessControlException ex) {
// Not allowed in some environments.
}
}
}
调用refresh方法
protected void refresh(ApplicationContext applicationContext) {
Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
((AbstractApplicationContext) applicationContext).refresh();
}
这个方法刷新applicationcontext容器
@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();
}
}
}
这个是他的刷新操作具体的实现
我们看onRefresh
protected void onRefresh() throws BeansException {
// For subclasses: do nothing by default.
}
空的里面有个注释,说为实现的类,默认是不做任何事情的
我们是web环境所以看他的实现了 ServletWebServerApplicationContext
@Override
protected void onRefresh() {
super.onRefresh();
try {
createWebServer();
}
catch (Throwable ex) {
throw new ApplicationContextException("Unable to start web server", ex);
}
}
他本对象中有个一个方法createWebServer 方法,创建webserver 容器
private void createWebServer() {
WebServer webServer = this.webServer;
ServletContext servletContext = getServletContext();
if (webServer == null && servletContext == null) {
ServletWebServerFactory factory = getWebServerFactory();
this.webServer = factory.getWebServer(getSelfInitializer());
}
else if (servletContext != null) {
try {
getSelfInitializer().onStartup(servletContext);
}
catch (ServletException ex) {
throw new ApplicationContextException("Cannot initialize servlet context",
ex);
}
}
initPropertySources();
}
他在容器中获取了servlertcontext 对象,然后用this调用方法getWebServerFactory 这个时候注入ioc 中的servlet 就会被获取出来,调用factory的getWebserver方法
调用的是 ServletWebServerFactory 接口中的方法
他有实现类
@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
Tomcat tomcat = new 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);
return getTomcatWebServer(tomcat);
}
调用这个
然后最后就反悔了tomcatwebserver servlet容器,并且启动点开gettomcatwebserver
protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
return new TomcatWebServer(tomcat, getPort() >= 0);
}
public TomcatWebServer(Tomcat tomcat, boolean autoStart) {
Assert.notNull(tomcat, "Tomcat Server must not be null");
this.tomcat = tomcat;
this.autoStart = autoStart;
initialize();
}
private void initialize() throws WebServerException {
TomcatWebServer.logger
.info("Tomcat initialized with port(s): " + getPortsDescription(false));
synchronized (this.monitor) {
try {
addInstanceIdToEngineName();
Context context = findContext();
context.addLifecycleListener((event) -> {
if (context.equals(event.getSource())
&& Lifecycle.START_EVENT.equals(event.getType())) {
// Remove service connectors so that protocol binding doesn't
// happen when the service is started.
removeServiceConnectors();
}
});
// Start the server to trigger initialization listeners
this.tomcat.start();
// We can re-throw failure exception directly in the main thread
rethrowDeferredStartupExceptions();
try {
ContextBindings.bindClassLoader(context, context.getNamingToken(),
getClass().getClassLoader());
}
catch (NamingException ex) {
// Naming is not enabled. Continue
}
// Unlike Jetty, all Tomcat threads are daemon threads. We create a
// blocking non-daemon to stop immediate shutdown
startDaemonAwaitThread();
}
catch (Exception ex) {
stopSilently();
throw new WebServerException("Unable to start embedded Tomcat", ex);
}
}
}
然后嵌入式tomcat 就愉快的启动了, 然后springboot 工作了!!!
9、使用外置的Servlet容器
嵌入式Servlet容器:应用打成可执行的jar
优点:简单、便携;
缺点:默认不支持JSP、优化定制比较复杂(使用定制器【ServerProperties、自定义EmbeddedServletContainerCustomizer】,自己编写嵌入式Servlet容器的创建工厂【EmbeddedServletContainerFactory】);
外置的Servlet容器:外面安装Tomcat---应用war包的方式打包;
1. 用ideaspring初始化器初始化项目, 选中war web
2.idea 中引入tomcat
spring.mvc.view.prefix=WEB-INF
spring.mvc.view.suffix=.jsp
配置一下解析器
必须编写一个SpringBootServletInitializer的子类,并调用configure方法
然后就可一启动了
原理
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的实现类的全类名
流程:
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<Class<?>>;为这些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(AnnotationConfigEmbeddedWebApplicationContext.class);
//调用configure方法,子类重写了这个方法,将SpringBoot的主程序类传入了进来
builder = configure(builder);
//使用builder创建一个Spring应用
SpringApplication application = builder.build();
if (application.getSources().isEmpty() && AnnotationUtils
.findAnnotation(getClass(), Configuration.class) != null) {
application.getSources().add(getClass());
}
Assert.state(!application.getSources().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.getSources().add(ErrorPageFilterConfiguration.class);
}
//启动Spring应用
return run(application);
}
7)、Spring的应用就启动并且创建IOC容器
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
FailureAnalyzers analyzers = null;
configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
Banner printedBanner = printBanner(environment);
context = createApplicationContext();
analyzers = new FailureAnalyzers(context);
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
//刷新IOC容器
refreshContext(context);
afterRefresh(context, applicationArguments);
listeners.finished(context, null);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
return context;
}
catch (Throwable ex) {
handleRunFailure(context, listeners, analyzers, ex);
throw new IllegalStateException(ex);
}
}
**==启动Servlet容器,再启动SpringBoot应用==**
推荐阅读
-
LTP 第四章 开发_exit()测试集
-
Springboot第四章 web开发
-
WEB前端:vuejs全家桶(35):模块化开发:vue-router模块化、自定义组件添加事件
-
渡一教育公开课web前端开发JavaScript精英课学习笔记(十九)正则表达式
-
Web前端开发JavaScript提高(4)
-
【学习笔记 - Linux】CentOS7下安装jdk1.8、tomcat8.5、mysql5.7——搭建java web开发环境
-
JSP 网站统计 博客分类: Web开发 htmljavapagecontenttype
-
jsp 练习 contentType练习 博客分类: web开发 contentTypejsp
-
Weex和Web开发体验的异同
-
Weex和Web开发体验的异同