回顾SpringMVC-03-请求转发、拦截器

311 阅读5分钟

回顾SpringMVC-03

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

上篇我们回顾了SpringMVC的注解式开发和Controller、RequestMapping和数据处理等,这篇我们看看SpringMVC的结果跳转、拦截器等

结果转发

处理器对请求的处理完毕后一般要向其他资源进行跳转,而跳转方式有两种:请求转发和重定向。

SpringMVC框架里把我们原来在Servlet的请求和重定向的操作进行了封装,可以直接简便的实现转发和重定向。

回忆一下Servlet里的转发和重定向:

//转发
request.getRequestDispatcher("xx.jsp").forward()
//重定向
response.sendRedirect("xxx.jsp")

在SpringMVC里只需要forward和redirect即可转发和重定向。

具体语法如下

ModelAndView

设置ModelAndView对象 , 根据view的名称 , 和视图解析器跳到指定的页面 .

页面 : {视图解析器前缀} + viewName +{视图解析器后缀}

<!-- 视图解析器 -->
<beanclass="org.springframework.web.servlet.view.InternalResourceViewResolver"
     id="internalResourceViewResolver">
   <!-- 前缀 -->
   <property name="prefix" value="/WEB-INF/jsp/" />
   <!-- 后缀 -->
   <property name="suffix" value=".jsp" />
</bean>

对应的controller类

public class ControllerTest1 implements Controller {

   public ModelAndView handleRequest(HttpServletRequesthttpServletRequest, HttpServletResponse httpServletResponse) throwsException {
       //返回一个模型视图对象
       ModelAndView mv = new ModelAndView();
       mv.addObject("msg","ControllerTest1");
       mv.setViewName("test");
       return mv;
  }
}

如果不使用视图解析器,则在视图路径前面加入forward:视图完整路径

@RequestMapping("/result")
public ModelAndView forwardDemo(String name,Integer age){
    ModelAndView mv = new ModelAndView();
    mv.addObject("myname",name);
    mv.addObject("myage",age);
    mv.setViewName("forward:/WEB-INF/view/forward.jsp");
    return mv;
}

ServletAPI

通过设置ServletAPI , 不需要视图解析器 .

1、通过HttpServletResponse进行输出

2、通过HttpServletResponse实现重定向

3、通过HttpServletResponse实现转发

@Controller
public class ResultGo {

   @RequestMapping("/result/t1")
   public void test1(HttpServletRequest req, HttpServletResponse rsp)throws IOException {
       rsp.getWriter().println("Hello,Spring BY servlet API");
  }

   @RequestMapping("/result/t2")
   public void test2(HttpServletRequest req, HttpServletResponse rsp)throws IOException {
       rsp.sendRedirect("/index.jsp");
  }

   @RequestMapping("/result/t3")
   public void test3(HttpServletRequest req, HttpServletResponse rsp)throws Exception {
       //转发
       req.setAttribute("msg","/result/t3");
       req.getRequestDispatcher("/WEB-INF/jsp/test.jsp").forward(req,rsp);
  }

}

SpringMVC

通过SpringMVC来实现转发和重定向 - 无需视图解析器;

测试前,需要将视图解析器注释掉

@Controller
public class ResultSpringMVC {
   @RequestMapping("/rsm/t1")
   public String test1(){
       //转发
       return "/index.jsp";
  }

   @RequestMapping("/rsm/t2")
   public String test2(){
       //转发二
       return "forward:/index.jsp";
  }

   @RequestMapping("/rsm/t3")
   public String test3(){
       //重定向
       return "redirect:/index.jsp";
  }
}

通过SpringMVC来实现转发和重定向 - 有视图解析器;

重定向 , 不需要视图解析器 , 本质就是重新请求一个新地方嘛 , 所以注意路径问题.

可以重定向到另外一个请求实现 .

@Controller
public class ResultSpringMVC2 {
   @RequestMapping("/rsm2/t1")
   public String test1(){
       //转发
       return "test";
  }

   @RequestMapping("/rsm2/t2")
   public String test2(){
       //重定向
       return "redirect:/index.jsp";
       //return "redirect:hello.do"; //hello.do为另一个请求/
  }

}

拦截器

SpringMVC中的Interceptor拦截器的主要作用是拦截指定的用户请求,我们也常用来做密码验证、权限限制等,在框架里进行相应的预处理与后处理。这里的预处理是处理器映射器根据用户提交的请求映射出了所有执行的处理器类,并且也找到了要执行该处理器类的处理器适配器,在处理器适配器执行之前。当然,在处理器映射器映射出所有执行的处理器类时,已经将拦截器与处理器组合为了一个处理器执行链,并返回给了中央调度器。

SpringMVC的处理器拦截器就像我们以前Servlet开发中的过滤器Filter。

过滤器和拦截器的区别:拦截器是AOP思想的具体应用。

过滤器拦截器
servlet规范中的一部分,任何javaweb工程都可以使用拦截器是SpringMVC框架自带的,只有使用SpringMVC框架的工程才能使用
在url-pattern中配置了/*之后,可以对所有要访问的资源进行拦截拦截器只会拦截访问的控制器方法,如果访问的是jsp/html/css/image/js是不会进行拦截的

接触过Struts2框架的就知道在Struts2中也有拦截器,那和SpringMVC中的拦截器有什么区别呢

SpringMVC拦截器&Struts2拦截器 区别

1、从拦截级别上来说,SpringMVC是方法级别的拦截,Struts2是类级别的拦截

2、数据独立性:SpringMVC方法间独立,独享request和response,而Struts2虽然方法也是独立,但所有的action变量是共享的

3、拦截机制:SpringMVC用的是独立的AOP方式,Struts2有自己的interceptor机制,所以Struts2的配置文件量要大于SpringMVC

拦截器的执行

实现HandlerInterceptor接口可以去源码看看

image-20201003095406583

perHandle

default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
      throws Exception {

   return true;
}

发现源码里preHandler的返回值是boolean,这个方法在处理器执行之前执行,如果返回true,则紧接着会执行处理器方法,并且把afterCompletion()方法放入到一个专门的方法栈中等待执行。

Demo

自定义一个拦截器

package com.feng.config;

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class MyInterceptor implements HandlerInterceptor {

    //return true就会放行,执行下一个拦截器
    //return false   不执行下一个拦截器
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("============处理前============");
        return true;
    }


    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("============处理后============");
    }


    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("============清理============");
    }
}

在springmvc的配置文件中配置拦截器

<!--关于拦截器的配置-->
<mvc:interceptors>
   <mvc:interceptor>
       <!--/** 包括路径及其子路径-->
       <!--/admin/* 拦截的是/admin/add等等这种 , /admin/add/user不会被拦截-->
       <!--/admin/** 拦截的是/admin/下的所有-->
       <mvc:mapping path="/**"/>
       <!--bean配置的就是拦截器-->
       <bean class="com.kuang.interceptor.MyInterceptor"/>
   </mvc:interceptor>
</mvc:interceptors>

编写一个Controller,接收请求

package com.kuang.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

//测试拦截器的控制器
@Controller
public class InterceptorController {

   @RequestMapping("/interceptor")
   @ResponseBody
   public String testFunction() {
       System.out.println("控制器中的方法执行了");
       return "hello";
  }
}

前端 index.jsp

<a href="${pageContext.request.contextPath}/interceptor">拦截器测试</a>

测试即可

拦截器中的方法与处理器方法的执行顺序如下:

image-20201003102013021

或者说是

image-20201003102311851

附(用户认证代码,通过拦截器实现)

如何实现?

1、需要一个登录页面,一个controller访问页面

2、登录页面有一提交表单的动作,需要在controller中处理,判断用户名密码是否正确。如果正确。则向session中存入用户信息,返回登陆成功

3、拦截用户请求,判断用户是否登录。如果用户已经登录则放行,反之拦截,强行跳转到登录页面。

具体实现

1、登录页面 login.jsp

<%--
  Created by IntelliJ IDEA.
  User: 16979
  Date: 2020/4/19
  Time: 22:48
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>


<%--在web-inf下的所有页面或者资源,只能通过controller,或者servlet进行访问--%>

<h1>登录页面</h1>


<form action="${pageContext.request.contextPath}/user/login" method="post">
    用户名: <input type="text" name="username">
    密码: <input type="text" name="password">
    <input type="submit" value="提交">
</form>

</body>
</html>

2、controller处理请求

package com.feng.controller;


import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.http.HttpSession;

@Controller
@RequestMapping("/user")
public class LoginController {

    @RequestMapping("/main")
    public String main(){
        return "main";
    }

    @RequestMapping("/goLogin")
    public String login(){
        return "login";
    }

    @RequestMapping("/login")
    public String login(HttpSession session, String username, String password, Model model){
        System.out.println("login---->"+username);
        //把用户的信息存在session中
        session.setAttribute("userLoginInfo",username);
        model.addAttribute("username",username);
        return "main";
    }

    @RequestMapping("/goOut")
    public String goOut(HttpSession session){
        session.removeAttribute("userLoginInfo");
        return "main";
    }

}

3、写一个登陆成功的页面main.jsp

<%--
  Created by IntelliJ IDEA.
  User: 16979
  Date: 2020/4/19
  Time: 22:48
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>

<h1>首页</h1>


<span>${username}</span>
<p>
    <a href="${pageContext.request.contextPath}/user/goOut">注销</a>
</p>

</body>
</html>

4、在index上测试跳转,启动tomcat后,发现未登录也能进入main页面

5、编写拦截器

package com.feng.config;

import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

public class LoginInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        HttpSession session = request.getSession();

        //放行判断:判断什么情况下登录

        //登录页面也会放行
        if(request.getRequestURI().contains("goLogin")){
            return true;
        }
        //说明我在提交登录
        if(request.getRequestURI().contains("login")){
            return true;
        }
        //第一次登录  没有session
        if(session.getAttribute("userLoginInfo")!=null){
            return true;
        }

        //判断什么情况下没有登录

        request.getRequestDispatcher("/WEB-INF/jsp/login.jsp").forward(request, response);
        return false;
    }
}

6、SpringMVC配置文件中注册拦截器!

 <!--拦截器配置-->
    <mvc:interceptors>
        <mvc:interceptor>
            <!--包括这个请求下面的所有请求 -->
            <mvc:mapping path="/**"/>
            <bean class="com.feng.config.MyInterceptor"/>
        </mvc:interceptor>
        <mvc:interceptor>
            <!--包括这个请求下面的所有请求 -->
            <mvc:mapping path="/user/**"/>
            <bean class="com.feng.config.LoginInterceptor"/>
        </mvc:interceptor>
    </mvc:interceptors>

7、重启tom猫再次测试即可