Weblogic中类的加载和应用的打包

最近工作,开发的环境是tomcat,而部署的环境是weblogic。由于工作不久,经验不足,tomcat上正常部署的应用到了weblogic,就会经常出现莫名的异常。有时候上网找一下,想碰碰运气,看下有没有解决方法,然后往往是一找就找个一两天,然后还没有解决,说实话,有时候真想把电脑给砸了。因为weblogic不像是自己开发的东西,自己开发的东西,出现问题,还知道是什么导致的,人家写的东西,出问题了,就只能碰运气了。后来想了想,我觉得与其浪费时间去网上找解决方法,不如认真学习一下weblogic它是怎样来组织自己的工作的,而刚好,看到下面的方章,所以小弟就献丑翻译一下(红色字体为自己的理解),希望大家指出我的错误。废话就不多说的,进入正题吧。


原文地址:http://middlewaremagic.com/weblogic/?p=6725

参考文章:http://www.ibm.com/developerworks/cn/java/j-lo-classloader/index.html

http://edocs.weblogicfans.net/wls/docs92/programming/classloading.html (这篇文章是翻译后看到,里面的内容好像差不多哭)

下面是翻译:

----------------------------------------------------------------------------------------------------------

类的加载和应用的打包

我们首先介绍类的加载,然后我们在解释类加载概念的时候,会给出一个自定义类加载器的例子。随后,我们会知道如何打包一个企业级的应用以及APP-INF/lib, bundled libraries, shared libraries等等的作用和区别。这样,我们就可以通过合适的方法找到我们的类,而不至于出现ClassNotFoundExceptions和NoClassDefFoundError这些过去经常遇到的问题。

关于类加载的介绍

除了引导类加载器(bootstrap class loader)以外,所有的类加载器都有父类加载器。而且,所有的类加载器都继承于java.lang.ClassLoader类型。最重要的一点,就是必需设置正确父类加载器。父类加载器是一个已经加载的类加载器实例(类加载器本身就是一个类)。

类和接口的加载

加载是指查找具有特定名称的二进制格式的类或接口类型这样一个过程,也许是通过某种方式来计算的,但更典型的,应该是检索先前从编译器编译源代码而得来的的二进制,而Class对象以这个二进制的格式来表示类或接口。而完成加载的过程是通过ClassLoader和它的子类来完成的。不同的ClassLoader子类可能会实现不同的加载策略。特别是,一个类加载器会缓存这些用二进制表示的类和接口,通过他们的预期使用量预先加载这些类或加载时将相关的类一起加载进来。对于正在运行的应用程来说是不完全透明的,举个例子,一个新版本的类可能会找不到,这是因为这个类已经加载并且缓存了(这个就解释了为什么我们在修改了类里的一些方法,应用需要重启)。虽然这些都是类加载器的责任,但是,在某些类还没有预加载或关联加载的地方就可能出现由反射加载导致的错误。
类和接口的链接
链接的过程就是拿一个类或接口的二进制流,然后将其转化成Java虚拟机的运行状态,这样它就可以被执行了。类或接口必需在链接前就要加载。链接过程中会有三种不同的处理:验证、准备和解析符号引用。
  • 验证二进制流 —— 验证过程保证了用二进制来表示的类和接口的结构是正确的。例如,它会校验每一个指令是否有一个正确的操作数;它会检查每个分支是否在另一个分支之后,而不会是交叉;它会检查每个方法是否提供了一个结构正确的签名;并且每个指令都要符合Java虚拟机的语言规则。
  • 准备类或接口类型 —— 准备阶段包括了为类或接口创建静态变量(类变量和常量)和初始化这些域的默认值。这并不需要执行任何的源代码。显示初始化这些静态域是作为初始化的一部份,并不是准备阶段的一部份。为了使之后对类和接口的操作更高效,Java虚拟机将在准备阶段先实现一些数据结构。一个特别有用的数据结构就是”方法表“或者其他的结构,这个结构可以节省一个实例执行方法的时间,因为它不需要去搜索它的父类。
  • 解析符号引用 —— 一个类或接口会用符号来引用其他的类或接口中的方法、域和构造器。对于这些域和方法,它们的符号引用会包括定义它们的类的名称,它们本身的名称以及其他一些需要的信息。需要经过解析后,符号引用才可以用,所以这个阶段会检查它的符号引用的正确性,某些情况下,将会用直接引用来替换符号引用,因为在重复使用的时候,直接引用的效率会更高。
类和接口的初始化
初始化类包括了执行静态初始化块和初始化静态变量(类变量)。初始化接口包括了对域(常量)执行静态初始化块。在一个类被初始化时,它的父类必需先初始化,不过实现接口的类还没有被初始化。相似的,在一个接口初始化之前,它的超接口并没有被初始化。当出现下面的几种情况时一个类或接口T将被立即初始化:
  • 当T被创建时,并且T是一个类。
  • 当T的一个static方法被调用时,并且T是一个类。
  • T声明的一个静态变量被赋值。
  • T声明的一个静态域被使用时,并且这个引用不是一个编译时常量。
     引用编译时常量必需在编译时就将值做一份拷贝,所以这样的域并不在初始化阶段。
调用Class或包 java.lang.reflect里的反射方法,一样会导致进行初始化操作。在其他情况下,类或接口将不会被初始化。
例子
自己去实现一个类加载器的目的就是为了控制JVM的类加载行为。识别一个类是通过包名和类名。对于实现了 java.io.Serializable的类,serialVersionUID在控制类的版本中扮演了一个重要的角色。该流的唯一标识符是一个64位的哈希值的类名,接口名,方法和字段。如果上述各方面都匹配的话,类是认为是相同的版本。重写类加载器的理由如下:
  • 想从其他库加载类。这是最常见的情况下,我们可能希望从其他地方来加载类,例如,通过网络连接。
  • 去分隔用户的代码。这种情况在Java客户端不经常使用,但广泛使用在servlet引擎中。
  • 想卸载类。如果应用程序创建了大量的类,但是却只在一段时间内使用,这种情况下自定义类加载器是有用的。因为一个类加载器维护一个高速缓存,它已经加载的类,不能被卸载,直到类加载器本身不再被引用(使用)的。因为这个原因,系统和扩展类从来不会被卸载,但是可以卸载应用程序的类,因为自己写的类加载器可以不被再使用。
下面的是一个类加载器的例子
public class VideotheekClassLoader extends ClassLoader { 
    private String root = "/home/oracle/temp"; 
    public VideotheekClassLoader() { 
 	  super(VideotheekClassLoader.class.getClassLoader()); 
    } 

    @Override 
    protected Class<?>; findClass(String name) throws ClassNotFoundException { 
        byte[] bytes = findClassBytes(name); 
        if (bytes == null) { 
            throw new ClassNotFoundException(name); 
        } else { 
            return defineClass(name, bytes, 0, bytes.length); 
        } 
    } 
    private byte[] findClassBytes(String className) { 
        String path = root + File.separatorChar + className.replace('.', File.separatorChar) + ".class"; //得到.class文件的路径
        InputStream inputStream = null; 
        try { 
            inputStream = new FileInputStream(path); 
            byte[] bytes = new byte[inputStream.available()]; 
            inputStream.read(bytes);//将.class文件一个字节一个字节的读到bytes缓存
            return bytes; 
     	} catch (FileNotFoundException e) {
            e.printStackTrace(); 
        } catch (IOException e) {
            e.printStackTrace(); 
        } finally { 
            try { 
                if (inputStream != null) { 
                    inputStream.close(); 
                } 
            } catch (IOException e) { 
                e.printStackTrace(); 
            } 
        } 
        return null; 
    } 
} 
如果父类加载器设置正确,当一个类请求ClassLoader的实例时,它首先会询问父类。如果父类不能找到它(这又意味着其父类也不能找到此类),并且如果调用findBootstrapClass方法也失败,那么我们自己写的findClass方法将被调用。

findClass的默认实现会抛出一个ClassNotFoundException异常。我们在自定义类加载器的时候,一般是重写findClass这个方法。自定义类加载器需要获取字节码的来源位置。这些来源可以是文件系统,网络URL,数据库或可以生成兼容且符合规范的Java字节码的任何地方。一旦获取了字节码后,就应该调用defineClass方法。运行时环境会指定特定的ClassLoader实例调用此方法。虽然来源位置相同,但是两个不同的ClassLoader实例定义的字节码是不相同的。一个类的加载可以被分解成三个阶段:
  1. 加载 —— 包括定位所需的类文件(通过搜索相对应的classpath)来加载字节码。在JVM加载过程中会定义一个基本的内存结构给Class对象。在这个阶段没有处理类的方法,字段和此类引用的其他类。因此类是不可用的。
  2. 链接 —— 可细分为三个主要阶段:字节码验证 - 类加载器对字节码做检查,以确保它是正确的。准备 - 这个阶段准备必要的数据结构来表示第一个类中定义的字段,方法和接口的实现。 解析 - 类加载器会加载所有引用类。通过以下一些方法可以引用其他类:父类,接口,字段,方法签名,方法中使用的局部变量。
  3. 初始化 —— 在这个阶段,任何包含在一个类中的静态初始化代码段将被执行。在这个阶段结束时,静态字段将赋上了默认值。
当这三个阶段结束时,一个类就被加载完全了,并且可以使用了。

Java运行时每一个类都会有一个对象来代表自己,这个对象就是java.lang.Class的实例。当我们编译Java文件时,编译器会在编译出来的字节码中嵌入一个public static final的java.lang.Class类型的属性。由于这是一个公共的静态变量,所以我们可以通过点号来访问它,例如java.lang.Class clazz = ClassLoaders.class。一旦类被载入JVM,同一类就不会被加载了。这意味着一个类加载到JVM中就会有一个特定的身份标识。在Java中,类是通过完整的类名来定义的。完整的类名包括包名和类名。确定一个JVM中的类,是通过全类名和是哪个ClassLoader加载这个类来区分的。

在JVM中,一个类是被java.lang.ClassLoader的实例加载的。每当一个新的JVM启动,当键入命令java MyStartClass时,引导类加载器将负责加载关键的Java类。运行时所需要的类都被打包到jre / lib/ rt.jar中。引导类加载器是一个native方法,所以根据本地的情况它的行为可能会不一样。由于是native方法,所以当我们通过核心Java类,例如String.class.getClassLoader()来获取类加载器,会得到null。我们可以存储扩展库到系统属性java.ext.dirs指定的路径。 sun.misc.Launcher$ ExtClassLoader负责加载所有扩展目录中的JAR文件。对于开发者来说,最重要的一个类加载器是sun.misc.Launcher$ AppClassLoader。这个类加载器负责加载所有由系统属性java.class.path所指定的目录里面的所有类。

java.lang.Thread类,包含方法getContextClassLoader(),该方法会返回一个特定的线程上下文类加载器。上下文类加载器提供了线程在代码中需要动态加载类和资源的时候。如果没有设置,默认的上下文类加载器是它的父线程。原始线程的上下文类加载器通常被设置来加载应用程序。

让我们来看看一个例子,它使用不同的类加载器来加载


package classloaders;

import java.lang.management.ManagementFactory;

import java.lang.management.MemoryPoolMXBean;

import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;

import java.util.Iterator;

public class Test {
    public Test() {
    }

    public static void main(String[] args) {
        testClassLoadingStuff();
	printClassLoaderInfo();
        memoryUsageInformation();
	testClassNotFound();
        testNoClassDefFound();
        testClassCast();
    }

    public static void testClassLoadingStuff() {
        VideotheekClassLoader loader1 = new VideotheekClassLoader();
        VideotheekClassLoader loader2 = new VideotheekClassLoader();

        Class clazz1 = null;
        Class clazz2 = null;
        Class clazz3 = null;
        try {
            clazz1 = loader1.loadClass("classloaders.Test");
            System.out.println(clazz1.getName() + "@" + clazz1.hashCode() + " loaded by " + clazz1.getClassLoader());

            clazz2 = loader1.loadClass("classloaders.Test");
            System.out.println(clazz2.getName() + "@" + clazz2.hashCode() + " loaded by " + clazz2.getClassLoader());

            System.out.println(clazz1.equals(clazz2));
            // Gives true: fully qualified class name is equal and also loaded by the same class loader instance

            clazz3 = loader2.loadClass("classloaders.Test");
            System.out.println(clazz3.getName() + "@" + clazz3.hashCode() + " loaded by " + clazz3.getClassLoader());

            System.out.println(clazz1.equals(clazz3));
            // Gives false: fully qualified class name is equal but the clazz3 instance is loaded by a different class loader instance
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    private static void printClassLoaderInfo() {
        System.out.println("CLASSLOADER OF THIS CLASS " + Test.class.getClassLoader());
        System.out.println("CONTEXT CLASSLOADER " + Thread.currentThread().getContextClassLoader());
        System.out.println("BOOTSTRAP CLASSES " + System.getProperty("sun.boot.class.path"));
        System.out.println("EXTENSION CLASSES DIRECTORY " + System.getProperty("java.ext.dirs"));
        System.out.println("USER CLASS PATH " + System.getProperty("java.class.path"));
        System.out.println("JAVA HOME " + System.getProperty("java.home"));
    }

    private static void memoryUsageInformation() {
        System.out.println(ManagementFactory.getMemoryMXBean().getHeapMemoryUsage());
        System.out.println(ManagementFactory.getMemoryMXBean().getNonHeapMemoryUsage());

        Iterator<MemoryPoolMXBean> iterator = ManagementFactory.getMemoryPoolMXBeans().iterator();
        while (iterator.hasNext()) {
            MemoryPoolMXBean memoryPoolMXBean = iterator.next();
            System.out.println(memoryPoolMXBean.getName());
            System.out.println(memoryPoolMXBean.getType());
            System.out.println(memoryPoolMXBean.getUsage());
            System.out.println(memoryPoolMXBean.getPeakUsage());
            System.out.println(memoryPoolMXBean.getCollectionUsage());
        }
    }

    private static void testClassNotFound() {
        URLClassLoader loader = null;
        try {
            loader = new URLClassLoader(new URL[]{new URL("file:///c:/temp")});
            loader.loadClass("some.where.SomeClass");
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    private static void testNoClassDefFound() {
        // create a new class
        // reference this class
        // compile the code
        // remove the class
    }

    private static void testClassCast() {
        try {
            doSomethingWithSomething("blabla");
        } catch (ClassCastException e) {
            e.printStackTrace();
        }
    }

    private static void doSomethingWithSomething(Object something) {
        Double result = (Double) something;
        System.out.println(result);
    }
}

一个类可以有两种加载方式 —— 显式或隐式。当一个类用下面的方式加载就是显式的类装载:
  • classLoader.loadClass();
  • Class.forName();
当调用这些方法时,类的名称将被作为一个参数传递给类加载器加载。如果类已经被加载,则会返回该类的引用,否则类加载器将通过代理模式加载。隐式的类加载发生在当引用,实例或继承了一个类时。在上面每种情况下,加载都会悄悄的进行并且JVM会解析必要的引用和加载相关联的类。

类加载器会保证在JVM中的类的一致性。换句话说,当两个类加载器加载不同的类(即不同的字节码)但是名称相同,类加载器会保证它们之间不会有任何类型匹配。当有以下四个条件时,就违反了类装载器的约束了:

  • 存在一个类加载器L,这个L已经用来加载一个类C名为N。
  • 存在一个类加载器L',这个L'已经用来加载一个类C'名为N。
  • 通过集合(传递闭包)来确定它们的等价关系N L = N L'
  • C != C'
避免出现类冲突的最简单的方法,就是系统中只保存类的一个副本,但有时候却需要有多个不同版本的类。有一种方式可以避免违反约束的同时,还可以部署多个版本的类,就是使用对等的类加载模型。对等的类加载不遵循传统的继承代理结构去加载。相反,它有一组彼此没有关系的类加载器,除非他们具有相同的父加载器(通常是系统类加载器)。这些类加载器不仅可以代理他们的父类,而且可以代理跟它们一样的类。这种类加载器结构允许独立的类存在于同一个JVM内,因此,它非常适合于以组件方式来开发的产品。

ClassNotFoundException异常是最常见的异常。它发生在加载阶段。当程序试图用下面的方法来加载类时会抛出这个异常:

  • Class类的forName方法。
  • ClassLoader类的findSystemClass方法。
  • ClassLoader类的loadClass方法。
不过没有找到所指定名称的类的定义。通过抛出一个ClassNotFoundException异常,类加载器会通知我们字节码文件不存在。有可能是在一个系统中,也有可能是类加载器对此类不可见。这是因为类加载器只可见被它加载或父类加载的类。在类加载的代理模式中,类加载器只能向上看,不能向下看,就是说只能看父类加载器加载的类,不能看子类加载器加载的类。

NoClassDefFoundError异常的抛出则是因为JVM或者ClassLoader的一个实例尝试去加载一个定义的类但是这个定义的类并没有找到。类定义的搜索是出现在,当前类虽然编译了,但却不到该类的定义。从本质上讲,这意味着出现NoClassDefFoundError表明隐式类加载不成功。

抛出ClassCastException异常则是因为出现了不兼容的类型。该异常表明程序试图将其强制转换为子类的实例对象。类加载器对被测试的类和被强制转化的类做以下规则的检查:
  • 对于一个正常的对象(非数组);这个对象必需是目标对类或其子类的一个对象。如果目标是接口,则会考虑实现了这个接口的一个子类。
  • 对于一个数组类型;目标类必需是一个数组类型或者是java.lang.Object,java.lang.Cloneablejava.io.Serializable
如果上面的两条规则都违反了,那么类加载器就会抛出一个ClassCastException异常。所以在做强制类型转化的时候,用instanceof就显示非常有意义了,至于数组则用泛型。

现在我们已经对类的加载有了一个基础的认识了,那现在我们就学一下如何给应用程序打包以使我们的应用可以找到我们的类。

应用程序的打包
我们有很多的方法去打包和部署一个包含EJB或Web模块的应用。
  • 独立模块 —— 直接部署EJB归档和Web归档文件到服务器,作为独立的应用。 Web组件可以使用远程业务接口访问EJB组件。
  • 企业归档文件 —— 将EJB或Web模块打包成企业归档文件(.ear)。这样的部署对于服务器是作为一个独立的单元。
  • 以目录的方式 —— 将EJB和Web模块打包成目录结构,然后作为一个单独单元部署到服务器。这种部署方式是Weblogic对Java EE的一种扩展支持。
第一种方式是不推荐的,因为它是将EJB归档和Web归档文件作为一个独立的JavaEE应用。也就是每一个应用都会被彼此独立的类加载器所加载。因为每一个类加载器是独立的,所以一个类加载器所加载的所有类对于另一个类加载器来说是不可见的。对于Web应用程序中调用EJB,它必须包含相关的EJB客户端类。因为这些类的加载都是通过没有关系的类加载器加载的,所以模块之间的通信必须使用远程接口,或者RMI序列化。这种序列化的开销进行远程网络调用时,小到可以接受的,但对于本地频繁的通信则是昂贵的。

被用在彼此分开的应用的类加载器,都是Java System classLoader的一个子类。被system classLoader加载的所有类(通过CLASSPATH环境变量),都会被用在服务器进程上。子classloader的一个组件,可以引用父classloader里的所有类,这就表明EJB和Web模块都可以访问system classloader所加载的所有类。所以,这里有一个选择就是可以把jar文件放到system的classpath里,而不用把这些jar文件放到每一个EJB模块中。这里有两个理由去不用system classloader去加载应用中的类:
  • 如果我们把应用中的类放到了system classpath下,那么我们就不能重新部署了,这是因为通过system classloader加载的类并不能移除和重新加载。如果我们确实需要重新加载由system classloader加载的类,那些我们就不得不重启server来使我们的修改生效。
  • 父classloader加载的类,并不能直接引用子classloader加载的类。如果任何的system classloader所加载的类,要尝试使用在EJB或Web模块中的类,我们会得到一个ClassNotFoundException或者NoClassDefFoundException。如果这个问题通过不断的将更多的类移到system classpath下,那么我们很快就会发现,应用中所有的类都在system classpath下了,这样我们将不可能热部署了。
所以我们推荐仅仅是用到系统级别的类才放到system classpath里让system classloader去加载,例如,JDBC的驱动程序类。甚至于应用中的框架类都不要放到system classpath中。Weblogic用了一个分开的classloader分别加载domain目录下的lib目录。这个classloader是system classloader的一个子类,是应用程序的classloader的一个父类。所有的类或资源放到这个lib目录下,将自动对此domain下的所有应用可见。然而,domain下的lib的classloader并不支持重新部署和动态重新加载,并且不能直接引用应用中的classloader。所以domain下的lib就相当于system lasspath了,所以不要把应用中的类放到这里。

另外的两种部署方式创建了一个企业的归档文件包括了EJB和Web归档文件。它们的不同之处仅仅是最后打包的步骤:企业级应用应该用展开目录的方式还是单独打成一个归档文件(.ear)的方式?
一个企业级的标准目录结构和内容应该如下所示:

Application.ear
	APP-INF/lib
	META-INF
		application.xml
		weblogic-application.xml
		weblogic-diagnostics.xml
		MANIFEST.MF
	EJB.jar
		META-INF
			ejb-jar.xml
			weblogic-ejb-jar.xml
			persistence.xml
			MANIFEST>MF
		packages containing Java classes
	Web.war
		META-INF
			MANIFEST.MF
		WEB-INF
			classes (packages containing Java classes)
			lib
			web.xml
			weblogic.xml

Application.ear目录包括了所有单独的应用组件。在Application.ear目录里的每一个目录,服务器都会单独作为一个JavaEE应用来看待。这就消除了许多和模块有关的问题。EJB和Web归档文件被放到了企业应用目录结构中的根目录级别,同属于这个级别的还有包含了描述文件的META-INF,而其他一些目录则是应用需要用到的目录。

模块的独立部署的一个重要区别是类加载器所加载的应用程序的结构。当EJB和Web模块被放置在一个企业应用程序的结构,WebLogic使用类加载器的结构,EJB模块被顶层的类加载器加载,而Web模块则被application类加载器的一个子类所加载。回想一下,一个子类加载器可以引用它的父类加载器加载的所有类,所以在Web模块中的类拥有对EJB模块的类的完全访问权限。
  • 因为在同一个企业级应用中,Web模块可以用本地EJB接口和调用EJB组件中的Java方法。而并不需要用到远程接口或者Serializable参数和返回值。
  • 这也不需要包括EJB的客户端类,因为它凭借着父应用程序的classloader对这些类有完全的权限。

Bundled libraries

Bundled libraries(jar文件)包括应用程序的组件,例如,一个第三方的产品库,例如,Hibernate和Spring。任何代码,只要是程序依赖到的都可以打成Bundled libraries。Bundled libraries比起用system classpath有三点优势:
  • 一个程序用Bundled library可以更少的依赖服务器的配置,并且更容易部署。
  • Bundled library是通过application classloader来加载的,这样的结果就是可以更容易的重新部署,而不用重启服务器。
  • 应用可以在同一台server上拥有不同版本的Bundled library的拷贝。
Bundled library的缺点就是这些打好的包放到了system classpath里会增加系统的大小,并且如果许多应用包括了相同的Bundled library将会使内存的占用率变高。在应用中引入Bundled library有以下的方式:
  • 配置Class-Path —— 应用中的META-INF/MANIFEST.MF文件中可以包含Class-Path,指明一个JAR文件列表。这些jar文件将作为Bundled library被加载,并且可以放在应用的任何一个目录。每一个Class-Path都是相对于指定的模块。jar文件里的Class-Path将递归操作,去加载其他的Bundled library。
  • Web归档文件里的WEB-INF/lib —— Web归档文件提供了一个内置的机制来加载类,Web模块使用相同的类加载器来加载WEB-INF/lib目录。放置在该目录中的任何JAR文件将被自动被Web模块的类加载器加载,并且在模块内的类将提供给Web组件使用。这些jar文件可以参考其他Bundled library的Class-Path配置。还有一个未归档的类文件的目录WEB-INF/classes。WEB-INF/classes,WEB-INF/lib目录,WEB-INF/lib下的Bundled library,都是通过Web模块的类加载器加载,并对应用中的其他模块是不可见的。
  • Weblogic App-INF/lib —— WebLogic允许jar文件放在APP-INF/lib的目录。这些jar文件可以通过Class-Path来引用其他的Bundled library。 WebLogic还支持APP-INF/classes目录来给未归档的类文件。这些APP-INF目录提供的功能就像WEB-INF目录一样,但是用到的是应用程序的类加载器。
  • JavaEE 5应用程序目录 —— Java EE5引入了一个跟WebLogic APP-INF/lib目录相类似的功能。任何在Java EE应用程序库目录的JAR文件将被作为Bundled library并且是用应用程序中的类加载器加载。这些jar文件可以有Class-Path配置去引用其他的Bundled library。默认的库名为lib目录,但可以改变application.xml文件里的library-directory描述符来改变目录名。library-directory为空则表示没有library目录。
让我们思考一个打包Bundled library的例子。根据EJB和Web模块的约定它们将会自动加载。另一方面,somelibrary.jar放置在libraries目录下将不会自动加载的,必须通过指定来加载。为了达到目的,必需添加一个Java EE library目录或添加Class-Path配置。

需要在META-INF目录中的application.xml文件里添加一个 library-directory描述符,从而将JavaEE的library目录设置为libraries,例如:
<application ...>
    <module>
        <ejb>EJB.jar</ejb>
    </module>
    <module>
        <web>
            <web-uri>Web.war</web-uri>
            <context-root>MiddlewareMagic</context-root>
        </web>
    </module>
	<library-directory>libraries</library-directory>
</application>

在META-INF/MANIFEST.MF文件里,需要加入以下内容:
Manifest-Version: 1.0
Class-Path: libraries/somelibrary.jar

请注意,清单文件中的行被限制在72个字符。长的Class-Path需要放到下一行,这样需要在每一行开头加一个空格。

需要注意的是Class-Path配置是唯一的方法去控制加载库的优先顺序。其他方法并没有定义顺序。如果假设是有顺序之分的话可能会出现细微的问题。例如,当一个文件系统变化时也可能改变的WEB-INF/lib目录下的库加载顺序,导致应用程序发生故障。 通过配置Class-Path,较早出现的Bundled library具有更高的优先级。这将非常有用,如果两个捆绑库包含相同的类,而我们需要确保其中一个将更优先考虑的。

共享库(Sharing libraries)
Weblogic提供了两种方式来打包和管理多个应用之间共享库:可选包和共享库。可选包是JavaEE 5规范的一部分。可选包是一个包含了编译后的类的普通jar文件,可以部署到服务器。应用和其他包可以引用在manifest文件清单里的其他包 —— 这种引用将在部署时完成。可选包可以有一个版本,不同版本的相同的可选包可以部署到相同服务器。应用中的manifest文件将决定要使用哪个版本的库。

运行时,可选包是通过application classloader类加载器加载的,这跟Bundled library的加载相类似。如果两个应用程序部署相同的可选包到服务器,那么将有两个独立的副本被加载到服务器上。可选包并不能减少应用程序的内存占用率。

共享库是WebLogic的特定包。跟可选包一样,共享库可以被部署到服务器上;可以有版本,并可以被应用程序或其他共享库所引用。共享库跟可选包的主要区别是,共享库可以是企业应用程序,EJB模块或Web模块,以及包含类和资源的简单jar文件。共享库利用这种能力去包含JavaEE应用程序和模块,但需要记住的是,这是WebLogic所特有的一个功能。

当一个应用程序指定一个共享库部署,它的行为就像把共享库合并到应用中去一样。就像可选包,共享库提供一种方式给应用程序之间共享代码,但每个应用程序都会有一个自己的运行时类的副本。下面将举例说明如何配置(weblogic.xml)让应用程序使用共享库:
<weblogic-web-app ...>
	<library-ref>
		<library-name>coherence-web-spi</library-name>
		<specification-version>1.0.0.0</specification-version>
		<implementation-version>1.0.0.0</implementation-version>
		<exact-match>true</exact-match>
	</library-ref>
</weblogic-web-app>
可选包和共享库跟 Bundled Library有很多相同的运行特征。可选包和共享库的好处是,他们可以让 应用单独管理,从而使应用的部署更快、归档更小。另一方面,依赖于可选包或共享库的应用将更小的包含它们。当可选包和共享库被一个或多个应用使用时,它们会选择自己拥有的类,这样当我们希望能够升级功能,则无需更改应用本身。

类加载的操作
过滤类加载器允许自定义类加载行为。过滤类加载器是用来解决应用程序和系统类路径,或者domain/lib目录中的类冲突。默认情况下,如果一个类同时放到系统类路径和应用程序中,系统类路径中的副本将被使用。过滤类加载器通常用于打包第三方库中的不同版本的包。过滤类加载器是通过文件weblogic-application.xml中的prefer-application-packages节点来控制的。假设我们要使用Hibernate提供的一个antlr版本而不是WebLogic提供的。我们可以将antlr打成bundled library,并使用下面的描述符节点:
<prefer-application-packages>
	<package-name>antlr.*</package-name>
</prefer-application-packages>
package-name表达式可以用一个*号的通配符,代表将匹配任何的字符。

注意以下例子的异常:
org.hibernate.HibernateException: Errors in named queries: Film.findAll, Persoon.findPersoonBySofinummer, Film.findFilmsForPersoon, Persoon.findAll
	at org.hibernate.impl.SessionFactoryImpl.<init>(SessionFactoryImpl.java:365)
	at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1300)
	at org.hibernate.ejb.Ejb3Configuration.buildEntityManagerFactory(Ejb3Configuration.java:691)
	at org.hibernate.ejb.HibernatePersistence.createContainerEntityManagerFactory(HibernatePersistence.java:127)

org.springframework.context.ApplicationContextException: Can't load stylesheet from 'classpath:xsl/personalMenu.xsl'; nested exception is javax.xml.transform.TransformerConfigurationException: Could not compile stylesheet
        at org.springframework.web.servlet.view.xslt.XsltView.loadTemplates(XsltView.java:424)
        at org.springframework.web.servlet.view.xslt.XsltView.initApplicationContext(XsltView.java:183)
        at org.springframework.context.support.ApplicationObjectSupport.initApplicationContext(ApplicationObjectSupport.java:119)
        at org.springframework.web.context.support.WebApplicationObjectSupport.initApplicationContext(WebApplicationObjectSupport.java:69)
        at org.springframework.context.support.ApplicationObjectSupport.setApplicationContext(ApplicationObjectSupport.java:73)
Caused By: javax.xml.transform.TransformerConfigurationException: Could not compile stylesheet
        at com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl.newTemplates(TransformerFactoryImpl.java:885)
        at weblogic.xml.jaxp.WebLogicTransformerFactory.newTemplates(WebLogicTransformerFactory.java:197)
        at weblogic.xml.jaxp.RegistryTransformerFactory.newTemplates(RegistryTransformerFactory.java:173)
        at org.springframework.web.servlet.view.xslt.XsltView.loadTemplates(XsltView.java:417)
        at org.springframework.web.servlet.view.xslt.XsltView.initApplicationContext(XsltView.java:183)

上面两个异常都没有说明异常是由类加载所导致的。第一个异常说有一个命名查询出现错误,所以我们的第一反应就是去检查命名查询来保证它是正确。经过相当长时间后(在某些情况下),我们得出的结论是 查询没有写错。第二个异常也是同样的问题,它告诉我们一个样式表无法被编译,所以一定有什么不对的地方,或者是没有? 不幸的是,由于类加载的问题是很难涉及到真正的问题根源。我们最终得出的结论是,问题跟类的加载顺序有关,我们必需知道哪些类发生冲突了。

在以上的情况下,冲突是分别由于antlr和xalan。WebLogic加载这两个包的某个版本,但这些版本对于应用程序来说是不正确的。那么,如何才能解决这些类加载问题?

当WebLogic启动在开发模式时,我们可以使用classloader analysis tool分析工具(http://docs.oracle.com/cd/E24329_01/web.1211/e24368/classloading.htm#WLPRG495 这个链接有介绍这个工具),如果WebLogic不是在开发模式时,那我们就只能使用我们自己的大脑了。为了解决上述的问题,我们可以使用下面的例子(在weblogic.xml文件)

<weblogic-web-app ...>
	<show-archived-real-path-enabled>true</show-archived-real-path-enabled>
	<container-descriptor>
		<prefer-application-packages>
			<package-name>antlr.*</package-name>
			<package-name>org.apache.xalan.*</package-name>
		</prefer-application-packages>
	</container-descriptor>
	<context-root>someapplication</context-root>
</weblogic-web-app>

注意上面是 weblogic.xml的另一种配置。 show-archived-real-path-enabled节点表明Web应用中getRealPath方法的行为。当设置为true时, getRealPath将返回资源文件的规范路径。如果设置为false时,Servlet容器将返回null表示Web应用的真实路径。虽然没有真正涉及到的类加载,但对于velocity工作时,是一个非常重要的参数。

在WebLogic中运行Apache CXF,我们需要以下两个系统参数:
Djavax.xml.soap.MessageFactory=weblogic.xml.saaj.MessageFactoryImpl 
Djavax.xml.soap.SOAPConnectionFactory=weblogic.wsee.saaj.SOAPConnectionFactoryImpl

可以我们还需要在weblogic-application.xml文件中指定另外一些选项的值,例如:
<weblogic-application ...>
    <xml>
		<parser-factory>
			<saxparser-factory>org.apache.xerces.jaxp.SAXParserFactoryImpl</saxparser-factory>
			<document-builder-factory>org.apache.xerces.jaxp.DocumentBuilderFactoryImpl</document-builder-factory>
			<transformer-factory>org.apache.xalan.processor.TransformerFactoryImpl</transformer-factory>
		</parser-factory>
	</xml>
	<application-param>
		<param-name>webapp.encoding.default</param-name>
		<param-value>UTF-8</param-value>
	</application-param>
	<prefer-application-packages>
		<package-name>javax.jws.*</package-name>
		<package-name>org.apache.xerces.*</package-name>
		<package-name>org.apache.xalan.*</package-name>
	</prefer-application-packages>
</weblogic-application>

更多的关于xml的值设置,可以点击这里 here

参考资料


------------------------------------------------------------------
用了两天的时间,终于把这文章给翻译了。文中翻译不到位的地方,或者理解有错的地方,希望各位可以指点指点。


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值