这章讲的主要是springboot在web开发中的一些问题,不会有springboot连接数据库的操作,主要记录了Spring boot对静态资源的映射规则,SpringMVC自动配置,错误处理机制以及通过一个RestfulCrud项目来记录Spring boot使用国际化以及拦截器等功能
使用springboot开发流程
- 创建SpringBoot应用。选中我们需要的模块
- SpringBoot已经默认将这些场景配置好了。只需要在配置文件中指定少量配置就可以运行起来
- 自己编写业务代码
SpringBoot对静态资源的映射规则
通过SpringBoot的源码可以发现有这样几个有趣的功能
SpringBoot的静态资源文件夹
先看一段源码,这段源码是从 WebMvcAutoConfiguration 类中取出的
@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));
}
}
通过上面的源码可以获得以下两个信息
所有/webjars/**的请求,都去 classpath:/META-INF/resources/webjars/ 下找资源
什么意思呢?就是说所有的localhost:8080/webjars/**请求都被映射到了 localhost:8080/META-INF/resources/webjars/
通过这个功能,我们就可以映入一些框架的文件,例如jQuery的,或者Bootstrap,不过得先通过Maven引入这些框架文件,我们可以webjars的官网去找到这些文件的依赖信息
只需要将上图Maven栏中的相关信息放到我们项目的pom.xml文件中即可
这是映入相关框架的jar包后的目录,以jquery为例:
前面说过所有的/webjars/**请求都去classpath:/META-INF/resources/webjars/ 下找资源,所以该jquery.js的文件的访问路径就是/webjars/jquery/3.3.1-1/jquery.js在项目中就可以通过这个路径去引入jquery文件
/**访问当前项目的任何资源,如果没有会去下面几个路径下找资源
"classpath:/META-INF/resources/" "classpath:/resources/" "classpath:/static/" "classpath:/public/" "/" # 当前项目的根路径(java目录跟resources都是根目录)
通过上面的源码我们还能发现这个功能,就是localhost:8080/会被映射到上面几个文件中
例如访问 localhost:8080/abc.js,就会去上面所说的几个位置寻找名为abc.js的文件
那么有一个问题,我们可不可以修改静态资源的位置呢?自然是可以的,只需要在application.properties中去修改即可
spring.resources.static-locations=classpath:/hello/, classpath:/jinxin
接收的是一个数组,多个文件通过逗号间隔
欢迎页
在WebMvcAutoConfiguration类中还有下面一段代码
@Bean
public WelcomePageHandlerMapping welcomePageHandlerMapping(
ApplicationContext applicationContext) {
return new WelcomePageHandlerMapping(
new TemplateAvailabilityProviders(applicationContext),
applicationContext, getWelcomePage(),
this.mvcProperties.getStaticPathPattern());
}
我们可以发现这样一个规则,即访问 localhost:8080/ 就能访问到静态资源文件下的index.html页面,这个页面被称为欢迎页
页面图标
在WebMvcAutoConfiguration类中还有一段代码
@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;
}
@Bean
public ResourceHttpRequestHandler faviconRequestHandler() {
ResourceHttpRequestHandler requestHandler = new ResourceHttpRequestHandler();
requestHandler.setLocations(resolveFaviconLocations());
return requestHandler;
}
private List<Resource> resolveFaviconLocations() {
String[] staticLocations = getResourceLocations(
this.resourceProperties.getStaticLocations());
List<Resource> locations = new ArrayList<>(staticLocations.length + 1);
Arrays.stream(staticLocations).map(this.resourceLoader::getResource)
.forEach(locations::add);
locations.add(new ClassPathResource("/"));
return Collections.unmodifiableList(locations);
}
由上面的代码可知所有的 **/favicon.ico 都在静态资源文件下找,即不管是那个页面都可以在静态资源下找这个图标,那么这个图标是显示在哪儿的呢?在浏览器的标签栏上边会有一个图标,这个图标就是显示在哪儿的
SpringMVC自动配置
我们可以去官方文档查看关于SpringMVC的自动配置
Spring Boot已经自动配置好了SpringMVC,以下是官方文档中显示的SpringBoot对SpringMVC的默认配置:(WebMvcAutoConfiguration)
- 包含了 ContentNegotiatingViewResolver 和 BeanNameViewResolver 组件
- 自动配置了ViewResolver(视图解析器:根据方法的返回值得到视图对象(view),视图对象决定如何渲染(转发还是重定向到页面))
- ContentNegotiatingViewResolver :组合所有视图解析器的
- 如何定制:我们可以自己给容器中添加一个视图解析器,ContentNegotiatingViewResolver 会自动的将其组装进来
- 支持静态资源文件夹路径和webjars
- 自动注册了 Converter, GenericConverter, Formatter 组件.
- Converter:转换器,例如:如果页面提交了一组数据,我们使用user对象接收(public String hello(User user);),而涉及到一些非字符串属性的时候,例如18,从前端传来的是字符串类型,这里样转换成数字类型就会用到Converter
- Formatter :格式化器,如果前端传来2017-1-12的字符串,格式化器就能将它转换成Date类型的数据
- 自己添加格式化转换器,我们只需要放在容器中即可(通过@Bean或@Component)
- 支持 HttpMessageConverters
- HttpMessageConverters :消息转换器,SpringMVC用来转换Http请求和响应的
- 自己给容器中添加HttpMessageConverters ,只需要将自己注册的组件添加到容器即可(通过@Bean或@Component)
- HttpMessageConverters :消息转换器,SpringMVC用来转换Http请求和响应的
- 定义错误代码生成规则 MessageCodesResolver.
- 以index.html作为静态首页访问
- 支持添加图标favicon.ico
- Automatic use of a ConfigurableWebBindingInitializerbean.
- 初始化WebDataBinder
- 请求的数据 ---> JavaBean
- 我们也可以配置一个ConfigurableWebBindingInitializer 来替换默认的,只需要放到容器中即可
org.springframework.boot.autoconfigure.web:web的所有自动场景;
If you want to keep Spring Boot MVC features, and you just want to add additional MVC configuration (interceptors, formatters, view controllers etc.) you can add your own @Configuration class of type WebMvcConfigurerAdapter, but without @EnableWebMvc. If you wish to provide custom instances of RequestMappingHandlerMapping, RequestMappingHandlerAdapter or ExceptionHandlerExceptionResolver you can declare a WebMvcRegistrationsAdapter instance providing such components.
If you want to take complete control of Spring MVC, you can add your own @Configuration annotated with @EnableWebMvc.
扩展SprngMVC
虽然上面说的一些功能已经让我们眼花缭乱了,但是可惜的是使用上面的那些功能开发仍旧不够,我们还需要自己扩展。。。
想要扩展SpringMVC的配置就必须要自己定义一个配置类,并且使用@Configuration注解标注,让它成为一个配置类,而且要实现 WebMvcConfigurer 接口,最关键的是一定不能将这个类标注上 @EnableWebMvc 注解
这样做的特点:既保留了所有的自动配置,也能用我们扩展的配置
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
// 浏览器发送 /jinxin 请求来到 success页面
registry.addViewController("/jinxin").setViewName("success");
}
}
全面接管SpringMVC
我们有时候不想使用SpringBoot默认配置的SpringMVC了,而是所有的配置都由我们自己写,那么如何让SpringBoot的默认配置失效呢?只需要在我们自己配置的类标注上@EnableWebMvc即可
@EnableWebMvc
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
// 浏览器发送 /jinxin 请求来到 success页面
registry.addViewController("/jinxin").setViewName("success");
}
}
原理:为什么@EnableWebMvc使自动配置失效了?
1、@EnableWebMvc的核心
@Import({DelegatingWebMvcConfiguration.class})
public @interface EnableWebMvc {
}
2、根据上一步@EnableWebMvc导入了DelegatingWebMvcConfiguration类,而这个类继承了WebMvcConfigurationSupport 类,所以这个类是WebMvcConfigurationSupport 类型的
@Configuration
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
3、而在WebMvcAutoConfiguration类中有一个注解很重要@ConditionalOnMissingBean,它表示容器中没有这个组件的时候,这个自动配置类才生效
@Configuration
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
// 表示容器中没有这个组件的时候,这个自动配置类才生效
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class,
ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
4、在上一步中@ConditionalOnMissingBean需要判断容器中没有某个组件才会让WebMvcAutoConfiguration自动配置类生效,而它检测的正是WebMvcConfigurationSupport 类,然而我们的@EnableWebMvc注解早已经把WebMvcConfigurationSupport类型的DelegatingWebMvcConfiguration类导入了容器中,所以这个注解不会判断通过,因此自动配置类不会加载,MVC自动配置全部失效
如何修改SpringBoot的默认配置
- SpringBoot在自动配置很多组件的时候,先看容器中有没有用户自己配置的(@Bean、@Component)如果有就用用户配置的,如果没有,才自动配置;如果有些组件可以有多个(ViewResolver)将用户配置的和自己默认的组合起来;
- 在SpringBoot中会有非常多的xxxConfigurer帮助我们进行扩展配置
- 在SpringBoot中会有很多的xxxCustomizer帮助我们进行定制配置
RestfulCrud项目
默认访问首页
在上面提到过SpringBoot中有这样一个功能,就是访问localhost:8080/会默认访问到index.html页面,但是前提是这个index.html文件必须在静态资源文件夹中才行,否则访问不到,但是不巧的是在使用thymeleaf模板的时候,必须将页面放到templates目录下,也就是说必须要将index.html页面放到templates目录中,那么再使用localhost:8080就访问不到了,这时的解决办法就是将localhost:8080/这个访问路径转到templates目录下的某个页面,这里以login.html为例进行绑定
//@EnableWebMvc
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
// 所有的WebMvcConfigurerAdapter组件都会一起起作用
@Bean // 将组件添加到容器中
public WebMvcConfigurer webMvcConfigurer(){
WebMvcConfigurer webMvcConfigurer = new WebMvcConfigurer() {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("login");
registry.addViewController("/index.html").setViewName("login");
}
};
return webMvcConfigurer;
}
}
上面将setViewName设置为了login,这里会使用到thymeleaf的字符串拼接,其实完整的路径是 /templates/login.html
国际化
如果由SpringMVC来做国际化:
- 编写国际化配置文件
- 使用ResourceBundleMessageSource管理国际化资源文件
- 在页面使用fmt:message取出国际化内容
现在由SpringBoot来做:
1、编写国际化配置文件,抽取页面需要显示的国际化信息
2、将基础文件(去掉国家名的国际化资源文件)指定为我们自己所写的文件(去application.properties文件)
3、去页面获取国际化的值
在thymeleaf中有一个表达式 #{} 就是专门去获取国际化信息的
th:text="#{login.tip}"
这样做的效果:根据浏览器语言设置的信息切换了国际化
原理:
国际化Locale(区域信息对象):通过LocaleResolver获取区域信息对象,根据请求头带来的区域信息获取Locale进行国际化
@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; }