栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Springboot基础-基于SpringMVC的Web开发

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Springboot基础-基于SpringMVC的Web开发

前提:在SpringBoot中导入了web场景包

	org.springframework.boot
	spring-boot-starter-web

文章目录
  • 前提:在SpringBoot中导入了web场景包
    • 一、简单静态资源访问
      • 1.静态资源目录
      • 2.静态资源前缀配置
      • 3.欢迎页支持
      • 4.原理
    • 二、请求参数处理
      • 1.REST请求映射
        • Rest原理(表单提交要使用REST时):
        • Rest(使用客户端工具):
        • 扩展注解
      • 2.请求分发
        • 总结
      • 3.普通参数与基本注解

一、简单静态资源访问 1.静态资源目录


默认配置了这四个文件夹的静态资源访问/staticor /publicor/resourcesor/meta-INF/resources
只要访问: 当前项目根路径/ + 静态资源名就可以拿到。
原理: 静态映射/**的所有请求,请求进来先去看Controller能不能处理,如果不能再转到静态资源处理, 最后再转到404页面。

2.静态资源前缀配置

默认无前缀

# 这表示只有静态资源的访问路径为/res/**时,才会处理请求
spring:
	mvc:
		static-path-pattern: /res/**

配置了之后可以使用: 项目根路径 + static-path-pattern + 资源名 = 静态资源文件夹下寻找

# 还可以配置
spring:
	web:
		resources:
			static-locations: classpath:/haha
# 这样就指定了/haha是静态资源文件夹, 取代了前面四个默认值(因为前四个默认值是一个数组,所以自定义写了值的话,就直接顶替掉了那个数组变量,详见下文源码)
3.欢迎页支持
  • 静态资源目录下放一个index.html
  • 静态资源目录下放一个favicon.ico
  • 能处理/index请求的Controller
4.原理

SpringBoot启动默认加载xxxAutoConfiguration类(自动配置类)
SpringMVC功能的自动配置类WebMVCAutoConfiguration,一系列@Conditional判断之后生效

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

那么这个自动配置类给容器中配置了什么?

@Configuration(proxyBeanMethods = false)
@import({WebMvcAutoConfiguration.EnableWebMvcConfiguration.class})
@EnableConfigurationProperties({WebMvcProperties.class, ResourceProperties.class, WebProperties.class})
@Order(0)
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer, ServletContextAware {}

它里面有一个内部类WebMvcAutoConfigurationAdapter,与配置文件的相关属性进行了绑定。WebMvcProperties(prefix = "spring.mvc")、ResourceProperties(prefix = "spring.resources")、WebProperties(@ConfigurationProperties("spring.web"))
同时该内部类只有一个有参构造器

# 有参构造器所有参数的值都会从容器中确定
# ResourceProperties resourceProperties:获取和spring.resources绑定的所有值的对象
# WebProperties webProperties:获取和spring.mvc绑定的所有值的对象
# ListableBeanFactory beanFactory:Spring的beanFactory
# HttpMessageConverters 找到所有的HttpMessageConverters
# ResourceHandlerRegistrationCustomizer 找到资源处理器的自定义器 <-
# DispatcherServletPath
# ServletRegistrationBean 给应用注册Servlet、Filter等

public WebMvcAutoConfigurationAdapter(ResourceProperties resourceProperties, WebProperties webProperties, WebMvcProperties mvcProperties, ListableBeanFactory beanFactory, ObjectProvider messageConvertersProvider, ObjectProvider resourceHandlerRegistrationCustomizerProvider, ObjectProvider dispatcherServletPath, ObjectProvider> servletRegistrations) {
    this.resourceProperties = (Resources)(resourceProperties.hasBeenCustomized() ? resourceProperties : webProperties.getResources());
    this.mvcProperties = mvcProperties;
    this.beanFactory = beanFactory;
    this.messageConvertersProvider = messageConvertersProvider;
    this.resourceHandlerRegistrationCustomizer = (WebMvcAutoConfiguration.ResourceHandlerRegistrationCustomizer)resourceHandlerRegistrationCustomizerProvider.getIfAvailable();
    this.dispatcherServletPath = dispatcherServletPath;
    this.servletRegistrations = servletRegistrations;
    this.mvcProperties.checkConfiguration();
}

在该内部类中找到静态资源配置的处理器代码:

public void addResourceHandlers(ResourceHandlerRegistry registry) {
	// spring.resources.add-mappings=false的话,静态资源就无法访问(默认是true)
    if (!this.resourceProperties.isAddMappings()) {
        logger.debug("Default resource handling disabled");
    } else {
    	// 默认注册/webjars/**匹配到classpath:/meta-INF/resources/webjars/路径
        this.addResourceHandler(registry, "/webjars/**", "classpath:/meta-INF/resources/webjars/");
        // 再注册spring.mvc.static-path-pattern匹配到spring.web.resources.static-locations路径的静态资源访问,如果用户配置文件没写也有默认值(/**和四个静态资源文件夹,见下一段代码)
        this.addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> {
            registration.addResourceLocations(this.resourceProperties.getStaticLocations());
            if (this.servletContext != null) {
                ServletContextResource resource = new ServletContextResource(this.servletContext, "/");
                registration.addResourceLocations(new Resource[]{resource});
            }

        });
    }
}
// 节选spring.web.resources.static-locations默认文件夹
public static class Resources {
	private static final String[]CLASSPATH_RESOURCE_LOCATIONS = new String[]{"classpath:/meta-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/"};
	private String[] staticLocations;
	
	public Resources() {
	    this.staticLocations = CLASSPATH_RESOURCE_LOCATIONS;
	}
}

同时在内部类中还可以看到欢迎页的处理规则:

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

WelcomePageHandlerMapping(TemplateAvailabilityProviders templateAvailabilityProviders, ApplicationContext applicationContext, Resource welcomePage, String staticPathPattern) {
	// 只能找到spring.mvc.static-path-pattern为/**(默认)的欢迎页,如果自己配置了访问前缀,就找不到index.html了
    if (welcomePage != null && "/**".equals(staticPathPattern)) {
        logger.info("Adding welcome page: " + welcomePage);
        this.setRootViewName("forward:index.html");
    } else if (this.welcomeTemplateExists(templateAvailabilityProviders, applicationContext)) {
    	// 如果没有找到符合条件的静态资源index.html,就去Controller找/index
        logger.info("Adding welcome page template: index");
        this.setRootViewName("index");
    }
}
二、请求参数处理 1.REST请求映射
  • @xxxMapping
  • Rest风格支持(使用HTTP请求方式动词来表示对资源的操作)
    • 以前: /getUser /deleteUser /editUser /saveUser
    • 现在: /user 然后以不同的请求方式-> GET DELETE PUT POST
    • 核心Filter HiddenHttpMethodFilter
    • 用法: 表单method=post, 隐藏域_method=put
    @RequestMapping(value = "/user", method = RequestMethod.GET)
    public String getUser(){
        return "GET";
    }
    @RequestMapping(value = "/user", method = RequestMethod.POST)
    public String saveUser(){
        return "SAVE";
    }
    @RequestMapping(value = "/user", method = RequestMethod.PUT)
    public String putUser(){
        return "PUT";
    }
    @RequestMapping(value = "/user", method = RequestMethod.DELETE)
    public String delUser(){
        return "DELETE";
    }
    
    // 默认是关闭掉隐藏域过滤器的
    @Bean
    @ConditionalOnMissingBean({HiddenHttpMethodFilter.class})
    @ConditionalOnProperty(
        prefix = "spring.mvc.hiddenmethod.filter",
        name = {"enabled"},    
        matchIfMissing = false
    )
    public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
        return new OrderedHiddenHttpMethodFilter();
    }
    
    # 在配置文件中显式开启该过滤器
    spring:
      mvc:
        hiddenmethod:
          filter:
            enabled: true # 开启页面表单的REST, 因为表单只能写POST请求, 客户端发送的请求可以直接是PUT, 则无需开启
    
    然后就可以通过Rest方式进入Controller同一个URL的不同method处理了
Rest原理(表单提交要使用REST时):
  • 表单提交会带上
  • 请求过来被OrderedHiddenHttpMethodFilter拦截
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        HttpServletRequest requestToUse = request;
        // 首先必须是POST请求
        if ("POST".equals(request.getMethod()) && request.getAttribute("javax.servlet.error.exception") == null) {
        	// methodParam = "_method";然后拿到隐藏域的_method的值
            String paramValue = request.getParameter(this.methodParam);
            if (StringUtils.hasLength(paramValue)) {
            	// 然后把_method内的单词转为大写
                String method = paramValue.toUpperCase(Locale.ENGLISH);
                // ALLOWED_METHODS = [PUT, DELETE, PATCH];
                if (ALLOWED_METHODS.contains(method)) {
                	// 进行了一个包装, 把符合要求的三种扩展请求方式, 包装进原请求
                    requestToUse = new HiddenHttpMethodFilter.HttpMethodRequestWrapper(request, method);
                    // 见下段代码
                }
            }
        }
        // 最后带着包装后的请求, 执行过滤器链, 完成了REST风格的扩展
        filterChain.doFilter((ServletRequest)requestToUse, response);
    }
    
    public HttpMethodRequestWrapper(HttpServletRequest request, String method) {
        super(request);
        this.method = method;
    }
    
Rest(使用客户端工具):
  • 如PostMan可以直接发送PUT, DELETE等请求, 无需Filter
扩展注解
// @RequestMapping(value = "/user", method = RequestMethod.GET)
@GetMapping("/user")
public String getUser() {
    return "GET";
}

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

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

// @RequestMapping(value = "/user", method = RequestMethod.DELETE)
@DeleteMapping("/user")
public String delUser() {
    return "DELETE";
}
2.请求分发


接下来我们研究doDispatch()方法:

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
	// 	...省略若干代码...
	// 找到当前请求使用哪个Handler处理(xxController.xxMethod())
	mappedHandler = this.getHandler(processedRequest);
	// 	...省略若干代码...
}
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
	// HandlerMappings保存了/url -> xxController.xxMethod() 的映射
    if (this.handlerMappings != null) {
        Iterator var2 = this.handlerMappings.iterator();

        while(var2.hasNext()) {
            HandlerMapping mapping = (HandlerMapping)var2.next();
            HandlerExecutionChain handler = mapping.getHandler(request);
            if (handler != null) {
                return handler;
            }
        }
    }
    return null;
}


默认有5个HandlerMapping, 比如我们熟悉的WelcomePageHandlerMapping内保存了/路径 -> /indexorclasspath:index.html的映射。

接下来我们的主角是RequestMappingHandlerMapping,它保存了所有@RequestMapping注解和handler的映射规则(在应用启动过程中,Spring扫描了所有Controller,并且将其信息保存到RequestMappingHandlerMapping中)。

比如我请求的是/user在匹配过程中, 会先根据路径匹配

然后发现有四个可以匹配到的路径, 再慢慢匹配Method, 直到最后拿到唯一确定的Handler返回给doDispatch()的mappedHandler,如果最终还有多个匹配的,就会报错。

总结
  • SpringBoot自动配置欢迎页的HandlerMapping,访问/能访问到index.html或者/index
  • SpringBoot自动配置了默认的RequestMappingHandlerMapping
  • 请求进来,挨个尝试所有HandlerMapping看是否有请求信息
    • 如果有就找到这个请求对应的handler
    • 如果没有就下一个HandlerMapping
  • 我们需要一些自定义的映射处理,我们也可以自己给容器中放HandlerMapping
3.普通参数与基本注解

@PathVariable 路径变量

@GetMapping("/car/{id}/window/{brand}")
public String getCar(@PathVariable("id") String id,
                     @PathVariable("brand") String brand,
                     @PathVariable Map pv){
    return id + "n" + brand + "n" + pv.toString();
}

http://localhost:8089/car/no1/window/BYD
请求得到 =>
no1 BYD {id=no1, brand=BYD}

@RequestHeader 获取请求头

@GetMapping("/test")
public String getCar(@RequestHeader("User-Agent") String userAgent,
                     @RequestHeader Map rh){
    return userAgent + "n" + rh.toString();
}

http://localhost:8089/test
请求得到 =>
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36 {host=localhost:8089, connection=keep-alive, pragma=no-cache, cache-control=no-cache, sec-ch-ua=" Not A;Brand";v="99", "Chromium";v="96", "Google Chrome";v="96", sec-ch-ua-mobile=?0, sec-ch-ua-platform="Windows", upgrade-insecure-requests=1, user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36, accept=text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/ *;q=0.8,application/signed-exchange;v=b3;q=0.9, sec-fetch-site=none, sec-fetch-mode=navigate, sec-fetch-user=?1, sec-fetch-dest=document, accept-encoding=gzip, deflate, br, accept-language=zh-CN,zh;q=0.9,en;q=0.8, cookie=Webstorm-66b7c9ab=246565bc-c7a7-4785-ae87-22d92088296e; Idea-ca2b6365=a9f831df-c5d1-4aa9-b53c-a9adebb95a7a}

@RequestParam 获取请求参数

@GetMapping("/test1")
public String test1(@RequestParam("name") String name,
                    @RequestParam("age") String age,
                    @RequestParam("hobby") List hobby,
                    @RequestParam Map rp){
    return name + " " + age + " " + hobby.toString() + " " + rp.toString();
}

http://localhost:8089/test1?name=Aomrsou&age=23&hobby=篮球&hobby=足球&hobby=羽毛球
请求得到 =>
Aomrsou 23 [篮球, 足球, 羽毛球] {name=Aomrsou, age=23, hobby=篮球}

@cookievalue 获取cookie值


getcookie
@GetMapping("/test2")
public String test2(@cookievalue("name") cookie cookie){
    return cookie.getName() + " is " + cookie.getValue();
}

点击a链接
请求得到 =>
name is Aomrsou

@RequestBody 获取请求体[POST请求]

  • String接收所有参数(会得到一坨键值对):
    @PostMapping("/test3")
    public String test3(@RequestBody String content){
        return content;
    }
    
    提交表单得到 =>
    name=Aomrsou&age=23
    
  • 对象接收所有参数(会自动匹配名字到变量上):
    @PostMapping("/test3")
    public String test3(@RequestBody User user){
        return user.toString();
    }
    
    public class User {
        private String name;
        private Integer age;
    }
    
    用PostMan发送Content type为'application/json'的POST请求,然后拿对象去接收 =>
    User{name='Aomrsou', age=23}
    

@RequestAttribute 获取请求域中的值

@Controller
public class HiController {

    @GetMapping("/goto")
    public String go(HttpServletRequest request){
        request.setAttribute("msg", "成了");
        return "forward:/success"; // 转发到success, 所以还是同一次请求
    }

    @ResponseBody
    @GetMapping("/success")
    public String to(@RequestAttribute("msg") String msg,
                     HttpServletRequest request){
        return msg + " " + request.getAttribute("msg");
    }
}

http://localhost:8089/goto
请求得到 =>
成了 成了

@MatrixVariable 获取矩阵变量中的值
Spring中默认是关掉了矩阵变量功能,我们需要自己实现,给UrlPathHelper类中的RemoveSemicolonContent(移除分号后的内容)属性设置为false

// 写法1:实现WebMvcConfigurer接口,然后重写configurePathMatch方法
@Configuration(proxyBeanMethods = true)
public class MyConfig implements WebMvcConfigurer{
    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
        UrlPathHelper urlPathHelper = new UrlPathHelper();
        urlPathHelper.setRemoveSemicolonContent(false);
        configurer.setUrlPathHelper(urlPathHelper);
    }
}
// 写法2: 直接@Bean给Spring注册一个WebMvcConfigurer实例
@Configuration(proxyBeanMethods = true)
public class MyConfig {
    @Bean
    public WebMvcConfigurer webMvcConfigurer(){
        return new WebMvcConfigurer() {
            @Override
            public void configurePathMatch(PathMatchConfigurer configurer) {
                UrlPathHelper urlPathHelper = new UrlPathHelper();
                urlPathHelper.setRemoveSemicolonContent(false);
                configurer.setUrlPathHelper(urlPathHelper);
            }
        };
    }
}
// 矩阵变量的内容必须写在路径{}内
@GetMapping("/test4/{path}")
public Maptest4(@MatrixVariable("name") String name,
                 @MatrixVariable("hobby") List hobby,
                 @PathVariable("path") String path){
    Map map = new HashMap<>();
    map.put("name", name);
    map.put("path", path);
    map.put("hobby", hobby.toString());
    return map;
}

http://localhost:8089/test4/whoami;name=Aomrsou;hobby=eat;hobby=sleep;hobby=play
请求得到 => 
{"path":"whoami","name":"Aomrsou","hobby":"[eat, sleep, play]"}
// 如果有多个同名的矩阵变量,还可以指定pathVar来进行区分
@GetMapping("/test5/{bossId}/{empId}")
public Map test5(@MatrixVariable(value = "age", pathVar = "bossId") String bossAge,
                 @MatrixVariable(value = "age", pathVar = "empId") String empAge){
    Map map = new HashMap<>();
    map.put("bossAge", bossAge);
    map.put("empAge", empAge);
    return map;
}

http://localhost:8089/test5/1;age=10/2;age=20
请求得到 =>
{"bossAge":"10","empAge":"20"}
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/687043.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号