我们使用springboot很大一部分场景是用于网页应用开发,但是现在大型的项目基本都采用前后端分离模式,所以后端一般只做api接口。而对于一些小型项目,可能前后端一体更节约时间。这次简单描述下springboot的web开发相关内容
使用SpringBoot;
1)、创建SpringBoot应用,选中我们需要的模块;
2)、SpringBoot已经默认将这些场景配置好了,只需要在配置文件中指定少量配置就可以运行起来
3)、自己编写业务代码;
自动配置原理?
这个场景SpringBoot帮我们配置了什么?能不能修改?能修改哪些配置?能不能扩展?怎么扩展等等。
xxxxAutoConfiguration:帮我们给容器中自动配置组件;
xxxxProperties:配置类来封装配置文件的内容;
我们先来看看springboot对静态资源的映射。
在org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration
类中,都是mvc相关的自动配置
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
if (!this.resourceProperties.isAddMappings()) {
logger.debug("Default resource handling disabled");
return;
}
Duration cachePeriod = this.resourceProperties.getCache().getPeriod();
//@ConfigurationProperties(prefix = "spring.resources", ignoreUnknownFields = false)
//public class ResourceProperties implements ResourceLoaderAware {
//resourceProperties可以设置跟资源有关的参数
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));
}
}
//配置欢迎页映射
//HandlerMapping 底层保存的对应的请求谁来处理
@Bean
public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext,
FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) {
WelcomePageHandlerMapping welcomePageHandlerMapping = new WelcomePageHandlerMapping(
new TemplateAvailabilityProviders(applicationContext), applicationContext, getWelcomePage(),
this.mvcProperties.getStaticPathPattern());
welcomePageHandlerMapping.setInterceptors(getInterceptors(mvcConversionService, mvcResourceUrlProvider));
welcomePageHandlerMapping.setCorsConfigurations(getCorsConfigurations());
return welcomePageHandlerMapping;
}
//实际还是在静态文件路径下拼上index.html
private Optional<Resource> getWelcomePage() {
String[] locations = getResourceLocations(this.resourceProperties.getStaticLocations());
return Arrays.stream(locations).map(this::getIndexHtml).filter(this::isReadable).findFirst();
}
private Resource getIndexHtml(String location) {
return this.resourceLoader.getResource(location + "index.html");
}
webjars官网
1)、所有 /webjars/** ,都去 classpath:/META-INF/resources/webjars/ 找资源;
webjars:以jar包的方式引入静态资源;
localhost:8080/webjars/jquery/3.3.1/jquery.js
<!--引入jquery-webjar-->在访问的时候只需要写webjars下面资源的名称即可
<dependency>
<groupId>org.webjars</groupId>
<artifactId>jquery</artifactId>
<version>3.3.1</version>
</dependency>
2)、"/**" 访问当前项目的任何资源,如果没有对应的处理规则,都去(静态资源的文件夹)找映射
"classpath:/META-INF/resources/",
"classpath:/resources/",
"classpath:/static/",
"classpath:/public/"
"/":当前项目的根路径
localhost:8080/abc === 去静态资源文件夹里面找abc
3)、欢迎页; 静态资源文件夹下的所有index.html页面;被"/**"映射;
localhost:8080/ 找index页面
4)、手动定义静态资源映射路径,默认定义会失效;
spring.resources.static-locations=classpath:/fantasy,classpath:/fantasy2
- SpringMVC自动配置
这里摘录一段官方文档说明,再配上相应的源码
> 28.1.1 Spring MVC自动配置
Spring Boot为Spring MVC提供了自动配置,适用于大多数应用程序。
自动配置在Spring的默认值之上添加了以下功能:
包含ContentNegotiatingViewResolver和BeanNameViewResolver beans。
支持提供静态资源,包括对WebJars的支持( 本文档稍后介绍))。
自动注册Converter,GenericConverter和Formatter beans。
支持HttpMessageConverters( 本文档稍后部分)。
自动注册MessageCodesResolver( 本文档后面部分)。
静态index.html支持。
自定义Favicon支持(本文档稍后介绍)。
自动使用ConfigurableWebBindingInitializer bean(本文 后面会介绍)。
如果你想保留Spring Boot MVC功能,并且你想添加额外的 MVC配置(拦截器,格式化程序,视图控制器和其他功能),
你可以添加自己的@Configuration类WebMvcConfigurer类但没有 @EnableWebMvc。
如果您希望提供RequestMappingHandlerMapping,RequestMappingHandlerAdapter或ExceptionHandlerExceptionResolver的自定义实例
,则可以声明WebMvcRegistrationsAdapter实例以提供此类组件。
如果您想完全控制Spring MVC,可以添加自己的@Configuration注释@EnableWebMvc。
ContentNegotiatingViewResolver 组合所有的视图解析器,返回最优解
@Nullable
private View getBestView(List<View> candidateViews, List<MediaType> requestedMediaTypes, RequestAttributes attrs) {
for (View candidateView : candidateViews) {
if (candidateView instanceof SmartView) {
SmartView smartView = (SmartView) candidateView;
if (smartView.isRedirectView()) {
return candidateView;
}
}
}
@Override
protected void initServletContext(ServletContext servletContext) {
Collection<ViewResolver> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(obtainApplicationContext(), ViewResolver.class).values();
//初始化时加载所有的视图解析器。们可以自定义视图解析器,在容器加载时,也会被加载进来
//一个视图解析器实现ViewResolver接口,实现其方法 如下:
//@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;
// }
//随后debug启动,可在DispatchServlet类的doDispatch方法中,找到此解析器
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);
}
}
AnnotationAwareOrderComparator.sort(this.viewResolvers);
this.cnmFactoryBean.setServletContext(servletContext);
}
Converter:类型转换器 Formatter:格式化器
/**
* Add {@link GenericConverter}, {@link Converter}, {@link Printer}, {@link Parser}
* and {@link Formatter} beans from the specified context.
* @param registry the service to register beans with
* @param beanFactory the bean factory to get the beans from
* @since 2.2.0
*/
public static void addBeans(FormatterRegistry registry, ListableBeanFactory beanFactory) {
Set<Object> beans = new LinkedHashSet<>();
beans.addAll(beanFactory.getBeansOfType(GenericConverter.class).values());
beans.addAll(beanFactory.getBeansOfType(Converter.class).values());
beans.addAll(beanFactory.getBeansOfType(Printer.class).values());
beans.addAll(beanFactory.getBeansOfType(Parser.class).values());
for (Object bean : beans) {
if (bean instanceof GenericConverter) {
registry.addConverter((GenericConverter) bean);
}
else if (bean instanceof Converter) {
registry.addConverter((Converter<?, ?>) bean);
}
else if (bean instanceof Formatter) {
registry.addFormatter((Formatter<?>) bean);
}
else if (bean instanceof Printer) {
registry.addPrinter((Printer<?>) bean);
}
else if (bean instanceof Parser) {
registry.addParser((Parser<?>) bean);
}
}
}
// 同理,我们自己也可以自定义转换器,再添加到容器里
HttpMessageConverters:springmvc转换http请求和响应
public WebMvcAutoConfigurationAdapter(ResourceProperties resourceProperties, WebMvcProperties mvcProperties,
ListableBeanFactory beanFactory, ObjectProvider<HttpMessageConverters> messageConvertersProvider,
ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider,
ObjectProvider<DispatcherServletPath> dispatcherServletPath) {
this.resourceProperties = resourceProperties;
this.mvcProperties = mvcProperties;
this.beanFactory = beanFactory;
this.messageConvertersProvider = messageConvertersProvider;
this.resourceHandlerRegistrationCustomizer = resourceHandlerRegistrationCustomizerProvider.getIfAvailable();
this.dispatcherServletPath = dispatcherServletPath;
}
//HttpMessageConverters 有参构造方法,入参都是从容器取的。自定义与以上同理
MessageCodesResolver:定义错误代码生成规则
ConfigurableWebBindingInitializer:初始化web数据绑定器WebDataBinder
org.springframework.boot.autoconfigure.web:web的所有自动配置场景; 详细的大家可以自己看看
自动配置的场景太多,再次不一一列举,说的最清楚的应该是官方文档。
扩展springmvc,既保留了所有自动配置,也能用我们自定义的配置
编写一个配置类(@Configuration),实现接口WebMvcConfigurer,自定义拓展;不能标注@EnableWebMvc
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
//浏览器发送MyMvcConfig请求,来到index.html页面
registry.addViewController("/MyMvcConfig").setViewName("index");
}
}
原因分析:
WebMvcAutoConfiguration是SpringMVC的自动配置类
在做其他自动配置时会导入;@Import(EnableWebMvcConfiguration.class)
@Configuration(proxyBeanMethods = false)
public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware {
@Configuration(proxyBeanMethods = false)
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();
@Autowired(required = false)
public void setConfigurers(List<WebMvcConfigurer> configurers) {
if (!CollectionUtils.isEmpty(configurers)) {
this.configurers.addWebMvcConfigurers(configurers);
//一个参考实现:将所有的webmvc相关配置都一起添加到容器
//@Override
//public void addViewControllers(ViewControllerRegistry registry) {
// for (WebMvcConfigurer delegate : this.delegates) {
// delegate.addViewControllers(registry);
// }
//}
}
}
容器中所有的WebMvcConfigurer都会一起起作用
效果:SpringMVC的自动配置和我们的扩展配置都会起作用;
全面接管springmvc 加@EnableWebMvc 默认配置不启用,所有配置自己配,but why?
@Import(DelegatingWebMvcConfiguration.class)
public @interface EnableWebMvc {
}
@Configuration(proxyBeanMethods = false)
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
//看下面这个条件
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
然后我们应该掌握一些共同点
- SpringBoot在自动配置很多组件的时候,先看容器中有没有用户自己配置的(@Bean、@Component)如果有就用用户配置的,如果没有,才自动配置;如果有些组件可以有多个(ViewResolver)将用户配置的和自己默认的组合起来;
- 在SpringBoot中会有非常多的xxxConfigurer帮助我们进行扩展配置
- 在SpringBoot中会有很多的xxxCustomizer帮助我们进行定制配置
以上,说的有点乱,大家多看源码多看文档吧