04-SpringBoot Web开发
一、介绍
1.1、spring-boot-starter-web
使用 Spring 框架除了开发少数的独立应用,大部分情况下实际上在使用 SpringMVC 开发 web 应用,为了简化快速搭建并开发一个 Web 项目,Spring Boot 提供了 spring-boot-starter-web 自动配置模块。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
只要将 spring-boot-starter-web 加入项目的 maven 依赖,就得到了一个直接可执行的 Web 应用,当前项目下运行mvn spring-boot:run就可以直接启动一个使用了嵌入式 tomcat 服务请求的 Web 应用。
1.2、SpringMVC默认配置
Spring Boot 自动配置好了SpringMVC,以下是SpringBoot对SpringMVC的默认配置:
- Inclusion of
ContentNegotiatingViewResolverandBeanNameViewResolverbeans.- Support for serving static resources, including support for WebJars (covered later in this document)).
- Automatic registration of
Converter,GenericConverter, andFormatterbeans.- Support for
HttpMessageConverters(covered later in this document).- Automatic registration of
MessageCodesResolver(covered later in this document).- Static
index.htmlsupport.- Custom
Faviconsupport (covered later in this document).- Automatic use of a
ConfigurableWebBindingInitializerbean (covered later in this document).
1.3、扩展SpringMVC
(1)、编写一个配置类
以前版本通过继承WebMvcConfigurerAdapter(过时)类来扩展功能,目前是使用WebMvcConfigurer接口扩展功能
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/hello").setViewName("hello");
}
}
(2)、扩展原理
WebMvcAutoConfiguration是SpringMVC的自动配置类,内部类WebMvcAutoConfigurationAdapter 也继承WebMvcConfigurerAdapter,实现具体细节,如addResourceHandlers()、dateFormatter()等方法。通过@Import(EnableWebMvcConfiguration.class)导入EnableWebMvcConfiguration类。
@Configuration
@ConditionalOnWebApplication
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
public class WebMvcAutoConfiguration {
//自动配置适配器也继承WebMvcConfigurerAdapter
@Configuration
//导入EnableWebMvcConfiguration类
@Import(EnableWebMvcConfiguration.class)
@EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })
public static class WebMvcAutoConfigurationAdapter extends WebMvcConfigurerAdapter {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
//静态资源映射具体实现
......
}
//内部类EnableWebMvcConfiguration
@Configuration
public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration {
......//实现细节
}
EnableWebMvcConfiguration 类setConfigurers()方法中通过 @Autowired自动注入从容器中获取所有WebMvcConfigurer对象,之后调用WebMvcConfigurerComposite对象的addWebMvcConfigurers()方法将从容器中获取的WebMvcConfigurer对象集合放入delegates 集合中。之后通过实现WebMvcConfigurer的方法遍历delegates集合返回相应的配置类。
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);
}
}
}
class WebMvcConfigurerComposite implements WebMvcConfigurer {
private final List<WebMvcConfigurer> delegates = new ArrayList();
public void addWebMvcConfigurers(List<WebMvcConfigurer> configurers) {
if (!CollectionUtils.isEmpty(configurers)) {
this.delegates.addAll(configurers);
}
}
public void addViewControllers(ViewControllerRegistry registry) {
Iterator var2 = this.delegates.iterator();
while(var2.hasNext()) {
WebMvcConfigurer delegate = (WebMvcConfigurer)var2.next();
delegate.addViewControllers(registry);
}
}
}
1.4、接管SpringMVC
如果要完全自己配置SpringMVC,我只要在我们的配置文件上添加@EnableWebMvc,就可以做到完全托管。
(1)、编写配置类
@Configuration
@EnableWebMvc
public class MyMvcConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/hello").setViewName("hello");
}
}
(2)、原理
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class})
@ConditionalOnMissingBean({WebMvcConfigurationSupport.class}) //这里
@AutoConfigureOrder(-2147483638)
@AutoConfigureAfter({DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class, ValidationAutoConfiguration.class})
public class WebMvcAutoConfiguration {}
WebMvcAutoConfiguration将WebMvcConfigurationSupport组件导入进来。
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({DelegatingWebMvcConfiguration.class})
public @interface EnableWebMvc {}
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {}
@EnableWebMvc有人会将将WebMvcConfigurationSupport组件导入进来。导致了WebMvcAutoConfiguration的失效。
二、静态资源映射规则
2.1、传统资源访问
!!!!
2.2、SpringBoot资源访问
项目结构层面与传统打包为 war 的 Java Web 应用的差异在于,静态文件和页面模板的存放位置变了,原来是放在 src/main/webapp 目录下的一系列资源,现在都统一放在 src/main/resources 相应子目录下,比如:
- src/main/resources/static 用于存放各类静态资源,比如 css,js 等。
- src/main/resources/templates 用于存放模板文件,比如 *.vm。
(1)、"/**" 访问当前项目的任何资源,都去(静态资源的文件夹)找映射
"classpath:/META-INF/resources/",
"classpath:/resources/",
"classpath:/static/",
"classpath:/public/"
"/":当前项目的根路径
原理
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
if (!this.resourceProperties.isAddMappings()) {
logger.debug("Default resource handling disabled");
return;
}
Integer cachePeriod = this.resourceProperties.getCachePeriod();
if (!registry.hasMappingForPattern("/webjars/**")) {
customizeResourceHandlerRegistration(
registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/")
.setCachePeriod(cachePeriod));
}
//静态资源文件夹映射
String staticPathPattern = this.mvcProperties.getStaticPathPattern();
if (!registry.hasMappingForPattern(staticPathPattern)) {
customizeResourceHandlerRegistration(
registry.addResourceHandler(staticPathPattern)
.addResourceLocations(this.resourceProperties.getStaticLocations())
.setCachePeriod(cachePeriod));
}
}
public class WebMvcProperties {
private String staticPathPattern;
public WebMvcProperties() {
this.staticPathPattern = "/**";
}
}
public class ResourceProperties {
private static final String[] CLASSPATH_RESOURCE_LOCATIONS = new String[]{"classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/"};
}
三、错误机制
Spring Boot官方文档的:
For machine clients it will produce a JSON response with details of the error, the HTTP status and the exception message. For browser clients there is a ‘whitelabel’ error view that renders the same data in HTML format
当发生异常时:
- 如果请求是从浏览器发送出来的,那么返回一个
Whitelabel Error Page - 如果请求是从machine客户端发送出来的,那么会返回相同信息的
json
3.1、错误机制原理
(1)、ErrorMvcAutoConfiguration错误处理的自动配置,给容器中添加了以下组件
- DefaultErrorAttributes
- BasicErrorController
- ErrorPageCustomizer
- DefaultErrorViewResolver
public class ErrorMvcAutoConfiguration {
@Bean
@ConditionalOnMissingBean(value = {ErrorAttributes.class},search = SearchStrategy.CURRENT)
public DefaultErrorAttributes errorAttributes() {
return new DefaultErrorAttributes();
}
@Bean
@ConditionalOnMissingBean(value = {ErrorController.class},search = SearchStrategy.CURRENT)
public BasicErrorController basicErrorController(
ErrorAttributes errorAttributes, ObjectProvider<ErrorViewResolver> errorViewResolvers) {
return new BasicErrorController(
errorAttributes, this.serverProperties.getError(),(List)errorViewResolvers
.orderedStream()
.collect(Collectors.toList()));
}
@Bean
public ErrorMvcAutoConfiguration.ErrorPageCustomizer errorPageCustomizer(
DispatcherServletPath dispatcherServletPath) {
return new ErrorMvcAutoConfiguration.ErrorPageCustomizer(
this.serverProperties, dispatcherServletPath);
}
static class ErrorPageCustomizer implements ErrorPageRegistrar, Ordered {
public void registerErrorPages(ErrorPageRegistry errorPageRegistry) {
ErrorPage errorPage = new ErrorPage(
this.dispatcherServletPath.getRelativePath(this.properties.getError().getPath()));
errorPageRegistry.addErrorPages(new ErrorPage[]{errorPage});
}
}
@Configuration(proxyBeanMethods = false)
static class DefaultErrorViewResolverConfiguration {
@Bean
@ConditionalOnBean({DispatcherServlet.class})
@ConditionalOnMissingBean({ErrorViewResolver.class})
DefaultErrorViewResolver conventionErrorViewResolver() {
return new DefaultErrorViewResolver(this.applicationContext, this.resourceProperties);
}
}
}
(2)、执行流程
一但系统出现4xx或者5xx之类的错误,ErrorPageCustomizer就会生效(定制错误的响应规则)/error请求;就会被BasicErrorController处理;
3.1、自定义错误页面
如果要自定义HTML错误页,可以将文件添加到/error目录。文件的名称应该是确切的状态代码或序列掩码。
示例一:无模板引擎映射404
src/
+- main/
+- java/
| + <source code>
+- resources/
+- public/
+- error/
| +- 404.html
+- <other public assets>
示例二:FreeMarker模板引擎映射5xx
src/
+- main/
+- java/
| + <source code>
+- resources/
+- templates/
+- error/
| +- 5xx.ftlh
+- <other templates>
添加实现ErrorViewResolver接口
public class MyErrorViewResolver implements ErrorViewResolver {
@Override
public ModelAndView resolveErrorView(HttpServletRequest request,
HttpStatus status, Map<String, Object> model) {
// Use the request or status to optionally return a ModelAndView
return ...
}
}
3.2、自定义json数据
(1)、自定义异常处理&返回定制json数据
@ControllerAdvice
public class MyExceptionHandler {
@ResponseBody
@ExceptionHandler(UserNotExistException.class)
public Map<String,Object> handleException(Exception e){
Map<String,Object> map = new HashMap<>();
map.put("code","user.notexist");
map.put("message",e.getMessage());
return map;
}
}
//没有自适应效果...
(2)、转发到/error进行自适应响应效果处理
@ExceptionHandler(UserNotExistException.class)
public String handleException(Exception e, HttpServletRequest request){
Map<String,Object> map = new HashMap<>();
//传入我们自己的错误状态码 4xx 5xx,否则就不会进入定制错误页面的解析流程
request.setAttribute("javax.servlet.error.status_code",500);
map.put("code","user.notexist");
map.put("message",e.getMessage());
return "forward:/error";
}
出现错误以后,会来到/error请求,会被BasicErrorController处理,响应出去可以获取的数据是由getErrorAttributes得到的(是AbstractErrorController(ErrorController)规定的方法);
1、完全来编写一个ErrorController的实现类【或者是编写AbstractErrorController的子类】,放在容器中;
2、页面上能用的数据,或者是json返回能用的数据都是通过errorAttributes.getErrorAttributes得到;
容器中DefaultErrorAttributes.getErrorAttributes();默认进行数据处理的;
自定义ErrorAttributes
//给容器中加入我们自己定义的ErrorAttributes
@Component
public class MyErrorAttributes extends DefaultErrorAttributes {
@Override
public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes, boolean includeStackTrace) {
Map<String, Object> map = super.getErrorAttributes(requestAttributes, includeStackTrace);
map.put("company","atguigu");
return map;
}
}
最终的效果:响应是自适应的,可以通过定制ErrorAttributes改变需要返回的内容,
四、Servlet容器
4.1、配置嵌入式Servlet容器

4.1.1、修改Servlet容器配置
(1)、修改和server有关的配置
//通用的Servlet容器设置
server.xxx
server.port=8081
server.context-path=/crud
//Tomcat的设置
server.tomcat.xxx
server.tomcat.uri-encoding=UTF-8
(2)、EmbeddedServletContainerCustomizer
@Bean //定制器加入到容器中
public EmbeddedServletContainerCustomizer embeddedServletContainerCustomizer(){
return new EmbeddedServletContainerCustomizer() {
//定制嵌入式的Servlet容器相关的规则
@Override
public void customize(ConfigurableEmbeddedServletContainer container) {
container.setPort(8083);
}
};
}
4.1.2、注册Servlet三大组件
(1)、ServletRegistrationBean
SpringBoot自动(DispatcherServletAutoConfiguration)注册SpringMVC的前端控制器(DIspatcherServlet)。
//注册三大组件
@Bean
public ServletRegistrationBean myServlet(){
ServletRegistrationBean registrationBean = new ServletRegistrationBean(new MyServlet(),"/myServlet");
return registrationBean;
}
(2)、FilterRegistrationBean
@Bean
public FilterRegistrationBean myFilter(){
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
registrationBean.setFilter(new MyFilter());
registrationBean.setUrlPatterns(Arrays.asList("/hello","/myServlet"));
return registrationBean;
}
(3)、ServletListenerRegistrationBean
@Bean
public ServletListenerRegistrationBean myListener(){
ServletListenerRegistrationBean<MyListener> registrationBean = new ServletListenerRegistrationBean<>(new MyListener());
return registrationBean;
}
4.1.3、替换Servlet容器
(1)、替换为jetty
<!-- 引入web模块 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<artifactId>spring-boot-starter-tomcat</artifactId>
<groupId>org.springframework.boot</groupId>
</exclusion>
</exclusions>
</dependency>
<!--引入其他的Servlet容器-->
<dependency>
<artifactId>spring-boot-starter-jetty</artifactId>
<groupId>org.springframework.boot</groupId>
</dependency>
(2)、替换为undertow
<!-- 引入web模块 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<artifactId>spring-boot-starter-tomcat</artifactId>
<groupId>org.springframework.boot</groupId>
</exclusion>
</exclusions>
</dependency>
<!--引入其他的Servlet容器-->
<dependency>
<artifactId>spring-boot-starter-undertow</artifactId>
<groupId>org.springframework.boot</groupId>
</dependency>
4.1.4、Servlet容器自动配置原理
(1)、嵌入式servlet容器自动配置类
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration
@ConditionalOnWebApplication //如果是web应用,当前配置类生效
@Import(BeanPostProcessorsRegistrar.class) //Bean后置处理器的注册器;给容器中注入一些组件//导入了EmbeddedServletContainerCustomizerBeanPostProcessor//后置处理器功能:在bean初始化前后(刚创建完对象,还没属性赋值)执行初始化工作
public class EmbeddedServletContainerAutoConfiguration {
/**
* Nested configuration if Tomcat is being used.
*/
@Configuration
@ConditionalOnClass({ Servlet.class, Tomcat.class }) //判断当前是否引入了Tomcat依赖;
@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT) //判断当前容器没有用户自定义的EmbeddedServletContainerFactory
public static class EmbeddedTomcat {
@Bean
public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory() {
return new TomcatEmbeddedServletContainerFactory();
}
}
}
//嵌入式servlet容器工厂
public interface EmbeddedServletContainerFactory {
//获取嵌入式的Servlet容器
EmbeddedServletContainer getEmbeddedServletContainer(ServletContextInitializer... initializers);
}
(2)、TomcatEmbeddedServletContainerFactory

浙公网安备 33010602011771号