---------------------- ASP.Net+Android+IOS开发、.Net培训、期待与您交流! ----------------------
1.反射
Java反射允许动态的发现和绑定类、方法、字段以及所有由语言产生的元素。反射是Java被视为动态语言的关键。
总的来说,Java 反射机制主要提供了一下功能:
1.在运行时判断任意一个对象所属类
2.在运行时构造任意一个类的对象
3.在运行时判断任意一个类所具有的的成员变量和方法
4.在运行时可以调用任意对象的方法,甚至是 private 的方法
5.生成动态代理
Java 反射所需要的类不多,主要有java.lang.Class类和java.lang.reflect包中的Field、Constructor、Method、Array类。
Class 类:Class类的实例是表示正在运行的 Java应用程序中的类和接口。
Field 类: 提供有关类或接口的属性的信息,以及对它的动态访问的权限。
Constructor类: 提供关于类的单个构造方法的信息以及对它的动态访问的权限。
Method类: 提供关于类或接口上单独某个方法的信息。
Array类: 提供了动态创建数组和访问数组的静态方法。
下面是使用的列子:
domain类:
public class domain {
/*
* 无参构造函数
*/
public domain(){}
/*
* 有参构造函数
*/
public domain(String name,int age){
this.name = name;
this.age = age;
}
/*
* 公有成员变量
*/
public String name = "老鼠";
/*
* 私有成员变量
*/
private int age = 20;
/*
* 私有成员函数
*/
private void show(){
System.out.println("name : " + name + " age : " + age);
}
/*
* 公有成员函数
*/
public int getAge(){
return this.age;
}
/*
* 公有静态函数 (无参数,无返回值)
*/
public static void sayHello(){
System.out.println("Hello World");
}
/*
* 公有静态函数 (有数组类型参数,返回整型值)
*/
public static int getLength(String[] strs){
return strs.length;
}
}
下面就通过反射来访问domain类的字段或方法
首先要获得这个类的字节码:
Class<domain> clazz = (Class<domain>) Class.forName("....domain");
先访问公有无参静态方法:
domain obj = clazz.newInstance(); // 调用默认无参构造函数
Method staticMethod = clazz.getMethod("sayHello", null);// 公有无参静态方法
staticMethod.invoke(null, null);
其中clazz.getMethod("sayHello",null) : 方法名为 sayHello 并且没有参数
staticMethod.invoke(null,null) ::第一个null表示不需要对象就可以调用 第二个null 表示没有参数
访问公有有参静态方法:staticMethod = clazz.getMethod("getLength", String[].class); // 公有有参静态方法
int result = (int) staticMethod.invoke(null, (Object)new String[]{"1","2","3"});
System.out.println(result);
和上面的差不多, staticMethod.invoke(null,(Object) new String[]{....}) : JDK5.0为了和JDK1.4兼容,因为JDK1.4没有可变参数,所以在这个地方这样写:new String[]{....} 编译器会将数组解开当作多个参数,从而会报 参数个数不对的异常。
访问公有成员方法
Method publicMethod = clazz.getMethod("getAge", null); // 公有成员方法
int age = (int) publicMethod.invoke(obj, null);
System.out.println("age : " + age);
这里的 publicMethod.invoke(obj,null); 是表示在obj这个对象上调用该方法
访问私有成员方法:
Method privateMethod = clazz.getDeclaredMethod("show", null); // 私有成员方法
privateMethod.setAccessible(true);
privateMethod.invoke(obj, null);
其中getDecclaredMethod 获得所有声明的方法
setAccessible(true) 设为true则为取消Java的语言访问检查,false则执行Java的语言访问检查
访问共有成员方法:
Constructor constructor = clazz.getConstructor(String.class,int.class); // 公有成员方法
domain obj2 = (domain) constructor.newInstance("XiaoQiang",12);
privateMethod.invoke(obj2, null);
访问公有成员变量:
Field publicField = clazz.getField("name"); // 公有成员变量
String name = (String) publicField.get(obj2);
System.out.println("name : " + name);
publicField,get(obj2); 在指定对象上获取该字段的值
访问私有成员变量:Field privateField = clazz.getDeclaredField("age"); // 私有成员变量
privateField.setAccessible(true);
int age = (int) privateField.get(obj2);
System.out.println("age : " + age);
注解又是JDK5.0的又一个新特性。
我们都有用过注解:@Override 、@Deprecated、@SuppressWarnings("unchecked") 等
@Override
public String toString(){
//return something
}
但是注解不仅仅只是这些
首先我们可以定义注解 关键字: @interface :
public @interface OtherAnnotation {
int years();
}
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD , ElementType.TYPE})
public @interface MyAnnotation {
String name() default "name";
String value();
String[] arr();
OtherAnnotation annotation();
}
@Retention @Target 都是一种元数据,是对注解起作用的注解。
@Retention(RetentionPolicy.RUNTIME) :表示注解作用期间
RetentionPolicy是一个枚举,其中有 :
CLASS : 编译器将把注释记录在类文件中,但在运行时 VM 不需要保留注释。
RUNTIME : 编译器将把注释记录在类文件中,在运行时 VM 将保留注释,因此可以反射性地读取。
SOURCE : 编译器要丢弃的注释。
@Target({ElementType.METHOD , ElementType.TYPE}) 表示注解作用目标
ElementType 是一个枚举,其中有:
FIELD : 字段声明
METHOD : 方法声明
TYPE : 类、接口(包括注解类型)或枚举声明
下面是使用该注解:
@MyAnnotation(value="哈哈",arr={"1","2"},annotation = @OtherAnnotation(years = 1) )
public class AnnotationTest {
}
在MyAnnotation中定义了一些属性: name,value,arr,annotaion
下面则是对几个属性赋值
value="哈哈"
arr={"1","2"}
annotation = @OtherAnnotation(years = 1)
下面在这个类中获取这些值:
if(AnnotationTest.class.isAnnotationPresent(MyAnnotation.class)){
MyAnnotation annotation = (MyAnnotation) AnnotationTest.class.getAnnotation(MyAnnotation.class); // 是不是该类注解
System.out.println(annotation.name()); // 获取name属性的值
System.out.println(annotation.value()); // 获取value属性的值
System.out.println(Arrays.asList(annotation.arr())); // 获取arr属性的值
System.out.println(annotation.annotation().years()); // 获取annotation属性的years属性值
}
3.类加载器
虚拟机在执行程序时,需要加载该程序需要的类文件,打个比方,假设程序从MyClass.class开始执行,则执行步骤为:
1.虚拟机有一个用于加载类文件的机制,(例如从磁盘上读取文件或者请求WEB上的文件;)它用这种机制加载MyClass类文件中的类容。
2.如果MyClass类用友其他类的类型,这这些类也会被加载。
3.如果main方法运行需要更多地类,则这些类也会被加载
类加载机制并非只使用一个类加载器,每个Java程序至少拥有3个类加载器
1.引导类加载器
2.扩展类加载器
3.系统类加载器
引导类加载器负责加载系统类,它是虚拟机整体的一部分。引导类加载器没有对应的ClassLoader对象,列如:
String.class.getClassLoader();
将会返回null
类加载器有一种父/子关系。除了引导类加载器外,每个加载器都有一个父类加载器,根据规定,每个类加载器会为其父类加载器提供一个机会,以便加载任何给定的类,并且只有在其父类加载器加载失败时,它才会加载给定的类。
类加载器层次结构图:
每个Java程序员都知道,报的命名是为了消除名字冲突。在标准类库中有两个Date类,它们的全名分别是java.util.Date和java.sql.Date。在一个正在执行的程序中,所有类名都包含它们的包名。
在同一个虚拟机中可以有两个类,它们的类名和包名都是相同的,这是因为类是由它的全名和类加载器决定的。
编写自己的类加载器:
首先有一个目标类:
public class MyTestTarget{
public String toString() {
return "你好啊";
}
}
先让其编译得到MyTestTarget.class文件
然后编写自己的类加密工具:
public class MyClassCompiler {
public static void main(String [] args) throws Exception{
String fileName = "....MyTestTarget.class"; // 没经过加密的MyTestTarget.class路径
String outputName = "...MyTestTarget.class"; // 经过加密的MyTestTarget.class路径
FileInputStream fis = new FileInputStream(fileName);
FileOutputStream fos = new FileOutputStream(outputName);
compiler(fis,fos);
fos.close();
fis.close();
}
private static void compiler(InputStream input, OutputStream output) throws Exception{
int c;
while((c = input.read()) != -1){
output.write(c ^ key);
}
}
public static final int key = 0;
}
该工具中,将读进来的每个字节都与key进行异或运算,这里我是用的key是0。
运行该工具,然后将加密后的MyTestTarget.class替代原来的
这样,其他任何类加载器都无法加载该类了,现在就编写我们自己的类加载器:
public class MyClassLoader extends ClassLoader{
protected Class<?> findClass(String name) throws ClassNotFoundException {
try{
FileInputStream input = new FileInputStream(name);
ByteArrayOutputStream output = new ByteArrayOutputStream();
decompile(input, output);
input.close();
byte[] bytes = output.toByteArray();
return this.defineClass(bytes, 0, bytes.length);
}catch(Exception e){
e.printStackTrace();
}
return super.findClass(name);
}
public void decompile(InputStream input, OutputStream output)throws Exception{
int c;
while((c = input.read()) != -1){
output.write(c ^ MyClassCompiler.key);
}
}
}
我的类加载器很简单,就是将读到的每个自己都与key进行异或运算,这里的key也是0。
编写自己的类加载器很简单只要继承ClassLoader这个抽象类,然后覆盖findClass(String name)这个方法就行了。
MyClassLoader classLoader = new MyClassLoader();
Object date = (Object) classLoader.loadClass("...\\MyTestTarget.class").newInstance(); //找到MyTestTarget.class的绝对路径
System.out.println(date);
运行结果: 你好啊