springboot_web开发
3.19
目录
1.Springboot开发步骤
2.Springboot对静态资源的映射规则
3.模板引擎
引入Thymeleaf
Thymeleaf使用和语法
4.SpringMVC自动配置原理
5.修改Springboot的默认配置
6.全面接管SpringMvc
1.步骤:
1.创建springboot应用,选中需要的模块
2.springboot已经默认将这些场景配置好了,只需要在配置文件中指定少量配置就可以运行起来
3.编写业务代码
2.springboot对静态资源的映射规则:
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration
public void addResourceHandlers(ResourceHandlerRegistry registry) { if (!this.resourceProperties.isAddMappings()) { logger.debug("Default resource handling disabled"); } else { Duration cachePeriod = this.resourceProperties.getCache().getPeriod(); CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl(); if (!registry.hasMappingForPattern("/webjars/**")) { this.customizeResourceHandlerRegistration(registry.addResourceHandler(new String[]{"/webjars/**"}).addResourceLocations(new String[]{"classpath:/META-INF/resources/webjars/"}).setCachePeriod(this.getSeconds(cachePeriod)).setCacheControl(cacheControl)); } String staticPathPattern = this.mvcProperties.getStaticPathPattern(); if (!registry.hasMappingForPattern(staticPathPattern)) { this.customizeResourceHandlerRegistration(registry.addResourceHandler(new String[]{staticPathPattern}).addResourceLocations(getResourceLocations(this.resourceProperties.getStaticLocations())).setCachePeriod(this.getSeconds(cachePeriod)).setCacheControl(cacheControl)); } } }
所有/webjars/**,都去classpath:/META-INF/resources/webjars/找资源
webjars:以jar包的方式引入静态资源 https://www.webjars.org/
<!--引入jquery-webjar--> <dependency> <groupId>org.webjars</groupId> <artifactId>jquery</artifactId> <version>3.3.1</version> </dependency> <!--引入bootstrap--> <dependency> <groupId>org.webjars</groupId> <artifactId>bootstrap</artifactId> <version>4.1.2</version> </dependency>
1)http://localhost:8084/webjars/jquery/3.3.1/jquery.js 能访问到引入的文档
在访问的时候只需要写webjars下面资源的名称即可
String staticPathPattern = this.mvcProperties.getStaticPathPattern(); if (!registry.hasMappingForPattern(staticPathPattern)) { this.customizeResourceHandlerRegistration(registry.addResourceHandler(new String[]{staticPathPattern}).addResourceLocations(getResourceLocations(this.resourceProperties.getStaticLocations())).setCachePeriod(this.getSeconds(cachePeriod)).setCacheControl(cacheControl)); }
2)/** 访问当前项目的任何资源
静态资源文件夹:
"classpath:/META-INF/resources/",
"classpath:/resources/",
"classpath:/static/",
"classpath:/public/"
"/":当前项目的根目录
为什么默认是这四个路径呢?可以看下面的源码。
/Users/mac/.m2/repository/org/springframework/boot/spring-boot-autoconfigure/2.0.0.RELEASE/spring-boot-autoconfigure-2.0.0.RELEASE.jar!/org/springframework/boot/autoconfigure/web/servlet/WebMvcProperties.class
this.staticPathPattern = "/**";
private static final String[] SERVLET_LOCATIONS = new String[]{"/"};
/Users/mac/.m2/repository/org/springframework/boot/spring-boot-autoconfigure/2.0.0.RELEASE/spring-boot-autoconfigure-2.0.0.RELEASE.jar!/org/springframework/boot/autoconfigure/web/ResourceProperties.class
private static final String[] CLASSPATH_RESOURCE_LOCATIONS =
new String[]{"classpath:/META-INF/resources/",
"classpath:/resources/",
"classpath:/static/",
"classpath:/public/"};
3)首页 静态资源下的所有index.html页面,被"/**"映射,localhost:8084/ 找index页面
4)配置喜欢的图标 也是在静态文件夹里找
源码:
@Bean public SimpleUrlHandlerMapping faviconHandlerMapping() { SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping(); mapping.setOrder(-2147483647); mapping.setUrlMap(Collections.singletonMap("**/favicon.ico", this.faviconRequestHandler())); return mapping; }
修改成功!
5)修改默认静态资源路径 application.properties
#多个路径可以用,隔离开路径 spring.resources.static-locations=classpath:/hello/,classpath:/public/
3.模板引擎
JSP、Velocity、Freemarker、Thymeleaf
springboot推荐的Thymeleaf:语法更简单,功能更强大
1.引入thymeleaf
2.0.0版本springboot默认导入的是3.0.9版本的thymeleaf
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency>
2.thymeleaf使用和语法
@ConfigurationProperties( prefix = "spring.thymeleaf" ) public class ThymeleafProperties { private static final Charset DEFAULT_ENCODING; public static final String DEFAULT_PREFIX = "classpath:/templates/"; public static final String DEFAULT_SUFFIX = ".html"; private boolean checkTemplate = true; private boolean checkTemplateLocation = true; private String prefix = "classpath:/templates/"; private String suffix = ".html"; private String mode = "HTML";
把HTML页面放在classpath:/templates/,thymeleaf就能自动渲染
thymeleaf语法官方文档:
https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.pdf
使用:
1)导入thymeleaf的名称空间
<html lang="en" xmlns:th="http://www.thymeleaf.org">
2)表达式
--------------------------
success.xml
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <!--th:text将div里面的文本内容替换 th:任意HTML属性,来替换原生属性的值 --> <div th:text="${hello}"></div> <div th:utext="${hello}"></div> <!--th:each每次遍历都会生成当前这个标签 3个h4--> <h4 th:text="${user}" th:each="user:${users}"></h4> <h4> <!--3个span--> <span th:each="user:${users}">[[${user}]]</span> </h4> </body> </html>
helloController.java
package com.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; import java.lang.reflect.Array; import java.util.Arrays; import java.util.Map; /* @RestContronller=@Controller+@ResponseBody */ @Controller public class helloContronller { @RequestMapping("/success") public String success(Map<String,Object>map){ map.put("hello","<h1>123888910<h1>"); map.put("users", Arrays.asList("zhangsan","lalala")); return "success"; } }
实验结果:
4.springMVC自动配置原理
官方文档:
1.
Inclusion of ContentNegotiatingViewResolver
and BeanNameViewResolver
beans.
自动配置视图了ViewResolver(视图解析器:根据方法的返回值得到视图对象(View),视图对象决定如何渲染)
ContentNegotiatingViewResolver 组合所有的视图解析器
@Bean @ConditionalOnBean({ViewResolver.class}) @ConditionalOnMissingBean( name = {"viewResolver"}, value = {ContentNegotiatingViewResolver.class} ) public ContentNegotiatingViewResolver viewResolver(BeanFactory beanFactory) { ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver(); resolver.setContentNegotiationManager((ContentNegotiationManager)beanFactory.getBean(ContentNegotiationManager.class)); resolver.setOrder(-2147483648); return resolver; }
可以看到ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver();
进入ContentNegotiatingViewResolver类 找到其中的resolveViewName方法,看如何解析视图。
@Nullable public View resolveViewName(String viewName, Locale locale) throws Exception { RequestAttributes attrs = RequestContextHolder.getRequestAttributes(); Assert.state(attrs instanceof ServletRequestAttributes, "No current ServletRequestAttributes"); List<MediaType> requestedMediaTypes = this.getMediaTypes(((ServletRequestAttributes)attrs).getRequest()); if (requestedMediaTypes != null) { List<View> candidateViews = this.getCandidateViews(viewName, locale, requestedMediaTypes); View bestView = this.getBestView(candidateViews, requestedMediaTypes, attrs); if (bestView != null) { return bestView; ··· ···
视图通过getCandidateViews方法获取,进入该方法
发现该方法获取viewResolvers视图解析器并遍历
private List<View> getCandidateViews(String viewName, Locale locale, List<MediaType> requestedMediaTypes) throws Exception { List<View> candidateViews = new ArrayList(); if (this.viewResolvers != null) { Assert.state(this.contentNegotiationManager != null, "No ContentNegotiationManager set"); Iterator var5 = this.viewResolvers.iterator(); while(var5.hasNext()) { ViewResolver viewResolver = (ViewResolver)var5.next(); View view = viewResolver.resolveViewName(viewName, locale); if (view != null) { candidateViews.add(view); } ··· ···
那么视图解析器viewResolvers是怎么得到的呢?通过BeanFactory工具从容器中获取所有视图解析器。
protected void initServletContext(ServletContext servletContext) { Collection<ViewResolver> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(this.obtainApplicationContext(), ViewResolver.class).values(); ViewResolver viewResolver; if (this.viewResolvers == null) { this.viewResolvers = new ArrayList(matchingBeans.size()); Iterator var3 = matchingBeans.iterator(); ··· ···
也就是说:
BeanFactory工具从容器中获取所有视图解析器viewResolvers
->getCandidateViews方法获取viewResolvers并遍历,返回值是List<View>列表
->在ContentNegotiatingViewResolver类中,resolveViewName方法使用getCandidateViews方法解析视图,返回值是View
->在ContentNegotiatingViewResolver类中,创建该类对象
所以,ContentNegotiatingViewResolver 组合了所有的视图解析器。
如何定制视图解析器:可以给容器中添加一个视图解析器,自动将其组合进来。通过测试验证正确性:
DemoApplication.java
package com; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; import org.springframework.web.servlet.View; import org.springframework.web.servlet.ViewResolver; import java.util.Locale; @SpringBootApplication public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } //将自己的视图解析器增加到容器中 @Bean public ViewResolver myViewResolver(){ return new MyViewResolver(); } //视图解析器实现ViewResolver接口 private static class MyViewResolver implements ViewResolver{ @Override public View resolveViewName(String s, Locale locale) throws Exception { return null; } } }
在DispatcherServlet.class的doDispatch方法处打上断点,debug
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
可以看到在容器中已经将自定义ViewResolver的组件添加进来了(蓝色行)
Support for serving static resources, including support for WebJars (covered later in this document)).静态资源文件夹路径webjars
Automatic registration of Converter
, GenericConverter
, and Formatter
beans.
Converter
: 转化器 类型转换
GenericConverter
Formatter:
格式化器
自己也可以添加格式化器转换器,只需放在容器中
Support for HttpMessageConverters
(covered later in this document).
HttpMessageConverters:springboot用来转换Http请求和响应的,从容器中确定,获取所有的
HttpMessageConverter 可以自己配置,添加到容器
Automatic registration of MessageCodesResolver
(covered later in this document). 定义错误代码生成规则 可以自己配置,添加到容器
Static index.html
support. 静态资源首页
Custom Favicon
support (covered later in this document). 图标设置 可以自己配置,添加到容器
Automatic use of a ConfigurableWebBindingInitializer
bean (covered later in this document). 初始化ConfigurableWebBinder把请求数据绑定到JavaBean中
可以自己配置,添加到容器
更多容器中的组件具体可以看:org.springframework.boot.autoconfigure.web.servlet 里的xxxConfiguration类:web的所有自动配置场景。
5.修改springboot的默认配置
模式:
扩展springmvc
springmvc.xml
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.1.xsd"> <mvc:view-controller path="/hello" view-name="success"/> <mvc:interceptors> <mvc:interceptor> <mvc:mapping path="/hello"/> <bean></bean> </mvc:interceptor> </mvc:interceptors> </beans>
原来在springmvc中可以如上配置(拦截hello请求),那么在springboot中不写xml配置文件怎么实现呢?
有两种方法:
" '' ''
If you want to keep Spring Boot MVC features and you want to add additional MVC configuration (interceptors, formatters, view controllers, and other features), you can add your own @Configuration
class of type WebMvcConfigurer
but without @EnableWebMvc
. If you wish to provide custom instances of RequestMappingHandlerMapping
, RequestMappingHandlerAdapter
, or ExceptionHandlerExceptionResolver
, you can declare a WebMvcRegistrationsAdapter
instance to provide such components.
If you want to take complete control of Spring MVC, you can add your own @Configuration
annotated with @EnableWebMvc
.'' '' ''
方法1:直接继承WebMvcConfigurationSupport
并添加@Configuration
注解
编写一个配置类(@Configuration)继承父类重写方法,不能标注@EnableWebMvc注解(springmvc的自动配置和我们的扩展配置都会生效,如果加上@EnableWebMvc则springmvc的自动配置全部失效-具体原因参见源码)。
package com.config; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport; @Configuration public class MyMvcConfig extends WebMvcConfigurationSupport { @Override protected void addViewControllers(ViewControllerRegistry registry) { //super.addViewControllers(registry); //浏览器发送/s请求,也来到success页面 registry.addViewController("/s").setViewName("success"); } }
方法2:直接在配置类上添加@EnableWebMvc
并实现WebMvcConfigurer
接口
会导致静态资源失效,需重写方法如下:
@Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/**") .addResourceLocations("classpath:/resources/") .addResourceLocations("classpath:/static/") .addResourceLocations("classpath:/public/"); super.addResourceHandlers(registry); }
6.全面接管SpringMvc(开发中不推荐)
springboot对springmvc的自动配置不需要了,所有都是自己配置。所有的springmvc的自动配置都失效了。
需要在配置类中添加@EnableWebMvc注解。
@EnableWebMvc @Configuration public class MyMvcConfig extends WebMvcConfigurationSupport { @Override protected void addViewControllers(ViewControllerRegistry registry) {
原理:
进入@EnableWebMvc
@Import({DelegatingWebMvcConfiguration.class}) public @interface EnableWebMvc {
进入DelegatingWebMvcConfiguration.class
@Configuration public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
在自动配置类WebMvcConfiguration.class可以看到:
@Configuration @ConditionalOnWebApplication( type = Type.SERVLET ) @ConditionalOnClass({Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class}) //容器中缺少 WebMvcConfigurationSupport类型的bean时,自动配置类才会生效 @ConditionalOnMissingBean({WebMvcConfigurationSupport.class}) @AutoConfigureOrder(-2147483638) @AutoConfigureAfter({DispatcherServletAutoConfiguration.class, ValidationAutoConfiguration.class}) public class WebMvcAutoConfiguration { public static final String DEFAULT_PREFIX = ""; public static final String DEFAULT_SUFFIX = "";
也就是说:@EnableWebMvc将DelegatingWebMvcConfiguration类中对应的组件都导入到容器中,而这个类是继承WebMvcConfigurationSupport类的,所以将父类中的组件也会导入容器。在自动配置类中可以看到,只有容器中没有WebMvcConfigurationSupport类中组件,这个自动配置类才会生效。而此时容器中已经有了WebMvcConfigurationSupport类中组件,所以自动配置类不会再生效了。