Java基础:反射机制

1.反射机制

反射是什么?

反射(Reflection)指 在运行状态下,对任意的类都可以获取到该类的所有属性和方法,对任意的对象都可以调用该对象的任意一个方法和属性。 (即 在运行状态下动态获取类信息以及动态的调用对象的方法 就是Java的反射机制,它带来的是在运行时动态地分析类及执行类中方法的能力)

前置知识:Class 类

  1. Class本身是个类
  2. 手动编写的类被编译后会产生一个Class对象,其表示的是创建的类的类型信息,而且这个Class对象保存在同名.class的文件中(字节码文件)
  3. 一个Class对象对应着一个加载到JVM中的.class文件 (因为加载到JVM的类只会有一个Class实例)
  4. 通过Class可以获取到该类所有被加载的结构(信息)
  5. Class类只存私有构造函数,因此对应Class对象只能有JVM创建和加载
  6. 为什么是前置知识? 想要动态加载运行的类必须先获取到该类的Class对象(Class类的对象作用是运行时提供或获得某个对象的类型信息)

四种获取Class类的实例的方式

  1. 通过类的class属性获取 (安全可靠性能高)
Class<String> clazz = String.class;
复制代码
  1. 已知类的实例,可以调用类实例的方法getClass()
String s = "6";
Class<? extends String> clazz = s.getClass();
复制代码
  1. 已知类的全类名,可以通过Class类的静态方法forName(),出现错误会抛异常ClassNotFoundException
try {
    Class<?> clazz = Class.forName("java.util.String");
} catch (ClassNotFoundException e) {
    e.printStackTrace();
}
复制代码
  1. 通过加载该类的类加载器获取 (不推荐)
try {
    this.getClass().getClassLoader().loadClass("com.xqsr.reflection.ClassTest");
} catch (ClassNotFoundException e) {
    e.printStackTrace();
}
复制代码

Class类的常用方法


有了Class类,可以通过反射能获取到运行时类的什么信息?

1.1 获取运行时类的对象

Class对象的newInstance()方法

① 该类必须有一个无参构造器

② 类的构造器的访问权限足够

// 获取Class实例
Class<String> clazz = String.class;
try {
    // 通过Class实例反射创建出运行时对象
    String s = clazz.newInstance();
} catch (InstantiationException | IllegalAccessException e) {
    e.printStackTrace();
}
复制代码

Constructor对象的newInstance(Object ... initargs)方法

通过这个方法可以使用指定形参类型的构造器创建运行时类的对象

// 获取Class实例
Class<String> clazz = String.class;
try {
    // 通过Class对象获取到Constructor对象
    Constructor<? extends String> constructor = clazz.getDeclaredConstructor(String.class);
    // 按照指定的类型String.class作为形参类型创建运行时类的对象
    String s = constructor.newInstance("6");
} catch (Exception e) {
    e.printStackTrace();
}
复制代码

1.2 获取运行时类的Constructor

Constructor类存在于反射包(java.lang.reflect)中,指的是Class 对象所表示的类的构造方法

通过Class对象获取到Constructor对象的方法

方法返回值方法名称方法说明
ConstructorgetConstructor(Class<?>... parameterTypes)返回指定参数类型、具有public访问权限的构造函数对象
Constructor<?>[]getConstructors()返回所有具有public访问权限的构造函数的Constructor对象数组
ConstructorgetDeclaredConstructor(Class<?>... parameterTypes)返回指定参数类型、所有声明的(包括private)构造函数对象
Constructor<?>[]getDeclaredConstructors()返回所有声明的(包括private)构造函数对象

Constructor类常用方法

方法返回值方法名称方法说明
ClassgetDeclaringClass()返回 Class 对象,该对象表示声明由此 Constructor 对象表示的构造方法的类,其实就是返回真实类型(不包含参数)
Type[]getGenericParameterTypes()按照声明顺序返回一组 Type 对象,返回的就是 Constructor对象构造函数的形参类型。
StringgetName()以字符串形式返回此构造方法的名称。
Class<?>[]getParameterTypes()按照声明顺序返回一组 Class 对象,即返回Constructor 对象所表示构造方法的形参类型
TnewInstance(Object... initargs)使用此 Constructor对象表示的构造函数来创建新实例
StringtoGenericString()返回描述此 Constructor 的字符串,其中包括类型参数。
intgetModifiers()以整型形式返回构造器的修饰符

1.3 获取运行时类的Field

Field 提供有关类或接口的单个字段的信息,以及对它的动态访问权限。反射的字段可能是一个类(静态)字段或实例字段

通过Class对象获取到Field对象的方法

方法返回值方法名称方法说明
FieldgetDeclaredField(String name)获取指定name名称的(包含private修饰的)字段,不包括继承的字段
Field[]getDeclaredFields()获取Class对象所表示的类或接口的所有(包含private修饰的)字段,不包括继承的字段
FieldgetField(String name)获取指定name名称、具有public修饰的字段,包含继承字段
Field[]getFields()获取修饰符为public的字段,包含继承字段

Field类常用方法

方法返回值方法名称方法说明
voidset(Object obj, Object value)将指定对象变量上此 Field 对象表示的字段设置为指定的新值。
Objectget(Object obj)返回指定对象上此 Field 表示的字段的值
Class<?>getType()返回一个 Class 对象,它标识了此Field 对象所表示字段的声明类型。
booleanisEnumConstant()如果此字段表示枚举类型的元素则返回 true;否则返回 false
StringtoGenericString()返回一个描述此 Field(包括其一般类型)的字符串
StringgetName()返回此 Field 对象表示的字段的名称
Class<?>getDeclaringClass()返回表示类或接口的 Class 对象,该类或接口声明由此 Field 对象表示的字段
voidsetAccessible(boolean flag)将此对象的 accessible 标志设置为指示的布尔值,即设置其可访问性

1.4 获取运行时类的Method

Method 提供关于类或接口上单独某个方法(以及如何访问该方法)的信息,所反映的方法可能是类方法或实例方法(包括抽象方法)。

通过Class对象获取到Method对象的方法

方法返回值方法名称方法说明
MethodgetDeclaredMethod(String name, Class<?>... parameterTypes)返回一个指定参数的Method对象,该对象反映此 Class 对象所表示的类或接口的指定已声明方法。
Method[]getDeclaredMethods()返回 Method 对象的一个数组,这些对象反映此 Class 对象表示的类或接口声明的所有方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法。
MethodgetMethod(String name, Class<?>... parameterTypes)返回一个 Method 对象,它反映此 Class 对象所表示的类或接口的指定公共成员方法。
Method[]getMethods()返回一个包含某些 Method 对象的数组,这些对象反映此 Class 对象所表示的类或接口(包括那些由该类或接口声明的以及从超类和超接口继承的那些的类或接口)的公共 member 方法。

Method类常用方法

方法返回值方法名称方法说明
Objectinvoke(Object obj, Object... args)对带有指定参数的指定对象调用由此 Method 对象表示的底层方法。
Class<?>getReturnType()返回一个 Class 对象,该对象描述了此 Method 对象所表示的方法的正式返回类型,即方法的返回类型
TypegetGenericReturnType()返回表示由此 Method 对象所表示方法的正式返回类型的 Type 对象,也是方法的返回类型。
Class<?>[]getParameterTypes()按照声明顺序返回 Class 对象的数组,这些对象描述了此 Method 对象所表示的方法的形参类型。即返回方法的参数类型组成的数组
Type[]getGenericParameterTypes()按照声明顺序返回 Type 对象的数组,这些对象描述了此 Method 对象所表示的方法的形参类型的,也是返回方法的参数类型
StringgetName()以 String 形式返回此 Method 对象表示的方法名称,即返回方法的名称
booleanisVarArgs()判断方法是否带可变参数,如果将此方法声明为带有可变数量的参数,则返回 true;否则,返回 false。
StringtoGenericString()返回描述此 Method 的字符串,包括类型参数。

2. 浅看反射机制的执行过程

通过一个反射执行过程了解反射机制

① 利用反射机制通过Class的forName方法加载指定包名的类信息Class,

② 通过Class对象获取到指定参数的构造器和指定的参数和名字的方法

③ 通过获取到的构造器创建对象实例并通过反射调用实例方法sayHello

/**
 * 目标类
 * @author 兴趣使然的L 2022/12/28
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Man {

    private String name;
    private int age;

    public void sayHello(String msg) {
        System.out.println("name:" + name + ", age:" + age + ", say:" + msg);
    }
}
复制代码
/**
 * @author 兴趣使然的L 2022/12/29
 */
public class Process {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException {
        // 通过forName获取类实例
        Class<?> clazz = Class.forName("com.xqsr.review.reflection.Man");
        // 获取构造器创建有参数的实例
        Constructor<?> constructor = clazz.getConstructor(String.class, int.class);
        Man man = (Man) constructor.newInstance("xqsr", 21);
        // 通过类实例获取到方法
        Method method = clazz.getDeclaredMethod("sayHello", String.class);
        // 反射调用方法
        method.invoke(man, "hello");
    }
}
复制代码

第一个点: forName() 如何获取到类信息

第二个点: 获取构造器信息 (获取方法信息与构造器类似)

============================ getConstructor方法 =====================================
@CallerSensitive
public Constructor<T> getConstructor(Class<?>... parameterTypes)
    throws NoSuchMethodException, SecurityException
{
    SecurityManager sm = System.getSecurityManager();
    // 1. 权限检测
    if (sm != null) {
        checkMemberAccess(sm, Member.PUBLIC, Reflection.getCallerClass(), true);
    }
    // 4. 通过 ReflectionFactory copy一份constructor返回
    return getReflectionFactory().copyConstructor(
        getConstructor0(parameterTypes, Member.PUBLIC));
}
复制代码
============================ getConstructor0方法 ==================================
private Constructor<T> getConstructor0(Class<?>[] parameterTypes,
                                    int which) throws NoSuchMethodException
{
    ReflectionFactory fact = getReflectionFactory();
    // 2. 获取所有constructors
    Constructor<T>[] constructors = privateGetDeclaredConstructors((which == Member.PUBLIC));
    // 3. 根据参数进行匹配,返回匹配结果
    for (Constructor<T> constructor : constructors) {
        if (arrayContentsEq(parameterTypes,
                            fact.getExecutableSharedParameterTypes(constructor))) {
            return constructor;
        }
    }
    throw new NoSuchMethodException(methodToString("<init>", parameterTypes));
}
复制代码

第三个点:Method的invoke()方法

反射执行过程小结

  1. 反射类及反射方法的获取,都是通过从列表中搜寻查找匹配的方法,所以查找性能会随类的大小方法多少而变化;
  2. 每个类都会有一个与之对应的Class实例,从而每个类都可以获取method反射方法,并作用到其他实例身上;
  3. 反射也是考虑了线程安全的,放心使用;
  4. 反射使用软引用relectionData缓存class信息,避免每次重新从jvm获取带来的开销;
  5. 反射调用多次生成新代理Accessor, 而通过字节码生存的则考虑了卸载功能,所以会使用独立的类加载器;
  6. 当找到需要的方法,都会copy一份出来,而不是使用原来的实例,从而保证数据隔离;
  7. 调度反射方法,最终是由jvm执行invoke0()执行;

3. 动态代理

反射的经典应用场景:动态代理

这里介绍两种常用的动态代理: JDK代理 和 CGLIB代理

代理类型代理机制回调方式适用场景效率
JDK委托机制,代理类和目标类都实现了同样的接口,InvocationHandler持有目标类,代理类委托InvocationHandler去调用目标类的原始方法反射目标类是接口类效率瓶颈在反射调用稍慢
CGLIB继承机制,代理类继承了目标类并重写了目标方法,通过回调函数MethodInterceptor调用父类方法执行原始逻辑通过FastClass方法索引调用非接口类,非final类,非final方法第一次调用因为要生成多个Class对象较JDK方式慢,多次调用因为有方法索引较反射方式快,如果方法过多switch case过多其效率还需测试

3.1 JDK 代理

实现 JDK 动态代理必备的两个基础方法

  1. Proxy 类中使用频率最高的方法:newProxyInstance() 方法
  2. 实现 InvocationHandler 接口类的 invoke() 方法

newProxyInstance() 方法用于生成代理对象

public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h)
复制代码

该方法有三个参数分别是:

  1. loader :类加载器,用于加载代理对象。
  2. interfaces : 被代理类实现的一些接口;
  3. h : 实现了 InvocationHandler 接口的对象;

实现InvocationHandler 接口类的 invoke() 用于自定义处理逻辑

public interface InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}
复制代码

该方法有三个参数分别是:

  1. proxy :动态生成的代理类
  2. method : 与代理类对象调用的方法相对应
  3. args : 当前 method 方法的参数

JDK动态代理过程其实就是 通过Proxy 类的 newProxyInstance() 创建的代理对象在调用方法的时候,实际会调用到实现InvocationHandler 接口的类的 invoke()方法。  可以在 invoke() 方法中自定义处理逻辑


接下来动手实现一下JDK动态代理

步骤

  1. 定义一个接口及其实现类;
  2. 自定义 InvocationHandler 并重写invoke方法,在 invoke 方法中我们会调用原生方法(被代理类的方法)并自定义一些处理逻辑;
  3. 通过 Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) 方法创建代理对象;

代码演示

  1. 目标类接口
/**
 * 用户服务接口
 * @author 兴趣使然的L 2022/12/29
 */
public interface IUserService {
    // 通过用户编号查询用户名
    String queryUserName(int userId);
}
复制代码
  1. 目标类接口实现类
/**
 * 用户服务接口实现类
 * @author 兴趣使然的L 2022/12/29
 */
public class IUserServiceImpl implements IUserService {

    @Override
    public String queryUserName(int userId) {
        System.out.println("查询成功:" + "L" + userId);
        return "L" + userId;
    }
}
复制代码
  1. JDK代理处理逻辑类
/**
 * JDK代理逻辑处理类
 * @author 兴趣使然的L 2022/12/29
 */
public class JDKInvocationHandler implements InvocationHandler {

    // 代理的目标类
    private final Object target;

    // 初始化target
    public JDKInvocationHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 可以在调用目标方法前做一些增强(增强方法)
        System.out.println("调用目标方法前增强代码");
        // 通过反射调用目标方法(核心)
        Object res = method.invoke(target, args);
        // 可以在调用后也做一些增强
        System.out.println("调用目标方法后增强代码");
        return res;
    }
}
复制代码
  1. 获取代理类实例的工厂类
/**
 * 获取JDK代理类实例的工厂类
 * @author 兴趣使然的L 2022/12/29
 */
public class JDKProxy {

    // 获取代理类
    public static Object getProxy(Object target) throws Exception {
        // 获取目标类的类加载器
        ClassLoader classLoader = target.getClass().getClassLoader();
        // 获取目标类的所有接口实现
        Class<?>[] interfaces = target.getClass().getInterfaces();
        // 获取代理类实例
        JDKInvocationHandler handler = new JDKInvocationHandler(target);
        return Proxy.newProxyInstance(classLoader, interfaces, handler);
    }
}
复制代码
  1. 测试
/**
 * 测试
 * @author 兴趣使然的L 2022/12/29
 */
public class ProxyTest {

    public static void main(String[] args) throws Exception {
        // 获取代理类对象(实例)
        IUserService proxy = (IUserService) JDKProxy.getProxy(new IUserServiceImpl());
        String userName = proxy.queryUserName(1001);
        System.out.println("检验用户名:" + userName);
    }
}
复制代码
  1. 结果


3.2 CGLIB代理

同样实现CGLIB必备的两个基础知识

  1. MethodInterceptor 的 intercept() 方法
  2. Enhancer 类

自定义 MethodInterceptor 并重写 intercept 方法,intercept 用于拦截增强被代理类的方法

public interface MethodInterceptor extends Callback{ 
    // 拦截被代理类中的方法 
    public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args,MethodProxy proxy) throws Throwable;
}
复制代码

该方法有四个参数分别是:

  1. obj : 被代理的对象(需要增强的对象)
  2. method : 被拦截的方法(需要增强的方法)
  3. args : 方法入参
  4. proxy : 用于调用原始方法

通过 Enhancer 类来动态获取被代理类,实际调用的还是 MethodInterceptor 中的 intercept() 方法


接下来动手实现一下CGLIB代理

步骤

  1. 定义一个类;
  2. 自定义 MethodInterceptor 并重写 intercept 方法,intercept 用于拦截增强被代理类的方法,和 JDK 动态代理中的 invoke 方法类似;
  3. 通过 Enhancer 类的 create()创建代理类;

代码演示

  1. 引入Jar包
<dependency>
  <groupId>cglib</groupId>
  <artifactId>cglib</artifactId>
  <version>3.3.0</version>
</dependency>
复制代码
  1. 目标类
/**
 * 学生服务类(被代理类)
 * @author 兴趣使然的L 2022/12/29
 */
public class IStudentService {
    public String sayHello(String msg){
        System.out.println("say:" + msg);
        return msg;
    }
}
复制代码
  1. 自定义方法拦截器
/**
 * 自定义方法拦截器
 * @author 兴趣使然的L 2022/12/29
 */
public class CGLIBMethodInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        // 可以在调用目标方法前做一些增强(增强方法)
        System.out.println("调用目标方法前增强代码");
        // 注意 methodProxy调用的invokeSuper方法 才是回调原生方法的操作
        Object res = methodProxy.invokeSuper(o, args);
        // 可以在调用后也做一些增强
        System.out.println("调用目标方法后增强代码");
        return res;
    }
}
复制代码
  1. 获取代理类实例的类
/**
 * 获取代理类实例的工厂类
 * @author 兴趣使然的L 2022/12/29
 */
public class CBLIBProxy {
    public static Object getProxy(Class<?> clazz) {
        // 创建动态代理增强类
        Enhancer enhancer = new Enhancer();
        // 设置类加载器
        enhancer.setClassLoader(clazz.getClassLoader());
        // 设置被代理类
        enhancer.setSuperclass(clazz);
        // 设置方法拦截器
        enhancer.setCallback(new CGLIBMethodInterceptor());
        // 创建代理类
        return enhancer.create();
    }
}
复制代码
  1. 测试
/**
 * 测试
 * @author 兴趣使然的L 2022/12/29
 */
public class ProxyTest {

    public static void main(String[] args) {
        // 获取代理类对象(实例)
        IStudentService proxy = (IStudentService) CBLIBProxy.getProxy(IStudentService.class);
        String say = proxy.sayHello("hello");
        System.out.println("检验:" + say);
    }
}
复制代码
  1. 结果

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值