反射 如果说大家研究过框架的底层原理或者咱们自己写过框架的话,一定对反射这个概念不陌生。
反射之所以被称为框架的灵魂,主要是因为它赋予了我们在运行时分析类以及执行类中方法的能力。
通过反射可以去构造任意一个类对象获取任意一个类的成员变量,成员方法,属性,可以在程序运行时动态的获取类的信息,以及动态的调用方法,通过java.lang.reflect实现反射相关的类库,包括Construct、Field、Method这样的类
谈谈反射机制的优缺点 优点 :可以让咱们的代码更加灵活、为各种框架提供开箱即用的功能提供了便利
增加程序的灵活性,可在运行的过程中动态对类进行修改和操作
提高代码的复用率 ,比如动态代理,就是用到了反射来实现
可以在运行时轻松获取任意一个类的方法、属性,并且还能通过反射进行动态调用
缺点 :让我们在运行时有了分析操作类的能力,这同样也增加了安全问题。比如可以无视泛型参数的安全检查(泛型参数的安全检查发生在编译时)。 另外,反射的性能也要稍差点,不过,对于框架来说实际是影响不大的。相关阅读:Java Reflection: Why is it so slow?
反射会涉及到运行时动态类型的解析 ,所以JVM无法对这些代码进行优化,导致性能要比非反前调用更低。
使用反射以后,代码的可读性会下降
反射可以绕过一些限制访问的属性或者方法 ,可能会导致破坏了代码本身的抽象性
反射的使用 创建学生类
1 2 3 4 5 public class Student { public void sleep () { System.out.println("sleep..." ); } }
在src下创建配置文件pro.properties
1 2 # 全类名className=cn.itcast.domain.Student # 方法名methodName=sleep
框架测试
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 public class ReflectTest { public static void main (String[] args) throws Exception { Properties pro = new Properties (); ClassLoader classLoader = ReflectTest.class.getClassLoader(); InputStream is = classLoader.getResourceAsStream("pro.properties" ); pro.load(is); String className = pro.getProperty("className" ); String methodName = pro.getProperty("methodName" ); Class cls = Class.forName(className); Object obj = cls.newInstance(); Method method = cls.getMethod(methodName); method.invoke(obj); } }
反射的应用场景 动态代理 像咱们平时大部分时候都是在写业务代码,很少会接触到直接使用反射机制的场景。
但是,这并不代表反射没有用。相反,正是因为反射,你才能这么轻松地使用各种框架。像 Spring/Spring Boot、MyBatis 等等框架中都大量使用了反射机制。
这些框架中也大量使用了动态代理,而动态代理的实现也依赖反射。
比如下面是通过 JDK 实现动态代理的示例代码,其中就使用了反射类 Method
来调用指定的方法。
实现 InvocationHandler
接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class ServiceInvocationHandler implements InvocationHandler { private final Object target; public ServiceInvocationHandler (Object target) { this .target = target; } @Override public Object invoke (Object proxy, Method method, Object[] args) throws Throwable { System.out.println("Before method: " + method.getName()); Object result = method.invoke(target, args); System.out.println("After method: " + method.getName()); return result; } }
目标类和其实现的接口
1 2 3 4 5 6 7 8 9 10 11 public interface Service { void performTask () ; }public class ServiceImpl implements Service { @Override public void performTask () { System.out.println("Executing task..." ); } }
客户端调用代理类执行方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import java.lang.reflect.Proxy;public class DynamicProxyDemo { public static void main (String[] args) { Service service = new ServiceImpl (); ServiceInvocationHandler handler = new ServiceInvocationHandler (service); Service proxyInstance = (Service) Proxy.newProxyInstance( service.getClass().getClassLoader(), new Class []{Service.class}, handler ); proxyInstance.performTask(); } }
Before method: performTask Executing task… After method: performTask
动态代理的好处 :
减少代码冗余 :动态代理不需要为每个接口手动编写代理类,避免了代码重复。只需编写一个通用的代理处理器,减少了类的数量和代码冗余。
提高灵活性 :动态代理允许在运行时动态决定代理哪个对象和方法。可以根据配置文件或运行时条件动态生成代理,具有更高的灵活性和扩展性。
支持更多的接口 :动态代理可以在运行时为多个接口创建代理实例,而不需要为每个接口创建单独的代理类。
减少维护成本 :因为动态代理不需要手动创建代理类,代码量减少,修改和维护成本也相对降低。
适用于AOP(面向切面编程) :动态代理非常适合实现 AOP,比如 Spring AOP 就大量使用动态代理来实现切面功能。可以在运行时为目标对象添加横切关注点(如日志记录、事务管理等)。
搭配注解使用 另外,像 Java 中的一大利器 注解 的实现也用到了反射。
为什么你使用 Spring 的时候 ,一个@Component
注解就声明了一个类为 Spring Bean 呢?为什么你通过一个 @Value
注解就读取到配置文件中的值呢?究竟是怎么起作用的呢?
这些都是因为你可以基于反射分析类,然后获取到类/属性/方法/方法的参数上的注解。你获取到注解之后,就可以做进一步的处理。
现有一个类和方法
1 2 3 4 5 public class Demo1 { public void show () { System.out.println("demo1...show" ); } }
写一个自定义注解
1 2 3 4 5 6 7 8 9 @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface Pro { String className () ; String methodName () ; }
写一个需要使用注解的类
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 @Pro(className = "annotation.Demo1",methodName = "show") public class ReflectTest { public static void main (String[] args) throws Exception { Class<ReflectTest> reflectTestClass = ReflectTest.class; Pro proimpl = reflectTestClass.getAnnotation(Pro.class); String className = proimpl.className(); String methodName = proimpl.methodName(); System.out.println(className); System.out.println(methodName); Class cls = Class.forName(className); Object obj = cls.newInstance(); Method method = cls.getMethod(methodName); method.invoke(obj); } }
其实就是在内存中生成了一个该注解接口的子类实现对象
1 2 3 4 5 6 7 8 public class ProImpl implements Pro { public String className () { return "cn.itcast.annotation.Demo1" ; } public String methodName () { return "show" ; } }
利用反射实现多态 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public interface Animal { void makeSound () ; }public class Dog implements Animal { @Override public void makeSound () { System.out.println("Woof" ); } }public class Cat implements Animal { @Override public void makeSound () { System.out.println("Meow" ); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import java.lang.reflect.Method;public class ReflectionPolymorphism { public static void main (String[] args) { try { String className = "Dog" ; Class<?> clazz = Class.forName(className); Animal animal = (Animal) clazz.getDeclaredConstructor().newInstance(); Method method = clazz.getMethod("makeSound" ); method.invoke(animal); } catch (Exception e) { e.printStackTrace(); } } }
获取Class对象的方式 1. Class.forName(“全类名”):将字节码文件加载进内存, 返回Class对象
多用于配置文件,将类名定义在配置文件 载类
2. 类名.class :通过类名的属性class获取
多用于参数的传递
3. **对象.getClass()**:getClass()方法在Object类中定义了。
多用于对象的获取字节码的方式,可以获取该类运行时的类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 Class cls1 = Class.forName("cn.itcast.reflect.Person" ); System.out.println(cls1); Class cls2 = Person.class; System.out.println(cls2); Person p = new Person ();Class cls3 = p.getClass(); System.out.println(cls3); System.out.println(cls1==cls2); System.out.println(cls2==cls3);
结论: 同一个字节码文件(*.class)在一次程序运行过程中,只会被加载一次,不论通过哪一种方式获取的Class对象都是同一个
Class类常用方法
获取成员变量们 Field[] getFields() :获取所有public修饰的成员变量
Field getField(String name) :获取指定名称的 public修饰的成员变量
Field[] getDeclaredFields() :获取所有的成员变量,不考虑修饰符
Field getDeclaredField(String name) :获取指定名称成员变量,不考虑修饰符
Field对象的方法
void set(Object obj, Object value): 设置成员便变量对象的值
Object get(Object obj): 获取成员变量对象
创建一个Person类
1 2 3 4 5 6 7 8 public class Person { private String name; private int age; public String a; protected String b; String c; private String d; }
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 Class personClass = Person.class; Field[] fields = personClass.getFields();for (Field field :fields) { System.out.println(field); } Field[] declaredFields = personClass.getDeclaredFields();for (Field declaredField :declaredFields) { System.out.println(declaredField); }Field a = personClass.getField("a" ); Person p = new Person ();Object value = a.get(p); System.out.println(value); a.set(p,"张三" );Object value1 = a.get(p); System.out.println(p);
获取构造方法们 Constructor<?>[] getConstructors()
Constructor< T> getConstructor(类<?>… parameterTypes)
Constructor< T> getDeclaredConstructor(类<?>… parameterTypes)
Constructor<?>[] getDeclaredConstructors()
Constructor对象的方法
**T newInstance(Object… initargs) **:通过构造器创建对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class Person { public Person () { } public Person (String name, int age) { this .name = name; this .age = age; } }Class personClass = Person.class;Constructor constructor = personClass.getConstructor(String.class, int .class); System.out.println(constructor);Object person = constructor.newInstance("张三" , 23 ); System.out.println(person);
如果使用空参数构造方法创建对象,操作可以简化:Class对象的newInstance方法
1 2 3 4 5 6 7 Constructor constructor1 = personClass.getConstructor(); System.out.println(constructor1); Object person2 = personClass.newInstance(); System.out.println(person2);
获取成员方法们 Method[] getMethods()
Method getMethod(String name, 类<?>… parameterTypes)
Method[] getDeclaredMethods()
Method getDeclaredMethod(String name, 类<?>… parameterTypes)
Method对象的方法
Object invoke(Object obj, Object… args) : 执行方法对象的方法
String getName(): 获取方法名
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 public class Person { public void eat () { System.out.println("eat..." ); } public void eat (String food) { System.out.println("eat..." +food); } }Class personClass = Person.class;Method eat_method = personClass.getMethod("eat" );Person p = new Person (); eat_method.invoke(p); Method eat_method2 = personClass.getMethod("eat" , String.class); eat_method2.invoke(p,"饭" ); Method[] methods = personClass.getMethods();for (Method method : methods) { System.out.println(method); String name = method.getName(); System.out.println(name); }
反射会产生的安全性问题 Java反射机制是一种强大的工具,但同时也可能引发一些安全性问题,主要包括以下几个方面:
破坏封装性 :通过反射,可以访问和修改类的私有成员变量、方法和构造函数,从而破坏了类的封装性。这可能导致程序的不稳定性和安全隐患,使得不恰当的访问可能产生意外的结果。
绕过访问控制 :通过反射,可以绕过Java语言的访问控制机制,访问和修改类的私有成员变量和方法。这可能导致未经授权的访问,破坏了程序的安全性。
执行恶意代码 :恶意用户可以利用反射机制执行恶意代码,例如在运行时动态加载恶意类并执行其中的恶意方法,从而实现攻击目标系统的目的。
潜在的性能问题 :使用反射可能会导致性能下降,因为反射调用通常比直接调用方法或访问变量要慢,而且会消耗更多的资源。因此,过度使用反射可能会影响程序的性能。
比如以下这个类,有一个final修饰的num变量,final修饰的变量只能在类初始化时赋值,并且不可改变
1 2 3 4 5 6 public class MyClass { public final int num; public MyClass (int num) { this .num = num; } }
通过setAccessible方法可以编译期的安全检测,修改final变量
setAccessible(boolean flag)
方法是 Java 反射机制中的一个方法,用于设置 Field、Method 或 Constructor 对象的可访问性。通过调用该方法并传入 true
参数,可以关闭 Java 语言的访问控制检查,使得无论访问修饰符是什么,都可以访问该对象。
通常情况下,如果尝试访问一个私有成员(字段、方法或构造函数),或者是对于受保护或默认访问权限的成员,Java 编译器会执行访问控制检查,防止直接访问这些成员。但是,通过调用 setAccessible(true)
方法,可以绕过这种访问控制检查,从而获得对这些私有成员的访问权。
1 2 3 4 5 6 7 8 9 10 public class Main { public static void main (String[] args) throws NoSuchFieldException, IllegalAccessException { MyClass obj = new MyClass (10 ); Class<MyClass> myClass = MyClass.class; Field num = myClass.getField("num" ); num.setAccessible(true ); num.set(obj, 20 ); System.out.println(obj.num); } }