Java开发人员必懂的基础——反射与动态代理

Posted 蜡笔大新001

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java开发人员必懂的基础——反射与动态代理相关的知识,希望对你有一定的参考价值。

   Java的反射与动态代理是java体系结构中较为底层的知识,初学者可能觉得没有太大的用处,但他们确实著名Spring框架IOC和AOP所用的最重要的内容。当我们需要开发更基础,更广泛的的代码时,就会用到这学知识了。

   在此之前,我们先来了解一下java的类加载机制

 

JVM与类加载机制:

/*
 * 1.JVM:当调用java命令来运行某个java程序时,该命令会启动一个java虚拟机进程,同一个JVM中的所有线程,所有变量都处于同一个进程里,都使用该JVM的内存区
 * 2.JVM:运行下面两个测试类的方法,都是输出2,因为运行这两个测试类,启动了两次JVM进程,在进程结束之后,它对A类所做的全部修改都会丢失--第二次运行JVM时
 *   会再次初始化A类,(两个java程序处于不同的JVM进程中,不同的进程之间不能共享数据)
 * 3.类加载:是指将类对应的.class文件加载到内存中,生成对应的java.lang.Class对象(类是java.lang.Class的对象),可以从本地文件系统,JAR文件中,
 *   通过网络加载.class文件,把一个源文件动态编译并执行加载四种方式
 * 4.类连接:把类的二进制数据合并到JRE中,验证,准备,解析
 * 5.初始化:主要负责对类变量进行初始化(JVM最先初始化的总是java.lang.Object)
 * 6.类初始化的时机:
 *   6.1 创建类的实例(new, 反射创建实例,反序列化创建实例)
 *   6.2 调用某个类的类方法
 *   6.3 调用某个类或接口的类变量,或为类变量赋值
 *   6.4 使用反射来创建某个类对应的java.lang.Class对象
 *   6.5 初始化某个类的子类
 *   6.6 直接使用java命令来运行某个主类
 *   特殊情况见代码:使用final修饰的变量
 */
class A
{
	public static int a = 1;
	static final String str="我是大好人" ; //宏变量,相当于常量,编译时值就能确定
	static final String date = new Date(System.currentTimeMillis())+"";  //不是宏变量,编译时不能确定值
	static
	{
		System.out.println("静态初始化块。。");
	}
}
public class JVMTest
{
	public static void main(String[] args) throws ClassNotFoundException
	{
		/*
		A a = new A();
		a.a++;
		System.out.println(a.a);  //2
		*/
		/*
		System.out.println(A.str);  //并没有进行初始化,
		System.out.println(A.date); //会进行初始化
		*/
		//加载类时并不会执行初始化
		ClassLoader cl = ClassLoader.getSystemClassLoader();
		cl.loadClass("jvm.B");  //!!!接收全类名,即需要加上包包包名,不然会出现classnotfindexception
		System.out.println("加载类完成");
		Class.forName("jvm.B");  //会执行初始化
	}
}
class JVMTest1
{
	public static void main(String[] args)
	{
		A a = new A();
		a.a++;
		System.out.println(a.a);  //2
	}
}


类加载器

/*
 * 7.类加载器:同一个类只会被加载一次(一个类用其全限定名+加载器作为唯一标识)
 *   7.1 Bootstrap ClassLoader:根类加载器,JVM自身实现的,负责加载核心类
 *       Extendsion ClassLoader:扩展类加载器,加载自定义的类库,放在lib.ext下
 *       System ClassLoader:系统类加载器  加载classpath路径下的类
 *       用户自定义类加载器(继承ClassLoader类)
 *   7.2 类加载机制:全盘负责,父类委托,缓存机制
 *   
 */

class ClassLoaderTest
{
	public static void main(String[] args) throws IOException
	{
		ClassLoader systemLoader = ClassLoader.getSystemClassLoader();
		System.out.println("系统类加载器:"+systemLoader); //AppClassLoader的子类
		Enumeration<URL> em = systemLoader.getSystemResources("");
		while(em.hasMoreElements())
		{
			System.out.println(em.nextElement());
		}
		ClassLoader extendLoader = systemLoader.getParent();
		System.out.println("扩展类加载器:"+extendLoader);
		System.out.println("加载路径:"+System.getProperty("java.ext.dirs"));
		System.out.println("扩展类加载器的父类:"+extendLoader.getParent()); //null 根类加载器由JVM实现
	}
}

反射:通过反射访问类的详细信息

package invoke;

import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

/*
 * 1.当编译时无法知道某个类或者对象可能属于哪个类,程序只能依靠运行时信息来发现该类和对象的真实信息,就需要用到反射
 * 2.获得Class对象:每个类被加载之后都会生成对应的java.lang.Class的对象,通过这个Class对象就可以访问到JVM中这个类的信息,获得Class对象有三种方式
 *   2.1 类名.class
 *   2.2 Class.forName(类的全限定名)
 *   2.3 类的实例名.getClass()
 *   前两种方式都是通过类名获取Class对象,通常使用第一种,代码更安全(通常在编译阶段就能检查需要访问的Class对象是否存在),程序性能好(无需调用方法)
 * 3.从Class获得信息:Class类提供了大量的实例方法来获取对应类的详细信息
 *   3.1 获取类包含的构造器
 *     Constructor<T> getConstructor(Class<?>...parameterTypes) 获取指定public构造器
 *     Constructor<?>[] getConstructors() 获取所有public构造器
 *     Constructor<T> getDeclaredConstructor(Class<?>...parameterTypes) 获取所有访问权限的指定参数构造器
 *     Constructor<?>[] getDeclaredConstructor() 获取所有访问权限的任意参数的构造器
 *   3.2 获取类包含的方法,变量,Annotation,内部类等,具体参见API
 *   3.3 java8还新增了方法参数的反射,用法见代码
 */
class Test
{
	public void replace(String str,Map<String,Object> list){}
}
public class InvokeTest {

	public static void main(String[] args) throws NoSuchMethodException, SecurityException
	{
		Class<Test> clazz = Test.class;
		Method replace = clazz.getMethod("replace", String.class,Map.class);
		System.out.println("replace方法参数的个数为:"+replace.getParameterCount());
		Parameter[] params = replace.getParameters();
		int i = 0;
		for (Parameter p : params)
		{
				System.out.println("第"+(++i)+"个参数信息");
				System.out.println("形参名:"+p.getName());
				System.out.println("形参类型"+p.getType());
			    System.out.println("泛型类型"+p.getParameterizedType());	//得到完整的参数和泛型
		}
	}
}

/*
 * 4.创建对象
 *   4.1 使用Class对象的newInstance()方法创建对应类的对象,需要该类有默认的构造器被调用(常用)
 *   4.2 先使用Class对象获取指定的Constructor对象,再调用Constructor的newInstance()方法来创建实例,可以指定构造器
 *  对象池工厂(实现简单的对象池,会根据配置文件读取key-value对,然后创建这些对象,并将这些对象放入HashMap中)
 */
class ObjectPoolFactory
{
	//对象池,key为对象名,value为实际的对象
	private Map<String,Object> objectPool = new HashMap<>();
	//创建对象,通过反射
	private Object createObject(String className) throws ClassNotFoundException, InstantiationException, IllegalAccessException
	{
		Class<?> clazz = Class.forName(className);
		return clazz.newInstance();
	}
	//读取配置文件初始化对象池
	private void initPool(String fileName) throws ClassNotFoundException, InstantiationException, IllegalAccessException
	{
		try(FileInputStream fis = new FileInputStream(fileName))
		{
			Properties prop = new Properties();
			prop.load(fis);
			for (String name : prop.stringPropertyNames())
			{
				objectPool.put(name, createObject(prop.getProperty(name)));
			}
		}
		catch(IOException e)
		{
			e.printStackTrace();
		}			
	}
	//外部获取反射对象
	/*
	 * 配置文件 prop.txt,放在工程名目录下
	 * a = java.lang.String
	   b = java.util.Date
	 */

	public Object getObject(String name)
	{
		return objectPool.get(name);
	}
	public static void main(String[] args) throws ClassNotFoundException,SecurityException ,
	InstantiationException, IllegalAccessException, NoSuchMethodException,
	IllegalArgumentException, InvocationTargetException 
	{
		ObjectPoolFactory pf = new ObjectPoolFactory();
		pf.initPool("prop.txt");
		System.out.println(pf.getObject("a"));
		System.out.println(pf.getObject("b"));

		//使用指定的构造器创建实例
		Class<?> clazz = Class.forName("javax.swing.JFrame");
		Constructor con = clazz.getConstructor(String.class); //获取指定形参构造器
		Object obj = con.newInstance("测试窗口");
		System.out.println(obj);
	}
}

/*
 * 5.调用方法:可用Method对象来调用它指定的方法
 *   5.1 Object invoke(Object obj,Object..args) obj是方法的主调,args是方法的参数
 *   下面程序对上面的对象池工程进行增强,允许在配置文	件中增加配置对象的成员变量的值,对象池工厂会读取为对象配置的成员变量值
 *   并利用该对象对应的setter方法设置成员变量的值(Spring框架就是通过这种方式将成员变量的值以及依赖对象都放在配置文件中进行管理的,
 *     从而实现较好的解耦。这也是IOC的实现方式)
 *   5.2 当通过Method的invoke方法调用对应的方法时,java要求程序必须由调用该方法的权限,如果程序确实需要调用某个对象的private方法
 *     则可调用Method对象的setAccessible(true)  可取消访问权限检查,Method Field Constructor都有
 */

class ExtendObjectPoolFactory
{
	private Map<String,Object> objectPool = new HashMap<>();
	private Properties config = new Properties();
	public void init(String fileName)
	{
		try(FileInputStream fis = new FileInputStream(fileName))
		{
			config.load(fis);
		}
		catch(IOException e)
		{
			e.printStackTrace();
		}
	}
	//创建对象
	public Object createObject(String clazzName) throws ClassNotFoundException, InstantiationException,
	IllegalAccessException
	{
		Class<?> clazz = Class.forName(clazzName);
		Object obj = clazz.newInstance();
		return obj;
	}
	//初始化对象池
	public void initPool() throws ClassNotFoundException, InstantiationException, IllegalAccessException
	{
		for (String name : config.stringPropertyNames())
			{
				if(!name.contains("%"))
				{
					objectPool.put(name,createObject(config.getProperty(name))); //创建成功
				}
			}
	}
	public Object getObject(String name)
	{
		return objectPool.get(name);
	}
	//根据属性配置文件调用对应的setter方法
	public void initProperty() throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException
	{
		for (String name:config.stringPropertyNames())
		{
			if(name.contains("%"))
			{
				String[] objAndProp = name.split("%");
				Object target = getObject(objAndProp[0]);
				String mtdName = "set"+objAndProp[1].substring(0,1).toUpperCase()+objAndProp[1].substring(1);
				Class<?> targetClass = target.getClass();
				Method method = targetClass.getMethod(mtdName, String.class);
				method.invoke(target, config.getProperty(name));
			}
		}
	}
	public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException,
	                                        NoSuchMethodException, SecurityException, IllegalArgumentException, InvocationTargetException
	{
		ExtendObjectPoolFactory factory = new ExtendObjectPoolFactory();
		factory.init("prop.txt"); //加载配置文件
		factory.initPool(); //初始化对象池
		factory.initProperty();
		System.out.println(factory.getObject("a"));
	}
}


/*
 * 6.访问成员变量的值
 */

class Person
{
	private String name;
	private int age;
	public String toString()
	{
		return "姓名:"+name+"  年龄:"+age;
	}
}
class FieldTest
{
	public static void main(String[] args) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException
	{
		Person p = new Person();
		Class<Person> clazz = Person.class;
		Field name = clazz.getDeclaredField("name");
		name.setAccessible(true);  //设置访问private变量的权限
		name.set(p, "lee");
		Field age = clazz.getDeclaredField("age");
		age.setAccessible(true);
		age.setInt(p, 20);
		System.out.println(p);
	}
}



动态代理与AOP

package invoke;

import java.lang.reflect.Field;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Proxy;
import java.lang.reflect.Type;
import java.util.Map;
/*
 * 1.Proxy提供了用于创建一个或多个接口的动态代理类和动态代理对象,JDK动态代理只能为借口创建动态代理
 *   1.1 static Class<?> getProxyClass(ClassLoader loader,Class<?>...interfsaces)
 *   1.2 static Object newProxyInstance(ClassLoader loader,Class<?>[] interface,InvocationHandler h)
 *   系统生成的每个代理对象都有一个与之关联的InvocationHandler对象,执行代理对象的每个方法都会被替换成执行InvocationHandler对象的invoke方法
 * 2.使用Proxy和InvocationHandler创建动态代理对象,见代码  
 */
interface Student
{
	void walk();
	void sayHello(String name);
}
class MyInvocationHandler implements InvocationHandler
{
	public Object invoke(Object proxy, Method method, Object[] args)throws Throwable
	{
		System.out.println("--正在执行的方法"+method);
		if(args != null)
		{
			System.out.println("执行方法时传入的实参:");
			for (Object object : args)
			{
				System.out.println(object);
			}
		}
		else
		{
			System.out.println("该方法没有传入任何参数");
		}
		return null;
	}
}

public class ProxyTest
{
	public static void main(String[] args)
	{
		InvocationHandler handler = new MyInvocationHandler();
		Student s = (Student) Proxy.newProxyInstance(Student.class.getClassLoader(),new Class[]{Student.class}, handler);
		s.walk();
		s.sayHello("lee");
	}
}

/*
 * 3.使用动态代理和AOP,目标对象与为其生成的代理对象实现相同的接口,具有相同的方法。非常灵活的实现了解耦,有助于代码的后期维护
*/
/*运行结果:
 * ==模拟插入方法1==
我是一只猎狗
==模拟插入方法2==
==模拟插入方法1==
我奔跑迅速
==模拟插入方法2==

 */
interface Dog
{
	void info();
	void run();
}//如果直接为接口创建动态代理,那么所有的方法执行体都相同,没太大意义,所以通常为接口提供一个或多个实现类
class GunDog implements Dog
{
	public void info()
	{
		System.out.println("我是一只猎狗");
	}
	public void run()
	{
		System.out.println("我奔跑迅速");
	}
}
class DogUtil
{
	public void method1()
	{
		System.out.println("==模拟插入方法1==");
	}
	public void method2()
	{
		System.out.println("==模拟插入方法2==");
	}
}

class MyInvocationhandler1 implements InvocationHandler
{
	private Object target;
	public void setTarget(Object target)
	{
		this.target = target;
	}
	
	public Object invoke(Object proxy, Method method, Object[] args)throws Throwable
	{
		DogUtil util = new DogUtil();
		util.method1();
		Object result = method.invoke(target, args);
		util.method2();
		return result;
	}
}
//为指定的target生成动态代理实例
class MyProxyFactory
{
	public static Object getProxy(Object target)
	{
		MyInvocationhandler1 hander1 = new MyInvocationhandler1();
		hander1.setTarget(target);
		return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),hander1);
	}
} //动态代理对象与target实现相同的接口

class TestAOP
{
	public static void main(String[] args)
	{
		Dog target = new GunDog();
		Dog dog = (Dog) MyProxyFactory.getProxy(target);
		dog.info();
		dog.run();
	}
}
/*
 * 4.使用反射来获取泛型信息
 */
class GenericTest
{
	//private Integer score;
	private Map<String,Integer> score;
	public static void main(String[] args) throws NoSuchFieldException, SecurityException
	{
		Class<GenericTest> clazz = GenericTest.class;
		Field f = clazz.getDeclaredField("score");
		Class<?> a = f.getType();
		System.out.println("score的类型是:"+a);
		Type gtype = f.getGenericType();
		if(gtype instanceof ParameterizedType)
		{
			ParameterizedType pt = (ParameterizedType)gtype;
			Type rtype = pt.getRawType();
			System.out.println("原始类型是:"+rtype);
			Type[] targs = pt.getActualTypeArguments();  //得到泛型的类型
			System.out.println("泛型信息是:");
			for(int i=0 ; i<targs.length; i++)
			{
				System.out.println("第"+(i+1)+"个泛型类型是:"+targs[i]);
			}
		}
		else
		{
			System.out.println("获取泛型信息出错");
		}
	}
 }



从函数的反射来认识集合的泛型

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


public class ClassDemo4
{
	public static void main(String[] args)
	{
		List list1 = new ArrayList();
		List<String> list2 = new ArrayList<String>();
		list2.add("QQQ");
		//list2.add(20); 编译出错
		Class c1 = list1.getClass();
		Class c2 = list2.getClass();
		System.out.println(c1 == c2);
		
		/*
		 * 	反射的操作都是编译之后的操作
		 * 	c1==c2结果为true,说明list集合编译之后是去泛型化的
		 * 	java集合的泛型是防止错误输入的,只在编译阶段有效,绕过编译就无效了
		 *      验证:通过方法的反射操作绕过编译	
		 */
		try
		{
			//Method method = c2.getMethod("add",new Class[]{String.class});
			Method method = c2.getMethod("add", Object.class);
			Object obj = method.invoke(list2,3);
			System.out.println(list2.size());
			//打印出来的list2的size()为2,也就是说整数2加进了拥有String泛型的list2 中,得证
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}
		
	}

}


这部分是学习java框架知识的基础,需要熟练掌握。

以上是关于Java开发人员必懂的基础——反射与动态代理的主要内容,如果未能解决你的问题,请参考以下文章

动态代理与反射在GraphQL客户端的应用

java深入分析Java反射-动态代理 proxy

Java编程基础-反射

##(⊙o⊙)值得收藏的JavaSE万字进阶版(⊙o⊙)##JavaSE 高级反射-动态代理-设计模式-JVM篇

反射——Java高级开发必须懂的

讲清python开发必懂的8种数据结构