1. 反射介绍
Java反射机制是在运行状态中,对于任意一个类,能够获取类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性,可以动态获取信息以及动态调用对象的方法。
Class类
Class是一个类,封装了当前对象所对应的类的信息。一个类中有属性,方法,构造器等。比如有一个Person类,一个Order类,一个Book类,这些都是不同的类,可以用Class描述类,它有类名,属性,方法,构造器等。Class是用来描述类的类。
当我们获取Class类后,就可以通过Class类获取类对象,获取类中的属性和方法。
2. 获取Class对象的三种方式
(1)Class.forName() 静态方法,返回类,参数是完整的包名;
(2)创建一个对象,对象调用getClass(),Object类中继承的方法;
(3)任何一个类型都有class属性;
public class ReflectTest01{ public static void main(String[] args){ Class c1 = null; Class c2 = null; //第一种 try{ c1 = Class.forName("java.lang.String"); //获取String类型类对象 c2 = Class.forName("java.util.Date"); //获取Date类型类对象 Class c3 = Class.forName("java.lang.Integer"); //获取Integer类型类对象 Class c4 = Class.forName("java.lang.System"); System.out.println(c1); System.out.println(c2); }catch(ClassNotFoundException e){ e.printStackTrace(); } //第二种 String s = "abc"; System.out.println(s.getClass() == c1); //true Date d = new Date(); System.out.println(d.getClass() == c2); //true //第三种 Class x = String.class; Class y = Date.class; Class z = int.class; System.out.println(x == c1); //true } }
使用反射创建对象
public class User{ public User(){ System.out.println("无参数构造方法"); } public User(String s){ System.out.println("有参数构造方法"); } } public class ReflectTest02{ public static void main(String[] args){ //1. 传统创建对象的方法 User u = new User(); System.out.println(u); //2. 使用反射创建对象; try{ Class c = Class.forName("com.reflect.test.User"); Object o = c.newInstance(); //调用无参构造方法,如果没有无参构造方法,就会抛出异常; System.out.println(o); }catch(Exception e){ e.printStackTrace(); } } }
使用反射可以更加灵活的实例化对象
// classinfo.properties className=com.reflect.test.User public class ReflectTest03{ public static void main(String[] args){ try{ FileReader fr = new FileReader("C:\\Users\\THINK\\Desktop\\JavaTest\\ReflectTest\\classinfo.properties"); Properties p = new Properties(); p.load(fr); fr.close(); String classname = p.getProperty("className"); Class c = Class.forName(classname); Object o = c.newInstance(); System.out.println(o); }catch(Exception e){ e.printStackTrace(); } } }
使用属性配置文件,只需要修改属性配置文件的value值,就可以实例化。
关于forName()方法的执行时机
forName()方法执行时类加载时执行,执行类的静态代码块
package com.reflect.test; public class MyClass{ static{ System.out.println("类的静态代码块执行了"); } } public class ReflectTest04{ public static void main(String[] args){ try{ Class.forName("com.reflect.test.MyClass"); //静态代码块是在类加载时执行 }catch(Exception e){ e.printStackTrace(); } } } //运行结果 //类的静态代码块执行了
3. 获取文件绝对路径
文件必须从根路径下开始(即使用IDEA创建项目后,从src路径下开始)
currentThread() 获取当前线程对象
getContextClassLoader() 获取当前线程的类加载器对象
getResource() 获取资源,默认从根路径下开始加载资源
public class FilePathTest{ public static void main(String[] args){ String path = Thread.currentThread().getContextClassLoader().getResource("classinfo.properties").getPath(); System.out.println(path); ///C:/Users/THINK/Desktop/JavaTest/ReflectTest/classinfo.properties String path1 = Thread.currentThread().getContextClassLoader().getResource("com/reflect/test/MyClass.class").getPath(); System.out.println(path1); ///C:/Users/THINK/Desktop/JavaTest/ReflectTest/com/reflect/test/MyClass.class } }
以文件绝对路径形式读取文件然后加载流
public class IoPropertiesTest{ public static void main(String[] args) throws Exception{ //第一种:读取后转化为字符流 String path = Thread.currentThread().getContextClassLoader().getResource("com/reflect/test/db.properties").getPath(); FileReader fr = new FileReader(path); System.out.println(path); //第二种:直接读取字节流 InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream("com/reflect/test/db.properties"); Properties p = new Properties(); p.load(is); is.close(); String user = p.getProperty("user"); System.out.println(user); } }
使用资源绑定器读取文件
注意:属性配置文件不能带后缀(.properites);属性文件路径从根路径下开始;
public class ResourceBundleTest{ public static void main(String[] args){ ResourceBundle rb = ResourceBundle.getBundle("com/reflect/test/db"); String user = rb.getString("user"); System.out.println(user); } }
4. JVM类加载机制
类加载是用来把类(class)装载到JVM的。JVM定义的类加载器:
Bootstap Classloader:引导类加载器,用C++编写,是JVM自带的类加载器,负责Java平台核心库,用来装载核心库,该加载器无法直接获取。
Extension Classloader:扩展类加载器,负责jdk home/lib/ext目录下的jar包装入工作库。
System Classloader:系统类加载器,负责java -classpath或-D java.class.path所指的目录下的类与jar包装入工作库。
类加载自底而上检查类是否已加载,如果当前类库有父类库会优先检查父类库是否加载,所以类加载顺序是自顶向下加载类。
public class String{ static{ System.out.println("静态代码块在类加载时执行"); } public static void main(String[] args){ String str = new String(); } } /* 运行结果: 错误: 在类 String 中找不到 main 方法, 请将 main 方法定义为: public static void main(String[] args) 否则 JavaFX 应用程序类必须扩展javafx.application.Application */
依次加载类,在系统类加载器中没有找到String中的main方法。
5. 获取类的属性Field
getName() 获取属性名,方法名
getFields() 获取public修饰的属性
getDeclaredFields() 获取所有属性
getModifiers() 获取属性修饰符,返回对应修饰符id(返回int类型)
Modifiers.toString() 对应id转换为修饰符(返回String类型)
getType() 获取属性类型
package com.reflect.test; import java.lang.reflect.Field; import java.lang.reflect.Modifier; public class ReflectTest05{ public static void main(String[] args) throws Exception{ Class studentclass = Class.forName("com.reflect.test.Student"); String studentclassname = studentclass.getName(); //获取完整类名 System.out.println("完整类名:" + studentclassname); Field[] fields = studentclass.getFields(); //只能获取public修饰的属性 System.out.println(fields.length); //1 Field field= fields[0]; System.out.println(field.getName()); //id Field[] fs = studentclass.getDeclaredFields(); //获取所有属性 System.out.println(fs.length); //5 for(Field fi : fs){ int i = fi.getModifiers(); //获取属性修饰符,返回对应修饰符id String modifiey = Modifier.toString(i); //id转换为对应修饰符 System.out.println(i + " : " + modifiey); Class fieldtype = fi.getType(); //获取属性类型 System.out.println(fieldtype.getName()); System.out.println(fi.getName()); //获取属性名 } } }
反射机制获取类的属性(反编译)
import java.lang.reflect.Field; import java.lang.reflect.Modifier; public class ReflectTest06{ public static void main(String[] args) throws Exception{ StringBuffer sb = new StringBuffer(); Class dateclass = Class.forName("java.util.Date"); sb.append(Modifier.toString(dateclass.getModifiers()) + " class " + dateclass.getSimpleName() + " {\n"); Field[] fields = dateclass.getDeclaredFields(); for(Field field : fields){ sb.append("\t"); sb.append(Modifier.toString(field.getModifiers())); sb.append(" "); sb.append(field.getType().getSimpleName()); sb.append(" "); sb.append(field.getName()); sb.append(";\n"); } sb.append("}"); System.out.println(sb); } }
6. 可变长度参数
参数是可变长度参数时:
(1)可以不传参数,可以传1个参数,也可以传多个参数;也可以直接传入一个数组;
(2)如果参数有多个,可以长度参数必须时最后一个;
public class ArgTest{ public static void main(String[] args){ m1(); m1(3); m1(1,2); m2("a","c","d"); String[] str = new String[]{"a","c","d"}; m2(str); } public static void m1(int... args){ System.out.println("m1方法被执行了"); } public static void m2(String... args){ for(int i=0; i<args.length; i++){ System.out.println(args[i]); } } //public static void m3(String... args, int a){} //错误:varargs 参数必须是最后一个参数 public static void m4(int a, String... args){} }
7. 访问对象属性
public class ReflectTest07{ public static void main(String[] args) throws Exception{ Class studentclass = Class.forName("com.reflect.test.Student"); Object obj = studentclass.newInstance(); Field idfield = studentclass.getDeclaredField("id"); //获取id属性 idfield.set(obj,23); //设置id值为23,这样只能给public属性赋值 System.out.println(idfield.get(obj)); Field agefiled = studentclass.getDeclaredField("age"); agefiled.setAccessible(true); //设置私有属性,要先设置可以访问 agefiled.set(obj,34); System.out.println(agefiled.get(obj)); } }
8. 反射获取类方法
例1:获取普通类中的方法
public class ReflectTest08{ public static void main(String[] args) throws Exception{ Class userclass = Class.forName("com.reflect.test.UserService"); Method[] usermethod = userclass.getDeclaredMethods(); for(Method method : usermethod){ System.out.print(Modifier.toString(method.getModifiers())); //获取方法修饰符 System.out.print(" "); System.out.print(method.getReturnType()); //获取方法返回值类型 System.out.print(" "); System.out.print(method.getName()); //获取方法名 System.out.println(); Class[] parameterTypes = method.getParameterTypes(); for(Class parameterType : parameterTypes){ System.out.println(parameterType.getSimpleName()); } } } }
public class UserService{ public boolean login(String username,String password){ if("admin".equals(username) && ("1243").equals(password)){ return true; } return false; } public void logout(){ System.out.println("系统自动退出"); } }
例2:获取String类中的方法
public class ReflectTest09{ public static void main(String[] args) throws Exception{ Class stringclass = Class.forName("java.lang.String"); StringBuffer sb = new StringBuffer(); sb.append(Modifier.toString(stringclass.getModifiers()) + " class " + stringclass.getSimpleName() + " {\n"); Method[] stringmethod = stringclass.getDeclaredMethods(); for(Method method : stringmethod){ sb.append("\t"); sb.append(Modifier.toString(method.getModifiers())); sb.append(" "); sb.append(method.getReturnType().getSimpleName()); sb.append(" "); sb.append(method.getName()); sb.append(" "); sb.append("("); Class[] stringpara = method.getParameterTypes(); for(Class para : stringpara){ sb.append(para.getSimpleName()); sb.append(","); } if(sb.toString().endsWith(",")){ sb.deleteCharAt(sb.length() - 1); } sb.append("){}\n"); } sb.append("}"); System.out.println(sb); } }
9. 反射获取类中构造方法
public class ReflectTest10{ public static void main(String[] args) throws Exception{ Class stringclass = Class.forName("java.lang.String"); StringBuffer sb = new StringBuffer(); sb.append(Modifier.toString(stringclass.getModifiers()) + " class " + stringclass.getSimpleName() + " {\n"); Constructor[] stringcon = stringclass.getDeclaredConstructors(); for(Constructor con : stringcon){ sb.append("\t"); sb.append(Modifier.toString(con.getModifiers())); sb.append(" "); sb.append(stringclass.getSimpleName()); sb.append("("); Class[] conpara = con.getParameterTypes(); for(Class para : conpara){ sb.append(para.getSimpleName()); sb.append(","); } if(conpara.length > 0){ sb.deleteCharAt(sb.length() - 1); } sb.append("){}\n"); } sb.append("}"); System.out.println(sb); } }
10 反射获取构造方法,创建对象
public class Vip{ public int no; public String name; public String email; public boolean sex; public Vip(){System.out.println("无参数构造方法");} public Vip(int no){this.no = no;} public Vip(int no, String name){ this.no = no; this.name = name; } public Vip(int no, String name, String email){ this.no = no; this.name = name; this.email = email; } public Vip(int no, String name, String email, boolean sex){ this.no = no; this.name = name; this.email = email; this.sex = sex; } public String toString(){ return "vip no" + no + "name" + name; } }
public class ReflectTest11{ public static void main(String[] args) throws Exception{ //1. 传统创建对象方法 Vip v1 = new Vip(); Vip v2 = new Vip(1,"zhang","zhang@11.com",true); //2. 反射创建对象 Class vipclass = Class.forName("com.reflect.test.Vip"); Object o = vipclass.newInstance(); //调用无参构造方法 System.out.println(o); Constructor con = vipclass.getDeclaredConstructor(int.class,String.class,String.class,boolean.class); Object o1 = con.newInstance(2,"zhang2","zhang2@11.com",true); //调用有参数构造方法 System.out.println(o1); Constructor noparacon = vipclass.getDeclaredConstructor(); Object o2 = noparacon.newInstance(); //调用无参数构造方法 System.out.println(o2); } }
11. 获取父类和接口
public class ReflectTest12{ public static void main(String[] args) throws Exception{ Class stringclass = Class.forName("java.lang.String"); //获取父类 Class superclass = stringclass.getSuperclass(); System.out.println(superclass.getName()); //获取接口(一个类可能有多个接口) Class[] stringinterface = stringclass.getInterfaces(); for(Class si : stringinterface){ System.out.println(si.getName()); } } }
12. 反射机制的应用
反射可以在运行时动态获取类中的属性和方法。
(1)准备两个业务类
package service; public class Service1{ public void doService1(){ System.out.println("业务方法1"); } }
package service; public class Service2{ public void doService2(){ System.out.println("业务方法2"); } }
在没有反射时,当我们要从业务1切换到业务2时,我们必须修改代码重新编译运行,才可以达到效果
package service; public class CommonTest{ public static void main(String[] args){ //new Service1().doService1(); new Service2().doService2(); } }
(2)使用反射方法则可以方便很多
准备一个配置文件,里面配置类名和方法名
#example.properties class=service.Service2 method=doService2
测试类
public class ReflectCommonTest{ public static void main(String[] args) throws Exception{ ResourceBundle rb = ResourceBundle.getBundle("service/example"); String servicemethod = rb.getString("method"); String serviceclass = rb.getString("class"); System.out.println(servicemethod); System.out.println(serviceclass); Class c1 = Class.forName(serviceclass); Method m1 = c1.getDeclaredMethod(servicemethod); Constructor con1 = c1.getDeclaredConstructor(); Object o1 = con1.newInstance(); m1.invoke(o1); //调用对象指定方法 } }
当我们从业务2切换业务1时,我们只需要修改配置文件中的class和method,对于测试类不需要修改一行代码,也不需要重新编译,就可以运行,完成业务切换。