前言
曾几何时,Apache旗下的项目: struts框架一度是MVC设计模式的主流框架。但后来随着Spring MVC3.0的发力,让它可议支持使用注解的方式进行快速开发一个Handler,并且有优秀的对静态资源的处理。
它基于无状态Bean的方法级别Handler设计,当然还有与Spring Framework天然无缝集成等优势,迅速吞并了 struts的大部分市场份额
另外,在2013年 struts爆出了安全漏洞问题,算是压死它的最后一根稻草。如今Spring家族产品大行其道,基于MVC的web层面框架:Spring MVC几乎已经成为了现实中的开发标准
什么是Spring MVC
在MVC设计模式之前,很多应用程序的问题在于处理业务数据的对象和显示业务数据的视图之间存在紧密耦合,通常,更新业务对象的命令都是从视图本身发起的,使视图对任何业务对象更改都有高度敏感性。而且,当多个视图依赖于同一个业务对象时是没有灵活性的。
SpringMVC是一种基于Java,实现了Web MVC设计模式,请求驱动类型的轻量级Web框架,即使用了MVC架构模式的思想,将Web层进行职责解耦。基于请求驱动指的就是使用请求-响应模型,框架的目的就是帮助我们简化开发,SpringMVC也是要简化我们日常Web开发。
Spring MVC的Handler的书写方式
作为新时代(Spring3.0以后)的程序员,采用Spring MVC框架书写控制器是非常简单的。固定的模式套路:准备一个XXXController类,然后在类上写方法,标注对应的@RequestMapping注解,这个方法就成了一个Handler,非常的方便简单,开发效率也是非常的高~~
而今天本文介绍不仅仅是这种方式,还有采用Spring MVC我们还可以使用其它的方式进行开发一个Handler来处理请求:*或许会让你有种大开眼界效果~~~~*
Controller接口
org.springframework.web.servlet.mvc.Controller是控制器接口,此处只有一个方法handleRequest,用于进行请求的功能处理,处理完请求后返回ModelAndView(Model模型数据部分 和 View视图部分)。
// @since 第一版Spring MVC就有了 所以这个接口是非常古老的接口~~~也是Spring MVC最早期的实现方式 @FunctionalInterface public interface Controller { @Nullable ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception; }
Demo示例如下:
@org.springframework.stereotype.Controller("/democontroller") // 注意此处需要以/开头,表示使用BeanNameURLHandlerMapping的方式处理 public class DemoController implements Controller { @Override public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception { System.out.println("this my demo controller~"); return new ModelAndView("index"); } }
访问:http://localhost:8080/demo_war_war/democontroller,控制台有输出:
this my demo controller~
显然该请求已经被我们的这个Handler处理了。
怎么样实现类似@ResponseBody的功能
题意就是想实现直接向body里写数据,而不是返回一个页面。
如果想直接在处理器/控制器里使用response向客户端写回数据,可以通过返回null来告诉DispatcherServlet我们已经写出响应了,不需要它进行视图解析
看下面这个例子 就是直接向浏览器写东西:
@org.springframework.stereotype.Controller("/democontroller") // 注意此处需要以/开头,表示使用BeanNameURLHandlerMapping的方式处理 public class DemoController implements Controller { @Override public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception { System.out.println("this my demo controller~"); response.getWriter().write("this my demo controller from body"); return null; // 返回null告诉视图渲染 直接把body里面的内容输出浏览器即可 } }
访问,控制台输出:
this my demo controller~
浏览器看到:
其实Spring默认提供了一些Controller接口的实现类以方便我们直接使用:
AbstractController
它对某些请求方式做了些特殊处理,提高了效率
// 关于WebContentGenerator 这里暂时略过 // AbstractController继承了org.springframework.web.servlet.support.WebContentGenerator抽象类。提供了针对http请求的设定 public abstract class AbstractController extends WebContentGenerator implements Controller { ... @Override @Nullable public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception { // 如果请求是OPTIONS请求 那就直接return null了 它一般用于跨域 所以设置上Allow这个请求头 //getAllowHeader()方法在WebContentGenerator里 if (HttpMethod.OPTIONS.matches(request.getMethod())) { response.setHeader("Allow", getAllowHeader()); return null; } // 指定supportedMethods后,看看这个request是否合法 checkRequest(request); // 处理response的cache缓存和缓存时间等等 prepareResponse(response); // Execute handleRequestInternal in synchronized block if required. // 如果有需要,会给这个请求上锁~~~~在锁内执行 if (this.synchronizeOnSession) { HttpSession session = request.getSession(false); if (session != null) { Object mutex = WebUtils.getSessionMutex(session); synchronized (mutex) { return handleRequestInternal(request, response); } } } return handleRequestInternal(request, response); } // 子类实现这个抽象方法即可~~~~~ @Nullable protected abstract ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception; }
由于有这个抽象实现,增加的能力也更多了。比如可以配置supportedMethods这个属性,让它只处理固定的一些请求方式等等,因此并不推荐直接实现接口Controller
ServletWrappingController
这是一个与Servlet相关的控制器,还有一个与Servlet相关的控制器是ServletForwardingController。
ServletWrappingController则是将当前应用中的某个 Servlet直接包装为一个Controller,所有到ServletWrappingController的请求实际上是由它内部所包装的这个Servlet来处理的。
它内部包装了servlet,对外并不公开,相当于屏蔽了servlet的效果。
// @since 1.1.1 实现了接口InitializingBean public class ServletWrappingController extends AbstractController implements BeanNameAware, InitializingBean, DisposableBean { ... @Override public void afterPropertiesSet() throws Exception { // 必须制定它关联的是哪个Servlet if (this.servletClass == null) { throw new IllegalArgumentException("'servletClass' is required"); } // 如果没有指定servlet的名字,就用beanName作为名字~ if (this.servletName == null) { this.servletName = this.beanName; } // 对servlet进行init方法 初始化 this.servletInstance = ReflectionUtils.accessibleConstructor(this.servletClass).newInstance(); this.servletInstance.init(new DelegatingServletConfig()); } // 最终请求是交给了这个servlet去真正处理的~~~~~ @Override protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception { Assert.state(this.servletInstance != null, "No Servlet instance"); this.servletInstance.service(request, response); return null; } }
ServletForwardingController:servlet转发控制器
将拦截的请求交由某个servlet来处理。
和ServletWrappingController类似,它也是一个Servlet相关的controller,他们都实现将HTTP请求适配到一个已存的Servlet实现。
它和请求包含和请求转发有关:
rd.include(request, response); rd.forward(request, response);
ParameterizableViewController
可参数化视图控制器(ParameterizableViewController),可参数化视图控制器只是简单的返回配置的视图名。
这个controller可以选择直接将一个request请求到JSP页面。这样做的好处就是不用向客户端暴露具体的视图技术而只是给出了具体的controller URL,而具体的视图则由视图解析器来决定
public class ParameterizableViewController extends AbstractController { // 由此课件,默认只支持get和Head方法 public ParameterizableViewController() { super(false); setSupportedMethods(HttpMethod.GET.name(), HttpMethod.HEAD.name()); } ... // 支持redirect:这样的前缀 @Nullable public String getViewName() { if (this.view instanceof String) { String viewName = (String) this.view; if (getStatusCode() != null && getStatusCode().is3xxRedirection()) { return viewName.startsWith("redirect:") ? viewName : "redirect:" + viewName; } else { return viewName; } } return null; } ... }
像这种实现类,一般都是运用在基于XML的配置上(当然你也可以使用@Bean配置)
<bean name="/index.action" class="org.springframework.web.servlet.mvc.ParameterizableViewController"> <property name="viewName" value="/index.jsp"/> </bean>
这样子这个请求:/index.action就直接被定位到/index.jsp这个页面里了~~~页面跳转非常方便 不用自己写Controller了~
UrlFilenameViewController
使用该控制器与ParameterizableViewController控制器相比可以省去实视图名的配置,直接通过url解析
例如访问的是login.do,那么视图名就是login。经常把它配置为默认的Handler
<bean id="viewMappings" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping" > <!-- 默认处理器 --> <property name="defaultHandler"> <bean name="index" class="org.springframework.web.servlet.mvc.UrlFilenameViewController" /> </property> </bean>
该控制器直接跳转到一个页面,该控制器根据请求的url,解析出视图名,省去了视图名的配置。当然它也可议指定前缀与后缀,如下的配置
@Bean("/*") //"/*"会把它注册为一个默认的handler~ public UrlFilenameViewController urlFilenameViewController() { UrlFilenameViewController controller = new UrlFilenameViewController(); controller.setPrefix("/api/v1/"); controller.setSuffix(".do"); return controller; }
因为这里把它设定为了默认的处理器,所以任何404的请求都会到它这里来,交给它处理。例如我访问:
/democontroller22,因为我配置了前缀后缀,所以最终会到视图/api/v1/democontroller22.do里去
它处理请求的方法handleRequestInternal都在父类AbstractUrlViewController中实现~
HttpRequestHandler接口
HttpRequestHandler
用于处理Http requests
,其类似于一个简单的Servlet,只有一个handlerRequest
方法,其处理逻辑随子类的实现不同而不同。
// @since 2.0 它是Spring2.0后才出来的 // 用于处理HTTP请求的组件的纯处理程序接口,类似于`servlet` @FunctionalInterface public interface HttpRequestHandler { // Process the given request, generating a response // 处理这个request 生成一个response void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException; }
看看它的继承树:
说明:caucho是个很厉害的公司。https://caucho.com/ 大名鼎鼎的resin容器就是它公司的作品。相较于tomcat和resin,他们各有优劣
hessian也是这个公司的产品,在业内也是非常的有名
DefaultServletHttpRequestHandler和ResourceHttpRequestHandler
说到这个类,它和Spring MVC处理静态资源有非常大的关系。
背景:如果将DispatcherServlet请求映射配置为"/",则Spring MVC将捕获Web容器所有的请求,包括静态资源的请求,Spring MVC会将它们当成一个普通请求处理,因此找不到对应处理器将导致错误。
优雅REST风格的资源URL不希望带 .html 或 .do 等后缀.由于早期的Spring MVC不能很好地处理静态资源,所以在web.xml中配置DispatcherServlet的请求映射,往往使用 *.do 、 *.xhtml等方式。
REST是Spring3.0最重要的功能之一,所以Spring团队很看重静态资源处理这项任务,给出了堪称经典的两
种解决方案:
方法1.采用<mvc:default-servlet-handler /> @since 3.0.4
若是基于XML的方式,我们会配置一个
<mvc:default-servlet-handler />
来专门处理静态资源文件。它其实就是向MVC的容器内注入了一个DefaultServletHttpRequestHandler实例,它会像一个检查员,对进入DispatcherServlet的URL进行筛查,如果发现是静态资源的请求,就将该请求转由Web应用服务器默认的Servlet处理,如果不是静态资源的请求,才由DispatcherServlet继续处理