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">
 5                 Message
 6             
 7             
 8                 

Message Page

9

 

10

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">
 5                     Model
 6                 
 7                 
 8                     

Model Page

9

 

10

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  测试登录权限。