SpringMVC(AbstractController,攔截器,註解)

1.Controller接口及其實現類
Controller是控制器/處理器接口,只有一個方法handleRequest,用於進行請求的功能處理(功能處理方法),處理完請求後返回ModelAndView對象(Model模型數據部分 和 View視圖部分)。

若是想直接在處理器/控制器裏使用response向客戶端寫回數據,能夠經過返回null來告訴DispatcherServlet咱們已經寫出響應了,不須要它進行視圖解析css

Spring默認提供了一些Controller接口的實現類以方便咱們使用,在Eclipse中選擇Controller接口而後右鍵open type Hierarchy便可查看該接口的實現類,每一個實現類都有本身特殊的功能,這裏以實現類AbstractController爲例簡單介紹下。
查看AbstractController類中代碼可知,咱們寫一個Controller的時候能夠繼承AbstractController而後實現handleRequestInternal方法便可。html

提供了【可選】的會話(session)的串行化訪問功能,例如:
即同一會話,線程同步java

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寫響應,例如:web

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方法spring

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

 

當前請求的session前置條件檢查,若是當前請求無session將拋出HttpSessionRequiredException異常,例如:
在進入該控制器時,必定要有session存在,不然拋出HttpSessionRequiredException異常。json

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

 

2.SpringMvc中的攔截器瀏覽器

SpringMVC的處理器攔截器相似於Servlet 開發中的過濾器Filter,用於對處理器進行預處理和後處理。攔截器的做用有侷限性只能夠做用於處理器安全

1)常見應用場景
一、日誌記錄
二、權限檢查
三、性能監控
四、通用行爲 例如讀取用戶cookie等服務器

2)攔截器接口cookie

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"的請求。

相關文章
相關標籤/搜索