【玩轉Spring】spring mvc

M:Model 模型

V:View 視圖

C:Controller 控制器

Spring MVC是基于模型-視圖-控制器模式實現的。不管你是struts,還是Spring MVC,只要是基于Java的WEB框架,都會通過一個前端控制器器。在Spring MVC中DispatcherServlet就是它的前端控制器,那么這個前端控制器做了什么呢?

DispatcherServlet


明白了DispatcherServlet是Spring MVC的前端控制器,那么我們又是怎么將請求全部先發給前端控制器,然后由前端控制器來控制跳轉到相應的組件呢?

注意,如果要使用注解的方式啟動MVC,你的項目必須部署在支持servlet3.0的容器當中,如tomcat7或者更好的版本;這是由于在Servlet 3.0環境中, 容器會在類路徑中查找實現javax.servlet.ServletContainerInitializer接口的類, 如果能發現的話, 就會用它來配置Servlet容器。而Spring提供了這個接口的實現, 名為SpringServletContainerInitializer, 這個類反過來又會查找實現WebApplicationInitializer的類并將配置的任務交給它們來完成。

程序一:配置前端控制器

/***********************DispatcherServlet*******************/
public class ServletInit extends AbstractAnnotationConfigDispatcherServletInitializer{
    public ServletInit() {
        System.out.println("dispatcherservlet啟動了");
    }

    //指定非WEB相關的配置類
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[] {RootConfig.class};
    }

    //指定WEB啟動的配置類
    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[] {WebConfig.class};
    }

     //將DispatcherServlet映射到 "/"
    @Override
    protected String[] getServletMappings() {
        return  new String[] {"/"};
    }
}

/*************************RooConfig**********************/
@Configuration
@ComponentScan(basePackages= {"mvc.logic"})
public class RootConfig {
    public RootConfig() {
        System.out.println("RootConfig啟動");
    }
}

/*************************WebConfig**********************/
@Configuration
@ComponentScan(basePackages= {"mvc.web"})
@EnableWebMvc
public class WebConfig {
    public WebConfig() {
        System.out.println("WebConfig啟動");
    }
}

上面的代碼有兩個問題

1、沒有配置視圖解析器

2、對靜態資源也進行了攔截

視圖解析器

視圖解析器的作用是將邏輯視圖轉為物理視圖,所有的視圖解析器都必須實現ViewResolver接口。Spring MVC將按照你配置的不同的視圖解析器來對模板進行渲染。Spring為提供了對多種視圖的支持,常用的是以下三種:

  • InternalResourceViewResolver 將視圖解析為JSP

  • FreeMarketViewResolver 將視圖解析為FreeMarker模板

  • ThymeleafViewResolver 將視圖解析為Thymeleaf模板

程序二:JSP視圖解析器

@Configuration
@ComponentScan(basePackages= {"mvc.web"})
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter{
    public WebConfig() {
        System.out.println("WebConfig啟動");
    }

    //配置JSP視圖解析器
    @Bean
    public ViewResolver viewResolver() {
        InternalResourceViewResolver  viewreResolver = new InternalResourceViewResolver("/WEB-INF/views/",".jsp");
        return viewreResolver;
    }

    //配置靜態資源過濾
    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }

}

控制器

控制器是真正對數據進行處理的地方,主要包括了參數的接收和view的返回。

程序三:簡單的控制器

@Controller
public class UserController {
    public UserController() {
        System.out.println("UserController被啟動了");
    }

    /*************表示接收處理路徑為index的,GET方式的請求********/
    @RequestMapping(value="/index",method=RequestMethod.GET)
    public void index() {
        System.out.println("調用了index");
    }
}

控制器其實就是一個類,只不過使用@RequestMaaping將方法與路徑進行了綁定。

參數的接收

@ReqeustParame == request.getParameter

@RequestMapping(value="/index/",method=RequestMethod.GET)
public void index(@RequestParam("userName") String userName ) {
    System.out.println("調用了index");
}

@PathVariable

@RequestMapping(value="/index/{userName}",method=RequestMethod.GET)
public void index(@PathVariable("userName") String userName ) {
    System.out.println("調用了index");
}

視圖返回

返回一個視圖名

@RequestMapping(value="/index/",method=RequestMethod.GET)
public String index(@RequestParam("userName") String userName ) {
    return "index";
}

返回ModelAndView

@RequestMapping(value="/index/",method=RequestMethod.GET)
public ModelAndView index(@RequestParam("userName") String userName ) {
    ModelAndView modelAndView = new ModelAndView();
    modelAndView.addObject("", "");
    modelAndView.setViewName("");
    return modelAndView;
}

文件上傳

首先我們想一想文件上傳我們在服務端知道些什么?

  • 文件名

  • 文件類型

  • 文件大小

  • 文件

那么Spring MVC是怎么處理這些問題的呢?

上一節我們已經實現自定義DispatcherServlet,但是在DispatcherServlet中并未實現任何解析multipart請求數據的功能。它將該任務委托給了Spring中MultipartResolver策略接口的實現, 通過這個實現類來解析multipart請求中的內容。 從Spring 3.1開始, Spring內置了兩個MultipartResolver的實現供我們選擇:CommonsMultipartResolver( 使用Jakarta Commons FileUpload解析multipart請求);StandardServletMultipartResolver(依賴于Servlet 3.0對multipart請求的支持)。一般來講, 在這兩者之間, StandardServletMultipartResolver可能會是優選的方案(不依賴于外部組件)。

程序一:在WebConfig中配置MultipartResolver

@Bean
public MultipartResolver multipartResolver() {
    return new StandardServletMultipartResolver();
}

同時在DispatcherServlet中,你還需要重寫customizeRegistration函數。

程序二:配置文件上傳的相關參數

@Override
protected void customizeRegistration(Dynamic registration) {
    String location = "E:/spring-mvc/tmp/uploads";
    File file = new File(location);
    if(!file.exists()) {
        file.mkdirs();
    }
        //每一個文件為3M
    long maxFileSize = 1024 * 1024 * 3;
        //一共上傳15M的內容 
    long requestFileSzie = maxFileSize * 5; 
        //當緩存中有好大的時候,寫入磁盤
    int fileSizeThreshold = 0; 
    registration.setMultipartConfig(
                new MultipartConfigElement(
                        location, 
                        maxFileSize,
                        requestFileSzie ,
                        fileSizeThreshold));
}

程序三:在controller中獲取MultipartFile數據

@RequestMapping(value="/upload")
public String upload(@RequestPart("myFile") MultipartFile myFile) {
    System.out.println("文件名稱:"+myFile.getOriginalFilename());
    return "";
}

至此,Spring MVC文件上傳就完了,是不是非常簡單,非常感謝Spring為我們帶來如此簡便的文件上傳方法。

異常處理

在Http中,大家經常會碰到404、500等常見異常錯誤碼,但是我們不可以直接將錯誤碼返回給用戶,那么我們應該怎么做?在前面的Spring AOP中講過,可以將所有的異常進行統一處理,但是又怎么返回到指定界面呢?

Spring提供了多種方式將異常轉換為響應:

  • 特定的Spring異常將會自動映射為指定的HTTP狀態碼;

  • 異常上可以添加@ResponseStatus注解, 從而將其映射為某一個HTTP的狀態碼

  • 在方法上可以添加@ExceptionHandler注解, 使其用來處理異常。

第一種和第二種方式是指將特定情況下的異常轉換為HTTP狀態碼。第三種是對異常的處理。

程序四:@ResponseStatus

@ResponseStatus(value=HttpStatus.INTERNAL_SERVER_ERROR)
public class MyExcetion extends RuntimeException{
}

需要注意的的是,@ResponseStatus是注解在異常類上的

當得到我們需要的異常之后,我們需要對異常進行處理,Spring MVC利用了AOP的原理,加入了@ControllerAdvice注解,此注解能夠攔截所有我們定義的異常

程序五:@ControllerAdvice + @ExceptionHandler

@ControllerAdvice
public class ExceptionAdvice {
    @ExceptionHandler(MyExcetion.class)
    public String exception() {
        return "error/500";
    }
}

Spring中的過濾器

程序一:自定義Filter

/////實現Filter類,自定義Filter
public class SessionFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("過濾器啟動");
    }
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        System.out.println("攔截到了訪問請求 ");
    }
    @Override
    public void destroy() {
        System.out.println("過濾器銷毀");
    }
}

/////web.xml中定義Filter
<filter>
   <filter-name>sessionFilter</filter-name>
   <filter-class>myfilter.SessionFilter</filter-class>
</filter>
<filter-mapping>
   <filter-name>sessionFilter</filter-name>
   <url-pattern>*</url-pattern>
</filter-mapping>

1、Filter在啟動時會被初始化,調用一次init方法,且只會初始化一次
2、按照XML定義順序進行攔截

Spring 攔截器

spring攔截器要實現的功能從名稱就看出,那就是攔截用戶的請求,功能相似于過濾器。那么它們有什么不同呢?

不管怎么說,把攔截器先運行起來。在Webconfig配置文件中提供了一個addInterceptors函數來完成注冊自定義攔截器,就這么簡單任性。

程序二:自定義攔截器

/////自定義Interceptor
@Component
public class SessionInterceptor implements HandlerInterceptor{

    public SessionInterceptor() {
        System.out.println("---SessionInterceptor---");
    }

    public boolean preHandle(HttpServletRequest request, 
HttpServletResponse response, Object handler)
            throws Exception {
        System.out.println("---preHandle----");
        return true;
    }

    public void postHandle(HttpServletRequest request, 
HttpServletResponse response, Object handler,
            ModelAndView modelAndView) throws Exception {
        System.out.println("---postHandle----");
    }

    public void afterCompletion(HttpServletRequest request, 
HttpServletResponse response, Object handler, Exception ex)
            throws Exception {
        System.out.println("---afterCompletion----");
    }

}

/////注冊sessionInterceptor
@Override
public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(sessionInterceptor).addPathPatterns("/user/*");
}

如果你以為Spring攔截器是仿照Filter來攔截URL那說明你太簡單了。Spring攔截器其實是利用了Aop的原理。正是因為如此,我們才能看到上面的preHander、postHander、afterCompltion。

  • preHander:被@RequestMapping注解的方法執行前調用

  • postHander:被@RequestMapping注解的方法執行后未返回ModelView之前調用

  • afterCompltion:方法執行完成后調用

preHahder如果返回false,則postHander不執行。
多個攔截器的執行順序與注冊順序相關

現在我們再來看Spring MVC的調用順序,就一目了然了。先通過自定義DispatcherServlet注解啟動配置類的方式啟動Spring + MVC。實際真正起分發作用的還是org.springframework.web.servlet.DispatcherServlet.doServiet()方法。


知識星球
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容