需要掌握 获取Class实例、创建运行时类的对象、调用运行时类的指定结构。
反射概述Reflection(反射)是被视为动态语言的关键,反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。
加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以,我们形象的称之为:反射。
补充:动态语言 vs 静态语言
1、动态语言
是一类在运行时可以改变其结构的语言:例如新的函数、对象、甚至代码可以被引进,已有的函数可以被删除或是其他结构上的变化。通俗点说就是在运行时代码可以根据某些条件改变自身结构。
主要动态语言:Object-C、C#、Javascript、PHP、Python、Erlang。
2、静态语言
与动态语言相对应的,运行时结构不可变的语言就是静态语言。如Java、C、 C++。
Java不是动态语言,但Java可以称之为“准动态语言”。即Java有一定的动态性,我们可以利用反射机制、字节码操作获得类似动态语言的特性。
Java的动态性让编程的时候更加灵活!
java.lang.Class:代表一个类
java.lang.reflect.Method:代表类的方法
java.lang.reflect.Field:代表类的成员变量
java.lang.reflect.Constructor:代表类的构造器
… …
public class Person { private String name; public int age; public Person() { } private Person(String name) { this.name = name; } public Person(String name, int age) { 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; } @Override public String toString() { return "Person{" + "name='" + name + ''' + ", age=" + age + '}'; } public void show(){ System.out.println("我是一个人"); } private String showNation(String nation){ System.out.println("我的国籍是" + nation); return nation; }}public class ReflectionTest { //反射之前,对于Person的操作 @Test public void test1(){ //1.创建Person类的对象 Person p1 = new Person("Tom",12); //2.通过对象,调用其内部的属性、方法 p1.age = 10; System.out.println(p1); p1.show(); //在Person类外部,不可以通过Person类的对象调用其内部私有结构 //比如:name、showNation()以及私有的构造器 } //反射之后,对于Person的操作 @Test public void test2() throws Exception{ Class clazz = Person.class; //1.通过反射创建Person类的对象 Constructor cons = clazz.getConstructor(String.class, int.class); Object obj = cons.newInstance("Tom",12); Person p = (Person) obj; System.out.println(p); //2.通过反射,调用对象指定的属性、方法 //调用属性 Field age = clazz.getDeclaredField("age"); age.set(p,10); System.out.println(p); //调用方法 Method show = clazz.getDeclaredMethod("show"); show.invoke(p); //通过反射,可以调用Person类的私有结构的。比如:私有的构造器、方法、属性 //调用私有的构造器 Constructor cons1 = clazz.getDeclaredConstructor(String.class); cons1.setAccessible(true); Person p1 = (Person) cons1.newInstance("Jerry"); System.out.println(p1); //调用私有的属性 Field name = clazz.getDeclaredField("name"); name.setAccessible(true); name.set(p1,"Lilei"); System.out.println(p1); //调用私有的方法 Method showNation = clazz.getDeclaredMethod("showNation", String.class); showNation.setAccessible(true); String nation = (String) showNation.invoke(p1, "中国");//相当于String nation = p1.showNation("中国"); System.out.println(nation); }}
反射不仅可以调用公有的构造器、属性、方法;还可以调用私有的构造器、属性、方法。
这就会涉及到一些疑问:
疑问1:通过new的方式或反射的方式都可以调用公共的结构,开发中到底用哪个?
建议:直接new的方式。
什么时候会使用反射的方式:反射的特征:动态性。当在执行过程中要修改对象、新建对象、属性、方法等可以使用反射的方式。
疑问2:反射机制与面向对象中的封装性是不是矛盾的?如何看待两个技术?
不矛盾。
封装性体现的是私有的结构并不是说不让你调用,是私有的结构已经封装好在公共的方法或构造器中了,直接调用公共的就可以了,没有必要调用私有的了。所以封装性体现的是建议如何做,建议不让调用私有的结构。
而反射是能不能的问题,是可以调用私有的构造器、属性、方法,只不过封装性建议你不要调用私有的,但是如果想调用还是可以调用,通过反射机制就可以进行调用。
关于java.lang.Class类的理解1.类的加载过程:
程序经过javac.exe(编译)命令以后,会生成一个或多个字节码文件(.class结尾)。
接着我们使用java.exe命令对某个字节码文件进行解释运行。相当于将某个字节码文件加载到内存中。此过程就称为类的加载(不包括编译)。加载到内存中的类,我们就称为运行时类,此运行时类,就作为Class的一个实例。
2.换句话说,Class的实例就对应着加载到内存中的一个运行时类。(这也说明了Class类的实例不是new出来的)。Class实例是一个类。
3.加载到内存中的运行时类,会缓存一定的时间。在此时间之内,我们可以通过不同的方式来获取此运行时类。
到此,就补充了万事万物皆对象。对象.xxx,File,URL,反射,前端,数据库操作
获取Class的实例的方式获取Class的实例的方式(前三种方式需要掌握)。其中,方式三是用的频率最高的。
加载到内存中的运行时类,会缓存一定的时间。在此时间之内,我们可以通过不同的方式来获取此运行时类。 运行时类在生命周期内都是同一个。只不过是通过不同的方式获取。
方式一之所以用的少是因为用反射就是因为反射的动态性,而方式一直接写死了,不能再做修改,只能获取某一个的Class实例,没有体现动态性。而方式三就体现了动态性,只有在实际运行的时候才能确定是得到哪个Class实例。
@Test public void test3() throws ClassNotFoundException { //方式一:调用运行时类的属性:.class Class
1.class:外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类
2.interface:接口
3.[]:数组。注意只要数组的元素类型与维度一样,就是同一个Class
4.enum:枚举
5.annotation:注解@interface
6.primitive type:基本数据类型
7.void
//Class实例可以是哪些结构的说明:Class c1 = Object.class;Class c2 = Comparable.class;Class c3 = String[].class;Class c4 = int[][].class;Class c5 = ElementType.class;Class c6 = Override.class;//注解Class c7 = int.class;Class c8 = void.class;Class c9 = Class.class;int[] a = new int[10];int[] b = new int[100];Class c10 = a.getClass();Class c11 = b.getClass();// 只要元素类型与维度一样,就是同一个Class System.out.println(c10 == c11);//true
类的加载过程加载:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口(即引用地址)。所有需要访问和使用类数据只能通过这个Class对象。这个加载的过程需要类加载器参与。链接:将Java类的二进制代码合并到JVM的运行状态之中的过程。
验证:确保加载的类信息符合JVM规范,例如:以cafe开头,没有安全方面的问题准备:正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存都将在方法区中进行分配。解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程。 初始化:
执行类构造器()方法的过程。类构造器()方法是由编译期自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的。(类构造器是构造类信息的,不是构造该类对象的构造器)。当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发虚拟机会保证一个类的()方法在多线程环境中被正确加锁和同步。
举例:
public class ClassLoadingTest { public static void main(String[] args) { System.out.println(A.m); }}class A { static {m = 300;}static int m = 100;}//第二步:链接结束后m=0//第三步:初始化后,m的值由
类加载器的作用:
类加载的作用:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后在堆中生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口。类缓存:标准的JavaSE类加载器可以按要求查找类,但一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间。不过JVM垃圾回收机制可以回收这些Class对象。
类加载器作用是用来把类(class)装载进内存的。JVM 规范定义了如下类型的类的加载器。
public class ReflectionTest{ @Test public void test4(){ //对于自定义类,使用系统类加载器进行加载 ClassLoader classLoader = ReflectionTest.class.getClassLoader();//获取当前类的类加载器 System.out.println(classLoader); //调用系统类加载器的getParent():获取拓展类加载器 ClassLoader classLoader1 = classLoader.getParent(); System.out.println(classLoader1); //调用拓展类加载器的getParent():无法获取引导类加载器 //引导类加载器主要负责加载java的核心类库,无法加载自定义类的。 ClassLoader classLoader2 = classLoader1.getParent(); System.out.println(classLoader2);//null 无法获取 //核心类库String ClassLoader classLoader3 = String.class.getClassLoader(); System.out.println(classLoader3);//null 无法获取 }}
使用ClassLoader加载配置文件(需要掌握)注意二者的默认读取路径不同即可。都需要掌握。
使用IO流读取默认在当前Module下,使用ClassLoader默认在当前Module的src下。
public class ReflectionTest{@Test public void test5() throws IOException { Properties pros = new Properties(); //此时的文件默认在当前的module下。 //读取配置文件的方式一// FileInputStream fis = new FileInputStream("jdbc.properties");// pros.load(fis); //读取配置文件的方式二:使用ClassLoader //配置文件默认识别为:当前module的src下 //注意:在web阶段配置文件是存放在src下的,如果用方式一需要填写src\jdbc.properties ClassLoader classLoader = ReflectionTest.class.getClassLoader();//获取当前类的类加载器 InputStream is = classLoader.getResourceAsStream("jdbc.properties"); pros.load(is); String user = pros.getProperty("user"); String password = pros.getProperty("password"); System.out.println("user = " + user + ",password = " + password); }}
通过反射创建运行时类的对象 创建对象只有调用构造器这一种方法,即便通过调用其他方法创建了对象,也是因为这个方法内部封装调用了构造器。
newInstance():调用此方法,创建对应的运行时类的对象。内部调用了运行时类的空参构造器。
要想此方法正常的创建运行时类的对象,要求:
1.运行时类必须提供空参的构造器
2.空参的构造器的访问权限得够。通常设置为public。
在JavaBean中要求提供一个public的空参构造器。原因:
1.便于通过反射,创建运行时类的对象
2.便于子类继承此运行时类时,默认调用super()时,保证父类有此构造器
public class NewInstanceTest { //通过反射创建对应的运行时类的对象 @Test public void test() throws Exception { Class
体会反射的动态性,在编译阶段无法预知将要创建哪个对象,只有运行起来才知道。后面框架中会大量用到反射。
//体会反射的动态性,在编译阶段无法预知将要创建哪个对象,只有运行起来才知道 //后面框架中会大量用到反射 @Test public void test1(){ int num = new Random().nextInt(3);//0,1,2 String classPath = ""; switch (num){ case 1: classPath = "java.util.Date"; break; case 2: classPath = "java.lang.Object"; break; case 0: classPath = "com.exer.later.Person"; break; } Object obj = null; try { obj = getInstance(classPath); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } System.out.println(obj); } public Object getInstance(String classPath) throws ClassNotFoundException, InstantiationException, IllegalAccessException { Class<?> clazz = Class.forName(classPath); Object obj = clazz.newInstance(); return obj; }
获取运行时类的完整结构 获取运行时类的属性结构及其内部结构实际开发中并不会这么去做,但是需要知道可以这样做。注意有无Declared的区别
@Test public void test2(){ Class
与属性相同。
@Test public void test4(){ Class
框架 = 注解+ 反射 + 设计模式
@Test public void test5(){ Class
注意:getConstructors():获取当前运行时类中声明为public的构造器。(这里跟方法、属性有所不同,获取父类的构造器也没用)
//获取构造器结构 @Test public void test6(){ Class
代码分为:逻辑性代码 vs 功能性代码。可以将一些代码封装起来重复使用。
@Test public void test7(){ Class
@Test public void test8(){ Class
获取父类中的泛型以后会用,获取运行时类实现的接口以后会用,获取注解以后会用。
调用运行时类的指定结构(掌握) 操作运行时类中的指定的属性只会第二种方法即可。
先创建运行时类的对象。
1.getDeclaredField(String fieldName):获取运行时类中指定变量名的属性
2.setAccessible(true);
3.获取、设置指定对象的此属性值
@Test public void test9() throws Exception { Class
先创建运行时类的对象。
1.获取指定的某个方法
2.setAccessible(true);
3.调用方法的invoke():参数1:方法的调用者 参数2:给方法形参赋值的实参
调用静态的方法。(同理可以写出静态属性)
@Test public void test11() throws Exception{ Class
因为调用构造器都是创建对象,而创建对象我们大多使用newInstance(),所以这个方法作为了解,只需知道可以这样做即可。
先获取Class实例
1.获取指定的构造器。
2.setAccessible(true);
3.调用此构造器创建运行时类的对象
//如何调用运行时类指定的构造器(很少用)。因为调用构造器都是创建对象,而创建对象我们大多使用newInstance() @Test public void test12() throws Exception { Class
总结:创建类的对象的方式
方式一:new + 构造器
方式二:要创建Xxx类的对象,可以考虑:Xxx、Xxxs、XxxFactory、XxxBuilder类中查看是否有静态方法的存在。可以调用其静态方法,创建Xxx对象。
方式三:通过反射。
优先考虑方式一,其次方式二、万不得已才考虑方式三。
反射的应用:动态代理代理模式必须有一个接口,被代理类和代理类均实现这个接口。
静态代理举例特点:代理类和被代理类在编译期间就确定下来了
interface ClothFactory{ void produceCloth();}//代理类class ProxyClothFactory implements ClothFactory{ private ClothFactory factory;//用被代理类对象进行实例化 public ProxyClothFactory(ClothFactory factory) { this.factory = factory; } @Override public void produceCloth() { System.out.println("代理工厂做准备工作"); factory.produceCloth(); System.out.println("代理工厂做收尾工作"); }}//被代理类class NikeClothFactory implements ClothFactory{ @Override public void produceCloth() { System.out.println("Nike工厂生产运动服装"); }}public class StaticProxyTest { public static void main(String[] args) { //创建被代理类的对象 NikeClothFactory nike = new NikeClothFactory(); //创建代理类的对象 ProxyClothFactory factory = new ProxyClothFactory(nike); factory.produceCloth(); }}
动态代理举例代码逻辑很强。是动态的生成一个代理,并不是已经确定好的。
interface Human{ String getBelief(); void eat(String food);}//被代理类class SuperMan implements Human{ @Override public String getBelief() { return "I believe I can fly!"; } @Override public void eat(String food) { System.out.println("我喜欢吃" + food); }}class ProxyFactory{ //调用此方法,返回一个代理类的对象。解决问题一 public static Object getProxyInstance(Object obj){//obj被代理类的对象 MyInvocationHandler handler = new MyInvocationHandler(); handler.bind(obj); return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(),handler); }}class MyInvocationHandler implements InvocationHandler{ private Object obj;//需要使用被代理类的对象进行赋值 public void bind(Object obj){ this.obj = obj; } //当我们通过代理类的对象,调用方法a时,就会自动的调用如下的方法:invoke() //将被代理类要执行的方法a的功能就声明在invoke()中 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //method:即为代理类对象调用的方法,此方法也就作为了被代理类对象要调用的方法 //obj:被代理类的对象 Object returnVal = method.invoke(obj, args); //上述方法的返回值就作为当前类中的invoke()的返回值。 return returnVal; }}public class ProxyTest { public static void main(String[] args) { SuperMan superMan = new SuperMan(); //proxyInstance:代理类的对象 Human proxyInstance = (Human) ProxyFactory.getProxyInstance(superMan);//动态生成的代理类与被代理类实现同一个接口 //当通过代理类对象调用方法时,会自动的调用被代理类中同名的方法 String belief = proxyInstance.getBelief(); System.out.println(belief); proxyInstance.eat("麻辣烫火锅"); NikeClothFactory nikeClothFactory = new NikeClothFactory(); ClothFactory proxyClothFactory = (ClothFactory) ProxyFactory.getProxyInstance(nikeClothFactory); proxyClothFactory.produceCloth(); }}
动态代理与AOP(了解)AOP(Aspect Orient Programming)。
前面介绍的Proxy和InvocationHandler,很难看出这种动态代理的优势,下面介绍一种更实用的动态代理机制。
改进前:
改进后:
改进后的说明:代码段1、代码段2、代码段3和深色代码段分离开了,但代码段1、2、3又和一个特定的方法A耦合了!最理想的效果是:代码块1、2、3既可以执行方法A,又无须在程序中以硬编码的方式直接调用深色代码的方法。
代码举例:
public interface Dog{ void info(); void run();}public class HuntingDog implements Dog{ public void info(){System.out.println("我是一只猎狗"); }public void run(){ System.out.println("我奔跑迅速"); }}public class DogUtil{ public void method1(){ System.out.println("=====模拟通用方法一====="); } public void method2(){ System.out.println("=====模拟通用方法二====="); }}public class MyInvocationHandler implements InvocationHandler{ // 需要被代理的对象private Object target;public void setTarget(Object target){ this.target = target; }// 执行动态代理对象的所有方法时,都会被替换成执行如下的invoke方法 public Object invoke(Object proxy, Method method, Object[] args) throws Exception{DogUtil du = new DogUtil();// 执行DogUtil对象中的method1。du.method1();// 以target作为主调来执行method方法 Object result = method.invoke(target , args); // 执行DogUtil对象中的method2。du.method2();return result; }}public class MyProxyFactory{ // 为指定target生成动态代理对象public static Object getProxy(Object target) throws Exception{// 创建一个MyInvokationHandler对象 MyInvokationHandler handler = new MyInvokationHandler();// 为MyInvokationHandler设置target对象 handler.setTarget(target);// 创建、并返回一个动态代理对象return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),handler); }}public class Test{ public static void main(String[] args) throws Exception{// 创建一个原始的HuntingDog对象,作为target Dog target = new HuntingDog();// 以指定的target来创建动态代理Dog dog = (Dog)MyProxyFactory.getProxy(target); dog.info();dog.run(); }}
使用Proxy生成一个动态代理时,往往并不会凭空产生一个动态代理,这样没有太大的意义。通常都是为指定的目标对象生成动态代理这种动态代理在AOP中被称为AOP代理,AOP代理可代替目标对象,AOP代理包含了目标对象的全部方法。但AOP代理中的方法与目标对象的方法存在差异:AOP代理里的方法可以在执行目标方法之前、之后插入一些通用处理、 学习的思维方式
1.大处着眼,小处着手
2.逆向思维、反证法
比如谈论一个事物的好坏,可以先逆向考虑没有它会怎么样
3.透过问题看本质
小不忍则乱大谋
识时务者为俊杰