Java动态编程
1. 反射
什么是反射
- 在运行时判断任意一个对象所属的类。
- 在运行时构造任意一个类的对象。
- 在运行时判断任意一个类所具有的成员变量和方法。
- 在运行时调用任意一个对象的方法
- 获取泛型信息
- 处理注解
- 反射机制的实现要借助于4个类:class,Constructor,Field,Method;
反射调用流程:
对应类、构造器,对象、成员变量、方法等,都是
- 1.先用反射获得
- 2.使用反射提供的方法调用(而不是通过反射获得一个类之后就能直接使用该类调用该类下的方法)
//获取类
User user = clazz.newInstance();
//获取方法
Method method = clazz.getDeclaredMethod("setUserName",String.class);
//调用
method.invode(user,"shunXu");
1.1 反射操作私有(private)
通过反射,能够对私有域进行操作。
- 通过使用方法名包含Declared的方法进行操作(如上述代码中的clazz.getDeclaredMetod)
- xxx.setAccessible(true),跳过安全检查。这也是提升反射性能的途径
1.2 反射操作泛型
Java采用泛型擦除机制引入泛型。
Java中的泛型仅仅是给编译器javac使用,确保数据的安全性和免去类型强转的麻烦。但是,一旦编译完成,所有和泛型相关的类型全部被擦除。
为了通过反射操作泛型,Java新增了ParameterizedType,GenericArrayType,TypeVariable和WildcardType来代表不能被归一到Class中的类型但是又和原始类型齐名的类型。
- ParameterizedType:表示一种参数化的类型,比如Collection<String>。
- GenericArrayT:表示元素类型是参数化类型或者类型变量的数组。
- TypeVariable:各种类型变量的公共父接口
- WildcardType:表示通配符,
1.3 注解Annotation
最常见的用法还是在框架中
可以通过反射获得相关注解信息。
//获得类的所有注解
Annotation[] annotations = clazz.getAnnotations();
for(Annotation a:annotations){
System.out.println(a);
}
//获得指定类注解
User user = (User) class.getAnnotation(User.calss);
System.out.println(user.value());
//获得指定属性注解
Field f = clazz.getDeclaredField("userName");
User user = f.getAnnotation(User.class);
System.out.println(User.columnName()+"---"+User.type()+"---"+User.length());
2. 动态编译
应用场景
- 浏览器中编写java代码,服务器编译运行响应结果
- 服务器动态加载某些类文件进行编译
2.1 编译
动态编译的常用做法:
- 通过
//编译并执行,但实际上还是静态
Runtime run = Runtime.getRuntime();
Process process = run.exec("javac -cp d:/myJava/ HelloWorld.java");
- 通过JavaCompiler实现真正的动态编译
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
int result = compiler.run(null,null,sourceFile);
System.out.println(result==0?"编译成功":"编译失败");
compiler.run中的参数说明
- 1.in “standard” input;若为null则使用System.in
- 2.out “standard” output;若为null则使用System.out
- 3.err “standard” error;若为null则使用System.err
- 4.java文件路径
2.2 运行
通过Runtime.getRuntime();
//编译并执行,但实际上还是静态
Runtime run = Runtime.getRuntime();
Process process = run.exec("javac -cp d:/myJava/ HelloWorld.java");
通过反射运行编译好的类
3. 执行其他语言代码
通过脚本引擎执行其他语言的代码,以js为例。
//获得脚本引擎
ScriptEngineManager sem = new ScriptEngineManager();
ScriptEngine engine = sem.getEngineByName("javascript");
//定义变量,存储到引擎上下文中
engine.put("password","123123");
//定义脚本代码
String jscode = "var user = {name:'shunXu',age:18};";
jscode += "println(user.name);";
//执行脚本
engine.eval(jscode);
System.out.println(engine.get("password"));
4. Java字节码操作
运行时操作字节码可以实现如下功能
- 动态生成新的类
- 动态改变某个类的结构(增删改 新的属性或方法)
优势
- 比反射开销小,性能高
- JAVAasist性能高于反射,低于ASM
常见字节码操作类库
- BCEL(ByteCodeEngineeringLibrary):在JVM指令层次进行操作
- ASM:轻量级框架,直接涉及到JVM底层操作和指令
- CGLIB:高性能、高质量的Code生成类库,基于ASM实现(常见于动态***,为没有实现接口的类提供***)
- Javassist:源代码级别的工作,使用相对简单。
5. 解析配置文件
作为数据的存储格式或用于存储软件的参数,程序解析此配置文件,就可以达到不修改代码就能更改程序的目的。
解析方法分为四种
- 1.DOM解析;
- 2.SAX解析;
- 3.JDOM解析;
- 4.DOM4J解析
前两种为基础方法,后两者为Java专属方法。
DOM解析
- 优点:树状结构、可随机访问、解析过程中,树存在内存中,方便修改
- 缺点:对内存耗费大、若XML文件较大,影响解析性能,可能造成内存溢出
SAX解析(事件驱动)
顺序访问模式,当SAX对XML进行解析时,会触发一系列事件,并激活相应时间的处理函数(事件驱动)
- 优点:事件驱动模式,内存消耗小、适用于只处理XML文件中的数据
- 缺点:编码麻烦、很难同时范根XML文件中的多出不同数据(只能按顺序)
相关方法:
- startDocument()
- 文档解析开始时调用,只调用一次
- startElement(String uri, String localName, String qName,Attributes attributes)
- 标签解析开始时调用,通常用于初始化JavaBean集合
- uri:xml文档的命名空间
- localName:标签名
- qName:带命名空间的标签名
- attributes:标签的属性集
- characters(char[] ch,int start,int length):解析标签内容时调用
- ch:当前读取到的文本节点的字节数组
- start:节点开始的位置,为0则读取全部
- length:当前文本节点的长度
- endElement :标签节点结束后调用
- endDocument() :文档解析结束后调用,只调用一次
实例
<PIANT>
<ZONE></ZONE>
</PIANT>
import java.util.ArrayList;
import java.util.List;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
public class PlantXml{
public static void main(String[] args)throws Exception {
//1.获取解析工厂
SAXParserFactory factory = SAXParserFactory.newInstance();
//2.从解析工厂获取解析器
SAXParser parse = factory.newSAXParser();
//3.编写处理器
//4.加载处理器
PHandler handler = new PHandler();
//5.解析
parse.parse(Thread.currentThread().getContextClassLoader()
.getResourceAsStream("xmlStudy/plant_catalog.xml")
,handler);
}
}
//3.编写处理器
class PHandler extends DefaultHandler{
private List<Plant> plants; //所有plants
private Plant plant; //当前解析的单个plant,在标签开始时new,在标签结束时加入容器
private String tag; //由于characters不能得到内部属性,因此在这里保存
@Override
public void startDocument() throws SAXException {
//解析仅仅开始一次,因此也可当作类构造器进行初始化
plants = new ArrayList<Plant>();
System.out.println("----------解析开始----------");
}
@Override
public void endDocument() throws SAXException {
System.out.println("----------解析结束----------");
System.out.println(plant.getZONE());
}
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
System.out.println("----------"+qName+"标签解析开始----------");
System.out.println();
if (null!=qName) {
if (qName.equals("PLANT")) {
plant = new Plant();
}
tag = qName;
}
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
System.out.println("----------"+qName+"标签解析结束----------");
System.out.println();
if (qName.equals("PLANT")) {
plants.add(plant);
}
tag=null;
}
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
String contents = new String(ch,start,length).trim();
//此处使用else-if存放属性
if (contents.length()>0 && tag!=null) {
if (tag.equals("ZONE")) {
plant.setZONE(Integer.valueOf(contents)); //注意类型转换,string->int
} //由于标签缩进问题,由于空的部分没有tag名字,因此tag还是上一次的ZONE,但是拿到的contests实际为空,因此最终ZONE为空
} //因此,在每一次的tag完成使命之后(endElement),需要赋值为空,在使命开始时(characters),要进行非空判断
}
}
xml解析在web框架开发中非常常见,不过解析过程都是框架自动完成,程序员只需要配置解析文件。
JDOM解析
特征:仅使用具体类,而不使用接口、大量使用Collections类
DOM4J解析
- 优点:性能优异、灵活性好、功能强大、易用
- 缺点:仅仅支持Java
若跨平台,则考虑SAX(JDOM基本没啥用,DOM虽然性能也不好,但是在其他平台上(如js中)会使用)
若不跨平台,果断DOM4J