Java 反射机制

本文围绕Java反射机制展开,介绍了反射基本原理,重点讲解Class类的使用,包括无参和有参构造实例化对象。还阐述了动态代理和静态代理的区别及使用场景,以及工厂设计模式和结合属性文件的工厂模式。最后总结了Class类实例化方式和反射机制的应用。

学习目的

了解反射的基本原理;掌握Class类的使用;使用Class类并结合java.lang.reflect包取得一个类的完整结构;通过反射机制动态地调用类中的指定方法,并能向这些方法中传递参数。

在java中较为重要的就是反射机制,那么什么是反射机制呢?正常情况下如果已经有一个类,则肯定可以通过类创建对象;那么如果现在要求通过一个对象找到一个类的名称,此时就需要用到反射机制。如果要完成反射操作,则首先应该认识的就是Class类。

在反射的学习中,读者一定要把握一个核心概念:“一切的操作都将使用Object完成,类、数组的引用都可以使用Object进行接收”,只有把握了这个概念才能更清晰地掌握反射机制的作用。

认识Class类

public final Class getClass();
复制代码

以上方法返回值的类型是一个Class类,实际上此类是Java反射的源头。所谓反射从程序的运行结果来看很好理解。即通过对象反射求出类的名称。

正常方式:引入需要的“包.类”名称——通过new实例化——取得实例化对象
反射方式:实例化对象——getClass()方法——得到完成的“包类”名称
复制代码

在java中Object类是一切类的父亲,那么所有类的对象实际上也就都是java.lang.Class类的实例,所有所有的对象都可以转变为java.lang.Class类型表示。

//传入完整的“包.类”名称实例化Class对象
public static Class<?> forName(String className) throws ClassNotFoundException 
复制代码
package com.dzj.reflect;
public class TestReflect {

}
复制代码
package com.dzj.reflect;
public class GetClassDemo {
	public static void main(String[] args) {
       Class<?> c1 = null;
       Class<?> c2 = null;
       Class<?> c3 = null;
       
       try {
		c1 = Class.forName("com.dzj.reflect.TestReflect");
	} catch (ClassNotFoundException e) {
		
		e.printStackTrace();
	}
    c2 = new TestReflect().getClass();
    c3 = TestReflect.class;
    System.out.println("类名称" + c1.getName());
    System.out.println("类名称" + c2.getName());
    System.out.println("类名称" + c3.getName());
	}
}
复制代码

3种实例化Class对象的方式是一样的,但是使用forName()静态方法实例化Class对象是较为常用的一种方式。

Class类的使用
通过无参构造实例化对象

如果要想通过Class类本身实例化其他类的对象,则可以使用newInstance()方法,但是必须保证被实例化的类中存在一个无参构造方法。

package com.dzj.reflect;
public class Person {
	private String name;
	private int age;

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}
	//覆盖toString方法
	public String toString(){
		return "姓名:" + this.name + ",年龄:" + this.age;
	}
}
复制代码
package com.dzj.reflect;

public class InstanceDemo {
	public static void main(String[] args) {
		
	   Class<?> c = null;
       try {
	   c = Class.forName("com.dzj.reflect.Person");
	} catch (ClassNotFoundException e) {
		e.printStackTrace();
	}
    
    Person per = null;
    try {
		per  = (Person)c.newInstance();
	} catch (Exception e) {
		e.printStackTrace();
	}
    per.setName("张三");
    per.setAge(30);
    System.out.println(per);
	}
}

复制代码

在实际开发中,反射是最为重要的操作原理,现在的开发设计中大量应用了反射处理机制,如Struts、Spring框架;在大部分操作中基本上都是操作无参构造方法,一定要保留无参构造方法。
复制代码
通过有参构造实例化对象
package com.dzj.reflect;

public class Person {
	private String name;
	private int age;
    //有参构造
	public Person(String name, int age) {
		super();
		this.name = name;
		this.age = age;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}
	//覆盖toString方法
	public String toString(){
		return "姓名:" + this.name + ",年龄:" + this.age;
	}

}

复制代码

InstanceDemo2类

package com.dzj.reflect;
import java.lang.reflect.Constructor;

public class InstanceDemo2 {
	public static void main(String[] args) {

		Class<?> c = null;
		try {
			c = Class.forName("com.dzj.reflect.Person");
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}

		Person per = null;
		Constructor<?> cons[] = null;//声明一个构造方法的数组
		cons = c.getConstructors();//通过反射取得全部构造
		try {
			//向构造方法中传递参数,此方法使用可变参数接收,并实例化对象;因为只有一个构造,所以下标为0
			per = (Person) cons[0].newInstance("张三",30);
		} catch (Exception e) {
			e.printStackTrace();
		}
		per.setName("张三");
		per.setAge(30);
		System.out.println(per);
	}
}

复制代码
动态代理和静态代理

静态代理:每个代理类只能为一个接口服务,导致程序开发中必然会产生过多的代理。

动态代理:通过一个代理类完成全部的代理功能,那么此时必须使用动态代理完成。

在java中要想实现动态代理机制,则需要java.lang.reflect.InvocationHandler接口和java.lang.reflect.Proxy类的支持。

InvocationHandler接口的定义如下:

public interfact InvocationHandler{
    public Object invoke(Object proxy,Method method,Object[] args) throws Throwable
}
复制代码

在此接口中只定义一个invoke()方法,此方法有3个参数,其参数的意义如下:

Proxy类是专门完成代理的操作类,可以通过此类为一个或多个接口动态地生成实现类。Proxy类提供了如下的操作方法:

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHander h) throws lllegalArgumentException
复制代码

通过newProxyInstance()方法可以动态地生成实现类,在此方法中的参数意义如下。

ClassLoader loader:类加载

Class<?>[] interfaces:得到全部的接口

InvocationHander h:得到InvocationHander接口的子类实例。

写一个 ArrayList 的动态代理类
package com.dzj.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.List;

public class TestProxy {
	public static void main(String[] args) {
		final List<String> list = new ArrayList<String>();
		List<String> proxyInstance = (List<String>) Proxy.newProxyInstance(list.getClass().getClassLoader(),
				list.getClass().getInterfaces(), new InvocationHandler() {

					@Override
					public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
						return method.invoke(list, args);
					}
				});
		proxyInstance.add("张三");
		System.out.println(list);
	}
}
复制代码
动静态代理的区别,什么场景使用?

静态代理通常只代理一个类,动态代理是代理一个接口下的多个实现类。 静态代理事先知道要代理的是什么,而动态代理不知道要代理什么东西,只有在运行时才知道。

动态代理是实现 JDK 里的 InvocationHandler 接口的 invoke 方法,但注意的是代理的是接口,也就是你的业务类必须要实现接口,通过 Proxy 里的 newProxyInstance 得到代理对象。

还有一种动态代理 CGLIB,代理的是类,不需要业务类继承接口,通过派生的子类来实现代理。通过在运行时,动态修改字节码达到修改类的目的。

AOP 编程就是基于动态代理实现的,比如著名的 Spring 框架、Hibernate 框架等等都是动态代理的使用例子。

工厂设计模式

通过简单的工厂模式可以达到类的解耦合目的。

package com.dzj.factory;
public interface Fruit {
   public void eat();
}
复制代码
package com.dzj.factory;
public class Apple implements Fruit{
	@Override
	public void eat() {
       System.out.println("***吃苹果");		
	}
}
复制代码
package com.dzj.factory;
public class Orange implements Fruit{

	@Override
	public void eat() {
       System.out.println("**吃橘子");		
	}

}
复制代码
package com.dzj.factory;

public class Factory {
	// 取得接口实例
	public static Fruit getInstance(String className) {
		Fruit fruit = null;// 定义接口对象
		try {
			fruit = (Fruit) Class.forName(className).newInstance();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return fruit;
	}
}
复制代码

结合属性文件的工厂模式

以上代码虽然可以通过反射取得接口的实例,但是在操作时需要传入完整的包.类名称,而且用户也无法知道一个接口有多少个可以使用的子类,所以此时可以通过属性文件的形式配置所要的子类信息。

fruit.properties类
apple = com.dzj.factory.Apple
复制代码
Init类
package com.dzj.factory;
//属性操作类

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.Properties;

public class Init {
   public static Properties getPro(){
	   Properties pro = new Properties();
	   File file = new File("/Users/dzj-admin/workspace/javaStudy/BuilderPartter/src/com/dzj/factory/fruit.properties");
	   try {
	   if(file.exists()){
			pro.load(new FileInputStream(file));//读取属性
		}else {
			pro.setProperty("apple", "");//建立一个新的属性文件,同时设置
			pro.store(new FileOutputStream(file), "FRUIT CLASS");
		}
	    }catch (Exception e) {
			e.printStackTrace();
		}
	   return pro;
	   }
 }
复制代码
测试类
package com.dzj.factory;
import java.util.Properties;
public class FactoryDemo2 {
	public static void main(String[] args) {
       Properties pro = Init.getPro();//初始化属性类
       //通过工厂类取得接口实例,通过属性的key传入完整的包.类名称
       Fruit fruit = Factory.getInstance(pro.getProperty("apple"));
       if(fruit != null ){//判断是否取得接口实例
    	  fruit.eat(); //调用方法
       }
	}
}
复制代码

在通过工厂类取得接口实例,直接输入属性的key就可以找到其完整的包.类名称,以达到对象实例化功能。

在本程序中可以发现,程序很好地实现类代码与配置的分离。通过配置文件配置要使用的类,之后通过程序读取配置文件,以完成具体的功能。

程序————程序代码————运行时读取属性文件以找到相应的配置信息————
配置文件(保存程序运行的相关信息;配置文件决定程序的运行)
复制代码

通过本程序要更好地理解配置文件在程序开发中的作用,这样就能编写出更好的程序代码。

总结

Class类的对象有3种实例化方式:

1.通过Object类中的getClass()方法;

2.通过“类.class”的形式;

3.通过Class.forName()方法,此种方式最为常用。

可以通过Class类中newInstance()方法进行对象的实例化操作,但是要求类中必须存在无参构造方法,如果类中没有无参构造,则必须使用Constructor类完成对象的实例化操作。

可以通过反射取得一个类所继承的父类、实现的接口、类中的全部构造方法、全部普通方法及全部属性。

使用反射机制可以通过Method调用类中的方法,也可以直接操作类中的属性。

动态代理可以解决开发中代理类过多的问题,提供统一的代理功能实现。

在程序开发中使用反射机制并结合属性文件,可以达到程序代码与配置文件相分离的目的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值