轉(zhuǎn)載請注明來源 賴賴的博客
導(dǎo)語
核心思想明了悄泥,骨架已經(jīng)建立虏冻,就可以一點(diǎn)一點(diǎn)的豐滿細(xì)節(jié),反哺骨架和核心思想
前面介紹了Spring MVC 是怎么接受一個(gè)訪問的弹囚,想必你已經(jīng)明白了基本的訪問過程厨相,作為一個(gè)應(yīng)用系統(tǒng),登錄模塊都必不可少鸥鹉,不妨今天就來做一個(gè)簡單的登錄模塊蛮穿,順便熟悉一下Spring MVC的訪問入?yún)@取和攔截器的使用
實(shí)例
項(xiàng)目工程目錄結(jié)構(gòu)和代碼獲取地址
獲取地址(版本Log將會(huì)注明每一個(gè)版本對(duì)應(yīng)的課程)
https://github.com/laiyijie/SpringLearning
目錄結(jié)構(gòu)
如圖所示,雖然目錄結(jié)構(gòu)看起來復(fù)雜了一些(就是文件夾深度深了一點(diǎn)兒而已)毁渗,其實(shí)最源文件只有五個(gè)践磅。所以不要緊張,這非常簡單祝蝠,而且相比上一課音诈,只多出了一個(gè)文件,和少量的配置
運(yùn)行工程
運(yùn)行方式
- 右鍵整個(gè)項(xiàng)目
- Run as
- Run On Server
- 在瀏覽器里依次輸入 (demo是工程的名稱绎狭,依照你的來替換)
- http://localhost:8080/demo/user/hi
- http://127.0.0.1:8080/demo/user/login?username=laiyijie&password=lailai
- http://127.0.0.1:8080/demo/user/hi
運(yùn)行結(jié)果
- http://localhost:8080/demo/user/hi
- http://127.0.0.1:8080/demo/user/login?username=laiyijie&password=lailai
- http://127.0.0.1:8080/demo/user/hi
第一次和最后一次的輸入是完全一樣的確實(shí)完全不同的兩個(gè)結(jié)果细溅?
由于第一次沒有登錄,然后返回的是403權(quán)限不足儡嘶,第二次是登錄以后訪問這個(gè)鏈接喇聊,可以正常返回。
項(xiàng)目詳解
依照上一章的知識(shí)蹦狂,訪問會(huì)被引流到由@RequestMapping
注解的函數(shù)執(zhí)行誓篱,我們不妨直接找到UserController
這個(gè)文件,訪問是由這個(gè)控制器進(jìn)行處理的
UserController.java(與上一課有變化)
package me.laiyijie.demo.controller;
import org.springframework.stereotype.Controller;
import javax.servlet.http.HttpSession;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@RequestMapping("/user")
public class UserController {
@RequestMapping("/hi")
@ResponseBody
public String hello(@RequestParam(value = "name", defaultValue = "world") String name) {
return "hello " + name;
}
@RequestMapping("/login")
@ResponseBody
public String login(@RequestParam("username") String username, @RequestParam("password") String password,
HttpSession httpsession) {
if ("laiyijie".equals(username) && "lailai".equals(password)) {
httpsession.setAttribute("username", username);
return "login success";
}
return "loging failed";
}
}
代碼有幾處變化:
- 在類前面多了一個(gè)
@RequestMapping
注解凯楔,根據(jù)前面的執(zhí)行結(jié)果可以知道窜骄,如果@RequestMapping
注解應(yīng)用到類上,那么這個(gè)類中的所有方法的URL都增加這個(gè)注解的前綴摆屯,在這個(gè)例子中邻遏,都增加了/user
,所以原來的/hi
變成了/user/hi
- 在方法參數(shù)中增加了由
@RequestParam
標(biāo)注的入?yún)⑴捌铮搮?shù)就是映射入?yún)?- 在第二次訪問的時(shí)候我們使用的URL為
127.0.0.1/demo/user/login?username=laiyijie&password=lailai
而其對(duì)應(yīng)的函數(shù)聲明有如下兩個(gè)參數(shù)@RequestParam("username") String username, @RequestParam("password") String password
- 顯而易見的准验,
@RequestParam
獲取了問號(hào)后面的鍵值對(duì)參數(shù) - 在
hi
這個(gè)函數(shù)中我們這樣使用@RequestParam(value = "name", defaultValue = "world") String name
設(shè)置了一個(gè)默認(rèn)值,這樣的話即使我們不傳這個(gè)參數(shù)也會(huì)返回hello world
(例如第三次訪問)
- 在第二次訪問的時(shí)候我們使用的URL為
-
login
函數(shù)中新增了一個(gè)HttpSession
的入?yún)?- 首先廷没,這個(gè)入?yún)暮味鴣砗ィ窟@是SpringMVC在使用
DispatcherServlet
分配URL對(duì)應(yīng)控制器的時(shí)候會(huì)根據(jù)函數(shù)聲明自動(dòng)注入的一個(gè)參數(shù),相應(yīng)的還可以參考官方文檔 - 作用很簡單就是獲取當(dāng)前的
HttpSession
從而可以操作Session
中的屬性(Session在這個(gè)例子中是為了保存登錄狀態(tài))
- 首先廷没,這個(gè)入?yún)暮味鴣砗ィ窟@是SpringMVC在使用
-
login
函數(shù)中颠黎,在用戶名和密碼正確以后除了會(huì)返回login success
以外還會(huì)設(shè)置一個(gè)名為username
的Session
屬性用來證明登錄成功
那么問題來了另锋,在第一次訪問/uesr/hi
的時(shí)候是怎么會(huì)返回403
錯(cuò)誤的呢滞项?
這就要引入Spring MVC的一個(gè)重要組件,攔截器(Interceptor)
LoginInterceptor.java(新增)
package me.laiyijie.demo.controller;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
String username = (String) request.getSession().getAttribute("username");
if (username != null) {
return true;
}else{
response.sendError(403);
return false;
}
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
}
}
不用看這么多代碼砰蠢,其實(shí)就兩點(diǎn):
- 實(shí)現(xiàn)了
HandlerInterceptor
接口 - 寫了
preHandle
方法(此方法用于在分配給Controller
之前執(zhí)行蓖扑,當(dāng)同意繼續(xù)訪問的時(shí)候返回true
否則返回false
)- 首先獲取了
Session
的username
屬性- String username = (String) request.getSession().getAttribute("username");
- 如果這個(gè)屬性設(shè)置了(代表處于登錄狀態(tài))返回
true
繼續(xù)執(zhí)行- return true;
- 如果這個(gè)屬性沒有設(shè)置(代表沒有登錄)設(shè)置返回
403
錯(cuò)誤并且返回false
- response.sendError(403);
- return false;
- 首先獲取了
是不是很簡單?看了這個(gè)接口其實(shí)就可以明白攔截器處于的位置:
-
/demo/user/hi
訪問被/demo
接受 - 如果定義了攔截器則進(jìn)入攔截器處理(
preHandel
方法) - 返回true則繼續(xù)分發(fā)給
/user/hi
對(duì)應(yīng)的方法hi
否則返回錯(cuò)誤
那么又有一個(gè)新問題來了台舱,/user/login
為什么沒有被攔截律杠?為什么沒有返回403
錯(cuò)誤?
那就要看Interceptor
的配置了:
servlet-context.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">
<context:component-scan base-package="me.laiyijie.demo" />
<mvc:annotation-driven />
<bean
class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"
p:messageConverters-ref="stringHttpMessageConverter" />
<bean id="stringHttpMessageConverter"
class="org.springframework.http.converter.StringHttpMessageConverter" />
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**" />
<mvc:exclude-mapping path="/user/login" />
<bean class="me.laiyijie.demo.controller.LoginInterceptor"></bean>
</mvc:interceptor>
</mvc:interceptors>
</beans>
攔截器配置如下(相比上節(jié)課新增):
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**" />
<mvc:exclude-mapping path="/user/login" />
<bean class="me.laiyijie.demo.controller.LoginInterceptor"></bean>
</mvc:interceptor>
</mvc:interceptors>
- 首先配置了一個(gè)
interceptors
也就是攔截器組(對(duì)竞惋,里面可以有很多個(gè)攔截器) - 然后配置了里面的其中一個(gè)
interceptor
- 攔截所有進(jìn)入
/demo
的訪問- <mvc:mapping path="/**" />
- 為何有兩個(gè)*柜去?因?yàn)橐粋€(gè)*不可以代表
/xx/xx
只能代表/xx
- 排除不需要攔截的路徑(原來在這里排除了
/user/login
)- <mvc:exclude-mapping path="/user/login" />
- 設(shè)置對(duì)應(yīng)的實(shí)現(xiàn)類
- 攔截所有進(jìn)入
簡單的兩個(gè)功能實(shí)現(xiàn)了一個(gè)簡單的登錄模塊。
小結(jié)
-
@RequstMapping
可以在類前注釋拆宛,代表這個(gè)類中所有處理函數(shù)都要帶其標(biāo)注的一個(gè)前綴 -
@RequestParam
在Controller
處理函數(shù)的入?yún)⑶白⑨屔ど荩梢詫⒗?code>?username=laiyijie&password=lailai這樣的參數(shù)綁定到對(duì)應(yīng)的入?yún)⒅?/li> -
@RequestParam
可以設(shè)置默認(rèn)值,這樣即使訪問不帶這個(gè)參數(shù)也可以有一個(gè)默認(rèn)的輸入浑厚,使用方式是@RequestParam(defaultValue="xxx")
-
Controller
中的處理函數(shù)可以增加HttpSession
的入?yún)⒐傻ⅲ?code>DispatcherServlet自動(dòng)注入,還有更多的屬性可以參考官方文檔 -
攔截器
通過繼承HandlerInterceptor
接口钳幅,并使用<mvc:interceptor>
配置實(shí)現(xiàn) - 攔截器有三個(gè)鉤子物蝙,其中一個(gè)是在訪問進(jìn)入
Controller
之前處理(preHandle
),處理結(jié)果為false可以阻止其進(jìn)入Controller
中處理 - 攔截器可以配置其攔截的路徑(
<mvc:mapping path="/**" />
) - 攔截器可以排除一些特殊的路徑不進(jìn)行攔截(
<mvc:exclude-mapping path="/user/login" />
)
附:
pom.xml(新增一個(gè)依賴)
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>me.laiyijie</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion>
</properties>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.3.2.RELEASE</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
新增一個(gè)依賴javax.servlet-api
作用于為provided
敢艰,由容器提供
web.xml(無變化)
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1" xmlns="http://xmlns.jcp.org/xml/ns/javaee">
<servlet>
<servlet-name>appServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>appServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>