加入VIP
  • 专属下载特权
  • 现金文档折扣购买
  • VIP免费专区
  • 千万文档免费下载

上传资料

关闭

关闭

关闭

封号提示

内容

首页 Java深度历险

Java深度历险

Java深度历险

Jason
2011-11-08 0人阅读 举报 0 0 暂无简介

简介:本文档为《Java深度历险pdf》,可适用于IT/计算机领域

免费在线版本(非印刷免费在线版)了解本书更多信息请登录本书的官方网站InfoQ中文站出品本书由InfoQ中文站免费发放如果您从其他渠道获取本书请注册InfoQ中文站以支持作者和出版商并免费下载更多InfoQ企业软件开发系列图书。本迷你书主页为http:wwwinfoqcomcnminibooksjavaexploreJava深度历险序《Java深度历险》专栏的作者成富是IBM中国软件开发中心的高级工程师也是我的前同事。他曾经是CTO毛新生的得意门生承担过LotusMashups产品的重要研发职责现在负责领导ProjectVulcan项目的重要组件在中国团队的开发。成富对于Java和Web开发有着很深的造诣同时在其他技术领域有着自己独到的见解。他是我见过的少有的具有极强技术领悟力和实践能力的一部分人之一。成富还是一个专业的技术写手看看他博客上的列表就知道他在一年内会投递多少优质的稿件。所以顺理成章地在我参与InfoQ中文站社区贡献时很自然邀请他来开辟一个深入Java和JVM的专栏他欣然应允重要的是他以专业的技术作者素质不再让我担心催稿最终有了这十篇关于Java不同方面但深入浅出的主题内容。在几乎每篇专栏的结尾都有多于平均数量的积极的评论在InfoQ内部月度内容排行上尤为突出。同样是出于读者的呼声才有了这本迷你书面世的可能。很高兴地知道成富接下来还会和华章有进一步的合作撰写有关Java方面的技术书籍让我们一起期待吧。InfoQ中文站原创团队主编张凯峰现在报名折优惠!知名网站案例分析阿里巴巴、淘宝、大众点评等知名网站背后的架构故事脚本代码之美专家解析HTML、JavaScript、NodeJS设计中的难题开放平台来自百度、、腾讯、盛大的案例分享首席架构师的架构观首席架构师眼中的简单原则大数据和NoSQLHadoop、HBase、MongoDB和Cassandra等技术在当前的企业中的应用DevOps最前沿的开发运维之道运行中的云计算架构云计算平台面面观从架构到实践Java依旧灿烂Java使用者与平台架构师谈Java为何依旧灿烂敏捷已到壮年敏捷与精益开发的现状与未来大会主题QCon全球企业开发大会(杭州站)月日月日月日前报名折优惠!Java深度历险目录序目录JAVA字节代码的操纵动态编译JAVA源文件JAVA字节代码增强JAVALANGINSTRUMENT总结参考资料JAVA类的加载、链接和初始化JAVA类的加载JAVA类的链接JAVA类的初始化创建自己的类加载器参考资料JAVA线程:基本概念、可见性与同步JAVA线程基本概念可见性JAVA中的锁JAVA线程的同步中断线程参考资料JAVA垃圾回收机制与引用类型JAVA垃圾回收机制JAVA引用类型参考资料JAVA泛型类型擦除实例分析通配符与上下界类型系统开发自己的泛型类最佳实践参考资料目录JAVA注解使用注解开发注解处理注解实例分析参考资料JAVA反射与动态代理基本用法处理泛型动态代理使用案例参考资料JAVAIO流缓冲区字符与编码通道参考资料JAVA安全认证权限控制加密、解密与签名安全套接字连接参考资料JAVA对象序列化与RMI基本的对象序列化自定义对象序列化序列化时的对象替换序列化与对象创建版本更新序列化安全性RMI参考资料Java深度历险Java字节代码的操纵在一般的Java应用开发过程中开发人员使用Java的方式比较简单。打开惯用的IDE编写Java源代码再利用IDE提供的功能直接运行Java程序就可以了。这种开发模式背后的过程是:开发人员编写的是Java源代码文件(java)IDE会负责调用Java的编译器把Java源代码编译成平台无关的字节代码(bytecode)以类文件的形式保存在磁盘上(class)。Java虚拟机(JVM)会负责把Java字节代码加载并执行。Java通过这种方式来实现其“编写一次到处运行(Writeonce,runanywhere)”的目标。Java类文件中包含的字节代码可以被不同平台上的JVM所使用。Java字节代码不仅可以以文件形式存在于磁盘上也可以通过网络方式来下载还可以只存在于内存中。JVM中的类加载器会负责从包含字节代码的字节数组(byte)中定义出Java类。在某些情况下可能会需要动态的生成Java字节代码或是对已有的Java字节代码进行修改。这个时候就需要用到本文中将要介绍的相关技术。首先介绍一下如何动态编译Java源文件。动态编译Java源文件在一般情况下开发人员都是在程序运行之前就编写完成了全部的Java源代码并且成功编译。对有些应用来说Java源代码的内容在运行时刻才能确定。这个时候就需要动态编译源代码来生成Java字节代码再由JVM来加载执行。典型的场景是很多算法竞赛的在线评测系统(如PKUJudgeOnline)允许用户上传Java代码由系统在后台编译、运行并进行判定。在动态编译Java源文件时使用的做法是直接在程序中调用Java编译器。JSR引入了Java编译器API。如果使用JDK的话可以通过此API来动态编译Java代码。比如下面的代码用来动态编译最简单的HelloWorld类。该Java类的代码是保存在一个字符串中的。publicclassCompilerTest{publicstaticvoidmain(Stringargs)throwsException{Stringsource="publicclassMain{publicstaticvoidmain(Stringargs){Systemoutprintln("HelloWorld!")}}"第一章Java字节代码的操纵JavaCompilercompiler=ToolProvidergetSystemJavaCompiler()StandardJavaFileManagerfileManager=compilergetStandardFileManager(,,)StringSourceJavaObjectsourceObject=newCompilerTestStringSourceJavaObject("Main",source)Iterable<extendsJavaFileObject>fileObjects=ArraysasList(sourceObject)CompilationTasktask=compilergetTask(,fileManager,,,,fileObjects)booleanresult=taskcall()if(result){Systemoutprintln("编译成功。")}}staticclassStringSourceJavaObjectextendsSimpleJavaFileObject{privateStringcontent=publicStringSourceJavaObject(Stringname,Stringcontent)throwsURISyntaxException{super(URIcreate("string:"namereplace('','')KindSOURCEextension),KindSOURCE)thiscontent=content}publicCharSequencegetCharContent(booleanignoreEncodingErrors)throwsIOException{returncontent}}}如果不能使用JDK提供的Java编译器API的话可以使用JDK中的工具类comsuntoolsjavacMain不过该工具类只能编译存放在磁盘上的文件类似于直接使用javac命令。另外一个可用的工具是EclipseJDTCore提供的编译器。这是EclipseJava开发环境使用的增量式Java编译器支持运行和调试有错误的代码。该编译器也可以单独使用。Play框架在内部使用了JDT的编译器来动态编译Java源代码。在开发模式下Play框架会定期扫描项目中的Java源代码文件一旦发现有修改会自动编译Java源代码。因此在修改代码之后刷新页面就可以看到变化。使用这些动态编译的方式的时候需要确保JDK中的toolsjar在应用的CLASSPATH中。下面介绍一个例子是关于如何在Java里面做四则运算比如求出来()*的值。Java深度历险一般的做法是分析输入的运算表达式自己来模拟计算过程。考虑到括号的存在和运算符的优先级等问题这样的计算过程会比较复杂而且容易出错。另外一种做法是可以用JSR引入的脚本语言支持直接把输入的表达式当做JavaScript或是JavaFX脚本来执行得到结果。下面的代码使用的做法是动态生成Java源代码并编译接着加载Java类来执行并获取结果。这种做法完全使用Java来实现。privatestaticdoublecalculate(Stringexpr)throwsCalculationException{StringclassName="CalculatorMain"StringmethodName="calculate"Stringsource="publicclass"className"{publicstaticdouble"methodName"(){return"expr"}}"省略动态编译Java源代码的相关代码参见上一节booleanresult=taskcall()if(result){ClassLoaderloader=CalculatorclassgetClassLoader()try{Class<>clazz=loaderloadClass(className)Methodmethod=clazzgetMethod(methodName,newClass<>{})Objectvalue=methodinvoke(,newObject{})return(Double)value}catch(Exceptione){thrownewCalculationException("内部错误。")}}else{thrownewCalculationException("错误的表达式。")}}上面的代码给出了使用动态生成的Java字节代码的基本模式即通过类加载器来加载字节代码创建Java类的对象的实例再通过Java反射API来调用对象中的方法。Java字节代码增强Java字节代码增强指的是在Java字节代码生成之后对其进行修改增强其功能。这种做法相当于对应用程序的二进制文件进行修改。在很多Java框架中都可以见到这种实现方式。Java字节代码增强通常与Java源文件中的注解(annotation)一块使用。注解在Java源代码中声明了需要增强的行为及相关的元数据由框架在运行时刻完成对字节代码的增强。Java字节代码增强应用的场景比较多一般都集中在减少冗余代码和对开发人员屏蔽底层的实现细节上。用过JavaBeans的人可能对其中第一章Java字节代码的操纵那些必须添加的gettersetter方法感到很繁琐并且难以维护。而通过字节代码增强开发人员只需要声明Bean中的属性即可gettersetter方法可以通过修改字节代码来自动添加。用过JPA的人在调试程序的时候会发现实体类中被添加了一些额外的域和方法。这些域和方法是在运行时刻由JPA的实现动态添加的。字节代码增强在面向方面编程(AOP)的一些实现中也有使用。在讨论如何进行字节代码增强之前首先介绍一下表示一个Java类或接口的字节代码的组织形式。类文件{xCAFEBABE小版本号大版本号常量池大小常量池数组访问控制标记当前类信息父类信息实现的接口个数实现的接口信息数组域个数域信息数组方法个数方法信息数组属性个数属性信息数组}如上所示一个类或接口的字节代码使用的是一种松散的组织结构其中所包含的内容依次排列。对于可能包含多个条目的内容如所实现的接口、域、方法和属性等是以数组来表示的。而在数组之前的是该数组中条目的个数。不同的内容类型有其不同的内部结构。对于开发人员来说直接操纵包含字节代码的字节数组的话开发效率比较低而且容易出错。已经有不少的开源库可以对字节代码进行修改或是从头开始创建新的Java类的字节代码内容。这些类库包括ASM、cglib、serp和BCEL等。使用这些类库可以在一定程度上降低增强字节代码的复杂度。比如考虑下面一个简单的需求在一个Java类的所有方法执行之前输出相应的日志。熟悉AOP的人都知道可以用一个前增强(beforeadvice)来解决这个问题。如果使用ASM的话相关的代码如下:ClassReadercr=newClassReader(is)ClassNodecn=newClassNode()craccept(cn,)for(Objectobject:cnmethods){MethodNodemn=(MethodNode)objectif("<init>"equals(mnname)||"<clinit>"equals(mnname)){continue}InsnListinsns=mninstructionsInsnListil=newInsnList()iladd(newFieldInsnNode(GETSTATIC,"javalangSystem","out",“Java深度历险"LjavaioPrintStream"))iladd(newLdcInsnNode("Entermethod>"mnname))iladd(newMethodInsnNode(INVOKEVIRTUAL,"javaioPrintStream","println","(LjavalangString)V"))insnsinsert(il)mnmaxStack=}ClassWritercw=newClassWriter()cnaccept(cw)byteb=cwtoByteArray()从ClassWriter就可以获取到包含增强之后的字节代码的字节数组可以把字节代码写回磁盘或是由类加载器直接使用。上述示例中增强部分的逻辑比较简单只是遍历Java类中的所有方法并添加对Systemoutprintln方法的调用。在字节代码中Java方法体是由一系列的指令组成的。而要做的是生成调用Systemoutprintln方法的指令并把这些指令插入到指令集合的最前面。ASM对这些指令做了抽象不过熟悉全部的指令比较困难。ASM提供了一个工具类ASMifierClassVisitor可以打印出Java类的字节代码的结构信息。当需要增强某个类的时候可以先在源代码上做出修改再通过此工具类来比较修改前后的字节代码的差异从而确定该如何编写增强的代码。对类文件进行增强的时机是需要在Java源代码编译之后在JVM执行之前。比较常见的做法有:由IDE在完成编译操作之后执行。如GoogleAppEngine的Eclipse插件会在编译之后运行DataNucleus来对实体类进行增强。在构建过程中完成比如通过Ant或Maven来执行相关的操作。实现自己的Java类加载器。当获取到Java类的字节代码之后先进行增强处理再从修改过的字节代码中定义出Java类。通过JDK引入的javalanginstrument包来完成。javalanginstrument由于存在着大量对Java字节代码进行修改的需求JDK引入了javalanginstrument包并在JDK中得到了进一步的增强。基本的思路是在JVM启动的时候添加一些代理(agent)。每个代理是一个jar包其清单(manifest)文件中会指定一个代理类。这个类会包含一个premain方法。JVM在启动的时候会首先执行代理类的premain方法再执行Java程序本身的main方法。在premain方法中就可以对程序本身的字节第一章Java字节代码的操纵代码进行修改。JDK中还允许在JVM启动之后动态添加代理。javalanginstrument包支持两种修改的场景一种是重定义一个Java类即完全替换一个Java类的字节代码另外一种是转换已有的Java类相当于前面提到的类字节代码增强。还是以前面提到的输出方法执行日志的场景为例首先需要实现javalanginstrumentClassFileTransformer接口来完成对已有Java类的转换。staticclassMethodEntryTransformerimplementsClassFileTransformer{publicbytetransform(ClassLoaderloader,StringclassName,Class<>classBeingRedefined,ProtectionDomainprotectionDomain,byteclassfileBuffer)throwsIllegalClassFormatException{try{ClassReadercr=newClassReader(classfileBuffer)ClassNodecn=newClassNode()省略使用ASM进行字节代码转换的代码ClassWritercw=newClassWriter()cnaccept(cw)returncwtoByteArray()}catch(Exceptione){return}}}有了这个转换类之后就可以在代理的premain方法中使用它。publicstaticvoidpremain(Stringargs,Instrumentationinst){instaddTransformer(newMethodEntryTransformer())}把该代理类打成一个jar包并在jar包的清单文件中通过PremainClass声明代理类的名称。运行Java程序的时候添加JVM启动参数javaagent:myagentjar。这样的话JVM会在加载Java类的字节代码之前完成相关的转换操作。总结操纵Java字节代码是一件很有趣的事情。通过它可以很容易的对二进制分发的Java程序进行修改非常适合于性能分析、调试跟踪和日志记录等任务。另外一个非常重要的作用是把开发人员从繁琐的Java语法中解放出来。开发人员应该只需要负责编写与业务逻辑相关的重要代码。对于那些只是因为语法要求而添加的或是模式固定的代码完全可以将其字节代码动态生成出来。字节代码增强和源代码生成是不同的概念。源代码生成之后就已经成为了程序的一部分开发人员需要去维护Java深度历险它:要么手工修改生成出来的源代码要么重新生成。而字节代码的增强过程对于开发人员是完全透明的。妥善使用Java字节代码的操纵技术可以更好的解决某一类开发问题。参考资料Java字节代码格式JavaCompilerAPI深入探讨Java类加载器Java深度历险Java类的加载、链接和初始化在上一篇文章中介绍了Java字节代码的操纵其中提到了利用Java类加载器来加载修改过后的字节代码并在JVM上执行。本文接着上一篇的话题讨论Java类的加载、链接和初始化。Java字节代码的表现形式是字节数组(byte)而Java类在JVM中的表现形式是javalangClass类的对象。一个Java类从字节代码到能够在JVM中被使用需要经过加载、链接和初始化这三个步骤。这三个步骤中对开发人员直接可见的是Java类的加载通过使用Java类加载器(classloader)可以在运行时刻动态的加载一个Java类而链接和初始化则是在使用Java类之前会发生的动作。本文会详细介绍Java类的加载、链接和初始化的过程。Java类的加载Java类的加载是由类加载器来完成的。一般来说类加载器分成两类:启动类加载器(bootstrap)和用户自定义的类加载器(userdefined)。两者的区别在于启动类加载器是由JVM的原生代码实现的而用户自定义的类加载器都继承自Java中的javalangClassLoader类。在用户自定义类加载器的部分一般JVM都会提供一些基本实现。应用程序的开发人员也可以根据需要编写自己的类加载器。JVM中最常使用的是系统类加载器(system)它用来启动Java应用程序的加载。通过javalangClassLoader的getSystemClassLoader()方法可以获取到该类加载器对象。类加载器需要完成的最终功能是定义一个Java类即把Java字节代码转换成JVM中的javalangClass类的对象。但是类加载的过程并不是这么简单。Java类加载器有两个比较重要的特征:层次组织结构和代理模式。层次组织结构指的是每个类加载器都有一个父类加载器通过getParent()方法可以获取到。类加载器通过这种父亲后代的方式组织在一起形成树状层次结构。代理模式则指的是一个类加载器既可以自己完成Java类的定义工作也可以代理给其它的类加载器来完成。由于代理模式的存在启动一个类的加载过程的类加载器和最终定义这个类的类加载器可能并不是一个。前者称为初始类加载器而后者称为定义类加载器。两者的关联在于:一个Java类的定义类加载器是该类所导入的其它Java类的初始类加载器。比如类A通过

用户评价(0)

关闭

新课改视野下建构高中语文教学实验成果报告(32KB)

抱歉,积分不足下载失败,请稍后再试!

提示

试读已结束,如需要继续阅读或者下载,敬请购买!

文档小程序码

使用微信“扫一扫”扫码寻找文档

1

打开微信

2

扫描小程序码

3

发布寻找信息

4

等待寻找结果

我知道了
评分:

/14

Java深度历险

仅供在线阅读

VIP

在线
客服

免费
邮箱

爱问共享资料服务号

扫描关注领取更多福利