从源码解析代理模式

Posted 碎格子

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了从源码解析代理模式相关的知识,希望对你有一定的参考价值。

大纲

代理模式(结构型设计模式)通过代理类去访问实现类中的方法,使用场景比如:已有接口和实现类的情况下,想要在已实现的方法基础上扩展更多的功能的场景。

代理模式里的主要类:

  1. 接口

  1. 实现类,需实现接口,用来完成真正的业务。(网上资料也叫做委托类、被代理类)

  1. 代理类,也需要实现接口,调用实现类的方法,本身不处理业务。

使用代理模式的好处:可以扩展实现类的功能,在实现类基本功能基础上增加一些额外的操作。

静态代理模式

图示:

要想做到在原来实现方法的基础上多增加功能,代理类同样也需要实现接口,并且需要持有一个实现类,在自己实现的方法中扩展功能,并且调用实现类的方法去处理真正的业务:

public class TV 
    String name;
    String model;

    public String getModel() 
        return model;
    

    public String getName() 
        return name;
    

    public void setModel(String model) 
        this.model = model;
    

    public void setName(String name) 
        this.name = name;
    

    public TV(String name,String model)
        this.name = name;
        this.model = model;
    

    @Override
    public String toString() 
        return "TV" +
            "name='" + name + '\\'' +
            ", model='" + model + '\\'' +
            '';
    

实体类TV.java

public interface ICompany 
    TV produceTV();

接口ICompany.java

实现类需要实现ICompany接口:

/**
* 实现类
*/
public class CompanyFactory implements ICompany
    @Override
    public TV produceTV() 
        System.out.println("生产商生产一台电视");
        return new TV("TCL","40寸");
    

代理类也需要实现ICompany接口:

/**
* 代理类
* 代理类和实现类都要实现接口
*/
public class TVProxy implements ICompany 
    ICompany company;

    @Override
    public TV produceTV() 
        System.out.println("代理收到一笔订单");
        if (Objects.isNull(company)) 
            company = new CompanyFactory();
        
        return company.produceTV();
    

CompanyFactory.java

运行时构建代理类,调用代理类的方法同时也调用了实体类的实现方法:

public class MainClass 
    public static void main(String[] args) 
        ICompany company = new TVProxy();
        TV tv = company.produceTV();
        System.out.println(tv.toString());
    

总结:

静态代理通过创建代理类去实现接口,在实现方法里添加额外功能,从而实现不改变原有实现方法下做到功能扩展。

动态代理模式

静态代理有什么缺点?(先别看下面内容思考一下)

静态代理的局限在于,如果接口新增了方法,则代理类必须实现该方法,而使用动态JDK代理,代理类不用实现接口。

实体类TV不变,接口增加维修方法,如果是静态代理,接口新增方法,则对应的实现类和代理类都要实现这个方法,而动态代理则在代码编写层面不用实现接口的所有方法:

图示:

接口新增repairTV方法:

public interface ICompany 
    TV produceTV();

    void repairTV(String name); //新增维修电视的方法

实现类需要实现这个方法:

public class CompanyFactory implements ICompany 
    @Override
    public TV produceTV() 
        System.out.println("生产商生产一台电视");
        return new TV("TCL", "40寸");
    

    @Override
    public void repairTV(String name) 
        System.out.println(String.format("生产商维修%s电视",name));
    

这里是动态代理类,可以看到,这里并没有实现ICompany接口了

public class TVDynamicProxy 
    Object factory;

    public TVDynamicProxy(Object factory) 
        this.factory = factory;
    

    
    public <T> T getProxy(Class<T> clazz) 
        //newProxyInstance实际上是返回了一个实现了接口的代理类实例,这个实例是存在在内存中的,动态生成的:
        return (T) Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[]clazz,
                new InvocationHandler() 
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable  //回调
                        System.out.println("动态代理执行:接收到一笔订单");
                        Object tv = method.invoke(factory, args); //这里通过反射调用了factory里实现的方法
                        System.out.println("动态代理结束");
                        return tv;
                
        );
    

TVDynamicProxy.java

上面方法中newProxyInstance实际上是返回了一个实现了接口的代理类实例,这个实例是存在在内存中的,动态生成的

public class MainClassB 
    public static void main(String[] args) 
        ICompany company = new CompanyFactory();
        //将生成的代理类写到本地:
        System.getProperties().put("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true"); //将内存中的$Proxy0.class写到本地
        ICompany company1 = new TVDynamicProxy(company).getProxy(ICompany.class);
        TV tv = company1.produceTV();
        company1.repairTV(tv.name);
    

代码运行查看结果

运行后断点可以看到,这里实际上返回了$Proxy0

$Proxy0实现了接口,并实现了接口里定义的方法。

所以先前我们说,是在代码编写层面上不用去实现接口,实际是JDK帮我们自动实现了。

public final class $Proxy0 extends Proxy implements ICompany 
    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m4;
    private static Method m0;

    public $Proxy0(InvocationHandler var1) throws  
        super(var1);
    

    public final boolean equals(Object var1) throws  
        try 
            return (Boolean)super.h.invoke(this, m1, new Object[]var1);
         catch (RuntimeException | Error var3) 
            throw var3;
         catch (Throwable var4) 
            throw new UndeclaredThrowableException(var4);
        
    

    //自动实现了接口里定义的方法
    public final TV produceTV() throws  
        try 
            return (TV)super.h.invoke(this, m3, (Object[])null); //这里回调了InvocationHandler的invoke方法
         catch (RuntimeException | Error var2) 
            throw var2;
         catch (Throwable var3) 
            throw new UndeclaredThrowableException(var3);
        
    

    public final String toString() throws  
        try 
            return (String)super.h.invoke(this, m2, (Object[])null);
         catch (RuntimeException | Error var2) 
            throw var2;
         catch (Throwable var3) 
            throw new UndeclaredThrowableException(var3);
        
    

    public final void repairTV(String var1) throws  
        try 
            super.h.invoke(this, m4, new Object[]var1);
         catch (RuntimeException | Error var3) 
            throw var3;
         catch (Throwable var4) 
            throw new UndeclaredThrowableException(var4);
        
    

    public final int hashCode() throws  
        try 
            return (Integer)super.h.invoke(this, m0, (Object[])null);
         catch (RuntimeException | Error var2) 
            throw var2;
         catch (Throwable var3) 
            throw new UndeclaredThrowableException(var3);
        
    
    ...

$Proxy0.class

这个$Proxy0生成到了我们的根目录下面

总结:

动态代理在运行后会在内存中创建一个实现了接口的代理类,通过调用代理类的实现方法,回调到InvocationHandler接口的invoke方法,通过反射拿到实现类的方法,并进行调用。

思考:

如果我新建一个工厂类,不实现ICompany接口,那是否可以代理成功?

public class FactoryB 
    public TV produceTVB() 
        System.out.println("B工厂生产电视");
        return new TV("华为电视机", "南京");
    

上面问题可以使用Cglib代理解决。有兴趣的同学可以看给出的参考链接自行拓展。

参考资料:

https://segmentfault.com/a/1190000040407024

以上是关于从源码解析代理模式的主要内容,如果未能解决你的问题,请参考以下文章

Retrofit源码解析-动态代理

Retrofit源码解析-动态代理

设计模式课程 设计模式精讲 16-5 代理模式源码解析

从源码层面解析SpringIOC容器

从Mapper到JavaBean源码层面解析ResultMap是怎么映射的

深入浅出SpringCloud原理及实战「Netflix系列之Fegin」从源码层面让你认识Feign工作流程和运作机制