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 ContentNegotiatingViewResolver and BeanNameViewResolver beans.
  • Support for serving static resources, including support for WebJars (covered later in this document)).
  • Automatic registration of Converter, GenericConverter, and Formatter beans.
  • Support for HttpMessageConverters (covered later in this document).
  • 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).

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

4.1.5、Servlet容器启动原理

4.2、使用外置的Servlet容器

4.2.1、使用步骤

4.2.2、原理

posted @ 2020-06-16 17:31  ciyelc  阅读(106)  评论(0)    收藏  举报