JSF 2.0检验总结之:同一个JBOSS 5.1.0 jsf 2.0(for primefaces) +JSF 1.2 同时运行的总结

背景:

公司原来有个产品是基于jboss 5.1.0 +jsf 1.2+EJB来开发的,由于jboss 5.1.0默认的提供了对jsf 1.2的支持,所以很容易的进行开发和部署,最近由于产品升级,要求前台需要用primefaces来实现,primefaces是基于jsf 2.0的,同时也要求旧的产品也可以在同一个runtime下运行


问题:

由于相同产品的两个不同实现要在同一个jboss 的ear下运行(jsf 1.2 A.war    jsf 2.0 B.war),考虑到两个war依赖的jar会不同,所以决定分成两个独立的war包,其中A.war中不包含jsf 1.2 相关的jar,而B.war中则包含了jsf 2.0 以及primefaces.jar. 可是在部署的时候总是会报ClassCastException的异常,通过对异常的分析得出的结论是由于B.war 中包含了jsf 2.0的jar,与jboss 5.1.0 中自带的jsf jar 冲突导致.


方案:

关于解决jsf 升级导致的classcast exception最常见的解决方案就是替换掉jboss 5.1.0中与jsf 相关的jar 为jsf 2.0 的jar,然后在部署B.war时,确保B.war 中不包含JSF 2.0 相关的jar,可是考虑到本次产品升级还要求旧的产品也要在一起运行,所以只能采用war 包class loader隔离的方法来实现(指定war的采用自己的class loader,这样就不会有class cast exception).

下面对这两种方案做一个总结

方案一:替换掉jboss 5.1.0 中jsf 1.2 相关的jar

网上这方面的文章比较多,下面总结了调整过后的目录情况(原文:http://forum.primefaces.org/viewtopic.php?f=3&t=13804&sid=3678068fe63d875536f5bd7008b4e855)

1. This is what I have in jboss-5.0.1.GA\server\yourprofile\deploy\jbossweb.sar\jsf-libs

- jsf-impl.jar (2.1.2)
- jsf-api.jar (2.1.2)
- jboss-faces.jar (came with jboss 5.0.1)

2. This is what I have added in jboss-5.0.1.GA\common\lib

- el-api-2.1.2-b03.jar
- el-impl-2.2.jar

2. This is what I have in \someApp\WEB-INF\lib (JSF related)

- primefaces-2.2.1.jar

3. This is my web.xml which is based on Oleg's sample.

<?xml version="1.0"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
   <display-name>EMoveWeb</display-name>
   <context-param>
      <param-name>javax.faces.PROJECT_STAGE</param-name>
      <param-value>Development</param-value>
   </context-param>
   <context-param>
      <param-name>javax.faces.FACELETS_REFRESH_PERIOD</param-name>
      <param-value>-1</param-value>
   </context-param>
   <context-param>
      <param-name>javax.faces.FACELETS_SKIP_COMMENTS</param-name>
      <param-value>true</param-value>
   </context-param>
   <context-param>
      <param-name>javax.faces.SEPARATOR_CHAR</param-name>
      <param-value>_</param-value>
   </context-param>
   <context-param>
      <param-name>javax.faces.VALIDATE_EMPTY_FIELDS</param-name>
      <param-value>true</param-value>
   </context-param>
   <context-param>
      <param-name>com.sun.faces.resourceUpdateCheckPeriod</param-name>
      <param-value>-1</param-value>
   </context-param>
   <context-param>
      <param-name>com.sun.faces.allowTextChildren</param-name>
      <param-value>true</param-value>
   </context-param>
   <context-param>
      <param-name>com.sun.faces.expressionFactory</param-name>
      <param-value>com.sun.el.ExpressionFactoryImpl</param-value>
   </context-param>

   <servlet>
      <servlet-name>Faces Servlet</servlet-name>
      <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
      <load-on-startup>1</load-on-startup>
   </servlet>
   <servlet-mapping>
      <servlet-name>Faces Servlet</servlet-name>
      <url-pattern>*.jsf</url-pattern>
   </servlet-mapping>
   <welcome-file-list>
      <welcome-file>index.html</welcome-file>
      <welcome-file>index.htm</welcome-file>
      <welcome-file>index.jsp</welcome-file>
      <welcome-file>default.html</welcome-file>
      <welcome-file>default.htm</welcome-file>
      <welcome-file>default.jsp</welcome-file>
   </welcome-file-list>
</web-app>

方案二:采用war class loader 隔离的方法
网上有很多关于这方面的总结,思路是通过在war,或者ear中添加jboss-web/app.xml的方法来实现加载器的分离,可是不知道是实际jboss版本的问题,总是达不到想要的效果
下面为比较好的文章转载:原文:https://community.jboss.org/wiki/ClassLoadingConfiguration

Discussion

 

By default JBoss (prior to version 3.2) uses a flat class loading model that avoids the need to redundantly include classes in different layers.  WAR files would only have the web contents and servlets, EJBs their interfaces, implementations and types, etc. Canonical packaging works correctly with this model.

 

However, if you have applications that cannot share classes because of version conflicts, then you need to isolate the classes from other deployments. Without isolation, common errors seen are ClassCastException, IllegalAccessErrors, VerifyErrors and in general, strange behavior that changes as new deployments are added and removed.

 

There are two levels of scoping, isolation from other deployments, and isolation that overrides the loading of JBoss server classes.  With nested modules, only the top level file may specify class loader scoping. If you have a .ear file containing other modules, only scoping specified in the .ear's META-INF/jboss-app.xml is used. This also applies for any other deployment which contains sub-deployments.  For example, if a .sar contains a .war deployment, only the .sar META-INF/jboss-service.xml scoping has effect.

 

Specifying Isolation

 

For .ear files, in your jboss-app.xml, add the following descriptor fragment construct to enable scoped class loading:

 

<jboss-app>
   <loader-repository> 
   com.example:archive=unique-archive-name 
   </loader-repository> 
</jboss-app>

 

 

For .war files, in your jboss-web.xml, the following template applies:

<jboss-web>
   <class-loading> 
      <loader-repository>com.example:archive=unique-archive-name</loader-repository> 
   </class-loading>
</jboss-web>

 

 

Note for AS 4.2.x and versions till 5.x: As of at least JBoss 4.2.1, the <class-loading> tag appears to no longer be supported as isolation is ignored. Instead, the following configuration appears to accomplish the same goal:

 
<jboss-web>
   <loader-repository> 
      com.example:archive=unique-archive-name 
   </loader-repository> 
</jboss-web>

Interestingly enough, 4.0.5 seems to support both configurations.*

 

 

For .sar files, in your jboss-service.xml, the following template applies:

<server>
     <loader-repository>
          com.example:archive=unique-archive-name 
     </loader-repository>
</server>

 

 

The <loader-repository> elements must be correctly placed within the respective

<jboss-app>, <jboss> and <jboss-web> XML elements.

Check the corresponding DTD found in the docs/dtd directory of the distribution for the complete content model.

 

Note for 5.x and later versions: The above mentioned configuration might still work with the 4.0.x till 5.x version due to their xsd/dtd, but for versions 5.x and above the old configuration still applies for jboss-web.xml:

 

<jboss-web>
   <class-loading> 
      <loader-repository>com.example:archive=unique-archive-name</loader-repository> 
   </class-loading>
</jboss-web>

 

The com.example:archive=unique-archive-name strings are JMX ObjectName

strings. These have no particular significance other than that they must be unique.

It might be useful to use the same name as used for the .ear, .war, or .sar file.

For example, for a petstore.ear file, use: com.example:loader=petstore.ear as the repository name.

 

The loader-repository ObjectName will appear in the JMX-Console (http://localhost:8080/jmx-console/).

This MBean is great for debugging any class-loading issues which might arise.

The hierarchical loaders created from the repository wll appear together under the loader-repository domain name,

com.example in the example.

 

 

 

Isolation with Overriding Server Classes

 

Use the following constructs to enabled scoped class loading with the deployment classes overriding the server classes.

 

For jboss-app.xml:

 

 
<jboss-app>
  <loader-repository> 
  com.example:archive=unique-archive-name 
     <loader-repository-config> 
     java2ParentDelegation=false 
     </loader-repository-config> 
  </loader-repository>
</jboss-app>

 

 

For jboss-web.xml:

<jboss-web>
   <class-loading java2ClassLoadingCompliance="false">
      <loader-repository>
         com.example:archive=unique-archive-name
         <loader-repository-config>java2ParentDelegation=false</loader-repository-config>
      </loader-repository>
   </class-loading>
 ...

Note: See comment above regarding web class loader isolation.

 

For jboss-service.xml:

<server>
   <loader-repository>
      com.example:archive=unique-archive-name
      <loader-repository-config>java2ParentDelegation=false</loader-repository-config>
   </loader-repository>
 ...

 

The isolated EAR or WAR repository will load its libraries in this order:

 

  1. WEB-INF/lib (for WARs)

  2. libraries in server/default/lib

  3. tomcat-libraries in server/default/deploy/jbossweb-tomcat50.sar (jboss-3.2.6).

 

The libraries in server/default/lib get mixed together with jbossweb-tomcat50.sar in no specific order (for details look into the loader-repository in the JMX-console).

 

The Web Container

 

In jboss-3.2.3, the jbossweb-tomcat41.sar is configured to use a unified class loader as the web application class loader. This is controlled by the UseJBossWebLoader attribute in the jbossweb-tomcat41.sar/META-INF/jboss-service.xml descriptor. The use of a unified class loader means that the classes available in the war inside of the WEB-INF/classes and WEB-INF/lib are incorporated into the default shared class loader repository. This may not be what you want as its contrary to the default servlet 2.3 class loading model and can result in sharing of classes/resources between web applications. You can disable this by setting this attribute to false.

 

The Web Container from 4.0.2

 

From 4.0.2 JBoss has changed to the Servlet spec classloading model, i.e. it uses the Tomcat classloader. See the related JIRA Task

 

Simplified Configuration from 3.2.4

 

From JBoss-3.2.4 the EAR deployer provides a simplified version of the isolation. You can configure all your ears to be in isolated classloader spaces using call by value for remote interfaces.

 

The EARDeployer is configured in conf/jboss-service.xml for versions 3.x and in deploy/ear-deployer.xml for versions 4.x+

 

 <!-- EAR deployer, remove if you are not using Web layers -->
 <mbean code="org.jboss.deployment.EARDeployer" name="jboss.j2ee:service=EARDeployer">

    <!-- Isolate all ears in their own classloader space -->
    <attribute name="Isolated">true</attribute>

    <!-- Enforce call by value to all remote interfaces -->
    <attribute name="CallByValue">true</attribute>
 </mbean>

 

Accessing EJBs in isolated ears

 

If you want to make JNDI lookups across EARs that are isolated, you need to force

marshalling of the lookup, e.g. if you have the following ejb-ref in your ejb-jar.xml

that references an EJB in an isolated EAR

 <?xml version="1.0" encoding="UTF-8"?>
 <!DOCTYPE ejb-jar PUBLIC
    "-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 2.0//EN"
    "http://java.sun.com/dtd/ejb-jar_2_0.dtd">

 <ejb-jar>
   <enterprise-beans>
     <session>
         <ejb-name>SessionA</ejb-name>
         <home>org.jboss.test.isolation.interfaces.a.SessionAHome</home>
         <remote>org.jboss.test.isolation.interfaces.a.SessionA</remote>
         <ejb-class>org.jboss.test.isolation.ejb.a.SessionAEJB</ejb-class>
         <session-type>Stateless</session-type>
         <transaction-type>Container</transaction-type>
         <ejb-ref>
            <ejb-ref-name>ejb/SessionB</ejb-ref-name>
            <ejb-ref-type>Session</ejb-ref-type>
            <home>org.jboss.test.isolation.interfaces.b.SessionBHome</home>
            <remote>org.jboss.test.isolation.interfaces.b.SessionB</remote>
         </ejb-ref>
     </session>
   </enterprise-beans>
 </ejb-jar>

 

In jboss.xml use the full scheme to define the lookup:

 <?xml version="1.0" encoding="UTF-8"?>

 <jboss>
    <enterprise-beans>
       <session>
         <ejb-name>SessionA</ejb-name>
         <ejb-ref>
            <ejb-ref-name>ejb/SessionB</ejb-ref-name>
            <jndi-name>jnp://${jboss.bind.address:localhost}:1099/SessionB</jndi-name>
         </ejb-ref>
      </session>
    </enterprise-beans>
 </jboss>

 

You can also use the full scheme in lookups from non-EJB clients such as standalone applications or JSP pages. In this case you would be encoding deployment knowledge into the code - the applications will always use call-by-value even if your needs change. If you are retro-fitting classloader isolation you may also have a lot of lookup code to change, although you should probably have used a constant or lookup service to hold the actual name.

 

An alternative is to force all JNDI lookups to be call-by-value, which you can do in

/deploy/naming-service.xml (This used to reference

/conf/jboss-service.xml, which is WRONG for 4.0.3) in the section headed JNDI. Change the jboss:service=Naming bean

definition so that the CallByValue attribute reads:

 


<mbean code="org.jboss.naming.NamingService"
      name="jboss:service=Naming"
      xmbean-dd="resource:xmdesc/NamingService-xmbean.xml">

  ...

  <attribute name="CallByValue">true</attribute>

  ...

</mbean>

 

This is indiscriminate - JBoss will be unable to optimise any JNDI calls any more, but may be appropriate in some circumstances.

 

Performance note - Call By Value

 

The use of call by value and marshalling is very inefficient. It typically means

method invocations take 10 times the cpu. Why? Compare Call By Value with Call By Reference

 

Call By Reference

  1. caller does ejb.someMethod()

  2. invocation is passed through ejb container

  3. container does bean.someMethod()

  4. result is returned to the caller

 

Call By Value

  1. caller does ejb.someMethod()

  2. invocation is marshalled - the parameters are turned into ObjectStream (a byte[])

  3. container with a different classloader unmarshalls - byte[] -> java Objects - including classloading

  4. invocation is passed through ejb container

  5. container does bean.someMethod()

  6. result is marshalled - the return value is turned into ObjectStream

  7. result is unmarshalled using the caller's classloader - byte[] -> java Object

  8. result is return to the caller

 

The marshalling and unmarshalling uses a lot of cpu.


最后总算是找到了一个最为简单又有效的办法,只需轻轻松松三部,轻松搞定class loader 隔离
终结版方案 原文:http://stinkycheese.ajexperience.com/2010/10/11/jboss-5-with-jsf-2-on-a-per-app-basis/

Explanation

Searching for “JBoss 5 with JSF 2” results in a bunch of messages explaining that “it can’t be done,” or “it shouldn’t be done,” or some other nonsense like “just remove JSF from JBoss” which obviously will break any already existing web application and is not an option in a production environment. The following is the solution and allows a web app in JBoss 5.0.1GA to use JSF 2 while leaving the built in JSF 1.2 on JBoss alone. So I think this is what people are actually looking for when they search for “JSF 2 with JBoss 5.” Note: I haven’t tried getting this to work with “MyEclipse,” only plain old Eclipse.

  1. Add the following three things to web.xml
    <!-- JSF 2: this tells JBoss 5 to use the jsf jars included in the app -->
    <context-param>
        <param-name>org.jboss.jbossfaces.WAR_BUNDLES_JSF_IMPL </param-name>
        <param-value>true </param-value>
    </context-param>

    <!-- JSF 2: this needs to be included to avoid an error message -->
    <context-param>
        <param-name>com.sun.faces.injectionProvider </param-name>
        <param-value>org.jboss.web.jsf.integration.injection.JBossInjectionProvider </param-value>
    </context-param>

    <!-- JSF 2: use pages named .xhtml instead of .jsp. -->
    <context-param>
        <param-name>javax.faces.DEFAULT_SUFFIX </param-name>
        <param-value>.xhtml </param-value>
    </context-param>
  2. Download the latest version of JBoss 6 (which I’ve heard you should upgrade to as soon as practical).
  3. Copy the following jars from JBoss 6 into the WEB-INFlib directory.
    jboss-6.0.0.20100911-M5commonlibjboss-mc-int-servlet.jar
    jboss-6.0.0.20100911-M5serverdefaultdeployersjsf.deployerMojarra-2.0jsf-libsjboss-faces.jar
    jboss-6.0.0.20100911-M5serverdefaultdeployersjsf.deployerMojarra-2.0jsf-libsjsf-api-2.0.2-FCS.jar
    jboss-6.0.0.20100911-M5serverdefaultdeployersjsf.deployerMojarra-2.0jsf-libsjsf-impl-2.0.2-FCS.jar
  4. Rejoice in JSF 2 goodness.

Notes

If you read the stuff you were copying and pasting then you’ll notice that .xhtml is used instead of the default .jsp. The additional information above should be everything necessary to get a basic example to work – the one at http://www.coreservlets.com/JSF-Tutorial/jsf2/#Getting-Started is the one I used.

Common Error Messages

You may run into these when trying to use JSF 2 on JBoss 5 without doing what’s listed above.

  • java.lang.ClassCastException: com.sun.faces.config.WebConfiguration cannot be cast to com.sun.faces.config.WebConfiguration
    • Make sure all entries exist in the web.xml. This means JBoss found both versions of JSF and doesn’t know which one to use. JBoss 5 has JSF 1.2 as part of it’s normal libs, so including JSF 2 with an app will cause problems unless we tell JBoss to only use the jars from our app.
  • java.lang.NoClassDefFoundError: org/jboss/mc/servlet/vdf/spi/VDFConnector
    • Make sure all of the specified jar files were copied and are included. JBoss is saying “what is this class VDFConnector? Never heard of it..
  • java.lang.NoClassDefFoundError: (anything)
    • Make sure all of the specified jar files were copied and are included.
  • JSF1029: The specified InjectionProvider implementation ‘org.jboss.web.jsf.integration.injection.JBossInjectionProvider’ does not implement the InjectionProvider interface.
    • Make sure all entries exist in the web.xml. I have no idea why we need to specify the exact thing it can’t find in order to make it work. Any clarification is encouraged.
  • java.lang.IllegalStateException: Application was not properly initialized at startup, could not find Factory:
    • Try adding
      <listener>
                <listener-class>com.sun.faces.config.ConfigureListener </listener-class>
      </listener>

      to the web.xml. This was contributed by Kley in the comments. Let me know if it works..

Credits

The WAR_BUNDLES_JSF_IMPL option is from Shivaji Byrapaneni at http://community.jboss.org/message/54085.
I can’t find the URL where I found the JBossInjectionProvider option.
The fact that the jboss-mc-int-servlet.jar is necessary is from http://community.jboss.org/thread/2656.
The rest is from me.




你可能感兴趣的:(JSF 2.0检验总结之:同一个JBOSS 5.1.0 jsf 2.0(for primefaces) +JSF 1.2 同时运行的总结)