Archive for the ‘技巧’ category

在Tomcat的多个WAR之间共享Spring

August 24th, 2010

由于非技术原因,公司的专门成立的支付部,招一批Java程序员,做了支付系统;又由于非技术原因,两个月内,这个部门从领导到程序员全走光了。然后这个支付系统就移交给我们这帮受苦受难的兄弟们维护了。在看他们代码的时候发现了很有趣的东西,就是在tomcat内共享Spring context。

1,实现方法

1.1 建立一个全局的jndi资源

这个全局的jndi资源代表了一个共享的spring上下文,每次通过jndi查找资源时,都返回同一个spring上下文实例。修改tomcat/conf/context.xml文件,加入以下代码:

    <Resource auth="Container"
       contextConfigLocation="/com/test/application-context.xml"
       factory="com.test.TomcatWebApplicationContextResourceFactory"
       name="bean/RootApplicationContextFactory"
       type="org.springframework.context.ApplicationContext"/>

上面这段配置的意思就是,当有人通过jndi名称”bean/RootApplicationContextFactory”来查找对象时,容器就新建一个TomcatWebApplicationContextResourceFactory类实例,并调用它的getObjectInstance方法来获得资源对象。这个工厂类要是想javax.naming.spi.ObjectFactory接口。代码如下:

package com.test;

import java.util.Hashtable;

import javax.naming.Context;
import javax.naming.Name;
import javax.naming.RefAddr;
import javax.naming.Reference;
import javax.naming.spi.ObjectFactory;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TomcatWebApplicationContextResourceFactory implements
        ObjectFactory {
    private static final String PARAM = "contextConfigLocation";
        private static final String DEFAULT = "application-context.xml";

    private static ApplicationContext context; //被共享的spring上下文

    private void init(String confFile) {
        context = new ClassPathXmlApplicationContext(confFile);
    }

    public Object getObjectInstance(Object obj,
                        Name name, Context nameCtx,
            Hashtable<?, ?> environment) throws Exception {
        if (null == context) {
            // Customize the bean properties from our attributes
            Reference ref = (Reference) obj;

                        //从xml配置文件里取得contextConfigLocation元素的值
            RefAddr addr = ref.get(PARAM);

            if (null != addr) {
                String value = (String) addr.getContent();
                init(value);
            } else {
                init(DEFAULT);
            }
        }
        return context;
    }
}

1.2 在web应用中引用上面定义的资源

修改tomcat/conf/web.xml,添加以下的内容,这样在这个tomcat下的所有的web应用就都可以访问共享的spring上下文了。

    <resource-env-ref>
        <description>Object factory for Root applicationcontext</description>
        <resource-env-ref-name> bean/RootApplicationContextFactory
        </resource-env-ref-name>
        <resource-env-ref-type> org.springframework.context.ApplicationContext
        </resource-env-ref-type>
    </resource-env-ref>

1.3 加入依赖的包

从第一步可以看出,tomcat容器需要访问spring的一些类,需要初始化上下文,所以要把初始化上下文时用到的类共享给tomcat。最简单的方法是把所有相关的类和jar包复制到tomcat/lib目录。还可以修改catalina.properties实现,如下:

shared.loader=/usr/local/jars/*.jar

上面的代码让tomcat载入自定目录里面所有的jar文件。

2 我的看法

一个庞大的spring上下文是要占用很多内存的,所有应用共享一个上下文,而不是每个web应用一份,就可以节省不少的内存。但是由此却带来了部署和配置的麻烦。如果有人利用这种机制,让多个web应用通过共享spring bean来实现一些奇怪的功能,结果导致这些应用间相互耦合,那么以后想拆分开也不容易了。总体来说,我不喜欢这样方案,但是如果真的很穷很缺内存的话,这也不失一个好方法。

  • Share/Bookmark

一次sql调优

July 20th, 2010

想知道我的Java应用程序在运行时到底执行了那些SQL,以及这些SQL运行时耗费多少时间。起初试图通过mysql的日志来查看,调查了一下发现很麻烦,于是就是用mysql的jdbc驱动程序提供的功能来查看,很方便。开启的方法很简单,就是在jdbc url上加上如下的参数:

jdbc:mysql://localhost/mydb?profileSQL=true

还有很多其它很有用的参数,如:

includeInnodbStatusInDeadlockExceptions=true

可以在发生innodb死锁时打印innodb status.详细情况看最新的mysql J connector 文档。

打开日志后,发现一般的sql语句的执行时间都< 1ms, 但是有一条sql的执行时间是180ms以上。这条sql是

SELECT max(oid) FROM inventory_entry WHERE sku =GROUP BY lot ;

这条sql是要查出相同sku记录按lot分组后最新的一条记录的主键。经查,因为lot在新业务模式下将不再被使用,所以可以把group by 去掉了,去掉之后执行时间降到100ms左右。select max(0id) 的作用是为了取得最新的记录,但是可以通过主键排序直接拿到的oid,因为oid是自增长的。改成:

SELECT oid FROM inventory_entry WHERE sku = ? ORDER BY oid DESC LIMIT 1;

后,时间降到1ms左右了,达到了性能要求。

  • Share/Bookmark

让JAXB生成序列化的类

November 20th, 2009

默认jaxb生成的类是没有序列化的,但是我们经常需要他们序列化。jaxb ri有生成序列化的类的这样的功能,但由于这不属于标准的功能,所以需要手动的开启。开启的方法是在用来生成java类的xml schema文件头添加如下内容:

<annotation>
    <appinfo>
        <jaxb:globalBindings generateIsSetMethod="true">
            <xjc:serializable uid="1"/>
        </jaxb:globalBindings>
    </appinfo>
</annotation>

另外,在使用jaxb的 xjc 编译器时,要加上 -extension 的参数。

  • Share/Bookmark

可恶的乱码问题

November 12th, 2009

在打印单据的时候,有两个汉字“玥”和“芃”,始终显示为“?”,这一问题困扰了业务人员一段时间了。以前没有空,就没有投入精力去查原因,上次得了空,终于把这个问题解决掉了。

这个乱码的汉字从哪里来的呢?首先是业务人员通过一个遗留的PHP应用,输入到一个mysql 4数据库中,然后我们的java程序从这个mysql 4中读取,再写入到新的mysql5的数据库中,最后通过java程序生成html在用户的浏览器里显示,结果就看到了这两个汉字乱码。那么这个问题如何解决呢?

只有两个汉字乱码,而不是所有的汉字都乱码,所以我们的程序一定使用了一种包含汉字较少的字符集,而这个出错的汉字恰好不在此字符集中,否则,就应当是大部分汉字乱码,而不仅仅是这两个了。很自然的就让人想到了 gb2312, gbk, gb18030, utf8等字符集之间的关系。其中gb2312中只有六千多个汉字,其它几个字符集支持的汉字数相差不多,我们程序一定是在该使用gbk, gb18030或utf8的地方使用了含有汉字较少的gb2312。

经查阅相关文档,果然发现那两个汉字都不在gb2312里,而在gbk, utf8字符集里。我们的mysql5使用的utf8字符集,生成的html也是utf8编码,如果mysql5中的汉字是正确的话,那么就应该能正常显示,因此,数据进入mysql5的时候就应该出错了。java程序在连接mysql5的时候,在jdbc连接中设置了utf8,所以如果从mysql4数据库中读取出来的是正确的,就不应该写入出问题,因此数据在mysql4中可能就错了,也可能是从mysql4中读取的时候出错了。php的兄弟们说mysql的数据库是gb2312字符集的,mysql的配置文件里也是这么配置的。如果果真如此,那么这个两个汉字在mysql4的数据库中就应该不正常了,所有的程序读出之后都应该是乱码,可是php的录入程序那边确实是能正常显示的。我断定这个数据库里放的实际上是gbk字符集的汉字。选用和mysql4相对应的mysql j connector 3.14,并在jdbc连接url里设置了characterEncoding=gbk,读出来的仍旧不正确!查阅了mysql j connector的文档,原来3 版本的mysql connector根本就不支持characterEncoding这个选项。看来只能修改mysql的配置文件了。修改之后,果然这两个汉字可以正常显示了。

如果计算机是中国人发明的,那么计算机也许一开始就得支持汉字,就再也没有乱码的问题。如果世界上只有一种汉字的编码方案,也不会有乱码的问题,可惜汉字偏偏有很多中编码方案。所以在我们的程序中,一定得搞清楚,我们的汉字数据来自于哪里,使用的哪种编码,每个环节都得配置得当,否则就会有乱码问题。

对于一般的web应用来说,需要关心编码的地方有:
1,数据库以什么编码存储数据
2,程序使用什么字符集连接数据库
3,程序内部运行时使用什么字符集
4,程序源码使用什么字符集
5,生成的html文件是什么字符集
6,程序告知浏览器的字符集
如果以上这些地方的字符集有配置不正确的地方,往往就会乱码。

如果可以的话,最好在所有的地方都使用一种编码,比如对于java应用来说,在所有的地方都使用utf8编码,就可以解决问题。需要注意的是,在windows环境下,默认的文件编码是gb18030的,而不是utf8。

另外,极端鄙视故意使用生僻字火星文的脑残族!!

  • Share/Bookmark