使用监控技术实现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的按需要进行重启”

使用powermock对基于testng的静态方法进行测试

    对于一个软件来说,测试非常重要,在基本的单元测试当中,我们经常需要mock一些类来替换一些实现,比如替换dao,使其在没有数据库的环境下也可以进行测试。通常情况下,我们使用easymock这个java测试框架来进行mock,它可以mock一个接口,或者一个实现类。但是如果是mock一个静态方法呢?easymock显然不行了。

    为什么需要mock一个静态方法,我们来看以下这个方法:

	@Transactional(readOnly = true)
	@SuppressWarnings("unchecked")
	public static <T extends Entityable> T get(Class<T> clazz, long id) {
		if(id <= 0)
			return null;
		return (T) sessionFactory.getCurrentSession().get(clazz, id);
	}

    这是一个根据对象的id取得对象的方法,如果是普通的dao,我们通过会使用getHibernateTemplate或者里面的sessionFactory.getCurrentSession来实现这个方法。但在当前这个实现当中,sessionFactory被静态注入了,加上aspectj的编织可以在静态方法中追加事务。因此此方法被写成一个静态方法。
    当然还有许多其它类似的方法,比如通常的查询方法,这些方法中均使用了类似静态方法的设计。现在我们要测试这些静态方法,主要即测试sql语句是否有问题,以及看是否取得了正确的数据。

    测试静态方法需要对类进行处理,即在字节码层面对静态类进行改造,使其能够进行静态方法模拟, 我们使用powermock来实现这一目的。
    在本文中,我们使用了powermock来模拟静态类,easymock进行普通的expect以及return操作,然后使用了testng来作为测试框架。

继续阅读“使用powermock对基于testng的静态方法进行测试”

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机制来防止并发重复提交”

使用正确的dtd声明和entityResolver避免saxReader联网验证

    在使用许多使用xml配置文件的框架时,都会碰到以下的问题。有时候项目运行起来,需要花费许多的时间,有时候项目甚至还启动不起来。如使用hibernate时,经常报以下的错误:

org.dom4j.DocumentException: XXXXXXX
	at org.dom4j.io.SAXReader.read(SAXReader.java:484)
	at org.hibernate.cfg.Configuration.doConfigure(Configuration.java:2211)

    这是由于在hibernate中在解析xml时,默认会对所需要解析的xml进行validate,即进行验证,验证所写的xml是否符合声明格式要求。在网上搜索相关的帖子,提出的办法就是禁用掉这个验证。但是hibernate解析xml工作是在它的内部进行的,程序上不能对其进行修改;同时,既然hibernate内部使用了这个验证,即有它验证的需要,仅仅禁用掉这个验证并不能解决实际的问题。

    在hibernate3.X版本一直到hibernate3.5版本,在hibernate.xml配置文件中,一直使用的xml声明即是如下:

<!DOCTYPE hibernate-configuration PUBLIC
	"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
	"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">

    而是hibernate3.6以上版本,所写的声明中的url地址就变了(当然以前的还继续有效),变成了

http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd

    这两个声明,在3.6以上版本都是可以接受了。前一声明在3.5及以前版本是正确的。但对于这个声明,如果修改了其中任意一个字符,在启动时都会报错。究其原因,则由于在对xml进行验证时,hibernate提供了一个自己实现的entityResolver。

继续阅读“使用正确的dtd声明和entityResolver避免saxReader联网验证”

alibaba fastjson(json序列化器)序列化部分源码解析-2-性能优化B

    上篇地址:http://www.iflym.com/index.php/code/alibaba-fastjson-serializer-source-analyse-2-performence-optimize-a.html
    前面讲了进行对象解析的两个方面,并讲了针对outWriter将不同类型的数据信息写到buf字符数组。本篇讲解对象解析的过程,即如何将不同类型的对象解析成outWriter所需要的序列信息。并考虑其中的性能优化。

    取得解析器    
    首先我们需要取得指定对象的json序列化器,以便使用特定的序列化器来序列化对象。因此,需要有一个方法来取得相对应的序列化器。在fastjson中,使用了一个类似map的结构来保存对象类型和及对应的解析器。对于对象类型,在整个fastjson中,分为以下几类:

    1    基本类型以及其包装类型,字符串
    2    基本类型数组以及包装类型数组
    3    Atomic类型
    4    JMX类型
    5    集合类型以及子类
    6    时间类型
    7    json类型
    8    对象数组类型
    9    javaBean类型

    对于第1,2,3,4类型,在fastjson中使用了一个全局的单态实例来保存相对应的解析器;第5类型,处理集合类型,对于集合类型及其,由于其处理逻辑均是一样,所以只需要针对子类作一些的处理,让其返回相对应的集合类型解析器即可;第6类型,时间处理器,将时间转化为类似yyyy-MM-ddTHH:mm:ss.SSS的格式;第7类型,处理fastjson专有jsonAwre类型;第8类型,处理对象的数组形式,即处理数组时,需要考虑数组中的统一对象类型;第9,即处理我们最常使用的对象,javaBean类型,这也是在项目中解析得最多的类型。

继续阅读“alibaba fastjson(json序列化器)序列化部分源码解析-2-性能优化B”

alibaba fastjson(json序列化器)序列化部分源码解析-2-性能优化A

    上篇地址:http://www.iflym.com/index.php/code/alibaba-fastjson-json-serializer-chapter-source-analyse-one-global-analyse.html
    接上篇,在论述完基本概念和总体思路之后,我们来到整个程序最重要的部分-性能优化。之所以会有fastjson这个项目,主要问题是为了解决性能这一块的问题,将序列化工作提高到一个新的高度。我们提到,性能优化主要有两个方面,一个如何将处理后的数据追加到数据储存器,即outWriter中;二是如何保证处理过程中的速度。
    本篇从第一个性能优化方面来进行解析,主要的工作集中在类SerializeWriter上。

    首先,类的声明,继承了Writer类,实现了输出字符的基本功能,并且提供了拼接数据的基本功能。内部使用了一个buf数组和count来进行计数。这个类的实现结果和StringBuilder的工作模式差不多。但我们说为什么不使用StringBuilder,主要是因为StringBuilder没有针对json序列化提出更加有效率的处理方式,而且单就StringBuilder而言,内部是为了实现字符串拼接而生,因为很自然地使用了更加能够读懂的方式进行处理。相比,serializeWriter单处理json序列化数据传输,功能单一,因此在某些方面更加优化一些。
    在类声明中,这里有一个优化措施(笔者最开始未注意到,经作者指出之后才明白)。即是对buf数组的缓存使用,即在一次处理完毕之后,储存的数据容器并不销毁,而是留在当前线程变量中。以便于在当前线程中再次序列化json时使用。源码如下:

public SerializeWriter(){
        buf = bufLocal.get(); // new char[1024];
        if (buf == null) {
            buf = new char[1024];
        } else {
            bufLocal.set(null);
        }
    }

    在初始构造时,会从当前线程变量中取buf数组并设置在对象属性buf中。而在每次序列化完成之后,会通过close方法,将此buf数组再次绑定在线程变量当中,如下所示:

/**
     * Close the stream. This method does not release the buffer, since its contents might still be required. Note:
     * Invoking this method in this class will have no effect.
     */
    public void close() {
        bufLocal.set(buf);
    }

    当然,buf重新绑定了,肯定计数器count应该置0。这是自然,count是对象属性,每次在新建时,自然会置0。

    在实现过程当中,很多具体的实现是借鉴了StringBuilder的处理模式的,在以下的分析中会说到。

    总体分类
   
    接上篇而言,我们说outWriter主要实现了五个方面的输出内容。
        1,提供writer的基本功能,输出字符,输出字符串
        2,提供对整形和长整形输出的特殊处理
        3,提供对基本类型数组输出的支持
        4,提供对整形+字符的输出支持
        5,提供对字符串+双(单)引号的输出方式
    五个方面主要体现在不同的作用域。第一个提供了最基本的writer功能,以及在输出字符上最基本的功能,即拼接字符数组(不是字符串);第二个针对最常用的数字进行处理;第三个,针对基本类型数组类处理;第四个针对在处理集合/数组时,最后一位的特殊处理,联合了输出数字和字符的双重功能,效率上比两个功能的实现原理上更快一些;第四个,针对字符串的特殊处理(主要是特殊字符处理)以及在json中,字符串的引号处理(即在json中,字符串必须以引号引起来)。

    实现思想

    数据输出最后都变成了拼接字符的功能,即将各种类型的数据转化为字符数组的形式,然后将字符数组拼接到buf数组当中。这中间主要逻辑如下:
        1    对象转化为字符数组
        2    准备装载空间,以容纳数据
        2.1    计数器增加
        2.2    扩容,字符数组扩容
        3    装载数据
        4    计数器计数最新的容量,完成处理
    这里面主要涉及到一个buf数组扩容的概念,其使用的扩容函数expandCapacity其内部实现和StringBuilder中一样。即(当前容量 + 1)* 2,具体可以见相应函数或StringBuilder.ensureCapacityImpl函数。

继续阅读“alibaba fastjson(json序列化器)序列化部分源码解析-2-性能优化A”