自学内容网 自学内容网

Java反射、静态代理、动态代理

往期推荐

Java io模型-CSDN博客

如何设计一个能根据任务优先级来执行的线程池-CSDN博客

Web实时消息推送的几种方案_setmessageinnerhtml is not defined-CSDN博客

yum、dnf、apt包管理工具-CSDN博客

概述

反射机制是在运行状态中,对于任意一个类,都能够知道这个类中的所有属性和方法,对于任意一个对象,都能够调用它的任意一个方法和属性,这种动态获取的信息以及动态调用对象的方法的功能称为Java语言的反射机制。

Spring、mybatis、动态代理、注解都是使用了反射。

优点:可以让咱们的代码更加灵活、为各种框架提供开箱即用的功能提供了便利。

缺点:让我们在运行时有了分析操作类的能力,这同样也增加了安全问题。比如可以无视泛型参数的安全检查(泛型参数的安全检查发生在编译时)。另外,反射的性能也要稍差点,不过,对于框架来说实际是影响不大的。

获取Class对象的4种方式 

如果我们动态获取到这些信息,我们需要依靠 Class 对象。Class 类对象将一个类的方法、变量等信息告诉运行的程序。

  • 知道具体类名,直接类名.class
  • 通过对象实例instance.getClass()获取
  • 通过 Class.forName()传入类的全路径获取
  • 通过类加载器xxxClassLoader.loadClass()传入类路径获取
//定义反射类
public class TargetObject {
    private String value;

    public TargetObject() {
        value = "QingQiu";
    }

    public void publicMethod(String s) {
        System.out.println("I love " + s);
    }

    private void privateMethod() {
        System.out.println("value is " + value);
    }
}


//使用反射操作上面的类方法及属性
public class Main {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException, NoSuchFieldException {
        /**
         * 获取 TargetObject 类的 Class 对象并且创建 TargetObject 类实例
         */
        Class<?> targetClass = Class.forName("com.serein.TargetObject");
        TargetObject targetObject = (TargetObject) targetClass.newInstance();
        /**
         * 获取 TargetObject 类中定义的所有方法
         */
        Method[] methods = targetClass.getDeclaredMethods();
        for (Method method : methods) {
            System.out.println(method.getName());
        }

        /**
         * 获取指定方法并调用
         */
        Method publicMethod = targetClass.getDeclaredMethod("publicMethod",
                String.class);

        publicMethod.invoke(targetObject, "QingQiu");

        /**
         * 获取指定参数并对参数进行修改
         */
        Field field = targetClass.getDeclaredField("value");
        //为了对类中的参数进行修改我们取消安全检查
        field.setAccessible(true);
        field.set(targetObject, "serein");

        /**
         * 调用 private 方法
         */
        Method privateMethod = targetClass.getDeclaredMethod("privateMethod");
        //为了调用private方法我们取消安全检查
        privateMethod.setAccessible(true);
        privateMethod.invoke(targetObject);
    }
}

常见的反射应用场景

  • 加载数据库驱动 ,Class.forName(com.mysql.cj.jdbc.Driver)
  • 加载配置文件,Spring通过xml装载Bean的过程:
    • 将xml配置文件加载入内存
    • java类里面解析xml的内容,得到对应实体类的字节码字符串以及相关的属性信息
    • 使用反射机制,根据这个字符串获得某个类的Class实例动态配置实例的属性

代理

通过代理对象来代替对真实对象的访问,这样就可以在不修改原目标对象的前提下,提供额外的功能操作,扩展目标对象的功能。比喻:活动方要请明星出席,不会直接去找明星(真实对象),而是去找其经纪人(代理对象)

静态代理

静态代理实现步骤

  1. 定义一个接口及其实现类
  2. 创建一个代理类同样实现这个接口
  3. 将目标对象注入进代理类,然后在代理类的对应方法调用目标类中的对应方法。这样的话,我们就可以通过代理类屏蔽对目标对象的访问,并且可以在目标方法执行前后做一些自己想做的事情。
代码演示 
//定义发送短信的接口
public interface SmsService {
    String send(String message);
}


//实现发送短信的接口
public class SmsServiceImpl implements SmsService {
    public String send(String message) {
        System.out.println("send message:" + message);
        return message;
    }
}


//创建代理类并同样实现发送短信的接口
public class SmsProxy implements SmsService {

    private final SmsService smsService;

    public SmsProxy(SmsService smsService) {
        this.smsService = smsService;
    }

    @Override
    public String send(String message) {
        //调用方法之前,我们可以添加自己的操作
        System.out.println("before method send()");
        smsService.send(message);
        //调用方法之后,我们同样可以添加自己的操作
        System.out.println("after method send()");
        return null;
    }
}


//实际使用
public class Main {
    public static void main(String[] args) {
        SmsService smsService = new SmsServiceImpl();
        SmsProxy smsProxy = new SmsProxy(smsService);
        smsProxy.send("java");
    }
}

动态代理

JDK动态代理

在 Java 动态代理机制中 InvocationHandler 接口和 Proxy 类是核心。

Proxy 类中使用频率最高的方法是:newProxyInstance() ,这个方法主要用来生成一个代理对象。

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

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




//这个私有方法通常是在实现代理类时,由 JVM 或框架内部的机制调用,来处理更复杂的代理类生成逻辑。
private static Object newProxyInstance(Class<?> caller,Constructor<?> cons,
InvocationHandler h)

要实现动态代理的话,还必须需要实现InvocationHandler 来自定义处理逻辑。 当我们的动态代理对象调用一个方法时,这个方法的调用就会被转发到实现InvocationHandler 接口类的 invoke 方法来调用。

public interface InvocationHandler {

    /**
     * 当你使用代理对象调用方法的时候实际会调用到这个方法
     */
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;

    // proxy :动态生成的代理类
    // method : 与代理类对象调用的方法相对应
    // args : 当前 method 方法的参数
}
JDK动态代理实现步骤
  • 定义一个接口及其实现类;
  • 自定义 InvocationHandler 并重写invoke方法,在 invoke 方法中我们会调用原生方法(目标类的方法)并自定义一些处理逻辑;
  • 通过 Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) 方法创建代理对象;
代码演示 
//定义发送短信的接口
public interface SmsService {
    String send(String message);
}

//实现发送短信的接口
public class SmsServiceImpl implements SmsService {
    public String send(String message) {
        System.out.println("send message:" + message);
        return message;
    }
}


//定义JDK动态代理类
public class DebugInvocationHandler implements InvocationHandler {
    /**
     * 代理类中的真实对象
     */
    private final Object target;

    public DebugInvocationHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
        //调用方法之前,我们可以添加自己的操作
        System.out.println("before method " + method.getName());
        //当我们的动态代理对象调用原生方法的时候,最终实际上调用到的是 invoke() 方法,然后 invoke() 方法代替我们去调用了被代理对象的原生方法。
        Object result = method.invoke(target, args);
        //调用方法之后,我们同样可以添加自己的操作
        System.out.println("after method " + method.getName());
        return result;
    }
}


//获取代理对象的工厂类
public class JdkProxyFactory {
    public static Object getProxy(Object target) {
        return Proxy.newProxyInstance(
                target.getClass().getClassLoader(), // 目标类的类加载器
                target.getClass().getInterfaces(),  // 代理需要实现的接口,可指定多个
                new DebugInvocationHandler(target)   // 代理对象对应的自定义 InvocationHandler
        );
    }
}


//实际使用
SmsService smsService = (SmsService) JdkProxyFactory.getProxy(new SmsServiceImpl());
smsService.send("java");

cglib动态代理

JDK动态代理通过实现接口实现代理,而CGLIB 通过继承方式实现代理。很多知名的开源框架都使用到了CGLIB, 例如 Spring 中的 AOP 模块中:如果目标对象实现了接口,则默认采用 JDK 动态代理,否则采用 CGLIB 动态代理。

在 CGLIB 动态代理机制中 MethodInterceptor 接口和 Enhancer 类是核心。

自定义 MethodInterceptor 并重写 intercept 方法,intercept 用于拦截增强被代理类的方法。Enhancer类来动态获取被代理类,当代理类调用方法的时候,实际调用的是 MethodInterceptor 中的 intercept 方法。

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

public interface MethodInterceptor
extends Callback{
    // 拦截被代理类中的方法
    public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args,MethodProxy proxy) throws Throwable;
}
CGLIB动态代理类使用步骤
  1. 定义一个类;
  2. 自定义 MethodInterceptor 并重写 intercept 方法,intercept 用于拦截增强被代理类的方法,和 JDK 动态代理中的 invoke 方法类似;
  3. 通过 Enhancer 类的 create()创建代理类;
代码演示 
<dependency>
  <groupId>cglib</groupId>
  <artifactId>cglib</artifactId>
  <version>3.3.0</version>
</dependency>


//实现一个使用阿里云发送短信的类
public class AliSmsService {
    public String send(String message) {
        System.out.println("send message:" + message);
        return message;
    }
}



//自定义 MethodInterceptor(方法拦截器)
public class DebugMethodInterceptor implements MethodInterceptor {

    /**
     * @param o           被代理的对象(需要增强的对象)
     * @param method      被拦截的方法(需要增强的方法)
     * @param args        方法入参
     * @param methodProxy 用于调用原始方法
     */
    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        //调用方法之前,我们可以添加自己的操作
        System.out.println("before method " + method.getName());
        Object object = methodProxy.invokeSuper(o, args);
        //调用方法之后,我们同样可以添加自己的操作
        System.out.println("after method " + method.getName());
        return object;
    }

}


//获取代理类
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();
    }
}


//实际使用
AliSmsService aliSmsService = (AliSmsService) CglibProxyFactory.getProxy(AliSmsService.class);
aliSmsService.send("java");

看到这里似乎发现cglib也是实现了一个接口重写intercept()来增强的,那么继承体现在哪里呢?其实在使用 CGLIB 进行代理时,CGLIB 会通过继承目标类,生成一个新的子类 Target$Proxy,并重写目标类的非 final 方法。CGLIB 会在这些重写的方法中加入代理逻辑。

静态代理和动态代理对比

  • 灵活性:动态代理更加灵活,不需要必须实现接口,可以直接代理实现类(cglib),并且可以不需要针对每个目标类都创建一个代理类。另外,静态代理中,接口一旦新增加方法,目标对象和代理对象都要进行修改,这是非常麻烦的!
  • JVM 层面:静态代理在编译时就将接口、实现类、代理类这些都变成了一个个实际的 class 文件。而动态代理是在运行时动态生成类字节码,并加载到 JVM 中的。

JDK代理和CGLIB代理对比

  • JDK 动态代理只能代理实现了接口的类或者直接代理接口,而 CGLIB 可以代理未实现任何接口的类。 另外, CGLIB 动态代理是通过生成一个被代理类的子类来拦截被代理类的方法调用,因此不能代理声明为 final 类型的类和方法。
  • 就二者的效率来说,大部分情况都是 JDK 动态代理更优秀,随着 JDK 版本的升级,这个优势更加明显

原文地址:https://blog.csdn.net/qq_73181349/article/details/144810289

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!