Web基礎之Spring MVC

Spring MVC

Spring MVC 說是框架,對Tomcat來講其實就是一個Servlet,關於如何從上古時期的Servlet演化到現在的SpringMVC的,能夠看看這篇博文:Spring MVC是如何逐步簡化Servlet的編程的html

業務分層以後既方便解耦,條例也更清晰。所以對於後臺web層中又分了MVC三層:前端

  • 模型(Model):負責封裝應用的狀態,並實現應用的功能。一般分爲數據模型和業務邏輯模型,數據模型用來存放業務數據,好比訂單信息、用戶信息等;而業務邏輯模型包含應用的業務操做,好比訂單的添加或者修改等。一般由java開發人員編寫程序完成,代碼量最多
  • 視圖(View):視圖經過控制器從模型得到要展現的數據,而後用本身的方式展示給用戶,至關於提供界面來與用戶進行人機交互。一般由前端和java開發人員完成,代碼量較多。
  • 控制器(Controller):用來控制應用程序的流程和處理用戶所發出的請求。當控制器接收到用戶的請求後,會將用戶的數據和模型的更新相映射,也就是調用模型來實現用戶請求的功能;而後控制器會選擇用於響應的視圖,把模型更新後的數據展現給用戶。起到總調度的做用,Controller一般由框架實現,使用時基本不須要編寫代碼

然而如今前端都在使用MVVM模型 😑java


環境鋪墊:
web


依賴及配置文件

建立maven工程並設置打包方式爲warajax

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
</properties>

<packaging>war</packaging>

而後是添加依賴:spring

<dependencies>
    <!--springmmvc-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>5.1.9.RELEASE</version>
    </dependency>
    <!--日誌-->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-log4j12</artifactId>
        <version>1.7.28</version>
        <scope>test</scope>
    </dependency>
    <!--jsp相關-->
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>4.0.1</version>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>jstl</groupId>
        <artifactId>jstl</artifactId>
        <version>1.2</version>
    </dependency>
    <dependency>
        <groupId>javax.servlet.jsp</groupId>
        <artifactId>jsp-api</artifactId>
        <scope>provided</scope>
        <version>2.2</version>
    </dependency>
</dependencies>

還用個吊毛的JSP啊🙃apache

日誌配置文件log4j.properties:編程

### direct log messages to stdout ###
### 輸出源的配置 語法  log4j.appender.輸出源的名字=輸出源的實現類 ###
### log4j.appender.輸出源的名字.屬性=屬性值   ###
log4j.appender.a=org.apache.log4j.ConsoleAppender
log4j.appender.a.Target=System.out
log4j.appender.a.layout=org.apache.log4j.PatternLayout
log4j.appender.a.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss,SSS} [%t] [%c]-[%p] %m%n
### set log levels - for more verbose logging change 'info' to 'debug' ###
### 日誌記錄器的配置,固定語法  log4j.rootLogger=輸出級別, 輸出源,輸出源...  ###
log4j.rootLogger=debug, a

springMVC主配置文件springMVC.xml:json

<?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:p="http://www.springframework.org/schema/p"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans 
   http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/mvc 
        http://www.springframework.org/schema/mvc/spring-mvc.xsd
        http://www.springframework.org/schema/context 
        http://www.springframework.org/schema/context/spring-context.xsd">

<!-- 開啓註解掃描 -->
<context:component-scan base-package="com.bilibili"></context:component-scan>
</beans>

jsp頁面:本文使用的是老舊的方式進行展現,新方式先後端分離,文章末尾有介紹JSP方式與先後端分離方式的不一樣後端

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
    <h1>hello world!!!</h1>
</body>
</html>

在web.xml中配置映射:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
        http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
           version="3.0">

    <servlet>
        <servlet-name>DispatcherServlet</servlet-name>
        <!--配置springmvc的核心控制器 -->
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <!-- 配置springmvc的核心配置文件的位置,Key是固定的 -->
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:springMVC.xml</param-value>
        </init-param>
    </servlet>
    <servlet-mapping>
        <servlet-name>DispatcherServlet</servlet-name>
        <!-- 配置springmvc的核心控制器攔截的請求,咱們攔截全部已 do爲後綴的全部請求 -->
        <url-pattern>*.do</url-pattern>
    </servlet-mapping>
</web-app>

建立自定義處理器:

/**
 *  @Controller:聲明當前類是一個控制器
 */
@Controller
public class MyController {
    /**
     * @RequestMapping: 配置指定的請求由指定的方法來處理
     * @return
     */
    @RequestMapping("/show1.do")
    public ModelAndView test1(){
        ModelAndView mv = new ModelAndView();
        mv.setViewName("/WEB-INF/views/hello.jsp");
        mv.addObject("msg","springMVC入門");
        return mv;
    }
}

Spring MVC中的組件執行流程

SpringMVC執行流程圖

組件說明:

  • DispatcherServlet:用戶請求到達前端控制器,它就至關於mvc模式中的c,dispatcherServlet是整個流程控制的中心,由它調用其它組件處理用戶的請求,dispatcherServlet的存在下降了組件之間的耦合性。
  • HandlerMapping:HandlerMapping負責根據用戶請求找到Handler即處理器,SpringMVC提供了不一樣的映射器實現不一樣的映射方式,例如:配置文件方式,實現接口方式,註解方式等。
  • HandlAdapter:經過HandlerAdapter對處理器進行執行,經過擴展適配器能夠對更多類型的處理器進行執行。
  • Handler:具體業務處理器,由DispatcherServlet把用戶請求轉發到Handler。由Handler對具體的用戶請求進行處理。
  • View Resolver:View Resolver負責將處理結果生成View視圖,View Resolver首先根據邏輯視圖名解析成物理視圖名即具體的頁面地址,再生成View視圖對象,最後對View進行渲染將處理結果經過頁面展現給用戶。
  • View:經過頁面標籤將模型數據經過頁面展現給用戶。

其中大部分組件框架都已經提供而且已經配置完成,好比映射器和適配器,我麼須要配置的是視圖解析器:

<context:component-scan base-package="com.bilibili"></context:component-scan>

<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <!-- 配置視圖解析器的後綴,去找以 .jsp結尾的視圖 -->
    <property name="suffix" value=".jsp"></property>
    <!-- 配置視圖解析器的 ,也就是去哪裏找視圖-->
    <property name="prefix" value="/WEB-INF/views/"></property>
</bean>
<!-- 低版本須要主動配置註解驅動 -->
<!-- <mvc:annotation-driven></mvc:annotation-driven> -->

RequestMapping(映射請求)

  • @Controller:表明這是一個Handler,也就是執行器。
  • @RequestMapping():規定映射規則
    1. 定義在方法上:請求路徑就是方法上的@RequestMapping的value值
    2. 定義在類上:至關於給url多加了一個一級路徑。

Ant映射風格:

  • ?:匹配一個字符
  • *:匹配任意個字符
  • **:匹配任意路徑

Rest映射風格:

路徑中使用{key}來傳參

舉例說明:

/**
 * Rest風格的映射
 * {key}表示佔位符,key爲URL中容許的字符
 * 可是注意,咱們能夠經過@PathVariable(key) 獲取地址中的參數。
 * @PathVariable 註解中的key必須和{key}佔位符中的key一致,才能獲取。形參名稱能夠是任意名
 * @return
 */
@RequestMapping("show5/{name}/{id}")
public ModelAndView test5(@PathVariable("name")String name,@PathVariable("id") int id){
    ModelAndView mv = new ModelAndView();
    mv.setViewName("hello");
    mv.addObject("msg","Rest風格的使用:name="+name+"  id="+id);
    return mv;
}

@RequestMapping()註解的屬性:

  • value:映射路徑
  • method:請求方法
    值:RequestMethod.GETRequestMethod.POST等,能夠爲多個
  • params:參數爲字符串數組
    值:"id"必須攜帶參數(id)
    值:"!id"不能攜帶參數(id)
    值:"id=2"參數id必須爲2(必須有參數id)
    值:"id!=2"參數id不能爲2(能夠沒有)

組合註解:

  • @GetMapping:至關於@RequestMapping(method = RequestMethod.GET)
  • @PostMapping:至關於@RequestMapping(method = RequestMethod.POST)
  • @PutMapping:至關於@RequestMapping(method = RequestMethod.PUT)
  • @DeleteMapping:至關於@RequestMapping(method = RequestMethod.DELETE)

@RequestMapping註解的方法返回值是String

@RequestMapping("show17")
//springMVC自動傳遞model
public String test17(Model model){

    model.addAttribute("msg","控制器優化後的代碼");
    return "hello";//springMVC默認將返回的字符串直接做爲視圖名
}

@RequestMapping註解的方法返回值是void:

@ResponseStatus(HttpStatus.OK)//若是不響應頁面,須要設置響應的狀態爲ok
@RequestMapping("show18")
public void test18(Model model){
    System.out.println("返回值是void類型");
}

接收servlet經常使用對象:

@ResponseStatus(HttpStatus.OK)
@RequestMapping("show19")
//直接寫參數類型便可,springMVC自動傳遞參數,不考慮參數順序
public void test19(Model model, HttpServletRequest request, HttpServletResponse response, HttpSession session){
    System.out.println(request);
    System.out.println(response);
    System.out.println(session);
}

接收普通參數:
@RequestParam(value="", required=true/false, defaultValue="")

  1. value:參數名
  2. required:是否必須,默認爲true,標示請求參數中必須包含該參數,若是不包含則拋出異常
  3. defaultValue:默認參數值,若是設置了該值,required=true將失效(即便手動設置了也會失效),自動爲false,若是請求中不包含該參數則使用默認值。
@RequestMapping("show20")
public String test20(Model model,@RequestParam(value = "username",required = true,defaultValue = "lisi") String username){
    model.addAttribute("msg",username);
    return "index";
}

獲取Cookie:

  • 在參數中添加HttpServletRequest request,使用request.getCookies()方法
  • 使用@CookieValue註解
//@CookieValue : 使用方式和@RequestParam一致,須要注意,value值就是cookie中的名字,區分大小寫。
@RequestMapping("show22")
public String test22(Model model,@CookieValue("JSESSIONID")String sessionId){
    model.addAttribute("msg",sessionId);
    return "index";
}

直接在參數中添加POJO類型能夠實現前端數據直接綁定爲POJO對象。

集合綁定:
集合屬性爲簡單類型:
當前端同一個name有多個值時能夠直接使用List來接收:@RequestParam("chenkBoxName") List<String> box

集合屬性爲POJO類型:
表單的name值須要是集合屬性名[索引].Pojo屬性名,好比users[0].age,表明第一個表單的age屬性。

後端這麼獲取:


沙雕方法

public class Users {
    private List<User> userList;

    public List<User> getUserList() {
        return userList;
    }

    public void setUserList(List<User> userList) {
        this.userList = userList;
    }
}

@RequestMapping("show26")
    public String test26(Model model, Users users){
        model.addAttribute("msg",userVo);
        return "hello";
}

哦,上帝,這種先後端交互這太蠢了。

響應JSON

Json纔是王道

首先引入依賴:

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.9.9</version>
</dependency>

注意須要在SpringMVC.xml文件中添加註解驅動(爲何???):

<mvc:annotation-driven></mvc:annotation-driven>

這樣就能夠返回Json類型數據:


Controller返回JSON

@ResponseBody//響應json數據
@RequestMapping("show28")
public List<User> test28(){
    List<User> userList = new ArrayList<>();
    User u1 = new User();
    u1.setName("張三1");
    u1.setAge(23);
    u1.setIncome(10000);
    u1.setIsMarry(true);
    User u2 = new User();
    u2.setName("張三2");
    u2.setAge(24);
    u2.setIncome(10002);
    u2.setIsMarry(false);

    userList.add(u1);
    userList.add(u2);
    return userList;
}

接收JSON爲POJO對象:

直接在參數裏添加@RequestBody修飾的POJO對象便可:

@RequestMapping("show29")
public String test29(Model model,@RequestBody User user){

}

若是出現亂碼現象:

  1. filter全局亂碼過濾器
  2. SpringMVC中配置字符串消息處理器編碼格式(StringHttpMessageConverter這個類提供了編碼的有參構造)

即springMVC.xml中配置:

<mvc:annotation-driven>
    <mvc:message-converters>
        <bean class="org.springframework.http.converter.StringHttpMessageConverter">
            <constructor-arg value="utf-8"></constructor-arg>
        </bean>
    </mvc:message-converters>
</mvc:annotation-driven>

@RestController

有時若是在一個Contoller中全部的方法都是用來響應json格式數據的,那麼若是有多個方法,就須要在多個方法上使用@ResponseBody,這樣太麻煩,springmvc提供了一個@RestController,將該註解使用在Controller類上,那麼該controller中的全部方法都默認是響應json格式的數據了


文件上傳

Spring有兩個web相關的包,spring-webmvc :這個裏面存放的是springmvc的核心功能。spring-web:這裏面存放的是web相關的功能,例如監聽器,文件上傳等,而文件上傳依賴apache的commons-fileupload依賴。

首先導入依賴:

<!-- 文件上傳依賴的包,apache的 -->
<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.4</version>
</dependency>

建立控制器:

/**
 *  @RequestParam: 注意此註解的值,須要和表單提交的名字一致
 * @param model
 * @param file  接受上傳的文件
 * @return
 * @throws Exception
 */
@RequestMapping("show34")
public String test34(Model model, @RequestParam("file") MultipartFile file) throws Exception{

    if(file!=null){
        file.transferTo(new File("f://download/"+file.getOriginalFilename()));
    }
    model.addAttribute("msg","上傳成功");

    return "hello";
}

前端HTML中建立表單:

<!-- 這裏提交方式必須是post,而且須要有 enctype="multipart/form-data" 這一屬性 -->
<form action="http://localhost:8080/hello/show34.do" method="post" enctype="multipart/form-data">
    <input type="file" name="files">
    <input type="submit"  value="上傳">
</form>

在spring配置文件中添加文件解析器:

<!-- 配置文件上傳解析器,注意,此處id必須爲 multipartResolver -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    <!-- 配置文件上傳的最大值,這裏是5M -->
    <property name="maxUploadSize" value="5242880"></property>
    <!-- 配置上傳文件的名字編碼格式 -->
    <property name="defaultEncoding" value="utf-8"></property>
</bean>

上傳多個文件:

首先在前端HTML的input標籤中添加multiple屬性:

<!-- 添加multiple屬性以支持多個文件 -->
<input type="file" multiple name="files">

後端控制器須要修改MultipartFile參數爲數組:

@RequestMapping("show35")
public String files(Model model, @RequestParam("files") MultipartFile[] files)throws Exception {
    for (MultipartFile file : files) {
        if (file != null) {
            file.transferTo(new File("D:/dir/B/"+file.getOriginalFilename()));
        }
    }
    model.addAttribute("msg", "成功");
    return "index";
}

此時即可支持多個文件上傳。

重定向及轉發

返回值爲字符串時,默認爲視圖名稱。當返回值字符串是以」forward:」或者」redirect:」開頭,則會被認爲是轉發或者重定向。
方式以下:
轉發:forward:/hello/show.do(絕對路徑)或者forward:show.do(相對路徑)
重定向:redirect:/hello/show.do(絕對路徑)或者redirect:show.do(相對路徑)
/:表示絕對路徑,指的是localhost:8080/springmvc(項目名稱能夠省略)
不帶/:表示相對路徑,相對於當前請求的路徑
    若是當前請求是:localhost:8080/springmvc(項目名稱能夠省略)/hello/show32
    那麼不帶/:表示localhost:8080/springmvc(項目名稱能夠省略)/hello/

攔截器

攔截器的執行過程:

攔截器執行過程

攔截器攔截後執行過程

自定義攔截器

建立類並實現HandlerInterceptor,而後註冊到spring中便可。

攔截器類:

public class MyInterceptor1 implements HandlerInterceptor {

    /**
     * 在handler方法執行以前執行,
     *
     * @return true,攔截器放行,返回false,攔截器不放行,後續業務邏輯進行處理。
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("myInterceptor1,預處理方法執行執行");
        return true;
    }

    /**
     *  handler執行以後執行。
     * @throws Exception
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("myInterceptor1,後置處理方法執行執行");
    }

    /**
     * 在視圖渲染以後執行
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("MyInterceptor1,請求完成回調方法正在執行");
    }
}

而後在spring中註冊攔截器:

<!-- 
    /: 表示絕對路徑:
    http://localhost:8080/springmvc
    /*:表示絕對路徑下的任意一級路徑:
    http://localhost:8080/springmvc/xxx
    /**:表示絕對路徑下的任意多級目錄:
    http://localhost:8080/springmvc/xxx
    http://localhost:8080/springmvc/xxx/xxx/xxx
 -->
<mvc:interceptors>
    <mvc:interceptor>
        <mvc:mapping path="/**"/>
        <bean class="com.bilibili.interceptor.MyInterceptor1"></bean>
    </mvc:interceptor>
</mvc:interceptors>

攔截器的執行順序爲註冊的順序

Post亂碼過濾器

此時只需在web.xml中配置spring過濾器便可便可:

<filter>
    <filter-name>CharacterEncodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
        <param-name>encoding</param-name>
        <param-value>utf-8</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>CharacterEncodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

自定義異常處理類

這種方式若是文件太大,會致使瀏覽器接收不到服務器返回的ModelAndView,文件小一點的話卻能夠

整體流程就是建立一個類實現HandlerExceptionResolver接口,而後在在spring中註冊便可:

public class MyException implements HandlerExceptionResolver {
    @Override
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        ModelAndView mv = new ModelAndView();
        if (ex instanceof MaxUploadSizeExceededException) {
            System.out.println("error");
            mv.setViewName("error");
            mv.addObject("msg", "文件過大");
        }
        return mv;
    }
}

而後在Spring容器中註冊便可


附:JSP與先後端分離

之前老的方式是:

  1. 客戶端請求
  2. 服務端的servlet或controller接收請求(路由規則由後端制定,整個項目開發的權重大部分在後端)
  3. 調用service,dao代碼完成業務邏輯
  4. 返回jsp
  5. jsp展示一些動態的代碼

新的方式是:

  1. 瀏覽器發送請求
  2. 直接到達html頁面(路由規則由前端制定,整個項目開發的權重前移)
  3. html頁面負責調用服務端接口產生數據(經過ajax等等)
  4. 填充html,展示動態效果。

出自知乎:JSP爲何被淘汰了


附:URL中的字符

URL的特殊字符 當幾種特定的字符集合出如今URL中時,你必須特別注意:

  • 首先,在URL中有特殊意義的字符,也就是保留字符:

    ;/?:@&=+$,,10個,這意味着,這些字符一般在URL中使用時,是有特殊含義的(如 ":"把每個部分分隔開來), 若是一個URL的某一部分(如查詢參數的一部分)可能包含這些字符之一,則應該在放入URL以前 對其進行轉義處理.

  • 第二組須要注意的字符集是非保留字符集.以下:
    -_.!~*'(),9個,這些字符能夠被用於URL的任何位置(有些地方,不容許它們出現). 使用它們做爲URL的一部分時,你不須要進行編碼/轉義處理.你能夠對它們進行轉義操做且不影響URL 的語義,但不建議這麼作.

  • 第三組 不推薦字符 也就是避用字符集合使用它們是不明智的:
    {}|\^[]、`(數字1鍵前),8個,不明智的緣由:網關有時會修改這樣的字符,或者將其做爲分隔符使用.這並不意味着網關總會修改這些字符,但這種狀況可能發生.若是真是要使用這些字符,請作轉義處理.

  • 第四組,例外字符集,這組字符集是全部的ASCII控制字符組成.包含空格字符如下列字符:
    <>#%"、` ,6個,控制字符是不可打印的US-ASCII字符(十六進制00~1F及7F)若是使用,請轉義處理.有些字符#(哈希)和%(百分比)在URL上下文中有着特殊含義,你能夠把它們看成保留字符對待.這個集合中的其它字符沒法被打印,所以對它們進行轉義是惟一的表示方式,<>"`、這三個字符須要被轉義,由於這些字符一般用來在文本中分隔URL。

參考知乎


寫的有點亂,不過這只是做爲學習筆記而已。┓( ´∀` )┏

相關文章
相關標籤/搜索