之前我们学习过传统的SSM,JavaWeb,都有过一定的web开发的基础。但是使用SSM发现配置繁琐,原生的JavaWeb,什么都需要自己写自己封装。到了spring boot这个部分,发现一切都变得简单起来了。这次我们就学习怎么使用spring boot进行web开发!
基本原理
之前我们就简单的使用这个搭建过环境,并且成功跑通了helloworld!我们再来回顾一下这个过程:
1. 首先是我们创建这个项目,勾选到我们需要的web环境
org.springframework.boot spring-boot-starter-web
2. 然后就是编写controller
@RestControllerpublic class HelloWorldController{ @RequestMapping("/hello") public String hello() { return "Hello World"; }}
3. 启动这个项目,进行测试。
我相信这个过程是很简单的,所以我也不再过多的详诉。
在这个过程我们并没有使用任何的页面,所以我们需要知道,样式资源放在哪?单体应用的时候,页面资源放在哪?我们的页面怎么进行跳转?
首先来说一说这个样式资源放在哪?
之前看过自动配置原理,所以我们第一反应就是看看这个WebMvcAutoConfiguration这个类,然后找到了这个东西:
@Overridepublic 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(); //这个就是对web资源的映射配置 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)); }}
这个配置的意思是,静态资源在这个目录下可以被访问到。
意思是 所有/webjars/** ,都去classpath:/META-INF/resources/webjars/ 找资源。
webjars:以jar包的方式引入静态资源,可以去webjars官网这个网站选择自己的静态资源。也就是说,我们之前的导入资源都是复制到我们对应的目录下,然后引入的时候都是写相对应的路径,所以,这个就是路径的变化。
可能还是不明白,我们用一个例子来说明:
假设我需要jQuery这个文件。我们需要怎么做,直接引入依赖:(可到官网查找这个依赖)
org.webjars jquery 3.1.1
导入之后,我们直接访问这个路径:localhost:8080/webjars/jquery/3.1.1/jquery.js,然后我们发现就可以访问到这个资源:
这个时候如果我们的静态资源需要就可以这么引用:
这种方式看起来可能有点不太习惯,然后我们就来看看第二种:
同样是上面的这个映射规则的源码:
@Overridepublic void addResourceHandlers(ResourceHandlerRegistry registry) { if (!this.resourceProperties.isAddMappings()) { logger.debug("Default resource handling disabled"); return; } ....... String staticPathPattern = this.mvcProperties.getStaticPathPattern(); if (!registry.hasMappingForPattern(staticPathPattern)) { //staticPathPattern这个是/**,也就是处理器可以处理的路径,即请求路径映射为下面的静态路径 customizeResourceHandlerRegistration(registry.addResourceHandler(staticPathPattern) //resourceProperties.getStaticLocations()是staticLocations。 .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/" }; /** * Locations of static resources. Defaults to classpath:[/META-INF/resources/, * /resources/, /static/, /public/]. */ private String[] staticLocations = CLASSPATH_RESOURCE_LOCATIONS; public String[] getStaticLocations() { return this.staticLocations; }
然后综合来看,得出结论,这个资源处理器把/** 映射到"classpath:/META-INF/resources/",
"classpath:/resources/", "classpath:/static/", "classpath:/public/"
这四个路径当中,换言之,我们直接访问localhost:8080对应的就是这个resources这个文件下的这四个文件夹里面的东西!
例如:localhost:8080/abc ---> 去静态资源文件夹里面找abc()
所以我们只需要把这个静态的资源文件放到这几个文件夹中,就可以直接被访问到。同时,我们注意这个源码中的这个:
@Overridepublic void addResourceHandlers(ResourceHandlerRegistry registry) { //如果这个资源映射我们手动的配置了,直接结束这个方法 if (!this.resourceProperties.isAddMappings()) { logger.debug("Default resource handling disabled"); return; } ......}
也就是说,我们之前找的静态资源路径是我们可以配置的。但是当我们自己配置了之后,这几个默认的配置就不会生效。webjars这个也不会生效!
我们可以进行自己的配置:
spring.resources.static-locations=classpath:/one/,classpath:/hello/
这个配置就可以创建自己对应的文件夹,然后把静态资源放在这个文件夹中即可。
然后再来看看欢迎页面的配置:
private Optional getWelcomePage() { //获取到这个静态资源路径 String[] locations = getResourceLocations(this.resourceProperties.getStaticLocations()); return Arrays.stream(locations).map(this::getIndexHtml).filter(this::isReadable).findFirst();}private Resource getIndexHtml(String location) { //将首页设置为静态资源路径+index.html return this.resourceLoader.getResource(location + "index.html");}
这样一来 localhost:8080/ 会去找静态资源下的index.html界面。
知道了这个样式资源放在那里之后,我们再来看一下这个页面资源怎么放。当然在这之前我们先来看看这个这个Thymeleaf模板引擎。
我们知道spring boot内置了tomcat,而且是以jar包的方式启动,所以默认也就是不支持JSP这个模板引擎,但是spring boot自己官方又出了一个Thymeleaf这个模板引擎。
首先引入这个依赖:
org.springframework.boot spring-boot-starter-thymeleaf
至于具体的语法什么的我就不过多的详诉:
thymeleaf中文手册:https://fanlychie.github.io/post/thymeleaf.html
总之这个东西跟JSP这种东西啥的都差不多。
然后我们来了解页面是怎么跳转的,这个就涉及这个视图解析器。我们引入spring MVC这个之后,这个WebMvcAutoConfiguration就帮我们配置好了一些视图解析器,这些视图解析器都是共同起作用的,也就是一个没有解决问题就拿下一个。
ContentNegotiatingViewResolver,这个就是将所有的视图解析器组合起来,方便管理,我们可以自己写一个视图解析器,然后只需要加入容器,这个就可以自动的将我们写的组合进去。例如:
同时我们直接搜索这个ThymeleafAutoConfiguration,我们引入这个依赖就可以开启这个自动配置。然后我们来关注这个自动配置类里面的一些配置。
@Configuration(proxyBeanMethods = false)//注入这个配置类@EnableConfigurationProperties(ThymeleafProperties.class)@ConditionalOnClass({ TemplateMode.class, SpringTemplateEngine.class })@AutoConfigureAfter({ WebMvcAutoConfiguration.class, WebFluxAutoConfiguration.class })public class ThymeleafAutoConfiguration { @Configuration(proxyBeanMethods = false) @ConditionalOnMissingBean(name = "defaultTemplateResolver") static class DefaultTemplateResolverConfiguration { private final ThymeleafProperties properties; private final ApplicationContext applicationContext; DefaultTemplateResolverConfiguration(ThymeleafProperties properties, ApplicationContext applicationContext) { this.properties = properties; this.applicationContext = applicationContext; } //向容器中添加这个视图解析器 @Bean SpringResourceTemplateResolver defaultTemplateResolver() { SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver(); resolver.setApplicationContext(this.applicationContext); //设置这个视图解析器的前缀和后缀 resolver.setPrefix(this.properties.getPrefix()); resolver.setSuffix(this.properties.getSuffix()); resolver.setTemplateMode(this.properties.getMode()); if (this.properties.getEncoding() != null) { resolver.setCharacterEncoding(this.properties.getEncoding().name()); } resolver.setCacheable(this.properties.isCache()); Integer order = this.properties.getTemplateResolverOrder(); if (order != null) { resolver.setOrder(order); } resolver.setCheckExistence(this.properties.isCheckTemplate()); return resolver; } }
这个就往我们的容器中添加了一个视图解析器,这个视图解析器从配置类中获取了一些属性然后设置到这个视图解析器中,我们再看一下这个前后缀的默认值:
@ConfigurationProperties(prefix = "spring.thymeleaf")public class ThymeleafProperties { private static final Charset DEFAULT_ENCODING = StandardCharsets.UTF_8; public static final String DEFAULT_PREFIX = "classpath:/templates/"; public static final String DEFAULT_SUFFIX = ".html";
这个不就是我们创建项目的时候这个默认的文件夹吗?所以我们现在也应该知道了这个跳转规则了吧,默认的视图解析器就是跳转到这个路径。也就是说如果我们这样写:
@GetMapping("/")public String toIndex(){ return "index";}
我们访问之后直接去template这个文件夹去找index.html。
当然这个东西我们是可以自己在配置文件更改,自行参考自动配置原理按照这个规则配置即可。
搞明白了这些基本原理,我们就可以来拓展一下。
Web拓展
这些都是spring boot自带的东西,虽然很全面,但是还是不够我们使用。所以,我们也可以自己来定义这些东西,添加自己想要的组件。
springboot为我们提供了一个接口来拓展这些功能。
可以添加自己的 WebMvcConfigurer 类型的 @Configuration 类,如果想自定义 RequestMappingHandlerMapping、RequestMappingHandlerAdapter 或者 ExceptionHandlerExceptionResolver 实例,可以声明一个 WebMvcRegistrationsAdapter 实例来提供这些组件。
这个拓展配置既保留了spring MVC的自动配置,也组合了我们拓展的配置
我们可以拓展一些功能,只需要编写一个配置类(@Configuration),要求是WebMvcConfigurer类型(实现这个类可以重写一些方法拓展);不能标注@EnableWebMvc。
例如:我们实现一个重定向的组件
@Configurationpublic class MyMvcConfig implements WebMvcConfigurer { @Override public void addViewControllers(ViewControllerRegistry registry) { //这个方法就是重定向的,第一个参数为请求路径,后者为重定向路径 registry.addRedirectViewController("hello","success"); }}
实现这个WebMvcConfigurer接口,然后里面可以重写各种组件的方法就可以实现这个配置。
来看一下这个的基本原理:
我们知道WebMvcAutoConfiguration
是SpringMVC的自动配置类
WebMvcAutoConfigurationAdapter
是其中一个的内部类
看一下@Import(EnableWebMvcConfiguration.class)
中的这个类,
这个类依旧是WebMvcAutoConfiguration
中的一个内部类。重点看一下这个类继承的父类DelegatingWebMvcConfiguration
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport { private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite(); public DelegatingWebMvcConfiguration() { } //自动注入,从容器中获取所有的WebMvcConfigurer @Autowired(required = false) public void setConfigurers(List configurers) { if (!CollectionUtils.isEmpty(configurers)) { this.configurers.addWebMvcConfigurers(configurers); } } ...... /** * 查看其中一个方法 * this.configurers:也是WebMvcConfigurer接口的一个实现类 * 看一下调用的configureViewResolvers方法 ↓ */ protected void configureViewResolvers(ViewResolverRegistry registry) { this.configurers.configureViewResolvers(registry); } public void configureViewResolvers(ViewResolverRegistry registry) { Iterator var2 = this.delegates.iterator(); while(var2.hasNext()) { WebMvcConfigurer delegate = (WebMvcConfigurer)var2.next(); //将所有的WebMvcConfigurer相关配置都来一起调用; delegate.configureViewResolvers(registry); } }
容器中所有的WebMvcConfigurer都会一起起作用;
我们的配置类也会被调用;
效果:SpringMVC的自动配置和我们的扩展配置都会起作用;
好了,这次先到这,剩下的下次再搞
如有错误还望指正