Archive for the ‘java’ category

静态域的作用范围

August 25th, 2010

静态域的作用范围是在同一个类之内,类不仅有名称上的区别,还有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

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

异步调用

June 29th, 2010

1 什么是异步

异步显然是和同步相对应的。同步调用,被调用者要执行完所有的代码才将执行流程交给调用者,在被调用者执行的过程中,调用者只能被动的等待;而异步,则是被调用先将执行流程转给调用者,然后自己再继续执行。如果调用者必须在得到被调用者的所有执行结果才能确定下一步该做什么的话,那么这种情况就该使用同步调用;相反,如果调用者不需要被调用者的执行结果,那么就可以使用异步。

2 怎样异步

在单线程或单进程等单一执行流程的情况下,实现不了能让被调用者个调用者并发执行的异步调用,这种情况下被调用者只能先将要执行的任务环境记录下来,带调用者执行完成后,再回头来执行之前要执行的任务,这样的方式其实算不上是异步,称之为推迟执行更贴切。

在多线程或多进程环境里,才能真正的实现异步,实现调用者和被调用者并发执行的异步调用。被调用者只要开启一个新的线程在新线程里执行任务,而立刻返回让调用者在当前线程继续执行,这样就可以实现异步调用了。

借助于ejb 3.1 , spring 3, seam这样的容器和框架,实现异步调用的方法非常简单,只需要再方法上加上指定的Annotation就可以了。使用spring 2的时候,可以注入taskExecutor,用taskExecutor.execute(Runnable r) 方法来实现异步。另外,通过JMS也可以实现异步。

3 异步有什么好处

说异步比同步调用性能好并不准确。假设 被调用方法里面要完成两个任务,分别耗时 t1和t2,从开始调用到调用返回的耗时,即响应时间,同步调用的时候,Rs = t1 + t2,异步调用的时候Ra = t1 + a1,其中a1代表为了异步调用开启新线程或新进程的耗时,如果要Ra < Rs,显然要求a1 < t2,可见如果异步执行的任务并不比启用异步执行耗时太多的话,异步执行也不会缩短响应时间。如果计算完成同等任务的总耗时的话,同步的时候 Ts = t1 + t2,异步的时候 Ta = t1 + a1 + t2,可见Ts < Ta,此时异步没有任何好处。在要求响应时间非常短的互联网应用中,合理的采用异步可以提高响应速度,提升程序的负载能力。快速的响应用户的请求,将耗时多而用户不急于知道结果的任务交给有限的线程来执行。

然而,对于Java EE这样的环境,一个事务的边界往往是在一个线程里的,所以使用异步之后,就失去了事务的保护,异常后的数据补偿工作增加了程序的复杂性,也让程序更容易出错。一个不好的异步实现机制,往往给程序带来更多的隐患。比如,为每一个异步执行的任务都新建一个线程,很可能在并发数到一定程度的时候,由于创建过多的线程,达到性能瓶颈,甚至于导致程序崩溃。

4 什么时候用异步

首先只有调用者不需要立刻知道结果的任务才能被异步执行;其次被异步执行的任务耗时多于使用异步执行本身的时候,采用异步才值得;再次,要求响应时间短的时候采用异步才有意义。

典型的例子:用户支付订单后要立刻告知用户支付是否成功,并给用户发送邮件,通知仓库发货,告知物流送货。这种情况下,用户只要知道支付成功就行了,发送邮件等任务用户可以过会再感知到,而且发送邮件等任务很耗时,用户肯定不愿意等待完成所有一切之后才知道支付结果。这种情况下,将发送邮件等任务异步执行就很合适。

另外一个例子,A,B两个系统每隔一个小时对一次账,如果B发现账户和A系统不一样,则自动调整。尽管A不需要知道B的调整结果,但是A完全可以等待B执行,反正也没人等也没人急,而且对账频率低负载低,这种情况下就没有必要异步执行调整动作。

  • Share/Bookmark

Java EE 6 之 Servlet 3.0

February 27th, 2010

Servlet之所以没有像之前的ejb那样被受到那么多的诟病,是因为servlet的设计确实非常好。在最新的Java EE6版本里,这么多年都没有多少重大改动的Servlet终于添加了很多新的功能。下面我们一个一个来看

1. 基于annotation的配置

自从java 5增加了annotation后,现在annotation已经广范用于各种框架,确实减少了很多的编写配置文件的工作。现在终于可以不用写web.xml,只要通过WebServlet, WebFilter, WebListener这几个annotation就可以轻易的配置Servlet, filter和listener了。而且, web.xml已经是可选的了,就算没有它,也一样运行。

2. 异步请求处理

Servlet 3.0 新添加了一个类AsyncContext,可以通过ServletRequest活得。如果调用了ServletRequest的startAsync(),那么这个请求将会被异步处理,这意味着即使当前的线程执行结束,也不会给发送会响应,而是要等到AsyncContext的complete()方法被调用。通过AsyncContext,一样可以取到ServletRequest,和ServletResponse对象,这意味着即使最初Servlet执行的线程执行结束,也一样可以取到那次请求数据和返回客户端数据。

在通常的Servlet设计中,servlet容器会为每个客户端的请求分配一个线程,如果处理请求的过程中需要等待某种很慢的资源,在访问量大的时候,可能导致servlet容器的线程池耗光,从而无法响应后续的请求。有了异步处理的servlet之后,可以将这些线程解放出来,把它们放到一个队列里等待。比起线程,普通的对象还是要占用较少资源的。

3. Web Fragments

Web容器加载的时候,会扫描WEB-INF/lib下的jar, 如果这个jar的有META-INF/web-fragment.xml,那这这个web-fragment.xml内的配置信息也会被使用。这个功能对做web框架很有用。很多web框架都是通过自定义自己的servlet, filter或listener实现的,现在,框架的开发者可以将默认的配置放到自己的jar里,使用者只要把jar丢到WEB-INF/lib/目录下就可以使用框架了,更加方便。通过web-fragment, 可以更容易的对一个war实现部署上的模块化。

4. 动态注册Web application组件

这个功能的意思是指,应用程序可以在运行时注册servlet, filter和listener。ServletContext里多了addFilter(), addServlet()等方法,来给用户调用。很奇怪的是,我只看到注册组件的,没有看到注销组件的,如果只能加不能减的话,那这一功能估计只能看作是通过xml配置的另一种方法。

5. 容器启动回调

如果WEB-INF/lib下有某个jar文件包含META-INF/services/javax.servlet.ServletContainerInitializer文件,且该文本文件内的类名所对应的类实现了javax.servlet.ServletContainerInitializer接口,那么这个类所实现的该接口方法会在容器启动时被调用。 这个接口方法是onStartup(java.util.Set> c, ServletContext ctx) , 其中c是一个类的集合。你必须在这个方法上使用HandlesTypes annation,这个annotation会指定一些类,而所有实现了,集成了或标注了这个些类的类会做成一个结合作为参数传给onStartup方法。说起来复杂,写起来简单:

@HandlesTypes({A.class})
public void onStartup(Set<Class<?>> c, ServletContext ctx){
   //
}

对于上面的例子,c集合里的类要么实现了A,要么集成了A,要么标注了A。
看来这个功能还是为了自定义框架用的。

6. 自定义session cookie

以前Session cookie的名字一般固定为JSESSIONID,现在通过ServletContext可以获得SessionCookieConfig对象,而该对象可以让我们自定义session cookie的名字等属性。

7. multipart支持

编写接受上传文件的程序更容易了,通过HttpServletRequest对象可以获得Part对象,每一个Part代表了上传的一个文件。调用Part.write(String)方法,可以很轻松的把上传的文件保存为参数指定的文件名。

结论

可以看出,这些更改,都无疑使Servlet编程更容易更灵活了。所以还是很期待能在项目中使用Servlet 3.0 。

  • Share/Bookmark