1. 程式人生 > >SpringMVC入門學習三

SpringMVC入門學習三

port 作用 strong 處理方式 frame Coding 返回json 地址 tac

今天是Springmvc學習的第三天,今天我將主要介紹一下:

  • 常用註解的使用
  • 關於非post、get請求的處理
  • 文件上傳與下載
  • 攔截器

技術分享圖片

常用註解的使用

老大在此

@Controller

@Controller放在類的上面,用於將一個類標記為Controller,處理DispatcherServlet分發的請求,當然真正處理請求的還是使用@RequestMapping註解的方法。


處理request URI的註解

@RequestMapping

@RequestMapping是一個用來處理請求地址映射的註解,可用於類上面【代表模塊】和方法上面【代表業務】

裏面參數屬性,其中所有的參數都可以傳入數組的形式:

  1. value:代表請求的地址如"/login",可以寫多個【使用數組】,只寫它時,可以省略不寫

  2. name:value的別名,作用是一樣的。

  3. method:請求的method類型,裏面要傳入RequestMethod類型的數據,如GET,POST,PUT等等,可以指令多個(使用數組傳遞即可)技術分享圖片
    它的衍生品:

    • @GetMapping:專門處理get請求
    • @PostMapping:專門處理Post請求
  4. params:設置需要的參數,如果沒有,請求就過不去,還可以設置參數是否等於或則不等於

    // 當穿過來的參數data不為not才進行響應
    @RequestMapping(value = "/test1",params = "data!=not")
    // 當穿過來的參數data為hava才進行響應
    @RequestMapping(value = "/test1",params = "data=have")
    

    ps:中間註意別打空格

  5. headers
    指定request中必須包含某些指定的header值,該方法才會處理請求。

    @RequestMapping
    (value="/hello",headers="Referer=https://baidu.com")
  6. consumes 消費者,指令請求提交內容的類型(Content-Type),例如可以限令必須為application/json;charset=UTF-8,指令多個使用數組形式

  7. produces 生產者,指令放回的內容的類型,但是必須是請求頭所包含的類型

@PathVariable

這個是restful風格的請求方式,用於將URL中的模板變量映射到方法的參數上面,這個在我的上一篇博客有所介紹。


共享數據

@ModelAttribute

@ModelAttribute註解可以被應用在方法參數上面和方法聲明上面。其中被@ModelAttribute註解的方法會優先於controller方法(被@RequestMapping註解)之前執行,因為模型對象優先於controller方法創建。

簡單的來說,@ModelAttribute有兩個作用,將數據保存在model上面【標註在方法上面】,將數據從model取出來【標註在參數裏面】

將數據保存在model上面

  1. 使用@ModelAttribute註解無返回值方法

    @ModelAttribute
    public void model1(Model model){
        model.addAttribute("modle1","你好呀");
    }
    
    @RequestMapping("/modleC")
    public String modleC(){
        return "test";
    }
    

    其實這個就相當於

    @RequestMapping("/modleC")
    public String modleC(Model model){
        model.addAttribute("modle1","你好呀");
        return "test";
    }
    
  2. 註解有返回值的方法

    /**
     * 註解有返回值的
     * 在這種情況下,放回值對象會默認的放在隱藏的Model中。
     * 其key為放回值 **類型** 首字母小寫,當然也可以在@ModelAttribute
     * 加上value或則name的屬性來指令key
     * @return
     */
    @ModelAttribute
    public String modeler02(){
        String Hello = "你好呀";
        return Hello;
        //在這裏面key為string
    }
    
  3. 與@RequestParam結合使用
    @RequestParam的具體用法將在下面介紹

    加入此時我們發送了一個這樣的請求:/test/name=googboy

    // 取出get請求的參數name,將其存入modle中
    @ModelAttribute("name")
    public String modeler02(@RequestParam()String name){
        return name;
    }
    
    @RequestMapping("/test")
    public String test(){
        return "test";
    }
    

將數據從model取出來
假如此時有了一個model,key為name,value為Tom數據,那麽我們可以通過@ModelAttribute將數據取出來。

@RequestMapping("/test")
public String test(@ModelAttribute("name")String name){
    // 輸出是  名字是Tom
    System.out.println("名字是"+name);
    return "test";
}

保存數據

@SessionAttributes

假如希望在多個請求之間共用數據,則可以在控制器類上面標註一個@SessionAttributes,其中value指令是model中的哪一個存入session,type為數據類型,兩者都可以放數組類型的數據。

技術分享圖片

取出數據

@SessionAttribute

@SessionAttribute的作用很簡單,就是獲得session的值

假如此時有一個session key為name,value為Tom,獲得session

@RequestMapping("/test2")
public String test2(@SessionAttribute("name") String name){
    System.out.println("session結果是"+name);
    return "test";
}

假如此時不存在就會報錯,這是可以使用required來確定是否必須

@RequestMapping("/test2")
    //非必須,同時value/name必須加上
    public String test2(@SessionAttribute(value = "name",required = false) String name){
        System.out.println("session結果是"+name);
        return "test";
    }

對於取出參數的處理

@RequestParam

@RequestParam主要是用於在Springmvc後臺控制層獲得參數,類似request.getParameter("name")

它有下列參數

  • value:value表示要取出的參數,當只有value這個參數時,可以省略不寫。

  • name:value的別名,作用和value一樣。

  • required:默認值是true,當傳入的參數值不存在時,程序就會報錯,這時候就可以將required設置為false。

  • defaultValue:當設置defaultValue時,會自動的將required設置為false,如果此時請求參數不存在,就會默認的將參數設置為defaultValue。當然,如果參數存在,defaultValue也就是不會發揮作用了。

舉個栗子:

@RequestMapping("/test3")
public String test3(@RequestParam(value = "name",defaultValue = "TomCat") String name){
    // 請求網址是: /test3
    // 輸出是  名字是TomCat
    System.out.println("名字是"+name);
    return "test";
}

@RequestBody

這時候大家可能會問,既然有@RequestParam來處理方式了,為什麽我們還要使用@RequestBody來獲取參數呢?

這個主要主要是因為他們處理數據的類型Content-Type不一樣

  • @RequestParam:處理application/x-www-form-urlencoded編碼的內容,提交方式為Get,Post。

  • @RequestBody:通常是來處理非application/x-www-form-urlencoded編碼格式的內容的數據,比如說application/jsonapplication/xml

既然是處理json數據,那麽就需要使用json的jar包。

<!--====>jackson包導入start-->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.9.7</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-core</artifactId>
    <version>2.9.7</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-annotations</artifactId>
    <version>2.9.7</version>
</dependency>
<!--====>jackson包導入end-->

當發了一個如下所示的json請求時

$(document).ready(function () {
    $("button").click(function () {
        $.ajax({
            url: "/test4",
            type:"post",
            data:JSON.stringify({
                "name":"TODO"
            }),
            dataType:"json",
            contentType:"application/json; charset=utf-8",
            success:function (data) {
                console.log(data)
            }
        });
    });
});

controller代碼,在參數裏面加上@RequestBody:

@RequestMapping("/test4")
public String test4(@RequestBody String name){
    // 名字是{"name":"TOFO"}
    System.out.println("名字是"+name);
    return "test";
}

對於返回數據的處理

@ResposeBody

返回json數據

在大多數的情況下,我們不一定是想返回一個視圖,只想返回一個json數據或者說xml數據,那麽這時候我們就要使用@RequestBody了。@ResponseBody註解被應用於方法上,標誌著響應會寫到響應體裏面去,而不是放到視圖Model裏面去。

那麽就可以這樣使用:

@RequestMapping(value = "/test4")
@ResponseBody
public User test4(@RequestBody String name){
    // 名字是{"name":"TOFO"}
    System.out.println("名字是"+name);
    User u = new User();
    u.setName("小明");
    u.setAge("17");
    return u;
}

返回xml數據

要導入額外的jacksonxml jar包

<dependency>
    <groupId>com.fasterxml.jackson.dataformat</groupId>
    <artifactId>jackson-dataformat-xml</artifactId>
    <version>2.9.7</version>
</dependency>
// 由於要返回xml格式的數據,所以需要設置produces為application/xml
// 使用MediaType裏面的靜態常量,可以防止寫錯
@ResponseBody
@RequestMapping(value = "/test5",produces = org.springframework.http.MediaType.APPLICATION_XML_VALUE)
public User test5(){
    User u = new User();
    u.setName("小紅");
    u.setAge("18");
    return u;
}

SpringMVC關於非post、get請求的處理

在HTML5的規範中,表單元素唯二允許的HTTP的方法是GET和POST,但是如果我們想使用PUT或則DELET方法,那怎麽辦呢?

  • 在瀏覽器中裝作自己是post或則get請求,實際上是PUT請求
<!-- 裝成自己是post請求 -->
<form action="/put" method="post">
    <!-- 實際上想發送的PUT請求 -->
    <input type="hidden" name="_method" value="PUT">
    姓名 <input  name="name" type="text">
    <input type="submit" value="提交">
</form>
  • 在web.xml文件中,從_method找到真正的http請求

加入

<filter>
    <!-- 原理就是從_method找到真正想要http請求,然後分發給controller 
         所以這個必須寫在DispatcherServlet的前面
    -->
    <filter-name>HiddenHttpMethodFilter</filter-name>
    <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>HiddenHttpMethodFilter</filter-name>
    <servlet-name>springmvc</servlet-name>
</filter-mapping>

<!--註冊一個前端控制器-->
<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:springmvc.xml</param-value>
    </init-param>
</servlet>

<!--servlet的映射配置-->
<servlet-mapping>
    <servlet-name>springmvc</servlet-name>
    <!--這裏統一寫/-->
    <url-pattern>/</url-pattern>
</servlet-mapping>

文件上傳與下載

文件上傳

文件上傳同時也是使用form表單進行提交,一般我們的表單的提交是文本型的數據,實際上就是進行字符串的拼接。那麽文件便不是這樣的進行處理,這時候我們就要使用到multipart表單了。

首先我們的配置一個MultipartResolver來告訴DispatchServlet如何來處理multipart請求。

在Servlet3.0中,Spring會默認註冊一個StandardServletMultipartResolver,我們只需要在web.xml啟用<multipart-config>就行。

<multipart-config>
    <location>/tmp</location>
</multipart-config>
屬性作用
location 上傳文件所存放的臨時目錄。必須指定
max-file-size 文件的最大大小,單位為字節。默認沒有限制
max-request-size 請求的最大大小,單位為字節。默認沒有限制
file-size-threshold 文件大小閾值,當大於這個閾值時將寫入到磁盤,否則在內存中。默認值為0

前端代碼

<form action="/upload" method="post" enctype="multipart/form-data">
    文件 <input type="file" name="file"/>
    <input type="submit" value="提交">
</form>

控制文件上傳的代碼

@RequestMapping("/upload")
public String upload(@RequestParam("file")MultipartFile file, HttpServletRequest request) {
    // 假如沒有文件
    if (!file.isEmpty()) {
        // 獲得文件原始名字
        String fileName = file.getOriginalFilename();
        // 獲得文件的後綴名
        String fileSuffix = fileName.substring(fileName.lastIndexOf("."));
        // 取新的名字 UUID可以保證產生的名字不一樣,產生全球唯一的ID
        String newName = UUID.randomUUID() + fileSuffix;
        // 獲取文件存儲目錄地址
        String filePath = request.getSession().getServletContext().getRealPath("/upload");

        File fileForPath = new File(filePath);
        // 如果文件不存在
        if (!fileForPath.exists()) {
            // 新建文件
            fileForPath.mkdir();
        }

        File targetFile = new File(filePath, newName);
        try {
            // 將文件寫入
            file.transferTo(targetFile);
        } catch (IOException e) {
            e.printStackTrace();
        }
    } else {
        System.out.println("文件不存在");
    }
    return "test";
}

文件下載

@RequestMapping("down")
public String down(HttpServletResponse response,HttpServletRequest request){

    // 假如我的文件在/home/xiaohiu/images/鬼刀.jpg
    String fileName= "鬼刀.jpg";

    String parentPath = "/home/xiaohiu/images";

    // 這時候path.toString就是/home/xiaohiu/images/鬼刀.jpg了
    Path path = Paths.get(parentPath,fileName);

    // 假如文件存在
    if (Files.exists(path)){
        // 取出後綴名 jpg
        String fileSuffix = fileName.substring(fileName.lastIndexOf(".")+1);
        // 設置ContentType,只有設置它,才會去下載
        response.setContentType("application/"+fileSuffix);

        try {
            // 添加頭信息 同時對文件名重新編碼防止中文名亂碼
            response.addHeader("Content-Disposition","attachment;filename="+new String(fileName.getBytes("utf-8"),"ISO8859-1"));
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }

        try {
        // 使用輸出流寫出去
            Files.copy(path,response.getOutputStream());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }else {
        System.out.println("文件不存在");
    }
    return "test";
}

SpringMVC攔截器

攔截器實在我們請求之前做檢查,同時有權決定我們接下來是否繼續,同時也可以對我們的請求進行加工處理,可以設置多個攔截器。

看到這裏,是不是想到了我在前面介紹的AOP,其實我們可以理解為Spring攔截器是SpringMVC對AOP的一種實現方式。AOP是通過配置<aop:config>進行配置,而攔截器是通過<mvc:interceptors>進行配置。

那麽我們怎麽實現呢?

  1. 實現HandlerInterceptorAdapter接口

    在SpringMVC中,為實現攔截器功能,有兩種方式,一個是實現HandlerInterceptor接口,第二個是實現WebRequestInterceptor接口,這裏我們選擇使用HandlerInterceptor。

    在HandlerInterceptor接口中,定義了三個方法

    • preHandle():請求之前調用,返回true或則false,返回true,接下來的postHandle和afterCompletion才會起作用。

    • postHandle():在請求之後調用,也就是controller方法執行完後再調用,但是卻是在DispatcherServlet進行視圖返回渲染之前被調用。也就是說,這個可以對modle進行操作。

    • afterCompletion():在DispatcherServlet進行完試圖渲染之後才執行。

    public class TestInterceptor implements HandlerInterceptor {
    
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    
            System.out.println("請求前進行攔截");
    
            // 返回true才會查找下一個攔截器,如果沒有下一個攔截器,則返回controller
            return true;
        }
    
        @Override
        public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
            System.out.println("視圖渲染前進行攔截");
        }
    
        @Override
        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
            System.out.println("視圖渲染後進行攔截");
        }
    }
    
  2. 攔截器的配置

    在springmvc的xml配置文件中

    <!--配置攔截器-->
    <mvc:interceptors>
        <!--配置一個攔截器
            可以配置多個,那麽攔截順序則是從上到下
        -->
        <mvc:interceptor>
            <!--攔截的URI
                /* 代表攔截這一層的任意字符
                /** 代表攔截任意層次的任意字符
            -->
            <mvc:mapping path="/*"/>
            <!--不進行攔截的uri-->
            <mvc:exclude-mapping path="/get"/>
            <bean class="cc.weno.interceptor.TestInterceptor"/>
        </mvc:interceptor>
    </mvc:interceptors>
    

    這時候就可以進行攔截了。

  3. 攔截器順序的問題

    攔截器順序是根據配置的順序來決定的,但是pre、post、after卻有些區別,這張圖就可以表示了。

    技術分享圖片

好了,今天的Springmvc就到了這裏了。

君子不行陌路,管它咫尺還是天涯

SpringMVC入門學習三