反射概括
反射概念
在Java中,反射是一种机制,通过反射,可以在运行时获取类的信息(例如字段、方法、构造函数等),并且可以动态地操作类、对象和成员。
- 动态地创建和访问对象:使用反射可以实例化一个类的对象,即使在编译时并不知道具体的类名。
- 动态地调用方法:反射可以在运行时调用一个对象的方法,甚至通过反射来调用私有方法。
- 获取和设置字段的值:通过反射可以获取和设置对象中的字段的值,即使这些字段是私有的。
- 获取和操作类、接口、构造函数等:反射还可以获取和操作类的信息,如获取类的注解、实现的接口、父类等。
反射优缺点
反射的优点:
- 动态性:反射使得程序能够在运行时动态地获取和操作类的信息,而不需要在编译期间确定,提高了灵活性和可扩展性
- 框架工具开发:反射机制为一些框架和工具提供了基础。例如,测试框架可以使用反射来自动化测试对象的属性和方法,而依赖注入框架可以通过反射来自动注入依赖。
反射的缺点:
- 性能开销:反射机制通常比直接调用代码的方式更慢。由于反射需要在运行时进行类型检查、方法查找等操作,因此会引入一定的性能开销。
- 安全性问题:反射机制可以绕过访问修饰符(如私有、受保护等),并允许访问和修改本来不应该被暴露的成员。这可能导致程序的安全性问题。
- 代码可读性和维护性:反射的代码通常较为复杂,可读性较差。由于反射操作的灵活性,一些错误可能直到运行时才能被发现,不利于排查问题和维护代码。
反射入门案例
需求:已知Cat类和类中的方法catName(),实现调用Cat类中的catName()方法和name属性
准备工作
(1)声明一个类Cat
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| public class Cat { private Integer id; public String name = "小喵";
private void getCatId() { System.out.println("私有方法被调用"); System.out.println("小猫的ID:" + id); } public void getCatName() { System.out.println("非私有方法被调用"); System.out.println("小猫的名字:" + name); }
public Cat() { }
public Cat(int id, String name) { this.id = id; this.name = name; } }
|
(2)创建类的配置信息文件Cat.properties
1 2 3 4 5 6 7 8 9 10
| ClassPath=com.wen.common.Cat
privateMethod=getCatId
publicMethod=getCatName
privateId=id
publicName=name
|
调用类中的属性
(1)使用普通方法,对象.成员变量;
1 2 3 4 5
| @Test public void test1() { Cat cat = new Cat(); System.out.println(cat.name); }
|
(2)利用反射,成员变量.get(对象);
1 2 3 4 5 6 7 8 9 10 11 12 13
| @Test public void test2() throws Exception { Class clazz = Class.forName("com.wen.common.Cat"); Object obj = clazz.newInstance(); Field name = clazz.getField("name"); System.out.println(name.get(obj)); }
|
(3)反射方式优化:将配置信息放入配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| @Test public void test3() throws Exception { Properties properties = new Properties(); FileInputStream fileInputStream = new FileInputStream("target/classes/Cat.properties"); properties.load(fileInputStream); String ClassPath = properties.get("ClassPath").toString(); String getCatName = properties.get("publicMethod").toString(); String name = properties.get("publicName").toString(); System.out.println(ClassPath); System.out.println(getCatName); System.out.println(name);
Class clazz = Class.forName("com.wen.common.Cat"); Object obj = clazz.newInstance(); Field field = clazz.getField(name); System.out.println(field.get(obj)); }
|
调用类中的方法
(1)使用普通方法,对象.方法();
1 2 3 4 5
| @Test public void test1() { Cat cat = new Cat(); cat.getCatName(); }
|
(2)利用反射,方法对象.invoke(对象);
1 2 3 4 5 6 7 8 9 10 11 12 13
| @Test public void test2() throws Exception { Class clazz = Class.forName("com.wen.common.Cat"); Object o = clazz.newInstance(); Method method = clazz.getMethod("getCatName"); method.invoke(o); }
|
(3)将配置信息放入配置文件,动态读取配置文件,好处是修改配置即可调用不同的方法,避免了直接修改源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| @Test public void test3() throws Exception { Properties properties = new Properties(); FileInputStream fileInputStream = new FileInputStream("target/classes/Cat.properties"); properties.load(fileInputStream); String ClassPath = properties.get("ClassPath").toString(); String getCatName = properties.get("publicMethod").toString(); String name = properties.get("publicName").toString(); System.out.println(ClassPath); System.out.println(getCatName); System.out.println(name);
Class clazz = Class.forName(ClassPath); Object obj = clazz.newInstance(); Method method_new = clazz.getMethod(getCatName); method_new.invoke(obj); }
|
反射前后差别
(1)使用反射前,在类的外部,不可以通过类的对象调用其内部的私有private结构
1 2 3 4 5 6 7 8 9 10
| @Test public void test1() { Cat cat = new Cat(1001, "小咪"); System.out.println(cat.name); cat.getCatName(); }
|
(2)使用反射之后,可以使用 setAccessible(boolean flag)方法临时改变访问权限,就可以获取可以调用私有的结构,比如:私有的构造器、方法、属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| @Test public void test2() throws Exception { Class<Cat> clazz = Cat.class; Constructor<Cat> constructor = clazz.getConstructor(int.class, String.class); constructor.setAccessible(true); Object obj = constructor.newInstance(1001, "小明"); Cat cat = (Cat) obj; System.out.println(cat.toString());
Field id = clazz.getDeclaredField("id"); Field name = clazz.getDeclaredField("name"); name.setAccessible(true); System.out.println("id:" + id + "name:" + name); Method getCatId = clazz.getDeclaredMethod("getCatId"); Method getCatName = clazz.getDeclaredMethod("getCatName"); getCatId.setAccessible(true); getCatId.invoke(cat); getCatName.invoke(cat); }
|
反射相关类
注:必须先获得Class才能获取Method、Constructor、Field
| API | 简介 |
|---|
| java.lang.Class | 代表一个类型,代表整个类。代表整个字节码。 |
| java.lang.reflect.Method | 代表类中的方法。代表字节码中的方法字节码。 |
| java.lang.reflect.Field | 代表类的成员变量(静态变量+实例变量)代表字节码中的属性字节码 |
| java.lang.reflect.Constructor | 代表类的构造方法(构造器)。代表字节码中的构造方法字节码 |
Class类
| 方法 | 简介 |
|---|
| newInstance() | 创建对象 |
| getName() | 返回完整类名带包名 |
| getSimpleName() | 返回类名 |
| getFields() | 返回类中public修饰的属性,返回Field数组 |
| getDeclaredFields() | 返回类中所有的属性,返回Field数组 |
| getField(String name) | 根据属性名获取指定的public修饰的属性 |
| getDeclaredField(String name) | 根据属性名获取指定的属性 |
| getModifiers() | 获取属性的修饰符列表,返回的修饰符是一个数字,每个数字是修饰符的代号 一般配合Modifier类的toString(int x)方法使用 |
| getDeclaredMethods() | 返回类中(类自身)所有的实例方法,包含public、protected和private方法。 |
| getDeclaredMethod(String name, Class<?>… parameterTypes) | java.lang.Class.getDeclaredMethod() 返回值:根据指定的方法名称和参数,匹配类中的方法,返回Method对象 name参数表示方法的名称(字符串),parameterTypes参数表示参数数组 |
| getDeclaredConstructors() | 返回类中所有的构造方法 |
| getDeclaredConstructor(Class<?>… parameterTypes) | 根据方法形参获取指定的构造方法 |
| getSuperclass() | 根据方法形参获取指定的构造方法 |
| getInterfaces() | 返回调用类实现的接口集合 |
Method类
| 方法 | 简介 |
|---|
| getName() | 返回方法名 |
| getModifiers() | 获取方法的修饰符列表,返回的修饰符是一个数字,每个数字是修饰符的代号 一般配合Modifier类的toString(int x)方法使用 |
| getReturnType() | 以Class类型,返回方法类型 一般配合Class类的getSimpleName()方法使用 |
| getParameterTypes() | 返回方法的修饰符列表(一个方法的参数可能会有多个。) 结果集一般配合Class类的getSimpleName()方法使用 |
| invoke(Object obj, Object… args) | 调用方法 |
Field类
| 方法 | 简介 |
|---|
| getName() | 返回属性名 |
| getModifiers() | 获取属性的修饰符列表,返回的修饰符是一个数字,每个数字是修饰符的代号 一般配合Modifier类的toString(int x)方法使用 |
| getType() | 以Class类型,返回属性类型 一般配合Class类的getSimpleName()方法使用 |
| set(Object obj, Object value) | 设置属性值 |
| get(Object obj) | 读取属性值 |
Constructor类
| 方法 | 简介 |
|---|
| public String getName() | 返回构造方法名 |
| public int getModifiers() | 获取构造方法的修饰符列表,返回的修饰符是一个数字,每个数字是修饰符的代号 一般配合Modifier类的toString(int x)方法使用 |
| public Class<?>[] getParameterTypes() | 返回构造方法的修饰符列表(一个方法的参数可能会有多个) 结果集一般配合Class类的getSimpleName()方法使用 |
| public T newInstance(Object … initargs) | 创建对象,参数为创建对象的数据 |
类的加载
类的加载过程
1 2 3 4
| 程序经过java.exe命令以后,会生成一个或多个字节码文件(以.class结尾) 接着我们使用java.exe命令对某个字节码文件进行解释运行,相当于将某个字节码文件加载到内存中(此过程就称为类的加载) 加载到内存中的类,我们就称为运行时类,此运行时类,就作为Class的一个示例,换句话说,Class的实例就对应着一个运行时类 加载到内存中的运行时类,会缓存一定的时间,在此时间之内,我们可以通过不同的方式来获取此运行时类
|
类的加载步骤
当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过如下三个步骤来对该类进行初始化
| 加载过程 | 简介 |
|---|
| 加载 | 将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口(即引用地址)。所有需要访问和使用类数据只能通过这个Class对象。这个加载的过程需要类加载器参与。 |
| 链接 | 将Java类的二进制代码合并到JVM的运行状态之中的过程。 验证:确保加载的类信息符合JVM规范,例如:以cafe开头,没有安全方面的问题 准备:正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存都将在方法区中进行分配 解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程。 |
| 初始化 | 执行类构造器<clinit>()方法的过程,类构造器()方法是由编译期自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的。(类构造器是构造类信息的,不是构造该类对象的构造器),当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类 的初始化,虚拟机会保证一个类的()方法在多线程环境中被正确加锁和同步 |
ClassLoader类加载器
类加载器作用是用来把类(class)装载进内存的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public class Demo { @Test public void test() { ClassLoader classloader = ClassLoaderTest.class.getClassLoader(); System.out.println(classloader);
ClassLoader parent = classloader.getParent(); System.out.println(parent);
ClassLoader classLoader3 = parent.getParent(); System.out.println(classLoader3); } }
class ClassLoaderTest { }
|
反射的动态性
动态语言和静态语言
| 概念 | 简介 | 举例 |
|---|
| 动态语言 | 指在编译时变量的数据类型即可确定的语言,在运行期是可变的,这意味着对象的多态性是与生俱来的,多数静态类型语言要求在使用变量之前必须声明数据类型,优点是编写的代码数量少,看起来更简洁,可以把精力更多地放在业务逻辑上 | JavaScript、PHP、Python |
| 静态语言 | 在运行时确定数据类型的语言,变量使用之前不需要类型声明,编译时会进行类型匹配检查,所以不能给变量赋予不同类型的值(除非用向上转型的技术来取得多态的效果) | Java、C、 C++ |
体会反射的动态性
Java 不是动态语言,但 Java 可以称之为 “准动态语言” 。即 Java 有一定的动态性,可以利用反射机制、字节码操作获得类似动态语言的特性,Java 的动态性让编程的时候更加灵活
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| public class dynamic {
@Test public void test() { int num = new Random().nextInt(3); System.out.println(num); String classPath = ""; switch (num) { case 0: classPath = "java.util.Random"; break; case 1: classPath = "java.util.Date"; break; case 2: classPath = "java.lang.Object"; break; } try { Object obj = getInstance(classPath); System.out.println(obj); } catch (Exception e) { e.printStackTrace(); } }
public Object getInstance(String classPath) throws Exception { Class<?> clazz = Class.forName(classPath); return clazz.newInstance(); } }
|
反射的使用
准备工作
(1)自定义接口MyInterface
1 2 3 4
| public interface MyInterface { void info(); }
|
(2)自定义注解MyAnnotation
1 2 3 4 5 6
| @Retention(RetentionPolicy.RUNTIME) @Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE}) public @interface MyAnnotation { String value() default "value属性默认值"; }
|
(3)父类Creature
1 2 3 4 5 6 7 8 9 10 11 12
| public class Creature<T>implements Serializable { private char gender; public double weight;
private void breath(){ System.out.println("生物呼吸"); } public void eat(){ System.out.println("生物吃东西"); } }
|
(4)子类Person
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
|
@Data @MyAnnotation(value = "hello!") public class Person extends Creature<String> implements Comparable<String>, MyInterface { private int id; public String name; int age;
@MyAnnotation() public void show1() { System.out.println("非静态方法被使用"); }
public static void show2() { System.out.println("静态方法被使用"); }
@MyAnnotation private String show3(String n) { System.out.println("私有方法被使用"); return n; }
@Override public void info() { System.out.println("重写info()方法被使用"); }
@Override public int compareTo(String o) { return 0; }
public Person() { System.out.println("空参构造器被使用"); }
public Person(int id, String name, int age) { System.out.println("全参构造器被使用"); this.id = id; this.name = name; this.age = age; } }
|
获取Class类实例对象的四种方式
获取Class类的实例的四种方法(前三种需要掌握,第四种了解,第三种用的多)
| 方法 | 前提 |
|---|
| Class clazz = 类名.class; | 若已知具体的类,通过类的class属性获取,该方法最为安全可靠,程序性能最高 |
| Class clazz = 类实例对象.getClass(); | 已知某个类的实例对象,调用该实例的getClass()方法获取Class对象 |
| Class clazz = Class.forName(“类的全类名”); | 已知一个类的全类名,且该类在类路径下,可通过Class类的静态方法forName(String ClassPath)获取,可能抛出ClassNotFoundException |
ClassLoader cl = this.getClass().getClassLoader(); Class clazz = cl.loadClass(“类的全类名”); | 使用类的加载器:ClassLoader |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
|
public class Demo {
@Test public void test1() { Class<Person> clazz = Person.class; System.out.println(clazz); }
@Test public void test2() { Person person = new Person(); Class<? extends Person> clazz = person.getClass(); System.out.println(clazz); }
@Test public void test3() throws ClassNotFoundException { Class<?> clszz = Class.forName("com.wen.common.Person"); System.out.println(clszz); }
@Test public void test4() throws ClassNotFoundException { ClassLoader classLoader = Demo.class.getClassLoader(); Class<?> clazz = classLoader.loadClass("com.wen.common.Person"); System.out.println(clazz); } }
|
Class类的实例对象的用途
(1)获取运行时类的完整结构
1 2 3 4 5
| 1、获取当前运行时类的属性结构(权限修饰符、数据类型、变量名) 2、获取运行时类的方法结构(注解、权限修饰符、返回值类型、方法名、形参列表、抛出的异常) 3、获取运行时类的构造器结构 4、获取运行时类的父类及父类泛型类型 5、获取运行时类的接口、包、注解
|
(2)调用运行时类的指定结构
1 2 3
| 1、调操作运行时类中指定的属性 2、操作运行时类中指定的方法 3、操作运行时类的指定构造器
|
哪些类型可以有Class的实例对象
| 类型 | 简介 |
|---|
| class | 外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类 |
| interface | 接口 |
| [] | 数组 |
| enum | 枚举 |
| annotation | 注解 |
| primitive type | 基本数据类型 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| public class Demo {
@Test public void test() { 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 aClass = a.getClass(); Class bClass = b.getClass(); System.out.println(aClass.hashCode()); System.out.println(bClass.hashCode()); System.out.println(aClass == bClass); } }
|
创建运行时类的对象
有了Class类的实例对象后,可以调用Class对象的newInstance()方法创建类的对象,要想newInstance()方法正常的创建运行时的对象,需要类必须有一个无参数的构造器、类的构造器的访问权限需要足够
1 2 3 4 5 6 7 8 9 10
| public class Demo { @Test public void test() throws IllegalAccessException, InstantiationException { Class<Person> clazz = Person.class; Person person = clazz.newInstance(); System.out.println(person); } }
|
获取运行时类的属性结构
有了Class类的实例对象后,还可以获取当前运行时类的属性结构(权限修饰符、数据类型、变量名)
| 方法 | 简介 |
|---|
| getField(String name) | 根据属性名获取指定的public修饰的属性 |
| getDeclaredField(String name) | 根据属性名获取指定的属性 |
| getFields() | 返回类中public修饰的属性,返回Field数组 |
| getDeclaredFields() | 返回类中所有的属性,返回Field数组 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| public class Demo {
@Test public void test() { Class<Person> clazz = Person.class;
Field[] fields = clazz.getFields(); for (Field f : fields) { System.out.println("所有属性:" + f); int modifiers = f.getModifiers(); System.out.println("权限修饰符:" + Modifier.toString(modifiers)); Class<?> type = f.getType(); System.out.println("数据类型:" + type); String name = f.getName(); System.out.println("变量名:" + name); }
Field[] declaredFields = clazz.getDeclaredFields(); for (Field f : declaredFields) { System.out.println(f); } } }
|
获取运行时类的方法结构
有了Class类的实例对象后,还可以获取运行时类的方法结构(注解、权限修饰符、返回值类型、方法名、形参列表、抛出的异常)
| 方法 | 简介 |
|---|
| getMethods() | 获取当前运行时类及其所有父类中声明为public权限的方法 |
| getDeclaredMethods() | 获取当前运行时类中声明的所有方法(不包含父类中的方法) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| public class Demo {
@Test public void test() { Class<Person> clazz = Person.class;
Method[] methods = clazz.getMethods(); for (Method m : methods) { System.out.println("方法:" + m); Annotation[] annotations = m.getAnnotations(); for (Annotation a : annotations) { System.out.println("注解:" + a); } System.out.println("权限修饰符:" + Modifier.toString(m.getModifiers())); System.out.println("返回值类型:" + m.getReturnType().getName()); System.out.println("方法名:" + m.getName()); 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.println(parameterTypes[i].getName() + i); break; } System.out.println(parameterTypes[i].getName() + i); } } Class<?>[] exceptionTypes = m.getExceptionTypes(); if (exceptionTypes.length > 0) { for (int i = 0; i < exceptionTypes.length; i++) { if (i == exceptionTypes.length - 1) { System.out.println(exceptionTypes[i].getName()); break; } System.out.println(exceptionTypes[i].getName()); } } }
Method[] declaredMethods = clazz.getDeclaredMethods(); for (Method m : declaredMethods) { System.out.println(m); } } }
|
获取运行时类的构造器结构
有了Class类的实例对象后,还可以获取运行时类的构造器结构
| 方法 | 简介 |
|---|
| getConstructors() | 获取当前运行时类中声明为public的构造器 |
| getDeclaredConstructors() | 获取当前运行时类中声明的所有的构造器 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| public class Demo {
@Test public void test() { Class<Person> clazz = Person.class;
Constructor<?>[] constructors = clazz.getConstructors(); for (Constructor c : constructors) { System.out.println(c); }
Constructor<?>[] declaredConstructors = clazz.getDeclaredConstructors(); for (Constructor c : declaredConstructors) { System.out.println(c); } } }
|
获取运行时类的父类
有了Class类的实例对象后,还可以获取运行时类的及父类泛型类型
| 方法 | 简介 |
|---|
| getSuperclass() | 获取运行时类的父类 |
| getGenericSuperclass() | 获取运行时类的带泛型的父类 |
| getActualTypeArguments() | 获取泛型类型 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| public class Demo {
@Test public void test() { Class<Person> clazz = Person.class;
Class<? super Person> superclass = clazz.getSuperclass(); System.out.println(superclass);
Type genericSuperclass = clazz.getGenericSuperclass(); System.out.println(genericSuperclass);
ParameterizedType genericSuperclass1 = (ParameterizedType) genericSuperclass; Type[] actualTypeArguments = genericSuperclass1.getActualTypeArguments(); System.out.println(actualTypeArguments[0].getTypeName()); } }
|
获取运行时类的接口、包、注解
有了Class类的实例对象后,还可以获取运行时类的接口、包、注解
| 方法 | 简介 |
|---|
| getInterfaces() | 获取运行时类的实现的接口 |
| getInterfaces() | 获取运行时类实现的接口 |
| getPackage() | 获取运行时类所在的包 |
| getAnnotations() | 获取运行时类声明的注解 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| public class Demo {
@Test public void test() { Class<Person> clazz = Person.class;
Class<?>[] interfaces = clazz.getInterfaces(); for (Class c : interfaces) { System.out.println("运行时类的实现的接口"+c); }
Class<?>[] interfaces1 = clazz.getSuperclass().getInterfaces(); for (Class c : interfaces1) { System.out.println("运行时类的父类实现的接口:"+c); }
Package pack = clazz.getPackage(); System.out.println("运行时类所在的包:"+pack);
Annotation[] annotations = clazz.getAnnotations(); for (Annotation a : annotations) { System.out.println("运行时类声明的注解:"+a); } } }
|
操作运行时类中指定的属性
有了Class类的实例对象后,还可以操作运行时类中指定的属性
| 方法 | 简介 |
|---|
| set(Object obj, Object value) | 设置属性值 |
| get(Object obj) | 读取属性值 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| public class Demo {
@Test public void test1() throws Exception { Class<Person> clazz = Person.class;
Person person = clazz.newInstance();
Field name = clazz.getField("name");
name.set(person, "小明");
Object obj = name.get(person); System.out.println(obj); }
@Test public void test2() throws Exception { Class<Person> clazz = Person.class;
Person person = clazz.newInstance();
Field id = clazz.getDeclaredField("id"); Field name = clazz.getDeclaredField("name"); id.setAccessible(true); name.setAccessible(true);
id.set(person, 1001); name.set(person,"小明");
Object pId = id.get(person); Object pName = name.get(person); System.out.println("id:"+pId); System.out.println("name:"+pName); } }
|
操作运行时类中指定的方法
有了Class类的实例对象后,还可以操作运行时类中指定的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| public class Demo {
@Test public void test1() throws Exception { Class<Person> clazz = Person.class;
Person person = clazz.newInstance();
Method show1 = clazz.getDeclaredMethod("show1"); Method show2 = clazz.getDeclaredMethod("show2"); Method show3 = clazz.getDeclaredMethod("show3", String.class);
show1.setAccessible(true); show2.setAccessible(true); show3.setAccessible(true);
show1.invoke(person); show2.invoke(person); show3.invoke(person, "实参"); }
@Test public void test2() throws Exception { Class<Person> clazz = Person.class;
Person person = clazz.newInstance();
Method show2 = clazz.getDeclaredMethod("show2");
show2.setAccessible(true);
show2.invoke(person);
Object invoke2 = show2.invoke(Person.class); System.out.println(invoke2);
show2.invoke(null); } }
|
操作运行时类的指定构造器
有了Class类的实例对象后,还可以操作运行时类的指定构造器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public class Demo {
@Test public void test() throws Exception { Class<Person> clazz = Person.class;
Constructor<Person> constructor = clazz.getDeclaredConstructor(int.class,String.class,int.class);
constructor.setAccessible(true);
Person per = constructor.newInstance(1001,"小明",18); System.out.println(per); } }
|
反射的应用:动态代理
代理模式
代理模式简介
(1)代理模式是二十三种设计模式中的一种,属于结构型模式,作用是在不修改目标对象(被代理对象)的基础上,通过代理对象(扩展代理类),进行一些功能的附加与增强
(2)代理模式优点:解耦,可以把附加功能从业务功能代码中抽取出来
(3)代理模式应用:Spring AOP面向切面编程
代理模式分类
(1)代理模式有静态代理和动态代理两种实现方式
| 代理模式 | 简介 |
|---|
| 静态代理 | 代理类在程序运行前就已经存在 |
| 动态代理 | 代理类是运行时根据在Java代码中的“指示”动态生成的 |
(2)静态代理和动态代理的对比
| 对比 | 简介 |
|---|
| 灵活性 | 静态代理中,接口一旦新增加方法,目标对象和代理对象都要进行修改,非常麻烦! 动态代理更加灵活,不需要必须实现接口,可以直接代理实现类,不需要针对每个目标类都创建一个代理类。 |
| JVM 层面 | 静态代理在编译时就将接口、实现类、代理类这些都变成了一个个实际的 class 文件 动态代理是在运行时动态生成类字节码,并加载到 JVM 中 |
静态代理
静态代理的特点
1 2 3
| 1、代理对象和被代理对象实现同一个接口,代理对象中的核心功能由被代理对象来完成,代理对象负责增强功能。 2、代理对象可以做很多被代理对象做不了的事情,而真实对象专注做自己的事情 3、代理对象在程序运行前就已经存在,支持被代理对象灵活的切换,但无法对功能灵活的处理(动态代理可解决此问题)
|
静态代理的缺点
| 缺点 | 简介 |
|---|
| 代码重复 | 代理类和目标类实现了相同的接口,每个代理都需要实现目标类的方法,这样就出现了大量的代码重复 |
| 难于管理 | 如果接口增加一个方法,除了所有目标类需要实现这个方法外,所有代理类也需要实现此方法。 所以静态代理只适合业务功能固定不变的情况,即业务接口方法不进行增加和减少,此时实现类不需要改动 |
| 代理复杂 | 代理类只服务于一种类型的目标类,如果要服务多个类型,势必要为每一种目标类都进行代理 若程序规模变大的话,代理类数量就会越来越多 |
静态代理案例
(1)接口ClothFactory
1 2 3
| interface ClothFactory { void produceCloth(); }
|
(2)被代理类实现ClothFactory接口重写方法produceCloth(),完成某些具体操作
1 2 3 4 5 6
| public class Nike implements ClothFactory { @Override public void produceCloth() { System.out.println("Nike工厂生产运动服"); } }
|
(3)代理类实现ClothFactory接口重写方法produceCloth(),做功能增强
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public 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("代理工厂做一些后续的工作"); } }
|
(4)测试使用
1 2 3 4 5 6 7 8 9 10
| public class Demo { public static void main(String[] args) { Nike nike = new Nike(); ProxyClothFactory proxyClothFactory = new ProxyClothFactory(nike); proxyClothFactory.produceCloth(); } }
|
静态代理计算机案例
(1)计算机接口
1 2 3 4 5 6 7 8 9 10 11 12 13
| public interface Calculator { int add(int i, int j);
int sub(int i, int j);
int mul(int i, int j);
int div(int i, int j); }
|
(2)计算机接口实现类,负责实际的操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| public class CalculatorImpl implements Calculator { @Override public int add(int i, int j) { int result = i + j; return result; }
@Override public int sub(int i, int j) { int result = i - j; return result; }
@Override public int mul(int i, int j) { int result = i * j; return result; }
@Override public int div(int i, int j) { int result = i / j; return result; } }
|
(3)代理工厂:计算机接口实现类,为实际操作提供功能扩展(日志记录)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| public class CalculatorStaticProxy implements Calculator { private Calculator target;
public CalculatorStaticProxy(Calculator target) { this.target = target; }
@Override public int add(int i, int j) { System.out.println("[日志] add 方法开始了,参数是:" + i + "," + j); int addResult = target.add(i, j); System.out.println("[日志] add 方法结束了,结果是:" + addResult); return addResult; }
@Override public int sub(int i, int j) { System.out.println("[日志] sub 方法开始了,参数是:" + i + "," + j); int result = target.sub(i, j); System.out.println("方法内部 result = " + result); return result; }
@Override public int mul(int i, int j) { System.out.println("[日志] mul 方法开始了,参数是:" + i + "," + j); int result = target.mul(i, j); System.out.println("方法内部 result = " + result); return result; }
@Override public int div(int i, int j) { System.out.println("[日志] div 方法开始了,参数是:" + i + "," + j); int result = target.div(i, j); System.out.println("方法内部 result = " + result); return result; } }
|
(4)静态代理测试
1 2 3 4 5 6 7 8 9 10
| public class CalculatorStaticProxyTest { public static void main(String[] args) { CalculatorImpl calculator = new CalculatorImpl(); CalculatorStaticProxy calculatorStaticProxy = new CalculatorStaticProxy(calculator); int add = calculatorStaticProxy.add(1, 3); } }
|
动态代理
动态代理简介
(1)在不改变方法源码的情况下,增强方法功能,提高复用性
(2)提高开发效率和可扩展性
(3)可为被代理对象所有方法做代理、可以为任意接口的实现类对象做代理
(4)动态代理的实现方式有很多种,比如 JDK 动态代理、CGLIB 动态代理等等
| 动态代理 | 简介 |
|---|
| JDK动态代理 | 要求必须有接口,最终生成的代理类和目标类实现相同的接口,JDK 动态代理只能代理实现了接口的类或者直接代理接口 |
| cglib动态代理 | 最终生成的代理类,会继承目标类,并且和目标类在相同的包下,CGLIB 动态代理可以代理未实现任何接口的类 |
(5)JDK 动态代理和 CGLIB 动态代理对比
1
| 从效率方面,大部分情况都是 JDK 动态代理更优秀,随着 JDK 版本的升级,这个优势更加明显
|
动态代理案例
(1)接口
1 2 3 4
| interface Human { void eat(String food); }
|
(2)被代理类
1 2 3 4 5 6
| class SupperMan implements Human { @Override public void eat(String food) { System.out.println("超人喜欢吃" + food); } }
|
(3)创建实现InvocationHandler接口的类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
|
class MyInvocationHandler implements InvocationHandler { private Object target;
public void bind(Object target) { this.target = target; }
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("===========代理前执行==========");
Object returnValue = method.invoke(target, args);
System.out.println("===========代理后执行=========="); return returnValue; } }
|
(4)动态代理测试使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| public class Demo { public static void main(String[] args) { SupperMan supperMan = new SupperMan();
Human proxyInstance = (Human) getProxyInstance(supperMan);
proxyInstance.eat("早餐"); }
public static Object getProxyInstance(Object obj) { MyInvocationHandler handler = new MyInvocationHandler(); handler.bind(obj); return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), handler); } }
|
动态代理计算机案例
(1)计算机接口
1 2 3 4 5 6 7 8 9 10 11 12 13
| public interface Calculator { int add(int i, int j);
int sub(int i, int j);
int mul(int i, int j);
int div(int i, int j); }
|
(2)计算机接口实现类,负责实际的操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| public class CalculatorImpl implements Calculator { @Override public int add(int i, int j) { int result = i + j; return result; }
@Override public int sub(int i, int j) { int result = i - j; return result; }
@Override public int mul(int i, int j) { int result = i * j; return result; }
@Override public int div(int i, int j) { int result = i / j; return result; } }
|
(3)生产代理对象的工厂类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| public class ProxyFactory { private Object target;
public ProxyFactory(Object target) { this.target = target; }
public Object getProxy() { ClassLoader classLoader = target.getClass().getClassLoader();
Class<?>[] interfaces = target.getClass().getInterfaces();
InvocationHandler invocationHandler = new InvocationHandler() {
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object result = null; try { System.out.println("[动态代理][日志] " + method.getName() + ",参 数:" + Arrays.toString(args)); result = method.invoke(target, args); System.out.println("[动态代理][日志] " + method.getName() + ",结 果:" + result); } catch (Exception e) { e.printStackTrace(); System.out.println("[动态代理][日志] " + method.getName() + ",异 常:" + e.getMessage()); } finally { System.out.println("[动态代理][日志] " + method.getName() + ",方法 执行完毕"); } return result; } };
return Proxy.newProxyInstance(classLoader, interfaces, invocationHandler); } }
|
(4)动态代理测试使用
1 2 3 4 5 6 7 8 9 10 11 12
| public class ProxyFactoryTest { public static void main(String[] args) { CalculatorImpl calculator = new CalculatorImpl(); ProxyFactory factory = new ProxyFactory(calculator); Calculator proxy = (Calculator) factory.getProxy(); int add = proxy.add(1, 3); } }
|
CGLIB动态代理简介
(1)上述案例都是基于JDK实现的动态代理,有一个最致命的问题是其只能代理实现了接口的类。为了解决这个问题,可以用 CGLIB 动态代理机制来避免,CGLIB 可以代理未实现任何接口的类
(2)CGLIB(Code Generation Library)是一个基于ASM的字节码生成库,它允许我们在运行时对字节码进行修改和动态生成。CGLIB 通过继承方式实现代理。很多知名的开源框架都使用到了CGLIB, 例如 Spring 中的 AOP 模块中:如果目标对象实现了接口,则默认采用 JDK 动态代理,否则采用 CGLIB 动态代理。
(3)CGLIB 动态代理是通过生成一个被代理类的子类来拦截被代理类的方法调用,因此不能代理声明为 final 类型的类和方法
(4)CGLIB 动态代理类使用步骤
1 2 3
| 1、定义一个类,用于被增强 2、自定义类实现MethodInterceptor接口,重写接口的intercept方法 3、通过Enhancer类的create()方法创建代理类
|
CGLIB动态代理计算机案例
(1)JDK 动态代理不需要额外的依赖,但CGLIB实际是属于一个开源项目,如果要使用,需要手动添加相关依赖。
1 2 3 4 5 6 7 8
| <dependencies> <dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.3.0</version> </dependency> </dependencies>
|
(2)计算机接口
1 2 3 4 5 6 7 8 9 10 11 12 13
| public interface Calculator { int add(int i, int j);
int sub(int i, int j);
int mul(int i, int j);
int div(int i, int j); }
|
(3)计算机接口实现类,负责实际的操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| public class CalculatorImpl implements Calculator { @Override public int add(int i, int j) { int result = i + j; return result; }
@Override public int sub(int i, int j) { int result = i - j; return result; }
@Override public int mul(int i, int j) { int result = i * j; return result; }
@Override public int div(int i, int j) { int result = i / j; return result; } }
|
(4)方法拦截器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
|
public class DebugMethodInterceptor implements MethodInterceptor {
@Override public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { Object result = null; try { System.out.println("[动态代理][日志] " + method.getName() + ",参 数:" + Arrays.toString(args)); result = methodProxy.invokeSuper(o, args); System.out.println("[动态代理][日志] " + method.getName() + ",结 果:" + result); } catch (Exception e) { e.printStackTrace(); System.out.println("[动态代理][日志] " + method.getName() + ",异 常:" + e.getMessage()); } finally { System.out.println("[动态代理][日志] " + method.getName() + ",方法 执行完毕"); } return result; } }
|
(5)生产代理对象的工厂类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
public class CglibProxyFactory {
public static Object getProxy(Class<?> clazz) { Enhancer enhancer = new Enhancer(); enhancer.setClassLoader(clazz.getClassLoader()); enhancer.setSuperclass(clazz); enhancer.setCallback(new DebugMethodInterceptor()); return enhancer.create(); } }
|
(6)CGLIB动态代理测试使用
1 2 3 4 5 6 7 8
| public class DebugMethodInterceptorTest { public static void main(String[] args) { CalculatorImpl proxy = (CalculatorImpl) CglibProxyFactory.getProxy(CalculatorImpl.class); int add = proxy.add(1, 3); } }
|