【快速学习系列】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