第1 页 共10页12345»...末页 »

并发与线程安全

串行运行时正确的程序在并发运行时可能会出错,这是由于并发运行的多个任务(进程或线程)之间共享了变量。在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/Save/Bookmark

December 18th, 2008王者归来之Seam ?!

王者归来之Seam??!!!

前言:
seam :  以前在项目开发的过程中,你需要引入很多框架(比如:hibernate, jsf , 工作流引擎….) ,  JBOSS说,你们自己做整合,不专业,我来帮你们做了这个工作吧,我来整合,肯定比你们自己整合要好。于此,JBOSS招兵买马,把hibernate的核心团队,JSF规范制定核心团队,EJB3规范制定核心团队等等,都找来当咨询顾问,共讨整合之事,如此诞生了一个新宝宝,取名为SEAM

使用Seam已经有1个多月的时间了,对Seam有一些简单的了解,谈谈我对Seam的一点感想吧。Seam的目标就是把各种优秀的开源系统,J2EE规范,都通过seam这个框架整合起来,从而简化开发工作,提高开发效率。我想这是seam的目标。

seam亮点:

1  JSF和EJB3.0规范实现与整合是亮点之一。

seam实现了SUN公司JSF规范做了不错的工作,在开发JSF应用时,简化了一些JSF。 Seam框架试图简化JSF的开发比如JSF的异常处理,XML配置,JSF组件的调用等等,在开发的时候让JSF和后端的业务逻辑组件(business service bean)达到紧凑,易用的效果。

2 Seam 很有意思的组件管理

我没有去看seam的源代码,但是从seam的介绍文档里有一些简要的介绍,如果有说得不对的地方,仅为交流。
Seam有2种方式来管理组件,一种为了跟JSF,JSP, servlet 等接口,seam 在web.xml 里添加了一个SeamListener,把JSF所需要的组件实例缓存到ServletContext或者HttpSession中,当请求完成或者Session失效时销毁组件实例.

另一种方式是Seam业务组件,这个组件的生命周期管理,类似EJB3规范里规定的方式,Seam业务组件的生命周期管理是否就是EJB3组件规范的实现,还是有自己的管理方式,这些方面的问题,将在以后再去深入了解。
总之,有了对组件的生命周期管理,您不需要去关心事务,并发,同步等问题。我一直都觉得EJB3的组件生命周期管理,比Spring的实现要舒服很多(Spring的组件生命周期管理,还要实现Spring 的InitializingBean,DisposableBean 这2个类,EJB3的组件就是一个很干净的POJO)。

3 Hibernate,Seam中集成的经典JPA

EJB3的JPA规范,作为EJB3的重要组成部分,Seam中对此规范的实现,整合了Hibernate3. 在使用的过程中,好像针对Hibernate3做了一些优化,让您在开发过程中,使用hibernate3更加简单一些了。

EJB2的持久化方案,在过去的几年,被hibernate3之前的版本彻底打败,EJB3开始, 这2家冤家对头,居然走到了一起。在ejb3的JPA规范发布不久,Hibernate就开始支持这一规范, 以前的相互指责,现在变成相互吹捧。

(题外话:其实我在做项目的过程中是很不喜欢hibernate的,不是因为他能提高效率的话,早就抛弃它了。我更喜欢ibatis一些,都是手工写的SQL代码做成映射关系,效率提高很多,可惜他现在对ejb3规范还不支持。虽然开发的时候会多写一点代码,那可是值得的)

Seam真的是王者归来,我见未必。让我们跳出SEAM看seam。

不要看seam的介绍文档说得多好听,好比一个人自己说自己多厉害一样,没用,还是要人家去评价才行。 在你使用seam之前,请仔细评估自己的需求,是否需要使用他。

1,你的项目是否需要使用EJB?

SEAM 在文档里有说明,SEAM也可以开发类似struts+spring+hibernate一样的轻量级架构的系统,但是那是胡说八道。我可以负责任的告诉 你,seam是一个很重的架构,他大多数的技术体系都是偏重于EJB,用他开发SSH类似的系统,只会误事误工。

2, JSF是否适合你的项目?

JSF 是SUN公司主推的一种表示层解决方案。可惜生不逢时,用JSF做企业应用,有Flex,Silverlight等技术比他强。 用JSF做WEB网页方向的应用,JSF更是滑稽可笑。也许有人说,JSF跟Seam有什么关系?但是我告诉你,seam核心目标之一就是整合JSF做表 示层开发,是他整合目标的重要组成部分。如果你打算应用seam,那么表示层的开发就一定绕不过JSF这一关。尽管seam说,他不用JSF也可以,但是 不用JSF,用什么!! 没有其它好的选择了。

3,开发效率低

呵呵,seam说我的开发效率很高嘛,但那是他自己说的嘛,seam开发环境只能在Jboss里运行才有良好的表现,Jboss在开发,调试,部署还是非常麻烦,和tomcat比效率低很多。

4,学习成本

seam引入了很多相关的其它框架包,假设你有良好的JAVA基础,一个未使用过seam的开发团队,熟悉seam起码也要3周以上的时间,这是我见过的学习成本最高的框架,他整合的框架很多,技术面很广.

5,大型系统的解决方案,seam还是无法提供

一个大型的系统,核心的关键点是:负载均衡,集中缓存处理,并行计算, 分布式存取等, seam面对这些问题,一样显得力不从心,这些问题还是需要架构师自己动手解决。而且seam中有一个web beans的技术(我在想,他使用这个是否考虑到整合JSF才这么做呢!),给负载均衡, session的处理还不及其它框架灵活。

Seam是否是归来的王者? 让我们拭目以待!

文章内容只是个人之见,肤浅之处,意在交流。

Share/Save/Bookmark

用消息驱动bean来处理本地的JMS消息太容易不过了,但是如何处理远程的消息呢?翻遍了Java EE手册和API也找不到。原来各种应用服务器都有各自的实现。那么如何让jboss下的消息驱动bean消费远程JMS消息呢?把下面的代码复制到一个文件里,并重名为以-service.xml结尾的文件,放到jboss的deploy目录,就可以在本地得到一个从远程获取消息的JMS provider。

    <mbean code="org.jboss.jms.jndi.JMSProviderLoader" 
name="jboss.mq:service=JMSProviderLoader,name=RemoteJMSProvider,server=remotehost">
        <attribute name="ProviderName">RemoteJMSProvider</attribute>
        <attribute name="ProviderAdapterClass">org.jboss.jms.jndi.JNDIProviderAdapter</attribute>
        <!-- The connection factory -->
        <attribute name="FactoryRef">UIL2XAConnectionFactory</attribute>
        <!-- The queue connection factory -->
        <attribute name="QueueFactoryRef">UIL2XAConnectionFactory</attribute>
        <!-- The topic factory -->
        <attribute name="TopicFactoryRef">UIL2XAConnectionFactory</attribute>
        <!-- Connect to JNDI on the host "the-remote-host-name" port 1099-->
        <attribute name="Properties">
            java.naming.factory.initial=org.jnp.interfaces.NamingContextFactory
            java.naming.factory.url.pkgs=org.jnp.interfaces
            java.naming.provider.url=the-remote-host-name:1099
        </attribute>
    </mbean>



注意,你要把上面的 the-remote-host-name 改成你的。部署完成后,确认在jboss的JndiViewer里能不能看到 java:/RemoteJMSProvider 。然后再MDB类上加上:

    @MessageDriven(activateConfig = {
        @ActivationConfigProperty(propertyName="destinationType", propertyValue="javax.jms.Queue"),
        @ActivationConfigProperty(propertyName="destination", propertyValue="queue/testQueue"),
        @ActivationConfigProperty(propertyName="providerAdapterJNDI", propertyValue="java:/RemoteJMSProvider")
    })
    public class MDB implements MessageListener {
        ...
    }



providerAdapterJNDI属性使这个MDB从刚才部署好的JMS provider那里读取消息。

Share/Save/Bookmark

在jboss集群时,有一种服务被称为高可用单例服务,意思是指在集群中同时肯定有且只会有一个节点提供这个服务。换句话说这种服务只会在一个节点上启动,并且当这个节点不可用时,集群中会有另一个节点代替它,它是一个具有故障恢复能力的单例服务。要实现这种服务,最简单的方法就是把部署单元,如war, ear, jar,等放到jboss/server/all/deploy-hasingleton/ 目录下。但是放在这个目录下的应用是不能热部署,也不具有farm deploy功能的。还好jboss里有一个很特殊的mbean叫 jboss.ha:service=HASingletonDeployer,type=Barrier,利用它可以很容易的实现高可用单例服务。这个bean很特殊,只有在集群的master节点上它才会被启动,而一旦master节点不再是master,它又会停止。所以一个普通的部署单元,只要依赖上了barrier,就会具有高可用单例服务的能力。下面举一个例子。

现在要实现一个高可用的消息驱动bean,监听远程机器上的消息topic/a。如果只在集群中的一个节点上部署这个MDB,一旦这个节点不可用,消息处理就停止了。为了提高可用性,可以在每个节点上部署这个MDB,但这时同一个消息会被每个MDB收到和处理,这不是我们想要的,因为同一个消息应该只被一个 MDB处理。这个时候只要让这个MDB对barrier依赖,再把它部署在所有节点上,那么就只有主节点上的MDB会运行,并且当主节点完蛋时,其他节点变成主节点时,那个节点上的MDB会自动运行。这样我们就实现了高可用单例消息驱动bean服务。

要让mbean对barrier依赖,只要在它的部署文件里填上一行,例如:

    <server>
        <mbean name="xx" code="xx.xx.xxx">
            <depends>jboss.ha:service=HASingletonDeployer,type=Barrier</depends>
        </mbean>
    </server>

对于war,jar,ear,可以在jboss.xml里添加,具体见jboss文档。对于ejb3,加上下面的annotation就好了:

@org.jboss.annotation.ejb.Depends("jboss.ha:service=HASingletonDeployer,type=Barrier")

Share/Save/Bookmark

December 7th, 2008Java EE的不足

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/Save/Bookmark


第1 页 共10页12345»...末页 »
© 2007 涂0实验室 | iKon Wordpress Theme by Windows Vista Administration | Powered by Wordpress