静态域的作用范围

sulong 于 2010-08-25 没有评论 »

静态域的作用范围是在同一个类之内,类不仅有名称上的区别,还有classloader,即类加载器的问题,同一个class文件被不同的类加载器加载后,即便它们有相同的名称,但是它们并不是同一个类。因此在不同类加载器中同名类的静态变量是不共享的。下面做一个测试,来验证它。下面的Test类有一个静态域value,两个方法分别增加value的值和打印状态。

public class Test {
    private static int value = 1;
   
    public void increase() {
        value++;
    }
   
    public void print() {
        System.out.println("My Class Loader: "
            + this.getClass().getClassLoader() +  ", Value: " + value);
    }
}

下面的类StaticTest,运行时创建两个类加载器,分别载入同一个class文件,并通过反射依次调用print, increase,print方法。如果静态域在整个JVM内共享,那么value被加了两次,最后会输出3;否则应该输出2。由于类加载器会先向父类加载器请求类,找不到的时候才会自己加载,所以在运行这个测试时,一定不要把Test类先放到classpath中。

import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;

public class StaticTest {
    public static void main(String[] args) throws Exception {
        URL[] classURLs = new URL[] {
            new URL("file:/home/sulong/tmp/")
        };
       
        URLClassLoader classLoader1 = new URLClassLoader(classURLs);
        URLClassLoader classLoader2 = new URLClassLoader(classURLs);
       
        test(classLoader1);
        System.out.println("--------------------");
        test(classLoader2);
    }
   
    private static void test(ClassLoader loader)
            throws Exception {
        Class clazz = loader.loadClass("Test");
        Object testObj = clazz.newInstance();
        invoke(clazz, testObj, "print");
        invoke(clazz, testObj, "increase");
        invoke(clazz, testObj, "print");
    }
   
    private static void invoke(Class clazz, Object obj, String name)
            throws Exception {
        Method method1 = clazz.getMethod(name, new Class[]{});
        method1.invoke(obj, new Object[]{});
    }
}

下面是我的机器上运行时的测试结果:

My Class Loader: java.net.URLClassLoader@7987aeca, Value: 1
My Class Loader: java.net.URLClassLoader@7987aeca, Value: 2
--------------------
My Class Loader: java.net.URLClassLoader@5d0385c1, Value: 1
My Class Loader: java.net.URLClassLoader@5d0385c1, Value: 2
  • Share/Bookmark

在Tomcat的多个WAR之间共享Spring

sulong 于 2010-08-24 没有评论 »

由于非技术原因,公司的专门成立的支付部,招一批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

LINK5上线啦

sulong 于 2010-07-22 1篇评论 »

LINK5终于上线了完成了,好累。回顾这个项目,在业务的驱动下,我们更改了产品商品模型并且增加了赠券的营销活动。在技术的驱动下,我们合并了order,inventory,market和coupon数据库,合并了order,inventory,market和coupon应用,由多个项目分布式部署,变成单个项目非分布式,减少了大量系统间通过jms的集成和不必要的异步,明确了不少程序的事务边界。在目前的业务量,简化的方案才是更好的。我还得再简化系统的路上走下去,让系统更简单,更易于维护,更灵活。

  • Share/Bookmark

一次sql调优

sulong 于 2010-07-20 1篇评论 »

想知道我的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