反射机制
反射机制
什么是Java反射
就是正在运行,动态获取这个类的所有信息。
反射机制的作用
1,反编译:.class-->.java
2.通过反射机制访问java对象的属性,方法,构造方法等;
反射机制的应用场景
Jdbc 加载驱动-----
Spring IOC
框架
反射机制获取类有三种方法
//第一种方式:
Classc1 = Class.forName("Employee");
//第二种方式:
//java中每个类型都有class 属性.
Classc2 = Employee.class;
//第三种方式:
//java语言中任何一个java对象都有getClass 方法
Employeee = new Employee();
Classc3 = e.getClass(); //c3是运行时类 (e的运行时类是Employee)
反射创建对象的方式
Class<?> forName = Class.forName("com.itmayiedu.entity.User");
// 创建此Class 对象所表示的类的一个新实例调用了User的无参数构造方法.
Object newInstance = forName.newInstance();
实例化有参构造函数
Class<?> forName = Class.forName("com.itmayiedu.entity.User");
Constructor<?> constructor = forName.getConstructor(String.class, String.class);
User newInstance = (User) constructor.newInstance("123", "123");
正在运行,动态获取类的信息
Java源代码 .class文件 字节码 获取到class字节码文件 获取类的信息
获取类的属性 方法 实例化对象
package com.toov5.fanshe; public class TestFanseh { private String userName; public TestFanseh() { System.out.println("构造函数"); throw new RuntimeException(); } public static void main(String[] args) { TestFanseh t = new TestFanseh(); t.userName="toov5"; System.out.println(t.userName); } }
构造函数里面抛异常 会这样:
上面是直接使用new的方式创建对象 下面则用Java的反射机制进行
package com.toov5.fanshe; public class TestFanshe { private String userName; public TestFanshe() { System.out.println("构造函数"); // throw new RuntimeException(); } public static void main(String[] args) { try { Class<?>forname =Class.forName("com.toov5.fanshe.TestFanshe"); TestFanshe instance =(TestFanshe)forname.newInstance(); instance.userName="okok"; System.out.println(instance.userName); } catch (Exception e) { } } }
运行结果
反射创建api
方法名称 |
作用 |
getDeclaredMethods [] |
获取该类的所有方法 |
getReturnType() |
获取该类的返回值 |
getParameterTypes() |
获取传入参数 |
getDeclaredFields() |
获取该类的所有字段 |
setAccessible |
允许访问私有成员 |
反射的应用场景
1、jdbc连接 spring IOC的底层是反射+DOM4J 2、框架Mybatis通过对象得到sql语句,反射获取对象信息,底层拼接成sql语句 3、zk的节点信息都是
使用反射机制获取类的信息 比如 forName.getMethods()
package com.toov5.fanshe; import java.lang.reflect.Method; public class TestFanshe { public static void main(String[] args) { try { Class<?>forname =Class.forName("com.toov5.fanshe.TestFanshe"); Method[] methods = forname.getMethods(); for(Method method : methods){ System.out.println(method); } } catch (Exception e) { } } }
运行结果:
方法这么多 是因为Object的类的嘛
package com.toov5.fanshe; import java.lang.reflect.Field; public class TestFanshe { private String userName; public TestFanshe() { System.out.println("构造函数"); } public static void main(String[] args) { try { Class<?>forname =Class.forName("com.toov5.fanshe.TestFanshe"); Field[] fields = forname.getDeclaredFields(); for( Field field : fields){ System.out.println(field.getName()); } } catch (Exception e) { } } }
forname.getDeclaredFields() 获取私有类的 如果是 forname.getFields() 获得不到
无参构造函数私有了,可以执行吗?
如果代码在当前类可以的,换个类呢? 不可以 包括 私有的属性都不可以调用了哈
有参构造函数:
package com.toov5.fanshe; import java.lang.reflect.Constructor; public class TestFanshe { private String userName; public TestFanshe() { System.out.println("构造函数"); } public TestFanshe(String hobby) { System.out.println(hobby); } public static void main(String[] args) { try { Class<?>forname =Class.forName("com.toov5.fanshe.TestFanshe"); Constructor<?> constructor = forname.getConstructor(String.class); //一个参数的那个构造方法 TestFanshe testFanshe =(TestFanshe) constructor.newInstance("come on!"); } catch (Exception e) { } } }
运行结果:
设计模式
设计模式分类
创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
设计模式的六大原则
开闭原则(Open Close Principle)
开闭原则就是说对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。所以一句话概括就是:为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类,后面的具体设计中我们会提到这点。
里氏代换原则(Liskov Substitution Principle)
里氏代换原则(Liskov Substitution Principle LSP)面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。 LSP是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。里氏代换原则是对“开-闭”原则的补充。实现“开-闭”原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。—— From Baidu 百科
依赖倒转原则(Dependence Inversion Principle)
这个是开闭原则的基础,具体内容:真对接口编程,依赖于抽象而不依赖于具体。
接口隔离原则(Interface Segregation Principle)
这个原则的意思是:使用多个隔离的接口,比使用单个接口要好。还是一个降低类之间的耦合度的意思,从这儿我们看出,其实设计模式就是一个软件的设计思想,从大型软件架构出发,为了升级和维护方便。所以上文中多次出现:降低依赖,降低耦合。
迪米特法则(最少知道原则)(Demeter Principle)
为什么叫最少知道原则,就是说:一个实体应当尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立。
合成复用原则(Composite Reuse Principle)
原则是尽量使用合成/聚合的方式,而不是使用继承。
Java2 就有的新特性
反射必须懂一个类 Class类。J
Java类用于描述异类事物的共性,该类事物有什么属性,没有什么属性,之余这个是属性的值是什么,则是由这个类的实例对象来确定的,不同的实例对象由不同观的属性值。Java程序中的各个Java类,他们是否属于同一类事物,是不是可以用一个类来描述事物呢?这个类的名字叫就是Class。
Class类描述了哪些方面的信息呢?类的名字,类的访问属性,类所属于的包名,字段名称的列表、方法名称的列表,等等。学习反射要明白Class这个类
类就是一种东西啊哈哈,各个Java类就是一种东西。
Java程序中各个Java类同属于一类事物,描述这类事物的Java类名就是Class
引申:
众多的人Person 类 :姓名 年龄 体重
众多的类: Class 类 : 类名 属于哪个包 方法 父类 成员变量 每一个Java类都拥有的方方面面的信息
比如方法 getInterfaces() 得到自己实现的多个接口
如果获取Class对象,new Class? 没有这个搞法哦 或许这个构造方法私有 或者压根就没有这个构造方法
Class 代表内存中的一份字节码
当在源程序里面用到Person这个类时候,首先从硬盘上将这些二进制类代码(class字节码),加载到内存中来。然后用这个字节码去创建出对象
每一份字节码 都是一个Class实例化对象 Date Person Math
对应各个类在内存中的字节码,例如,Person类的字节码,ArrayList类的字节码等等
一个类被类加载器加载到内存中,占用一片存储空间,这个空间里面的内容就是类的字节码,不同的类的字节码是不同的,所以他在内存中的内容是不同的,这一个个的空间可分别用一个个的对象来表示,这些对象显然具有相同类型。
方式一:
p.getClass() 得到哪一份字节码把我搞出来的~ 哈哈 调用对象的getClass方法
方式二:
Class.forName("java.lang.String") 得到这个类的字节码
情况一:这个类的字节码已经加载到内存了
情况二:得到这份类的字节码还没在虚拟机中。于是用虚拟机去加载,然后将字节码缓存起来,同时方法返回刚刚加载进来的字节码。
方式四:
类名.class 比如System.class
public class test1 { public static void main (String[] args) { String s = "toov5"; Class c1 = s.getClass(); Class c2 = String.class; Class c3 = null; try { c3 = Class.forName("java.lang.String"); } catch (ClassNotFoundException e) { e.printStackTrace(); } System.out.println(c1 == c2); System.out.println(c1 == c3); } }
不管怎么搞 都是那一份字节码
注意Java中九个预定义Class实例对象, 基本类型+void
Stirng 是类还是基本类型? String不是基本类型!!!
只要是在源程序中出现的类型,都有各自的Class实例对象,例如 int[] void
请看:
public class test1 { public static void main (String[] args) { String s = "toov5"; Class c1 = s.getClass(); Class c2 = String.class; Class c3 = null; try { c3 = Class.forName("java.lang.String"); } catch (ClassNotFoundException e) { e.printStackTrace(); } System.out.println(c1 == c2); System.out.println(c1 == c3); System.out.println(c1.isPrimitive()); //false System.out.println(int.class.isPrimitive()); //true System.out.println(int.class == Integer.class); //false 各有各的字节码 System.out.println(int.class == Integer.TYPE); //包装类型所包装的基本类型的字节码 true System.out.println(int[].class.isPrimitive()); //数组也是类型 false System.out.println(int[].class.isArray()); //数组也是类型 true 数组类型的Class实例对象 } }
反射
我们通过写程序可以得到类里面各个成分所对应的对象。每个成分都是个Class哦
反射就是把Java类中的各个成分映射成相应的Java类。例如,
一个Java类中用一个Class类的对象来表示,一个类中的组成部分:成员变量、方法、构造方法、包信息等等也用一个个的Java类表示。就像汽车是一个类,汽车中的发动机等等也是一个个的类。表示Java类的Class类显然要提供一些列的方法来获得其中的变量,方法,构造方法,修饰符,包等信息。
这些信息就是用相应类的实例对象来表示,他们是Field、Method、Contructor、Package等等
得到各个成分所对应的对象 ,用这个对象搞事情
方法都可以用Method表示,得到metho的类对象肯定是用它
反射中一个很重要的类 Constructor类,一个Class代表一份字节码,一个Method代表字节码的一个方法,一个Constructor代表字节码构造方法
一个类身上有很多个构造方法,如何获取到呢? getConstructor()方法 所有 构造方法 。 返回数组
得到某一个构造方法:
比如String这里类 有很多构造方法 非常多~~
比如想得到 String(StringBuffer buffer) 这个构造方法
注意返回的是数组!没有固定顺序的数组哦
用反射的方式实现同样的效果
public class test1 { public static void main (String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { //两个参数的构造方法 // new Stirng(new StringBuffer("abc")); Constructor constructor = String.class.getConstructor(StringBuffer.class); //获取到构造方法 // 通过构造方法1、 获取到修饰符(私有、公有) 2、 获取到构造方法所属的类 String o =(String) constructor.newInstance(new StringBuffer("abc")); System.out.println(o); } }
总结:
Constructor类代表某个类中的一个构造方法
得到某个类所有的构造方法:
Constructor[] constructors = Class.forName("java.lang.String").getConstructors()
得到某一个构造方法:
Constructor constructor = Class.forName("java.lang.String").getConstructor(StringBuffer.class) 获得方法时候需要用到类型
创建实例对象:
通常方法: String str = new String(new StringBuffer("abc"))
反射方法: String o =(String) constructor.newInstance(new StringBuffer("abc"));
Class.newInstance() 方法 即使没有这个方法也不影响开发。为啥提供这个方法呢?
String o =(String) Class.forName("java.lang.String").newInstance(); //不带参数的构造方法
该方法先得到默认的构造方法,然后用该构造方法创建实例对象
该方法颞部的具体实现: 用到了缓存机制来保存默认构造方法实例对象
前面由于Class得到Constructor再得到对象
省略中间的这一步
内部得到构造方法进行对象创建
关于 Field类
Field类带包某个类中的一个成员变量
问题,得到的Field对象是对应到类上面的成员变量,还是对应到对象上的成员变量。类只有一个,而类的实例对象有多个,如果是与对象关联,哪关联的是哪个对象呢?所以字段fieldX代表的定义,而不是具体的x变量
public class test1 { public static void main (String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException { ReflectPoint reflectPoint = new ReflectPoint(3, 5); //要反射先有字节码 Field FieldY = reflectPoint.getClass().getField("y"); //FieldY是类上的变量,不是对象上的! System.out.println(FieldY.get(reflectPoint)); //从这个对象身上去取值 Field FieldX = reflectPoint.getClass().getDeclaredField("x");//私有变量 只要是声明过的管你什么类型的 FieldX.setAccessible(true); //这个私有变量可以进行访问 暴力反射 System.out.println(FieldX.get(reflectPoint)); //从这个对象身上去取值 } }
注意对于私有变量,进行暴力反射。
成员变量反射综合实例:
你给我一个对象,我把属性全改掉!
在配置文件里面配置好多的东西,自动扫描配置文件,把配置文件里面的东西都换掉。
Bean:
public class ReflectPoint { public String str1 = "ball"; public String str2 = "basketball"; public String str3 = "toov5"; @Override public String toString() { return "ReflectPoint{" + "str1='" + str1 + '\'' + ", str2='" + str2 + '\'' + ", str3='" + str3 + '\'' + '}'; } }
反射进行值的修改:
public class test1 { public static void main (String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException { ReflectPoint reflectPoint = new ReflectPoint(); changeStringValue(reflectPoint); System.out.println(reflectPoint); } private static void changeStringValue(Object obj) throws IllegalAccessException { //扫描这个类上所有String类型的变量 Field[] fields = obj.getClass().getFields(); for (Field field : fields){ if (field.getType() == (String.class)) { //变量是有类型的 都是拿一份去比较 字节码只有一份 String oldValue = (String)field.get(obj); String newValue = oldValue.replace('b', 'a'); //换完了对象身上的值还没有改变 field.set(obj, newValue); } } } }
Method类
首先不是一个对象的方法,是一个类里面的方法
得到类中的某一个方法
通过反射方式得到字节码里面的方法,然后用这个方法去用于某个对象。
public class test1 { public static void main (String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException { //String1.charAt(1) String str1 = "abcde"; Method methodCharAt = String.class.getMethod("charAt", int.class); System.out.println(methodCharAt.invoke(str1,1)); //那个对象的? str1 参数是啥1 } }
总结:invoke 是方法对象的方法~~~ Method
举个栗子:
圆:圆心 半径 . 圆自己知道
但是我们可以不画,交给圆。“”圆,你去画吧“
方法的调用:
System.out.println(methodCharAt.invoke(null,1)); //静态方法不需要对象!!!!! 所以null
如果第一个参数为null,说明Method对象对应的是一个静态方法
用反射执行某个类中的main方法
目标: 写一个程序,能够根据就用户提供的类名,去执行该类中的main方法
public class test1 { public static void main (String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException { ReflectPoint.main(new String[]{"1","2","3"}); } }
测试类调用main方法:
public class ReflectPoint { public static void main(String[] args) { for (String arg : args){ System.out.println(arg); } } }
结果:
下面用反射的方式调用:
思考为啥用反射去调用? 不知道类的名字!
测试:
public class test1 { public static void main (String[] args) { for (String arg : args){ System.out.println(arg); } } }
代码:
public class ReflectPoint { public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException { String startingClassName = args[0]; Method mainMethod = Class.forName(startingClassName).getMethod("main", String[].class); mainMethod.invoke(null,new Object[]{new String[]{"1","2","3","4"}} );//main方法是静态的 } }
运行结果:
关于数组的反射:
public class ReflectPoint { public static void main(String[] args){ int[] a1 = new int[3]; int[] a2 = new int[4]; int[][] a3 = new int[2][3]; String [] a4 = new String[3]; //数组维数相同 并且类型相同 得到的字节码就是同一份!! System.out.println(a1.getClass() == a2.getClass()); //字节码名字 System.out.println(a1.getClass().getName()); //[I //Class的父类 System.out.println(a1.getClass().getSuperclass().getName()); // java.lang.Object System.out.println(a3.getClass().getSuperclass().getName()); //java.lang.Object } }
运行结果:
int 不是Object哦
小结:
具有相同维数和元素类型的数组属于同一个类型,即具有相同的Class对象
代表数组的Class实例对象getSuperClass()方法返回的父类Object类定的Class
基本类型的意味数组可以被当做Object类型使用,不能当做Object[]类型使用,非基本类型的一维数组既可以当做Object类型使用,又可以当做Object[] 类型使用
Arrays.asList() 方法处理int[] 和 String[] 时的差异
Array工具类用用户完成对数组的反射操作
那么如何得到数组中的元素类型呢?
你是数组我就拆开 不是就总体打印~ 数组的反射:
public class ReflectPoint { public static void main(String[] args){ String[] a = {"1","2","3"}; printObject(a); printObject("abcd"); } private static void printObject(Object obj){ Class clazz = obj.getClass(); if (clazz.isArray()) { //如果是數組 int len = Array.getLength(obj); for (int i=0; i<len; i++){ System.out.println(Array.get(obj, i)); } }else { System.out.println(obj); } } }
运行结果:
反射的价值,框架!
配置文件:
配置内容:
className=java.util.ArrayList
代码:
public class test1 { public static void main(String[] args) throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException { FileInputStream imputStream = new FileInputStream("config.properties"); Properties properties = new Properties(); properties.load(imputStream); imputStream.close(); System.out.println(imputStream); String className = properties.getProperty("className"); Collection collection = (Collection)Class.forName(className).newInstance(); collection.add("1"); collection.add("3"); collection.add("5"); System.out.println(collection.size()); } }
结果:
反射与框架
运行哪个类不是从具体的配置文件读取出来的,通过反射区调用运行。
对于配置文件的路径问题,相对路径是不可靠的! 相对于当前工作目录来的。
解决方案一:
就是绝对路径,此时的config需要配置,用户需要配置声明位置,然后程序去读取。
解决方案二:
得到web项目的,在硬盘的具体目录。方法是:getRealPath() 。 然后进行拼接内部地址路径
解决方案三:
硬盘的.class文件加载到jvm中,config配置文件呢? 一样的!也可以加载普通的文件!
这样的用到的很多,框架中的配置文件往往与类放在一起。如何加载类呢? 用类加载器! 也可以加载配置文件。 classPath路径下!
public class test1 { public static void main(String[] args) throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException { // FileInputStream imputStream = new FileInputStream("config.properties"); //从根目录开始一个个往下找 //通过这个类class 找到他的类加载器,然后用它去加载资源。 InputStream imputStream =test1.class.getClassLoader().getResourceAsStream("com/toov5/test/config.properties"); Properties properties = new Properties(); properties.load(imputStream); imputStream.close(); System.out.println(imputStream); String className = properties.getProperty("className"); Collection collection = (Collection)Class.forName(className).newInstance(); collection.add("1"); collection.add("3"); collection.add("5"); System.out.println(collection.size()); } }
运行结果:
也可以这么玩儿:只需要写上 配置文件名字不需要写配置目录名字
public class test1 { public static void main(String[] args) throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException { // FileInputStream imputStream = new FileInputStream("config.properties"); //从根目录开始一个个往下找 //通过这个类class 找到他的类加载器,然后用它去加载资源。 InputStream imputStream =test1.class.getResourceAsStream("config.properties"); Properties properties = new Properties(); properties.load(imputStream); imputStream.close(); System.out.println(imputStream); String className = properties.getProperty("className"); Collection collection = (Collection)Class.forName(className).newInstance(); collection.add("1"); collection.add("3"); collection.add("5"); System.out.println(collection.size()); } }
运行结果一样 这样简化了配置目录
也可以这么玩儿,在创建一个包:
代码:
public class test1 { public static void main(String[] args) throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException { // FileInputStream imputStream = new FileInputStream("config.properties"); //从根目录开始一个个往下找 //通过这个类class 找到他的类加载器,然后用它去加载资源。 InputStream imputStream =test1.class.getResourceAsStream("resources/config.properties"); Properties properties = new Properties(); properties.load(imputStream); imputStream.close(); System.out.println(imputStream); String className = properties.getProperty("className"); Collection collection = (Collection)Class.forName(className).newInstance(); collection.add("1"); collection.add("3"); collection.add("5"); System.out.println(collection.size()); } }
结果:
也可以这么玩儿:
这个是相对于classPath的根!!!
/resources/config.properties
public class test1 { public static void main(String[] args) throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException { // FileInputStream imputStream = new FileInputStream("config.properties"); //从根目录开始一个个往下找 //通过这个类class 找到他的类加载器,然后用它去加载资源。 InputStream imputStream =test1.class.getResourceAsStream("/com/toov5/test/resources/config.properties"); Properties properties = new Properties(); properties.load(imputStream); imputStream.close(); System.out.println(imputStream); String className = properties.getProperty("className"); Collection collection = (Collection)Class.forName(className).newInstance(); collection.add("1"); collection.add("3"); collection.add("5"); System.out.println(collection.size()); } }
运行结果:
总结:
配置文件的加载往往是 类加载器!!!!