Java基础&增强 反射 注解 类加载器

         ---------------------- 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);


2.注解

     注解又是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);
运行结果: 你好啊


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值