• SpringMVC(AbstractController,拦截器,注解)


    1.Controller接口及其实现类
    Controller是控制器/处理器接口,只有一个方法handleRequest,用于进行请求的功能处理(功能处理方法),处理完请求后返回ModelAndView对象(Model模型数据部分 和 View视图部分)。

    如果想直接在处理器/控制器里使用response向客户端写回数据,可以通过返回null来告诉DispatcherServlet我们已经写出响应了,不需要它进行视图解析

    Spring默认提供了一些Controller接口的实现类以方便我们使用,在Eclipse中选择Controller接口然后右键open type Hierarchy即可查看该接口的实现类,每个实现类都有自己特殊的功能,这里以实现类AbstractController为例简单介绍下。
    查看AbstractController类中代码可知,我们写一个Controller的时候可以继承AbstractController然后实现handleRequestInternal方法即可。

    提供了【可选】的会话(session)的串行化访问功能,例如:
    即同一会话,线程同步

    public class HelloWorldController extends AbstractController{
      @Override
      protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response)throws Exception {
    
      String name = request.getParameter("name");
    
      //ModelAndView对象中包括了要返回的逻辑视图,以及数据模型
      ModelAndView mv = new ModelAndView();
      //设置视图名称,可以是字符串 也可以是视图对象
      mv.setViewName("hello");
      //设置数据模型
      mv.addObject("name", name);
    
      return mv;
      }
    }
    
    <bean name="/hello" class="com.briup.web.controller.HelloWorldController">
      <property name="synchronizeOnSession" value="true"></property>
    </bean>

    直接通过response写响应,例如:

    public class HelloWorldController extends AbstractController{
    @Override
    protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response)
    throws Exception {
    
    response.getWriter().write("Hello World!!");    
    //如果想直接在该处理器/控制器写响应 可以通过返回null告诉DispatcherServlet自己已经写出响应了,不需要它进行视图解析
    return null;
    }
    }

    强制请求方法类型,例如:
    只支持post和get方法

    <bean name="/hello" class="com.briup.web.controller.HelloWorldController">
    <property name="supportedMethods" value="POST,GET"></property>
    </bean>

    当前请求的session前置条件检查,如果当前请求无session将抛出HttpSessionRequiredException异常,例如:
    在进入该控制器时,一定要有session存在,否则抛出HttpSessionRequiredException异常。

    <bean name="/hello" class="com.briup.web.controller.HelloWorldController">
    <property name="requireSession" value="true"/>
    </bean>

    2.SpringMvc中的拦截器

    SpringMVC的处理器拦截器类似于Servlet 开发中的过滤器Filter,用于对处理器进行预处理和后处理。拦截器的作用有局限性只可以作用于处理器

    1)常见应用场景
    1、日志记录
    2、权限检查
    3、性能监控
    4、通用行为 例如读取用户cookie等

    2)拦截器接口

    public interface HandlerInterceptor {
      boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception;
    
      void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)throws Exception;
    
      void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)throws Exception;
    }

    preHandle方法
      预处理回调方法,实现处理器的预处理,第三个参数为的处理器(本次请求要访问的那个Controller)
      返回值:true表示继续流程(如调有下一个拦截器或处理器)
      false表示流程中断(如登录检查失败),不会继续调用其他的拦截器或处理器,此时我们需要通过response来产生响应

    postHandle方法
      后处理回调方法,实现处理器的后处理(但在渲染视图之前),此时我们可以通过modelAndView对模型数据进行处理或对视图进行处理,modelAndView也可能为null。

    afterCompletion方法
      整个请求处理完毕回调方法,即在视图渲染完毕时回调

    3)拦截器适配器
    有时候我们可能只需要实现三个回调方法中的某一个,如果实现HandlerInterceptor 接口的话,三个方法必须实现,不管你需不需要,此时spring 提供了一个HandlerInterceptorAdapter 适配器(适配器模式),允许我们只实现需要的回调方法。
    在HandlerInterceptorAdapter中,对HandlerInterceptor 接口中的三个方法都进行了空实现,其中preHandle方法的返回值,默认是true

    4)测试一个拦截器
    拦截器代码:

    public class MyInterceptor1 extends HandlerInterceptorAdapter{
      @Override
      public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception {
        System.out.println("MyInterceptor1 preHandle");
        return true;
      }
      @Override
      public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,ModelAndView modelAndView) throws Exception {
        System.out.println("MyInterceptor1 postHandle");
      }
      @Override
      public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)throws Exception {
        System.out.println("MyInterceptor1 afterCompletion");
      }
    }

    配置文件:(注意此配置在文件中的配置顺序,要写在配置文件的上面)

    <bean name="handlerInterceptor1" class="包名.MyInterceptor1"/>
    
    <bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping">
      <property name="interceptors">
        <list>
          <ref bean="handlerInterceptor1"/>
        </list>
      </property>
    </bean>

    访问一个测试的Controller查看结果:
    MyInterceptor1 preHandle
    TestController执行
    MyInterceptor1 postHandle
    MyInterceptor1 afterCompletion

    5)测试俩个拦截器
    俩个拦截器的代码和上面类似,只是每个输出的内容不同
    配置文件:

    <bean name="handlerInterceptor1" class="com.briup.web.interceptor.MyInterceptor1"/>
    <bean name="handlerInterceptor2" class="com.briup.web.interceptor.MyInterceptor2"/>
    
    <bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping">
    <property name="interceptors">
    <list>
    <ref bean="handlerInterceptor1"/>
    <ref bean="handlerInterceptor2"/>
    </list>
    </property>
    </bean>

    访问一个测试的Controller查看结果:
    MyInterceptor1 preHandle
    MyInterceptor2 preHandle
    TestController执行
    MyInterceptor2 postHandle
    MyInterceptor1 postHandle
    MyInterceptor2 afterCompletion
    MyInterceptor1 afterCompletion

    注意:<list>标签中引用拦截器的顺序会影响结果输出的顺序

    6)拦截器mvc标签进行配置
    注意:每个<mvc:interceptor>只能配置一个拦截器

    <mvc:interceptors>
      <mvc:interceptor>
        <mvc:mapping path="/**"/>
        <ref bean="handlerInterceptor1"/>
      </mvc:interceptor>
    </mvc:interceptors>

    例如1: 注意/*和/**的区别

    <bean name="myInter1" class="com.briup.web.interceptor.MyInterceptor1" />
    
    <bean class="com.briup.web.interceptor.MyInterceptor2" />
    
    <bean class="com.briup.web.interceptor.MyInterceptor3" />
    
    <!-- mvc提供的拦截器配置方式 -->
    <mvc:interceptors>
      <mvc:interceptor>
        <mvc:mapping path="/**"/>
        <ref bean="myInter1"/>
      </mvc:interceptor>
    
      <mvc:interceptor>
        <mvc:mapping path="/**"/>
        <mvc:exclude-mapping path="/test"/>
        <ref bean="myInter2"/>
      </mvc:interceptor>
    
      <mvc:interceptor>
        <mvc:mapping path="/**"/>
        <ref bean="timeInter"/>
      </mvc:interceptor>
    </mvc:interceptors>

    7)拦截器是单例
    因此不管多少用户请求多少次都只有一个拦截器实现,即线程不安全。
    所以在必要时可以在拦截器中使用ThreadLocal,它是和线程绑定,一个线程一个ThreadLocal,A 线程的ThreadLocal只能看到A线程的ThreadLocal,不能看到B线程的ThreadLocal。
    举个例子:
    记录执行Controller所用时间

    public class TimeInterceptor extends HandlerInterceptorAdapter{
      //拦截器是单例,不是线程安全的,所以这里使用ThreadLocal
      private ThreadLocal<Long> local = new ThreadLocal<>();
    
      @Override
      public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception {
        long start = System.currentTimeMillis();
        local.set(start);
        return true;
      }
      @Override
      public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)throws Exception {
        long end = System.currentTimeMillis();
        System.out.println("共耗时:"+(end-local.get()));
      }
    }

    8)登录检查

    public class LoginInterceptor extends HandlerInterceptorAdapter{
      @Override
      public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception {
      //请求到登录页面放行
        if(request.getServletPath().startsWith("/login")) {
          return true;
        }
    
      //如果用户已经登录放行
        if(request.getSession().getAttribute("username") != null) {
          return true;
        }
    
      //其他没有登录的情况则重定向到登录页面
      response.sendRedirect(request.getContextPath() + "/login");
    
      return false;
      }
    }

    注意:推荐能使用servlet规范中的过滤器Filter实现的功能就用Filter实现,因为HandlerInteceptor只有在SpringWebMVC环境下才能使用,因此Filter是最通用的、最先应该使用的。

    3.基于注解的SpringMVC

    1)用于支持注解的配置
    使用基于注解的配置可以省略很多操作,更方便。我们之前所看到的所有的xml配置,如果替换成基于注解只需要在spring的xml文件中做如下配置:
    <mvc:annotation-driven/>
    在Spring中
    处理器类可以使用 @Controller注解
    业务逻辑层可以使用 @Service注解
    数据持久层可以使用 @Repository注解

    如果在处理器上使用 @Controller注解,那么还需要在配置文件中指定哪个包下面的类使用了该注解:
    <context:component-scan base-package="com.briup.web.controller"></context:component-scan>

        <!-- 使容器可以识别mvc的注解 -->
        <mvc:annotation-driven></mvc:annotation-driven>
        <!-- 扫描指定包及其子包下所有的注解 -->
        <context:component-scan base-package="com.briup.annotation"/>

    2)基于注解的Controller
    使用注解后,就不需要再实现特定的接口,任意一个javaBean对象都可以当做处理器对象,对象中任意一个方法都可以作为处理器方法。
    只需
    在类上加上 @Controller注解
    方法上加上 @RequestMapping注解
    即可

    例如:
    web.xml中:

    <servlet>
      <servlet-name>SpringMVC</servlet-name>
      <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
      <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring-web-mvc.xml</param-value>
      </init-param>
      <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>SpringMVC</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

    src下面的spring-web-mvc.xml中:

    <mvc:annotation-driven/>
    <context:component-scan base-package="com.briup.web.controller"></context:component-scan>
    
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> 
      <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/> 
      <property name="prefix" value="/WEB-INF/jsp/"/> 
      <property name="suffix" value=".jsp"/> 
    </bean>

    自定义的Controller中:

    @Controller
    public class HomeController {
      @RequestMapping("/home")
      public ModelAndView home(){
        ModelAndView mv = new ModelAndView("index");
        return mv;
      }
    }

    如上代码,使用 @Controller表明HomeController类是一个处理器类,通过 @RequestMapping("/home")表明当url请求名为/home时,调用home方法执行处理,当处理完成之后返回ModelAndView对象。因为在spring-web-mvc.xml中配置了视图解析器的前缀和后缀,所以最后视图home.jsp被返回

    3)基于注解的Controller的返回值

    1.返回ModelAndView,和之前一样

    2.返回String,表示跳转的逻辑视图名字,模型可以通过参数传过来

    @Controller
    public class HomeController {
      @RequestMapping("/home")
      public String home(Model model){
        model.addAttribute("msg", "hello world");
        return "index";
      }
    }

    3.声明返回类型为void
    可以通过参数获取request和response,分别使用服务器内部跳转和重定向,自己来决定要跳转的位置。

    @Controller
    public class HomeController {
    @RequestMapping("/home")
      public void home(HttpServletRequest request,HttpServletResponse response){
        String username = request.getParameter("username");
        response.setContentType("text/html;charset=utf-8");
        response.getWriter().write("hello world! "+username);
        //或者使用servlet的方式进行跳转/重定向
      }
    }

    可以写一个类来测试所有方法:

    @Controller
    public class HelloController {
        
        //@RequestMapping("/test1")
        //@RequestMapping(value = "/test1")
        @RequestMapping(value= {"/test1","/annotest1"})
        public ModelAndView test1(HttpServletRequest req,
                HttpServletResponse reqs) throws Exception{
            
            ModelAndView mv = new ModelAndView("test");
            mv.addObject("name", "李四");
            return mv;
        }
        
        //如果方法返回值的类型为String,则表示返回的逻辑视图名
        @RequestMapping("/test2")
        public String test2() throws Exception{
            return "hello";
        }
        
        @RequestMapping("/test3")
        public String test3(Model model) throws Exception{
            model.addAttribute("name", "王五");
            return "hello";
        }
        
        @RequestMapping("/test4")
        public void test4(HttpServletResponse response) throws Exception{
            response.getWriter().write("hello world!");
            response.getWriter().flush();
        }
        
        /*
         * 如果方法没有返回值,并且不通过响应用流的方式给客户端写回数据。
         * 那么,SpringMVC会自动将@RequestMapping里面的参数,作为逻辑视图名
         */
        @RequestMapping("/test5")
        public void test5() throws Exception{
            System.out.println("--------------");
        }
        
        @RequestMapping("/test6")
        public void test6(HttpServletRequest request,
                HttpServletResponse response) throws Exception{
            String path = "/WEB-INF/jsp/hello.jsp";
            request.setAttribute("name", "赵四");
            request.getRequestDispatcher(path).forward(request, response);
        }
        
        @RequestMapping("/test7")
        public void test7(HttpServletRequest request,
                HttpServletResponse response) throws Exception{
            String path = "/WEB-INF/jsp/hello.jsp";
            response.sendRedirect(request.getContextPath()+"/test6");
        }
    }

    4)Spring2.5中引入注解对控制器/处理器(controller/handler)支持
    @Controller
    用于标识是控制器/处理器类;
    @RequestMapping
    请求到处理器功能方法的映射规则;
    @RequestParam
    请求参数到处理器功能处理方法的方法参数上的绑定;
    @ModelAttribute
    请求参数到命令对象的绑定;
    @SessionAttributes
    用于声明session 级别存储的属性,放置在处理器类上,通常列出模型属性(如@ModelAttribute)对应的名称,则这些属性会透明的保存到session 中
    @InitBinder
    自定义数据绑定注册支持,用于将请求参数转换到命令对象属性的对应类型;


    5).Spring3引入了更多的注解,其中包含了对RESTful架构风格的支持
    @CookieValue
    cookie数据到处理器功能处理方法的方法参数上的绑定;
    @RequestHeader
    请求头数据到处理器功能处理方法的方法参数上的绑定;
    @RequestBody
    请求的body体的绑定
    @ResponseBody
    处理器功能处理方法的返回值作为响应体
    @ResponseStatus
    定义处理器功能处理方法/异常处理器返回的状态码和原因;
    @ExceptionHandler
    注解式声明异常处理器;
    @PathVariable
    请求URI 中的模板变量部分到处理器功能处理方法的方法参数上的绑定,从而支持RESTful架构风格的URI;

    6).Spring3中引入的mvc命名空间
    mvc这个命名空间是在Spring3中引入的,其作用是用来支持mvc的配置
    需要在<beans>中声明出这个命名空间及其对应的schemaLocation中的值
    <mvc:annotation-driven>
    自动注册基于注解风格的映射器和适配器:(也就是说这个mvc标签是基于注解的)
    在spring2.5中是DefaultAnnotationHandlerMapping和AnnotationMethodHandlerAdapter

    在spring3中是RequestMappingHandlerMapping和RequestMappingHandlerAdapter.
    同时还支持各种数据的转换器.

    配置自定义的处理器拦截器,例如:

    <mvc:interceptors>
        <mvc:interceptor>
          <mvc:mapping path="/**"/>
            <ref bean="handlerInterceptor1"/>
        </mvc:interceptor>
    </mvc:interceptors>

    收到相应请求后直接选择相应的视图,例如:
    <mvc:view-controller path="/hello" view-name="test"></mvc:view-controller>

    逻辑静态资源路径到物理静态资源路径的对应.例如:解决了静态资源拦截的问题

    <mvc:resources mapping="/images/**" location="/images/"/> 
    <mvc:resources mapping="/js/**" location="/js/"/> 
    <mvc:resources mapping="/css/**" location="/css/"/> 
    
    <mvc:default-servlet-handler>

    当在web.xml中DispatcherServlet使用<url-pattern>/</url-pattern> 映射的时候,会静态资源也映射了,如果配置了这个mvc标签,那么再访问静态资源的时候就转交给默认的Servlet来响应静态文件,否则报404 找不到静态资源错误。

    7).@Controller和@RequestMapping注解

    1声明处理器

    @Controller
    public class HelloWorldController {
    
    }

    2映射处理器中的【功能处理方法】

    @Controller
    public class HelloWorldController {
      @RequestMapping("/home")
      public ModelAndView home(){
        ModelAndView mv = new ModelAndView("index");
        return mv;
      }
    }

    表明该方法映射的url路径为/home

    3@RequestMapping也可以写在处理器类上

    // /test/home
    @RequestMapping("/test")
    @Controller
    public class HomeController {
      @RequestMapping("/home")
      public ModelAndView home(){
        ModelAndView mv = new ModelAndView("index");
        return mv;
      }
    }

    表明该方法映射的url路径为/test/home

    8)请求映射@RequestMapping
    假设浏览器发送了一个请求如下:
    -------------------------------
    POST /login HTTP1.1
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
    Accept-Encoding: gzip, deflate
    Accept-Language: zh-CN,en;q=0.8,zh;q=0.5,en-US;q=0.3
    Connection: keep-alive
    Cookie: JSESSIONID=DBC6367DEB1C024A836F3EA35FCFD5A2
    Host: 127.0.0.1:8989
    Upgrade-Insecure-Requests: 1
    User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:49.0) Gecko/20100101 Firefox/49.0

    username=tom&password=123
    --------------------------------


    http协议的请求格式如下:
    ---------------------------------
    请求方法 URL 协议版本号
    请求头信息
    请求头信息
    请求头信息
    ..
    回车换行
    请求正文
    ---------------------------------


    从格式中我们可以看到【请求方法、URL、请求头信息、请求正文】这四部分一般是可变的,因此我们可以把请求中的这些信息在处理器的【功能处理方法】中进行的映射,因此请求的映射分为如下几种:
    URL路径映射
    使用URL映射到处理器的功能处理方法;
    请求方法映射限定
    例如限定功能处理方法只处理GET请求;
    请求参数映射限定
    例如限定只处理包含username参数的请求;
    请求头映射限定
    例如限定只处理"Accept=application/json"的请求。

  • 相关阅读:
    再学 GDI+[11]: DrawCurve 绘制曲线
    再学 GDI+[7]: DrawLines 绘制一组直线
    再学 GDI+[9]: DrawPolygon 绘制多边形
    再学 GDI+[10]: DrawClosedCurve 绘制闭合曲线
    再学 GDI+[13]: DrawBezier 绘制贝塞尔线
    再学 GDI+[8]: DrawRectangles 绘制一组矩形
    再学 GDI+[5]: DrawArc 绘制弧线
    再学 GDI+[12]: 准备工作 矩形命中
    再学 GDI+[6]: DrawPie 绘制饼形
    判断字符串中子串个数的函数
  • 原文地址:https://www.cnblogs.com/Magic-Li/p/11775965.html
Copyright © 2020-2023  润新知