struts2中如何自动纠正如contextpath/contextpath/*.action这种路径

在上篇 struts2中如何根据请求路径定位到详细的访问action 中,主要了解了在struts2中是如何通过一种路径解析成一个action的。那么针对以下的路径
contextpath/contextpath/*.action的路径,在命名空间为""的情况下,struts2是如何进行纠正的呢。

其实也很简单,这里面可以说是struts2本身的一处处理策略,因为在整个解析过程中,除掉上下文之后,就剩下以下的一个路径了:

contextpath/actionName

这里面有一个/符号,并且从理论上来讲,并没有一个有效的actionConfig与之匹配,但是由于项目中设置了参数

struts.enable.SlashesInActionNames

这个值没有被设置,那么默认就是false,即不允许在actionName中存在着/符号,那么在struts2中就直接在actionName中去除了这个/符号,如下代码所示,类DefaultActionMapper的方法parseNameAndNamespace

if (!allowSlashesInActionNames && name != null) {
            int pos = name.lastIndexOf('/');
            if (pos > -1 && pos < name.length() - 1) {
                name = name.substring(pos + 1);
            }
        }

这样处理之后,剩下的actionName就成了我们所需要的actionName了,这样就间接的找到了正确的actionConfig,也可以说是碰巧正确了。

struts2中如何根据请求路径定位到详细的访问action

在struts2中在访问一个菜单链接时,我们只需要将相应的package 命名空间和 action的name进行组合,并加上相应的后缀,就可以直接访问到相应的Action了,那么这个过程是如何进行的,多个相同命名空间的package是如何满足互不冲突的呢,这就需要详细了解struts2中是如何解析路径信息,并根据访问路径寻找相应的action配置了。

整个过程我们可以分成以下几个步骤进行处理

  1. 解析xml,将所有可以访问到的路径信息进行保存
  2. 根据访问请求信息,取其中可用的路径
  3. 根据路径进行查找,最终查找到我们所需要的Action

继续阅读“struts2中如何根据请求路径定位到详细的访问action”

使用struts2的builder alias机制加载objectFactory

以下代码基于struts2版本2.1.8.1版本分析。
在使用Struts2的过程中,我们都喜欢使用struts2的spring插件来让spring作为struts2的默认对象容器,原理就在于在加载struts2之前先加载spring容器,然后将spring容器加载至applicationContext中,在struts2的objectFactory(称之为对象容器)实现中,找到spring容器并进行各项对象创建工作。

在spring插件中,使用了StrutsSpringObjectFactory类来作为struts2的对象容器,实现过程即除重写objectFactory的各项buildBean方法以符合spring规范之外,其它则就是根据struts2的各项参数设置spring参数(如autoType等)。之所以使用此类作为struts2的对象容器,原因就在于在struts2-spring.jar中的struts-plugin.xml中定义了如下的声明:

<bean type="com.opensymphony.xwork2.ObjectFactory" name="spring" class="org.apache.struts2.spring.StrutsSpringObjectFactory" />
    
    <constant name="struts.objectFactory" value="spring" />

第一句是声明了一个实现类objectFactory的对象,这就是spring插件所提供的对象容器,而第二句则声明属性struts.objectFactory的值为spring,即key为spring的对象将成为struts2的对象容器。

为什么这样说,我们可以看一下struts2对struts.objectFactory的解释,以下解释摘自struts2中的default.properties:

### if specified, the default object factory can be overridden here
### Note: short-hand notation is supported in some cases, such as "spring"
###       Alternatively, you can provide a com.opensymphony.xwork2.ObjectFactory subclass name here

即只要设置了此值,则默认的对象容器,将被改写,那么spring插件所提供的功能就是定义了一个spring实现的对象容器,并且重写了struts2定义,将spring容器再成为struts2的对象容器。

继续阅读“使用struts2的builder alias机制加载objectFactory”

解决使用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 ” “]” “]的问题”

使用监控技术实现tomcat的按需要进行重启

    我们经常需要碰到这种情况,当用户提出一个问题,并且我们已经修改之后,已经由自动更新系统更新到客户方,但这时需要重新启动tomcat才能使新修改的代码工作。这时候,就要想办法来重新启动这个tomcat了。
    有的同学提到一个使用tomcat控制台重新部署项目的想法,但是通过这种方法,有时候并不能有效地进行部署,因为可能存在非守护进程不能完全退出,以及有些资源未能正确回收的问题。相比来说,直接重启tomcat来得更方便一些。

    在前面已经说到,实现项目的自动更新,是利用embeded tomcat来进行重新启动来完成的,这中间实现tomcat在指定时间更新并重启的功能是使用类似监听技术来实现的。即在客户机器上,同时部署了两个组件,一个为运行我们项目的tomcat,另一个则为监控项目代码的java监听器。此监听器用于在指定时间监听项目代码是否有更新,如果有更新,则使用svn进行更新,并重新启动tomcat。相应的代码如下所示:

	private void updateAndRestart() throws Exception {
		logger.log(Level.INFO, "准备执行更新线程......");
		if(hasUpdate(svn)) {
			logger.log(Level.INFO, "发现更新,准备重新启动tomcat......");
			shutdownTomcat();
			logger.log(Level.INFO, "成功关闭tomcat,正在重起");
			startTomcat();
			logger.log(Level.INFO, "重起tomcat成功......");
		} else
			logger.log(Level.INFO, "无有效更新......");
	}

    这样的代码,即实现了tomcat的更新和重启操作,惟一不足的是这段代码并不能接收来自于网页上的命令实现按需要重启,它只能在规定的时间(如每天24点)进行操作。
    有的同学,想到如果将这个监听器做成一个类似项目的服务并启动, 即可以实现效果了。但实际的问题时,对于项目来说,通过路由器打开太多的端口是有害而无利了。我们已经打开了80端口用于项目访问,最好不要再打开其它端口了。

    本文描述了一个在项目代码中增加用于实现重启的功能代码,并通过socket请求访问监听器并通过监听器来重启项目tomcat的实现。

    实现原理如下:

  1.     监听器监听指定端口,进行监听操作
  2.     由项目向外提供重启功能界面,通过功能界面进行操作
  3.     项目的重启功能向监听器指定端口发送重启指令
  4.     监听器接收到相应socket请求,即执行重启操作

继续阅读“使用监控技术实现tomcat的按需要进行重启”

javaEE开发中使用session同步和token机制来防止并发重复提交

    通常在普通的操作当中,我们不需要处理重复提交的,而且有很多方法来防止重复提交。比如在登陆过程中,通过使用redirect,可以让用户登陆之上重定向到后台首页界面,当用户刷新界面时就不会触发重复提交了。或者使用token,隐藏在表单中,当提交时进行token验证,验证失败也不让提交。这都是一般的做法。

    我们这次碰到的问题是重复提交本身就是一个错误,重复提交会导致一些相关数据的逻辑不再正确。而这些重复提交并不是通过普通的刷新界面,或者两次点击按钮来进行的。在普通的操作当中,我们可以通过一系列的手段,使得相应参数被清零,从而防止数据上的不正确。但是,在一种情况下,这些手段都不再有效,那就是并发的重复提交。
    并发重复提交,那就是在同一时间内(时间间隔可以缩短到0.X秒之内),在这种情况下,所有的常规逻辑都不再有效,因为多个请求,同时进入系统,系统已不能判断出这些请求是否是无效的,它们同时通过常规的重复逻辑判断,并最终在同一时间内将数据写入到数据库中,引起数据错误。

    举一个简单的例子,在系统中销售一个商品,首先通过该商品id进入到系统逻辑判断,判断此商品是否已售出,如果未售出,就进行数据存取操作。商品是否售出,是一个逻辑判断,是验证数据存储到数据库的一道门。在常规的判断当中,前一请求通过这道门之后,后一请求就不能通过了,因为验证为false。但在并发请求中,两个或多个请求同时通过了这道门,因为都是同时进入到判断,在判断之前都验证商品没有被售出,所以就同时进入到数据的存储当中。

    在常规的java开发中,对于这种情况,临界资源,通常是使用加锁来保证这种情况的先后顺序。但是加锁有一个问题即是,它是对于全局信息的加锁,即对整个将要销售的商品进行加锁了。对于BS应用来说,我们必须保证另一个操作人员的同一种商品的销售请求通过,即只限制同一个操作人员销售的并发请求,不限制多个操作人员不同请求的处理。
    在这种情况下,我们的加锁就不能简单的锁定在商品上,而是要锁定在与操作人员有关的信息上,这就是session。

    session是一个在单个操作人员整个操作过程中,与服务器端保持通信的惟一识别信息。在同一操作人员的多次请求当中,session始终保证是同一个对象,而不是多个对象,因为可以对其加锁。当同一操作人员多个请求进入时,可以通过session限制只能单向通行。
    本文正是通过使用session以及在session中加入token,来验证同一个操作人员是否进行了并发重复的请求,在后一个请求到来时,使用session中的token验证请求中的token是否一致,当不一致时,被认为是重复提交,将不准许通过。
    整个流程可以由如下流程来表述:

  1. 客户端申请token
  2. 服务器端生成token,并存放在session中,同时将token发送到客户端
  3. 客户端存储token,在请求提交时,同时发送token信息
  4. 服务器端统一拦截同一个用户的所有请求,验证当前请求是否需要被验证(不是所有请求都验证重复提交)
  5. 验证session中token是否和用户请求中的token一致,如果一致则放行
  6. session清除会话中的token,为下一次的token生成作准备
  7. 并发重复请求到来,验证token和请求token不一致,请求被拒绝

继续阅读“javaEE开发中使用session同步和token机制来防止并发重复提交”