4.SpringMVC

SpringMVC

SpringMVC的核心是DispatcherServlet,所有請求都要通過它轉(zhuǎn)發(fā),當(dāng)一個(gè)用戶發(fā)起一個(gè)請求,DispatcherServlet先找到處理器映射,根據(jù)映射找到類中對應(yīng)的控制器,然后調(diào)用控制器中對應(yīng)的方法,返回?cái)?shù)據(jù)模型(即各種需要被前端用到的數(shù)據(jù),比如實(shí)體類)以及視圖名稱,然后DispatcherServlet在根據(jù)控制器返回的視圖名稱找到視圖解析器解析視圖,最后輸出到前端響應(yīng)

  1. DispatcherServlet找到處理器映射
  2. 根據(jù)映射結(jié)果找到控制器中對應(yīng)的方法
  3. 根據(jù)方法返回的模型和視圖名找到視圖解析器解析
  4. 然后根據(jù)視圖解析器找到視圖使用模型渲染結(jié)果

在這里模型指的是返回給用戶并在瀏覽器顯示的數(shù)據(jù)

1.通過java配置取代web.xml配置

servlet3.0規(guī)范定義了一個(gè)ServletContainerInitializer接口來初始化類,用于替代web.xml,在servlet容器啟動(dòng)時(shí),會(huì)加載該接口的實(shí)現(xiàn)類用于加載相關(guān)配置,SpringMVC的AbstractAnnotationConfigDispatcherServletInitializer實(shí)現(xiàn)了這個(gè)接口,繼承這個(gè)接口即可實(shí)現(xiàn)不通過web.xml配置文件,


//當(dāng)web程序啟動(dòng)時(shí),tomcat會(huì)加載實(shí)現(xiàn)了ServletContainerInitializer,而下面這個(gè)就實(shí)現(xiàn)了
//所以。這個(gè)就是一個(gè)配置類,用于代替web.xml,在該類被初始化時(shí),會(huì)同時(shí)初始化Dispatcher核心類和ConTextLoaderListener
//后面ContextLoaderListener個(gè)類用于getServletConfigClasses方法
public class SpitterWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

   //根配置,配置數(shù)據(jù)庫等 其他要用到的組件信息
   @Override
   protected Class<?>[] getRootConfigClasses() {
       return new Class<?>[]{RootConfig.class};
   }

   //Dispatcher配置
   //使用conTextLoaderListener加載前端控制器的上下文
   @Override
   protected Class<?>[] getServletConfigClasses() {
       return new Class<?>[]{WebConfig.class};
   }


   //實(shí)現(xiàn)ServletContainerInitializer是用于替代web.xml,而Abstra....是SpringMVC 在替代web.xml的基礎(chǔ)上加入MVC特有的組件
//而這個(gè)ServletContainer是servlet3.0以后的產(chǎn)物,servlet容器tomcat7或者更高的版本才能這么使用
   //攔截
   @Override
   protected String[] getServletMappings() {
       return new String[]{"/"};
   }
}

RootConfig

package spitter.config;


import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;

@Configuration
@ComponentScan(basePackages = {"spitter"},excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,value = EnableWebMvc.class)})
public class RootConfig {
}

WebConfig

package spitter.config;


import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.view.InternalResourceViewResolver;

@Configuration
@EnableWebMvc
@ComponentScan("spitter.web")
public class WebConfig extends WebMvcConfigurerAdapter {

    @Bean
    public ViewResolver viewResolver(){
        InternalResourceViewResolver resourceViewResolver = new InternalResourceViewResolver();

        resourceViewResolver.setPrefix("/WEB-INF/view/");
        resourceViewResolver.setSuffix(".jsp");
        resourceViewResolver.setExposeContextBeansAsAttributes(true);

        return resourceViewResolver;
    }

    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }
}

其中g(shù)etServletConfigClass用于獲取配置Dispatcher相關(guān)屬性,而rootConfig用于配置其他類,例如mybatis redies等

最后在創(chuàng)建一個(gè)控制器

package spitter.web;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
public class HomeController {
    static {
        System.out.println("初始化成功");
    }
    @RequestMapping(value = "/",method = RequestMethod.GET)
    public String home(){
        return "home";
    }
}

然后在對應(yīng)設(shè)置的目錄創(chuàng)建一個(gè)jsp,最后測試就能成功跳轉(zhuǎn)到頁面

傳遞模型數(shù)據(jù)到視圖中

在控制器的方法中,可以指定模型參數(shù),就可以把模型數(shù)據(jù)帶到前端顯示,實(shí)際這個(gè)參數(shù)就是一個(gè)map,具體如下,在這里傳遞了一個(gè)對象集合

    /**
     * 給定一個(gè)模型參數(shù),用于傳遞 需要顯示在前端的數(shù)據(jù),實(shí)際上model是一個(gè)map,
     * 當(dāng)不給其傳遞key時(shí),他會(huì)自己推斷,在這里名字為spittles,如果你不希望使用Model 作為參數(shù)
     * 你也可以使用Map model作為參數(shù),效果已有
     */
    public String home(Model model){
        model.addAttribute(createSpittleList(20));
        return "home";
    }

接收請求的輸入

SpringMVC自然也需要支持前端傳參數(shù)到后臺(tái),SpringMVC中可以在方法參數(shù)前面添加一個(gè)@RequestParam表示要接受的參數(shù)的name,代碼如下

   /**
     * 在這里@RequestParam代表必須要傳的參數(shù),也可以指定一個(gè)默認(rèn)值,在沒有傳入?yún)?shù)的時(shí)候
     * 將賦值為默認(rèn)值,默認(rèn)值都必須是String類型的,不過在賦值給max的時(shí)候會(huì)做對應(yīng)的轉(zhuǎn)換
     * @param max
     * @param count
     * @return
     */
    public List<Spittle> spittles(@RequestParam(value = "max",defaultValue = "3") long max,@RequestParam int count){
        System.out.println(max +" " +count);
        return findSpittle(max,count);
    }

如果在一個(gè)頁面的form表單上不寫action,那么這個(gè)表單提交時(shí),就會(huì)默認(rèn)提交到 跳轉(zhuǎn)到這個(gè)頁面上的路徑,意思就是 如果你訪問 /index,而Controller跳轉(zhuǎn)到index.jsp,那么這個(gè)jsp就會(huì)提交到該控制器中去,即可指定一個(gè)POST同名方法,參數(shù)不一致即可(重載)

如下,其中redirect代表重定向,直接輸入controller的url路徑

    @RequestMapping(value = "view",method = RequestMethod.POST)
    public String spittles(@RequestParam(value = "max",defaultValue = "3") long max,@RequestParam int count){
        System.out.println(max +" " +count);
        return "redirect:/view";
    }
    @RequestMapping(value = "view",method = RequestMethod.GET)
    public String spittles(){

        return "form";
    }

校驗(yàn)表單

普通的后臺(tái)校驗(yàn)表單是獲取到前端的值,然后做很多邏輯判斷,這樣既寫了很多重復(fù)代碼,而且如果字段比較多,寫起來也比較麻煩,從Spring3.0開始,Spring提供了對java校驗(yàn)api,又稱之為JSR-303,實(shí)際上是一個(gè)接口,而其實(shí)現(xiàn)類就是Hibernate Validator,下面是相關(guān)注解

  • @AssertFalse:所注解的屬性必須是false
  • @AssertTrue:所注解的屬性必須是true
  • @DecimalMax:所注解的屬性必須是數(shù)字,且小于給定的值
  • @DecimalMin:所注解的屬性必須是數(shù)字,且大于給定的值
  • @Digits:必須是數(shù)字,且必須有指定的數(shù)字
  • @Future:必須是將來的日期
  • @Max:必須是數(shù)字,且值要小于或者等于給定的值
  • @Min:同上,大于給定的值
  • @NotNULL:不能為空
  • @NUll:必須為null
  • @Past:必須是過去的日期
  • @Pattern:必須匹配正則表達(dá)式
  • @Size:值必須是String、集合、數(shù)組,且長度符合要求

用法如下

package spitter;

import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.util.Date;


public class Spittle {
    private  Long id;
    @NotNull(message = "不能為空")
    @Size(min=5,max = 16,message = "不能超過5-16")
    private  String message;
    @NotNull
    @Size(min=5,max=25,message = "不能超過5-25")
    private  String time;
    @NotNull
    @Size(min=2,max=30,message = "不能超過2-30")
    private String latitude;
    @NotNull
    @Size(min=2,max = 30,message ="不能超過2-30")
    private String longitude;

    public Spittle(Long id, String message, String time, String latitude, String longitude) {
        this.id = id;
        this.message = message;
        this.time = time;
        this.latitude = latitude;
        this.longitude = longitude;
    }

    public Spittle() {
    }

    public Spittle(String message, String time) {
        this.message = message;
        this.time = time;
    }

    @Override
    public int hashCode() {
        return super.hashCode();
    }

    @Override
    public boolean equals(Object obj) {
        return super.equals(obj);
    }

    @Override
    public String toString() {
        return "Spittle{" +
                "id=" + id +
                ", message='" + message + '\'' +
                ", time='" + time + '\'' +
                ", latitude='" + latitude + '\'' +
                ", longitude='" + longitude + '\'' +
                '}';
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public String getTime() {
        return time;
    }

    public void setTime(String time) {
        this.time = time;
    }

    public String getLatitude() {
        return latitude;
    }

    public void setLatitude(String latitude) {
        this.latitude = latitude;
    }

    public String getLongitude() {
        return longitude;
    }

    public void setLongitude(String longitude) {
        this.longitude = longitude;
    }
}

    @RequestMapping(value = "registry",method = RequestMethod.POST)
    public String processRegistration( @Valid Spittle spittle, Errors error){
        System.out.println(spittle);
        if(error.hasErrors()){
            System.out.println(error.getAllErrors());
            return "form";
        }

        return "home";
    }

建議不要使用高版本,我使用6.0.9報(bào)錯(cuò),版本兼容報(bào)錯(cuò),顯示初始化失敗,換成5.3版本以后正常執(zhí)行

使用Apache Tiles視圖定義布局

如果需要對每一個(gè)布局添加頭部和尾部,常規(guī)做法是為每一個(gè)jsp模版設(shè)置頭部和尾部,這樣明顯擴(kuò)展性不好且增加日后維護(hù)成本,所以就需要布局引擎Apache Tiles

沒什么卵用,多了些莫名其妙的配置,實(shí)際也就和一般的頭部引用jsp,沒什么差別

文件上傳

Spring的文件上傳比較簡單,只要在Spring 中注冊一個(gè)文件上傳的解析器就行了,Spring中有兩個(gè)解析器

  • ComonsMultipartResolver:通用
  • StandardServletMultipartResolver:適用于servlet3.0以后

基本用法

  1. 在Spring配置文件中實(shí)例化一個(gè)上傳解析器
  2. 接收用MultipartFile

使用java配置的一般例子

在主配置類中重寫方法


    /**
     * 設(shè)置上傳文件的臨時(shí)文件
     * @param registration
     */
    @Override
    protected void customizeRegistration(ServletRegistration.Dynamic registration) {
        //設(shè)置零時(shí)文件夾,以及其他相關(guān)設(shè)置
        registration.setMultipartConfig(new MultipartConfigElement("/Users/f7689386/PycharmProjects",2097152,4194304,0));
    }

Controller寫法

    public String processRegistration(@RequestPart("profilePicture")MultipartFile profilePicture){
        //獲取名字
        System.out.println(profilePicture.getOriginalFilename());


        return "home";
    }
    

MultipartFile接口相關(guān)方法

public abstract interface MultipartFile {
  String getName();
  String getOriginalFilename();
  String getContentType();
  boolean isEmpty();
  long getSize();
  byte[] getBytes() throws java.io.IOException;
  InputStream getInputStream() throws java.io.IOException;
  void transferTo(File arg0) throws IOException,IllegalStateException;
}
 

定義異常通知切面

如果有多個(gè)控制器拋出同樣的異常,要對每一個(gè)方法都要進(jìn)行處理跳轉(zhuǎn)到error頁面,這樣會(huì)有大量存在代碼,可以抽取成一個(gè)切面,對每一個(gè)拋出該異常的方法 作為一個(gè)切點(diǎn)

在SpringMVC中,可以給類添加@ControllerAdvice代表這個(gè)類是一個(gè)通知類,@ExceptionHandler代表這個(gè)方法是一個(gè)異常通知方法

java

@ControllerAdvice
public class AppWideExceptionHandler{
   @ExceptionHandler(DuplicateSprittleException.class)
   public String duplicateSplittleHandler(){
       return "error";
   }
}

上面這個(gè)類代表如果有某個(gè)類拋出DulicateSprittleException異常,則執(zhí)行上面那個(gè)方法,其中ControllerAdvice里面包含的@Component,所以在啟動(dòng)的時(shí)候它也會(huì)被掃描進(jìn)來被Spring管理

重定向傳遞數(shù)據(jù)

當(dāng)用戶提交post請求以后,如果不使用重定向跳轉(zhuǎn)而使用請求轉(zhuǎn)發(fā),那么在刷新頁面或者后退可能會(huì)產(chǎn)生多次提交數(shù)據(jù)等危險(xiǎn)操作,但是在SpringMVC中Model的生命周期都是一次請求,在重定向model存儲(chǔ)的數(shù)據(jù)都會(huì)消失

有個(gè)方案是把需要訪問的數(shù)據(jù)存儲(chǔ)在會(huì)話Session中,Spring也認(rèn)為這是一個(gè)不錯(cuò)的方式,但是Spring認(rèn)為我們不需要管理這些數(shù)據(jù),Spring提供了RedirectAttributes設(shè)置屬性,在跳轉(zhuǎn)到頁面以后數(shù)據(jù)消失,類似一次請求存儲(chǔ)的數(shù)據(jù)

    @RequestMapping(value = "redirect",method = RequestMethod.GET)
    public String redirect(RedirectAttributes model){
        model.addFlashAttribute("a","a");
        return "redirect:/spitter/(username)";
    }

如果傳遞的是一個(gè)變量名,還可以不設(shè)置key,他會(huì)默認(rèn)以變量名作為key

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容