【小家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

相关文章
|
1月前
|
XML 前端开发 Java
【Spring】@RequestMapping、@RestController和Postman
【Spring】@RequestMapping、@RestController和Postman
26 2
【Spring】@RequestMapping、@RestController和Postman
|
1月前
|
前端开发 Java 应用服务中间件
【Spring】Spring MVC的项目准备和连接建立
【Spring】Spring MVC的项目准备和连接建立
57 2
|
2月前
|
缓存 前端开发 Java
【Java面试题汇总】Spring,SpringBoot,SpringMVC,Mybatis,JavaWeb篇(2023版)
Soring Boot的起步依赖、启动流程、自动装配、常用的注解、Spring MVC的执行流程、对MVC的理解、RestFull风格、为什么service层要写接口、MyBatis的缓存机制、$和#有什么区别、resultType和resultMap区别、cookie和session的区别是什么?session的工作原理
【Java面试题汇总】Spring,SpringBoot,SpringMVC,Mybatis,JavaWeb篇(2023版)
|
1月前
|
XML 前端开发 Java
Spring,SpringBoot和SpringMVC的关系以及区别 —— 超准确,可当面试题!!!也可供零基础学习
本文阐述了Spring、Spring Boot和Spring MVC的关系与区别,指出Spring是一个轻量级、一站式、模块化的应用程序开发框架,Spring MVC是Spring的一个子框架,专注于Web应用和网络接口开发,而Spring Boot则是对Spring的封装,用于简化Spring应用的开发。
136 0
Spring,SpringBoot和SpringMVC的关系以及区别 —— 超准确,可当面试题!!!也可供零基础学习
|
3月前
|
Java 数据库连接 Spring
后端框架入门超详细 三部曲 Spring 、SpringMVC、Mybatis、SSM框架整合案例 【爆肝整理五万字】
文章是关于Spring、SpringMVC、Mybatis三个后端框架的超详细入门教程,包括基础知识讲解、代码案例及SSM框架整合的实战应用,旨在帮助读者全面理解并掌握这些框架的使用。
后端框架入门超详细 三部曲 Spring 、SpringMVC、Mybatis、SSM框架整合案例 【爆肝整理五万字】
|
4月前
|
SQL Java 数据库连接
Spring问题之@RequestMapping注解的作用和使用方式是啥
Spring问题之@RequestMapping注解的作用和使用方式是啥
|
3月前
|
前端开发 Java Spring
Java 新手入门:Spring Boot 轻松整合 Spring 和 Spring MVC!
Java 新手入门:Spring Boot 轻松整合 Spring 和 Spring MVC!
66 0
|
4月前
|
前端开发 Java 应用服务中间件
Spring Boot 2.x 嵌入式 Servlet 容器
Spring Boot使用内嵌Tomcat,默认端口8080,可通过`application.properties`配置端口、上下文路径等。配置方式有两种:1) 直接在配置文件中添加`server.port`和`server.servlet.context-path`;2) 创建`WebServerFactoryCustomizer` Bean来自定义配置,如设置端口`factory.setPort(8083)`,这种方式优先级更高。
|
4月前
|
XML 前端开发 Java
Spring Boot与Spring MVC的区别和联系
Spring Boot与Spring MVC的区别和联系
|
3月前
|
开发框架 前端开发 .NET
ASP.NET MVC WebApi 接口返回 JOSN 日期格式化 date format
ASP.NET MVC WebApi 接口返回 JOSN 日期格式化 date format
49 0
下一篇
无影云桌面