5

由反射引出的Java动态代理与静态代理 - JavaBuild

 7 months ago
source link: https://www.cnblogs.com/JavaBuild/p/18004316
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

《深入剖析Java中的反射,由浅入深,层层剥离!》这篇文章中我们讲反射时,曾提到过Java的动态代理中使用了反射技术,那么好,今天我们要就着反射的索引,来学习一下Java中的代理!

在Java中有多达23种的设计模式(后面Java基础更新完后,会找个时间详细的去写写这些设计模式),恰当的设计模式的使用能够提升代码的效率,简化代码的复杂性。

而今天我们要说的代理模式就是其中之一,所谓代理是为某对象提供一种代理以控制对该对象的访问。即客户端通过代理间接地访问该对象,从而限制、增强或修改该对象的一些特性。

大白话:买房的(客户方),房产销售(代理方),卖房的(委托方)

3271023-20240203075803620-1977490342.png

在Java中有静态代理和动态代理两种实现方式,继续放下看!!!

所谓静态代理,一般是针对编译期就已经完成了接口,实现类,代理类的定义,我们对目标对象的增强需要手工去完成,一个目标对象就要有个代理类,非常不灵活。

静态代理的实现步骤

1,因为代理类与被目标对象有相似的行为(共同),所以我们先创建一个接口。

public interface SaleHouse {
     String saleHouse();
}

2,提供接口的实现类,当做目标对象

public class SaleHouseImpl implements SaleHouse{
    @Override
    public String saleHouse() {
        return "我要卖房子啦!!!";
    }
}

3,代理类同样也要实现接口,并在目标方法前后做一些控制操作

public class SaleHouseProxy implements SaleHouse{

    private SaleHouse saleHouse;
	//提供一个包含目标对象的有参构造
    public SaleHouseProxy(SaleHouse saleHouse) {
        this.saleHouse = saleHouse;
    }

    @Override
    public String saleHouse() {
        //调用方法前,我们可以加一些自己的控制
        System.out.println("我要收代理费!!!");
        System.out.println("--------------------");
        String s = saleHouse.saleHouse();
        System.out.println(s);
        System.out.println("--------------------");
        //调用方法后,我们依旧可以操作
        System.out.println("我要拿提成!!!");
        return "这就是静态代理";
    }
}

4,客户端调用代理类,并传入目标对象

public class Test {
    public static void main(String[] args) throws FileNotFoundException {
        //客户端调用静态代理
        SaleHouse saleHouse = new SaleHouseImpl();
        SaleHouseProxy saleHouseProxy = new SaleHouseProxy(saleHouse);
        saleHouseProxy.saleHouse();
    }
}

5,控制台查看打印结果

我要收代理费!!!
--------------------
我要卖房子啦!!!
--------------------
我要拿提成!!!

其实无论是静态代理还是静态代理,在我们的日常开发中,使用的都是很多,但对于SpringAop、RPC等框架来说,动态代理发挥着相当大的作用,动态代理具有:运行时控制,灵活性更好的特点。
那怎么实现动态代理呢?
如下三种方式:

JDK 动态代理
CGLib 动态代理
使用 Spring aop 模块完成动态代理功能 //今天先不说这个

JDK动态代理

实现步骤:
1,定义一个接口及其实现类;
代码同静态代理中步骤1,步骤2;

2,自定义 InvocationHandler (调用处理器)并重写invoke方法,在 invoke 方法中我们会调用原生方法(被代理类的方法)并自定义一些处理逻辑;

public class JdkDynamicInvocationHandler implements InvocationHandler {

    //代理类中的真实对象
    private final Object target;

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

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //调用方法前,我们可以加一些自己的控制
        System.out.println("我要收代理费!!!");
        Object invoke = method.invoke(target, args);
        //调用方法后,我们依旧可以操作
        System.out.println("我要拿提成!!!");
        return invoke;
    }
}

3,通过 Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) 方法创建代理对象;其实,这一步也可以写在第2步的代码里,不过为了代码的可读性,我们进行解耦实现!
3.1,定义一个工厂类,在工厂类中通过Proxy.newProxyInstance()方法获取某个类的代理对象

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

3.2,客户端传入目标对象,实现代理扩展

  //客户端调用静态代理
SaleHouse proxySaleHouse = (SaleHouse) JdkDynamicProxyFactory.getProxy(new SaleHouseImpl());
proxySaleHouse.saleHouse();

4,控制台输出

我要收代理费!!!
我要卖房子啦!!!
我要拿提成!!!

【扩展】
关于Proxy类的静态工厂方法newProxyInstance()如何创建代理实例的过程,感兴趣的可以去读源码,或者参考下面这篇文章《代理模式在开源代码中的应用》

CGLIB 动态代理

其实在JDK动态代理中有一个弊端,那就是只能代理接口或接口的实现类,那么未实现任何接口的类就不能代理了吗?答案是否定的,因为咱们有CGLIB!

CGLIB (Code Generation Library) 是一个基于ASM的字节码生成库,它允许我们在运行时对字节码进行修改和动态生成,CGLIB 通过继承方式实现代理。

实现步骤:
1,引入cglib依赖
因为是第三方实现的动态代理,所以在使用前先引入依赖包

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>

2,定义一个类;

public class Person {
    public void eat(){
        System.out.println("我在吃饭!!!");
    }
}

3,自定义 MethodInterceptor 并重写 intercept 方法,intercept 用于拦截增强被代理类的方法,和 JDK 动态代理中的 invoke 方法类似;

public class CglibMethodInterceptor 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("饭前先洗手");
        Object object = methodProxy.invokeSuper(o, args);
        //调用方法前,我们可以加一些自己的控制
        System.out.println("饭后要擦嘴");
        return object;
    }
}

4,创建一个工厂类,用来构建代理对象,通过 Enhancer 类的 create()方法实现;

public class CglibProxyFactory {

    public static Object getProxy(Class<?> clazz) {
        // 创建动态代理增强类
        Enhancer enhancer = new Enhancer();
        // 设置类加载器
        enhancer.setClassLoader(clazz.getClassLoader());
        // 设置被代理类
        enhancer.setSuperclass(clazz);
        // 设置方法拦截器
        enhancer.setCallback(new CglibMethodInterceptor());
        // 创建代理类
        return enhancer.create();
    }
}

5、客户端调用,通过反射传入Person类信息

public static void main(String[] args) throws FileNotFoundException {
        //客户端调用静态代理
        Person person = (Person) CglibProxyFactory.getProxy(Person.class);
        person.eat();
    }
饭前先洗手
我在吃饭!!!
饭后要擦嘴

OK,终于码完了动态代理,自己还去看了很久的源码,头昏脑涨!

如果本篇博客对您有一定的帮助,大家记得留言+点赞+收藏呀。原创不易,转载请联系Build哥!

3271023-20240203075851249-295858775.png

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK