Posts Tagged ‘java EE’

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

JavaEE组件的并发与无状态

January 5th, 2009

并发与线程安全

串行运行时正确的程序在并发运行时可能会出错,这是由于并发运行的多个任务(进程或线程)之间共享了变量。在Java EE环境下很多的组件最终都是在多线程环境下并发运行的,应该牢记住这一点,避免发生诡异的错误。在上个项目中,我们的程序就出现过诡异的错误,在并发量小的测试环境下,它很少出现,但是随着并发量的增大,它出现的次数增多。经查,发现如下类似代码:

    public class SomeServlet extends HttpServlet {
        private SomeType someVariable;

        public void doPost(HttpServletRequest req, HttpServletRespons rsp) {
            method1();
            method2();
        }

        void method1() {
            ...
            someVariable = someValue;
            ...
        }

        void method2() {
            ...
            someVariable = someValue;
            ...
        }
    }

method1() 和method2()都访问到了someVariable,而且程序员的本意是想在method2()使用由method1()产生的 someVariable,但是Servlet可能被多个线程共享,上面的代码不能正常工作。当Servlet容器用thread1和thread2两个线程来服务两个请求request1和request2,而thread1和thread2使用了同一个SomeServlet对象,如果JVM在 SomeServlet在执行到method1()后和method2()前时发生线程间的切换,那么就很可能导致出错,比如:thread1.method1()->thread2.method1()->thread1.method2()->thread2.method2()。实例变量和类变量都是在线程间共享的,而方法内的局部变量和参数是不会被共享的,所以要处理这个问题,最简单的方法是通过方法参数来传递 someVariable,而不是通过实例变量。所以这段程序改成这样就可以了:

    public class SomeServlet extends HttpServlet {

        public void doPost(HttpServletRequest req, HttpServletRespons rsp) {
            SomeType someVariable = method1();
            method2(someVariable );
        }

        SomeType someVariablemethod1() {
            ...
            return someValue;
        }

        void method2(SomeType someVariable) {
            ...
            someVariable = someValue;
            ...
        }
    }

通过synchronized等并发访问控制机制也可以解决这个问题,但是我觉得上面的方式是最直观的。也许有人会说后面的代码比前面的看起来要难看,不够面向对象。这里不讨论怎样才是面向对象,或者面向对象好不好。总之后者是正确的,前者是错误的,没有正确性的程序是没有意义的。

被过线程共享而不会出错的对象,被称之为线程安全的。显然Servlet不是线程安全的,而且还有很多组件也不是线程安全的,比如HttpSession。在上个项目中,我们使用JAXB来处理XML,每次使用时都创建一个JAXBContext对象,后来发现创建JAXBContext的代价是相当的高,于是我们想把它缓存起来在整个应用程序范围内使用,幸好我们使用的JAXBContext的实现是线程安全的,可以放心的被多个线程共享。

有状态与无状态

有状态组件是这样一种组件,组件调用者的返回结果会依赖于这个组件之前或正在受到的调用。无状态的组件则相反,调用者的返回结果只和本次调用相关,所有调用者的调用过程不会影响到其他调用者。举例来说,someComponent组件的getLatestCaller()返回上一次的调用者,这个行为的返回值总会和上一次的调用者相关,这样的组件就是有状态的。假如有个组件math有个方法为add(x,y),返回x+y的值,那么无论之前被哪个调用者调用过,math.add()的返回值只和本次调用的参数相关,这样的组件是无状态的。无状态的组件是线程安全的,因为被别的调用者调用并不会影响到本次的调用结果。

除非有必要,否则应当尽量把你的程序组件实现成无状态的。在spring, seam, ejb里都有无状态的组件。不同的框架在如何实现无状态方面各不相同。最简单的实现方法就是为无状态组件做一个包装,每次调用组件的方法时,都由包装类生成一个新的对象来处理,这样实际上就不再任何的调用者之间共享被调用者的实例,实现了无状态。比如:

    public interface SomeInterface {
        public SomeType doMyBusinness(SomeArg arg);
    }

    public class SomeStatlessComponent implements SomeInterface {
        public SomeType doMyBusinness(SomeArg arg) {
           ...
        }
    }

    public class SomeStatlessComponentWrapper implements SomeInterface {
        public SomeType doMyBusinness(SomeArg arg) {
           return new SomeStatlessComponent().doMyBusinness(arg);
        }
    }

如果实例化组件的代价是昂贵的,用一个对象池缓存组件实例,并在每次从池中取对象时清空对象的状态可能是个更好的办法。其实只要遵循简单的原则,就很容易实现无状态,就是不使用实例变量或类变量,或者只使用只读的实例变量或只读类变量。这样的组件用单例就可以实先无状态。

由于实现上的差别,有些框架根本就不会做过多的努力来保证组件的无状态性,相反他们把责任交给了程序员。作为组件的创作者,如果你确定你要的是无状态的组件,那么只使用只读的实例变量或类变量,是个明智的选择。

  • Share/Bookmark

Java EE的不足

December 7th, 2008

Java EE的发布,尤其是ejb3,让我高兴了一把。改进是显而易见的,但是还不够,经过一年的多的使用,我今天忍不住要发发牢骚。

混乱的jndi

把对象绑定到树形结构的目录上,易于查找和使用,这本来是一个非常好的想法,但是偏偏被搞的非常复杂。在同一个服务器内,你能看到的jndi的形式可能有 /a/b, java:a/b,java:com/env/a/b, 甚至a/b。为什么JCP那帮人就不能把这个名称统一一下呢?部署在应用服务器上的组件,可以有一个jndi,但是Java EE偏偏没有定义将组件映射到一个什么样的jndi名字上,于是在glassfish下,默认的数据源的jndi名是java:/jdbc/db,在 jboss下则是java:/db,就算是ejb,在不同的应用服务器上被部署后都会被分配给不同的名字。@Resource可以把jndi资源注入到组件中,但是在引用组件时却可以通过两个名字,如:@Resource(name=”a/b”) @Resource(mappedName=”a/b”),好讨厌!什么时候jndi能够被统一起来,java EE在易用性上一定会进一大步!

半吊子的依赖注入

当我听到可以通过@Resource @EJB注入资源时,我非常高兴,那岂不是意味着Java 应用服务器,一下子就变成了个spring bean容器了?但是最终的结果很令人失望,因为你只能注入有jndi名字的东西,而且只能将它注入到指定的几种组件中,这样的限制一下子把好好的依赖注入给废了。通过jndi去找组件,比通过spring bean的名字找bean要难多了,而且我们确实需要将组件注入到某个普通的类里面。这样的限制最终导致了在实际项目中,很少会用到Java EE本身的依赖注入功能。

classloader的噩梦

相同的一个类,被不同的classloader载入,那么在jvm中这两个类就不是同一个类。classloader的这一机制有它的道理,比如提高安全性,但是会产生一些很奇怪的问题。假设有两个classloader, cl1和cl2,这两个classloader都载入了同一个class,比如 A.class,其中cl1的A.class实例化了一个对象a1, 那么,当你试图把a1 cast成 cl2的A.class的类型的时候,就会发生ClassCastException,尽管cl1的A.class和cl2的A.class是同一个 class。J2ee规定了每个ear内的library必须能被ear内的每个成员共享,容器生成的ejb的stub也必须能在ear内被共享。通常要实现这个功能,只要给这个ear分配一个classloader,并由这个classloader载入需要被整个ear内共享的class,而ear内的 war,和ejb-jar则由这个ear的classloader的子classloader载入。有了这一机制,在ear内部,ejb的local接口可以被正常的使用而不会出现问题。但是如果跨越ear调用ejb的local接口,就会出现诸如ClassloaderException的异常。跨越 ear使用jpa也会遇到类似的问题。Java EE没有给出统一的解决方案,不同的服务器给出了不同的方案,但还没有一个解决方案算是真正的优美。

MDB不能消费远程的消息

ejb2的时代,同样作为ejb,message driven bean 远不像会话bean和实体bean那样有那么多负面评论。MDB确实很好用,唯一的不足就是没有统一和简单的方法可以让mdb监听远程的jms消息。我对此感到非常纳闷,既然Java EE的标准已经统一了那么多,为何不多进一步,也统一一下如何让MDB访问远程JMS?

或许Java EE标准的制定有太多的政治因素了,那些专家们总是再制造一些半成品。

  • Share/Bookmark

Java EE 的依赖注入功能有限,不能注入到非管束的普通类里面

November 13th, 2007

JavaEE的依赖誉为入功能远没有spring那样强大,只能把特定的资源注入到特定的对象里面。这些特定的资源是指ejb, timer等 java EE的标准服务,其它的乱七八糟的东西是不能注入到别的组件里的。同时,能接受注入的也只有被容器管束的servlet, filter, ejb等标准组件,普通的类也是不能享受到被注入的待遇的。结果现在用struts2时,想在struts2的action里面取得ejb,就不能走注入的这条路了,很不幸呀!怎么办呢?初步想来有这样几种方法,一种是写一个新的struts2的dispathfilter,在这个filter里注入ejb,然后分发到action里,或者写一个struts2的intercepter,用拦截器通过jndi找到ejb,再注入到action里面。嗯……,好像后一种方法相对方便点呢。

  • Share/Bookmark