概述

什么是动态编程?动态编程解决什么问题?Java中如何使用?什么原理?如何改进?

什么是动态编程

动态编程是相对于静态编程而言的,平时我们讨论比较多的就是静态编程语言,例如Java,与动态编程语言,例如JavaScript。那二者有什么明显的区别呢?简单的说就是在静态编程中,类型检查是在编译时完成的,而动态编程中类型检查是在运行时完成的。所谓动态编程就是绕过编译过程在运行时进行操作的技术
在Java中有如下几种方式:

  • 动态编译

动态编译是从Java 6开始支持的,主要是通过一个JavaCompiler接口来完成的。通过这种方式我们可以直接编译一个已经存在的java文件,也可以在内存中动态生成Java代码,动态编译执行

  • 调用JavaScript引擎

Java 6加入了对Script(JSR223)的支持。这是一个脚本框架,提供了让脚本语言来访问Java内部的方法。你可以在运行的时候找到脚本引擎,然后调用这个引擎去执行脚本。这个脚本API允许你为脚本语言提供Java支持。

  • 动态生成字节码

这种技术通过操作Java字节码的方式在JVM中生成新类或者对已经加载的类动态添加元素

动态编程解决什么问题

在静态语言中引入动态特性,主要是为了解决一些使用场景的痛点。其实完全使用静态编程也办得到,只是付出的代价比较高,没有动态编程来得优雅。例如依赖注入框架Spring使用了反射,而Dagger2 却使用了代码生成的方式(APT)例如 :

  • 在那些依赖关系需要动态确认的场景:

  • 需要在运行时动态插入代码的场景,比如动态代理的实现。

  • 通过配置文件来实现相关功能的场景

Java中如何使用

此处我们主要说一下通过动态生成字节码的方式,其他方式可以自行查找资料。
操作java字节码的工具有两个比较流行,一个是ASM,一个是Javassit

  • ASM

直接操作字节码指令,执行效率高,使用者需要掌握Java类字节码文件格式及指令,对使用者的要求比较高。

  • Javassit

提供了更高级的API,执行效率相对较差,但无需掌握字节码指令的知识,对使用者要求较低。

应用层面来讲一般使用建议优先选择Javassit,如果后续发现Javassit 成为了整个应用的效率瓶颈的话可以再考虑ASM.当然如果开发的是一个基础类库,或者基础平台,还是直接使用ASM吧,相信从事这方面工作的开发者能力应该比较高。

接下来介绍如何使用Javassit来操作字节码

Javassit使用方法

Javassist是一个开源的分析、编辑和创建Java字节码的类库。是由东京工业大学的数学和计算机科学系的 Shigeru Chiba (千叶 滋)所创建的。它已加入了开放源代码JBoss 应用服务器项目,通过使用Javassist对字节码操作为JBoss实现动态AOP框架。javassist是jboss的一个子项目,其主要的优点,在于简单,而且快速。直接使用java编码的形式,而不需要了解虚拟机指令,就能动态改变类的结构,或者动态生成类。

Javassist中最为重要的是ClassPoolCtClass ,CtMethod 以及 CtField这几个类。

  • ClassPool

一个基于HashMap实现的CtClass对象容器,其中键是类名称,值是表示该类的CtClass对象。默认的ClassPool使用与底层JVM相同的类路径,因此在某些情况下,可能需要向ClassPool添加类路径或类字节

  • CtClass

表示一个类,这些CtClass对象可以从ClassPool获得

  • CtMethods

表示类中的方法

  • CtFields

表示类中的字段

动态生成一个类

下面的代码会生成一个实现了Cloneable接口的类GenerateClass

public class Main{public void generate(){ClassPool pool = ClassPool.getDefault();CtClass ct = pool.makeClass("top.ss007.GenerateClass");//创建类ct.setInterfaces(new CtClass[]{pool.makeInterface("java.lang.Cloneable")});//让类实现Cloneable接口CtField f = new CtField(CtClass.intType, "id", ct);//获得一个类型为int,名称为id的字段f.setModifiers(AccessFlag.PUBLIC);//将字段设置为publicct.addField(f);//将字段设置到类上//添加构造函数CtConstructor constructor = CtNewConstructor.make("public GeneratedClass(int pId){this.id=pId;}", ct);ct.addConstructor(constructor);//添加方法CtMethod helloM = CtNewMethod.make("public void hello(String des){ System.out.println(des);}", ct);ct.addMethod(helloM);ct.writeFile();//将生成的.class文件保存到磁盘ct.toClass();//获取class实例//下面的代码为验证代码Field[] fields = ct.toClass().getFields();System.out.println("属性名称:" + fields[0].getName() + "  属性类型:" + fields[0].getType());}
}

动态添加构造函数及方法

有很多种方法添加构造函数,我们使用CtNewConstructor.make,他是一个的静态方法,其中有一个重载版本比较方便,如下所示。第一个参数是source text 类型的方法体,第二个为类对象。

 CtConstructor constructor=CtNewConstructor.make("public GeneratedClass(int pId){this.id=pId;}",ct);ct.addConstructor(constructor);

这段代码执行后会生成如下java代码,代码片段是使用反编译工具JD-GUI产生的,可以看到构造函数的参数名被修改成了paramInt

  public GeneratedClass(int paramInt){this.id = paramInt;}

同样有很多种方法添加函数,我们使用CtNewMethod.make这个比较简单的形式

CtMethod helloM=CtNewMethod.make("public void hello(String des){ System.out.println(des);}",ct);
ct.addMethod(helloM);

这段代码执行后会生成如下java代码:

public void hello(String paramString){System.out.println(paramString);}

动态修改方法体

动态的修改一个方法的内容才是我们关注的重点,例如在AOP编程方面,我们就会用到这种技术,动态的在一个方法中插入代码。 例如我们有下面这样一个类

public class Point {private int x;private int y;public Point(){}public Point(int x, int y) {this.x = x;this.y = y;}public void move(int dx, int dy) {this.x += dx;this.y += dy;}
}

我们要动态的在内存中在move()方法体的前后插入一些代码

public void modifyMethod(){ClassPool pool=ClassPool.getDefault();try {CtClass ct=pool.getCtClass("top.ss007.Point");CtMethod m=ct.getDeclaredMethod("move");//$1,$2   表示获取方法的入参值m.insertBefore("{ System.out.print(\"dx:\"+$1); System.out.println(\"dy:\"+$2);}");m.insertAfter("{System.out.println(this.x); System.out.println(this.y);}");ct.writeFile();//通过反射调用方法,查看结果Class pc=ct.toClass();Method move= pc.getMethod("move",new Class[]{int.class,int.class});Constructor<?> con=pc.getConstructor(new Class[]{int.class,int.class});move.invoke(con.newInstance(1,2),1,2);}...}

使用反编译工具查看修改后的move方法结果:

public void move(int dx, int dy) {System.out.print("dx:" + dx);System.out.println("dy:" + dy);this.x += dx;this.y += dy;Object localObject = null;//方法返回值System.out.println(this.x);System.out.println(this.y);}

Javassit 还有许多功能,例如在方法中调用方法,异常捕捉,类型强制转换,注解相关操作等,而且其还提供了字节码层面的API(Bytecode level API)。

什么原理

反射:由于Java执行过程中是将类型载入虚拟机中的,在运行时我们就可以动态获取到所有类型的信息。只能获取却不能修类型信息。 
动态编译与动态生成字节码:这两种方法比较相似,原理也都是利用了Java的设计原理,存在一个虚拟机执行字节码,这就使我们在此处有了改变字节码的操作空间。

总结

有关动态编程的知识在平时的应用层使用不是特别多,多是用在构建框架。例如Spring框架使用反射来构建,而用于AOP编程的动态代理则多是采用生成字节码的方式,例如JBoss,Spring中的AOP部分。了解这部分知识可以在日后遇到相关问题时比别人多一条思考的思路也是好的,做一个思路开阔的Developer。

整理自:https://mp.weixin.qq.com/s/rWmwEIUi5RQInERy1OAWOg

如有侵权请及时联系谢谢!!

秒懂Java动态编程(Javassist简单入门级)相关推荐

  1. 使用java网络编程模拟简单网络即时通信

    使用java网络编程模拟简单网络即时通信 通信流程图: 解析: 1.在上图中我们可以看出对于任何一个客户端,都由两部分构成,发送端和接收端(分别由两个线程来维系) 2.客户端在每一次请求链接时都会轮询 ...

  2. Java网络编程学习——简单模拟在线聊天

    Java网络编程学习--简单模拟在线聊天 学了java网络,也是该做个小案例来巩固一下了. 本次案例将使用UDP和多线程模拟即时聊天,简单练练手. 1.前提知识 需要知道简单的IO流操作,以及简单的U ...

  3. Java动态编程初探——Javassist

    最近需要通过配置生成代码,减少重复编码和维护成本.用到了一些动态的特性,和大家分享下心得. 我们常用到的动态特性主要是反射,在运行时查找对象属性.方法,修改作用域,通过方法名称调用方法等.在线的应用不 ...

  4. Java动态编程技术

    Java是一种静态显式强类型语言,导致java代码存在大量的模版化代码,一直被人诟病开发效率低.其实java语言本身以及JVM生态提供了大量动态编程技术,可以大量减少模版化代码.本文尝试整理相关的技术 ...

  5. Java动态编程初探

    作者简介 传恒,一个喜欢摄影和旅游的软件工程师,先后从事饿了么物流蜂鸟自配送和蜂鸟众包的开发,现在转战 Java,目前负责物流策略组分流相关业务的开发. 什么是动态编程 动态编程是相对于静态编程而言的 ...

  6. java动态编程解决分硬币问题,动态编程硬币更改问题

    我在理解各种问题的动态编程解决方案时遇到问题,特别是硬币找零问题: "给定值N,如果我们要N分钱找零,并且我们有无限数量的S = {S1,S2,..,Sm}硬币的供应,我们可以用几种方法进行 ...

  7. Java网络编程以及简单的聊天程序

    网络编程技术是互联网技术中的主流编程技术之一,懂的一些基本的操作是非常必要的.这章主要讲解网络编程,UDP和Socket编程,以及使用Socket做一个简单的聊天软件. 全部代码下载:链接 1.网络编 ...

  8. Java网络编程之简单UDP通信

    UDP是一种基于不可靠连接的协议,它无法保证传输的数据能不丢失不重复到达,尽自己努力传输,但是不会重传,不需要建立连接,则它所需要的时间会很快.它是基于数据报为单位进行传输的,不想TCP是一种基于流进 ...

  9. Java网络编程的简单应用 例程

    先运行服务器,再运行客户端时得到服务器发送到的hello world!信息. 服务器代码: import java.io.*;import java.net.*;public class HelloS ...

最新文章

  1. ubuntu 设置开机执行脚本_ubuntu-18.04 设置开机启动脚本
  2. Python以表格、可视化图像的形式输出模型特征重要度(feature importances)并进行重要度归一化及排序
  3. 【转】SQL存储结构(页)
  4. golang json数据的处理 动态key 动态字段
  5. php PDO 浮点数返回,php – 如何在PDO中简单地返回对象?
  6. 基于Kubeadm的Flannel分析
  7. 我两个月来对Quarkus的了解
  8. 乐高计算机发展史教程,【乐高产品发展史特别篇】乐高恐龙发展史
  9. android p正式版,国内首家!一加正式推送Android P正式版
  10. git 合并提交 error: cannot ‘squash‘ without a previous commit
  11. pthread_detach():主线程与子线程分离,子线程结束后,资源自动回收
  12. java 常用集合list与Set、Map区别及适用场景总结
  13. U盘安装linux后无法引导
  14. unity3d软阴影和硬阴影的原理_使用随机采样创建软阴影
  15. php验证码图像程序,完美的php生成验证码程序代码-图像处理-Php教程
  16. 清澄A1223. 画圈圈(范浩强)
  17. 八:微服务调用组件Dubbo
  18. cahrt框架 ios_iOS-Charts介绍
  19. SQL --子查询与临时表
  20. 关于把秒换算成分钟和小时的c程序

热门文章

  1. 【吐血整理】Hibernate常用的主键生成策略的原理、优缺点、应用场合
  2. win7硬件要求_可以玩使命召唤14,只要win7系统,怎么配电脑?-鑫谷光荣使命GT...
  3. Python基础案例-数据可视化
  4. 梅科尔工作室-李庆浩 Django笔记
  5. Unity Fungus插件的对话系统简单使用
  6. 注意自己的dns设置 - 阿权的书房
  7. 在线支付系列【18】微信支付实战篇之集成账单相关API
  8. Python 时间-时-分-秒 与 秒数 的互相转换(转)
  9. notTX FLV探测/播放和基于P2P的即时通讯多功能软件
  10. html雷达图代码,HTML5 Canvas制作雷达图实战