[Java基础]—动态代理

前言

有一个月没看Java了,再不学估计要忘干净了。。。。

在之前审计CommonsCollections1时,由于LazyMap需要会用到动态代理就学习了一波,但对LazyMap链和动态代理都是不怎么理解,所以又对动态代理的内部运行过程进行了一下分析,LazyMap链会在审计CC6前重新审计一遍。

代理

代理是一种常用的设计模式,其目的就是为其他对象提供一个代理以控制对某个对象的访问。代理类负责为委托类预处理消息,过滤消息并转发消息,以及进行消息被委托类执行后的后续处理。

代理可以理解为我们朋友圈中的代购,在买家与卖家间进行协调。

静态代理

要求被代理类和代理类同时实现相应的一套接口,通过代理类调用重写接口的方法,实际上调用的是原始对象的同样的方法。

实例

电影是电影公司委托给影院进行播放的,但是影院可以在播放电影时,产生一些自己的经济收益,比如卖爆米花、饮料等。

首先定义一个接口(Movie)

package Sentiment.unserialize.Poxy;

public interface Movie  {
    public void show();
}

其次,定义一个真正的实现这个 Movie接口的类(Real)

package Sentiment.unserialize.Poxy;
//委托类
class Real implements Movie{
    public void show(){
        System.out.println("您正在观看电影");
    }
}

代理类StaticPoxy,在同样调用show()方法后会进行处理(加上广告)

package Sentiment.unserialize.Poxy;
//代理类
public class StaticPoxy implements Movie{
    private Movie movie;
    public StaticPoxy(Movie movie) {
        this.movie=movie;
    }

    @Override
    public void show() {
        System.out.println("电影马上开始了,爆米花、饮料快来买啊");
        movie.show();
        System.out.println("电影已经结束了,爆米花、饮料买回家吧");
    }
}

测试类Test

package Sentiment.unserialize.Poxy;

public class Test {
    public static void main(String[] args){
        Movie test = new Real();
        System.out.println("-----无代理-----");
        test.show();
        System.out.println("-----静态代理-----");
        StaticPoxy staticPoxy = new StaticPoxy(test);
        staticPoxy.show();
    }
}

结果
在这里插入图片描述
可以看到,代理可以在不修改被代理对象的基础上,进行一些功能的扩展。但代理类和委托类类应共同实现一个接口,或者是共同继承某个类。

静态代理的优点

我们可以在不改变Real委托类源代码的情况下 ,通过StaticPoxy代理类来扩展Real委托类的功能,从而实现代理操作。

静态代理的缺点

代理类和委托类都实现了一个接口,会有很多的代码重复;除此外,当我们需要添加一个其他接口时,代理类和委托类都需要修改代码实现接口,可以说会产生很多的冗余并且需要一定的运维成本。这时就出现了一种更为方便、高效的代理方式——动态代理

动态代理

动态代理是指动态的在内存中构建代理对象(需要我们制定要代理的目标对象实现的接口类型),即利用JDK的API生成指定接口的对象,也称之为JDK代理或者接口代理。

动态代理主要涉及java.lang.reflect包下的Proxy类和InvocationHandler接口。

java.lang.reflect.Proxy

先看下java.lang.reflect.Proxy

Proxy类继承了java.io.Serializable接口,此外其中有一个newProxyInstance方法(还有很多方法,由于暂时不会用到所以没有列举),动态代理就是由该静态方法来实现的

package java.lang.reflect;

import java.lang.reflect.InvocationHandler;

public class Proxy implements java.io.Serializable {

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

}

看下参数作用

InvocationHandler

刚才有提到InvocationHandler接口,并且在newProxyInstance方法中也有用到,下面来看下该接口

InvocationHandler接口是proxy代理实例的调用处理程序实现的一个接口,每一个proxy代理实例都有一个关联的调用处理程序;在代理实例调用方法时,方法调用被编码分派到调用处理程序的invoke方法。

而该类只有一个方法也就是invoke方法,也正是这个方法决定了怎么样处理代理传递过来的方法调用。

public interface InvocationHandler {

    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}

还是看一下参数作用

  • proxy 代理对象
  • method 要调用的代理对象方法
  • args 要调用方法的参数

实例

假如进入一个商场后肯定会有很多卖酒卖烟的代理,本次就用这种方式进行实例分析

还是先定义个接口SellWine,意为卖酒

package Sentiment.unserialize.Pxoy2;

public interface SellWine {
    public void sell();
}

实现类Wine

package Sentiment.unserialize.Pxoy2;

public class Wine implements SellWine{
    public void sell(){
        System.out.println("卖酒");
    }
}

代理类DynamicPoxy

package Sentiment.unserialize.Pxoy2;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class DynamicPoxy implements InvocationHandler {
    private Object agent;

    public DynamicPoxy(Object poxy) {
        this.agent = poxy;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("销售开始 代理商是:"+this.getClass().getSimpleName());
        method.invoke(agent,args);
        System.out.println("销售结束");
        return null;
    }
}

测试类Test

package Sentiment.unserialize.Pxoy2;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

public class Test {
    public static void main(String[] args) {
        Wine wine = new Wine();
        InvocationHandler invocationHandler = new DynamicPoxy(wine);
        SellWine dynamicPoxy1 = (SellWine) Proxy.newProxyInstance(wine.getClass().getClassLoader(), wine.getClass().getInterfaces(), invocationHandler);
        dynamicPoxy1.sell();

    }
}

运行结果
在这里插入图片描述

分析
代理文件生成

在Test类中最后调用了dynamicPoxy1.sell();,按正常思路来说,他应该跳到sell()方法才对,但这里却直接跳到了DynamicPoxy类的invoke方法,所以进行了一下分析。

在分析前在Test类中加上这段话,这样在运行后就会在com.sun.proxy生成动态代理文件

System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");

在这里插入图片描述

先看下这个代理文件怎么生成的,代码量比较大看些关键代码,跟进11行的newProxyInstance()

跟进getProxyClass0()

在这里插入图片描述

之后又执行了

return proxyClassCache.get(loader, interfaces);

跟进get()

while循环的第二轮会进入get()

在这里插入图片描述

会调用ProxyClassFactory的apply方法,这里看下ProxyClassFactory作用(通过apply()->defineClass0()生成class对象)

目的:生成新的代理类的class对象。
原理:ProxyClassFactory是BiFunction接口的实现类,泛型参数为BiFunction<ClassLoader, Class<?>[], Class<?>>,表示可以传入一个类加载器和一个接口class对象的数组,通过apply方法生成一个代理类的class对象,并返回。之所以它可以生成代理类的class对象,也是因为在apply方法中调用了native的defineClass0方法。apply方法中包括了生成代理类class对象的一些预处理,如检查传入的class对象数组中的class对象是否被类加载器可见、是否是接口、生成新的代理类的类名proxyName等,最后调用了defineClass0方法来生成新的代理类的class对象,该class对象随后用于生成代理类实例。ProxyClassFactory的apply方法在proxyClassCache的二级缓存的value——工厂对象调用get方法时调用,表示通过该工厂对象返回代理类的class对象。

在这里插入图片描述

跟进apply()通过627行和634行生成路径和文件,最后通过defineClass0生成代理文件

在这里插入图片描述

流程分析

调用dynamicPoxy1.sell();时,会调用$Proxy0.class中的sell(),其中会调用h.invoke()

在这里插入图片描述

而h,在动态代理最开始部分说过就是一个InvocationHandler 对象,而我们在Test类中对该对象赋过值即:new DynamicPoxy(wine)

在这里插入图片描述

所以h.invoke就相当于时调用了DynamicPoxy类中的invoke

之后再看下method和agent的值,所以经过method.invoke(agent,args)后就调用了Wine类中的sell()方法
在这里插入图片描述

扩展

此时若我们我们想再增加一个卖酒功能,若用静态代理则需要添加接口,并修改委托类、代理类、测试类等;但动态代理则可跳过修改代理类的过程,通过添加接口来实现需求

再定义一个卖烟接口SellCigarette

package Sentiment.unserialize.Poxy;

public interface SellCigarette {
    public void sell();
}

实现类Cigarette

package Sentiment.unserialize.Poxy;

public class Cigarette implements SellCigarette{
    public void sell(){
        System.out.println("卖烟");
    }
}

此时我们的代理类已经不需要更改了,只需要在实现类Test类中加上我们要实现的功能即可

Cigarette cigarette = new Cigarette();
InvocationHandler invocationHandler2 = new DynamicPoxy(cigarette);

SellCigarette dynamicPoxy2 = (SellCigarette) Proxy.newProxyInstance(cigarette.getClass().getClassLoader(), cigarette.getClass().getInterfaces(), invocationHandler2);

dynamicPoxy2.sell();

最终Test

package Sentiment.unserialize.Pxoy2;

import Sentiment.unserialize.Poxy.Cigarette;
import Sentiment.unserialize.Poxy.SellCigarette;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

public class Test {
    public static void main(String[] args) {
        Wine wine = new Wine();
        Cigarette cigarette = new Cigarette();
        InvocationHandler invocationHandler1 = new DynamicPoxy(wine);
        InvocationHandler invocationHandler2 = new DynamicPoxy(cigarette);
        SellWine dynamicPoxy1 = (SellWine) Proxy.newProxyInstance(wine.getClass().getClassLoader(), wine.getClass().getInterfaces(), invocationHandler1);
        SellCigarette dynamicPoxy2 = (SellCigarette) Proxy.newProxyInstance(cigarette.getClass().getClassLoader(), cigarette.getClass().getInterfaces(), invocationHandler2);
        dynamicPoxy1.sell();
        System.out.println("-------------------");
        dynamicPoxy2.sell();

    }
}

运行结果
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值