SpringBoot 4:web开发

源码地址:https://gitee.com/pidaner/springboot-practice.git

1、SpringMVC自动配置概览

Spring Boot provides auto-configuration for Spring MVC that works well with most applications.(大多场景我们都无需自定义配置)
The auto-configuration adds the following features on top of Spring’s defaults:

• Inclusion of ContentNegotiatingViewResolver and BeanNameViewResolver beans.

• 内容协商视图解析器和BeanName视图解析器

• Support for serving static resources, including support for WebJars (covered later in this document)).

• 静态资源(包括webjars)

• Automatic registration of Converter, GenericConverter, and Formatter beans.

• 自动注册 Converter,GenericConverter,Formatter 

• Support for HttpMessageConverters (covered later in this document).

• 支持 HttpMessageConverters (后来我们配合内容协商理解原理)

• Automatic registration of MessageCodesResolver (covered later in this document).

• 自动注册 MessageCodesResolver (国际化用)

• Static index.html support.

• 静态index.html 页支持

• Custom Favicon support (covered later in this document).

• 自定义 Favicon  

• Automatic use of a ConfigurableWebBindingInitializer bean (covered later in this document).

• 自动使用 ConfigurableWebBindingInitializer ,(DataBinder负责将请求数据绑定到JavaBean上)

If you want to keep those Spring Boot MVC customizations and make more MVC customizations (interceptors, formatters, view controllers, and other features), you can add your own @Configuration class of type WebMvcConfigurer but without @EnableWebMvc.

不用@EnableWebMvc注解。使用 @Configuration + WebMvcConfigurer 自定义规则
If you want to provide custom instances of RequestMappingHandlerMapping, RequestMappingHandlerAdapter, or ExceptionHandlerExceptionResolver, and still keep the Spring Boot MVC customizations, you can declare a bean of type WebMvcRegistrations and use it to provide custom instances of those components.

声明 WebMvcRegistrations 改变默认底层组件
If you want to take complete control of Spring MVC, you can add your own @Configuration annotated with @EnableWebMvc, or alternatively add your own @Configuration-annotated DelegatingWebMvcConfiguration as described in the Javadoc of @EnableWebMvc.
使用 @EnableWebMvc+@Configuration+DelegatingWebMvcConfiguration 全面接管SpringMVC

Spring Boot Features,点击查看官网

2、简单功能分析

新建 initialize 项目,勾选 如图框架支持
在这里插入图片描述

2.1、静态资源访问

静态资源目录

只要静态资源放在类路径下: called /static (or /public or /resources or /META-INF/resources

访问 : 当前项目根路径/ + 静态资源名

原理: 静态映射/**

请求进来,先去找Controller看能不能处理。不能处理的所有请求又都交给静态资源处理器。静态资源也找不到则响应404页面
在这里插入图片描述

1、改变默认的静态资源路径

改变静态资源路径后,只在指定的目录下生效,其他系统默认的目录不再生效

#采用数组形式,可以设置多个静态资源目录
spring:
  web:
    resources:
      static-locations: [classpath:/haha/]

2、静态资源访问前缀

默认无前缀

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

当前项目 + static-path-pattern + 静态资源名 = 静态资源文件夹下找

3、webjar

自动映射,点击webjar官网 /webjars/**

<dependency>
   <groupId>org.webjars</groupId>
   <artifactId>jquery</artifactId>
   <version>3.5.1</version>
</dependency>

访问地址:http://localhost:8080/webjars/jquery/3.5.1/jquery.js 后面地址要按照依赖里面的包路径

注:引入新的pom依赖,项目要重新启动才生效

2.2、欢迎页支持

Spring Boot supports both static and templated welcome pages. It first looks for an index.html file in the configured static content locations. If one is not found, it then looks for an index template. If either is found, it is automatically used as the welcome page of the application.

• 静态资源路径下 index.html
• 可以配置静态资源路径
• 但是不可以配置静态资源的访问前缀。否则导致 index.html不能被默认访问

举例子

1、自定义配置资源路径

#静态资源访问前缀
spring:
#  mvc:
#    static-path-pattern: /res/**

#采用数组形式,可以设置多个静态资源目录
  web:
    resources:
      static-locations: [classpath:/haha/]

2、在haha目录下新建 index.html 文件

3、浏览器测试 http://localhost:8080/,即可直接访问到index.html文件

controller能处理/index

2.3、自定义 Favicon

favicon.ico 放在静态资源目录下即可

2.4、静态资源配置原理

SpringBoot启动默认加载 xxxAutoConfiguration 类(自动配置类)
SpringMVC功能的自动配置类 WebMvcAutoConfiguration,生效

所有的自动配置类都在autoconfigure包下
在这里插入图片描述
我们是web项目,以web为例
在目录打开 autoconfigure/web/servlet/WebMvcAutoConfiguration

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

上面代码已经生效,给容器中配了什么

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

配置文件的相关属性和xxx进行了绑定。
WebMvcProperties == spring.mvc
ResourceProperties ==spring.resources

1、配置类只有一个有参构造器

//有参构造器所有参数的值都会从容器中确定
//ResourceProperties resourceProperties;获取和spring.resources绑定的所有的值的对象
//WebMvcProperties mvcProperties 获取和spring.mvc绑定的所有的值的对象
//ListableBeanFactory beanFactory Spring的beanFactory
//HttpMessageConverters 找到所有的HttpMessageConverters
//ResourceHandlerRegistrationCustomizer 找到 资源处理器的自定义器。=========
//DispatcherServletPath  
//ServletRegistrationBean   给应用注册Servlet、Filter....

public WebMvcAutoConfigurationAdapter(
	org.springframework.boot.autoconfigure.web.ResourceProperties resourceProperties,
	WebProperties webProperties, WebMvcProperties mvcProperties, ListableBeanFactory beanFactory,
	ObjectProvider<HttpMessageConverters> messageConvertersProvider,
	ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider,
	ObjectProvider<DispatcherServletPath> dispatcherServletPath,
	ObjectProvider<ServletRegistrationBean<?>> servletRegistrations) {
		this.resourceProperties = resourceProperties.hasBeenCustomized() ? resourceProperties
					: webProperties.getResources();
		this.mvcProperties = mvcProperties;
		this.beanFactory = beanFactory;
		this.messageConvertersProvider = messageConvertersProvider;
		this.resourceHandlerRegistrationCustomizer = resourceHandlerRegistrationCustomizerProvider.getIfAvailable();
		this.dispatcherServletPath = dispatcherServletPath;
		this.servletRegistrations = servletRegistrations;
		this.mvcProperties.checkConfiguration();
}

2、资源处理的默认规则

// WebMvcAutoConfiguration类

@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
	if (!this.resourceProperties.isAddMappings()) {
		logger.debug("Default resource handling disabled");
			return;
	}
	Duration cachePeriod = this.resourceProperties.getCache().getPeriod();
	CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl();
	if (!registry.hasMappingForPattern("/webjars/**")) {
		customizeResourceHandlerRegistration(registry.addResourceHandler("/webjars/**")
			.addResourceLocations("classpath:/META-INF/resources/webjars/")
			.setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl)
			.setUseLastModified(this.resourceProperties.getCache().isUseLastModified()));
	}
	String staticPathPattern = this.mvcProperties.getStaticPathPattern();
		if (!registry.hasMappingForPattern(staticPathPattern)) {
			customizeResourceHandlerRegistration(registry.addResourceHandler(staticPathPattern)
				.addResourceLocations(getResourceLocations(this.resourceProperties.getStaticLocations()))
				.setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl)
				.setUseLastModified(this.resourceProperties.getCache().isUseLastModified()));
		}
	}

代码分析——禁用静态资源影射路径

// 如果isAddMappings 为false,就会资源禁用
if (!this.resourceProperties.isAddMappings()) {
	logger.debug("Default resource handling disabled");
		return;
}

// isAddMappings 默认为true	
private boolean addMappings = true;

案例演示

1、禁用静态资源的影射路径

spring:
  web:
    resources:
      # 默认为true
      add-mappings: true
      # 禁用静态资源影射路径,静态资源不可访问
      //add-mappings: false

2、测试

当 add-mappings: true 时,浏览器可以访问到静态资源
在这里插入图片描述
当 add-mappings: false 时,浏览器不能访问静态资源
在这里插入图片描述
代码分析——缓存时间

Duration cachePeriod = this.resourceProperties.getCache().getPeriod();

// WebProperties类
public void setPeriod(Duration period) {
		this.customized = true;
		this.period = period;

// 缓存时间以秒为单位
@DurationUnit(ChronoUnit.SECONDS)
	private Duration period;
}

在application.yaml 配置缓存

# 静态资源保存100秒,过期失效
spring:
  web:
    resources:
      cache:
        period: 100

代码分析——资源路径配置

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)
	.setUseLastModified(this.resourceProperties.getCache().isUseLastModified()));
}

解释:因为源码配置好了路径,所以如下网址可访问到jquery
在这里插入图片描述
代码分析——静态资源路径

// WebMvcAutoConfiguration类
String staticPathPattern = this.mvcProperties.getStaticPathPattern();
	if (!registry.hasMappingForPattern(staticPathPattern)) {
	customizeResourceHandlerRegistration(registry.addResourceHandler(staticPathPattern)
	.addResourceLocations(getResourceLocations(this.resourceProperties.getStaticLocations()))
	.setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl)
	.setUseLastModified(this.resourceProperties.getCache().isUseLastModified()));
}

// WebMvcAutoConfiguration类
private final WebMvcProperties mvcProperties;

//WebMvcProperties类
@ConfigurationProperties(prefix = "spring.mvc")
public class WebMvcProperties {}

默认路径是 /**

//点击 getStaticPathPattern()方法,进入WebMvcProperties类

//WebMvcProperties类
public String getStaticPathPattern() {
	return this.staticPathPattern;
}

private String staticPathPattern = "/**";

源码指定类资源路径

//点击getStaticLocations()方法,进入WebProperties类
public String[] getStaticLocations() {
	return this.staticLocations;
}

private String[] staticLocations = CLASSPATH_RESOURCE_LOCATIONS;

private static final String[] CLASSPATH_RESOURCE_LOCATIONS = { "classpath:/META-INF/resources/",
				"classpath:/resources/", "classpath:/static/", "classpath:/public/" };

3、欢迎页的处理规则

//HandlerMapping:处理器映射。保存了每一个Handler能处理哪些请求。  

@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(TemplateAvailabilityProviders templateAvailabilityProviders,
	ApplicationContext applicationContext, Optional<Resource> welcomePage, String staticPathPattern) {
	//要用欢迎页功能,必须是/**
	if (welcomePage.isPresent() && "/**".equals(staticPathPattern)) {
	logger.info("Adding welcome page: " + welcomePage.get());
	setRootViewName("forward:index.html");
	}
	else if (welcomeTemplateExists(templateAvailabilityProviders, applicationContext)) {
	// 调用Controller  /index
	logger.info("Adding welcome page template: index");
	setRootViewName("index");
	}
}

4、favicon

3、请求参数处理

0、请求映射

1、rest使用与原理

• Rest风格支持(使用HTTP请求方式动词来表示对资源的操作)

• 以前:/getUser 获取用户 /deleteUser 删除用户 /editUser 修改用户 /saveUser 保存用户

• 现在: /user GET-获取用户 DELETE-删除用户 PUT-修改用户 POST-保存用户

• 核心Filter;HiddenHttpMethodFilter

• 用法: 表单method=post,隐藏域 _method=put

• SpringBoot中手动开启

举个栗子 :@RequestMapping 注解使用

1、创建控制层

@RestController
public class MyController {

    @RequestMapping(value = "/user",method = RequestMethod.GET)
    public String test1(){
        return "GET";
    }

    @RequestMapping(value = "/user",method = RequestMethod.POST)
    public String test2(){
        return "POST";
    }

    @RequestMapping(value = "/user",method = RequestMethod.PUT)
    public String test3(){
        return "PUT";
    }

    @RequestMapping(value = "/user",method = RequestMethod.DELETE)
    public String test4(){
        return "DELETE";
    }
}

2、表单配置

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1>welcome you</h1>
    测试rest风格
    <form action="/user" method="get">
        <input value="REST-GET 提交" type="submit"/>
    </form>
    <form action="/user" method="post">
        <input value="REST-GET 提交" type="submit"/>
    </form>
    <form action="/user" method="post">
        <input name="_method" type="hidden" value="put">
        <input value="REST-PUT 提交" type="submit"/>
    </form>
    <form action="/user" method="post">
        <input name="_method" type="hidden" value="delete">
        <input value="REST-DELETE 提交" type="submit"/>
    </form>
</body>
</html>

3、HiddenHttpMethodFilter 配置

spring:
  mvc:
    hiddenmethod:
      filter:
        enabled: true

4、测试即可成功,会跳转到对应方法
在这里插入图片描述
Rest原理(表单提交要使用REST的时候)

Rest原理(表单提交要使用REST的时候)

  1. 表单提交会带上_method=PUT
  2. 请求过来被HiddenHttpMethodFilter拦截
  3. 请求是否正常,并且是POST
  4. 获取到_method的值。
  5. 兼容以下请求;PUT.DELETE.PATCH
  6. 原生request(post),包装模式requesWrapper重写了getMethod方法,返回的是传入的值。
  7. 过滤器链放行的时候用wrapper。以后的方法调用getMethod是调用requesWrapper的。
  8. Rest使用客户端工具,如PostMan直接发送Put、delete等方式请求,无需Filter

源码解析

1、WebMvcAutoConfiguration 类

// WebMvcAutoConfiguration,springboot对springMVC的配置都在此类里
/** 
* @Bean作用:将组件添加到spring容器中
* @ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
* 当容器中没有这个组件 HiddenHttpMethodFilter,就给放一个
* @ConditionalOnProperty(prefix = *"spring.mvc.hiddenmethod.filter", name = "enabled", matchIfMissing = false)
* 默认为false,不开启,如需开启到 yaml中自行配置
*/
@Bean
@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
@ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = "enabled", matchIfMissing = false)
public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
	return new OrderedHiddenHttpMethodFilter();
}

2、OrderedHiddenHttpMethodFilter 类

//此类继承自HiddenHttpMethodFilter 类
public class OrderedHiddenHttpMethodFilter extends HiddenHttpMethodFilter implements OrderedFilter {

	/**
	 * The default order is high to ensure the filter is applied before Spring Security.
	 */
	public static final int DEFAULT_ORDER = REQUEST_WRAPPER_FILTER_MAX_ORDER - 10000;

	private int order = DEFAULT_ORDER;

	@Override
	public int getOrder() {
		return this.order;
	}

	/**
	 * Set the order for this filter.
	 * @param order the order to set
	 */
	public void setOrder(int order) {
		this.order = order;
	}
}

3、HiddenHttpMethodFilter 类

public class HiddenHttpMethodFilter extends OncePerRequestFilter {
	
	// 默认允许的方法有PUT、DELETE、PATCH
	private static final List<String> ALLOWED_METHODS =
			Collections.unmodifiableList(Arrays.asList(HttpMethod.PUT.name(),
					HttpMethod.DELETE.name(), HttpMethod.PATCH.name()));

	/** 默认方法的参数 */
	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;
		
		//此处要求表单提交方法必须为post
		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;
		}
	}
}

扩展:如何把_method 这个名字换成我们自己喜欢的

1、新建配置类

@Configuration(proxyBeanMethods = false)
public class MyConfig {

    @Bean
    public HiddenHttpMethodFilter hiddenHttpMethodFilter(){
        HiddenHttpMethodFilter methodFilter = new HiddenHttpMethodFilter();
        methodFilter.setMethodParam("_m");
        return methodFilter;
    }
}

2、修改表单

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

2、请求映射原理

springboot的底层用的是springmvc,DispatcherServlet是处理所有请求的开始,底层还是servlet

doDispatch()方法是DispatcherServlet里最重要的方法,每个请求都会调用这个方法

推导过程 doDispatch()方法

//1、核心类 DispatcherServlet 继承自 FrameworkServlet 
public class DispatcherServlet extends FrameworkServlet {}

//2、进入 FrameworkServlet 类,查看 doGet方法
@Override
protected final void doGet(HttpServletRequest request, HttpServletResponse response)
	throws ServletException, IOException {

	processRequest(request, response);
	}

//3、进入processRequest方法内
doService(request, response);

//4、在DispatcherServlet 类中全局搜索并进入doService方法
doDispatch(request, response);

SpringMVC功能分析都从 org.springframework.web.servlet.DispatcherServlet-》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);

            // 找到当前请求使用哪个Handler(Controller的方法)处理
             mappedHandler = getHandler(processedRequest);
                
           //HandlerMapping:处理器映射。/xxx->>xxxx

所有的请求映射都在HandlerMapping中。
• SpringBoot自动配置欢迎页的 WelcomePageHandlerMapping 。访问 /能访问到index.html;
• SpringBoot自动配置了默认 的 RequestMappingHandlerMapping
• 请求进来,挨个尝试所有的HandlerMapping看是否有请求信息。
• 如果有就找到这个请求对应的handler
• 如果没有就是下一个 HandlerMapping
• 我们需要一些自定义的映射处理,我们也可以自己给容器中放HandlerMapping。自定义 HandlerMapping

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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值