控制器Controller是处理器,是真正处理请求的组件
一般在src/main/java/com/qdu下建立一个controller包用来存放所有控制器。当创建一个控制器时,首先要记得使用@Controller标记将该类注册成为一个控制器类。
然后在SpringMVCConfig类中开启对com.qdu.controller包的扫描,并使用@EnableWebMvc注解,这样controller待会要用到的@RequestMapping等的配置会生效。
@Configuration
@ComponentScan(basePackages = {"com.qdu.controller"})
@EnableWebMvc
public class SpringMVCConfig implements WebMvcConfigurer{
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/static/**").addResourceLocations("/static/");
}
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.jsp("/WEB-INF/jsp/", ".jsp");
}
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
registry.addViewController("/toIndex").setViewName("index");
registry.addViewController("/toPage1").setViewName("page1");
registry.addViewController("/toPage2").setViewName("page2");
registry.addViewController("/toPage3").setViewName("page3");
registry.addViewController("/toNext").setViewName("next");
registry.addViewController("/toAddStudent").setViewName("add_student");
registry.addViewController("/toFindStudentPage").setViewName("find_student");
}
}
可以发现在Spring MVC的配置类中我们也配置了许多视图控制器,用于跳转到对应的JSP页面,如这里的toIndex是跳转到index.jsp,在前端页面的引用方法为:
在controller中可以使用@RequestMapping注解,它默认可以映射所有请求,包括get和post。也可以通过method属性指定当前方法处理的请求的类型,这样方法只能处理对应类型请求。
假设现在有一个控制器TestController1,要在该控制器中处理两数相加的get请求与post请求,方法名分别是cal1与cal2
@Controller
@RequestMapping("/test1")
public class TestController1 {
public String cal1(int num1, int num2, Model model) { }
public String cal2(int num1, int num2, Model model) { }
}
首先对两种方法使用@RequestMapping注解,内容应填入对应的url,也可以用method属性指定请求对应的类型。
@RequestMapping(value = "/test1/cal1", method = RequestMethod.GET)
public String cal1(int num1, int num2, Model model) { }
@RequestMapping(value = "/test1/cal2", method = RequestMethod.POST)
public String cal2(int num1, int num2, Model model) { }
接下来继续写方法内容,大致过程就是获取请求参数(这里就是要相加的两个数),然后进行加和操作后再返回结果页面。
我们可以从前端的表单看到输入进来的两个数:
注意观察两个数字的name属性的值即可。
@RequestMapping(value = "/test1/cal1", method = RequestMethod.GET)
public String cal1(int num1, int num2, Model model) {
model.addAttribute("jieguo", num1 + num2);
return "result";
}
这里给前端展示结果的变量取名为“jieguo”,故引用时应为${jieguo},代码应如下:
计算结果: ${jieguo}
同时还可以在前端展示请求的url,在cal1方法中加上:
model.addAttribute("requestUrl", "/test1/cal1");
前端展示的代码应为:
请求url: ${requestUrl}
最后展示的结果应该为:
请求url:/test1/cal1
计算结果:300
cal2方法的代码大同小异,只需要将method属性的值改为:RequsetMethod.POST
如果觉得使用@RequestMapping注解书写较多,也可使用@GetMapping、@PostMapping简化书写。
最后,完整的的TestController1代码如下:
package com.qdu.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@Controller
@RequestMapping("/test1")
public class TestController1 {
@GetMapping("/cal1")
public String cal1(int num1, int num2, Model model) {
model.addAttribute("requestUrl", "/test1/cal1");
model.addAttribute("jieguo", num1 + num2);
return "result";
}
@PostMapping("/cal2")
public String cal2(int num1, int num2, Model model) {
model.addAttribute("requestUrl", "/test1/cal2");
model.addAttribute("jieguo", num1 + num2);
return "result";
}
}
一定要注意,这里的
return "result"
是指跳转到result.jsp页面,原理在创建和配置Spring MVC框架构建Web应用这篇文章中提到过
因为WEB-INF/jsp目录下的页面是不能直接访问,需要经过控制器跳转,而且最一开始的Spirng MVC配置类中,我并没有给result.jsp配置对应的视图控制器,而是应该在得出计算结果之后,由对应的控制器控制跳转到结果界面。
最后有一个小区别,我们观察浏览器的url栏,发现get请求和post请求的url并不一样:
get请求会显示出请求参数及其值:
而post请求则不会:
控制方法参数可以是任何类型,可以保持参数名和请求参数名同名来接收对应的参数,常规类型转换会自动完成。
请看接下来的例子,前端请求两个数num1、num2,返回二者相加的结果,控制器类方法的参数也为num1、num2
前端:
1. 任意类型-方法参数名和请求参数名同名
在这一章节中使用的TestController2的代码初始化如下:
@Controller
@RequestMapping("/test2")
public class TestController2 {
@Autowired
private ExamService examService;
}
控制器类方法req1:(注意,req1的参数应该与前端的一样,为num1、num2)
@GetMapping("/req1")
public String req1(int num1, int num2, Model model) {
model.addAttribute("jieguo", num1 + num2);
return "result";
}
若前端发来的请求参数名与控制器类的方法参数不同名,则应使用@RequestParam注解指定请求参数的信息。
假如在前端,传请求参数的代码为:
2. 使用@RequestParam注解
可以看到前端的请求参数名为number1、number2,但是在控制器类方法中,我仍然想用num1、num2当方法参数,可以这样写:
@GetMapping("/req2")
public String req2(
@RequestParam(value = "number1", defaultValue = "0") int num1,
@RequestParam(value = "number2", defaultValue = "0") int num2,
Model model) {
model.addAttribute("jieguo", num1 + num2);
return "result";
}
value或name属性: 指定请求参数的名称
defaultValue属性: 指定当该请求参数没有提供的时候,使用什么默认值
requird属性: 指定该请求参数是否必须要提供,默认是true,不提供会抛出异常,对于可选的参数可设置为false
有时候一个参数会有多个值,比如我们使用复选框来提交的数据。
假设我们前端让用户选取自己的爱好有哪些,可以多选,前端会传来多个请求参数hobbies的值。对于这多个hobbies的值,我们可以用一个String[]数组来接受。
@GetMapping("/req3")
public String req3(String[] hobbies, Model model) {
model.addAttribute("hobbies", hobbies);
return "hobby_page";
}
在前端hobby_page展示爱好,使用
结果:
${hobby}
有的时候除了获取请求参数,还可能需要操作请求,可以直接获取请求对象
可以使用HttpServletRequest或ServletRequest类型来获取请求对象
可以使用HttpServletResponse或ServletResponse类型来获取响应对象
前端的请求参数为:
4. 请求对象(HttpServletRequest)和响应对象(HttpServletResponse)
在控制器方法中,使用HttpServletRequest类型的req获取请求对象,进而得到请求参数num1、num2
@GetMapping("/req4")
public String req4(Model model, HttpServletRequest req, HttpServletResponse resp) {
int num1 = Integer.parseInt(req.getParameter("num1"));
int num2 = Integer.parseInt(req.getParameter("num2"));
model.addAttribute("jieguo", num1 + num2);
return "result";
}
有的时候可能需要获取所有的请求参数(请求参数比较多的时候,而且个数不定的时候),可以直接使用一个Map
@GetMapping("/req5")
public String req5(@RequestParam Map map, Model model) {
int num1 = Integer.parseInt(map.get("num1"));
int num2 = Integer.parseInt(map.get("num2"));
model.addAttribute("jieguo", num1 + num2);
return "result";
}
可以发现,我们之前的控制器类方法中都有一个参数Model对象,放入Model对象的数据是请求范围的数据,默认跳转页面是以请求转发的形式跳转页面。
可以直接加一个HttpSession类型的参数,用于获取当前会话对象,以操作当前会话对象
@GetMapping("/req6_1")
public String req6_1(int number, HttpSession session) {
session.setAttribute("jieguo", number * number);
return "result";
}
如果希望获取会话范围的属性或操作会话,可以直接获取会话对象,进而操作会话
@GetMapping("/req6_2")
public String req6_2(HttpSession s) {
System.out.println("~~~~~~~~~~~~~~~jieguo:" + s.getAttribute("jieguo"));
return "hint";
}
在这里,我们会在控制台输出6_1中number * number的结果:
如果希望获取会话范围的属性,也可使用@SessionAttribute注解修饰的方法参数来获取一个会话范围的属性,可以直接使用对应类型接收会话属性。
需要指定获取的属性的名称,如这里的jieguo
@GetMapping("/req6_3")
public String req6_3(@SessionAttribute("jieguo") int result) {
System.out.println("***************jieguo:" + result);
return "hint";
}
如果控制器方法中需要返回一些数据显示到页面上,可以使用Model或ModelMap或Map对象,作用一样,但是每个类型提供的方法不同
可以根据选择,通常使用Model即可,这三个对象中的数据都是请求范围的数据
@GetMapping("/req7")
public String req7(Model model, ModelMap mm, Map map) {
model.addAttribute("msg1", "这是使用Model对象添加的属性");
mm.addAttribute("msg2", "这是使用ModelMap对象添加的属性");
map.put("msg3", "这是使用Map对象添加的属性");
return "message";
}
对于REST风格的路径req8/8 ,如果某一级表示的是参数值,则使用{变量名}来表示这是一个路径变量/路径参数
前端我们设定的url为test2/req8/8,我们要将最后这个8设定为路径变量
8. 使用@PathVariable(路径变量)-单个参数
我们在控制器方法添加了一个参数number(使用对应类型即可),用于接收路径变量num的值:
@GetMapping("/req8/{num}")
public String req8(Model model, @PathVariable("num") int number) {
model.addAttribute("jieguo", number * number);
return "result";
}
对于url:/req9/9/square
//如果方法参数名和路径变量名同名,则使用@PathVariable注解的时候就不需要指定路径变量的名称了
@GetMapping("/req9/{num}/square")
public String req9(@PathVariable int num, Model model) {
model.addAttribute("jieguo", num * num);
return "result";
}
对于url:/req10/10/add/100,我想将10与100分别设为路径变量,分别命名为num1、num2,直接在方法声明多个参数即可
@GetMapping("/req10/{num1}/add/{num2}")
public String req10(@PathVariable int num1, @PathVariable int num2, Model model) {
model.addAttribute("jieguo", num1 + num2);
return "result";
}
使用 * 通配符匹配一级路径
诸如此路径:/req11/*/square/111,这个路径可以匹配/req11/user/square/111,也可以匹配/req11/admin/square/111
假设在前端有这两个请求:
11.1. 使用@PathVariable(路径变量)-通配符*
11.2. 使用@PathVariable(路径变量)-通配符*
在这里,路径最后的数为路径变量num,要在控制器方法中得到num*num,除此之外只有第三级user与admin不同,可以用一个*通配符来匹配这一级路径。
@GetMapping("/req11/*/square/{num}")
public String req11(Model model, @PathVariable int num) {
model.addAttribute("jieguo", num * num);
return "result";
}
使用 ** 通配符匹配任意多级路径
诸如这两个路径:
/req12/aaa/bbb/ccc/ddd/121/square
/req12/ddd/122/square
我们可以发现,路径中的数字为路径变量num,在控制器方法中得到num*num,其余部分只在/req12与/{num}之间的路径不同,但是这之间并不只是单级路径,而是多级路径,要使用 ** 通配符来匹配:
@GetMapping("/req12/**/{num}/square")
public String req12(Model model, @PathVariable int num) {
model.addAttribute("jieguo", num * num);
return "result";
}
控制器类方法参数支持对象类型,在有以下类似的情形可以使用对象类型参数:
要提交多个数据,每个请求参数与实体类的参数一一对应,诸如我们前端有一个表单,用来收集学生的个人信息:
在这里我们还有一个实体类Student,Student类中的每一个参数与表单中的每一请求参数一一对应:
public class Student {
private String sid;
private String sname;
private String spassword;
private String sgender;
private String sbatch;
//所有的请求参数都是以字符串的形式提交,字符串转成double、int等类型会自动转换
//但是不能自动转换成Date类型
//使用@DateTimeFormat注解后,字符串类型的请求参数就可以转换成Date类型
@DateTimeFormat(pattern="yyyy-MM-dd")
private Date sdob;
public Student() {
}
public Student(String sid, String sname, String spassword, String sgender, String sbatch, Date sdob) {
this.sid = sid;
this.sname = sname;
this.spassword = spassword;
this.sgender = sgender;
this.sbatch = sbatch;
this.sdob = sdob;
}
public String getSid() {
return sid;
}
public void setSid(String sid) {
this.sid = sid;
}
public String getSname() {
return sname;
}
public void setSname(String sname) {
this.sname = sname;
}
public String getSpassword() {
return spassword;
}
public void setSpassword(String spassword) {
this.spassword = spassword;
}
public String getSgender() {
return sgender;
}
public void setSgender(String sgender) {
this.sgender = sgender;
}
public String getSbatch() {
return sbatch;
}
public void setSbatch(String sbatch) {
this.sbatch = sbatch;
}
//对于java对象,如果有日期类型的属性,转换成json的时候,默认是以毫秒的形式显示时间
//对于日期时间类型的数据,可以添加@JsonFormat来指定转成json的时候
//日期时间是什么格式,还可以指定时区
@JsonFormat(pattern="yyyy-MM-dd",timezone = "GMT+8")
public Date getSdob() {
return sdob;
}
public void setSdob(Date sdob) {
this.sdob = sdob;
}
@Override
public String toString() {
return "Student [sid=" + sid + ", sname=" + sname + ", spassword=" + spassword + ", sgender=" + sgender
+ ", sbatch=" + sbatch + ", sdob=" + sdob + "]";
}
}
我们要将前端获得的学生信息进行展示:
@GetMapping("/req13")
public String req13(Student s) {
System.out.println("学生信息:"+s);
return "hint";
}
在这里为了简化,不再自行创建cookie,每个web程序都会放置一个名为JSESSIONID的cookie到客户端
为了获取某个Cookie的值,可以给控制器方法添加参数,用于获取Cookie的值
用于获取Cookie值的参数需要使用@CookieValue注解修饰,并指定Cookie的名称
@GetMapping("/req14")
public String req14(@CookieValue("JSESSIONID") String sessionId) {
System.out.println("*******************sessionId:"+sessionId);
return "hint";
}
从前端接受一个数并返回这个数的平方,控制器类的方法为String类型
前端发送请求的代码为:
3.1. String
控制器类方法代码:
@GetMapping("/req3_1")
public String req3_1(int number, Model model) {
model.addAttribute("jieguo", number * number);
return "result";
}
在默认情况下,最后的return "result"是指要跳转的视图的名称。
但有些情况下,我们希望控制器方法返回的内容为响应正文的内容,而不是跳转的视图名称,此时可以使用@ResponseBody注解修饰该方法。
// produces属性用于指定返回的响应内容的MIME类型和字符集编码
@GetMapping("/req4_1", produces = "text/html;charset=utf-8")
@ResponseBody
public String req4_1(String name) {
return "Hello, " + name + "
";
}
这里控制器类的方法为ModelAndView类型,返回ModelAndView类型的mv。将结果和要跳转的视图都封装到ModelAndView中,最后返回这个ModelAndView。
@GetMapping("/req3_2")
public ModelAndView req3_2(int number) {
ModelAndView mv = new ModelAndView();
mv.setViewName("result");
mv.addObject("jieguo", number * number);
return mv;
}
@GetMapping("/req4_2")
@ResponseBody
public Integer req4_2(int number) { return number * number; }
Spring MVC默认使用Jackson作为json处理器,来转换java对象和json数据。只要添加jackson的依赖,java对象就会被自动转换为json对象。
如果返回一个对象(封装器类型和String等除外),则数据默认会被转换成json格式
@GetMapping("/req4_3")
@ResponseBody
public Student req4_3() {
return new Student(
return new Student(
"2019209999","张三","123456","男","19软件J03",Date.valueOf("2001-12-03")
);
}
再举一个例子,发送ajax请求,返回单个对象。
假设前端有一个表单收集学生的学号,填写学号后能显示该学生的学生信息:
显示查到的学生信息的前端代码如下:
学号:
姓名:
密码:
姓名:
班级:
生日:
我们有一个控制器类TestController4,调用StudentService中的方法根据学号查询学生,返回一个Student对象:
//@RestController是@Controller+@ResponseBody注解的组合
//@RestController说明当前类是个控制器类,并且相当于每个方法前添加@ResonseBody注解
@RestController
@RequestMapping("/test4")
public class TestController4 {
@Autowired
StudentService studentService;
@GetMapping("/req4_7")
public Student req4_7(String id) {
return studentService.getOne(id);
}
}
此时我们回到前端来写ajax部分的代码,根据后端Controller的方法可以得知,请求的url应为/test4/req4_7,请求类型为Get请求
如果返回的是列表,列表会被转换成json数组
@GetMapping("/req4_4")
public List req4_4() {
List list=new ArrayList();
list.add(new Student("2019209991","小胖","123456","男","19软件J01",Date.valueOf("2001-12-03")));
list.add(new Student("2019209992","小强","123456","男","19软件J01",Date.valueOf("2002-02-09")));
list.add(new Student("2019209993","小兰","123456","女","19软件J02",Date.valueOf("2003-11-10")));
list.add(new Student("2019209994","小明","123456","男","19软件J02",Date.valueOf("2000-09-01")));
list.add(new Student("2019209995","小奇","123456","男","19软件J03",Date.valueOf("2001-07-08")));
list.add(new Student("2019209996","小红","123456","女","19软件J03",Date.valueOf("2002-11-19")));
return list;
}
如果返回的是一个Map集合,则该Map对象会被转换成一个json对象
@GetMapping("/req4_5")
public Map req4_5() {
Map map=new HashMap();
map.put("error_code", 101);
map.put("error_message", "发送的请求参数有错");
return map;
}
返回类型为void,说明返回的响应内容为空
@GetMapping("/req4_6")
public void req4_6() {
System.out.println("...................我没返回任何值!!!");
}