SpringMVC的优势(model view controller)

1、清晰的角色划分: 前端控制器(DispatcherServlet) 请求到处理器映射(HandlerMapping) 处理器适配器(HandlerAdapter) 视图解析器(ViewResolver) 处理器或页面控制器(Controller) 验证器( Validator) 命令对象(Command 请求参数绑定到的对象就叫命令对象) 表单对象(Form Object 提供给表单展示和提交到的对象就叫表单对象) 2、分工明确,而且扩展点相当灵活,可以很容易扩展,虽然几乎不需要 3、由于命令对象就是一个 POJO,无需继承框架特定 API,可以使用命令对象直接作为业务对象 4、和 Spring 其他框架无缝集成,是其它 Web 框架所不具备的 5、可适配,通过 HandlerAdapter 可以支持任意的类作为处理器 6、可定制性,HandlerMapping、ViewResolver 等能够非常简单的定制 7、功能强大的数据验证、格式化、绑定机制 8、利用 Spring 提供的 Mock 对象能够非常简单的进行 Web 层单元测试 9、本地化、主题的解析的支持,使我们更容易进行国际化和主题的切换 10、强大的 JSP 标签库,使 JSP 编写更容易 ………………还有比如RESTful风格的支持、简单的文件上传、约定大于配置的契约式编程支持、基于注解的零配 置支持等等

执行流程
image.png

  1. 服务器启动,应用被加载。读取到 web.xml 中的配置创建 spring 容器并且初始化容器中的对象
  2. 浏览器发送请求,被 DispatherServlet 捕获,该 Servlet 并不处理请求,而是把请求转发出去。转发 的路径是根据请求 URL,匹配@RequestMapping 中的内容
  3. 匹配到了后,执行对应方法。该方法有一个返回值
  4. 根据方法的返回值,借助 InternalResourceViewResolver 找到对应的结果视图
  5. 渲染结果视图,响应浏览器

1 SpringMVC 的请求响应流程

image.png

2 SpringMVC中的一些组件

DispatcherServlet:前端控制器

  • 用户请求到达前端控制器,它就相当于 mvc 模式中的 c,dispatcherServlet 是整个流程控制的中心,由它调用其它组件处理用户的请求,dispatcherServlet 的存在降低了组件之间的耦合性

HandlerMapping:处理器映射器

  • HandlerMapping 负责根据用户请求找到 Handler 即处理器,SpringMVC 提供了不同的映射器实现不同的 映射方式,例如:配置文件方式,实现接口方式,注解方式等

Handler:处理器

  • 它就是我们开发中要编写的具体业务控制器。由 DispatcherServlet 把用户请求转发到 Handler。由 Handler 对具体的用户请求进行处理

HandlAdapter:处理器适配器

  • 通过 HandlerAdapter 对处理器进行执行,这是适配器模式的应用,通过扩展适配器可以对更多类型的处理 器进行执行

View Resolver:视图解析器

  • View Resolver 负责将处理结果生成 View 视图,View Resolver 首先根据逻辑视图名解析成物理视图名 即具体的页面地址,再生成 View 视图对象,最后对 View 进行渲染将处理结果通过页面展示给用户

View:视图

  • SpringMVC 框架提供了很多的 View 视图类型的支持,包括:jstlView、freemarkerView、pdfView 等。我们最常用的视图就是 jsp。 一般情况下需要通过页面标签或页面模版技术将模型数据通过页面展示给用户,需要由程序员根据业务需求开 发具体的页面

说明

  1. SpringMVC 的各个组件中,处理器映射器、处理器适配器、视图解析器称为 SpringMVC 的三大组件。
  2. 使 <mvc:annotation-driven> 自动加载 RequestMappingHandlerMapping (处理映射器)
  3. RequestMappingHandlerAdapter(处理适配器),可 SpringMVC.xml 使
  4. <mvc:annotation-driven>替代注解处理器和适配器的配置。

3 注解

3.1 RequestMapping

作用:用于建立请求URL和处理请求方法之间的对应关系

源码:

  1. @Target({ElementType.METHOD, ElementType.TYPE}) // 说明该注解应该加在方法上或者是类上
  2. @Retention(RetentionPolicy.RUNTIME) // 写在.class文件中,jvm加载时依然在
  3. @Documented
  4. @Mapping

属性:

  1. public @interface RequestMapping {
  2. String name() default ""; // 路径名
  3. @AliasFor("path")
  4. String[] value() default {};
  5. @AliasFor("value")
  6. String[] path() default {};
  7. RequestMethod[] method() default {}; // 指定请求的方式:GET/POST/PUT/DELETE
  8. String[] params() default {}; // 用于指定限制请求参数的条件
  9. // 例如:
  10. // params = {"accountName"},表示请求参数必须有 accountName
  11. // params = {"moeny!100"},表示请求参数中 money 不能是 100
  12. String[] headers() default {}; // 用于指定限制请求消息头的条件
  13. String[] consumes() default {};
  14. String[] produces() default {};
  15. }

请求参数的绑定

**
支持的数据类型:

  • 基本类型参数:包括基本类型和 String 类型

    • 要求:我们的参数名称必须和控制器中方法的形参名称保持一致。(严格区分大小写)
  • POJO 类型参数:包括实体类,以及关联的实体类

    • 要求:表单中参数名称和 POJO 类的属性名称保持一致。并且控制器方法的参数类型是 POJO 类型。
  • 数组和集合类型参数:包括 List 结构和 Map 结构的集合(包括数组)

    • 第一种: 要求集合类型的请求参数必须在 POJO 中。在表单中请求参数名称要和 POJO 中集合属性名称相同。 给 List 集合中的元素赋值,使用下标。 给 Map 集合中的元素赋值,使用键值对。
    • 第二种: 接收的请求参数是 json 格式数据。需要借助一个注解实现。

3.2 RequestParam

作用: 把请求中指定名称的参数给控制器中的形参赋值 属性: value:请求参数中的名称。 required:请求参数中是否必须提供此参数。默认值:true。表示必须提供,如果不提供将报错

  1. @RequestMapping("/useRequestParam") // 改为GetMapping
  2. public String useRequestParam(@RequestParam("name")String username,
  3. @RequestParam(value="age",required=false)Integer age) {
  4. System.out.println(username+","+age);
  5. return "success";
  6. }

3.3 RequestBody

作用: 用于获取请求体内容。直接使用得到是 key=value&key=value…结构的数据 .get 请求方式不适用。 属性: required:是否必须有请求体。默认值是:true。当取值为 true 时,get 请求方式会报错。如果取值 为 false,get 请求得到是 null

  1. @RequestMapping("/useRequestBody")
  2. public String useRequestBody(@RequestBody(required=false) String body){
  3. System.out.println(body);
  4. return "success";
  5. }

3.4 PathVariable

作用: 用于绑定 url 中的占位符。例如:请求 url 中 /delete/{id},这个{id}就是 url 占位符。 url 支持占位符是 spring3.0 之后加入的。是 springmvc 支持 rest 风格 URL 的一个重要标志 属性: value:用于指定 url 中占位符名称。 required:是否必须提供占位符

  1. @RequestMapping("/usePathVariable/{id}")
  2. public String usePathVariable(@PathVariable("id") Integer id){
  3. System.out.println(id);
  4. return "success";
  5. }

3.5 CookieValue

作用:用于把指定的cookie名称的值传入控制器方法参数 属性:value:指定cookie的名称 required:是否必须有此cookie

  1. @RequestMapping("/useCookieValue")
  2. public String useCookieValue(@CookieValue(value="JSESSIONID",required=false) String cookieValue){
  3. System.out.println(cookieValue);
  4. return "success";
  5. }

3.6 RequestHeader(了解)

作用:用于获取请求消息头 属性:value:提供消息头名称, required:是否必须有此消息头

3.7 ModelAttribute

作用: 该注解是 SpringMVC4.3 版本以后新加入的。它可以用于修饰方法和参数。 出现在方法上,表示当前方法会在控制器的方法执行之前,先执行。它可以修饰没有返回值的方法,也可 以修饰有具体返回值的方法。 出现在参数上,获取指定的数据给参数赋值。

属性: value:用于获取数据的 key。key 可以是 POJO 的属性名称,也可以是 map 结构的 key。

应用场景: 当表单提交数据不是完整的实体类数据时,保证没有提交数据的字段使用数据库对象原来的数据。 例如: 我们在编辑一个用户时,用户有一个创建信息字段,该字段的值是不允许被修改的。在提交表单数据是肯定没有此字段的内容,一旦更新会把该字段内容置为 null,此时就可以使用此注解解决问题。

  1. @ModelAttribute
  2. public void showModel(User user) {
  3. System.out.println("执行了 showModel 方法"+user.getUsername());
  4. }
  5. /**
  6. * 接收请求的方法
  7. * @param user
  8. * @return
  9. */
  10. @RequestMapping("/testModelAttribute")
  11. public String testModelAttribute(User user) {
  12. System.out.println("执行了控制器的方法"+user.getUsername());
  13. return "success";
  14. }

image.png

基于 Map 的应用场景示例 1:ModelAttribute 修饰方法带返回值

  1. <!-- 修改用户信息 -->
  2. <form action="springmvc/updateUser" method="post">
  3. 用户名称:<input type="text" name="username" ><br/>
  4. 用户年龄:<input type="text" name="age" ><br/>
  5. <input type="submit" value="保存">
  6. </form>
  1. @ModelAttribute
  2. public User showModel(String username) {
  3. //模拟去数据库查询
  4. User abc = findUserByName(username);
  5. System.out.println("执行了 showModel 方法"+abc);
  6. return abc;
  7. }
  8. /**
  9. * 模拟修改用户方法
  10. * @param user
  11. * @return
  12. */
  13. @RequestMapping("/updateUser")
  14. public String testModelAttribute(User user) {
  15. System.out.println("控制器中处理请求的方法:修改用户:"+user);
  16. return "success";
  17. }
  18. /**
  19. * 模拟去数据库查询
  20. * @param username
  21. * @return
  22. */
  23. private User findUserByName(String username) {
  24. User user = new User();
  25. user.setUsername(username);
  26. user.setAge(19);
  27. user.setPassword("123456");
  28. return user;
  29. }

image.png

基于 Map 的应用场景示例 1:ModelAttribute 修饰方法不带返回值

  1. @ModelAttribute
  2. public void showModel(String username,Map<String,User> map) {
  3. //模拟去数据库查询
  4. User user = findUserByName(username);
  5. System.out.println("执行了 showModel 方法"+user);
  6. map.put("abc",user);
  7. }
  8. /**
  9. * 模拟修改用户方法
  10. * @param user
  11. * @return
  12. */
  13. @RequestMapping("/updateUser")
  14. public String testModelAttribute(@ModelAttribute("abc")User user) {
  15. System.out.println("控制器中处理请求的方法:修改用户:"+user);
  16. return "success";
  17. }
  18. /**
  19. * 模拟去数据库查询
  20. * @param username
  21. * @return
  22. */
  23. private User findUserByName(String username) {
  24. User user = new User();
  25. user.setUsername(username);
  26. user.setAge(19);
  27. user.setPassword("123456");
  28. return user;
  29. }

image.png

3.8 SessionAttribute

作用: 用于多次执行控制器方法间的参数共享。 属性: value:用于指定存入的属性名称 type:用于指定存入的数据类型

  1. jsp 中的代码:
  2. <!-- SessionAttribute 注解的使用 -->
  3. <a href="springmvc/testPut">存入 SessionAttribute</a>
  4. <hr/>
  5. <a href="springmvc/testGet">取出 SessionAttribute</a>
  6. <hr/>
  7. <a href="springmvc/testClean">清除 SessionAttribute</a>
  1. @Controller("sessionAttributeController")
  2. @RequestMapping("/springmvc")
  3. @SessionAttributes(value ={"username","password"},types={Integer.class})
  4. public class SessionAttributeController {
  5. /**
  6. * 把数据存入 SessionAttribute
  7. * @param model
  8. * @return
  9. * Model 是 spring 提供的一个接口,该接口有一个实现类 ExtendedModelMap
  10. * 该类继承了 ModelMap,而 ModelMap 就是 LinkedHashMap 子类
  11. */
  12. @RequestMapping("/testPut")
  13. public String testPut(Model model){
  14. model.addAttribute("username", "泰斯特");
  15. model.addAttribute("password","123456");
  16. model.addAttribute("age", 31);
  17. //跳转之前将数据保存到 username、password 和 age 中,因为注解@SessionAttribute 中有这几个参数
  18. return "success";
  19. }
  20. @RequestMapping("/testGet")
  21. public String testGet(ModelMap model){
  22. System.out.println(model.get("username")+";"+model.get("password")+";"+model.get("age"));
  23. return "success";
  24. }
  25. @RequestMapping("/testClean")
  26. public String complete(SessionStatus sessionStatus){
  27. sessionStatus.setComplete();
  28. return "success";
  29. }
  30. }

image.png

4 Restful风格

HTTP 协议里面,四个表示操作方式的动词:GET 、POST 、PUT、 DELETE,它们分别对应四种基本操作

  • GET用来获取资源
  • POST用来新建资源
  • PUT用来更新资源
  • DELETE用来删除资源

示例:

/account/1 HTTP GET 得到id=1的account
/account/1 HTTP DELETE 删除id=1的account
/account/1 HTTP PUT 更新id=1的account
/account/1 HTTP POST 新增account

5 响应数据和结果视图

5.1 ModelAndView

ModelAndView 是 SpringMVC 为我们提供的一个对象,该对象也可以用作控制器方法的返回值。

  1. @RequestMapping("/testReturnModelAndView")
  2. public ModelAndView testReturnModelAndView() {
  3. ModelAndView mv = new ModelAndView();
  4. mv.addObject("username", "张三");
  5. mv.setViewName("success");
  6. return mv;
  7. }
  1. 响应的 jsp 代码:
  2. <%@ page language="java" contentType="text/html; charset=UTF-8"
  3. pageEncoding="UTF-8"%>
  4. <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
  5. "http://www.w3.org/TR/html4/loose.dtd">
  6. <html>
  7. <head>
  8. <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  9. <title>执行成功</title>
  10. </head>
  11. <body>
  12. 执行成功!
  13. ${requestScope.username}
  14. </body>
  15. </html>

注意:
我们在页面上上获取使用的是 requestScope.username 取的,所以返回 ModelAndView 类型时,浏览器跳转只能是请求转发。

5.2 转发和重定向

forward转发: controller 方法在提供了 String 类型的返回值之后,默认就是请求转发

  1. @RequestMapping("/testForward")
  2. public String testForward() {
  3. System.out.println("AccountController 的 testForward 方法执行了。。。。");
  4. return "forward:/WEB-INF/pages/success.jsp";
  5. }

需要注意的是,如果用了 forward:则路径必须写成实际视图 url,不能写逻辑视图。
它相当于 request.getRequestDispatcher("url").forward(request,response) 。使用请求转发,既可以转发到 jsp,也可以转发到其他的控制器方法。

Redirect 重定向:controller方法提供了一个 String 类型返回值之后,它需要在返回值里使用:redirect:

  1. @RequestMapping("/testRedirect")
  2. public String testRedirect() {
  3. System.out.println("AccountController 的 testRedirect 方法执行了。。。。");
  4. return "redirect:testReturnModelAndView";
  5. }

它相当于 response.sendRedirect(url) 。需要注意的是,如果是重定向到 jsp 页面,则 jsp 页面不 能写在 WEB-INF 目录中,否则无法找到。

5.3 ResponseBody响应json数据

作用: 该注解用于将 Controller 方法返回的对象,通过 HttpMessageConverter 接口转换为指定格式的 数据如:json,xml 等,通过 Response 响应给客户端 需求: 使用@ResponseBody 注解实现将 controller 方法返回对象转换为 json 响应给客户端。 Springmvc 默认用 MappingJacksonHttpMessageConverter 对 json 数据进行转换,需要加入 jackson 的包。

  1. @Controller("jsonController")
  2. public class JsonController {
  3. /**
  4. * 测试响应 json 数据
  5. */
  6. @RequestMapping("/testResponseJson")
  7. public @ResponseBody Account testResponseJson(@RequestBody Account account) {
  8. System.out.println("异步请求:"+account);
  9. return account;
  10. }
  11. }

6 异常处理器和拦截器

异常处理器: HandlerExceptionResolver

  1. public class CustomExceptionResolver implements HandlerExceptionResolver {
  2. @Override
  3. public ModelAndView resolveException(HttpServletRequest request,
  4. HttpServletResponse response, Object handler, Exception ex) {
  5. ex.printStackTrace();
  6. CustomException customException = null;
  7. //如果抛出的是系统自定义异常则直接转换
  8. if(ex instanceof CustomException){
  9. customException = (CustomException)ex;
  10. }else{
  11. //如果抛出的不是系统自定义异常则重新构造一个系统错误异常。
  12. customException = new CustomException("系统错误,请与系统管理 员联系!");
  13. }
  14. ModelAndView modelAndView = new ModelAndView();
  15. modelAndView.addObject("message", customException.getMessage());
  16. modelAndView.setViewName("error");
  17. return modelAndView;
  18. }
  19. }

拦截器: Spring MVC 的处理器拦截器类似于 Servlet 开发中的过滤器 Filter,用于对处理器进行预处理后处理。 用户可以自己定义一些拦截器来实现特定的功能。 谈到拦截器,还要向大家提一个词——拦截器链(Interceptor Chain)。拦截器链就是将拦截器按一定的顺 序联结成一条链。在访问被拦截的方法或字段时,拦截器链中的拦截器就会按其之前定义的顺序被调用。 说到这里,可能大家脑海中有了一个疑问,这不是我们之前学的过滤器吗?是的它和过滤器是有几分相似,但 是也有区别,接下来我们就来说说他们的区别: 过滤器是 servlet 规范中的一部分,任何 java web 工程都可以使用。 拦截器是 SpringMVC 框架自己的,只有使用了 SpringMVC 框架的工程才能用。 过滤器在 url-pattern 中配置了/之后,可以对所有要访问的资源拦截。 拦截器它是只会拦截访问的控制器( controller )方法,如果访问的是 jsp,html,css,image 或者 js 是不会进行拦截的。 它也是 AOP 思想的具体应用。 我们要想自定义拦截器, 要求必须实现:*HandlerInterceptor 接口。

6.1 自定义拦截器

  • 编写一个普通类实现 HandlerInterceptor 接口

    1. public class HandlerInterceptorDemo1 implements HandlerInterceptor {
    2. @Override
    3. public boolean preHandle(HttpServletRequest request, HttpServletResponseresponse, Object handler)
    4. throws Exception {
    5. System.out.println("preHandle 拦截器拦截了");
    6. return true;
    7. }
    8. @Override
    9. public void postHandle(HttpServletRequest request, HttpServletResponse response,Object handler,
    10. ModelAndView modelAndView) throws Exception {
    11. System.out.println("postHandle 方法执行了");
    12. }
    13. @Override
    14. public void afterCompletion(HttpServletRequest request, HttpServletResponse
    15. response, Object handler, Exception ex)
    16. throws Exception {
    17. System.out.println("afterCompletion 方法执行了");
    18. }
    19. }
  • 配置拦截器 ```xml

  1. - 结果
  2. ![image.png](https://cdn.nlark.com/yuque/0/2020/png/2482342/1608729661159-df957fab-fa54-430c-8310-21b2c7aaf354.png#align=left&display=inline&height=219&margin=%5Bobject%20Object%5D&name=image.png&originHeight=438&originWidth=722&size=106735&status=done&style=none&width=361)
  3. <a name="xNtPy"></a>
  4. #### 6.2 拦截器的细节
  5. > **拦截器的放行:**
  6. > 放行的含义是指,如果有下一个拦截器就执行下一个,如果该拦截器处于拦截器链的最后一个,则执行控制器 中的方法。
  7. ![image.png](https://cdn.nlark.com/yuque/0/2020/png/2482342/1608790744162-23fe8db7-4b1d-40d3-a460-ec64c2e7d01d.png#align=left&display=inline&height=340&margin=%5Bobject%20Object%5D&name=image.png&originHeight=340&originWidth=615&size=100933&status=done&style=none&width=615)
  8. > 拦截器中的方法说明
  9. ```java
  10. public interface HandlerInterceptor {
  11. /**
  12. * 如何调用:
  13. * 按拦截器定义顺序调用
  14. * 何时调用:
  15. * 只要配置了都会调用
  16. * 有什么用:
  17. * 如果程序员决定该拦截器对请求进行拦截处理后还要调用其他的拦截器,或者是业务处理器去进行处理,
  18. * 则返回 true。
  19. * 如果程序员决定不需要再调用其他的组件去处理请求,则返回 false。
  20. */
  21. default boolean preHandle(HttpServletRequest request, HttpServletResponse
  22. response, Object handler)throws Exception {
  23. return true;
  24. }
  25. /**
  26. * 如何调用:
  27. * 按拦截器定义逆序调用(栈的结构)
  28. * 何时调用:
  29. * 在拦截器链内所有拦截器返成功调用
  30. * 有什么用:
  31. * 在业务处理器处理完请求后,但是 DispatcherServlet 向客户端返回响应前被调用,
  32. * 在该方法中对用户请求 request 进行处理。
  33. */
  34. default void postHandle(HttpServletRequest request, HttpServletResponse
  35. response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
  36. }
  37. /**
  38. * 如何调用:
  39. * 按拦截器定义逆序调用
  40. * 何时调用:
  41. * 只有 preHandle 返回 true 才调用
  42. * 有什么用:
  43. * 在 DispatcherServlet 完全处理完请求后被调用,
  44. * 可以在该方法中进行一些资源清理的操作。
  45. */
  46. default void afterCompletion(HttpServletRequest request, HttpServletResponse
  47. response, Object handler, @Nullable Exception ex) throws Exception {
  48. }
  49. }

拦截器的作用路径

  1. 作用路径可以通过在配置文件中配置。
  2. <!-- 配置拦截器的作用范围 -->
  3. <mvc:interceptors>
  4. <mvc:interceptor>
  5. <mvc:mapping path="/**" /><!-- 用于指定对拦截的 url -->
  6. <mvc:exclude-mapping path=""/><!-- 用于指定排除的 url-->
  7. <bean id="handlerInterceptorDemo1"
  8. class="com.itheima.web.interceptor.HandlerInterceptorDemo1"></bean>
  9. </mvc:interceptor>
  10. </mvc:interceptors>

image.png

配置文件

  1. <!-- 配置拦截器的作用范围 -->
  2. <mvc:interceptors>
  3. <mvc:interceptor>
  4. <!-- 用于指定对拦截的 url -->
  5. <mvc:mapping path="/**" />
  6. <bean id="handlerInterceptorDemo1" class="com.itheima.web.interceptor.HandlerInterceptorDemo1"></bean>
  7. </mvc:interceptor>
  8. <mvc:interceptor>
  9. <mvc:mapping path="/**" />
  10. <bean id="handlerInterceptorDemo2" class="com.itheima.web.interceptor.HandlerInterceptorDemo2"></bean>
  11. </mvc:interceptor>
  12. </mvc:interceptors>

6.3 拦截器的用户登录案例

业务需求: 1、有一个登录页面,需要写一个 controller 访问页面 2、登录页面有一提交表单的动作。需要在 controller 中处理。 2.1、判断用户名密码是否正确 2.2、如果正确 向 session 中写入用户信息 2.3、返回登录成功。 3、拦截用户请求,判断用户是否登录 3.1、如果用户已经登录。放行 3.2、如果用户未登录,跳转到登录页面

控制器代码:

  1. //登陆页面
  2. @RequestMapping("/login")
  3. public String login(Model model)throws Exception{
  4. return "login";
  5. }
  6. //登陆提交
  7. //userid:用户账号,pwd:密码
  8. @RequestMapping("/loginsubmit")
  9. public String loginsubmit(HttpSession session,String userid,String pwd)throws Exception{
  10. //向 session 记录用户身份信息
  11. session.setAttribute("activeUser", userid);
  12. return "redirect:/main.jsp";
  13. }
  14. //退出
  15. @RequestMapping("/logout")
  16. public String logout(HttpSession session)throws Exception{
  17. //session 过期
  18. session.invalidate();
  19. return "redirect:index.jsp";
  20. }

拦截器代码:

  1. public class LoginInterceptor implements HandlerInterceptor{
  2. @Override
  3. Public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
  4. //如果是登录页面则放行
  5. if(request.getRequestURI().indexOf("login.action")>=0)
  6. {
  7. return true;
  8. }
  9. HttpSession session = request.getSession();
  10. //如果用户已登录也放行
  11. if(session.getAttribute("user")!=null){
  12. return true;
  13. }
  14. //用户没有登录挑战到登录页面
  15. request.getRequestDispatcher("/WEB-INF/jsp/login.jsp").forward(request,
  16. response);
  17. return false;
  18. }
  19. }