Spring基础知识(14)- Spring MVC (四)
重定向和转发、Spring Bean定义和装配相关的注解、@ModelAttribute注解
1. 重定向和转发
Spring MVC 请求方式分为重定向、转发,分别使用 forward 和 redirect 关键字在 controller 层进行处理。
1) 重定向
重定向是将用户从当前处理请求定向到另一个视图(例如 JSP)或处理请求,以前的请求(request)中存放的信息全部失效,并进入一个新的 request 作用域。
重定向过程:
(1) 客户浏览器发送 http 请求,Web 服务器接受后发送 302 状态码响应及对应新的 location 给客户浏览器;
(2) 客户浏览器发现是 302 响应,则自动再发送一个新的 http 请求,请求 URL 是新的 location 地址,服务器根据此请求寻找资源并发送给客户。
重定向是客户端行为,浏览器做了两次访问请求,而且两次访问的地址栏路径是不一样的。
示例
1 package com.example.controller; 2 import org.springframework.stereotype.Controller; 3 import org.springframework.web.bind.annotation.RequestMapping; 4 5 @Controller 6 @RequestMapping("/index") 7 public class IndexController { 8 9 @RequestMapping(value="/redirect", method=RequestMethod.GET) 10 public String redirect() { 11 // 重定向到一个请求方法 (同一个控制器类可以省略/index/) 12 return "redirect:home"; 13 } 14 15 @RequestMapping(value="/home", method=RequestMethod.GET) 16 public String home() { 17 return "home"; 18 } 19 }
2) 转发
转发是将用户对当前处理的请求转发给另一个视图或处理请求,以前的 request 中存放的信息不会失效。
转发过程:
(1) 客户浏览器发送 http 请求,Web 服务器接受此请求,调用内部的一个方法在容器内部完成请求处理和转发动作,将目标资源发送给客户;
(2) 在这里转发的路径必须是同一个 Web 容器下的 URL,其不能转向到其他的 Web 路径上,中间传递的是自己的容器内的 request。
转发是服务器行为,浏览器只做了一次访问请求,地址栏一直显示其第一次访问的路径。
在 Spring MVC 框架中,控制器类中处理方法的 return 语句默认就是转发实现,只不过实现的是转发到视图。
示例
1 package com.example.controller; 2 import org.springframework.stereotype.Controller; 3 import org.springframework.web.bind.annotation.RequestMapping; 4 5 @Controller 6 @RequestMapping("/index") 7 public class IndexController { 8 9 @RequestMapping(value="/forward", method=RequestMethod.GET) 10 public String forward() { 11 // 转发到一个请求方法(同一个控制器类可以省略/index/) 12 return "forward:home"; 13 } 14 15 ... 16 }
3) 转发静态资源
在 Spring MVC 框架中,如果直接转发到一个不需要 DispatcherServlet 的资源。
示例
1 package com.example.controller; 2 import org.springframework.stereotype.Controller; 3 import org.springframework.web.bind.annotation.RequestMapping; 4 5 @Controller 6 @RequestMapping("/index") 7 public class IndexController { 8 9 @RequestMapping(value="/demo", method=RequestMethod.GET) 10 public String demo() { 11 return "forward:/static/demo.html"; 12 } 13 14 }
注:要先确保可以访问静态资源,如何配置可以参考 的 "2. 访问静态资源"。
2. Spring Bean定义和装配相关的注解
1)Spring Bean 定义相关的注解
注解 | 说明 |
@Component | 该注解用于描述 Spring 中的 Bean,它是一个泛化的概念,仅仅表示容器中的一个组件(Bean),并且可以作用在应用的任何层次,例如 Service 层、Dao 层等。 |
@Repository | 该注解用于将数据访问层(Dao 层)的类标识为 Spring 中的 Bean,其功能与 @Component 相同。 |
@Service | 该注解通常作用在业务层(Service 层),用于将业务层的类标识为 Spring 中的 Bean,其功能与 @Component 相同。 |
@Controller | 该注解通常作用在控制层(如 Struts2 的 Action、SpringMVC 的 Controller),用于将控制层的类标识为 Spring 中的 Bean,其功能与 @Component 相同。 |
@Component 属于通用注解,当组件不好归类的时候,可以使用这个注解进行标注。@Controller 在前面的文章里已经详细介绍,本文示例会用到 @Repository 和 @Service 注解。
2)Spring Bean 属性注入和自动装配相关的注解
注解 | 说明 |
@Autowired | 可以应用到 Bean 的属性变量、setter 方法、非 setter 方法及构造函数等,默认按照 Bean 的类型进行装配。 |
@Resource | 作用与 Autowired 相同,区别在于 @Autowired 默认按照 Bean 类型装配,而 @Resource 默认按照 Bean 的名称进行装配。 |
@Qualifier | 与 @Autowired 注解配合使用,会将默认的按 Bean 类型装配修改为按 Bean 的实例名称装配,Bean 的实例名称由 @Qualifier 注解的参数指定。 |
其中 @Resource 不是 Spring 的注解,它的包是 javax.annotation.Resource,Spring 支持该注解。本文示例会用到 @Autowired 和 @Qualifier注解。
示例
在 “” 里创建的 SpringmvcBasic 项目基础上,代码如下:
(1) 使用 src/main/java/com/example/entity/User.java 文件
1 package com.example.entity; 2 3 public class User { 4 private int id; 5 private String username; 6 private String password; 7 8 public User() { 9 10 } 11 12 // 省略 getter 和 setter方法 13 ... 14 }
(2) 创建 src/main/java/com/example/dao/UserDao.java 文件
1 package com.example.dao; 2 3 import com.example.entity.User; 4 5 public interface UserDao { 6 7 User getById(int id); 8 int getCount(); 9 10 }
(3) 创建 src/main/java/com/example/dao/UserDaoImpl.java 文件
1 package com.example.dao; 2 3 import org.springframework.stereotype.Repository; 4 import com.example.entity.User; 5 6 @Repository 7 public class UserDaoImpl implements UserDao { 8 9 @Override 10 public User getById(int id) { 11 return null; 12 } 13 14 @Override 15 public int getCount() { 16 return 99; 17 } 18 19 }
(4) 创建 src/main/java/com/example/service/UserService.java 文件
1 package com.example.service; 2 3 public interface UserService { 4 5 int getUserCount(); 6 7 }
(5) 创建 src/main/java/com/example/service/UserServiceImpl.java 文件
1 package com.example.service; 2 3 import org.springframework.stereotype.Service; 4 import org.springframework.beans.factory.annotation.Autowired; 5 import com.example.dao.UserDaoImpl; 6 7 @Service("userServiceImpl") 8 public class UserServiceImpl implements UserService { 9 @Autowired 10 private UserDaoImpl userDaoImpl; 11 12 @Override 13 public int getUserCount() { 14 return userDaoImpl.getCount(); 15 } 16 }
(6) 创建 src/main/java/com/example/service/UserServiceImpl2.java 文件
1 package com.example.service; 2 3 import org.springframework.stereotype.Service; 4 import org.springframework.beans.factory.annotation.Autowired; 5 import com.example.dao.UserDaoImpl; 6 7 @Service("userServiceImpl2") 8 public class UserServiceImpl2 implements UserService { 9 @Autowired 10 private UserDaoImpl userDaoImpl; 11 12 @Override 13 public int getUserCount() { 14 return userDaoImpl.getCount() + 6; 15 } 16 }
(7) 创建 src/main/java/com/example/controller/MessageController.java 文件
1 package com.example.controller; 2 3 import org.springframework.beans.factory.annotation.Autowired; 4 import org.springframework.beans.factory.annotation.Qualifier; 5 import org.springframework.stereotype.Controller; 6 import org.springframework.ui.Model; 7 import org.springframework.web.bind.annotation.RequestMapping; 8 import org.springframework.web.bind.annotation.RequestMethod; 9 10 import com.example.service.UserService; 11 12 @Controller 13 @RequestMapping("/msg") 14 public class MessageController { 15 @Autowired 16 //@Qualifier("userServiceImpl") 17 @Qualifier("userServiceImpl2") 18 private UserService userService; 19 20 @RequestMapping(value="/count", method= RequestMethod.GET) 21 public String message(Model model) { 22 model.addAttribute("message", "userService.getUserCount() = " + userService.getUserCount()); 23 return "message"; 24 } 25 }
注:@Autowired 根据类型无法分辨 @Service("userServiceImpl") 和 @Service("userServiceImpl2"),需要 @Qualifier 来指定实例名称。
(8) 创建 src/main/webapp/WEB-INF/jsp/message.jsp 文件
1 <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %> 2 3 4 "Content-Type" content="text/html; charset=UTF-8"> 5Message 6 7 8Message Page
910
Message: ${message}
11 12
访问 http://localhost:9090/msg/count
Message Page
Message: userServiceImpl.getUserCount() = 105
3. @ModelAttribute 注解
@ModelAttribute 注解用于将请求参数绑定到 Model 对象。
在 Controller 中使用 @ModelAttribute 时,有以下几种应用情况。
(1) 应用在方法上
(2) 应用在方法的参数上
(3) 应用在方法上,并且方法也使用了 @RequestMapping
需要注意的是,因为模型对象要先于 Controller 方法之前创建,所以被 @ModelAttribute 注解的方法会在 Controller 每个方法执行之前都执行。因此一个 Controller 映射多个 URL 时,要谨慎使用。
1) 应用在方法上
(1)应用在无返回值的方法
示例:创建 ModelAttributeController,代码如下。
1 package com.example.controller; 2 3 import org.springframework.stereotype.Controller; 4 import org.springframework.ui.Model; 5 import org.springframework.web.bind.annotation.ModelAttribute; 6 import org.springframework.web.bind.annotation.RequestMapping; 7 import org.springframework.web.bind.annotation.RequestParam; 8 9 @Controller 10 public class ModelAttributeController { 11 12 // 方法无返回值 13 @ModelAttribute 14 public void modelAttr(@RequestParam(required = false) String username, Model model) { 15 model.addAttribute("username", username); 16 } 17 18 @RequestMapping(value = "/model") 19 public String model() { 20 return "model"; 21 } 22 }
创建 model.jsp 页面,代码如下。
1 <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %> 2 3 4 "Content-Type" content="text/html; charset=UTF-8"> 5Model 6 7 8Model Page
910
Username: ${username}
11 12
访问:http://localhost:9090/model?username=Tester
Model Page
Username: Tester
以上示例,在请求 /model?username=Tester 后,Spring MVC 会先执行 modelAttr 方法,将 username 的值存入到 Model 中。然后执行 model 方法,这样 username 的值就被带到了 model 方法中。
将 modelAttr 和 model 方法合二为一后,代码如下。
1 @RequestMapping(value = "/model") 2 public String model(@RequestParam(required = false) String username, Model model) { 3 model.addAttribute("username", username); 4 return "model"; 5 }
(2) 应用在有返回值的方法
示例:修改 ModelAttributeController 控制类,代码如下。
1 package com.example.controller; 2 3 import org.springframework.stereotype.Controller; 4 import org.springframework.ui.Model; 5 import org.springframework.web.bind.annotation.ModelAttribute; 6 import org.springframework.web.bind.annotation.RequestMapping; 7 import org.springframework.web.bind.annotation.RequestParam; 8 9 @Controller 10 public class ModelAttributeController { 11 // 方法有返回值 12 @ModelAttribute("username") 13 public String modelAttr(@RequestParam(required = false) String username) { 14 return username; 15 } 16 17 @RequestMapping(value = "/model") 18 public String model() { 19 return "model"; 20 } 21 }
访问:http://localhost:9090/model?username=Tester2
Model Page
Username: Tester2
2) 应用在方法的参数上
@ModelAttribute 注解在方法的参数上,调用方法时,模型的值会被注入。这在实际使用时非常简单,常用于将表单属性映射到模型对象。
1 @RequestMapping("/login/post6") 2 public String loginPost6(@ModelAttribute("user") User user, Model model) { 3 4 if ("admin".equals(user.getUsername()) && "123456".equals(user.getPassword())) { 5 model.addAttribute("message", "success - 通过 @ModelAttribute 接收请求参数"); 6 return "success"; 7 } else { 8 model.addAttribute("message", "failed - 通过 @ModelAttribute 接收请求参数"); 9 return "failed"; 10 } 11 12 }
上述代码中 “@ModelAttribute("user") User user” 语句的功能有两个:
(1) 将请求参数的输入封装到 user 对象中
(2) 创建 User 实例
以 “user” 为键值存储在 Model 对象中,和 “model.addAttribute("user", user)” 语句的功能一样。如果没有指定键值,即 “@ModelAttribute User user”,那么在创建 User 实例时以 “user” 为键值存储在 Model 对象中,和“model.addAtttribute("user", user)” 语句的功能一样。
3) ModelAttribute + RequestMapping
示例:修改 ModelAttributeController,代码如下。
1 package com.example.controller; 2 3 import org.springframework.stereotype.Controller; 4 import org.springframework.ui.Model; 5 import org.springframework.web.bind.annotation.ModelAttribute; 6 import org.springframework.web.bind.annotation.RequestMapping; 7 import org.springframework.web.bind.annotation.RequestParam; 8 9 @Controller 10 public class ModelAttributeController { 11 // @ModelAttribute 和@ RequestMapping 同时放在方法上 12 @RequestMapping(value = "/model") 13 @ModelAttribute("username") 14 public String model(@RequestParam(required = false) String username) { 15 return username; 16 } 17 }
访问:http://localhost:9090/model?username=Tester3
Model Page
Username: Tester3
@ModelAttribute 和 @RequestMapping 注解同时应用在方法上时,有以下作用:
(1) 方法的返回值会存入到 Model 对象中,key 为 ModelAttribute 的 value 属性值。
(2) 方法的返回值不再是方法的访问路径,访问路径会变为 @RequestMapping 的 value 值,例如:@RequestMapping(value = "/model") 对应 model.jsp 页面。
4) Model 和 ModelView 的区别
Model:每次请求中都存在的默认参数,利用其 addAttribute() 方法即可将服务器的值传递到客户端页面中。
ModelAndView:包含 model 和 view 两部分,使用时需要自己实例化,利用 ModelMap 来传值,也可以设置 view 的名称。
5) 其它
@ModelAttribute 注解的方法会在每次调用该控制器类的请求处理方法前被调用。这种特性可以用来控制登录权限。
控制登录权限的方法有很多,例如拦截器、过滤器等。
创建 ModelController ,代码如下所示:
package com.example.controller;
import javax.servlet.http.HttpSession;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.stereotype.Controller;
@Controller
@RequestMapping("/admin")
public class ModelController {
@ModelAttribute
public void isLogin(HttpSession session) throws Exception {
if (session.getAttribute("user") == null) {
System.out.println("ModelController -> isLogin(): user is null");
throw new Exception("Not authorized");
}
}
@RequestMapping("/add")
public String add(Model model) {
model.addAttribute("message", "Add success");
return "success";
}
@RequestMapping("/update")
public String update(Model model) {
model.addAttribute("message", "Update success");
return "success";
}
}
在上述 ModelController 类中的 add、update 请求处理方法执行时,首先执行 isLogin() 方法判断登录权限。
访问:http://localhost:9090/admin/add 测试登录权限。