【小家Spring】Spring MVC控制器中Handler的四种实现方式:Controller、HttpRequestHandler、Servlet、@RequestMapping

前言

曾几何时,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继续处理

一般Web应用服务器默认的Servlet名称是"default",因此DefaultServletHttpRequestHandler可以找到它。如果你所有的Web应用服务器的默认Servlet名称不是"default",则需要通过default-servlet-name属性显示指定:<mvc:default-servlet-handler default-servlet-name="xxx" />

如果你是基于注解驱动的呢?这么做即可:

@Configuration
@EnableWebMvc
public class WebMvcConfig extends WebMvcConfigurerAdapter {

    // 注册 一个默认的servlet处理器  让它处理静态资源
    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        DefaultServletHandlerConfigurer handlerConfigurer = new DefaultServletHandlerConfigurer(servletContext);
        // 这个Configurer只有一个enable方法,内部会 new DefaultServletHttpRequestHandler();
        handlerConfigurer.enable("default"); //默认值的servlet名字就是default
        super.configureDefaultServletHandling(configurer);
    }
}

这样就会把这个DefaultServletHttpRequestHandler这样子:

	protected SimpleUrlHandlerMapping buildHandlerMapping() {
		if (this.handler == null) {
			return null;
		}
		
		SimpleUrlHandlerMapping handlerMapping = new SimpleUrlHandlerMapping();
		handlerMapping.setUrlMap(Collections.singletonMap("/**", this.handler));
		handlerMapping.setOrder(Integer.MAX_VALUE);
		return handlerMapping;
	}

使用的SimpleUrlHandlerMapping把handler和url映射上。并且order的数学怒是最后一名。它最终使用的适配器是:HttpRequestHandlerAdapter去做适配~

// @since 3.0.4  可见它是一个HttpRequestHandler
public class DefaultServletHttpRequestHandler implements HttpRequestHandler, ServletContextAware {
}
方法2.采用<mvc:resources /> @since 3.0.4

<mvc:default-servlet-handler />将静态资源的处理经由Spring MVC框架交回Web应用服务器处理。 而<mvc:resources />更进一步,由Spring MVC框架自己处理静态资源,并添加一些有用的附加值功能。

首先,<mvc:resources />允许静态资源放在任何地方,如WEB-INF目录下、类路径下等,你甚至可以将JavaScript等静态文件打到JAR包中(为后续的webjar做好了充分的支持~)。通过location属性指定静态资源的位置,由于location属性是Resources类型,因此可以使用诸如"classpath:"等的资源前缀指定资源位置。

传统Web容器的静态资源只能放在Web容器的根路径下,<mvc:resources />完全打破了这个限制。

其次,<mvc:resources />依据当前著名的Page SpeedYSlow等浏览器优化原则对静态资源提供优化。你可以通过cacheSeconds属性指定静态资源在浏览器端的缓存时间,一般可将该时间设置为一年,以充分利用浏览器端的缓存。在输出静态资源时,会根据配置设置好响应报文头的Expires 和 Cache-Control值

这样在接收到静态资源的获取请求时,会检查请求头的Last-Modified值,如果静态资源没有发生变化,则直接返回303/304响应状态码,提示客户端使用浏览器缓存的数据,而非将静态资源的内容输出到客户端,以充分节省带宽,提高程序性能。

基于XML的使用示例:

<!-- 一次性可以写多个路径,里面都可议放置静态资源文件~   location表示资源文件的位置  mapping:表示请求URL-->
<mvc:resources location="/,classpath:/META-INF/publicResources/" mapping="/resources/**"/>

// 当然我们也可以写成多个,类似这样子
<mvc:resources location="/img/" mapping="/img/**"/>
<mvc:resources location="/js/" mapping="/js/**"/>
<mvc:resources location="/css/" mapping="/css/**"/>

以上配置将Web根路径"/"及类路径下 /META-INF/publicResources/ 的目录映射为/resources路径

它的核心处理类为:ResourceHttpRequestHandler,也是个HttpRequestHandlerResourcesBeanDefinitionParser是用于解析XML里的这个标签的~~~~

如果你是基于注解驱动的呢?这么做即可:

@Configuration
@EnableWebMvc
public class WebMvcConfig extends WebMvcConfigurerAdapter {
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/resource/**").addResourceLocations("/WEB-INF/static/"); // 支持Ant风格的路径匹配
    }
}

最终每个Mapping都是对应着一个HttpRequestHandler处理器的

	Map<String, HttpRequestHandler> urlMap = new LinkedHashMap<>();


// 这个HttpRequestHandler是这么生成的
// 使用的ResourceHttpRequestHandler  因为我们无法指定locationValues  所以这里是[] 空集合
// 个人感觉这是Spring的一个问题,后面有时间回去社区里反馈此问题~~~(我使用的Spring版本为5.1.x)
	protected ResourceHttpRequestHandler getRequestHandler() {
		ResourceHttpRequestHandler handler = new ResourceHttpRequestHandler();
		// 给设置好ResourceResolvers
		if (this.resourceChainRegistration != null) {
			handler.setResourceResolvers(this.resourceChainRegistration.getResourceResolvers());
			handler.setResourceTransformers(this.resourceChainRegistration.getResourceTransformers());
		}
		
		// location们  可以发现每个mapping相当于对应着所有的location的
		handler.setLocationValues(this.locationValues);
		// 设置浏览器cache相关内容 
		if (this.cacheControl != null) {
			handler.setCacheControl(this.cacheControl);
		}
		else if (this.cachePeriod != null) {
			handler.setCacheSeconds(this.cachePeriod);
		}
		return handler;
	}

看看它的源码

// @since 3.0.4 可以看到它实现的接口很多  是非常多的强大的一个资源处理器
// EmbeddedValueResolverAware:它能处理占位符~~~~
// 它的afterPropertiesSet()方法里做了非常多的事~~~~
public class ResourceHttpRequestHandler extends WebContentGenerator
		implements HttpRequestHandler, EmbeddedValueResolverAware, InitializingBean, CorsConfigurationSource {
		...
}

说明:HttpRequestHandler也是一个Handler,它能够处理请求。由HttpRequestHandlerAdapter进行调用HttpRequestHandlerhandleRequest(request, response)方法


以上是Spring MVC中最为重要的两个HttpRequestHandler,下面来看看关于远程调用的两个HttpRequestHandler

HttpInvokerServiceExporter

HttpInvoker是过去常用的Java同构系统之间方法调用实现方案。它通过HTTP通信即可实现两个Java系统之间的远程方法调用,使得系统之间的通信如同调用本地方法一般。

HttpInvoker和RMI同样使用JDK自带的序列化方式,但是HttpInvoker采用HTTP方式通信,这更容易配合防火墙、网闸的工作。

RMI:使用JRMP协议(基于TCP/IP),不允许穿透防火墙,使用JAVA系列化方式,使用于任何JAVA应用之间相互调用 Hessian:使用HTTP协议,允许穿透防火墙,使用自己的系列化方式,支持JAVA、C++、.Net等跨语言使用 Spring HTTP Invoker: 使用HTTP协议,允许穿透防火墙,使用JAVA系列化方式,但仅限于Spring应用之间使用,即调用者与被调用者都必须是使用Spring框架的应用。

服务端书写: 配置你需要暴露出来的服务端的接口:

@Configuration
@EnableWebMvc
public class WebMvcConfig extends WebMvcConfigurerAdapter {

    // 服务端:把HelloService暴露出去  服务端对接口必须有具体的实现~
    @Bean("/helloService") // 注意这里的BeanName请使用/路径的形式  (推荐使用这种方式,简单明了~~~)
    public HttpInvokerServiceExporter helloServiceExporter(HelloService helloService) {
        HttpInvokerServiceExporter exporter = new HttpInvokerServiceExporter();
        exporter.setService(helloService); // 真正的实现类  这个对服务端来说是必须的
        exporter.setServiceInterface(HelloService.class); // 必须设置一个接口~~~~
        return exporter;
    }
}

显然我们访问:http://localhost:8080/demo_war_war/helloService反馈如下:

不是404说明咱们的请求是通的,服务端存在这个URL的映射了。

另外一种方式:就是配置一个HttpRequestHandlerServlet如下:

    //@Autowired
    //private ServletContext servletContext;
    // 需要注意的是:在Spring的config里面,是不允许通过servletContext再向web容器内添加servlet的~~~~
    
    // 注册一个HttpRequestHandlerServlet 来专门处理client端的请求
    // 要求:此servletName必须是上面对应的Bean的BeanName~~~~
    //servletContext.addServlet("helloServiceExporter", new HttpRequestHandlerServlet()).addMapping("/helloService");

// 这里在SPI里完成Servlet的注册~~~
public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
    @Override
    protected void registerDispatcherServlet(ServletContext servletContext) {
        super.registerDispatcherServlet(servletContext);

        // 注册我们自己的Servlet 因为这个servlet在jar包里面  所以这里只能通过编程的方式注册~~~~
        // 请保证这个servlet的名字和BeanName相同~~  helloServiceExporter:为Bean的名字~~~
        servletContext.addServlet("helloServiceExporter", new HttpRequestHandlerServlet()).addMapping("/helloService");
        servletContext.addServlet("helloServiceExporter", new HttpRequestHandlerServlet()).addMapping("/helloService");
    }
}

这样就不要求BeanName必须是/开头咯~~~~

因为服务端需要提供HTTP请求服务,而且是基于Servlet的,所以服务端需要跑在如Tomcat这样的Servlet Web容器上 客户端书写: 写一个和服务端一样的接口(不需要实现类)配置上:

@Configuration
public class RootConfig {

    @Bean("helloServiceFactoryBean")
    public HttpInvokerProxyFactoryBean helloServiceFactoryBean() {
        HttpInvokerProxyFactoryBean factoryBean = new HttpInvokerProxyFactoryBean();
        factoryBean.setServiceInterface(HelloService.class);
        factoryBean.setServiceUrl("http://localhost:8080/demo_war_war/helloService");
        return factoryBean;
    }

}

单元测试:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {RootConfig.class, AsyncConfig.class})
public class TestSpringBean {

    @Autowired
    private HelloService helloService;

    @Test
    public void test1() {
        System.out.println(helloService); // HTTP invoker proxy for service URL [http://localhost:8080/demo_war_war/helloService]
        System.out.println(helloService.getClass()); //class com.sun.proxy.$Proxy30
        System.out.println(helloService.hello()); //service hello
        System.out.println("结束~");
    }

}

由此可见,我们的Client就这样就访问到服务端得接口。不用再自己去构造Http请求了,就像本地方法调用一样,使用起来确实非常方便。

Tips: 该种方式暴露请求,和请求方式。建议把Bean的注册都放在跟容器里面,而不是web子容器里。(若放在web子容器里,使用HttpRequestHandlerServlet这种方式的时候可能会找到Bean,亲测~)

HessianServiceExporter

使用方式同上。只是使用的是HessianServiceExporterHessianProxyFactoryBean。它也可以使用HttpRequestHandlerServlet做servlet化处理。当然还是推荐BeanName使用"/"的方式去表示~

另外,若你想手动注册web组件,还可以自己实现一个SCI的方式:

@HandlesTypes(value={XXX.class})
public class MyServletContainerInitializer implements ServletContainerInitializer {
	@Override
	public void onStartup(Set<Class<?>> arg0, ServletContext sc) throws ServletException {
		// 在这里里面完成所有的注册工作~~~~
	}
}

目前,Spring支持三种远程技术:

  1. 远程方法调用(RMI):RmiProxyFactoryBean 和 RmiServiceExporter
  2. Spring的HTTP调用器: HttpInvokerProxyFactoryBean和HttpInvokerServiceExporter
  3. Hessian:HessianProxyFactoryBean 和 HessianServiceExporter(使用Caucho提供的基于HTTP的轻量级二进制协议)

附:URL与Servlet的路径匹配规则: 当一个url与多个servlet的匹配规则可以匹配时,则按照:“ 精确路径 > 最长路径>扩展名”这样的优先级匹配到对应的servlet。 所以一般来说我们的DispatcherServlet一般都是最后被匹配上的(若有多个Servlet匹配的情况下)

自定义一个HttpRequestHandler的实现

它的使用方式和Servlet的使用方式特别的像:

@Controller("/demoHttpRequestHandler")
public class DemoHttpRequestHandler implements HttpRequestHandler {

    @Override
    public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("this my DemoHttpRequestHandler");
        response.getWriter().write("this my DemoHttpRequestHandler");
    }
}

就这样访问http://localhost:8080/demo_war_war/demoHttpRequestHandler,控制台和浏览器都会有对应的输出。看看Serevlet的使用:

@WebServlet("/myServlet")
public class MyServlet extends HttpServlet {

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        super.service(req, resp);
    }
}

使用方式几乎如出一辙。所以上面才会说:其类似于一个简单的Servlet

Servlet方式

Servlet是我们很熟悉的一个类。Spring MVC也是对这种实现方式提供了支持,也把它能够当作一个Spring MVC的Bean,作为一个Handler来实现的~~

@Controller("/servletController")
public class ServletController extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("this my servlet controller");
    }
}

小细节:即使你使用了@EnableWebMvc,Spring MVC默认也不会给你注册SimpleServletHandlerAdapter这个适配器(其余三个都给注册了),因此此处我们自己注册一下即可。

@Configuration
@EnableWebMvc
public class WebMvcConfig extends WebMvcConfigurerAdapter {
	// 让支持Servlet这种Handler的方式~~你 SpringMVC默认是不予支持的
    @Bean
    public SimpleServletHandlerAdapter simpleServletHandlerAdapter() {
        return new SimpleServletHandlerAdapter();
    }	
}

这样子是也是能够正常访问的,因为它就是个Handler了。 从Spring MVC的意图中我们也可以看出,Spring并不推荐我们再使用源生的Servlet来处理请求了~~~

@RequestMapping注解方式

是当下最为广泛使用的方式。 这种方式此处就不做介绍了,不介绍并不是它不重要,反而是它太重要了此处篇幅不够,比如它的数据绑定可以完全屏蔽Servlet源生的API,为后续的WebFlux做了充分的准备~后面还有大篇幅讲解它相关的内容

小知识

  1. Spring2.5之前,我们都是通过实现Controller接口或其实现来定义我们的处理器类。显然现在已经不推荐这么做了
  2. Spring2.5引入注解式处理器支持,通过@Controller 和 @RequestMapping注解定义我们的处理器类。并且有一批注解都是这个时候出来的: 1. @RequestMapping:请求到处理器功能方法的映射规则; 2. @RequestParam:请求参数到处理器功能处理方法的方法参数上的绑定; 3. @ModelAttribute:请求参数到命令对象的绑定; 4. @SessionAttributes:用于声明session级别存储的属性,放置在处理器类上,通常列出模型属性(如@ModelAttribute)对应的名称,则这些属性会透明的保存到session中; 5. @InitBinder:自定义数据绑定注册支持,用于将请求参数转换到命令对象属性的对应类型;
  3. Spring3.0引入RESTful架构风格支持(通过@PathVariable注解和一些其他特性支持),且又引入了更多的注解支持: 1. @CookieValue:cookie数据到处理器功能处理方法的方法参数上的绑定; 2. @RequestHeader:请求头(header)数据到处理器功能处理方法的方法参数上的绑定; 3. @RequestBody:请求的body体的绑定(通过HttpMessageConverter进行类型转换); 4. @ResponseBody:处理器功能处理方法的返回值作为响应体(通过HttpMessageConverter进行类型转换); 5. @ResponseStatus:定义处理器功能处理方法/异常处理器返回的状态码和原因; 6. @ExceptionHandler:注解式声明异常处理器; 7. @PathVariable:请求URI中的模板变量部分到处理器功能处理方法的方法参数上的绑定,从而支持RESTful架构风格的URI;
  4. Spring3.1使用新的HandlerMappingHandlerAdapter来支持@Contoller@RequestMapping注解处理器。 1. 处理器映射RequestMappingHandlerMapping 和 处理器适配器RequestMappingHandlerAdapter组合 2. 对应代替了Spring2.5开始有的DefaultAnnotationHandlerMappingAnnotationMethodHandlerAdapter

命令对象/模式:来自客户端的请求传入一个对象,从而使你可用不同的请求对客户进行参数化。用于“行为请求者”与“行为实现者”解耦,可实现二者之间的松耦合,以便适应变化。分离变化与不变的因素。一般可以实现命令的执行和撤销操作。 比如:遥控器给灯可以发送命令:开灯

总结

在使用Spring MVC的开发过程中,Handler(就是Controller)是我们需要手动开发的主要内容(其余的都是Spring MVC自动去处理的,开发者基本不用关心~),注解的配置方式比较固定,可以限定请求方式,请求映射到方法级,基本可以满足我们的日常需求。

但是如果知道这些controller的模式,比如UrlFilenameViewController这种,可以不用开发或者非常少量开发的情况下,极其快速的定位到handler到页面的映射关系,也是大大的提升了我们的效率有木有~

附:

Spring MVC中对静态资源的访问

  1. 当静态资源放在webapp下面的时候,可直接通过浏览器访问,不需要配置映射,安全性略低,对应的访问效率就略高

但是静态资源若很多,访问频率很高的话,强烈建议放在静态服务器或者CDN上,不要放在tomcat里,这不是它擅长的

  1. WEB-INF是Java的WEB应用的安全目录。所谓安全就是客户端无法直接访问,只有服务端可以访问的目录(所以理论上必须经过controller)

对于第一点这里需要强调一下:其实这么说是有问题的。因为我们一般会把DispatcherServlet映射为 /从而拦截所有的请求(包括静态资源的请求),所以如果直接访问就找不到Handler还是会报404的。后面会解释原因~

那么现在问题就来了,Spring MVC下怎么让访问WEB-INF下面的静态资源呢? 比如现在我有如下静态资源:

通过浏览器直接访问肯定是会404的,但是因为它是html文件,我们也不能通过controller直接转发,那肿么办呢?

如果我们之前的Spring MVC项目是基于xml的,相信很多人都看到过如下的配置项:

<mvc:default-servlet-handler/>
...
<mvc:resources mapping="/js/**" location="/js/"/>
<mvc:resources mapping="/css/**" location="/css/"/>
<mvc:resources mapping="/html/**" location="/html/"/>

// 备注两种只需要配其一,其中<mvc:resources />是被推荐的方式

那么现在我们是java代码的方式,怎么弄呢?

DefaultServlet方式:DefaultServletHttpRequestHandler

对应xml的<mvc:default-servlet-handler/>方式。 此时会注册一个默认的Handler:DefaultServletHttpRequestHandler,这个Handler是用来处理静态文件的。(tomcat等容器里都一个名字为defaultServlet的Servlet来默认处理这些,当然默认名称各个容器都不一样,你可以自己指定)

当我们的请求倒带DispatcherServelt,当并没有找到合适的Handler来处理请求时,就会交给DefaultServletHttpRequestHandler来处理。

注意注意注意:这里的静态资源是放置在web根目录下,而非WEB-INF下,若在该目录下只能下面方案解决~

简单的举个例子描述下这个情况: 在webroot目录下有一个图片:1.png。我们知道的是:Servlet规范中web根目录(webapp目录)下的文件我们是可以直接访问的(不需要经过Servlet处理)。 但是但是但是由于DispatcherServlet配置了映射路径是:/ ,它几乎把所有的请求都拦截了,从而导致1.png 访问不到。这时注册一个DefaultServletHttpRequestHandler就可以解决这个问题。

DispatcherServlet破坏了Servlet的这个特性规范(根目录下的文件可以直接访问),DefaultServletHttpRequestHandler是帮助回归这个特性的。可谓也没啥损失(只是Spring MVC默认并没有开启哦~~)

因此若要开启此特性,我们只需要这么做即可:

@Configuration
@EnableWebMvc
public class WebMvcConfig extends WebMvcConfigurerAdapter {
    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
        //configurer.enable("default");
    }
}

这样非WEB-INF下的静态资源就可以正常反问啦~

ResourceHandler方式:ResourceHttpRequestHandler

这种对应的是XML中的<mvc:resources />方式。 我们Java代码的方式一般都这么做:

@Configuration
@EnableWebMvc
public class WebMvcConfig extends WebMvcConfigurerAdapter {
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/resource/**").addResourceLocations("/WEB-INF/static/");
        // 备注这里addResourceLocations(“file:D:/”)这样都是支持的~~~~~
    }
}

这样只要我们访问路径中是匹配/resource/**的,那就能够访问了。比如上面截图中的1.html我们这样访问:http://localhost:8080/demo_war_war/resource/1.html就能正常访问到了。(相当于路径中给加个/resource/

那它的基本原理是什么呢?

它的原理也还行,就是向容器注册了一个ResourceHttpRequestHandler,它是一个HttpRequestHandler。关于HttpRequestHandler前面文章是有重点讲述的,具体参考: 【小家Spring】Spring MVC控制器中Handler的四种实现方式:Controller、HttpRequestHandler、Servlet、@RequestMapping

DefaultServletHttpRequestHandler它也是一个HttpRequestHandler

//*有什么区别?

/会拦截除了jsp以外的所有url,/* 会拦截所有url,包括jsp。

例如:在webroot下面有一个test.jsp,当DispatcherServlet 配置映射/ 时,浏览器输入:http://localhost:8080/test.jsp 这个jsp是可以直接访问的并且不经过DispatcherServlet ;而当DispatcherServlet 配置映射/* 时,这个请求就会被DispatcherServlet 拦截。

Spring Boot中静态资源的访问

它就比Spring稍微简单点,因为Boot已经做好了很多事。 在 Spring Boot 中,默认情况下,一共有5个位置可以放静态资源,五个路径分别是如下5个:

  1. classpath:/META-INF/resources/
  2. classpath:/resources/
  3. classpath:/static/
  4. classpath:/public/
  5. /

前四个目录好理解,分别对应了resources目录下不同的目录,第5个 / 是啥意思呢?稍微解释下:在 Spring Boot 项目中,默认是没有 webapp 这个目录的,当然我们也可以自己添加(例如在需要使用JSP的时候),这里第5个 / 其实就是表示 webapp 目录中的静态资源也不被拦截。如果同一个文件分别出现在五个目录下,那么优先级也是按照上面列出的顺序。

所以在SpringBoot中问问静态资源默认情况下我们并不需要做什么。 具体原理参考类:ResourceProperties,它定义了这5个路径以及顺序~ WebMvcProperties.staticPathPattern属性值定义了访问的url的pattern。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Super 前端

JS常用代码块

版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。

41220
来自专栏技术从心

JAVAAPI中SortedMap解释

A Map进一步提供其键上的总排序 。地图根据其键的natural ordering或通过在分类地图创建时提供的Comparator进行排序。当迭代排序的地图的...

15820
来自专栏Super 前端

jquery $(document).ready()与window.onload的区别

版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。

10220
来自专栏AI科技大本营的专栏

如何用Python编写一个Lisp解释器

这篇文章有两个目的:一是展示如何实现一个计算机语言的解释器,二是演示如何使用 Python 3 构造 Lisp 的一种方言 Schema,作者把自己的这个语言解...

13940
来自专栏Java技术栈

Java 12 骚操作, String居然还能这样玩!

栈长之前在Java技术栈微信公众号分享过《Java 11 已发布,String 还能这样玩!》这篇文章,介绍了 Java 11 的 String 新玩法,让大家...

10330
来自专栏Super 前端

JavaScript的工作原理:引擎,运行时和调用堆栈的概述

随着 JavaScript 变得越来越流行,各团队正在多个领域栈中使用它们,其中包括 — 前端,后端,混合应用,嵌入式等等。

8420
来自专栏Java后端技术栈cwnait

再也不敢使用集合默认初始化值了

集合初始化通常进行分配容量、设定特定参数等相关工作。我们以使用频率相对较高的ArrayList和HashMap为例,简要说明初始化的相关工作,并解释为什么在任何...

11530
来自专栏女程序员的日常_Lin

Iterator 、Generator(一)

调用Generator函数,返回一个遍历器对象,代表Generator函数的内部指针。以后,每次调用遍历器对象的next方法,就会返回一个有着value和don...

7930
来自专栏Java研发军团

Java性能优化的50个细节,我必须分享给你!

来源:blog.csdn.net/dongnan591172113/article/details/51790428

7520
来自专栏Super 前端

压缩JS,提高代码执行速度

版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。

12820

扫码关注云+社区

领取腾讯云代金券

年度创作总结 领取年终奖励