1.什么是java反射机制
Reflection(反射)是被视为动态语言
的关键,反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。
加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。
2.反射的优缺点
优点:
- 提高了Java程序的灵活性和扩展性,降低了耦合性,提高
自适应
能力 - 允许程序创建和控制任何类的对象,无需提前
硬编码
目标类
缺点:
- 反射的
性能较低
。- 反射机制主要应用在对灵活性和扩展性要求很高的系统框架上
- 反射会
模糊
程序内部逻辑,可读性较差
。
3.
不使用反射: 占绝大多数的使用场景。也就是说,在编写代码时,已经完全确定要创建的对象所属的类,以及明确调用的方法。
使用反射:在编写代码前,不确定要创建的对象所属的类型,以及不确定要调用的方法
Java反射机制提供的功能:
- 在运行时判断任意一个对象所属的类
- 在运行时构造任意一个类的对象
- 在运行时判断任意一个类所具有的成员变量和方法
- 在运行时获取泛型信息
- 在运行时调用任意一个对象的成员变量和方法
- 在运行时处理注解
- 生成动态代理
反射机制的应用场景:
1、JDBC加载驱动连接class.forname
2、Spring容器框架IOC实例化对象
3、自定义注解生效(反射+Aop)
4、第三方核心的框架:框架= 注解 + 反射 + 设计模式
4.应用测试代码:
自定义注解:
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE}) @Retention(RetentionPolicy.RUNTIME) public @interface MyAnnotation { String value(); }
自定义接口:
public interface MyInterface { void method(); }
父类:
public class Creature<T> { boolean gender; public int id; public void breath(){ System.out.println("呼吸"); } private void info(){ System.out.println("我是一个生物"); } }
子类:
@MyAnnotation("t_persons") public class Person extends Creature<String> implements Comparable<Person>,MyInterface{ private String name; public int age = 1; @MyAnnotation("info") private static String info; public Person(){ System.out.println("Person()..."); } protected Person(int age){ this.age = age; } private Person(String name, int age){ this.name = name; this.age = age; } public void show() throws RuntimeException,ClassNotFoundException{ System.out.println("你好,我是一个Person"); } @MyAnnotation(value="show_nation") private String showNation(String nation,int age){ System.out.println("showNation..."); return "我的国籍是:" + nation + ",生活了" + age + "年"; } @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + '}'; } @Override public int compareTo(Person o) { return 0; } @Override public void method() { } public static void showInfo(){ System.out.println("我是一个人"); } }
5.反射的应用1:创建运行时类的对象
创建运行时类的对象有两种方式:
方式1:直接调用Class对象的newInstance()方法
方式一的步骤:
1)获取该类型的Class对象
2)调用Class对象的newInstance()方法创建对象
要想创建对象成功,需要满足:
> 运行时类中必须声明空参的构造器。否则,会报InstantiationException异常
> 空参的构造器的权限得够。否则,报IllegalAccessException异常
代码示例:
@Test public void test() throws InstantiationException, IllegalAccessException { // clazz代表Person类型 Class<Person> clazz = Person.class; // clazz.newInstance()创建的就是Person的对象 Person per = clazz.newInstance(); System.out.println(per); }
或如下方式:
@Test public void test() throws ClassNotFoundException, InstantiationException, IllegalAccessException { Class<?> clazz = Class.forName("com.fblinux.reflect.Person"); Object obj = clazz.newInstance(); System.out.println(obj); }
方式2:通过获取构造器对象来进行实例化
方式二的步骤:
1)通过Class类的getDeclaredConstructor(Class … parameterTypes)取得本类的指定形参类型的构造器
2)向构造器的形参中传递一个对象数组进去,里面包含了构造器中所需的各个参数。
3)通过Constructor实例化对象。
如果构造器的权限修饰符修饰的范围不可见,也可以调用setAccessible(true)
代码示例:
@Test public void test1() throws InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchMethodException { Class clazz = Person.class; //调用运行时类的空参构造器,创建对象 Constructor constructor = clazz.getDeclaredConstructor(); constructor.setAccessible(true); Person p = (Person) constructor.newInstance(); System.out.println(p); }
6.反射的应用2:获取运行时类的完整结构
可以获取:包、修饰符、类型名、父类(包括泛型父类)、父接口(包括泛型父接口)、成员(属性、构造器、方法)、注解(类上的、方法上的、属性上的)。
6.1.获取所有的属性及相关信息
getDeclaredFields():获取当前运行时类中声明的所有属性
@Test public void test2(){ Class clazz = Person.class; Field[] declaredFields = clazz.getDeclaredFields(); for(Field f : declaredFields){ System.out.println(f); } }
获取属性中的权限修饰符、变量类型、变量名
@Test public void test2(){ Class clazz = Person.class; Field[] declaredFields = clazz.getDeclaredFields(); for(Field f : declaredFields){ //1.权限修饰符 int modifier = f.getModifiers(); System.out.print(Modifier.toString(modifier) + "\t"); //2.数据类型 Class type = f.getType(); System.out.print(type.getName() + "\t"); //3.变量名 String fName = f.getName(); System.out.print(fName); System.out.println(); } }
6.2.获取所有方法及相关信息
@Test public void test2(){ Class clazz = Person.class; Method[] declaredMethods = clazz.getDeclaredMethods(); for (Method m : declaredMethods) { System.out.println(m); } }
获取方法中声明的注解信息、权限修饰符、返回值类型、方法名、形参列表和抛出的异常
@Test public void test2(){ Class clazz = Person.class; Method[] declaredMethods = clazz.getDeclaredMethods(); for (Method m : declaredMethods) { // 1.获取方法声明的注解 Annotation[] annos = m.getAnnotations(); for (Annotation a : annos) { System.out.println(a); } // 2.权限修饰符 System.out.print(Modifier.toString(m.getModifiers()) + "\t"); // 3.返回值类型 System.out.print(m.getReturnType().getName() + "\t"); // 4.方法名 System.out.print(m.getName()); System.out.print("("); // 5.形参列表 Class[] parameterTypes = m.getParameterTypes(); if (!(parameterTypes == null && parameterTypes.length == 0)) { for (int i = 0; i < parameterTypes.length; i++) { if (i == parameterTypes.length - 1) { System.out.print(parameterTypes[i].getName() + " args_" + i); break; } System.out.print(parameterTypes[i].getName() + " args_" + i + ","); } } System.out.print(")"); // 6.抛出的异常 Class[] exceptionTypes = m.getExceptionTypes(); if (exceptionTypes.length > 0) { System.out.print("throws "); for (int i = 0; i < exceptionTypes.length; i++) { if (i == exceptionTypes.length - 1) { System.out.print(exceptionTypes[i].getName()); break; } System.out.print(exceptionTypes[i].getName() + ","); } } System.out.println(); } }
6.3.
public class OtherTest { /* 获取当前类中的所有的构造器 */ @Test public void test1(){ Class clazz = Person.class; Constructor[] cons = clazz.getDeclaredConstructors(); for(Constructor c :cons){ System.out.println(c); } } /* 获取运行时类的父类 */ @Test public void test2(){ Class clazz = Person.class; Class superclass = clazz.getSuperclass(); System.out.println(superclass); } /* 获取运行时类的所在的包 */ @Test public void test3(){ Class clazz = Person.class; Package pack = clazz.getPackage(); System.out.println(pack); } /* 获取运行时类的注解 */ @Test public void test4(){ Class clazz = Person.class; Annotation[] annos = clazz.getAnnotations(); for (Annotation anno : annos) { System.out.println(anno); } } /* 获取运行时类所实现的接口 */ @Test public void test5(){ Class clazz = Person.class; Class[] interfaces = clazz.getInterfaces(); for (Class anInterface : interfaces) { System.out.println(anInterface); } } /* 获取运行时类的带泛型的父类 */ @Test public void test6(){ Class clazz = Person.class; Type genericSuperclass = clazz.getGenericSuperclass(); System.out.println(genericSuperclass); } }
7.反射的应用3:调用运行时类的指定结构
7.1.调用指定的属性
在反射机制中,可以直接通过Field类操作类中的属性,通过Field类提供的set()和get()方法就可以完成设置和取得属性内容的操作。
(1)获取该类型的Class对象
Class clazz = Class.forName(“包.类名”);
(2)获取属性对象
Field field = clazz.getDeclaredField(“属性名”);
(3)如果属性的权限修饰符不是public,那么需要设置属性可访问
field.setAccessible(true);
(4)创建实例对象:如果操作的是非静态属性,需要创建实例对象
Object obj = clazz.newInstance(); //有公共的无参构造
Object obj = 构造器对象.newInstance(实参…);//通过特定构造器对象创建实例对象
(5)设置指定对象obj上此Field的属性内容
field.set(obj,”属性值”);
如果操作静态变量,那么实例对象可以省略,用null表示
(6)取得指定对象obj上此Field的属性内容
Object value = field.get(obj);
如果操作静态变量,那么实例对象可以省略,用null表示
示例代码:
public class Student { private int id; private String name; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "Student{" + "id=" + id + ", name='" + name + '\'' + '}'; } }
public class TestField { public static void main(String[] args)throws Exception { //1、获取Student的Class对象 Class clazz = Class.forName("com.fblinux.reflect.Student"); //2、获取属性对象,例如:id属性 Field idField = clazz.getDeclaredField("id"); //3、如果id是私有的等在当前类中不可访问access的,我们需要做如下操作 idField.setAccessible(true); //4、创建实例对象,即,创建Student对象 Object stu = clazz.newInstance(); //5、获取属性值 /* * 以前:int 变量= 学生对象.getId() * 现在:Object id属性对象.get(学生对象) */ Object value = idField.get(stu); System.out.println("id = "+ value); //6、设置属性值 /* * 以前:学生对象.setId(值) * 现在:id属性对象.set(学生对象,值) */ idField.set(stu, 2); value = idField.get(stu); System.out.println("id = "+ value); } }
关于setAccessible方法的使用:
- Method和Field、Constructor对象都有setAccessible()方法。
- setAccessible启动和禁用访问安全检查的开关。
- 参数值为true则指示反射的对象在使用时应该取消Java语言访问检查。
- 提高反射的效率。如果代码中必须用反射,而该句代码需要频繁的被调用,那么请设置为true。
- 使得原本无法访问的私有成员也可以访问
- 参数值为false则指示反射的对象应该实施Java语言访问检查。
7.2.调用指定的方法
(1)获取该类型的Class对象
Class clazz = Class.forName(“包.类名”);
(2)获取方法对象
Method method = clazz.getDeclaredMethod(“方法名”,方法的形参类型列表);
(3)创建实例对象
Object obj = clazz.newInstance();
(4)调用方法
Object result = method.invoke(obj, 方法的实参值列表);
如果方法的权限修饰符修饰的范围不可见,也可以调用setAccessible(true)
如果方法是静态方法,实例对象也可以省略,用null代替
public class TestMethod { @Test public void test()throws Exception { // 1、获取Student的Class对象 Class<?> clazz = Class.forName("com.fblinux.reflect.Student"); //2、获取方法对象 /* * 在一个类中,唯一定位到一个方法,需要:(1)方法名(2)形参列表,因为方法可能重载 * * 例如:void setName(String name) */ Method setNameMethod = clazz.getDeclaredMethod("setName", String.class); //3、创建实例对象 Object stu = clazz.newInstance(); //4、调用方法 /* * 以前:学生对象.setName(值) * 现在:方法对象.invoke(学生对象,值) */ Object setNameMethodReturnValue = setNameMethod.invoke(stu, "张三"); System.out.println("stu = " + stu); //setName方法返回值类型void,没有返回值,所以setNameMethodReturnValue为null System.out.println("setNameMethodReturnValue = " + setNameMethodReturnValue); Method getNameMethod = clazz.getDeclaredMethod("getName"); Object getNameMethodReturnValue = getNameMethod.invoke(stu); //getName方法返回值类型String,有返回值,getNameMethod.invoke的返回值就是getName方法的返回值 System.out.println("getNameMethodReturnValue = " + getNameMethodReturnValue);//张三 } @Test public void test02()throws Exception{ Class<?> clazz = Class.forName("com.fblinux.reflect.Student"); Method printInfoMethod = clazz.getMethod("printInfo", String.class); //printInfo方法是静态方法 printInfoMethod.invoke(null,"飞冰"); } }
8.反射的应用4:读取注解信息
一个完整的注解应该包含三个部分:
(1)声明
(2)使用
(3)读取
8.1.声明自定义注解
@Inherited @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface Table { String value(); }
@Inherited @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface Column { String columnName(); String columnType(); }
- 自定义注解可以通过四个元注解@Retention,@Target,@Inherited,@Documented,分别说明它的声明周期,使用位置,是否被继承,是否被生成到API文档中。
- Annotation 的成员在 Annotation 定义中以无参数有返回值的抽象方法的形式来声明,我们又称为配置参数。返回值类型只能是八种基本数据类型、String类型、Class类型、enum类型、Annotation类型、以上所有类型的数组
- 可以使用 default 关键字为抽象方法指定默认返回值
- 如果定义的注解含有抽象方法,那么使用时必须指定返回值,除非它有默认值。格式是“方法名 = 返回值”,如果只有一个抽象方法需要赋值,且方法名为value,可以省略“value=”,所以如果注解只有一个抽象方法成员,建议使用方法名value。
8.2.
@Table("t_stu") public class Student { @Column(columnName = "sid",columnType = "int") private int id; @Column(columnName = "sname",columnType = "varchar(20)") private String name; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "Student{" + "id=" + id + ", name='" + name + '\'' + '}'; } }
8.3.读取和处理自定义注解
自定义注解必须配上注解的信息处理流程才有意义。
我们自己定义的注解,只能使用反射的代码读取。所以自定义注解的声明周期必须是RetentionPolicy.RUNTIME。
public class TestAnnotation { public static void main(String[] args) { Class studentClass = Student.class; Table tableAnnotation = (Table) studentClass.getAnnotation(Table.class); String tableName = ""; if(tableAnnotation != null){ tableName = tableAnnotation.value(); } Field[] declaredFields = studentClass.getDeclaredFields(); String[] columns = new String[declaredFields.length]; int index = 0; for (Field declaredField : declaredFields) { Column column = declaredField.getAnnotation(Column.class); if(column!= null) { columns[index++] = column.columnName(); } } String sql = "select "; for (int i=0; i<index; i++) { sql += columns[i]; if(i<index-1){ sql += ","; } } sql += " from " + tableName; System.out.println("sql = " + sql); } }