欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Springboot第四章 web开发

程序员文章站 2024-03-18 09:15:52
...

 

1、简介

使用SpringBoot;

1)、创建SpringBoot应用,选中我们需要的模块;

2)、SpringBoot已经默认将这些场景配置好了,只需要在配置文件中指定少量配置就可以运行起来

3)、自己编写业务代码;

自动配置原理?

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

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

webjars 

webjars 就是把静态资源打成jar包,然后通过maven 引入

webjars 的查找可以参考网站 https://www.webjars.org 找到引入maven 就行了 

Springboot静态资源映射规则

web 的所有自动配置都是在 WebMvcAtuoConfiguraction 类中配置的 

Springboot第四章 web开发

@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包的方式引入静态资源;

http://www.webjars.org/

==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第四章 web开发

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 包下

Springboot第四章 web开发

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属性;来替换原生属性的值

Springboot第四章 web开发

表达式

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;
        }
    }

大家知道所有请求一定经过前段控制器,然后发配给不同的处理器,我们在这里打断点

Springboot第四章 web开发

Springboot第四章 web开发

由此可见如果我们想添加视图解析器,我们自己定义的视图解析器我们直接把他放入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

自定义图标

Springboot第四章 web开发

	@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)、编写国际化配置文件,抽取页面需要显示的国际化消息

Springboot第四章 web开发

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>

Springboot第四章 web开发

 

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

@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 容器 

Springboot第四章 web开发

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 接口有什么关系  >?  

Springboot第四章 web开发

在Server Properties 中的某一个方法右击Find Usages  可以看到他被谁调用,

Springboot第四章 web开发

把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 ,

Springboot第四章 web开发

//默认拦截: /  所有请求;包静态资源,但是不拦截jsp请求;   /*会拦截jsp
    //可以通过server.servletPath来修改SpringMVC前端控制器默认拦截的请求路径

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

jetty  (长链接)  

undertow (不支持jsp)

ConfigurableServletWebServerFactory  我们在配置servlet 的配置的时候使用WebServerFactoryCustomizer

,使用过这个类,翻译过来就式可配置的web servlet 工厂 ,工厂就应该有很多的servlet 在里面我们看他的继承关系

Springboot第四章 web开发

这是他默认支持的所有的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 下面的,

Springboot第四章 web开发

第一个

EmbeddedWebServerFactoryCustomizerAutoConfiguration

这个类就式一个自动配置类,翻译过来就式嵌入式web 服务器工厂定制自动配置 ,他就式自动的定制一个webserver 

Springboot第四章 web开发

在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 是干嘛的?   

Springboot第四章 web开发

通过分析发现他是用来和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 的, 

Springboot第四章 web开发

	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 容器,应该说是容器对象

Springboot第四章 web开发

嵌入式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 

Springboot第四章 web开发

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;

Springboot第四章 web开发

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应用==**