Java AOP利剑之动态代理,从不一样的角度编程

Java静态代理和动态代理以及AOP编程

Posted by Anriku on March 23, 2019

看了下日期才发现我是真的好久没写博客。最近几个月来实习发现好好沉淀写博客的时间都少了。其实想了想还是时间管理上有问题,于是乎立下了一个大大的Flag,以后每周更新一两篇博客。废话就不多说了。

正题上来,今天主要想搞清楚两件事:

  • 动态代理和静态代理的区别、优缺点。
  • 使用动态代理实现AOP编程。

整体代码结构

整个场景是一个通过国外电脑作为VPN代理上网的场景。这里主要把国外电脑分为了两类个人电脑、服务器。它们属于不同类型的VPN,继承于不同的接口,个人电脑继承于PC接口,服务器继承于Service,它们都可以作为VPN

然后还有一点需要注意的是为了好理解,这里ShadowSocks所有类型的国外电脑用一个(就当使用Java实现的跨平台的吧),而且只有服务端有。

主要分为了三大块:

  • common:动态代理和静态代理都会用到的内容
  • staticproxy: 静态代理独有代码
  • dynamicproxy: 动态代理独有代码

common模块

PC子模块

PC接口:

package common.pc;

/**
 * 代表一台个人电脑
 */
public interface PC {
    void searchByGoogle();
}

Linux PC实现:

package common.pc;

/**
 * Linux系统的个人电脑
 */
public class LinuxPC implements PC {
    @Override
    public void searchByGoogle() {
        System.out.println("Searching with Google by LinuxPC");
    }
}

Service子模块

Service接口:

package common.service;

/**
 * 代表一台服务器
 */
public interface Service {
    void searchByGoogle();
}

Linux Service实现:

package common.service;

/**
 * 一台Linux服务器
 */
public class LinuxService implements Service {
    @Override
    public void searchByGoogle() {
        System.out.println("Searching with Google by LinuxService");
    }
}

ShadowSocks

package common;

/**
 * 现实中ShadowSocks是要分平台的,这里为了方便就想成全平台用一个ShadowSocks。总之就想成服务端实现代理的一个软件吧。
 * 而且现实中不仅服务端需要安装ShadowSocks,客户端也是需要的。这里客户端也忽略掉。
 */
public class ShadowSocks {

    public void startProxy() {
        System.out.println("start proxy");
    }

    public void stopProxy() {
        System.out.println("stop proxy");
    }
}

都没啥好说的没有类都有自己独有的职责。意思结合场景应该很好明白。

静态代理

上面在common包中,已经有了一个Linux PC、Linux Service还有就是跨平台的ShadowSocks了。要进行带上翻墙的话,就需要PC或者Service使用ShadowSocks来组件VPN了。因此VPN就是安装了ShadowSocks进行代理的PC或者Service了。有了思路就看下代码吧~

PC VPN

package staticproxy;

import common.ShadowSocks;
import common.pc.PC;

/**
 * 代表由一台国外PC搭建的VPN
 */
public class VPNByPC implements PC {

    private PC PC;

    private ShadowSocks shadowSocks;

    public VPNByPC(PC PC) {
        this.PC = PC;
        shadowSocks = new ShadowSocks();
    }

    @Override
    public void searchByGoogle() {
        shadowSocks.startProxy();
        PC.searchByGoogle();
        shadowSocks.stopProxy();
    }
}

Service VPN

package staticproxy;

import common.ShadowSocks;
import common.service.Service;

/**
 * 一台由国外服务器搭建的VPN
 */
public class VPNByService implements Service {

    private Service service;
    private ShadowSocks shadowSocks;


    public VPNByService(Service service) {
        this.service = service;
        shadowSocks = new ShadowSocks();
    }


    @Override
    public void searchByGoogle() {
        shadowSocks.startProxy();
        service.searchByGoogle();
        shadowSocks.stopProxy();
    }
}

可以看到上面PC VPN会直接代理给对应的PC进行上网;Service也一样会直接代理给对应的Service上网。

客户端通过搭建好的VPN上网

package staticproxy;

import common.pc.LinuxPC;
import common.service.LinuxService;

/**
 * 静态代理上网客户端
 */
public class StaticProxyClient {

    public static void main(String[] args) {
        // 使用Linux PC作为VPN代理上网
        VPNByPC vpnByPC = new VPNByPC(new LinuxPC());
        vpnByPC.searchByGoogle();

        // 使用Linux Service作为VPN代理上网
        VPNByService vpnByService = new VPNByService(new LinuxService());
        vpnByService.searchByGoogle();
    }
}

这就是静态代理的实现,应该很简单,就是一个类把自己要做的事情委托给另外的类实际的去做,代理类就相当于一个中介者。

动态代理

Java的动态代理是通过Proxy类来进行实现的。Proxy动态生成的类的所有方法都会通过InvocationHandler的继承类的invoke方法来进行处理。因此实现动态代理的关键就是如何定义好InvocationHandler的继承类。

VPNHandler

package dynamicproxy;

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


/**
 * foreignFacility表示一台国外设备,这里可以是PC或者是Service。
 * shadowSocks代理上网软件。
 *
 * 可以看到在这里对PC和Service一起进行了ShadowSocks安装操作,然后生成不同VPN的任务就交给Proxy动态生成     	* 了。
 * 这就不同像静态代理一样写两个不同的VPN出来了。
 */
public class VPNHandler implements InvocationHandler {

    private ShadowSocks shadowSocks;
    private Object foreignFacility;

    public VPNHandler(Object foreignFacility) {
        this.foreignFacility = foreignFacility;
        shadowSocks = new ShadowSocks();
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        shadowSocks.startProxy();
        Object result = method.invoke(foreignFacility, args);
        shadowSocks.stopProxy();
        return result;
    }
}

可以看到在这里对PC和Service一起进行了ShadowSocks安装操作,然后生成不同VPN的任务就交给Proxy动态生成了。这就不同像静态代理一样写两个不同的VPN出来了。

客户端代理上网

package dynamicproxy;

import common.pc.LinuxPC;
import common.pc.PC;
import common.service.LinuxService;
import common.service.Service;

import java.lang.reflect.Proxy;

/**
 * 动态代理上网的客户端
 */
public class DynamicProxyClient {

    public static void main(String[] args) {
        // 使用Linux PC作为VPN代理上网
        PC vpnByPC = (PC) Proxy.newProxyInstance(PC.class.getClassLoader(),
                new Class[]{PC.class}, new VPNHandler(new LinuxPC()));
        vpnByPC.searchByGoogle();

        // 使用Linux Service作为VPN代理上网
        Service vpnByService = (Service) 
            Proxy.newProxyInstance(Service.class.getClassLoader(),
                new Class[]{Service.class}, new VPNHandler(new LinuxService()));
        vpnByService.searchByGoogle();
    }
}

这里就通过Proxy类动态生成了PC搭建的VPN和Service搭建的VPN来进行代理上网了。

动态代理和静态代理的区别和优缺点

区别

  • 静态代理的类需要自己写并完成在代理类中完成代理逻辑。
  • 动态代理的类是通过Proxy动态生成的。代理逻辑写在InvocationHandler继承类中。
  • 动态代理适用于单一场景,换句话说就是适用于添加功能的操作只存在在一个接口继承的类中。今天例子就不适合静态代理。
  • 动态代理就适用于复杂的场景,也就是不同接口继承的类都需要添加共有的功能的情况。比如上面PC和Service的继承类就是不同类型的类。通过动态代理可以明显看到类数量是减少了一个的那么如果是有更多类型的类都需要添加相同的功能呢?可想而知动态代理会大大减少代码编写的成本。

静态代理优缺点

优点

  • 简单明了
  • 没有反射,效率高。

缺点

  • 只适合单一场景。如果想要为不同接口的继承类(不同类型的类)添加相同的功能。不得不生成不同类型的类代理类。

动态代理的优缺点

优点

  • 擅长应付复杂的场景,在对不同接口继承类(不同类型的类)添加相同的功能。只需要在InvocationHandler继承类中进行公共的实现。然后通过Proxy动态生成不同的代理类。

缺点

  • 代码可读性没有静态代理高
  • 使用了反射,效率更低

AOP编程

其实如果理解上面的动态代理AOP概念的理解其实很简单。

平常常说的OOP编程是把不同的功能交给了不同的类来完成。上面的静态代理就是一种OOP编程的思想:我们有两个功能要实现:给PC安装ShadowSocks搭建VPN和给Service安装ShadowSocks搭建VPN。这时候就需要生成不同的类来完成两个功能了。

但是对于AOP编程就不一样了,仔细想一想这里给PC和Service要添加的功能是完全一样的。我们把生成不同类比作纵向,那么AOP要给这些类添加相同的功能就相当于从横向来一次性给所有这些类添加上这个相关的功能。

下面画了一幅图来进行说明,这里只是从我们编程层面上的图。从原理上面来说的话,动态代理还是像静态代理那样生成了不同类来实现不同类型的类相同功能的添加。

总结

这篇博客主要介绍了静态代理和动态代理。上面的区别和优缺点都说了,这里就不重复了。

然后介绍一个重要的思想AOP编程。用一句话来说就是从横向给不同类型的类添加相同功能。这篇博客的AOP是通过Proxy静态代理来实现的。

下一篇博客将介绍通过ASM从字节码层面上来实现AOP编程,其实那才是真正的AOP。敬请期待吧~

转载请注明链接地址