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

本文涉及的产品
云防火墙,500元 1000GB
简介: 【小家Spring】Spring MVC控制器中Handler的四种实现方式:Controller、HttpRequestHandler、Servlet、@RequestMapping(中)

一般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 Speed、YSlow等浏览器优化原则对静态资源提供优化。你可以通过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,也是个HttpRequestHandler。ResourcesBeanDefinitionParser是用于解析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进行调用HttpRequestHandler的handleRequest(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反馈如下:


image.png


不是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


使用方式同上。只是使用的是HessianServiceExporter和HessianProxyFactoryBean。它也可以使用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

相关文章
|
19天前
|
SQL Java 数据库连接
对Spring、SpringMVC、MyBatis框架的介绍与解释
Spring 框架提供了全面的基础设施支持,Spring MVC 专注于 Web 层的开发,而 MyBatis 则是一个高效的持久层框架。这三个框架结合使用,可以显著提升 Java 企业级应用的开发效率和质量。通过理解它们的核心特性和使用方法,开发者可以更好地构建和维护复杂的应用程序。
110 29
|
2月前
|
设计模式 前端开发 Java
步步深入SpringMvc DispatcherServlet源码掌握springmvc全流程原理
通过对 `DispatcherServlet`源码的深入剖析,我们了解了SpringMVC请求处理的全流程。`DispatcherServlet`作为前端控制器,负责请求的接收和分发,处理器映射和适配负责将请求分派到具体的处理器方法,视图解析器负责生成和渲染视图。理解这些核心组件及其交互原理,有助于开发者更好地使用和扩展SpringMVC框架。
69 4
|
3月前
|
前端开发 Java 开发者
Spring MVC中的请求映射:@RequestMapping注解深度解析
在Spring MVC框架中,`@RequestMapping`注解是实现请求映射的关键,它将HTTP请求映射到相应的处理器方法上。本文将深入探讨`@RequestMapping`注解的工作原理、使用方法以及最佳实践,为开发者提供一份详尽的技术干货。
234 2
|
3月前
|
前端开发 Java Spring
Spring MVC核心:深入理解@RequestMapping注解
在Spring MVC框架中,`@RequestMapping`注解是实现请求映射的核心,它将HTTP请求映射到控制器的处理方法上。本文将深入探讨`@RequestMapping`注解的各个方面,包括其注解的使用方法、如何与Spring MVC的其他组件协同工作,以及在实际开发中的应用案例。
67 4
|
4月前
|
JSON 前端开发 Java
SSM:SpringMVC
本文介绍了SpringMVC的依赖配置、请求参数处理、注解开发、JSON处理、拦截器、文件上传下载以及相关注意事项。首先,需要在`pom.xml`中添加必要的依赖,包括Servlet、JSTL、Spring Web MVC等。接着,在`web.xml`中配置DispatcherServlet,并设置Spring MVC的相关配置,如组件扫描、默认Servlet处理器等。然后,通过`@RequestMapping`等注解处理请求参数,使用`@ResponseBody`返回JSON数据。此外,还介绍了如何创建和配置拦截器、文件上传下载的功能,并强调了JSP文件的放置位置,避免404错误。
|
4月前
|
XML 前端开发 Java
【Spring】@RequestMapping、@RestController和Postman
【Spring】@RequestMapping、@RestController和Postman
52 2
【Spring】@RequestMapping、@RestController和Postman
|
4月前
|
前端开发 Java 应用服务中间件
【Spring】Spring MVC的项目准备和连接建立
【Spring】Spring MVC的项目准备和连接建立
79 2
|
6月前
|
开发框架 前端开发 .NET
ASP.NET MVC WebApi 接口返回 JOSN 日期格式化 date format
ASP.NET MVC WebApi 接口返回 JOSN 日期格式化 date format
85 0
|
9月前
|
开发框架 前端开发 JavaScript
JavaScript云LIS系统源码ASP.NET CORE 3.1 MVC + SQLserver + Redis医院实验室信息系统源码 医院云LIS系统源码
实验室信息系统(Laboratory Information System,缩写LIS)是一类用来处理实验室过程信息的软件,云LIS系统围绕临床,云LIS系统将与云HIS系统建立起高度的业务整合,以体现“以病人为中心”的设计理念,优化就诊流程,方便患者就医。
97 0
|
9月前
|
开发框架 前端开发 .NET
ASP.NET CORE 3.1 MVC“指定的网络名不再可用\企图在不存在的网络连接上进行操作”的问题解决过程
ASP.NET CORE 3.1 MVC“指定的网络名不再可用\企图在不存在的网络连接上进行操作”的问题解决过程
246 0