【快速学习系列】SpringMVC的使用、传递参数(request、session、对象、集合...)及显示风格

Source

【快速学习系列】SpringMVC的使用、传递参数(request、session、对象、集合…)及显示风格

SpringMVC扮演的角色就相当于Servlet的角色

Spring MVC框架特点

清晰地角色划分
灵活的配置功能
提供了大量的控制器接口和实现类
真正做到与View层的实现无关(JSP、Velocity、Xslt等)
国际化支持
面向接口编程
Spring提供了Web应用开发的一整套流程,不仅仅是MVC,他们之间可以很方便的结合一起

首先来感受Controller的简单使用

Controller的简单使用

新建maven项目

导入jar包—pom.xml

<!-- javax-Servlet -->
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>servlet-api</artifactId>
    <version>2.5</version>
</dependency>
<!-- springmvc -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-web</artifactId>
    <version>${spring.version}</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>${spring.version}</version>
</dependency>
<!-- lombok:懒得写实体类可以导一下 -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.16.8</version>
</dependency>

新建类IndexController.java

让其继承AbstractController类来使之成为Controller控制器类

然后重写其方法

首先来打印一句Hello,Spring MVC!!!

然后返回一个ModelAndView对象到index.jsp

package com.r.controller;

import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.AbstractController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * @Author Tuerlechat,
 * @Date 2022/11/7
 */
public class IndexController extends AbstractController {
    
      
    @Override
    protected ModelAndView handleRequestInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {
    
      

        System.out.println("Hello,Spring MVC!!!");
        return new ModelAndView("index.jsp");
    }
}

接着创建一个springmvc.xml配置文件

在里面来配置好我们即将要访问这个控制器类的映射路径

注意创建bean的不是id,而是name

<!-- 相当于index = new IndexController() -->
        <!-- 映射 -->
        <bean name="/index" class="com.r.controller.IndexController"/>

但是配置好了会发现并没有文件来读它,因为我们并不是用单元测试来运行它,所以我们需要配置web.xml文件来读取访问

<!-- 读取springmvc配置文件(首先需要经过前端控制器(DispatcherServlet)才能到controller,所以需要先配置一下前端控制器) -->
<servlet>
    <!-- 随便起个名字就行 -->
    <servlet-name>springmvc</servlet-name>
    <!-- 设置前端控制器 -->
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <!-- 开启前端控制器后需要读取的配置 -->
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <!-- 读取springmvc.xml配置文件 -->
        <param-value>classpath:springmvc.xml</param-value>
    </init-param>
    <!-- 项目启动时就先加载Servlet,如果不设置则默认是收到请求后才开始加载Servlet -->
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <!-- 和上面起的名字需要一样 -->
    <servlet-name>springmvc</servlet-name>
    <!-- 路径为/则代表是这个项目的所有文件路径 -->
    <url-pattern>/</url-pattern>
</servlet-mapping>

然后部署运行tomcat,可以发现成功显示类页面,

请添加图片描述

但此时这个页面是默认访问的index.jsp页面,并不是访问的我们配置的controller映射路径,所以我们需要改一下访问的url,然后运行

请添加图片描述

然后查看控制台,可以看到成功输出信息

请添加图片描述

利用注解实现Controller使用

使用注解前应扫包并开启注解

配置文件springmvc.xml

<!--自动扫描的包名,只扫描@Controller -->
<context:component-scan base-package="com.r.controller"/>
<!--开启注解-->
<mvc:annotation-driven/>

可以改原来的不过我写了个新控制器类IndexConotroller1.java

package com.r.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

/**
 * @Author Tuerlechat,
 * @Date 2022/11/7
 */
@Controller	//控制器注解
public class IndexController1 {
    
      

    @RequestMapping("/hello")	//访问映射路径,一般与方法名一致,不过也可以
    public ModelAndView index() {
    
      
        System.out.println("进入index方法");
        return new ModelAndView("index.jsp");
    }

    @RequestMapping("/findById")
    public ModelAndView findById() {
    
      
        System.out.println("进入findById方法");
        return new ModelAndView("index.jsp");
    }
}

然后运行即可

请添加图片描述

请添加图片描述

然后可以看到控制台显示

请添加图片描述

SpringMVC常用注解

@RequestMapping

通过请求URL进行映射

  • value:路径
  • method:
    • RequestMethod.GET 必须以GET方式进入 @GetMapping
    • RequestMethod.POST 必须以POST方式进入 @PostMapping

@RequestParam

参数传递**(如果用了传参必须用这个别名)**

  • value:参数别名
  • params:请求参数的映射条件,指定请求的URL地址需要带那些参数
    • “param1=value1” :传入的参数必须是这个值
    • “param2”:必须传入这个参数
    • “!param3” :非必须
  • defaultValue:默认值(Integer类型直接写数字不行,直接加字符串""就可以显示int类型
传递参数理解例子

实体类Provider.java

import lombok.Data;

@Data
public class Provider {
    
      
    
	private String proName; //供应商名称
	private String proDesc; //供应商描述
	
}

控制器类ProviderController.java

@RequestMapping("/index")
public ModelAndView index(@RequestParam(value = "pname",required = false) String proName,
                          @RequestParam(value = "pdesc", defaultValue = "平平无奇普通犬") String proDesc) {
    
      
    System.out.println("名称====" + proName);
    System.out.println("描述====" + proDesc);
    return new ModelAndView("/hello");
}

运行tomcat,在url上传入参数,然后回车

请添加图片描述

然后回到控制台,可以看到

请添加图片描述

说明参数传递成功

传递对象参数理解例子

实体类不变

控制器类加一个saveProvider方法

@RequestMapping(value = "/saveProvider", method = RequestMethod.POST)
public ModelAndView saveProvider(Provider provider) {
    
      
    System.out.println(provider.toString());
    return new ModelAndView("/hello");  //前面有模块路径的情况下不要忘记添/
}

新建一个register.jsp

<%--
  Created by IntelliJ IDEA.
  User: Tuerlechat,
  Date: 2022/11/7
  Time: 17:12
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>注册</title>
</head>
<body>

<%-- 跳转路径是访问相应controller类的映射路径 --%>
<form action="/provider/saveProvider" method="post">
    <%-- 这里的name要对应实体类属性名 --%>
    供应商名称:<input type="text" name="proName"/><br/>
    供应商描述:<input type="text" name="proDesc"/><br/>
    <input type="submit" value="注册">
</form>

</body>
</html>

然后运行tomcat,路径访问到register.jsp,填写内容,然后点击注册

请添加图片描述

可以看到页面改变了,说明进入到了相应的Controller方法中

请添加图片描述

然后回到控制台,可以看到控制台输出

请添加图片描述

说明对象传参也成功

@ResponseBody

自动返回JSON数据 一般用于方法上 (用类上表示整个类都返回JSON数据)

tips:使用需要先导包

<!--JSON依赖-->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-core</artifactId>
    <version>2.4.3</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.4.3</version>
</dependency>

@RestController

标注该类中所有的方法都返回JSON 用于类中。无须在写@Controller

tips:@RestController=@Controller+@ResponeBody

日期类型格式转换

@DateTimeFormat

@DateTimeFormat (pattern=“yyyy-MM-dd HH:mm:ss”)

用户对象属性,控制入参时日期类型转换 (实体类入参)

@JsonFormat

@JsonFormat(pattern = “yyyy-MM-dd HH:mm:ss”, timezone = “GMT+8”)

控制出参时日期类型转换 (实体类出参)

需要规定东八区时差,否则显示相差晚7小时

tips:一般Date类型的实体类都用String了,所以这个一般也用不到😬

表示属性为null时不返回

对象中添加(放在实体类所需属性或类上):

@JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL)

@RequestHeader

获取请求头中的参数

@ResponseBody
@RequestMapping(value = "/index",method = RequestMethod.POST)
public User index(@RequestHeader(value = "token",defaultValue = "1",required = false) int tokenid){
    
      
    ......
}

参数传递

request作用域

五种方式

1、@ModelAttribute注解(不常用)

Controller层

@RequestMapping("/providerInfo1")
public ModelAndView providerInfo1(@ModelAttribute("uname") String uname) {
    
      
    return new ModelAndView("/hello");
}

前端jsp

<%@ page contentType="text/html;charset=UTF-8" isELIgnored="false" language="java" %>
<html>
<body>
<h2>Hello World!</h2>
<h2>${uname}</h2>
<h2>${msg}</h2>
</body>
</html>

tips:前端一直不用改

效果

请添加图片描述

2、new一个ModelAndView对象(较麻烦)

使用mav.addObject();

Controller层

@RequestMapping("/providerInfo2")
public ModelAndView providerInfo2() {
    
      
    ModelAndView mav = new ModelAndView("/hello");
    String msg = "我是providerInfo2";
    mav.addObject("msg", msg);
    return mav;
}

效果

请添加图片描述

3、用Model传参(常用)

使用model.addAttribute();

Controller层(返回类型是String)

@RequestMapping("/providerInfo3")
public String providerInfo3(Model model) {
    
      
    String msg = "我是providerInfo3";
    model.addAttribute("msg", msg);
    return "/hello";
}

效果

请添加图片描述

4、用map传参(常用)

使用map.put();

Controller层(返回类型是String)

@RequestMapping("/providerInfo4")
public String providerInfo4(Map<String, Object> map) {
    
      
    String msg = "我是providerInfo4";
    map.put("msg", msg);
    return "/hello";
}

效果

请添加图片描述

5、使用HttpServletRequest传参

需要先引入servlet-api.jar

pom.xml

<!-- javax-Servlet -->
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>servlet-api</artifactId>
    <version>2.5</version>
</dependency>

Controller层

@RequestMapping("/providerInfo5")
public ModelAndView providerInfo5(HttpServletRequest request) {
    
      
    String msg = "我是providerInfo5";
    request.setAttribute("msg", msg);
    return new ModelAndView("/hello");
}

效果

请添加图片描述

session作用域

1、@SessionAttributes

只能定义在类上,

作用是将指定的Model中的键值对添加至session中

Controller层

@Controller
@SessionAttributes(value = {
    
      "uname"})
@RequestMapping("provider") //模块路径:更清晰也防止映射路径访问同名混乱出现问题
public class ProviderController {
    
      
    @RequestMapping("/providerInfo")
    public ModelAndView providerInfo(String uname) {
    
      
        ModelAndView mav = new ModelAndView("/hello");
        mav.addObject("uname",uname);
        return mav;
    }
}

效果

请添加图片描述

先访问providerInfo加入参数添加session,再访问其它方法(providerInfo1)能够接收到

@SessionAttributes(types=User.class)会将model中所有类型为 User的属性添加到会话中。
@SessionAttributes(value={“user1”, “user2”}) 会将model中属性名为user1和user2的属性添加到会话中。
@SessionAttributes(types={User.class, Dept.class}) 会将model中所有类型为 User和Dept的属性添加到会话中。
@SessionAttributes(value={“user1”,“user2”},types={Dept.class})会将model中属性名为user1和user2以及类型为Dept的属性添加到会话中。

2、以Servlet存储

1.使用HttpServletRequest

Controller层

@RequestMapping("/providerInfo11")
public ModelAndView providerInfo11(HttpServletRequest request) {
    
      
    String msg = "我是Session11";
    request.getSession().setAttribute("msg", msg);
    return new ModelAndView("/hello");
}
2.使用HttpSession

Controller层

@RequestMapping("/providerInfo22")
public ModelAndView providerInfo22(HttpSession session) {
    
      
    String msg = "我是Session22";
    session.setAttribute("msg", msg);
    return new ModelAndView("/hello");
}

可以先进入然后再分别返回providerInfo1方法中试一下

效果

请添加图片描述

运行的是providerInfo1方法但仍然保存着providerInfo22的session值

传对象参数

传一个provider对象

Controller层

@RequestMapping("findProviderJson")
@ResponseBody
public Provider findProviderJson() {
    
      
    Provider provider = new Provider();
    provider.setProName("呆古米");
    provider.setProCode("222");
    provider.setCreationDate(new Date());

    return provider;
}

效果

请添加图片描述

想为null时不返回可以用上面说过的(放在实体类所需属性或类上):

@JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL)

效果

请添加图片描述

传集合参数

Controller层

@RequestMapping("findProviderListJson")
@ResponseBody
public List<Provider> findProviderListJson() {
    
      
    List<Provider> providers = new ArrayList<>();

    Provider provider1 = new Provider();
    provider1.setProName("呆古米");
    provider1.setProCode("222");
    provider1.setCreationDate(new Date());

    Provider provider2 = new Provider();
    provider2.setProName("海胆kuma");
    provider2.setProCode("333");
    provider2.setCreationDate(new Date());

    providers.add(provider1);
    providers.add(provider2);

    return providers;
}

效果
请添加图片描述

重定向与转发

public String index1(User user)
{
    
      
    .....
    return "redirect:/user.jsp";//重定向
    return "forward:页面";//转发 
}

当我们使用重定向时可以解决一些传参问题,比如两个Controller的传参问题

Controller传参到另一个Controller中

  • 有时候可能会碰到这样的问题:
    • 在A的Controller中传参到B的Controller中,
    • 而A的返回值是作为B的参数,
    • 并且本身请求A的参数中有一些需要进行一些处理后才能用于请求B(而可能甚至我们并不需要用到注入Service的方法(这个条件也是。。。亿点死亡😅))
    • A中一些传参名需要更改成对应B中的传参名(尤其这个条件很致命😅,相同的参数数量,可能其中有一个需要经过处理后再改参数名再传到B的Controller中作为请求参数之一)

这时候那个需要进行一定处理的参数传到B的Controller时,会发现传过去的这个参数为null,而无论用到上文的request作用域中的什么Model、ModelAndView、Map等方法时均会失灵(是这种感觉),但是明明代码又没有问题

解决方法

RedirectAttributes attributes传参

@RequestMapping("xxx")
public String xxx(String 无需做处理可直接请求下一个controller的参数, String 需要经过处理才能请求下一个controller的参数(且传递过去的参数名会变),假设此参数叫做a1, RedirectAttributes attributes){
    
      

    //一系列处理操作....

    String a2 = xxxxx;	//此时a1参数经过处理转换成a2
    
    //用attributes.addAttribute("key",value)来传递参数(可以发现方法中写好的参数是不用再次添加的,可能是被存进request作用域中了,上面提到的Model、Map等方法也是如此,不确定是否可以不写,不过这样本人试了的确是即使没写也直接传过去了)
    attributes.addAttribute("a2", a2);
    //写好重定向的路径即可
    return "redirect:/aaaa/bbbbbbbbb";
}

拆分来看是这样的(因为这个问题让我心态炸了一下午,所以解释起来会很磨叽😬,而且这个情况应该也比较少,网上一堆不靠谱的):

1、用RedirectAttributes attributes传参

2、用attributes.addAttribute("key",value)来传递参数

3、写好重定向的路径即可(不用多写什么其它乱七八糟的)

return "redirect:/aaaa/bbbbbbbbb";

REST风格

将参数作为访问路径(比如http://www.xxx.com/1222.html)

可同时设置多个参数

参数不是以?参数的形式显示且比较美观

@RequestMapping(value="/ts1/{user_id}.html")
public String index1(@PathVariable("user_id") Long user_id)
{
    
      
    System.out.println("id======"+user_id);
    return "user";
}


请求路径为:.../ts1/参数.html
    如:..../ts1/1.html或..../ts1/10.html

servlet方式

上面例子里就用到过

需要先引入servlet-api.jar

pom.xml

<!-- javax-Servlet -->
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>servlet-api</artifactId>
    <version>2.5</version>
</dependency>

Controller层

public String cs(HttpServletRequest request, HttpServletResponse response, HttpSession session){
    
      
    session.setAttribute("username","123");
    return "user";
}
不是以`?参数`的形式显示且比较美观

```java
@RequestMapping(value="/ts1/{user_id}.html")
public String index1(@PathVariable("user_id") Long user_id)
{
    
      
    System.out.println("id======"+user_id);
    return "user";
}


请求路径为:.../ts1/参数.html
    如:..../ts1/1.html或..../ts1/10.html