IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> SpringBoot-2-web开发(上) -> 正文阅读

[Java知识库]SpringBoot-2-web开发(上)

1 web场景

1.1 静态资源访问

只要将静态资源放在类路径下,如static、public、resources、META-INF/resources中
也可以在application.yaml中配置静态资源的位置

spring:
  resources:
    static-locations: classpath:[/pic/]

请添加图片描述
就可以直接通过localhost:8080/ + 静态资源名 访问静态资源
请添加图片描述
当请求先到来时,先找Controller看能不能处理,如果不能交给静态资源处理器
静态资源处理器如果能在static、public、resources、META-INF/resources中找到则显示
如果找不到则是404

一般访问静态资源都需要加前缀,以区分访问静态资源还是动作请求
可以在application.yaml中配置访问前缀:

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

此时访问静态资源需要以res/开头请添加图片描述

1.2 welcome&favicon

SpringBoot还提供欢迎页
只需将index.html文件放在静态资源路径下即可
注意可以配置静态资源路径,但不能配置静态资源访问前缀,否则导致index.html无法访问
请添加图片描述
同时还可以为访问页设置小图标,只需将小图标命名为favicon.ico
并存放在静态资源路径下,即可自动生效

1.3 静态资源配置原理

静态资源的配置原理也属于SpringBoot的自动配置过程

1,首先所有的自动配置项对应的java文件都以xxxAutoConfiguration结尾
这些文件都可以在spring-boot-autoconfigure包下

请添加图片描述
2,静态资源属于web范围,在org.springframework.boot.autoconfigure包下寻找
在web包下的servlet包里,可以看到有许多AutoConfiguration
如DispatcherServletAutoConfiguration,以及编码的HttpEncodingAutoConfiguration…
请添加图片描述
3,静态资源所属配置在WebMvcAutoConfiguration中
在该类中可以看到一个内部类WebMvcAutoConfigurationAdapter请添加图片描述
可以看到该配置类带有@EnableConfigurationProperties注解
即该配置类的相关属性关联了两个配置文件WebMvcProperties和ResourceProperties
进入WebMvcProperties,可以看到它带有@ConfigurationProperties属性
且prefix为spring.mvc 意味着我们可以在properties文件或yaml文件中
以spring.mvc开头来覆盖底层默认的配置
同理ResourceProperties绑定了spring.resources配置
请添加图片描述
4,内部类WebMvcAutoConfigurationAdapter只有一个有参构造
这代表有参构造需要的所有参数都要从容器中找

public WebMvcAutoConfigurationAdapter(ResourceProperties resourceProperties, WebMvcProperties mvcProperties, ListableBeanFactory beanFactory, ObjectProvider<HttpMessageConverters> messageConvertersProvider, ObjectProvider<WebMvcAutoConfiguration.ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider, ObjectProvider<DispatcherServletPath> dispatcherServletPath, ObjectProvider<ServletRegistrationBean<?>> servletRegistrations) {
            this.resourceProperties = resourceProperties;
            this.mvcProperties = mvcProperties;
            this.beanFactory = beanFactory;
            this.messageConvertersProvider = messageConvertersProvider;
            this.resourceHandlerRegistrationCustomizer = (WebMvcAutoConfiguration.ResourceHandlerRegistrationCustomizer)resourceHandlerRegistrationCustomizerProvider.getIfAvailable();
            this.dispatcherServletPath = dispatcherServletPath;
            this.servletRegistrations = servletRegistrations;
        }

先看其中的两项赋值this.resourceProperties = resourceProperties;
和this.mvcProperties = mvcProperties;
即从容器中取出WebMvcProperties和ResourceProperties实例赋值
WebMvcProperties和ResourceProperties又分别绑定了配置spring.mvc和spring.resources
当SpringBoot启动时,加载默认的WebMvcProperties和ResourceProperties配置
如在yaml文件或application文件中使用了spring.mvc和spring.resources配置,则会加载新配置

5,回到静态资源配置,resourceHandlerRegistrationCustomizer也是从容器中获得的
接着看内部类中关于静态资源配置的方法

	public void addResourceHandlers(ResourceHandlerRegistry registry) {
		    // 获取ResourceProperties中的isAddMappings属性 默认为true
		    // ResourceProperties对应yaml中的spring.resources配置
		    // addMappings属性决定是否禁用默认静态资源配置
            if (!this.resourceProperties.isAddMappings()) {
                logger.debug("Default resource handling disabled");
            } else {
                Duration cachePeriod = this.resourceProperties.getCache().getPeriod();
                CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl();
                // 配置webjars相关规则
                if (!registry.hasMappingForPattern("/webjars/**")) {
                    this.customizeResourceHandlerRegistration(registry.addResourceHandler(new String[]{"/webjars/**"}).addResourceLocations(new String[]{"classpath:/META-INF/resources/webjars/"}).setCachePeriod(this.getSeconds(cachePeriod)).setCacheControl(cacheControl));
                }
				// 通过mvcProperties.getStaticPathPattern()配置静态资源路径
				// 该方法对应的是WebMvcProperties的staticPathPattern属性
				// 可以在yaml中配置静态资源路径
                String staticPathPattern = this.mvcProperties.getStaticPathPattern();
                if (!registry.hasMappingForPattern(staticPathPattern)) {
                // 具体的路径在resourceProperties.getStaticLocations()中
                // 是一个数组默认 "classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/"
                    this.customizeResourceHandlerRegistration(registry.addResourceHandler(new String[]{staticPathPattern}).addResourceLocations(WebMvcAutoConfiguration.getResourceLocations(this.resourceProperties.getStaticLocations())).setCachePeriod(this.getSeconds(cachePeriod)).setCacheControl(cacheControl));
                }

            }
        }

2 请求处理

2.1 Rest映射

对现有的请求,我们在Controller层常用@RequestMapping接收
并使用Rest风格来确定动作的执行方式(使用HTTP请求方式动词来表示对资源的操作)

如对user的操作
GET		获取用户
DELETE	删除用户
PUT		修改用户
POST	保存用户

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

但是html中的form表单的method只支持get和post,并不支持put和delete
在SpringMVC中需要配置HiddenHttpMethodFilter以开启Rest风格
但在SpringBoot中 WebMvcAutoConfiguration中,此项功能已经默认配置
请添加图片描述并需要在yaml中开启该功能

spring:
  mvc:
    hiddenmethod:
      filter:
        enabled: true

想要使用PUT和DELETE请求,只需在html界面中的form表单中新增一个input标签即可

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

// 对于put请求和delete请求 仍然使用post 但需增加新的input标签 
<form action = "/user" method = "post">
	<input name = "_method" type = "hidden" value = "DELETE" >
	<input value = "REST-DELETE 提交" type = "submit" />
</form>

其原理大致是:
请求会携带_method参数,到达服务器后会被HiddenHttpMethodFilter拦截
执行doFilterInternal方法

	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) {
        // 获取到_method的值
            String paramValue = request.getParameter(this.methodParam);
            // _method的值不为空
            if (StringUtils.hasLength(paramValue)) {
            	// 强转大写
                String method = paramValue.toUpperCase(Locale.ENGLISH);
                // 查看列表中是否有method对应的value
                // ALLOWED_METHODS列表已被静态初始化 其中包含put delete patch
                if (ALLOWED_METHODS.contains(method)) {
                	// 将原生request 使用装饰者模式创建了新请求 其中的method已被更换
                    requestToUse = new HiddenHttpMethodFilter.HttpMethodRequestWrapper(request, method);
                }
            }
        }

		// 使用包装后的request继续chain的执行
        filterChain.doFilter((ServletRequest)requestToUse, response);
    }

2.2 请求映射原理

当封装好新的request后,再来看请求如何找到Controller层对应的方法
所有的请求首先都会到达DispatcherServlet,在DispatcherServlet的父类中FrameworkServlet
重写了超类HttpServlet中的doGet和doPost及doPut和doDelete方法
请添加图片描述
这些方法又调用了processRequest方法
请添加图片描述
该方法的核心处理在于doService方法
请添加图片描述
该方法是一个抽象方法,由子类DispatcherServlet实现

	protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
        ...

        try {
        	// 其核心在于doDispatch方法
            this.doDispatch(request, response);
        } finally {
            if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted() && attributesSnapshot != null) {
                this.restoreAttributesAfterInclude(request, attributesSnapshot);
            }

        }

    }

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

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

                try {
                	// 检查是否为文件上传请求
                    processedRequest = this.checkMultipart(request);
                    multipartRequestParsed = processedRequest != request;
					// 核心方法 
					// 能找到哪个Handler(Controller)可以处理该请求
                    mappedHandler = this.getHandler(processedRequest);
                    if (mappedHandler == null) {
                        this.noHandlerFound(processedRequest, response);
                        return;
                    }

		...
    }
    
	protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
		// 从handlerMappings中匹配
		// handlerMappings是一个List<HandlerMapping> 由initHandlerMappings初始化
        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;
    }

打断点进行调试时,发现handlerMappings中此时已经有5个HandlerMapping
请添加图片描述
此时进入了RequestMappingHandlerMapping,即对应@RequestMapping注解
在其中可以看到我们发来的GET /user被注册在其中
并确认了由HelloController的getUser()方法处理
请添加图片描述
类似RequestMappingHandleMapping这样的handle都是Bean被存放在容器中
同样的,可以在webMvcAutoConfiguration中看到默认配置
如果需要一些自定义的映射处理,也可以向容器中存放自定义的HandleMapping
请添加图片描述

2.3 普通参数与常用注解

2.3.1 @PathVariable、@RequestHeader、@RequestParam…

1、@PathVariable:

@PathVariable
获取路径变量

使用方式:

	@GetMapping("/pet/{id}/owner/{username}")
    public String getPet(@PathVariable("id") Integer id, 
    						@PathVariable("username") String username) {
        Person person = new Person();
        person.setName("tom");
        return new Pet("caka", 6, person).toString();
    }

2、@RequestHeader:

@RequestHeader
用于获取请求头中的内容,如host、浏览器信息等
可以指明获取一个,也可以通过Map<String, String>获取全部

使用方式:

	@GetMapping("/header")
    public String getPet(@RequestHeader("User-Agent") String userAgent,
                         @RequestHeader Map<String, String> header) {

        System.out.println(userAgent);
        for(String k : header.keySet()) {
            System.out.println(k + " : " + header.get(k));
        }

        return "";
    }

3、@RequestParam:

@RequestParam
获取请求参数 如url: "pet/1/person/1?name=caka&age=18"

使用方式:

	@GetMapping("/pet")
    public String getPet(@RequestParam("name") Integer name,
                         @RequestParam("age") String age) {

        System.out.println("age: " + age + " name: " + name);
        return "";
    }

	获取全部参数:
	@GetMapping("/pet")
    public String getPet(@RequestParam Map<String, String> params) {
        return "";
    }

4、@CookieValue:

@CookieValue
获取cookie中存储的信息或Cookie对象

使用方式:

	@GetMapping("/pet")
    public String getPet(@CookieValue("_ga") String _ga) {
        System.out.println(_ga);
        return "";
    }

	直接获取Cookie对象
	@GetMapping("/pet")
    public String getPet(@CookieValue("_ga") Cookie _ga) {
        System.out.println(_ga);
        return "";
    }

5、@RequestBody:

@RequestBody
获取请求体中的内容,如表单中内容

使用方式:

	@PostMapping("/save")
    public String doSomeThing(@RequestBody String content) {
        System.out.println(content);
        return "";
    }

2.3.2 @RequestAttribute、@MatrixVariable

1、@RequestAttribute:

@RequestAttribute
获取请求域中的值

使用方式:

	@ResponseBody
    @GetMapping("/success")
    public String success(@RequestAttribute("msg") String msg,
                          @RequestAttribute("code") Integer code) {

        return "success";
    }
	
	获取ServletRequest后再get
	@ResponseBody
    @GetMapping("/success")
    public String success(HttpServletRequest request) {
        
        String msg = (String) request.getAttribute("msg");
        Integer code = (Integer) request.getAttribute("code");

        return "success";
    }

2、@MatrixVariable

获取矩阵变量的值
常用的 /pet/{path}?xxx=xxx&yyy=yyy 称为queryString
以;分隔属性的类似/pet/sell;age=10;color=white,yellow 称为矩阵变量

当cookie被禁用时,可以使用矩阵变量将cookie中的内容存储其中 
以供后端解析并与queryString区分

SpringBoot默认禁用了矩阵变量的功能,为此需要在yaml中开启
先看下WebMvcAutoConfig中关于路径的处理

	public void configurePathMatch(PathMatchConfigurer configurer) {
            if (this.mvcProperties.getPathmatch().getMatchingStrategy() == MatchingStrategy.PATH_PATTERN_PARSER) {
                configurer.setPatternParser(WebMvcAutoConfiguration.pathPatternParser);
            }

            configurer.setUseSuffixPatternMatch(this.mvcProperties.getPathmatch().isUseSuffixPattern());
            configurer.setUseRegisteredSuffixPatternMatch(this.mvcProperties.getPathmatch().isUseRegisteredSuffixPattern());
            this.dispatcherServletPath.ifAvailable((dispatcherPath) -> {
                String servletUrlMapping = dispatcherPath.getServletUrlMapping();
                if (servletUrlMapping.equals("/") && this.singleDispatcherServlet()) {
                    UrlPathHelper urlPathHelper = new UrlPathHelper();
                    urlPathHelper.setAlwaysUseFullPath(true);
                    configurer.setUrlPathHelper(urlPathHelper);
                }

            });
        }

其中涉及到了一个UrlPathHelper,该类中存在属性removeSemicolonContent
且默认值设置了true,即我们的矩阵变量路径中的;会被截取并删除
请添加图片描述
为此需要关闭该功能,有两种方式:

1,自定义Config类,实现WebMvcConfigurer接口,重写configurePathMatch方法
@Configuration(proxyBeanMethods = true)
public class MyConfig implements WebMvcConfigurer {

  	@Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
        UrlPathHelper pathHelper = new UrlPathHelper();
        // 关闭路径移除分号功能
        pathHelper.setRemoveSemicolonContent(false);
        configurer.setUrlPathHelper(pathHelper);
    }

}

2,在自定义Config类中,创建一个新的WebMvcConfigurerBean 
重新实现configurePathMatch方法即可
@Configuration(proxyBeanMethods = true)
public class MyConfig {

    @Bean
    public WebMvcConfigurer webMvcConfigurer() {
        return new WebMvcConfigurer() {
            @Override
            public void configurePathMatch(PathMatchConfigurer configurer) {
                UrlPathHelper pathHelper = new UrlPathHelper();
                // 关闭路径移除分号功能
                pathHelper.setRemoveSemicolonContent(false);
                configurer.setUrlPathHelper(pathHelper);
            }
        };
    }

}

使用方式:

	// pets/sell;age=10;color=yellow,white
    @GetMapping("/pets/{path}")
    public Map petSell(@MatrixVariable("age") Integer age,
                       @MatrixVariable("color") List<String> color) {
        Map<String, Object> map = new HashMap<>();
        map.put("age", age);
        for(String s : color) {
            map.put(s, "");
        }

        return map;
    }

2.4 参数处理原理

所有的请求都会通过DispatcherServlet的doDispatcher方法来处理
首先通过getHandler找到对应的Controller以及其中的方法
请添加图片描述
接着找到对应的Adapter,这里是RequestMappingHandlerAdapter
请添加图片描述
接着沿着Adapter的handle方法一路向下调用,来到RequestMappingHandlerAdapter的invokeHandlerMethod方法,执行方法前会看到设置ArgumentResolvers的过程
在这里设置了27个参数解析器,如RequestParamMethodArgumentResolver
该参数解析器就是用来处理带@RequestParam注解的参数
请添加图片描述
每个解析器都实现了HandlerMethodArgumentResolver接口
实现了其中两个方法,一个判断参数是否支持由当前解析器解析
如果支持则调用另一个方法解析参数,还原成Object类型数据,后续用于反射执行
请添加图片描述
接着配置返回值处理器,返回值处理器有15种,如ModelAndView
请添加图片描述
每个返回值处理器都实现了HandlerMethodReturnValueHandler接口
其中两个方法分别用来判断返回值类型和处理返回值
请添加图片描述
接着一路向下,来到真正的执行过程invokeAndHandle
请添加图片描述
请添加图片描述
进入invokeAndHandle方法后,会反射调用Controller层中对应的方法
首先使用提前设置好的参数解析器依次解析参数,组装Object[]
请添加图片描述
再通过doInvoke方法反射执行

	@Nullable
    protected Object doInvoke(Object... args) throws Exception {
        Method method = this.getBridgedMethod();

        try {
            return KotlinDetector.isSuspendingFunction(method) 
            ? CoroutinesUtils.invokeSuspendingFunction(method, this.getBean(), args) 
            : method.invoke(this.getBean(), args);
        } catch (IllegalArgumentException var5) {
        ...

执行完毕后,再回到invokeAndHandle方法中
再完成最后的返回值处理,整个调用过程结束
请添加图片描述

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-04-04 11:57:15  更:2022-04-04 11:59:41 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 8:04:43-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码