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

原创 2013年12月03日 19:10:08

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


Java类加载器与反射

一个命令对应一个进程 当我们启动一个Java程序,即启动一个main方法时,都将启动一个Java虚拟机进程,不管这个进程有多么复杂。而不同的JVM进程之间是不会相互影响的。这也就是为什么说,Java程...
  • rusbme
  • rusbme
  • 2016年05月03日 15:16
  • 1579

Java---注解、类加载器-加强-实现运行任意目录下class中加了@MyTest的空参方法

做自己的类加载器 虚拟机的核心是通过类加载器来加载.class文件,然后进行相应的解析执行。那么我们可以自己做类加载器,手动加载需要的.class以进行解析执行,从而扩展虚拟机的功能。 以下内容摘自A...
  • qq_26525215
  • qq_26525215
  • 2016年05月06日 17:38
  • 11429

当Java反射遇到了类加载器

以前经常偷懒用反射实现一些方便的功能()
  • Enbandarix
  • Enbandarix
  • 2014年09月03日 00:10
  • 1146

Java(8)--初识反射

反射:一个应用软件,在使用时不想让外部应用改变其内部代码,并且想使用继承该软件的接口或类,这就需要使用到反射机制。反射机制的好处:提高了程序的可扩展性。反射:java中可以动态获取已知类中成员的机制。...
  • Mrzhoug
  • Mrzhoug
  • 2015年12月12日 19:16
  • 486

Java注解处理之反射API

java.lang.reflect 包下主要包含一些实现反射功能的工具类,实际上,java.lang.reflect 包所有提供的反射API扩充了读取运行时Annotation信息的能力。当一个Ann...
  • liuxigiant
  • liuxigiant
  • 2017年01月09日 23:23
  • 676

java机制学习——反射,注解,代理,泛型

java机制——反射,注解,代理,泛型
  • hdkjdhdj
  • hdkjdhdj
  • 2016年11月08日 15:17
  • 677

java 通过反射获取注解

有的时候我们想使用反射获取某个类的注释、方法上的注释、属性上的注释。 package com.mine.practice.reflectfield; import java.lang.an...
  • Mrzhang0419
  • Mrzhang0419
  • 2016年07月14日 10:12
  • 2834

java基础增强(泛型,反射, 注解,日志)

泛型// 运行时期异常 @Test public void testGeneric() throws Exception { // 集合的声明 Lis...
  • guanhang89
  • guanhang89
  • 2016年08月20日 00:05
  • 1120

JAVA反射机制深入学习(三)ClassLoader类装载器介绍

接下来介绍下JAVA中非常重要的一个类ClassLoader(类装载器) 类装载器就是寻找类的字节码文件并构造出类在JVM内部表示的对象组件。主要工作由ClassLoader及其子类负责,Clas...
  • icarus_wang
  • icarus_wang
  • 2016年05月29日 12:55
  • 884

Java---JUnita、注解与类加载器详解以及实例

JUnit软件测试技术(工具)在项目中建立专门用户测试的包结构。 在Junit中,通过@Test注解,可以运行一个方法。★ Junit注解说明使用了@Test注解应该满足以下条件: 1) ...
  • qq_26525215
  • qq_26525215
  • 2016年05月04日 17:13
  • 3813
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Java基础&增强 反射 注解 类加载器
举报原因:
原因补充:

(最多只允许输入30个字)