SpringBoot知识点总结

目录:

目录

目录:

一、SpringBoot特点

1.1 依赖管理

 1.2 自动配置

二、容器功能:

2.1 @Configuration  添加组件

2.2 @Import 

 2.3 @Conditional条件装配

2.4 @ImportResource导入Spring配置文件

2.5 @ConfigurationProperties配置绑定

三、自动配置原理

3.1 引导加载自动配置类

3.2 按需开启自动配置

四、web开发

4.1 静态资源

4.2 静态资源 源码分析

4.3 请求处理 Rest映射 源码分析

4.4 请求处理 请求映射 源码分析

4.5 请求处理-常用参数注解使用

4.6 请求处理-【源码分析】-各种类型参数解析原理

4.7 响应处理

​ 五、拦截器

5.1 流程

 5.2 原理

六、文件上传

6.1 流程

6.2 原理

七、异常处理

7.1 默认规则

7.2 自动配置类

 7.3 源码

7.4 几种异常处理原理

八、web原生组件的注入

8.1 原生注解

 ​

8.2 使用RegistrationBean

九、嵌入式web容器

9.1 简介

 

一、SpringBoot特点

1.1 依赖管理

springboot的父项目:

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.4</version>
    </parent>

springboot的父项目的父项目:

  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-dependencies</artifactId>
    <version>2.5.4</version>
  </parent>

点进去以后所有常见的依赖都已经被添加:

  <properties>
    <activemq.version>5.16.3</activemq.version>
    <antlr2.version>2.7.7</antlr2.version>
    <appengine-sdk.version>1.9.91</appengine-sdk.version>
    <artemis.version>2.17.0</artemis.version>
    <aspectj.version>1.9.7</aspectj.version>
    <assertj.version>3.19.0</assertj.version>
    <atomikos.version>4.0.6</atomikos.version>
    <awaitility.version>4.0.3</awaitility.version>
    <build-helper-maven-plugin.version>3.2.0</build-helper-maven-plugin.version>
    <byte-buddy.version>1.10.22</byte-buddy.version>
    <caffeine.version>2.9.2</caffeine.version>
    <cassandra-driver.version>4.11.3</cassandra-driver.version>
    .............
  </properties>

 若所需的jar包的版本与springboot默认的不一致,例如mysql

    <properties>
        <mysql.version>5.1.43</mysql.version>
    </properties>

只要引入starter,这个场景的所有常规需要的依赖都会自动引入 ,你也可以开发自己的starter

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

 1.2 自动配置

自动配好tomcat

引入spring-boot-starter-web开发场景后,默认引入了tomcat,并且自动配置了tomcat

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-tomcat</artifactId>
      <version>2.5.4</version>
      <scope>compile</scope>
    </dependency>

引入springmvc全套组件,自动配置好了springmvc常用功能,例如dispatchservlet,乱码问题,

文件上传

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>5.3.9</version>
      <scope>compile</scope>
    </dependency>
@SpringBootApplication
public class DemoApplication {


    public static void main(String[] args) {
        //1、返回IOC容器
        ConfigurableApplicationContext run = SpringApplication.run(DemoApplication.class, args);
        //2、查看里面的组件
        String[] names = run.getBeanDefinitionNames();
        for(String name:names){
            System.out.println(name);
        }
        //org.springframework.context.annotation.internalConfigurationAnnotationProcessor
        //org.springframework.context.annotation.internalAutowiredAnnotationProcessor
        //org.springframework.context.annotation.internalCommonAnnotationProcessor
        //org.springframework.context.event.internalEventListenerProcessor
        //org.springframework.context.event.internalEventListenerFactory
        //demoApplication
        //org.springframework.boot.autoconfigure.internalCachingMetadataReaderFactory
        //org.springframework.boot.autoconfigure.AutoConfigurationPackages
        //org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration
        //propertySourcesPlaceholderConfigurer
        //...........
    }

}

默认的包扫描规则:主程序所在包及其所有子包 里面的组件都会被默认扫描进来

例如controller

 有效

 无效

如非要那样写还想访问的到

@SpringBootApplication(scanBasePackages = "com")
public class DemoApplication {
    public static void main(String[] args) { 
        SpringApplication.run(DemoApplication.class, args);
    }
}

各种配置有默认值:

可以通过application.properties文件改变,最终都会映射到某个类上去

按需加载所有配置:

引入web开发场景,只会加载web开发场景,而不会加载其它的开发场景,例如数据库

所有的自动配置都在spring-boot-autoconfigure包里面

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-autoconfigure</artifactId>
      <version>2.5.4</version>
      <scope>compile</scope>
    </dependency>

二、容器功能:

2.1 @Configuration  添加组件

/**
 * 1、配置类里面使用@Bean标注在方法上给容器注册组件,默认也是单实例的
 * 2、配置类本身也是组件
 * 3、proxyBeanMethods:代理bean的方法
 *      Full(proxyBeanMethods = true)(保证每个@Bean方法被调用多少次返回的组件都是单实例的)
        //每次调用都会检查所依赖的组件是否存在,不存在就会报错
(默认)
 *      Lite(proxyBeanMethods = false)(每个@Bean方法被调用多少次返回的组件都是新创建的)
        //不会检查所依赖的组件是否存在,提高了启动的过程
 */
@Configuration(proxyBeanMethods = false) 
//为true则用户中的Pet就是注册的Pet,为false则用户中的Pet是新创建的
public class MyConfig {

    /**
     * Full:外部无论对配置类中的这个组件注册方法调用多少次获取的都是之前注册容器中的单实例对象
     * @return
     */
    @Bean //给容器中添加组件。以方法名作为组件的id。返回类型就是组件类型。返回的值,就是组件在容器中的实例
    public User user01(){
        User zhangsan = new User("zhangsan", 18);
        //user组件依赖了Pet组件
        zhangsan.setPet(tomcatPet());
        return zhangsan;
    }

    @Bean("tom")
    public Pet tomcatPet(){
        return new Pet("tomcat");
    }
}

测试:

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan("com.atguigu.boot")
public class MainApplication {

    public static void main(String[] args) {
    //1、返回我们IOC容器
        ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);

    //2、查看容器里面的组件
        String[] names = run.getBeanDefinitionNames();
        for (String name : names) {
            System.out.println(name);
        }

    //3、从容器中获取组件
        Pet tom01 = run.getBean("tom", Pet.class);
        Pet tom02 = run.getBean("tom", Pet.class);
        System.out.println("组件:"+(tom01 == tom02));

    //4、com.atguigu.boot.config.MyConfig$$EnhancerBySpringCGLIB$$51f1e1ca@1654a892
        MyConfig bean = run.getBean(MyConfig.class);
        System.out.println(bean);

    //如果@Configuration(proxyBeanMethods = true)代理对象调用方法。SpringBoot总会检查这个组件是否在容器中有。
        //保持组件单实例
        User user = bean.user01();
        User user1 = bean.user01();
        System.out.println(user == user1);

        User user01 = run.getBean("user01", User.class);
        Pet tom = run.getBean("tom", Pet.class);

        System.out.println("用户的宠物:"+(user01.getPet() == tom));
    }
}

 最佳实战:

  • 配置 类组件之间无依赖关系用Lite模式加速容器启动过程,减少判断
  • 配置 类组件之间有依赖关系,方法会被调用得到之前单实例组件,用Full模式(默认)

主配置类

@SpringBootApplication(scanBasePackages = "com")
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

默认会扫描相应的包,获取@Bean、@Component、@Controller、@Service、@Repository

为注解的类 

2.2 @Import 

给容器中自动创建出这两个类型的组件、默认组件的名字就是全类名

@Import({User.class, DBHelper.class})
@Configuration(proxyBeanMethods = false) //告诉SpringBoot这是一个配置类 == 配置文件
public class MyConfig {
}

测试:


//1、返回我们IOC容器
ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);

//...

//5、获取组件
String[] beanNamesForType = run.getBeanNamesForType(User.class);

for (String s : beanNamesForType) {
    System.out.println(s);
}

DBHelper bean1 = run.getBean(DBHelper.class);
System.out.println(bean1);

 2.3 @Conditional条件装配

条件装配:满足Conditional指定的条件,则进行组件注入

在这里插入图片描述

 用@ConditionalOnMissingBean举例说明

@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(name = "tom")//没有tom名字的Bean时,MyConfig类的Bean才能生效。
public class MyConfig {

    @Bean
    public User user01(){
        User zhangsan = new User("zhangsan", 18);
        zhangsan.setPet(tomcatPet());
        return zhangsan;
    }

    @Bean("tom22")
    public Pet tomcatPet(){
        return new Pet("tomcat");
    }
}

public static void main(String[] args) {
    //1、返回我们IOC容器
    ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);

    //2、查看容器里面的组件
    String[] names = run.getBeanDefinitionNames();
    for (String name : names) {
        System.out.println(name);
    }

    boolean tom = run.containsBean("tom");
    System.out.println("容器中Tom组件:"+tom);//false

    boolean user01 = run.containsBean("user01");
    System.out.println("容器中user01组件:"+user01);//true

    boolean tom22 = run.containsBean("tom22");
    System.out.println("容器中tom22组件:"+tom22);//true

}

2.4 @ImportResource导入Spring配置文件

比如,公司使用bean.xml文件生成配置bean,然而你为了省事,想继续复用bean.xml,就可以用@ImportResource

bean.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans ...">

    <bean id="haha" class="com.lun.boot.bean.User">
        <property name="name" value="zhangsan"></property>
        <property name="age" value="18"></property>
    </bean>

    <bean id="hehe" class="com.lun.boot.bean.Pet">
        <property name="name" value="tomcat"></property>
    </bean>
</beans>
@ImportResource("classpath:beans.xml")
public class MyConfig {
...
}
public static void main(String[] args) {
    //1、返回我们IOC容器
    ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);

	boolean haha = run.containsBean("haha");
	boolean hehe = run.containsBean("hehe");
	System.out.println("haha:"+haha);//true
	System.out.println("hehe:"+hehe);//true
}

2.5 @ConfigurationProperties配置绑定

如何使用Java读取到properties文件中的内容,并且把它封装到JavaBean中,以供随时使用

传统方法:

public class getProperties {
     public static void main(String[] args) throws FileNotFoundException, IOException {
         Properties pps = new Properties();
         pps.load(new FileInputStream("a.properties"));
         Enumeration enum1 = pps.propertyNames();//得到配置文件的名字
         while(enum1.hasMoreElements()) {
             String strKey = (String) enum1.nextElement();
             String strValue = pps.getProperty(strKey);
             System.out.println(strKey + "=" + strValue);
             //封装到JavaBean。
         }
     }
 }

Spring Boot一种配置配置绑定:

@ConfigurationProperties + @Component

假设有配置文件application.properties

mycar.brand=BYD
mycar.price=100000

只有在容器中的组件,才会拥有SpringBoot提供的强大功能

@Component
@ConfigurationProperties(prefix = "mycar")
@Data
public class Car {
    private String brand;
    private Integer price;
}

Spring Boot另一种配置配置绑定:

外部的jar包不可能添加@Component注解所以便有了以下注解

@EnableConfigurationProperties + @ConfigurationProperties

  1. 开启Car配置绑定功能
  2. 把这个Car这个组件自动注册到容器中
@EnableConfigurationProperties(Car.class)
public class MyConfig {
...
}
@ConfigurationProperties(prefix = "mycar")
public class Car {
...
}

三、自动配置原理

3.1 引导加载自动配置类

@SpringBootApplication相当于下面三个核心注解

@SpringBootConfiguration

@EnableAutoConfiguration

@ComponentScan

SpringBootApplication.class

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
    excludeFilters = {@Filter(
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {AutoConfigurationExcludeFilter.class}
)}
)

 SpringBootConfiguration.class

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
//代表这是一个配置类,所以main程序就是一个配置类
@Indexed
public @interface SpringBootConfiguration {
    @AliasFor(
        annotation = Configuration.class
    )
    boolean proxyBeanMethods() default true;
}

 EnableAutoConfiguration.class

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

    Class<?>[] exclude() default {};

    String[] excludeName() default {};
}

核心:

AutoConfigurationPackage

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({Registrar.class})
public @interface AutoConfigurationPackage {
    String[] basePackages() default {};

    Class<?>[] basePackageClasses() default {};
}
//利用Registrar给容器中导入一系列组件
//将指定的一个包下的所有组件都导入进来,即Main程序所在包下的所有组件

 其次:@Import({AutoConfigurationImportSelector.class})

    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        } else {
            AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
            return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
        }
    }
//指定了导入的默认的127个组件
//利用工厂加载
//从spring-boot-autoconfigure的jar包的META-INF文件夹下spring.factories导入资源

spring.factories

# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener

# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer

# Environment Post Processors
org.springframework.boot.env.EnvironmentPostProcessor=\
org.springframework.boot.autoconfigure.integration.IntegrationPropertiesEnvironmentPostProcessor

# Auto Configuration Import Listeners
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener

# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnBeanCondition,\
org.springframework.boot.autoconfigure.condition.OnClassCondition,\
org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition
.........
.........

但这127个组件不一定全部生效

3.2 按需开启自动配置

xxx-AutoConfiguration相当于各种自动配置类

例如aop技术

AopAutoConfiguration

@Configuration(
    proxyBeanMethods = false
)
@ConditionalOnProperty(
    prefix = "spring.aop",
    name = {"auto"},
    havingValue = "true",
    matchIfMissing = true
)
//只有配置文件中存在spring.aop.auto且值为true
//matchIfMissing = true表示没有配置也默认生效,相当于默认开启
public class AopAutoConfiguration {
    public AopAutoConfiguration() {
    }
 @Configuration(
        proxyBeanMethods = false
    )
    @ConditionalOnMissingClass({"org.aspectj.weaver.Advice"})
    @ConditionalOnProperty(
        prefix = "spring.aop",
        name = {"proxy-target-class"},
        havingValue = "true",
        matchIfMissing = true
    )
//只有org.aspectj.weaver.Advice这个类不存在时下面的类才生效
//matchIfMissing = true表示没有配置也默认生效,相当于默认开启
    @ConditionalOnClass({Advice.class})
//只有存在Advice.class时下面的类才生效
    static class AspectJAutoProxyingConfiguration {
        AspectJAutoProxyingConfiguration() {
        }

        @Configuration(
            proxyBeanMethods = false
        )
        @EnableAspectJAutoProxy(
            proxyTargetClass = true
        )
        @ConditionalOnProperty(
            prefix = "spring.aop",
            name = {"proxy-target-class"},
            havingValue = "true",
            matchIfMissing = true
        )

加了

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
        </dependency>

ctrl+f9生效

四、web开发

4.1 静态资源

静态资源目录:

类路径(resources)下的static、public、resources、META-INF/resources

原理:请求进来,先去找Controller看能不能处理,不能处理的所有请求又全部交给静态资源处理器,静态资源找不到就报404

指定静态资源访问前缀:

spring:
  mvc:
    static-path-pattern: /res/**

 指定静态资源目录:

spring:
  mvc:
    static-path-pattern: classpath:/a
#一旦指定其它默认的静态资源目录就失效

欢迎页:

1、静态资源路径下index.html

2、controller能处理/index

4.2 静态资源 源码分析

WebMvcAutoConfiguration.java  (autoconfiguration里面)

1、是否生效

@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)

生效

2、给容器中配了什么

@Configuration(proxyBeanMethods = false)
	@Import(EnableWebMvcConfiguration.class)
	@EnableConfigurationProperties({ WebMvcProperties.class,
			org.springframework.boot.autoconfigure.web.ResourceProperties.class, WebProperties.class })
	@Order(0)
	public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer, ServletContextAware {

 @EnableConfigurationProperties说明一些属性绑定了配置文件 

WebMvcProperties.class

@ConfigurationProperties(prefix = "spring.mvc")

跟spring.mvc前缀相关

org.springframework.boot.autoconfigure.web.ResourceProperties.class

@ConfigurationProperties(prefix = "spring.resources", ignoreUnknownFields = false)

 WebProperties.class

@ConfigurationProperties("spring.web")
有参构造器所有参数的值都会从容器中确定
public WebMvcAutoConfigurationAdapter(WebProperties webProperties, WebMvcProperties mvcProperties,
		ListableBeanFactory beanFactory, ObjectProvider<HttpMessageConverters> messageConvertersProvider,
		ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider,
		ObjectProvider<DispatcherServletPath> dispatcherServletPath,
		ObjectProvider<ServletRegistrationBean<?>> servletRegistrations) {
	this.mvcProperties = mvcProperties;
	this.beanFactory = beanFactory;
	this.messageConvertersProvider = messageConvertersProvider;
	this.resourceHandlerRegistrationCustomizer = resourceHandlerRegistrationCustomizerProvider.getIfAvailable();
	this.dispatcherServletPath = dispatcherServletPath;
	this.servletRegistrations = servletRegistrations;
	this.mvcProperties.checkConfiguration();
}
  • ResourceProperties resourceProperties;获取和spring.resources绑定的所有的值的对象
  • WebMvcProperties mvcProperties 获取和spring.mvc绑定的所有的值的对象
  • ListableBeanFactory beanFactory Spring的beanFactory
  • HttpMessageConverters 找到所有的HttpMessageConverters
  • ResourceHandlerRegistrationCustomizer 找到 资源处理器的自定义器。
  • DispatcherServletPath
  • ServletRegistrationBean 给应用注册Servlet、Filter…

资源处理的默认规则 

		public void addResourceHandlers(ResourceHandlerRegistry registry) {
			if (!this.resourceProperties.isAddMappings()) {
				logger.debug("Default resource handling disabled");
				return;
			}
			addResourceHandler(registry, "/webjars/**", "classpath:/META-INF/resources/webjars/");
			addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> {
				registration.addResourceLocations(this.resourceProperties.getStaticLocations());
				if (this.servletContext != null) {
					ServletContextResource resource = new ServletContextResource(this.servletContext, SERVLET_LOCATION);
					registration.addResourceLocations(resource);
				}
			});
		}
spring:
  web:
    resources:
      add-mappings: false
#若配置false 
#则走下面的代码部分
    if (!this.resourceProperties.isAddMappings()) {
    logger.debug("Default resource handling disabled");
    return;
  }
#所有得静态资源规则将失效
addResourceHandler(registry, "/webjars/**", "classpath:/META-INF/resources/webjars/");
//可以访问jar包下的一些资源

静态资源规则:

@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 class WebMvcAutoConfiguration {
    ...
	public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware {
        ...
		@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;
		}
    

 WelcomePageHandlerMapping的构造方法如下:

WelcomePageHandlerMapping(TemplateAvailabilityProviders templateAvailabilityProviders,
                          ApplicationContext applicationContext, Resource welcomePage, String staticPathPattern) {
    if (welcomePage != null && "/**".equals(staticPathPattern)) {
        //要用欢迎页功能,必须是/**
        logger.info("Adding welcome page: " + welcomePage);
        setRootViewName("forward:index.html");
    }
    else if (welcomeTemplateExists(templateAvailabilityProviders, applicationContext)) {
        //调用Controller /index
        logger.info("Adding welcome page template: index");
        setRootViewName("index");
    }
}

4.3 请求处理 Rest映射 源码分析

只有表单提交才会走下面的流程

Rest风格

spring:
  mvc:
    hiddenmethod:
      filter:
        enabled: true   #开启页面表单的Rest功能
@GetMapping("/user")
//@RequestMapping(value = "/user",method = RequestMethod.GET)
public String getUser(){
    return "GET-张三";
}

@PostMapping("/user")
//@RequestMapping(value = "/user",method = RequestMethod.POST)
public String saveUser(){
    return "POST-张三";
}

@PutMapping("/user")
//@RequestMapping(value = "/user",method = RequestMethod.PUT)
public String putUser(){
    return "PUT-张三";
}

@DeleteMapping("/user")
//@RequestMapping(value = "/user",method = RequestMethod.DELETE)
public String deleteUser(){
    return "DELETE-张三";
}
<form action="/user" method="get">
    <input value="REST-GET提交" type="submit" />
</form>

<form action="/user" method="post">
    <input value="REST-POST提交" type="submit" />
</form>

<form action="/user" method="post">
    <input name="_method" type="hidden" value="DELETE"/>
    <input value="REST-DELETE 提交" type="submit"/>
</form>

<form action="/user" method="post">
    <input name="_method" type="hidden" value="PUT" />
    <input value="REST-PUT提交"type="submit" />
<form>
  • 表单提交会带上\_method=PUT
  • 请求过来被HiddenHttpMethodFilter拦截
  • 请求是否正常,并且是POST
  • 获取到\_method的值。(全部转为大写)
  • 兼容以下请求;PUT   DELETE    PATCH
  • 原生request(post),包装模式requesWrapper重写了getMethod方法,返回的是传入的值。
  • 过滤器链放行的时候用wrapper。以后的方法调用getMethod是调用requesWrapper的。

从WebMvcAutoConfiguration.java里面debug出来的

public class HiddenHttpMethodFilter extends OncePerRequestFilter {

	private static final List<String> ALLOWED_METHODS =
			Collections.unmodifiableList(Arrays.asList(HttpMethod.PUT.name(),
					HttpMethod.DELETE.name(), HttpMethod.PATCH.name()));

	/** Default method parameter: {@code _method}. */
	public static final String DEFAULT_METHOD_PARAM = "_method";

	private String methodParam = DEFAULT_METHOD_PARAM;


	/**
	 * Set the parameter name to look for HTTP methods.
	 * @see #DEFAULT_METHOD_PARAM
	 */
	public void setMethodParam(String methodParam) {
		Assert.hasText(methodParam, "'methodParam' must not be empty");
		this.methodParam = methodParam;
	}

	@Override
	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {

		HttpServletRequest requestToUse = request;

		if ("POST".equals(request.getMethod()) && request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) == null) {
			String paramValue = request.getParameter(this.methodParam);
			if (StringUtils.hasLength(paramValue)) {
				String method = paramValue.toUpperCase(Locale.ENGLISH);
				if (ALLOWED_METHODS.contains(method)) {
					requestToUse = new HttpMethodRequestWrapper(request, method);
				}
			}
		}

		filterChain.doFilter(requestToUse, response);
	}


	/**
	 * Simple {@link HttpServletRequest} wrapper that returns the supplied method for
	 * {@link HttpServletRequest#getMethod()}.
	 */
	private static class HttpMethodRequestWrapper extends HttpServletRequestWrapper {

		private final String method;

		public HttpMethodRequestWrapper(HttpServletRequest request, String method) {
			super(request);
			this.method = method;
		}

		@Override
		public String getMethod() {
			return this.method;
		}
	}

}

4.4 请求处理 请求映射 源码分析

在这里插入图片描述

doService()核心调用doDispatch()

        try {
            this.doDispatch(request, response);
        } finally {
            if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted() && attributesSnapshot != null) {
                this.restoreAttributesAfterInclude(request, attributesSnapshot);
            }

            if (this.parseRequestPath) {
                ServletRequestPathUtils.setParsedRequestPath(previousRequestPath, request);
            }

        }

 研究doDispatch()即可  以后调试可以在这里打断点

	protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
		HttpServletRequest processedRequest = request;
		HandlerExecutionChain mappedHandler = null;
		boolean multipartRequestParsed = false;

		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

		try {
			ModelAndView mv = null;
			Exception dispatchException = null;

			try {
				processedRequest = checkMultipart(request);
				multipartRequestParsed = (processedRequest != request);

				// Determine handler for the current request.
				mappedHandler = getHandler(processedRequest);
				if (mappedHandler == null) {
					noHandlerFound(processedRequest, response);
					return;
				}

				// Determine handler adapter for the current request.
				HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

				// Process last-modified header, if supported by the handler.
				String method = request.getMethod();
				boolean isGet = HttpMethod.GET.matches(method);
				if (isGet || HttpMethod.HEAD.matches(method)) {
					long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
					if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
						return;
					}
				}

				if (!mappedHandler.applyPreHandle(processedRequest, response)) {
					return;
				}

				// Actually invoke the handler.
				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

				if (asyncManager.isConcurrentHandlingStarted()) {
					return;
				}

				applyDefaultViewName(processedRequest, mv);
				mappedHandler.applyPostHandle(processedRequest, response, mv);
			}
			catch (Exception ex) {
				dispatchException = ex;
			}
			catch (Throwable err) {
				// As of 4.3, we're processing Errors thrown from handler methods as well,
				// making them available for @ExceptionHandler methods and other scenarios.
				dispatchException = new NestedServletException("Handler dispatch failed", err);
			}
			processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
		}
		catch (Exception ex) {
			triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
		}
		catch (Throwable err) {
			triggerAfterCompletion(processedRequest, response, mappedHandler,
					new NestedServletException("Handler processing failed", err));
		}
		finally {
			if (asyncManager.isConcurrentHandlingStarted()) {
				// Instead of postHandle and afterCompletion
				if (mappedHandler != null) {
					mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
				}
			}
			else {
				// Clean up any resources used by a multipart request.
				if (multipartRequestParsed) {
					cleanupMultipart(processedRequest);
				}
			}
		}
	}

详细分析:

				processedRequest = checkMultipart(request);//是否是文件上传请求
				multipartRequestParsed = (processedRequest != request);//如果是进行一个转化
				// Determine handler for the current request.
				mappedHandler = getHandler(processedRequest);//当前请求使用那个handler处理

step into 进去 getHandler(processedRequest);

	protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
		if (this.handlerMappings != null) {
			for (HandlerMapping mapping : this.handlerMappings) {
				HandlerExecutionChain handler = mapping.getHandler(request);
				if (handler != null) {
					return handler;
				}
			}
		}
		return null;
	}

handlerMappings---->处理器映射  xxx请求---->xxx处理  默认5个

 

 控制首页路径的访问,即http://localhost:8080/  默认是index.html

 @RequestMapping注解的所有处理器映射

RequestMappingHandlerMapping-->mappingRejistry-->pathLookup

springboot(WebMvcAutoConfiguration) 配置了 

		public RequestMappingHandlerMapping requestMappingHandlerMapping(
				@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
				@Qualifier("mvcConversionService") FormattingConversionService conversionService,
				@Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {
			// Must be @Primary for MvcUriComponentsBuilder to work
			return super.requestMappingHandlerMapping(contentNegotiationManager, conversionService,
					resourceUrlProvider);
		}

		@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;
		}

RequestMappingHandlerMapping  解析RequestMapping注解

4.5 请求处理-常用参数注解使用

  • @PathVariable 路径变量
  • @RequestHeader 获取请求头
  • @RequestParam 获取请求参数(指问号后的参数,url?a=1&b=2)
  • @CookieValue 获取Cookie值
  • @RequestAttribute 获取request域属性
  • @RequestBody 获取请求体[POST]
  • @MatrixVariable 矩阵变量
  • @ModelAttribute
@RestController
public class ParameterTestController {


    //  car/2/owner/zhangsan
    @GetMapping("/car/{id}/owner/{username}")
    public Map<String,Object> getCar(@PathVariable("id") Integer id,
                                     @PathVariable("username") String name,
                                     @PathVariable Map<String,String> pv,
                                     @RequestHeader("User-Agent") String userAgent,
                                     @RequestHeader Map<String,String> header,
                                     @RequestParam("age") Integer age,
                                     @RequestParam("inters") List<String> inters,
                                     @RequestParam Map<String,String> params,
                                     @CookieValue("_ga") String _ga,
                                     @CookieValue("_ga") Cookie cookie){

        Map<String,Object> map = new HashMap<>();

//        map.put("id",id);
//        map.put("name",name);
//        map.put("pv",pv);
//        map.put("userAgent",userAgent);
//        map.put("headers",header);
        map.put("age",age);
        map.put("inters",inters);
        map.put("params",params);
        map.put("_ga",_ga);
        System.out.println(cookie.getName()+"===>"+cookie.getValue());
        return map;
    }


    @PostMapping("/save")
    public Map postMethod(@RequestBody String content){
        Map<String,Object> map = new HashMap<>();
        map.put("content",content);
        return map;
    }
}

4.6 请求处理-【源码分析】-各种类型参数解析原理

进入doDispatch   找到相应的controller后

 所有参数的解析都在适配器中

HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());   step into

	protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
		if (this.handlerAdapters != null) {
			for (HandlerAdapter adapter : this.handlerAdapters) {
				if (adapter.supports(handler)) {
					return adapter;
				}
			}
		}
		throw new ServletException("No adapter for handler [" + handler +
				"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
	}

4种适配器

0:支持方法上标注@RequestMapping

1:  支持函数式编程

找到相应适配器后往下执行到 

mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

 

step into

相应的方法执行都在 RequestMappingHandlerAdapter里面

mav = invokeHandlerMethod(request, response, handlerMethod);//执行目标方法

 step into

参数解析器:27个

 根据相应的注解找到相应的参数解析器

 

15种支持的返回值解析 例如: @ResponseBody

  • WebRequest
  • ServletRequest
  • MultipartRequest
  • HttpSession
  • javax.servlet.http.PushBuilder
  • Principal
  • InputStream
  • Reader
  • HttpMethod
  • Locale
  • TimeZone
  • ZoneId

ServletRequestMethodArgumentResolver用来处理以上的参数 

Map<String,Object> map参数用MapMethodProcessor处理

Model modelModelMethodProcessor处理

4.7 响应处理

在这里插入图片描述

响应数据 

step into

15个返回值解析器

 五、拦截器

5.1 流程

  1. 编写一个拦截器实现HandlerInterceptor接口

  2. 拦截器注册到容器中(实现WebMvcConfigureraddInterceptors()

  3. 指定拦截规则(注意,如果是拦截所有,静态资源也会被拦截】

接口

HandlerInterceptor

@Slf4j
public class LoginInterceptor implements HandlerInterceptor {

    /**
     * 目标方法执行之前
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        String requestURI = request.getRequestURI();
        log.info("preHandle拦截的请求路径是{}",requestURI);

        //登录检查逻辑
        HttpSession session = request.getSession();

        Object loginUser = session.getAttribute("loginUser");

        if(loginUser != null){
            //放行
            return true;
        }

        //拦截住。未登录。跳转到登录页
        request.setAttribute("msg","请先登录");
//        re.sendRedirect("/");
        request.getRequestDispatcher("/").forward(request,response);
        return false;
    }

    /**
     * 目标方法执行完成以后
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        log.info("postHandle执行{}",modelAndView);
    }

    /**
     * 页面渲染以后
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        log.info("afterCompletion执行异常{}",ex);
    }
}

WebMvcConfigurer

 

@Configuration
public class AdminWebConfig implements WebMvcConfigurer{
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor())//拦截器注册到容器中
                .addPathPatterns("/**")  //所有请求都被拦截包括静态资源
                .excludePathPatterns("/","/login","/css/**","/fonts/**","/images/**",
                        "/js/**","/aa/**"); //放行的请求
}

 

 5.2 原理

  1. 根据当前请求,找到HandlerExecutionChain(可以处理请求的handler以及handler的所有 拦截器)
  2. 先来顺序执行 所有拦截器的 preHandle()方法。
    • 如果当前拦截器preHandle()返回为true。则执行下一个拦截器的preHandle()
    • 如果当前拦截器返回为false。直接倒序执行所有已经执行了的拦截器的 afterCompletion();
  3. 如果任何一个拦截器返回false,直接跳出不执行目标方法。
  4. 所有拦截器都返回true,才执行目标方法。
  5. 倒序执行所有拦截器的postHandle()方法。
  6. 前面的步骤有任何异常都会直接倒序触发 afterCompletion()
  7. 页面成功渲染完成以后,也会倒序触发 afterCompletion()

在这里插入图片描述

六、文件上传

6.1 流程

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form role="form" method="post" enctype="multipart/form-data" action="/file">
    <div class="form-group">
        <label for="exampleInputEmail1">邮箱</label>
        <input type="email" name="email" class="form-control" id="exampleInputEmail1" placeholder="Enter email">
    </div>

    <div class="form-group">
        <label for="exampleInputPassword1">名字</label>
        <input type="text" name="username" class="form-control" id="exampleInputPassword1" placeholder="Password">
    </div>

    <div class="form-group">
        <label for="exampleInputFile">头像</label>
        <input type="file" name="headerImg" id="exampleInputFile">
    </div>

    <div class="form-group">
        <label for="exampleInputFile">生活照</label>
        <input type="file" name="photos" multiple>
    </div>

    <div class="checkbox">
        <label>
            <input type="checkbox"> Check me out
        </label>
    </div>
    <button type="submit" class="btn btn-primary">提交</button>
</form>

</body>
</html>
public String HandlerFile(@RequestParam("email") String email,
                              @RequestParam("username") String username,
                              @RequestPart("headerImg") MultipartFile headerImg,
                              @RequestPart("photos") MultipartFile[] photos) throws IOException {
        if(!headerImg.isEmpty()){
            String originalFilename = headerImg.getOriginalFilename();
            headerImg.transferTo(new File("D:/File",originalFilename));
        }
        if(photos.length>0){
            for(MultipartFile file:photos){
                if(!file.isEmpty()){
                    String originalFilename = file.getOriginalFilename();
                    headerImg.transferTo(new File("D:/File",originalFilename));
                }
            }
        }

        return "file";
    }
spring:
  servlet:
    multipart:
      max-file-size: 100MB  #单个文件的大小
      max-request-size: 1GB #所有文件加起来的大小

6.2 原理

step into

resolveMultipart 文件上传解析器

 判断是否是文件上传请求 step into

这就是html页面加enctype="multipart/form-data"的原因

然后被封装为一个MultipartHttpServletRequest对象

接着被文件上传解析器来解析请求中的文件内容,封装为MultipartFile

七、异常处理

7.1 默认规则

  • 默认情况下,Spring Boot提供/error处理所有错误的映射

  • 机器客户端,它将生成JSON响应,其中包含错误,HTTP状态和异常消息的详细信息。对于浏览器客户端,响应一个“ whitelabel”错误视图,以HTML格式呈现相同的数据

错误页面   templates/error/404.html   默认路径  或者 templates/error/4xx.html 

7.2 自动配置类

自动配置类  ErrorMvcAutoConfiguration

 

 7.3 源码

譬如写一个会抛出异常的控制层:

@Slf4j
@RestController
public class HelloController {

    @RequestMapping("/hello")
    public String handle01(){

        int i = 1 / 0;//将会抛出ArithmeticException

        log.info("Hello, Spring Boot 2!");
        return "Hello, Spring Boot 2!";
    }
}

当浏览器发出/hello请求,DispatcherServlet的doDispatch()的mv = ha.handle(processedRequest, response, mappedHandler.getHandler());将会抛出ArithmeticException。
 

public class DispatcherServlet extends FrameworkServlet {
    ...
	protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
		...
				// Actually invoke the handler.
            	//将会抛出ArithmeticException
				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

				applyDefaultViewName(processedRequest, mv);
				mappedHandler.applyPostHandle(processedRequest, response, mv);
			}
			catch (Exception ex) {
                //将会捕捉ArithmeticException
				dispatchException = ex;
			}
			catch (Throwable err) {
				...
			}
    		//捕捉后,继续运行
			processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
		}
		catch (Exception ex) {
			triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
		}
		catch (Throwable err) {
			triggerAfterCompletion(processedRequest, response, mappedHandler,
					new NestedServletException("Handler processing failed", err));
		}
		finally {
			...
		}
	}

	private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
			@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
			@Nullable Exception exception) throws Exception {

		boolean errorView = false;

		if (exception != null) {
			if (exception instanceof ModelAndViewDefiningException) {
				...
			}
			else {
				Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
				//ArithmeticException将在这处理
                mv = processHandlerException(request, response, handler, exception);
				errorView = (mv != null);
			}
		}
		...
	}

	protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
			@Nullable Object handler, Exception ex) throws Exception {

		// Success and error responses may use different content types
		request.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);

		// Check registered HandlerExceptionResolvers...
		ModelAndView exMv = null;
		if (this.handlerExceptionResolvers != null) {
            //遍历所有的 handlerExceptionResolvers,看谁能处理当前异常HandlerExceptionResolver处理器异常解析器
			for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
				exMv = resolver.resolveException(request, response, handler, ex);
				if (exMv != null) {
					break;
				}
			}
		}
		...
	
        //若只有系统的自带的异常解析器(没有自定义的),异常还是会抛出
		throw ex;
	}

}

 系统自带的异常解析器

在这里插入图片描述

  • DefaultErrorAttributes先来处理异常,它主要功能把异常信息保存到request域,并且返回null。
public class DefaultErrorAttributes implements ErrorAttributes, HandlerExceptionResolver, Ordered {
    ...
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        this.storeErrorAttributes(request, ex);
        return null;
    }

    private void storeErrorAttributes(HttpServletRequest request, Exception ex) {
        request.setAttribute(ERROR_ATTRIBUTE, ex);//把异常信息保存到request域
    }
    ...
    
}    

  • 默认没有任何解析器(上图的HandlerExceptionResolverComposite)能处理异常,所以最后异常会被抛出。

  • 最终底层就会转发/error 请求。会被底层的BasicErrorController处理。

@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class BasicErrorController extends AbstractErrorController {

    @RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
    public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
       HttpStatus status = getStatus(request);
       Map<String, Object> model = Collections
             .unmodifiableMap(getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.TEXT_HTML)));
       response.setStatus(status.value());
       ModelAndView modelAndView = resolveErrorView(request, response, status, model);
       //如果/template/error内没有4**.html或5**.html,
       //modelAndView为空,最终还是返回viewName为error的modelAndView
       return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
    }
    
    ...
}

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    
    ...
    
	protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        ...
     	// Actually invoke the handler.
		mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
		...
        //渲染页面
		processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
        ...
    }
    
    private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
			@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
			@Nullable Exception exception) throws Exception {

        boolean errorView = false;
        ...
		// Did the handler return a view to render?
		if (mv != null && !mv.wasCleared()) {
			render(mv, request, response);
			if (errorView) {
				WebUtils.clearErrorRequestAttributes(request);
			}
		}
		...
	}
    
    protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
		...

		View view;
		String viewName = mv.getViewName();
		if (viewName != null) {
			// We need to resolve the view name.
            //找出合适error的View,如果/template/error内没有4**.html或5**.html,
            //将会返回默认异常页面ErrorMvcAutoConfiguration.StaticView
            //这里按需深究代码吧!
			view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
			...
		}
		...
		try {
			if (mv.getStatus() != null) {
				response.setStatus(mv.getStatus().value());
			}
            //看下面代码块的StaticView的render块
			view.render(mv.getModelInternal(), request, response);
		}
		catch (Exception ex) {
			...
		}
	}
    
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class })
// Load before the main WebMvcAutoConfiguration so that the error View is available
@AutoConfigureBefore(WebMvcAutoConfiguration.class)
@EnableConfigurationProperties({ ServerProperties.class, ResourceProperties.class, WebMvcProperties.class })
public class ErrorMvcAutoConfiguration {
    
    ...
        
   	@Configuration(proxyBeanMethods = false)
	@ConditionalOnProperty(prefix = "server.error.whitelabel", name = "enabled", matchIfMissing = true)
	@Conditional(ErrorTemplateMissingCondition.class)
	protected static class WhitelabelErrorViewConfiguration {

        //将创建一个名为error的系统默认异常页面View的Bean
		private final StaticView defaultErrorView = new StaticView();

		@Bean(name = "error")
		@ConditionalOnMissingBean(name = "error")
		public View defaultErrorView() {
			return this.defaultErrorView;
		}

		// If the user adds @EnableWebMvc then the bean name view resolver from
		// WebMvcAutoConfiguration disappears, so add it back in to avoid disappointment.
		@Bean
		@ConditionalOnMissingBean
		public BeanNameViewResolver beanNameViewResolver() {
			BeanNameViewResolver resolver = new BeanNameViewResolver();
			resolver.setOrder(Ordered.LOWEST_PRECEDENCE - 10);
			return resolver;
		}

	}     
   
    
	private static class StaticView implements View {

		private static final MediaType TEXT_HTML_UTF8 = new MediaType("text", "html", StandardCharsets.UTF_8);

		private static final Log logger = LogFactory.getLog(StaticView.class);

		@Override
		public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response)
				throws Exception {
			if (response.isCommitted()) {
				String message = getMessage(model);
				logger.error(message);
				return;
			}
			response.setContentType(TEXT_HTML_UTF8.toString());
			StringBuilder builder = new StringBuilder();
			Object timestamp = model.get("timestamp");
			Object message = model.get("message");
			Object trace = model.get("trace");
			if (response.getContentType() == null) {
				response.setContentType(getContentType());
			}
            //系统默认异常页面html代码
			builder.append("<html><body><h1>Whitelabel Error Page</h1>").append(
					"<p>This application has no explicit mapping for /error, so you are seeing this as a fallback.</p>")
					.append("<div id='created'>").append(timestamp).append("</div>")
					.append("<div>There was an unexpected error (type=").append(htmlEscape(model.get("error")))
					.append(", status=").append(htmlEscape(model.get("status"))).append(").</div>");
			if (message != null) {
				builder.append("<div>").append(htmlEscape(message)).append("</div>");
			}
			if (trace != null) {
				builder.append("<div style='white-space:pre-wrap;'>").append(htmlEscape(trace)).append("</div>");
			}
			builder.append("</body></html>");
			response.getWriter().append(builder.toString());
		}

		private String htmlEscape(Object input) {
			return (input != null) ? HtmlUtils.htmlEscape(input.toString()) : null;
		}

		private String getMessage(Map<String, ?> model) {
			Object path = model.get("path");
			String message = "Cannot render error page for request [" + path + "]";
			if (model.get("message") != null) {
				message += " and exception [" + model.get("message") + "]";
			}
			message += " as the response has already been committed.";
			message += " As a result, the response may have the wrong status code.";
			return message;
		}

		@Override
		public String getContentType() {
			return "text/html";
		}

	}
}

7.4 几种异常处理原理

自定义错误页

  • error/404.html error/5xx.html;有精确的错误状态码页面就匹配精确,没有就找 4xx.html;如果都没有就触发白页
  • @ControllerAdvice+@ExceptionHandler处理全局异常;底层是 ExceptionHandlerExceptionResolver 支持的

@Slf4j
@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler({ArithmeticException.class,NullPointerException.class})  //处理异常
    public String handleArithException(Exception e){

        log.error("异常是:{}",e);
        return "login"; //视图地址
    }
}

@ResponseStatus+自定义异常 ;底层是 ResponseStatusExceptionResolver ,把responseStatus注解的信息底层调用 response.sendError(statusCode, resolvedReason),tomcat发送的/error

@ResponseStatus(value= HttpStatus.FORBIDDEN,reason = "用户数量太多")
public class UserTooManyException extends RuntimeException {

    public  UserTooManyException(){

    }
    public  UserTooManyException(String message){
        super(message);
    }
}
@Controller
public class TableController {
    
	@GetMapping("/dynamic_table")
    public String dynamic_table(@RequestParam(value="pn",defaultValue = "1") Integer pn,Model model){
        //表格内容的遍历
	     List<User> users = Arrays.asList(new User("zhangsan", "123456"),
                new User("lisi", "123444"),
                new User("haha", "aaaaa"),
                new User("hehe ", "aaddd"));
        model.addAttribute("users",users);

        if(users.size()>3){
            throw new UserTooManyException();//抛出自定义异常
        }
        return "table/dynamic_table";
    }
    
}
  • Spring自家异常如 org.springframework.web.bind.MissingServletRequestParameterExceptionDefaultHandlerExceptionResolver 处理Spring自家异常。

  • 自定义实现 HandlerExceptionResolver 处理异常;可以作为默认的全局异常处理规则

@Order(value= Ordered.HIGHEST_PRECEDENCE)  //优先级,数字越小优先级越高
@Component
public class CustomerHandlerExceptionResolver implements HandlerExceptionResolver {
    @Override
    public ModelAndView resolveException(HttpServletRequest request,
                                         HttpServletResponse response,
                                         Object handler, Exception ex) {

        try {
            response.sendError(511,"我喜欢的错误");
        } catch (IOException e) {
            e.printStackTrace();
        }
        return new ModelAndView();
    }
}

ErrorViewResolver 实现自定义处理异常

  • response.sendError(),error请求就会转给controller。
  • 你的异常没有任何人能处理,tomcat底层调用response.sendError(),error请求就会转给controller。
  • basicErrorController 要去的页面地址是 ErrorViewResolver 。
@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class BasicErrorController extends AbstractErrorController {

    ...
    
	@RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
	public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
		HttpStatus status = getStatus(request);
		Map<String, Object> model = Collections
				.unmodifiableMap(getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.TEXT_HTML)));
		response.setStatus(status.value());
		ModelAndView modelAndView = resolveErrorView(request, response, status, model);
		return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
	}
    
    protected ModelAndView resolveErrorView(HttpServletRequest request, HttpServletResponse response, HttpStatus status,
			Map<String, Object> model) {
        //这里用到ErrorViewResolver接口
		for (ErrorViewResolver resolver : this.errorViewResolvers) {
			ModelAndView modelAndView = resolver.resolveErrorView(request, status, model);
			if (modelAndView != null) {
				return modelAndView;
			}
		}
		return null;
	}
    
    ...
    
}

八、web原生组件的注入

8.1 原生注解

servlet

 

@WebServlet(urlPatterns = "/my")
public class MyServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().write("123");
    }
}
@ServletComponentScan(basePackages= "com.gzj.servlet")
@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

直接响应,没有经过Spring的拦截器

listeners

@WebListener
public class MyServletContextListener implements ServletContextListener {


    @Override
    public void contextInitialized(ServletContextEvent sce) {
        System.out.println("MySwervletContextListener监听到项目初始化完成");
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        System.out.println("MySwervletContextListener监听到项目销毁");
    }
}

8.2 使用RegistrationBean

九、嵌入式web容器

9.1 简介

默认支持的WebServer

  • TomcatJetty, or Undertow
  • ServletWebServerApplicationContext容器启动寻找ServletWebServerFactory 并引导创建服务器。

原理

  • SpringBoot应用启动发现当前是Web应用,web场景包-导入tomcat。
  • web应用会创建一个web版的IOC容器 ServletWebServerApplicationContext 。
  • ServletWebServerApplicationContext 启动的时候寻找 ServletWebServerFactory (Servlet 的web服务器工厂——>Servlet 的web服务器)。
  • SpringBoot底层默认有很多的WebServer工厂(ServletWebServerFactoryConfiguration内创建Bean),如:
  •         TomcatServletWebServerFactory
  •         JettyServletWebServerFactory
  •         UndertowServletWebServerFactory
  • 底层直接会有一个自动配置类ServletWebServerFactoryAutoConfiguration
  • ServletWebServerFactoryAutoConfiguration导入了ServletWebServerFactoryConfiguration(配置类)。
  • ServletWebServerFactoryConfiguration根据动态判断系统中到底导入了那个Web服务器的包。(默认是web-starter导入tomcat包),容器中就有 TomcatServletWebServerFactory
  • TomcatServletWebServerFactory创建出Tomcat服务器并启动;TomcatWebServer 的构造器拥有初始化方法initialize——this.tomcat.start();
  • 内嵌服务器,与以前手动把启动服务器相比,改成现在使用代码启动(tomcat核心jar包存在)。

Spring Boot默认使用Tomcat服务器,若需更改其他服务器,则修改工程pom.xml:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </exclusion>
    </exclusions>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jetty</artifactId>
</dependency>

学习尚硅谷视频整理的文档 Spring Boot 1 1 Spring Boot入门 4 1.1 简介 4 1.2 微服务(martin fowler发表了一篇文章) 5 1.3 环境约束 7 1.4 第一个Spring Boot项目(jar):HelloWorld 8 1.5 入门案例详解 11 1.5.1 POM文件 11 1.5.2 主程序类,主入口类 12 1.6 使用Spring Initializer向导快速创建Spring Boot 16 2 Spring Boot配置 18 2.1 配置文件 18 2.2 YML语法 19 2.3 YML配置文件值获取 21 2.4 properties配置文件乱码问题 24 2.5 @ConfigurationProperties与@Value的区别 25 2.6 配置@PropertySource、@ImportResource、@Bean 27 2.7 配置文件占位符 30 2.8 Profile多环境支持 31 2.9 配置文件的加载位置 33 2.10 外部配置加载顺序 36 2.11 自动配置原理 37 2.12 @Conditional派生注解 41 3 Spring Boot与日志 42 3.1 日志框架分类和选择 42 3.2 SLF4j使用 43 3.3 其他日志框架统一转换成slf4j+logback 44 3.4 Spring Boot日志使用 45 3.5 Spring Boot默认配置 47 3.6 指定日志文件和日志Profile功能 52 3.7 切换日志框架(不使用SLF4j+LogBack) 54 4 Spring Boot与Web开发 55 4.1 Web开发简介 55 4.2 静态资源映射规则 56 4.3 引入Thymeleaf 60 4.4 Thymeleaf语法 61 4.5 SpringMVC自动配置原理 67 4.6 SpringBoot扩展与全面接管 70 4.7 如何修改SpringBoot的默认配置 72 4.8 【实验】CRUD操作 73 4.8.1 默认访问首页 73 4.8.2 登录页面国际化 74 4.8.3 登录 80 4.8.4 拦截器进行登录检查 81 4.8.5 实验要求(没按要求做,不想改了!) 82 4.8.6 CRUD-员工列表 83 4.8.7 CRUD-员工修改 86 4.8.8 CRUD-员工添加 87 4.8.9 CRUD-员工删除 88 4.9 错误处理原理&错误页面定制 90 4.10 配置嵌入式Servlet容器(springboot 1.50版本) 97 4.10.1 如何定制和修改Servelt容器的相关配置 97 4.10.2 注册servlet三大组件【servlet,filter,listener】 98 4.10.3 替换为其他嵌入式容器 102 4.10.4 嵌入式servlet容器自动配置原理 103 4.10.5 嵌入式servlet容器启动原理 103 4.11 使用外置的Servlet容器 104 4.11.1 步骤 104 4.11.2 原理 107 5 Spring Boot与Docker(虚拟化容器技术) 110 5.1 简介 110 5.2 核心概念 111 5.3 安装Docker 112 5.4 Docker常用命令&操作 113 5.5 安装MySQL示例 114 6 Spring Boot与数据访问 115 6.1 JDBC 115 6.1.1 实现 115 6.1.2 自动配置原理 116 6.2 整合Durid数据源 117 6.3 整合Mybatis 122 6.3.1 注解版 123 6.3.2 配置文件版 124 6.4 整合SpringData JPA 125 6.4.1 SpringData简介 125 6.4.2 整合 126 7 Spring Boot启动配置原理 128 7.1 启动流程(Springboot 1.50版本) 128 7.1.1 创建SpringApplication对象 129 7.1.2 运行run方法 130 7.1.3 编写事件监听机制 132 8 Spring Boot自定义starters 136 8.1 概述 136 8.2 步骤 137 9 更多Springboot整合示例 144 10 Spring Boot与缓存 145 10.1 JSR107缓存规范 145 10.2 Spring的缓存抽象 146 10.2.1 基本概念 146 10.2.2 整合项目 146 10.2.3 CacheEnable注解 148 10.2.4 Cache注解 150 10.3 整合redis 154 10.3.1 在Docker上安装redis 154 10.3.2 Redis的Template 154 10.3.3 整合(百度) 155
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值