学习目的
了解反射的基本原理;掌握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());
}
}
复制代码
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调用类中的方法,也可以直接操作类中的属性。
动态代理可以解决开发中代理类过多的问题,提供统一的代理功能实现。
在程序开发中使用反射机制并结合属性文件,可以达到程序代码与配置文件相分离的目的。