反射定义
反射是java的特性之一,它允许运行中的java程序获取自身的信息,并且可以操作类或对象的内部属性。简而言之,在程序运行期可以拿到一个对象的所有信息。
反射最重要的用途就是开发各种通用框架。很多框架都是配置化的,为了保证框架的通用性,它们可能需要根据配置文件加载不同的对象或类,调用不同的方法,这个时候就必须用到反射,运行时动态加载需要加载的对象。
反射常见应用
获取class对象
获取class对象的方式:
java.lang.Object.getClass()
方法- 静态class属性
- 反射方式:
java.lang.class.forName()
ClassLoader.loadclass()
class对象创建流程
- 在Java中用来表示运行时类型信息的对应类就是class类,对应于JDK的
java.lang.Class
类。Class类被创建后的对象就是Class对象,Class对象表示的是自己手动编写类的类型信息,在Java中每个类都有一个Class对象。比如创建一个Shapes类,那么JVM就会自动创建一个Shapes对应Class类的Class对象,该Class对象保存了Shapes类相关的类型信息,这个Class对象会被保存在同名.class字节码文件里。 - 当我们new一个新对象或者引用静态成员变量时,(JVM)中的类加载器子系统会将对应class对象加载到JVM中,然后JVM根据这个类型信息相关的Class对象创建实例对象或者提供静态变量的引用值。
注意:在内存中每个类有且只有一个相对应的Class对象。
java.lang.Object.getClass()获取对象
1 | Student student = new Student(); |
最后输出 class com.student
静态class属性获取对象
任何数据类型(包括基本数据类型)都有一个“静态”的class属性,当已经加载了某类,可以直接类名.class来获取这个类。
1 | Class stuClass2 = Student.class; |
反射方式1调用Class.forName获取对象
java.lang.Class.forName
(),其为静态方法,返回指定类名或接口关联的Class对象。有两个方法重载。
1 | public static Class<?> forName(String className) |
默认情况下,forName
的第一个参数的全限定类名,第二个参数initialize表示是否进行类的初始化,第三个参数就是ClassLoader
类加载器。
1 | Class<Student> aClass = (Class<Student>) Class.forName("com.Student"); |
仅调用Class.forName()
会且仅加载静态方法
实战中经常会把恶意代码放到static静态代码中,使其默认执行。
反射方式2调用Class.loadClass()获取对象
使用类加载器指定加载类
1 | Class aClass1 = ClassLoader.getSystemClassLoader().loadClass("com.Student"); |
获取属性与方法
常用的API
返回类的公有Method
1 | java.lang.Class.getMethod(String name,class<?>... parameterTypes) |
返回所有声明的Method类对象,包括公共,保护,默认(包)访问和私有方法,但不包括继承的方法。
1 | java.lang.Class.getDeclaredMethod(String name,Class<?>.....parameterTypes) |
返回已加载类声明的所有public成员变量的Field对象,包括从⽗类继承过来的成员变量,参数 name指定成员变量的名称
1 | java.lang.Class.getField(String name) |
返回当前类所有成员变量的Field对象
1 | java.lang.Class.getDeclaredField(String name) |
返回obj对象的Field属性值
1 | java.lang.reflect.Field.get(Object obj) |
向obj对象的这个Field属性设置新值value。
1 | java.lang.reflect.Field.set(Object obj, Object value) |
返回该Field已声明的修饰符集的整数值。
1 | java.lang.reflect.Field.getModifiers() |
将该Field的值设置为指定obj对象上的int值。
1 | java.lang.reflect.Field.setInt(Object obj, int i) |
field.setAccessible(boolean flag)
⼀般情况下,我们并不能对类的私有字段进⾏操作,⽽setAccessible则是启⽤和禁⽤访问安全检查的开 关。flag值为 true 则指示反射的对象在使⽤时应该取消 Java 语⾔访问检查。
java.lang.reflect.Modifier
修饰符⼯具类,提供解码类和成员访问修饰符的静态⽅法和常量,修饰符集合具有表示不同的修饰符的 不同位置的整数。
java.lang.reflect.Modifier.toString(int modifiers)
以获得修饰符的⽂本形式
利用反射获取私有属性和方法
1 | package com; |
1 | public static void test2() throws Exception { |
反射修改static final修饰的变量值
1 | public class Student2 { |
1 | public static void test3() throws Exception { |
调用构造方法
实体类
1 | public class Student { |
测试类
1 | public class GetConstructors { |
对象实例化
- java.lang.Class.newinstance()
- java.lang.reflect.Constructor.newinstace()
很多框架使用的都是构造器反射的方式获取对象,如Spring,Guava,Zookeeper,Jackson,Servlet等
student类
1 | public class Student { |
测试类
1 | public static void test5() throws ClassNotFoundException, InstantiationException, IllegalAccessException { |
调用内部类-默认构造器
例如:声明内部类,重写构造器。注意:当我们重写构造器后,默认的无参构造器将不存在
1 | public class NestedClass1 { |
1 | public static void Test1() throws Exception{ |
调用内部类,重写构造器
注意:当我们重写构造器后,默认的无参构造器将不存在
1 | public class NestedClass2 { |
1 | public static void Test2() throws Exception{ |
不安全的反射
命令执行
例如:反射调用Runtime类exec方法
1 | public class ExecClass { |
代理模式
代理模式提供了对目标对象额外的访问方式,即通过代理对象访问目标对象,这样可以在不修改原目标对象的前提下,提供额外的功能操作,扩展目标对象的功能。简言之,代理模式就是设置一个中间代理来控制访问原目标对象,以达到增强原对象的功能和简化访问方式。但是对于真正的调用来说,实际上并不关心这个代理对象,只要能够实现相应的业务逻辑就好。
比如说,火车站很挤,人一多,买票就很恼火。于是就有了代售点,我们就能从代售点买车票了。这就是代理模式的体现,代售点代理了火车站售票点的对象,提供了购买车票的方法。
静态代理
定义
Subject — 抽象主题类,定义了代理对象和真实对象的共同接⼝⽅法,可以理解为定义了某⼀种业 务需求的实现规范。既可以是接⼝也可以是抽象类,其中声明了需要被实现的⽅法。
RealSubject — 真实主题类,该类可以被称为被委托类或被代理类,该类定义了代理对象所表示的 3 代理模式 ● ● 3.1 静态代理 3.1.1 定义 ● ● 37 真实对象,实现了Subject接⼝⽅法。
Proxy — 代理类,该类也被称为委托类或代理类,该类中持有⼀个真实主题类的引⽤,同样实现了 Subject接⼝。在其实现的接⼝⽅法中调⽤真实主题类中相应的接⼝⽅法,在此基础上附加其他动 作。Client端通过代理类间接调⽤的真实主题类中的⽅法,由其执⾏真正的业务逻辑。
Client — 客户端。
要求:代理类和被代理类应该共同实现一个接口,或者是共同继承某个类。
例如:电影公司委托电影院播放电影,电影院想要在播放电影的时候,加一些广告或服务项目赚取收益。
静态代理服务接口:MovieServiceImpl
通用的接口是代理模式实现的基础。MovieService
接口代表电影播放服务。
静态代理服务服务实现类MovieSerbiceImpl
MovieService
接口的实现类,可以当作电影公司,作为接口实现类,要求必须定义接口中声明的所有方法。
服务的代理类:MovieProxy
MovieService
服务的代理类,就是电影服务的代理对象,当作电影院,代理了电影公司提供的电影播放服务。方法实现时,主要调用服务实现类提供的方法,并在其执行前后添加附加功能。
实例
1 | //服务的代理类 |
1 | //代理服务实现类 |
1 | //通用接口类 |
缺陷
- 冗余
由于代理对象要实现与目标对象一致的接口,随着系统中需要被代理的目标对象类的增多,也会产生过多的代理类。
- 不易维护
一旦接口增加方法,被代理的目标对象类与代理类都要进行修改,对增加的方法进行实现,维护工作较为复杂。
为此,特地拥有了动态代理。
静态代理vs动态代理
- 静态代理需要手动编写代码让代理类实现某接口。而动态代理则可以让程序在运行时自动在内存中创建一个实现某接口的代理,而不需要去手动定义代理类。
- 静态代理在程序运行前,代理类的class文件(类字节码)就已经存在了。而动态代理是在运行时动态生成类字节码,并加载到JVM中。
动态代理
与静态代理相比,多了一个InvocationHandler角色和一个Proxy角色,InvocationHandler是java提供的一个接口,我们需要定义一个类实现InvocationHandler接口,这里就叫做DynamicProxy角色;Proxy是java提供用于动态生成ProxySubject的一个类,它需要ProxySubject继承。
我们看到DynamicProxy在ProxySubject和RealSubject之前起到了中间人的角色,ProxySubject会把事情委托给DynamicProxy来做,而DynamicProxy最终把事情委托给RealSubject来做。
动态代理的实现可以使用JDK来做。
JDK动态代理
JDK提供了java.lang.reflect.InvocationHandler接⼝和java.lang.reflect.Proxy类。 java.lang.reflect.InvocationHandler 接⼝中仅声明了⼀个⽅法invoke(),第⼀个参数 proxy⼀般是指代理 类,method是被代理的⽅法,args为⽅法中声明的形参。
1 | public interface InvocationHandler{ |
JDK中动态代理常用API
java.lang.reflect.Proxy
动态代理类中提供的 getProxyClass()
静态⽅法可以⽤来获取⼀个代理Class 对象,其接受的参数为类加载器和⽬标类实现的接⼝。
public static Class getProxyClass(ClassLoader loader, Class... interfaces)
Proxy类提供的 另⼀个静态⽅法 newProxyInstance()
可以直接获取代理实例对象,连创建代理类对象的过程都封装 起来了。
public static Object newProxyInstance(ClassLoader loader,Class<?>[]interface,InvocationHandler h)
loader是类加载器,interface是代理要实现的服务接口,h是已给InvocationHandler对象,表示的是当动态代理对象调用方法的时候会关联到哪一个invocationHandler对象,并最终由其调用。
关于Proxy.newProxyinstance()
方法
- 判断传入的invocationHandler实例对象是否为null,并对传入的接口进行克隆权限校验。获取系统安全接口(安全管理器),如果不为空,检查创建代理类所需的权限。
- 查找或生成指定的代理类对象,要求调用此函数之前必须调用
checkProxyAccess
方法执行权限检查。首先判断接口数量,过多则抛出异常。随后调用proxyClassCache.get(loader,interfaces)方法,参数接收类加载器和接口数组,从proxyClassCache缓存中获取代理类,如果找不到,则通过ProxyClassFactory创建代理类。
1 | public static void test1(){ |