使用greys找到泄漏的本地线程变量值

在程序代码中,出现过这样一种情况,在一个新的线程组中,尝试去获取一个threadLocal的值,按照相应的程序代码,应该是不能获取到的。但是在实际的运行过程中,却发现能够获取到相应的变量值。
在我们的程序代码中,使用了shiro来存储相应的用户信息,即一个简化版的的权限管理程序。其中,在标准的web程序中,shiro拦截器会把相应的会话信息存储在session中,并且在应用代码中,可以通过一个threadLocal的ThreadContext来获取相应的subject,进而拿到相应的会话值.相应的代码如下所示:

    public static UserId getUserId() {
        Optional<Subject> optional = Optional.ofNullable(ThreadContext.getSubject());
        return optional.map(t -> t.getSession(false))
                .map(t -> t.getAttribute("userId"))
                .orElse(null);
    }

因为相应的数据为web调用时才会注入到sessionId,而如果是一个定时类的任务,那么理论上应该是不会有相应的session对象存在,那么即不会有相应的subject存在,在调用此方法时即会返回在调用getSubject时失败。但是在实际上时,却发生调用subject不为null,进而在调用getSession方法时出现了错误信息。

相应的调用链看起来应该像是这样

  1. 线程1调用了 ThreadContext.setSubject 方法,设置了session信息
  2. 线程1完成整个业务方法的运行,相应的session信息被销毁,但相应的removeSubject方法并没有被调用
  3. 线程1重新以任务的形式执行任务代码,其中调用了getSubject,但不能拿到相应的数据

在这种调用链中,我们只看到了最终的现场,但原始的设置值的现场即不能复现,即不清楚数据是什么时候,哪个调用任务进行处理的。整个问题称之为数据泄漏,即数据在不应该出现的地方出现了。
处理这种问题,一种作法即是异常标记法。详细的步骤如下所示:

  1. 在调用 ThreadContext.setSubject 时,设置一个异常线程栈(Exception),此里面封装了当前调用的整个路径
  2. 将异常线程栈(字符串形式)存储在一个threadLocal变量中,方便后面获取
  3. 在调用 getSubject 时,如果发生了相应的错误信息,即表示复现了相应的错误场景
  4. 这时候即将之前存储到threadLocal中的字符串获取出来,打印出原始的setSubject调用路径

整个作法参考于alibaba druid的数据库连接泄漏检测。

本文介绍了在不修改源代码的情况下,并且在线上环境,使用greys,来监控相应的调用,当出现了满足条件的场景时,自动打印出相应的调用路径。

继续阅读“使用greys找到泄漏的本地线程变量值”

JavaScript的单线程性质以及同步ajax提交

本文转自:http://www.phpweblog.net/rainman/archive/2009/01/05/6267.html

How JavaScript Timers Work

从基础的层面来讲,理解JavaScript的定时器是如何工作的是非常重要的。计时器的执行常常和我们的直观想象不同,那是因为JavaScript引擎是单线程的。我们先来认识一下下面三个函数是如何控制计时器的。

  • var id = setTimeout(fn, delay); – 初始化一个计时器,然后在指定的时间间隔后执行。该函数返回一个唯一的标志ID(Number类型),我们可以使用它来取消计时器。
  • var id = setInterval(fn, delay); – 和setTimeout有些类似,但它是连续调用一个函数(时间间隔是delay参数)直到它被取消。
  • clearInterval(id);, clearTimeout(id); – 使用计时器ID(setTimeout 和 setInterval的返回值)来取消计时器回调的发生

为了理解计时器的内在执行原理,有一个重要的概念需要加以探讨:计时器的延迟(delay)是无法得到保障的。由于所有JavaScript代码是在一个线程里执行的,所有异步事件(例如,鼠标点击和计时器)只有拥有执行机会时才会执行。用一个很好的图表加以说明:

在这个图表中有许多信息需要理解,如果完全理解了它们,你会对JavaScript引擎如何实现异步事件有一个很好的认识。

继续阅读“JavaScript的单线程性质以及同步ajax提交”

使用struts2标签定制ajax版的分页组件

上一文中,我们介绍了如何使用page.jsp界面输出分页信息,那么针对于像动态表格,或者需要局部刷新的界面又如何处理呢。道理其实还是很简单,我们只需要按照需求让它按指定的动作进行即可以了。

具体的作法也很简单,我们只需要将所有的链接修改为一个点击函数就可以了,那么当点击链接时,具体界面怎么跳转,以及刷新哪个部分,这其实就与分页组件无关了。我们只需要将相应的链接传递给调用方,让调用方自行处理就行了,这样也可以保证分页组件的通用性。

在实现层面,原来的链接就是一个href链接,那么修改为ajax版之后,我们就将这个href链接当成一个参数传递给调用方,比如可以定义一个clickPage函数,然后把href传递给clickPage即可,当然,你也可以同时传递当前的页码,具体实现由需求决定。
简单参考代码如下所示:

<s:url id="goto"><s:param name="page.currentPage" value="1"/></s:url>
<s:a href="%{goto}" onclick="clickPage('' + this.href);return false;" title="首页"></s:a>

继续阅读“使用struts2标签定制ajax版的分页组件”

使用webBrowser控件进行无需选择打印机直接打印

以前做的一个系统(内部小型局域网,在线售票系统),采用的是基于浏览器的打印方式,客户提出在每次打印时都必须选择一次打印机,即在调用window.print()的时候弹出选择打印机的界面,这样很费时间,能不能去掉。

后来在网上找了一些控件,比较有名的就是scriptx这个控件,它可以实现打印控制,包括控制边距,选择页数等。的确很不错,不过很可惜,这个控件是收费的,而且破解版根本就找不到,要不就是已经失效了。估计内部使用了基于联网的方式进行认证。
而且该控件提供的免费版中,方法factory.printing.print(false)基本起不到预期的作用,本来想不弹出选择界面,结果还是不能幸免。于是放弃寻找破解版的念头,寻找其他的控件吧。

后台终于找到可以使用windows自带的webBrowrser进行操作了,因为笔者的需求很简单,不需要控制打印选项这些,就是想直接使用默认的打印机,不弹出打印选择界面而已。OK,以下是实现代码,很简单的(主要是后面有一些限制)//界面上

<object ID="PrintCommandObject" WIDTH=0 HEIGHT=0 CLASSID="CLSID:8856F961-340A-11D0-A96B-00C04FD705A2"></object>

//javascript
		var PrintCommand = document.getElementById("PrintCommandObject");
			 PrintCommandObject.ExecWB(6, 2);//这里使用的是6,2参数,有的说6,6,笔者测试不通过

继续阅读“使用webBrowser控件进行无需选择打印机直接打印”

使用js(kibo)监听浏览器键盘事件

在进行BS系统开发之时,总有客户提出某些按钮应该支持快捷键,甚至整个系统都采用快捷键来进行操作。如果没有一个有效的键盘事件处理的js框架,在每个界面处理不同的键盘事件,是一件非常可怕的事情。幸运的是,老外开发了一个有效的js框架,来监听浏览器中的键盘事件,并提供丰富的事件处理来让开发人员进行界面处理。那就是kibo,一个键盘处理js。
本文为kibo中文教程,直接从kibo官方教程翻译而来,不足之处望谅。

可以从以下网站下载最新的版本:https://github.com/marquete/kibo。整个文件才8k,足够的小,都不需要使用压缩了,而且与jquery也不冲突。它是一个简单地处理键盘事件的js library,没有其它依赖。

语法和使用
kibo主要提供了两个键盘事件的调用函数,down和up,分别表示按键的按下和弹起。这两个函数接收两个参数,一个参数为需要监听的键盘按键集合,包括一个或多个按键,以及表示指定类型的任意一个按键(如*),另一个参数则是处理这个事件的对应函数。kibo提供了像jquery一样的连续调用,你可以使用kibo.down(…).up(…)这样的调用。

可以使用的键盘包括我们普通使用的a-z这些普通按钮,以及特殊的alt,ctrl等控制按键,也包括表示方向键的"up,down,left等,也包括f1和f12这些功能按键。在调用方法时,可以传递一个或多个按键作为相对应的参数。

当处理按键的事件触发时,我们可以在函数中处理这个事件,也可以直接忽略这个事件。如果你想要调用浏览器默认的处理(如f5调用),则可以在回调函数时直接返回false即可。
在回调函数时,我们可以使用kibo.lastKey方法,来获取在键盘事件触发时所对应的按键信息。此方法将返回相对应的按键名称。或者当不识别这个按键时,将返回undefined。如果给这个方法传递一个按键,如kibo.lastKey("shift"),它将返回在这个按键中,是否按下了指定的键,返回true或false。

继续阅读“使用js(kibo)监听浏览器键盘事件”

解决使用jquery1.3以上版本时出现Malformed OGNL expression: f[] [ognl.ParseException: Encountered ” “]” “]的问题

    当使用jquery1.3以上版本时,进行ajax参数传值时,会出现以下的一个错误:

ognl.ExpressionSyntaxException: Malformed OGNL expression: f[] [ognl.ParseException: Encountered " "]" 
"] "" at line 1, column 3.

    这个错误是因为,jquery在传递数组类参数时,将不再遵循1.3时如f=x&f=y的参数传递了,而是采用了像php一样,带中括号的参数传递。js值 {f:["x","y"]},将被转化成f[]=x&f[]=y,而这种参数形式传递到后台时,使用struts2.1.8版本时,就会出现以上的错误形式。

    struts2一直能够识别的模式仅是f=x&f=y这样,当后台声明f为一个list或set时,就会把x,y分别加入到list或set中。而如果是f[]这种形式,则会报相应的转换错误。

    解决此问题的方法很简单,在进行ajax请求时,追加一条以下语句即可:

$.ajaxSettings.traditional=true;

    这是一个全局参数,故可以在引入jquery.js之后进行声明。此参数的意思在于,使用$.param时,将采用旧的jquery1.3版本的param生成方式进行处理。

继续阅读“解决使用jquery1.3以上版本时出现Malformed OGNL expression: f[] [ognl.ParseException: Encountered ” “]” “]的问题”