从零开始重新认识 SpringMVC

时间:2022-07-22
本文章向大家介绍从零开始重新认识 SpringMVC,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

一、核心

前端控制器

我们需要在 web.xml 中做如下配置:

<servlet>
    <servlet-name>dispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:springmvc.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>dispatcherServlet</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>
  • DispatcherServlet:这是 Spring MVC 的核心类,叫前端控制器。
  • contextConfigLocation:这是适配器的初始化参数,我们需要配置 Spring 配置文件的全路径,跟他说配置文件在哪里,这样他就会去加载。

二、请求

请求参数的封装

默认情况下,你在方法的参数列表中写什么,前端表单中 input 标签的 name 属性值就要是什么,比如:

@RequestMapping(path = "/hello")
public String sayHello(String username) {
    System.out.println("Hello Spring MVC!" + username);
    return "success";
}

那么前端一定要这么写才能接收到参数:

<form action="hello" method="post">
    <label>
        姓名:
        <input name="username" type="text">
    </label> <br>
    <input type="submit" value="提交">
</form>

或者直接拼接在请求路径上面。

  • @RequestMapping:指定请求路径,匹配请求路径即可访问到该注解所在的方法,从而去处理业务逻辑。

我们也可以直接写成一个对象:

@RequestMapping("/login")
public String login(Account account) {
    System.out.println(account);
    return "success";
}

这样在前端提供对应的表单即可:

<form action="login" method="post">
    <label>
        姓名:
        <input name="username" type="text">
    </label> <br>
    <label>
        密码:
        <input name="password" type="text">
    </label> <br>
    <label>
        年龄:
        <input name="age" type="text">
    </label> <br>
    <label> 日期:
        <input name="date" type="text">
    </label>
    <input type="submit" value="提交">
</form>

这一点比 Servlet 方便了很多很多。

不过也有一个问题,就是如果我们前端提供的数据和后端方法参数中的不匹配该怎么办呢?

只需要在参数上面接一个注解即可,这样前端传 name 即可:

public String sayHello(@RequestParam("name") String username)

再送你一个,如果不想一个一个的拿,想一下把表单中的数据全部拿出来该怎么做?

public String sayHello(@RequestBody String body)

这样就行了,这里的 body 就是表单中传入参数的键值对。

如果我们的路径中有 /login/{id}这种请求 url,那么我们就可以使用另一个注解:

@RequestMapping(path = "/hello/{id}")
public String sayHello(@PathVariable("id") String id)

再来一个获取请求头的注解:

public String sayHello(@RequestHeader("Accept") String accept)
@CookieValue 		放在属性上:是获取 Cookie 的值;

@ModelAttribute		放在方法上:优先执行该方法。
					放在属性值上:代表从上一个 `ModelAttribute` 中取出来具体的值。

请求作用域

我们可以使用 Model 类来将参数直接封装到请求作用域中:

@RequestMapping("model")
public String testModel(Model model) {
    model.addAttribute("msg", "你好");
    return "success";
}

同时我们可以在类上添加一个 @SessionAttributes 注解,这样就会把只存入 session 域中。

@SessionAttributes(value = {"msg"})
public class HelloController

如果我们想从 session 域中取值:

@RequestMapping("getAttr")
public String getAttribute(ModelMap modelMap) {
    modelMap.get("msg");
    return "success";
}

如果想从 session 中清除数据:

@RequestMapping("clear")
public String getAttribute(SessionStatus status) {
    status.setComplete();
    return "success";
}

视图解析器

上面的代码中我们返回了一个success,这是个字符串?

确实是个字符串,但是如果我们配置了视图解析器之后就不一样了,他会去找这个名称的文件,比如我在 pages 文件夹下放了一个 successjsp 文件,我想让页面直接找到它应该怎么做呢?

很简单,只需要在 Spring 的配置文件中配置如下信息:

<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="internalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/pages/"/>
    <property name="suffix" value=".jsp"/>
</bean>
  • internalResourceViewResolver:就是视图解析器,他是 ViewResolver 的子类。
    • prefix:指定文件的前缀;
    • suffix:指定文件的后缀。

表单提交乱码问题

在以前我们是自己写一个 Request 实现类去配合 Filter 实现全局编码控制,在 Spring MVC 中,这一步又被大大简化了。

我们只需要在 部署描述文件 中做如下配置即可:

<filter>
    <filter-name>encodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
        <param-name>encoding</param-name>
        <param-value>utf-8</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>encodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
  • CharacterEncodingFilter:是 Spring MVC 为我们提供的过滤器,我们只需要设置其初始化参数即可。

自定义类型转换器

我们与数据库交换一般使用的日期类型为2020-06-06这种格式的,但是网页上面提交的却是 2020/06/06 这个类型的,所以我们会收到一个 400 Bad Request 的错误提示,那么怎么解决这个问题呢?

我们可以写一个实现类去实现 Spring 为我们提供的一个接口 Converter,然后泛型写我们想改变的类型,这里我们指定为 Date 类型:

public class DateConverter implements Converter<String, Date> {
    @Override
    public Date convert(String source) {
        Date date = null;
        try {
            date = new SimpleDateFormat("yyyy-MM-dd").parse(source);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return date;
    }
}

然后在 Spring 的配置文件中做如下配置:

<bean class="org.springframework.context.support.ConversionServiceFactoryBean" id="conversionService">
    <property name="converters">
        <set>
            <bean class="top.wsuo.config.DateConverter"/>
        </set>
    </property>
</bean>

<mvc:annotation-driven conversion-service="conversionService"/>

记得配置好 Bean 之后将转化类交给 Spring MVC 的 conversion-service属性。

这样我们再去提交就没有问题了。

三、响应

我们怎么将后台获取到的数据传给前端呢?

使用域对象传数据

我们传统的思路是通过 request 域对象传值,那么 Spring MVC 可以吗? 那肯定可以。

首先是后端代码:

@RequestMapping("testString")
public String testString(Model model) {
    System.out.println("testString 方法执行了!");
    model.addAttribute("user", new User("妹妹", "1234", 18));
    return "success";
}

然后是前端代码:

<body>
<h1>成功</h1>
${user.username} <br>
${user.password} <br>
${user.age}
</body>

执行结果:

这里使用 Model 就相当于将对象存入了域中,所以可以直接获取。

那么为什么我们返回 String 就可以直接跳转到页面了呢?

我们再来看一个方法:

@RequestMapping("testMAV")
public ModelAndView testModelAndView() {
    System.out.println("testModelAndView 方法执行了!");
    ModelAndView mv = new ModelAndView();
    mv.addObject("user", new User("妹妹", "1234", 18));
    mv.setViewName("success");
    return mv;
}

这个方法的作用和上一个一模一样,这就说明,返回 String 类型的参数其实就是返回的 ModelAndView 类型的参数,只是多了一层封装,用的都是 视图解析器

如果我们的方法返回值不给他会怎么办呢?

@RequestMapping("testVoid")
public void testString() {
    System.out.println("testVoid 方法执行了!");
}

结果就是他会找一个名字叫做 testVoid 的文件,如果我们没有提供就会 404。

我们也可以自己实现转发:

@RequestMapping("testString")
public String testString() {
    System.out.println("testString 方法执行了!");
    return "forward:/WEB-INF/pages/success.jsp";
}

返回 JSON 数据

有时候我们使用 js 或者 css 样式会失效,这是因为没有配置静态资源访问过滤,我们之前配置前端过滤器的时候是拦截所有的资源,这其中就包括静态资源,所以我们要在 Spring 的配置文件中配置一下:

<!--  告诉前端控制器,那些静态资源不拦截  -->
<mvc:resources mapping="/js/**" location="/js/"/>
<mvc:resources mapping="/images/**" location="/images/"/>
<mvc:resources mapping="/css/**" location="/css/"/>

然后我们就可以使用 Ajax 异步发送请求了,代码如下:

<script>
    // 页面加载,绑定单击事件
    $(function () {
        $("#btn").click(function () {
            // alert("hello btn")
            $.ajax({
                // 编写 json 格式
                url: "user/ajax",
                contentType: "application/json;charset=utf-8",
                data: '{"username":"王硕", "password":"123", "age":"18"}',
                dataType: "json",
                type: 'POST',
                success: function (data) {
                    alert(data);
                }
            });
        });
    });
</script>

我们在最原始的 ajax 方法中配置了一些属性,然后用 data 接收,我们后台写一个接口:

@RequestMapping("ajax")
public void testAjax(@RequestBody String body) {
    System.out.println("Ajax 执行了!");
    System.out.println(body);
}

随后点击按钮,控制台输出如下:

Ajax 执行了!
{"username":"王硕", "password":"123", "age":"18"}

这说明后台已经拿到前端传的 JSON 格式的数据了,那么接下来,我们就将这些数据封装成一个对象再传回去,这就需要使用其他的 jar 包了。

这里我们使用 Jackson

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.12</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.9.0</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-core</artifactId>
    <version>2.9.0</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-annotations</artifactId>
    <version>2.9.0</version>
</dependency>

我们来测试一下,前端这么写:

后端这么写:

@RequestMapping("ajax")
@ResponseBody
public User testAjax(@RequestBody User user) {
    System.out.println("Ajax 执行了!");
    System.out.println(user);
    user.setUsername("王硕二号");
    user.setPassword("12345");
    user.setAge(19);
    return user;
}

执行结果:

拦截器

拦截器是 SpringMVC 所特有的功能,它主要用于拦截控制器方法,也就是说你访问静态资源不关他的事,你访问接口才会被拦截。

注意:除了 过滤器前端控制器 是在 web.xml 中配置以外,其余配置均在 spring 的配置文件中配置,如视图解析器、静态资源过滤器、拦截器等。

这里我们配置一下拦截器,首先需要创建一个类用于实现 Spring 为我们提供的接口:

public class HandlerInterceptorImpl implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("preHandle 方法执行了!");
        return true;
    }
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle 方法执行了!");
    }
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("afterCompletion 方法执行了!");
    }
}
  • preHandle:如果返回 true 表示放行,继续执行下一个拦截器。
<mvc:interceptors>
    <mvc:interceptor>
        <mvc:mapping path="/user/*"/>
        <bean class="top.wsuo.interceptor.HandlerInterceptorImpl" id="interceptor"/>
    </mvc:interceptor>
</mvc:interceptors>

结果如下: