SpringBoot整合DWR-3.0.2-RELEASE版本,以及解决项目在开发环境及其外置Tomcat运行正常,独立JAR形式内置Tomcat运行异常的问题

填坑背景

由于最近在进行本公司一个老产品的技术升级改造工作,在原产品中使用了DWR,并且涉及到的代码量比较大,所以在本次使用SpringBoot重构时选择保留原有DWR相关的技术代码。自然就需要做SpringBoot和DWR的整合工作。本来一切无恙,很快就完成了这一块的技术改造工作,并且开发环境下一切运行良好。本以为改造工作就此结束,谁料想,在开发环境及其外置Tomcat运行良好的项目,打包成JAR包运行时,出问题了,DWR无法正常初始化及其调用,报错信息如下:
在这里插入图片描述

问题溯源

当遇到上面这个问题的时候,一开始还是比较懵的,怀疑是自己打包哪里出了问题,于是又反反复复折腾了好久,无果!都是一样的结果摆在眼前,那就是整合后的工程在开发环境下运行良好,打成WAR包部署到外置Tomcat下也运行正常,唯独使用内置Tomcat采用JAR的形式无法运行。
反反复复,从代码缺陷,运行环境,JDK版本、Tomcat版本、SpringBoot版本各方面去试图排查解决,结果都是一无所获。最终想到了找度娘,找谷歌,欲哭无泪,都是技术整合代码一箩筐,遇到该问题的较多,但实际解决的没有一个,于是在网上看到的很多都是建议直接部署到外置Tomcat下运行,这就完事了吗,问题是我们的项目有技术要求,不可以这么玩,那不是废了么,无奈只能考虑从源码入手,由于从2016年DWR 3.0.2-RELEASE发布至今基本后期维护较少,好在开源项目可以从源码入手,终于找到了该问题的根源所在。
由于DWR最后一个发布版本发布之时,SpringBoot并没有像现在这么大范围的应用起来,自然源码中有点缺陷也不足为奇。经过一翻阅读,最终定位到问题的根源所在尽然是由于类加载器不同而导致的,我们都知道SpringBoot项目中加载资源的方式如果你使用的是Jar方式运行,那么资源的读取都是无法使用文件路径再去定位的,都是要采用流的方式读取。
经过排查,在开发环境和外置Tomcat部署运行是,使用的类加载器主要为:
sun.misc.Launcher和TomcatEmbeddedWebappClassLoader
而采用SpringBoot的独立JAR方式运行时,使用的类加载器为
org.springframework.boot.loader.LaunchedURLClassLoader
既然找到了问题的根源所在,那就可以愉快的填坑了。

填坑步骤

一、示例代码结构

示例代码结构如下图所示,其中红色框起来的地方为整合的核心代码,从示例代码可以看出来整合中没有相关的配置文件,即以往DWR框架相关的配置文件如dwr.xml都是不需要的,我们都采用Java代码去实现这些配置工作。
在这里插入图片描述

二、示例代码说明

采用配置方式整合SpringBoot和DWR时,主要包括三个核心步骤,即:DWR框架配置、后端服务代码注册、DWR框架初始化三个环节。其中DwrEngineConfig.java主要罗列了DWR框架常用的一些配置,实际项目中可以根据需要进行配置使用,代码如下(注意:此处罗列了可能用到的相关配置,很多目前都是赋给的初始值,实际根据项目需要只配置其中个别配置项即可):
DWR框架配置示例代码如下:

1、框架配置代码编写

package com.sword.dwrdemo.config;

import org.directwebremoting.servlet.DwrListener;
import org.directwebremoting.servlet.DwrServlet;
import org.springframework.boot.web.servlet.ServletListenerRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.HashMap;
import java.util.Map;

/**
 * @Description: SpringBoot整合Dwr框架核心配置类
 * @Author 刘剑涛
 * @Date 2021-02-27 00:50
 */
@Configuration
public class DwrEngineConfig {

    @Bean
    public ServletRegistrationBean dwr() {
        ServletRegistrationBean servlet = new ServletRegistrationBean(new DwrServlet(), "/dwr/*");
        Map<String, String> initParam = new HashMap<>();

        /**===============以下列举所有可配置参数,根据需要选择配置===============*/

        // 1.是否支持JSONP,设置为true则支持JSONP。默认:false
        initParam.put("jsonpEnabled", "false");
        // 2.是否让DWR工作在Safari 1.x,设置为true则支持Safari 1.x,会稍微降低安全性。默认:false
        initParam.put("allowGetForSafariButMakeForgeryEasier", "false");
        // 3.是否支持跨域,设置为false则支持跨域请求。默认:true
        initParam.put("crossDomainSessionSecurity", "false");
        // 4.是否启用script标签的远程访问,设为true启用。默认:true
        initParam.put("allowScriptTagRemoting", "true");
        // 5.是否启用Debug模式,true标识启用。默认:false
        initParam.put("debug", "true");
        // 6.设置scriptSessions的超时时间。默认:1800000(表示30分钟)
        initParam.put("scriptSessionTimeout", "1800000");
        // 7.一次批量(batch)允许最大的调用数量。(帮助保护Dos攻击)。默认:20
        initParam.put("maxCallCount", "20");
        // 8.是否启用轮询和长连接,设置为true标识启用(2.0 RC3版本)。默认:false
        initParam.put("activeReverseAjaxEnabled", "true");
        // 9.是否启用轮询和长连接,设置为true标识启用(2.0 RC1版本)。默认:false,设置成true能增加服务器的加载能力,尽管DWR有保护服务器过载的机制。
        initParam.put("pollAndCometEnabled", "true");
        // 10.等待线程的最大数量。默认:100
        initParam.put("maxWaitingThreads", "100");
        // 11.每秒最大的访问数量。默认:40
        initParam.put("maxHitsPerSecond", "40");
        // 12.dwr服务端类的页面引用方式。默认:interface
        initParam.put("generateDtoClasses", "interface");
        // 13.默认值支持最后修改,这样就允许服务器端对客户端请求较少资源。设置为true就能屏蔽支持。
        initParam.put("ignoreLastModified", "false");
        // 14.默认来说逆向Ajax对同一页面不同查询参数将会认为同一个页面。默认:false
        initParam.put("normalizeIncludesQueryString", "false");
        // 15.默认来说逆向Ajax对同一页面不同session id将会认为同一个页面。默认:false
        initParam.put("normalizeIncludesSessionID", "false");
        // 16.DWR通过检查文档和提取当前session ID支持URL重写。一些servlet引擎使用非标准的cookie名。
        initParam.put("sessionCookieName", "JSESSIONID");
        // 17.DWR能够执行简单的压缩,设置为true可以激活此功能。另外还有一个未公开的有关系的重要参数“compressionLevel”,此参数允许你配置压缩类型。
        initParam.put("scriptCompressed", "false");
        // 18.对一个打开流后的反应,等待的最大时间,默认值:1000(单位:毫秒)
        initParam.put("postStreamWaitTime", "1000");
        // 19.对一个打开流前的反应,等待的最大时间,默认值:29000(单位:毫秒)
        initParam.put("preStreamWaitTime", "29000");
        // 20.其余可配置参数及其自定义重写参数配置
        initParam.put("initApplicationScopeCreatorsAtStartup", "true");
        initParam.put("maxWaitAfterWrite", "1800000");
        initParam.put("disconnectedTime", "60000");
        initParam.put("classes", "java.lang.Object");
        //WARN
        initParam.put("logLevel", "ERROR");
        //自定义配置,org.directwebremoting.impl.StartupUtil#configureFromInitParams name.equals("customConfigurator")
        initParam.put("customConfigurator", "com.sword.dwrdemo.config.DwrXmlConfig");
        servlet.setInitParameters(initParam);
        return servlet;
    }

    @Bean
    public ServletListenerRegistrationBean dwrListener() {
        return new ServletListenerRegistrationBean(new DwrListener());
    }

}

2、后端服务代码编写

package com.sword.dwrdemo.web;

import org.directwebremoting.Browser;
import org.directwebremoting.ScriptSession;
import org.directwebremoting.ScriptSessionFilter;
import org.directwebremoting.ScriptSessions;
import org.springframework.stereotype.Controller;

import java.util.Map;

/**
 * @Description: 待请求处理类
 * @Author 刘剑涛
 * @Date 2021-02-27 00:50
 */
public class DwrDemoHandler{

    public String call(String name) {
        System.out.println("本次调用示例代码传递的参数为:" + name);
        return "您好,欢迎使用DWR!本次回声测试参数为:" + name;
    }

}

3、后端服务注册配置

package com.sword.dwrdemo.config;

import org.directwebremoting.Container;
import org.directwebremoting.create.NewCreator;
import org.directwebremoting.extend.Configurator;
import org.directwebremoting.extend.CreatorManager;

/**
 * @Description: 配置原有的DwrXml文件内容
 * @Author 刘剑涛
 * @Date 2021-02-27 00:50
 */
public class DwrXmlConfig implements Configurator {

    @Override
    public void configure(Container container) {
        CreatorManager creatorManager = container.getBean(CreatorManager.class);
        NewCreator creator = new NewCreator();
        creator.setClass("com.sword.dwrdemo.web.DwrDemoHandler");
        creator.setJavascript("DwrDemoHandler");
        creatorManager.addCreator(creator);
    }

}

4、框架服务初始配置

package com.sword.dwrdemo.config;

import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Component;
import org.springframework.web.context.WebApplicationContext;

import javax.servlet.ServletContext;

/**
 * @Description: 初始化DwrServlet
 * @Author 刘剑涛
 * @Date 2021-02-27 00:50
 */
@Component
public class ContextInitListener implements ApplicationListener<ContextRefreshedEvent> {

    @Override
    public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
        WebApplicationContext webApplicationContext = (WebApplicationContext)contextRefreshedEvent.getApplicationContext();
        ServletContext servletContext = webApplicationContext.getServletContext();
        servletContext.setAttribute("dwrServlet", "org.directwebremoting.servlet.DwrServlet");
    }

}

三、框架源码改动

上面提到了由于框架本身缺陷导致的结果,在和SpringBoot做集成时,开发环境和外置Tomcat环境均运行正常,但是使用内置Tomcat以JAR包形式运行时则产生异常,导致DWR框架无法初始化,为了解决这点,主要修改了DWR源码中关于类加载器加载DWR相关资源文件的代码,由于源码涉及内容较多,我已经将修改过的DWR依赖重新打包,并上传到如下地址,
https://download.csdn.net/download/weixin_38122095/15499156
需要的朋友可以去下载,资源中包括上面示例的完整代码,以及修改后的dwr-3.0.2-RELEASE.jar资源包,及其示例代码可独立运行的示例JAR文件。

四、示例运行效果

示例的运行效果如下图所示:
在这里插入图片描述
服务端反馈信息:
在这里插入图片描述
DWR框架测试页面:
在这里插入图片描述

五、示例资源代码

由于涉及到DWR源码改动打包相关内容,此处不再详细阐述,我已经将本示例的完整代码及其修改后的dwr-3.0.2-RELEASE.jar资源文件上传,需要的朋友可以去下面的链接访问下载。
https://download.csdn.net/download/weixin_38122095/15499156

填坑随记

因为在网上找了很久没有找到开篇提到的问题的解决方案,所以就把自己解决该问题的完成过程记录在此,不管后续是否还有人再使用该框架,可能自己找到的只是其中一种解决方案,手敲不易,大家不喜勿碰哦。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

缘码人生

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值