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