• JavaSSM笔记(二)SpringMvc基础


    在SpringMVC阶段,你就能逐渐够体会到Spring框架为我们带来的便捷之处了。

    此阶段,我们将再次回到Tomcat的Web应用程序开发中,去感受SpringMVC为我们带来的巨大便捷。

    一、MVC理论基础

    在之前,我们给大家讲解了三层架构,包括:
    在这里插入图片描述
    每一层都有着各自的职责,其中最关键的当属表示层,因为它相当于就是直接与用户的浏览器打交道的一层,并且所有的请求都会经过它进行解析,然后再告知业务层进行处理,任何页面的返回和数据填充也全靠表示层来完成,因此它实际上是整个三层架构中最关键的一层,而在之前的实战开发中,我们编写了大量的Servlet(也就是表示层实现)来处理来自浏览器的各种请求,但是我们发现,仅仅是几个很小的功能,以及几个很基本的页面,我们都要编写将近十个Servlet,如果是更加大型的网站系统,比如淘宝、B站,光是一个页面中可能就包含了几十甚至上百个功能,想想那样的话写起来得多恐怖。

    因此,SpringMVC正是为了解决这种问题而生的,它是一个非常优秀的表示层框架(在此之前还有一个叫做Struts2的框架,但是现阶段貌似快凉透了),采用MVC思想设计实现。

    MVC解释如下:

    • M是指业务模型(Model):通俗的讲就是我们之前用于封装数据传递的实体类
    • V是指用户界面(View):一般指的是前端页面
    • C则是控制器(Controller):控制器就相当于Servlet的基本功能,处理请求,返回响应。

    在这里插入图片描述
    SpringMVC正是希望这三者之间进行解耦,实现各干各的,更加精细地划分对应的职责。最后再将View和Model进行渲染,得到最终的页面并返回给前端(就像之前使用Thymeleaf那样,把实体数据对象和前端页面都给到Thymeleaf,然后它会将其进行整合渲染得到最终有数据的页面,而本教程也会使用Thymeleaf作为视图解析器进行讲解)

    二、配置环境并搭建项目

    由于SpringMVC还没有支持最新的Tomcat10(主要是之前提到的包名问题,神仙打架百姓遭殃)所以我们干脆就再来配置一下Tomcat9环境,相当于回顾一下。

    下载地址:https://tomcat.apache.org/download-90.cgi

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    添加SpringMVC依赖

    (这个SpringMVC的依赖包含了我们之前Spring核心组建的依赖)

    <dependency>
        <groupId>org.springframeworkgroupId>
        <artifactId>spring-webmvcartifactId>
        <version>5.3.13version>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    (然后我们把index.jsp删除)

    接着我们需要配置一下web.xml,将DispatcherServlet(SpringMVC提供的)(现在我们希望所有请求都走这个DispatcherServlet)替换掉Tomcat自带的Servlet,这里url-pattern需要写为/,即可完成替换:

    (web.xml)

    
    <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
             version="4.0">
        <servlet>
            <servlet-name>mvcservlet-name>
            <servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
        servlet>
        <servlet-mapping>
            <servlet-name>mvcservlet-name>
            <url-pattern>/url-pattern>
        servlet-mapping>
    web-app>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    接着需要为整个Web应用程序配置一个Spring上下文环境(也就是容器),因为SpringMVC是基于Spring开发的,它直接利用Spring提供的容器来实现各种功能,这里我们直接使用注解方式进行配置,不再使用XML配置文件

    (然后我们把HelloServlet删除)

    编写一下最基本的配置类:

    (com.example.config.WebConfiguration)

    @Configuration
    public class WebConfiguration {
    
    }
    
    • 1
    • 2
    • 3
    • 4

    (在web.xml中)
    (这个WebConfiguration是配置类名字,记得更改)

    <init-param>
        <param-name>contextConfigLocationparam-name>
        <param-value>com.example.config.WebConfigurationparam-value>
    init-param>
    <init-param>
        <param-name>contextClassparam-name>
        <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContextparam-value>
    init-param>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    (然后变成:)

    
    <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
             version="4.0">
        <servlet>
            <servlet-name>mvcservlet-name>
            <servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
            <init-param>
                <param-name>contextConfigLocationparam-name>
                <param-value>com.example.config.WebConfigurationparam-value>
            init-param>
            <init-param>
                <param-name>contextClassparam-name>
                <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContextparam-value>
            init-param>
        servlet>
        <servlet-mapping>
            <servlet-name>mvcservlet-name>
            <url-pattern>/url-pattern>
        servlet-mapping>
    web-app>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    (然后还要在WebConfiguration那里点一下配置应用程序上下文
    在这里插入图片描述

    如果还是想使用XML配置文件进行配置,那么可以直接这样写:

    (还是加在web.xml的servlet标签中,替换刚才的contextConfigLocation所在的init-param)
    (然后把下面的contextClass所在的init-param去掉)
    (配置文件名称 比如 test.xml)

    <init-param>
        <param-name>contextConfigLocationparam-name>
        <param-value>配置文件名称param-value>
    init-param>
    
    • 1
    • 2
    • 3
    • 4

    如果你希望完完全全丢弃配置文件(也就是web.xml也不要,把web.xml删除),可以直接添加一个类Tomcat会在类路径中查找实现ServletContainerInitializer 接口的类,如果发现的话,就用它来配置Servlet容器,Spring提供了这个接口的实现类SpringServletContainerInitializer , 通过@HandlesTypes(WebApplicationInitializer.class)设置,这个类反过来会查找实现WebApplicationInitializer 的类,并将配置的任务交给他们来完成,因此直接实现接口即可:

    再创建一个com.example.config.MainConfiguration

    @Configuration
    public class MainConfiguration {
        
    }
    
    • 1
    • 2
    • 3
    • 4

    (现在我们把web.xml删除)

    (com.example.config.MainInitializer)

    public class MainInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
        @Override
        protected Class<?>[] getRootConfigClasses() {
            return new Class[]{MainConfiguration.class};   //基本的Spring配置类,一般用于业务层配置
        }
    
        @Override
        protected Class<?>[] getServletConfigClasses() {
            return new Class[]{WebConfiguration.class};  //配置DispatcherServlet的配置类、主要用于Controller等配置
        }
    
        @Override
        protected String[] getServletMappings() {
            return new String[]{"/"};    //匹配路径,与上面一致,与配置文件中写的是一样的;直接就是根目录 
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    后面我们都采用无XML配置方式进行讲解。

    (有两个容器(上面我们在MainInitializer中也需要配置两个),分别是:)
    在这里插入图片描述
    这样,就完成最基本的配置了,现在任何请求都会优先经过DispatcherServlet进行集中处理,下面我们会详细讲解如何使用它。

    三、Controller控制器

    有了SpringMVC之后,我们不必再像之前那样一个请求地址创建一个Servlet了,它使用DispatcherServlet替代Tomcat为我们提供的默认的静态资源Servlet,也就是说,现在所有的请求除了jsp,因为Tomcat还提供了一个jsp的Servlet(是处理静态的Servlet被替掉了))都会经过DispatcherServlet进行处理。

    那么DispatcherServlet会帮助我们做什么呢?
    在这里插入图片描述
    根据图片我们可以了解,我们的请求到达Tomcat服务器之后,会交给当前的Web应用程序进行处理,而SpringMVC使用DispatcherServlet处理所有的请求,也就是说它被作为一个统一的访问点,所有的请求全部由它来进行调度

    当一个请求经过DispatcherServlet之后,会先走HandlerMapping,它会将请求映射为HandlerExecutionChain,依次经过HandlerInterceptor有点类似于之前我们所学的过滤器,不过在SpringMVC中我们使用的是拦截器,然后再交给HandlerAdapter,根据请求的路径选择合适的控制器进行处理,控制器处理完成之后,会返回一个ModelAndView对象,包括数据模型和视图,通俗的讲就是页面中数据和页面本身(只包含视图名称即可)。

    返回ModelAndView之后,会交给ViewResolver(视图解析器)进行处理,视图解析器会对整个视图页面进行解析,SpringMVC自带了一些视图解析器,但是只适用于JSP页面,我们也可以像之前一样使用Thymeleaf作为视图解析器,这样我们就可以根据给定的视图名称,直接读取HTML编写的页面,解析为一个真正的View。

    解析完成后,就需要将页面中的数据全部渲染到View中,最后返回给DispatcherServlet一个包含所有数据的成形页面,再响应给浏览器,完成整个过程。

    因此,实际上整个过程我们只需要编写对应请求路径的的Controller以及配置好我们需要的ViewResolver即可,之后还可以继续补充添加拦截器,而其他的流程已经由SpringMVC帮助我们完成了。

    1、配置视图解析器和控制器

    首先我们需要实现最基本的页面解析并返回,第一步就是配置视图解析器,这里我们使用Thymeleaf为我们提供的视图解析器,导入需要的依赖:

    <dependency>
        <groupId>org.thymeleafgroupId>
        <artifactId>thymeleaf-spring5artifactId>
        <version>3.0.12.RELEASEversion>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    配置视图解析器非常简单,我们只需要将对应的ViewResolver注册为Bean即可,这里我们直接在配置类中编写:

    (这里的关键是配置templateResolver,
    比如如果是 webapp/WEB-INF/template/index.html,那么就写成

    resolver.setSuffix(".html");
    resolver.setPrefix("/WEB-INF/template/");
    
    • 1
    • 2

    (注意添加三个注解!!!)

    @ComponentScan("com.example.controller")
    @Configuration
    @EnableWebMvc
    public class WebConfiguration {
    
      //我们需要使用ThymeleafViewResolver作为视图解析器,并解析我们的HTML页面
        @Bean
        public ThymeleafViewResolver thymeleafViewResolver(@Autowired SpringTemplateEngine springTemplateEngine){
            ThymeleafViewResolver resolver = new ThymeleafViewResolver();
            resolver.setOrder(1);   //可以存在多个视图解析器,并且可以为他们设定解析顺序
            resolver.setCharacterEncoding("UTF-8");   //编码格式是重中之重
            resolver.setTemplateEngine(springTemplateEngine);   //和之前JavaWeb阶段一样,需要使用模板引擎进行解析,所以这里也需要设定一下模板引擎
            return resolver;
        }
      
      	//配置模板解析器
      	@Bean
        public SpringResourceTemplateResolver templateResolver(){
            SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver();
            resolver.setSuffix(".html");   //需要解析的后缀名称
            resolver.setPrefix("/WEB-INF/template/");   //需要解析的HTML页面文件存放的位置
            return resolver;
        }
      	
      	//配置模板引擎Bean
      	@Bean
        public SpringTemplateEngine springTemplateEngine(@Autowired ITemplateResolver resolver){
            SpringTemplateEngine engine = new SpringTemplateEngine();
            engine.setTemplateResolver(resolver);   //模板解析器,默认即可
            return engine;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    别忘了在Initializer中添加此类作为配置:

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[]{WebConfiguration.class};
    }
    
    • 1
    • 2
    • 3
    • 4

    现在我们就完成了视图解析器的配置,我们接着来创建一个Controller,创建Controller也非常简单,只需在一个上添加一个@Controller注解即可,它会被Spring扫描并自动注册为Controller类型的Bean(在配置类上添加包扫描),然后我们只需要在类中编写方法用于处理对应地址的请求即可:

    (com.example.controller.MainController)
    (建立请求映射,这样的话,我们之后访问这个地址,它就会执行这个方法,然后方法返回ModelAndView,它会根据我们提供的视图的名称进行解析,然后就解析到了我们template下的index.html。这是因为之前我们在WebConfiguration中的templateResolver配置的结果)
    (将在templateResolver中的前缀、后缀和这里这个进行拼接)
    (这个方法名随便,比如另一个页面我们可以让方法名是index2)

    @Controller   //直接添加注解即可
    public class MainController {
    
        // http:localhost:8080/mvc/test
        @RequestMapping("/test")   //直接填写访问路径
        public ModelAndView index(){
            return new ModelAndView("index");  //返回ModelAndView对象,这里填入了视图的名称
          	//返回后会经过视图解析器进行处理
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    我们会发现,打开浏览器之后就可以直接访问我们的HTML页面了。

    页面中的数据我们可以直接向Model进行提供:

    @RequestMapping(value = "/index")
    public ModelAndView index(){
        ModelAndView modelAndView = new ModelAndView("index");
        modelAndView.getModel().put("name", "啊这");
        return modelAndView;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    这样Thymeleaf就能收到我们传递的数据进行解析:
    (注意 xmlns:th=“http://www.thymeleaf.org”)

    DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>Titletitle>
        <script src="static/test.js">script>
    head>
    <body>
        HelloWorld!
        <div th:text="${name}">div>
    body>
    html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    当然,如果仅仅是传递一个页面不需要任何的附加属性(不需要model了hhh,只需要页面view),我们可以直接返回View名称,SpringMVC会将其自动包装为ModelAndView对象

    @RequestMapping(value = "/index")
    public String index(){
        return "index";
    }
    
    • 1
    • 2
    • 3
    • 4

    还可以单独添加一个Model作为形参进行设置,SpringMVC会自动帮助我们传递实例对象:

    (这样也能得到model中的name的值)

    @RequestMapping(value = "/index")
    public String index(Model model){  //这里不仅仅可以是Model,还可以是Map、ModelMap
        model.addAttribute("name", "yyds");
        return "index";
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    这么方便的写法,你就说你爱不爱吧,你爱不爱。

    注意,一定要保证视图名称下面出现横线并且按住Ctrl可以跳转配置才是正确的(最新版IDEA)

    我们的页面中可能还会包含一些静态资源,比如js、css,因此这里我们还需要配置一下,让静态资源通过Tomcat提供的默认Servlet进行解析,我们需要让配置类实现一下WebMvcConfigurer接口,这样在Web应用程序启动时,会根据我们重写方法里面的内容进行进一步的配置:

    (http://localhost:8080/mvc/static/test.js)
    (其中,“**”表示一级或者多级目录,相当于匹配static下所有东西
    (前面那个是请求地址,后面那个是本地文件的路径)

    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();   //开启默认的Servlet
    }
    
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/static/**").addResourceLocations("/WEB-INF/static/");   
      	//配置静态资源的访问路径
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    我们编写一下前端内容:

    (@{/static/test.js}会自动添加web应用程序的名称到链接前面)

    DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>Titletitle>
          
          
        <script th:src="@{/static/test.js}">script>
    head>
    <body>
        HelloWorld!
    body>
    html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    创建test.js并编写如下内容:

    (WEB-INF/static/test.js)

    window.alert("欢迎来到GayHub全球最大同性交友网站")
    
    • 1

    最后访问页面,页面在加载时就会显示一个弹窗,这样我们就完成了最基本的页面配置。相比之前的方式,这样就简单很多了,直接避免了编写大量的Servlet来处理请求。

    (js也成功地响应过来了)

    在这里插入图片描述

    2、@RequestMapping详解

    前面我们已经了解了如何创建一个控制器来处理我们的请求,接着我们只需要在控制器添加一个方法用于处理对应的请求即可,之前我们需要完整地编写一个Servlet来实现,而现在我们只需要添加一个@RequestMapping即可实现,其实从它的名字我们也能得知,此注解就是将请求和处理请求的方法建立一个映射关系,当收到请求时就可以根据映射关系调用对应的请求处理方法,那么我们就来先聊聊@RequestMapping吧,注解定义如下:

    @Mapping
    public @interface RequestMapping {
        String name() default "";
    
        @AliasFor("path")
        String[] value() default {};
    
        @AliasFor("value")
        String[] path() default {};
    
        RequestMethod[] method() default {};
    
        String[] params() default {};
    
        String[] headers() default {};
    
        String[] consumes() default {};
    
        String[] produces() default {};
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    其中最关键的是path属性(等价于value),它决定了当前方法处理的请求路径,注意路径必须全局唯一,任何路径只能有一个方法进行处理,它是一个数组,也就是说此方法不仅仅可以只用于处理某一个请求路径,我们可以使用此方法处理多个请求路径

    @RequestMapping({"/index", "/test"})
    public ModelAndView index(){
        return new ModelAndView("index");
    }
    
    • 1
    • 2
    • 3
    • 4

    现在我们访问/index或是/test都会经过此方法进行处理。

    我们也可以直接将@RequestMapping添加到类名上,表示为此类中的所有请求映射添加一个路径前缀,比如:

    @Controller
    @RequestMapping("/yyds")
    public class MainController {
    
        @RequestMapping({"/index", "/test"})
        public ModelAndView index(){
            return new ModelAndView("index");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    那么现在我们需要访问/yyds/index或是/yyds/test才可以得到此页面。我们可以直接在IDEA下方的端点板块中查看当前Web应用程序定义的所有请求映射,并且可以通过IDEA为我们提供的内置Web客户端直接访问某个路径。

    在这里插入图片描述

    路径还支持使用通配符进行匹配:

    • ?:表示任意一个字符,比如@RequestMapping("/index/x?")可以匹配/index/xa、/index/xb等等。
    • *:表示任意0-n个字符,比如@RequestMapping("/index/*")可以匹配/index/lbwnb、/index/yyds等。
    • **:表示当前目录或基于当前目录的多级目录,比如@RequestMapping("/index/**")可以匹配/index、/index/xxx等。

    我们接着来看下一个method属性,顾名思义,它就是请求的方法类型,我们可以限定请求方式,比如:

    @RequestMapping(value = "/index", method = RequestMethod.POST)
    public ModelAndView index(){
        return new ModelAndView("index");
    }
    
    • 1
    • 2
    • 3
    • 4

    现在我们如果直接使用浏览器访问此页面,会显示405方法不支持,因为浏览器默认是直接使用GET方法获取页面,而我们这里指定为POST方法访问此地址,所以访问失败,我们现在再去端点中用POST方式去访问,成功得到页面。

    (而且我们发现这个method也是数组,因此可以:, method = {RequestMethod.POST, RequestMethod.GET}

    我们也可以使用衍生注解直接设定为指定类型的请求映射

    @PostMapping(value = "/index")
    public ModelAndView index(){
        return new ModelAndView("index");
    }
    
    • 1
    • 2
    • 3
    • 4

    这里使用了@PostMapping直接指定为POST请求类型的请求映射,同样的,还有@GetMapping可以直接指定为GET请求方式,这里就不一一列举了。

    我们可以使用params属性来指定请求必须携带哪些请求参数,比如:

    @RequestMapping(value = "/index", params = {"username", "password"})
    public ModelAndView index(){
        return new ModelAndView("index");
    }
    
    • 1
    • 2
    • 3
    • 4

    比如这里我们要求请求中必须携带username和password属性,否则无法访问。它还支持表达式,比如我们可以这样编写:

    @RequestMapping(value = "/index", params = {"!username", "password"})
    public ModelAndView index(){
        return new ModelAndView("index");
    }
    
    • 1
    • 2
    • 3
    • 4

    在username之前添加一个感叹号表示请求的不允许携带此参数,否则无法访问,我们甚至可以直接设定一个固定值

    @RequestMapping(value = "/index", params = {"username!=test", "password=123"})
    public ModelAndView index(){
        return new ModelAndView("index");
    }
    
    • 1
    • 2
    • 3
    • 4

    这样,请求参数username不允许为test,并且password必须为123,否则无法访问。

    headers属性用法与params一致,但是它要求的是请求头中需要携带什么内容,比如:

    @RequestMapping(value = "/index", headers = "!Connection")
    public ModelAndView index(){
        return new ModelAndView("index");
    }
    
    • 1
    • 2
    • 3
    • 4

    那么,如果请求头中携带了Connection属性,将无法访问。其他两个属性:

    • consumes: 指定处理请求的提交内容类型(Content-Type),例如application/json, text/html;
    • produces: 指定返回的内容类型,仅当request请求头中的(Accept)类型中包含该指定类型才返回;

    3、@RequestParam和@RequestHeader详解

    我们接着来看,如何获取到请求中的参数

    我们只需要为方法添加一个形式参数,并在形式参数前面添加@RequestParam注解即可:

    @RequestMapping(value = "/index")
    public ModelAndView index(@RequestParam("username") String username){
        System.out.println("接受到请求参数:"+username);
        return new ModelAndView("index");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在这里插入图片描述

    我们需要在@RequestParam中填写参数名称,参数的值会自动传递给形式参数,我们可以直接在方法中使用,注意,如果参数名称与形式参数名称相同,即使不添加@RequestParam也能获取到参数值

    一旦添加@RequestParam,那么此请求必须携带指定参数,我们也可以将require属性设定为false将属性设定为非必须

    @RequestMapping(value = "/index")
    public ModelAndView index(@RequestParam(value = "username", required = false) String username){
        System.out.println("接受到请求参数:"+username);
        return new ModelAndView("index");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    我们还可以直接设定一个默认值,当请求参数缺失时,可以直接使用默认值

    @RequestMapping(value = "/index")
    public ModelAndView index(@RequestParam(value = "username", required = false, defaultValue = "伞兵一号") String username){
        System.out.println("接受到请求参数:"+username);
        return new ModelAndView("index");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    如果需要使用Servlet原本的一些类,比如:

    @RequestMapping(value = "/index")
    public ModelAndView index(HttpServletRequest request){
        System.out.println("接受到请求参数:"+request.getParameterMap().keySet());
        return new ModelAndView("index");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    直接添加HttpServletRequest形式参数即可,SpringMVC会自动传递该请求原本的HttpServletRequest对象,同理,我们也可以添加HttpServletResponse作为形式参数,甚至可以直接将HttpSession也作为参数传递:

    @RequestMapping(value = "/index")
    public ModelAndView index(HttpSession session){
        System.out.println(session.getAttribute("test"));
        session.setAttribute("test", "鸡你太美");
        return new ModelAndView("index");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    我们还可以直接将请求参数传递给一个实体类

    (com.example.entity.User)

    @Data
    public class User {
        String username;
        String password;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    注意必须携带set方法或是构造方法中包含所有参数,请求参数会自动根据类中的字段名称进行匹配:

    @RequestMapping(value = "/index")
    public ModelAndView index(User user){
        System.out.println("获取到cookie值为:"+user);
        return new ModelAndView("index");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5


    http://localhost:8080/mvc/index?username=zxl&password=123456

    获取到cookie值为:User(username=zxl, password=123456)

    @RequestHeader与@RequestParam用法一致,不过它是用于获取请求头参数的,这里就不再演示了。

    4、@CookieValue和@SessionAttrbutie

    通过使用@CookieValue注解,我们也可以快速获取请求携带的Cookie信息

    @RequestMapping(value = "/index")
    public ModelAndView index(HttpServletResponse response,
                              @CookieValue(value = "test", required = false) String test){
        System.out.println("获取到cookie值为:"+test);
        response.addCookie(new Cookie("test", "lbwnb"));
        return new ModelAndView("index");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    同样的,Session也能使用注解快速获取:

    @RequestMapping(value = "/index")
    public ModelAndView index(@SessionAttribute(value = "test", required = false) String test,
                              HttpSession session){
        session.setAttribute("test", "xxxx");
        System.out.println(test);
        return new ModelAndView("index");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    可以发现,通过使用SpringMVC框架,整个Web应用程序的开发变得非常简单,大部分功能只需要一个注解就可以搞定了,正是得益于Spring框架,SpringMVC才能大显身手。

    5、重定向和请求转发

    重定向和请求转发也非常简单,我们只需要在视图名称前面添加一个前缀即可,比如重定向

    (用return new ModelAndView("redirect:home");也可以)

    (创建一个home.html)

    @RequestMapping("/index")
    public String index(){
        return "redirect:home";
    }
    
    @RequestMapping("/home")
    public String home(){
        return "home";
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    (浏览器输入/index,地址自动变成/home并跳转至home)

    在这里插入图片描述

    通过添加redirect:前缀,就可以很方便地实现重定向,那么请求转发呢,其实也是一样的,使用forward:前缀表示转发给其他请求映射

    @RequestMapping("/index")
    public String index(){
        return "forward:home";
    }
    
    @RequestMapping("/home")
    public String home(){
        return "home";
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    (地址没变,但是交给home去处理了)

    在这里插入图片描述

    使用SpringMVC,只需要一个前缀就可以实现重定向和请求转发,非常方便。

    6、Bean的Web作用域

    在学习Spring时我们讲解了Bean的作用域,包括singletonprototype,Bean分别会以单例多例模式进行创建,而在SpringMVC中,它的作用域被继续细分

    • request:对于每次HTTP请求,使用request作用域定义的Bean都将产生一个新实例,请求结束后Bean也消失
    • session:对于每一个会话,使用session作用域定义的Bean都将产生一个新实例,会话过期后Bean也消失。 (浏览器不关的话,存session的那个cookie(默认-1,是临时Cookie,关闭浏览器即失效)清不掉,这个时候,一直访问的话,就一直都是当前这个服务器进行的会话;浏览器关闭后cookie就清理掉了,那这个session就没了,所以就变成一个新的会话了,再去访问就会产生一个新的实例了)
    • global session:不常用,不做讲解。

    这里我们创建一个测试类来试试看:

    (com.example.bean.TestBean)

    public class TestBean {
    
    }
    
    • 1
    • 2
    • 3

    接着将其注册为Bean,注意这里需要添加@RequestScope或是@SessionScope表示此Bean的Web作用域

    (在WebConfiguration中注册!)

    @Bean
    @RequestScope
    public TestBean testBean(){
        return new TestBean();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    接着我们将其自动注入到Controller中

    @Controller
    public class MainController {
    
        @Resource
        TestBean bean;
    
        @RequestMapping(value = "/index")
        public ModelAndView index(){
            System.out.println(bean);
            return new ModelAndView("index");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    我们发现,每次发起得到的Bean实例都不同,接着我们将其作用域修改为@SessionScope,这样作用域就上升到Session只要清理浏览器的Cookie,那么都会被认为是同一个会话,只要是同一个会话,那么Bean实例始终不变。

    如果只加一个@Bean注解,说明这是默认的作用域Singletion单例模式,对象始终只有一个;即使把浏览器关闭重新打开,还是刚才那个对象,不会根据session变化而变化

    实际上,它也是通过代理实现的(Bean.getClass()后看到是通过CGLIB生成的代理类),我们调用Bean中的方法会被转发到真正的Bean对象去执行。

    RestFul风格

    中文释义为 表现层状态转换(名字挺高大上的),它不是一种标准,而是一种设计风格。它的主要作用是充分并正确利用HTTP协议的特性,规范资源获取的URI路径。通俗的讲,RESTful风格的设计允许将参数通过URL拼接传到服务端,目的是让URL看起来更简洁实用,并且我们可以充分使用多种HTTP请求方式(POST/GET/PUT/DELETE),来执行相同请求地址的不同类型操作。

    因此,这种风格的连接,我们就可以直接从请求路径读取参数,比如:

    http://localhost:8080/mvc/index/123456

    我们可以直接将index的下一级路径作为请求参数进行处理,也就是说现在的请求参数包含在了请求路径中:

    @RequestMapping("/index/{str}")
    public String index(@PathVariable String str) {
        System.out.println(str);
        return "index";
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    注意请求路径我们可以手动添加类似占位符一样的信息,这样占位符位置的所有内容都会被作为请求参数,而方法的形参列表中必须包括一个与占位符同名的并且添加了@PathVariable注解的参数,或是由@PathVariable注解指定为占位符名称:

    // http://localhost:8080/mvc/index/123456
    @RequestMapping("/index/{str}")
    public String index(@PathVariable("str") String text){
        System.out.println(text);
        return "index";
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    如果没有配置正确,方法名称上会出现黄线。

    我们可以按照不同功能进行划分:

    • POST http://localhost:8080/mvc/index - 添加用户信息,携带表单数据
    • GET http://localhost:8080/mvc/index/{id} - 获取用户信息,id直接放在请求路径中
    • PUT http://localhost:8080/mvc/index - 修改用户信息,携带表单数据
    • DELETE http://localhost:8080/mvc/index/{id} - 删除用户信息,id直接放在请求路径中

    我们分别编写四个请求映射

    @Controller
    public class MainController {
    
        @RequestMapping(value = "/index/{id}", method = RequestMethod.GET)
        public String get(@PathVariable("id") String text){
            System.out.println("获取用户:"+text);
            return "index";
        }
    
    	// 这里省略了@RequestParam,与username一致因此可以省了
    	// 从post携带的数据中读取username
        @RequestMapping(value = "/index", method = RequestMethod.POST)
        public String post(String username){
            System.out.println("添加用户:"+username);
            return "index";
        }
    
        @RequestMapping(value = "/index/{id}", method = RequestMethod.DELETE)
        public String delete(@PathVariable("id") String text){
            System.out.println("删除用户:"+text);
            return "index";
        }
    
        @RequestMapping(value = "/index", method = RequestMethod.PUT)
        public String put(String username){
            System.out.println("修改用户:"+username);
            return "index";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    在这里插入图片描述

    (默认情况下无法发生PUT和DELETE,因此我们用端点(或者用POSTMAN(专门用来调接口))来调
    在这里插入图片描述

    这只是一种设计风格而已,各位小伙伴了解即可。

    Interceptor拦截器

    拦截器是整个SpringMVC的一个重要内容,拦截器与过滤器类似,都是用于拦截一些非法请求,但是我们之前讲解的过滤器作用于Servlet之前,只有经过层层的拦截器才可以成功到达Servlet,而拦截器并不是在Servlet之前,它在ServletRequestMapping之间,相当于DispatcherServlet在将请求交给对应Controller中的方法之前进行拦截处理,它只会拦截所有Controller中定义的请求映射对应的请求不会拦截静态资源),这里一定要区分两者的不同。
    在这里插入图片描述

    1、创建拦截器

    创建一个拦截器我们需要实现一个HandlerInterceptor接口:

    (com.example.interceptor.MainInterceptor)

    public class MainInterceptor implements HandlerInterceptor {
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            System.out.println("我是处理之前!");
            return true;   //只有返回true才会继续,否则直接结束
        }
    
        @Override
        public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
            System.out.println("我是处理之后!");
        }
    
        @Override
        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
            System.out.println("我是完成之后!");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    接着我们需要在配置类中进行注册

    (WebConfiguration中)

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new MainInterceptor())
          .addPathPatterns("/**")    //添加拦截器的匹配路径,只要匹配一律拦截
          .excludePathPatterns("/home");   //拦截器不进行拦截的路径
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    @Controller
    public class MainController {
    
        @RequestMapping(value = "/index")
        public String get() {
            System.out.println("我是处理器Controller");
            return "index";
        }
    
        @RequestMapping(value = "/home")
        public String home() {
            System.out.println("我是home");
            return "home";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    现在我们在浏览器中访问index页面,拦截器已经生效。

    得到整理拦截器的执行顺序

    我是处理之前!
    我是处理!
    我是处理之后!
    我是完成之后!

    也就是说,处理前和处理后,包含了真正的请求映射的处理,在整个流程结束后还执行了一次afterCompletion方法,其实整个过程与我们之前所认识的Filter类似,不过在处理前,我们只需要返回true或是false表示是否被拦截即可,而不是再去使用FilterChain进行向下传递。

    那么我们就来看看,如果处理前返回false,会怎么样:

    我是处理之前!

    (而且访问的/index页面是空白的,没有内容)

    (如果访问/home页面(之前在配置类中不进行拦截的路径),可以正常显示内容)(注意是在return false的情况下也这样)(也就是说,配置类中不进行拦截的路径直接不经过拦截器,要进行拦截的路径再看return的是true还是false)

    通过结果发现一旦返回false之后的所有流程全部取消,那么如果是在处理中发生异常了呢?

    (先将拦截器中改回return true)

    @RequestMapping("/index")
    public String index(){
        System.out.println("我是处理Controller!");
        if(true) throw new RuntimeException("我是异常!");
        return "index";
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    结果为:

    (浏览器显示状态码500内部服务器错误)

    我是处理之前!
    我是处理Controller!
    我是完成之后!

    我们发现如果处理过程中抛出异常,那么就不会执行处理后postHandle方法,但是会执行afterCompletion方法,我们可以在此方法中获取到抛出的异常

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println(ex.getMessage());
        System.out.println("我是完成之后!");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    我是处理之前!
    我是处理Controller!
    我是异常!
    我是完成之后!

    2、多级拦截器

    前面介绍了仅仅只有一个拦截器的情况,我们接着来看如果存在多个拦截器会如何执行,我们以同样的方式创建二号拦截器

    public class SubInterceptor implements HandlerInterceptor {
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            System.out.println("二号拦截器:我是处理之前!");
            return true;
        }
    
        @Override
        public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
            System.out.println("二号拦截器:我是处理之后!");
        }
    
        @Override
        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
            System.out.println("二号拦截器:我是完成之后!");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    注册二号拦截器:

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //一号拦截器
        registry.addInterceptor(new MainInterceptor())
                .addPathPatterns("/**")    //添加拦截器的匹配路径,只要匹配一律拦截
                .excludePathPatterns("/home");   //拦截器不进行拦截的路径
    
        //二号拦截器
        registry.addInterceptor(new SubInterceptor())
                .addPathPatterns("/**");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    注意拦截顺序就是注册的顺序,因此拦截器会根据注册顺序依次执行,我们可以打开浏览器运行一次:

    一号拦截器:我是处理之前!
    二号拦截器:我是处理之前!
    我是处理Controller!
    二号拦截器:我是处理之后!
    一号拦截器:我是处理之后!
    二号拦截器:我是完成之后!
    一号拦截器:我是完成之后!

    和多级Filter相同,在处理之前,是按照顺序从前向后进行拦截的,但是处理完成之后,就按照倒序执行处理后方法,而完成后是在所有的postHandle执行之后再同样的以倒序方式执行。

    那么如果这时一号拦截器在处理前返回了false呢?

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("一号拦截器:我是处理之前!");
        return false;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    得到结果如下:

    (页面也无法显示)

    一号拦截器:我是处理之前!

    我们发现,与单个拦截器的情况一样,一旦拦截器返回false,那么之后无论有无拦截器,都不再继续

    (如果是只将二号拦截器return false:
    发现一号拦截器还是会执行afterCompletion

    一号拦截器:我是处理之前!
    二号拦截器:我是处理之前!
    一号拦截器:我是完成之后!

    异常处理

    当我们的请求映射方法出现异常时,会直接展示在前端页面,这是因为SpringMVC为我们提供了默认的异常处理页面,当出现异常时,我们的请求会被直接转交给专门用于异常处理的控制器进行处理。

    (return false 并 throw new RuntimeException())
    在这里插入图片描述

    我们可以自定义一个异常处理控制器,一旦出现指定异常,就会转接到此控制器执行:

    (com.example.controller.ErrorController)

    @ControllerAdvice
    public class ErrorController {
    
        @ExceptionHandler(Exception.class)
        public String error(Exception e, Model model){  //可以直接添加形参来获取异常
            e.printStackTrace();
            model.addAttribute("e", e);
            return "500";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    接着我们编写一个专门显示异常的页面

    (template/500.html)

    DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>出错了title>
    head>
    <body>
      <div>500 - 服务器出现了一个内部错误QAQdiv>
      <div th:text="${e}">div>
    body>
    html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    接着修改:

    @RequestMapping("/index")
    public String index(){
        System.out.println("我是处理!");
        if(true) throw new RuntimeException("您的氪金力度不足,无法访问!");
        return "index";
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    在这里插入图片描述

    访问后,我们发现控制台会输出异常信息,同时页面也是我们自定义的一个页面。

    JSON数据格式与AJAX请求

    (之前我们用xml来做配置文件

    JSON (JavaScript Object Notation, JS 对象简谱) 是一种轻量级数据交换格式

    我们现在推崇的是前后端分离的开发模式,而不是所有的内容全部交给后端渲染再发送给浏览器,也就是说,整个Web页面的内容在一开始就编写完成了,而其中的数据由前端执行JS代码来向服务器动态获取,再到前端进行渲染(填充),这样可以大幅度减少后端的压力,并且后端只需要传输关键数据即可(在即将到来的SpringBoot阶段,我们将完全采用前后端分离的开发模式)

    1、JSON数据格式

    既然要实现前后端分离,那么我们就必须约定一种更加高效的数据传输模式,来向前端页面传输后端提供的数据。因此JSON横空出世,它非常容易理解,并且与前端的兼容性极好,因此现在比较主流的数据传输方式则是通过JSON格式承载的。

    一个JSON格式的数据长这样,以学生对象为例:

    {"name": "杰哥", "age": 18}
    
    • 1

    多个学生可以以数组的形式表示:

    [{"name": "杰哥", "age": 18}, {"name": "阿伟", "age": 18}]
    
    • 1

    嵌套关系可以表示为:

    {"studentList": [{"name": "杰哥", "age": 18}, {"name": "阿伟", "age": 18}], "count": 2}
    
    • 1

    它直接包括了属性的名称和属性的值,与JavaScript的对象极为相似,它到达前端后,可以直接转换为对象,以对象的形式进行操作和内容的读取,相当于以字符串形式表示了一个JS对象,我们可以直接在控制台窗口中测试:

    在这里插入图片描述
    将JSON字符串转换为JS对象:

    let obj = JSON.parse('{"studentList": [{"name": "杰哥", "age": 18}, {"name": "阿伟", "age": 18}], "count": 2}')
    //将JSON格式字符串转换为JS对象
    obj.studentList[0].name   //直接访问第一个学生的名称
    
    • 1
    • 2
    • 3

    在这里插入图片描述

    我们也可以将JS对象转换为JSON字符串
    在这里插入图片描述

    JSON.stringify(obj)
    
    • 1

    我们后端就可以以JSON字符串的形式向前端返回数据,这样前端在拿到数据之后,就可以快速获取,非常方便。

    那么后端如何快速创建一个JSON格式的数据呢?我们首先需要导入以下依赖:

    <dependency>
        <groupId>com.alibabagroupId>
        <artifactId>fastjsonartifactId>
        <version>1.2.78version>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    JSON解析框架有很多种,比较常用的是JacksonFastJSON,这里我们使用阿里巴巴的FastJSON进行解析。

    首先要介绍的是JSONObject,它和Map的使用方法一样(实现了Map接口),比如我们向其中存放几个数据:

    @RequestMapping(value = "/index")
    public String index(){
        JSONObject object = new JSONObject();
        object.put("name", "杰哥");
        object.put("age", 18);
        System.out.println(object.toJSONString());   //以JSON格式输出JSONObject字符串
        return "index";
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    最后我们得到的结果为:

    {"name": "杰哥", "age": 18}
    
    • 1

    实际上JSONObject就是对JSON数据的一种对象表示。同样的还有JSONArray,它表示一个数组,用法和List一样,数组中可以嵌套其他的JSONObject或是JSONArray:

    @RequestMapping(value = "/index")
    public String index(){
        JSONObject object = new JSONObject();
        object.put("name", "杰哥");
        object.put("age", 18);
        JSONArray array = new JSONArray();
        array.add(object);
        System.out.println(array.toJSONString());
        return "index";
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    得到的结果为:

    [{"name": "杰哥", "age": 18}]
    
    • 1

    当出现循环引用时,会按照以下语法来解析:
    在这里插入图片描述
    我们可以也直接创建一个实体类,将实体类转换为JSON格式的数据:

    (现在我不想返回页面,我想返回json格式的数据,但要求返回的是view的名称)

    @RequestMapping(value = "/index", produces = "application/json")
    @ResponseBody
    public String data(){
        Student student = new Student();
        student.setName("杰哥");
        student.setAge(18);
        return JSON.toJSONString(student);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    在这里插入图片描述

    这里我们修改了produces的值,将返回的内容类型设定为application/json,表示服务器端返回了一个JSON格式的数据(当然不设置也行,也能展示,这样是为了规范)(对应了响应标头里的Content-Type,比如如果是text/html,浏览器会按照普通的html文件去解析,charset编码自动设定成ISO编码而不是utf8所以会乱码)(而设置成这个application/json后中文也可以解析了)然后我们在方法上添加一个@ResponseBody表示方法返回(也可以在类上添加@RestController表示此Controller默认返回的是字符串数据)的结果不是视图名称而是直接需要返回一个字符串(返回的是什么就是什么。字符串是因为方法名前我们写着返回值是String)作为页面数据,这样,返回给浏览器的就是我们直接返回的字符串内容。

    接着我们使用JSON工具类将其转换为JSON格式的字符串,打开浏览器,得到JSON格式数据。

    SpringMVC非常智能,我们可以直接返回一个对象类型,它会被自动转换为JSON字符串格式:

    @RequestMapping(value = "/data", produces = "application/json")
    @ResponseBody
    public Student data(){
        Student student = new Student();
        student.setName("杰哥");
        student.setAge(18);
        return student;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    注意需要在配置类(WebConfiguration)中添加一下FastJSON转换器(默认只支持JackSon):

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        converters.add(new FastJsonHttpMessageConverter());
    }
    
    • 1
    • 2
    • 3
    • 4

    2、AJAX请求

    前面我们讲解了如何向浏览器发送一个JSON格式的数据,那么我们现在来看看如何向服务器请求数据。
    在这里插入图片描述
    Ajax即Asynchronous Javascript And XML(异步JavaScript和XML),它的目标就是实现页面中的数据动态更新,而不是直接刷新整个页面,它是一个概念。

    它在JQuery框架中有实现,因此我们直接导入JQuery(JQuery极大地简化了JS的开发,封装了很多内容,感兴趣的可以了解一下):

    <script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
    
    • 1

    接着我们就可以直接使用了,首先修改一下前端页面:

    DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>Titletitle>
        <script src="https://code.jquery.com/jquery-3.1.1.min.js">script>
        <script th:src="@{/static/test.js}">script>
    head>
    <body>
        你好,
        <span id="username">span>
        您的年龄是:
        <span id="age">span>
        <button onclick="updateData()">点我更新页面数据button>
    body>
    html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    现在我们希望用户名称和年龄需要在我们点击按钮之后才会更新,我们接着来编写一下JS:

    function updateData() {
        //美元符.的方式来使用Ajax请求,这里使用的是get方式,第一个参数为请求的地址(注意需要带上Web应用程序名称),第二个参数为成功获取到数据的方法,data就是返回的数据内容
      	$.get("/mvc/data", function (data) {   //获取成功执行的方法
            window.alert('接受到异步请求数据:'+JSON.stringify(data))  //弹窗展示数据
            $("#username").text(data.name)   //这里使用了JQuery提供的选择器,直接选择id为username的元素,更新数据
            $("#age").text(data.age)
        })
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    注意这个请求是异步的请求,也就是说我这个在执行的时候,会执行其他的,不会管这个执行完了没有,就算这个获取数据的时候卡在这里了,它也不会管,直接交给另外一个线程去执行了,但是后面的代码还是在当前这个线程执行的,它是异步的
    既然是异步的,肯定要在里面写,如果是执行成功了会做什么事情,这个get的第一个参数是访问哪一个地址,第二个参数function是回调函数,就是执行成功之后会回调这个方法,这个function中的data就是接收它返回的数据,它会把这个数据自动转换成js的对象

    使用JQuery非常方便,我们直接通过JQuery的选择器就可以快速获取页面中的元素,注意这里获取的元素是被JQuery封装过的元素,需要使用JQuery提供的方法来进行操作。

    这样,我们就实现了从服务端获取数据并更新到页面中(实际上之前,我们在JavaWeb阶段使用XHR请求也演示过,不过当时是纯粹的数据)

    代码:

    MainController

    @Controller
    public class MainController {
    
        @RequestMapping(value = "/index")
        public String get() {
            return "index";
        }
    
        @RequestMapping(value = "/data", produces = "application/json")
        @ResponseBody
        public User home() {
            User user = new User();
            user.setUsername("用户名");
            user.setAge(18);
            System.out.println(user);
            return user;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    User

    @Data
    public class User {
        String username;
        int age;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    test.js

    function updateData() {
        //美元符.的方式来使用Ajax请求,这里使用的是get方式,第一个参数为请求的地址(注意需要带上Web应用程序名称),第二个参数为成功获取到数据的方法,data就是返回的数据内容
        $.get("/mvc/data", function (data) {   //获取成功执行的方法
            window.alert('接受到异步请求数据:'+JSON.stringify(data))  //弹窗展示数据
            $("#username").text(data.username)   //这里使用了JQuery提供的选择器,直接选择id为username的元素,更新数据
            $("#age").text(data.age)
        })
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    index.html

    DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>Titletitle>
        <script src="https://code.jquery.com/jquery-3.1.1.min.js">script>
        <script th:src="@{/static/test.js}">script>
    head>
    <body>
    你好,
    <span id="username">span>
    您的年龄是:
    <span id="age">span>
    <button onclick="updateData()">点我更新页面数据button>
    body>
    html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    浏览器中访问index后,点击按钮,弹窗后:
    在这里插入图片描述

    现在都是这样一种模式,向后台请求数据然后再来更新页面,而不是说一开始就给你一个ok的页面,点一下再请求数据,减轻了后端很大的负担,

    那么我们接着来看,如何向服务端发送一个JS对象数据并进行解析:
    (可以把它包装成json的数据格式,然后再由后端来接收

    (向后端发送请求,而且这个请求里带数据,因此我们用post)
    后端如果成功接收到了,返回的数据会被封装成data

    function sendData() {
        $.post("/mvc/submit", {   //这里使用POST方法发送请求
            username: "测试",     //第二个参数是要传递的对象,会以表单数据的方式发送
            age: 18
        }, function (data) {
            window.alert(JSON.stringify(data))   //发送成功执行的方法
        })
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    <button onclick="sendData()">点我发送页面数据button>
    
    • 1

    服务器端只需要在请求参数位置添加一个对象接收即可(和前面是一样的,因为这里也是提交的表单数据):

    @RequestMapping("/submit")
    @ResponseBody
    public String submit(Student student){
        System.out.println("接收到前端数据:"+student);
        return "{\"success\": true}";
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    (这里我们实际提交的也是表单数据)

    在WebConfiguration中:

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
    
        // 限定只有发送json格式才需要转换,否则就不转换了
        FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter();
        converter.setSupportedMediaTypes(Collections.singletonList(MediaType.APPLICATION_JSON));
        converters.add(converter);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    然后看到,发送的是一个表单,响应的是一个json数据
    在这里插入图片描述
    在这里插入图片描述

    我们也可以将js对象转换为JSON字符串的形式进行传输,这里需要使用ajax方法来处理:

    function submitData() {
        $.ajax({   //最基本的请求方式,需要自己设定一些参数
            type: 'POST',   //设定请求方法
            url: "/mvc/submit",   //请求地址
            data: JSON.stringify({name: "测试", age: 18}),  //转换为JSON字符串进行发送
            success: function (data) {
                window.alert(JSON.stringify(data))
            },
            contentType: "application/json"  //请求头Content-Type一定要设定为JSON格式
        })
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    如果我们需要读取前端发送给我们的JSON格式数据,那么这个时候就需要添加@RequestBody注解:

    @RequestMapping("/submit")
    @ResponseBody
    public String submit(@RequestBody JSONObject object){
        System.out.println("接收到前端数据:"+object);
        return "{\"success\": true}";
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    这样,我们就实现了前后端使用JSON字符串进行通信。

  • 相关阅读:
    【R语言】生存分析模型
    【达梦数据库】备份恢复后要执行两个sql语句
    Google Swift 与 DC 传输
    第7章 C语言的递归函数 (六)
    服务拆分和远程调用(微服务)
    IDEA报错汇总:
    【前端】根据后端返回的url进行下载并设置文件下载名称
    Vue 组件间通信并不是每一次操作都会触发新的通信
    centos8同步时间安装时间校准服务
    SQL—— 优化
  • 原文地址:https://blog.csdn.net/m0_51448653/article/details/127587561