本章介绍了4个例子,类加载器(2个)和字节码(2个)。
案例分析
Tomcat:正统的类加载器架构
一个服务器有多个自定义的加载器(如:tomcat、jetty等)。
功能健全的Web服务器要解决的问题:
- 同服务器的两个Web应用程序所使用的Java类库可以实现相互隔离
- 同服务器的两个Web应用程序所使用的Java类库可以互相共享。
- 服务器需要尽可能的保证自身的安全不受部署的Web应用程序影响。
- 支持JSP应用的Web服务器,大多数都需要支持HotSwap功能。
OSGi:灵活的类加载架构(网状结构)
Java社区流传观点:学习J2EE规范,去看Jboss源码;学习类加载器,就去看OSGi源码。
最小模块:Bundle
特征:
- 依赖关系:从传统的上层模块依赖底层模块转变为平级之间的依赖。
- 可见性:只有被Export和Package才可能被外界访问。
- 扩展性:可以实现模块级的热插拔功能。
- 无关系性:Bundle类加载器之间只有规则,没有固定的委派关系。
- 精确访问:一个Bundle类加载器为其他Bundle提供服务时,会根据Export-Package列表严格控制访问范围。
缺点:
- 额外的复杂度
- 带来了死锁和内存泄露的风险
字段码生成技术与动态代理的实现
应用场景:
- Web服务器的JSP编译器
- 编译时植入的AOP框架
- 常用的动态代理技术
- 使用反射的时候虚拟机都有可能会在运行时生成字符码
过程:
Retrotranslator
跨越JDK版本(Java逆向移植)。
用途:
实现原理(略).
JDK升级4类改动:
- 在编译层面做的改进
- 对Java API的代码增强
- 需要在字节码中进行支持的改动
- 虚拟机内部的改进
实战:自己动手实现远程执行功能
JDK1.6之前:写一个JSP文件上传到服务器,然后通过服务器运行它,或者在服务端程序中加入BeanShell Script等的执行引擎去执行动态脚本。
JDK1.6之后:Compliler API。
目标
在服务端执行临时代码。
思路
- 如何编译提交到服务器的Java代码?
- 使用tools.jar包(引入了额外的JAR包,JDK移植要把tools.jar带上)
- 直接在客户端编译好,把字节码而不是Java代码传到服务端
- 如何执行编译之后的Java代码?
- 让类加载器加载这个类生成一个Class对象,然后反射调用一下某个方法就可以了。(能卸载和回收)
- 如何收集Java代码的执行结果?
- 标准输出System.out(会存在问题)
- 解决:直接在执行的类中把对System.out的符号引用替换为我们准备的PrintStream的符号引用。
实现
- 实现同一类被多次加载(指定HotSwapClassLoader作为父类加载器)
- 实现将java.lang.System替换为我们自己定义的HackSystem类的过程
验证(略)