Java实现静态代理、动态代理

一、代理模式

  代理模式是设计模式中的一种结构型模式,在设计模式中算比较好理解的一种模式。具体来说就是使用代理对象来代替对真实对象的访问,当我们需要新增额外功能时,不需要修改目标对象就能达到功能扩展的效果。代理模式的关键点--代理对象与目标对象,代理对象是目标对象的扩展,并会调用目标对象。

  例子:记得几年前微商很火,小明的高中同学也很多在做微商(听说已经提玛莎拉蒂了!!!),每天朋友圈都被大量的广告刷屏。这些微商,大部分都是从厂家拿货,然后自己通过社交平台宣传,走向财富自由。现实生活中我们购买产品时,很少直接自己跑去找厂家购买,一般的销售模式都是厂家委托给代理商进行销售,而顾客直接向代理商购买即可,不需要与厂家直接联系。这里的代理商就类似我们的代理对象,而厂家就类似目标对象。

  优点:1、职责清晰。 2、高扩展性。3、智能化。

  缺点:1、在客户端和目标对象之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。 2、实现代理模式需要额外的工作,有些代理模式的实现非常复杂。

  Java实现代理模式主要有静态代理和动态代理两种方式。

二、静态代理

  1、基本概念

  静态代理从它的名字我们大概就能猜出来它是什么了,即在编译时就将已经将接口,被代理类,代理类确定下来了,在程序运行之前,代理类的.class文件就已经生成了。

   静态代理的实现步骤:

  a. 定义一个接口及其实现类,即被代理类;

  b. 创建一个代理类同样实现这个接口;

  c. 将目标对象注入这个代理类,然后在代理对象的对应方法调用目标类中的对应方法,在这期间我们可以在目标方法执行前后做一些自己想做的事情。

    2、情景引入

    小明的学校附近新开了一家蛋糕店,作为一名资深舔狗,小明已经迫不及待地想去店里买个大蛋糕送给他的女神了。一进店后,看到各种精美的蛋糕,小明就已经忍不住yy女神拿到蛋糕后感谢他的样子了。最后小明忍痛花了299买了个草莓奶油蛋糕准备送给女神。晚上,在女神宿舍楼下等送蛋糕的时候,小明闲得慌,便在脑袋里复习起最近正在学的设计模式,突然想到--“唉,这个草莓奶油蛋糕的制作过程,如何结合设计模式中的代理模式用Java来实现呢?”。

    3、代码实现 

     1)首先应该有一个做蛋糕的机器,因此我们先定义一个蛋糕机器的接口CakeMachine,同时在接口内定义一个制作蛋糕的方法。

public interface CakeMachine {
  //制作蛋糕
void makeCake(); }

    2)接着我们定义一个CakeMachine接口的实现类CakeMachineImpl,也就是我们的被代理类,通过这个实现类我们能做出奶油味的蛋糕胚。

//制做奶油味蛋糕胚
public
class CakeMachineImpl implements CakeMachine { @Override public void makeCake() { System.out.println("奶油蛋糕制作完成!!!"); } }

   此时我们通过CakeMachineImpl制作的只是普通的奶油蛋糕胚,那如果我们要做草莓奶油蛋糕的话怎么办?难道专门再做一个制作草莓奶油蛋糕的机器吗?当然不是,如果这样的话,对于草莓巧克力蛋糕我们又得制作一个机器,就变得格外麻烦。此时我们通过代理模式就能很好的解决问题,通过创建代理类,帮我们实现在蛋糕制作完成之后“铺草莓”这个动作,这个代理类能对所有的CakeMachine的实现类都新增这一功能,这样我们就省去了制作多种机器的繁琐过程。

    3)创建代理类ProxyCakeMachine,并同样实现CakeMachine接口,这时候我们可以在蛋糕制作完成之后,在表面铺上美味的草莓,这样我们的草莓蛋糕就制作完成啦。

public class ProxyMachine implements CakeMachine{

    private CakeMachine cakeMachine;

    public ProxyMachine(CakeMachine cakeMachine) {
        this.cakeMachine = cakeMachine;
    }

    @Override
    public void makeCake() {
        cakeMachine.makeCake();
        System.out.println("铺上美味的草莓~~~");
        System.out.println("草莓味蛋糕完成啦!!!");
    }
}

    4)最后,当我们去蛋糕店购买时,老板使用的是代理对象进行操作。

public class CakeShop {

    public static void main(String[] args) {
        CakeMachine cakeMachine = new CakeMachineImpl();
        ProxyMachine proxyMachine = new ProxyMachine(cakeMachine);
        proxyMachine.makeCake();
        
    }
}

    运行结果:

蛋糕制作完成!!!
铺上美味的草莓~~~
草莓味蛋糕完成啦!!!

三、动态代理

  相较于静态代理,动态代理更加的灵活,我们不需要针对每个目标类都单独创建一个代理类。上面的例子中,ProxyCakeMachine类是我们自己定义好的,在程序运行前就已经编译完成,然而动态代理,代理类并不是在Java代码中定义好的,而是在运行期间根据我们在Java代码中的“指示”动态生成的。相比于静态代理,动态代理的优势在于可以很方便的对代理类的函数进行统一处理,而不用修改每个代理类中方法。

  1、JDK动态代理机制

   在Java动态代理机制中,InvocationHandler接口和Proxy是核心,上述例子用Java动态代理的实现过程如下:

   a. 新建一个类,这个类必须实现InvocationHandler接口,并重写它的invoke方法,在invoke方法中实现我们的铺草莓动作。

public class StrawberryHandler implements InvocationHandler {

    private Object object;

    public StrawberryHandler(Object object) {
        this.object = object;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object o = method.invoke(object, args);
        System.out.println("铺上美味的草莓~~~");
        System.out.println("草莓蛋糕完成啦!!!");
        return o;
    }
}

  b. 在蛋糕店类中,我们使用Proxy类的newProxyInstance静态方法生成我们的代理对象,通过这个代理对象来制作我们的草莓奶油蛋糕。

public class CakeShop {

    public static void main(String[] args) {
        CakeMachine cakeMachine = new CakeMachineImpl();
        StrawberryHandler strawberryHandler = new StrawberryHandler(cakeMachine);
        CakeMachine cakeMachine1 = (CakeMachine)Proxy.newProxyInstance(CakeMachineImpl.class.getClassLoader(),
                CakeMachineImpl.class.getInterfaces(),
                strawberryHandler);
        cakeMachine1.makeCake();
    }
}

  执行结果:

蛋糕制作完成!!!
铺上美味的草莓~~~
草莓蛋糕完成啦!!!

    newProxyInstance()方法的三个参数:

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

    1)loader:类加载器,用于加载代理对象;

     2)interface:被代理类实现的一些接口;

     3)h:实现了InvocationHandler接口的对象。

  注意:使用动态代理,我们的“铺草莓”动作,即InvocationHandler,不仅能用在蛋糕机器接口CakeMachine的实现类,也能用于其他的接口实现类,如面包机器接口、饼干机器接口实现类。而在静态代理中,我们的静态代理类ProxyMachine只能对蛋糕机器接口的实现类起作用,若要实现在面包上铺草莓的动作,则需要另外创建面包机器的代理类。

  2、CGLIB实现动态代理

  JDK动态代理有一个最致命的问题,就是只能代理实现了接口的类,对于没有实现接口的类,JDK动态代理则无能为力。而CGLIB则可以为我们解决这个问题。(Spring的AOP模块中,如果目标对象实现了接口,则默认采用 JDK 动态代理,否则采用 CGLIB 动态代理)

  在CGLIB动态代理中,MethodInterceptor接口和Enhancer类是核心。具体步骤如下:

  1)定义一个类;

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

  3)通过 Enhancer 类的 create()创建代理类;

  CGLIB实现制作草莓奶油蛋糕:

  a. 在pom.xml中加入CGLIB的依赖坐标。

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

  b. 自定义MethodInterceptor,并重写Interceptor方法。

public class StrawberryInterceptor implements MethodInterceptor {

    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        Object o1 = methodProxy.invokeSuper(o, objects);
        System.out.println("铺上美味的草莓~~~");
        System.out.println("草莓蛋糕完成啦!!!");
        return o1;
    }
}

  MethodInterceptor接口参数解析:

public interface MethodInterceptor
extends Callback{
    // 拦截被代理类中的方法
    public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args,
                               MethodProxy proxy) throws Throwable;
}

  1)obj :被代理的对象(需要增强的对象)

  2)method :被拦截的方法(需要增强的方法)

  3)args :方法入参

  4)methodProxy :用于调用原始方法

  c. 蛋糕商店类使用enhancer获取代理类

public class CakeShop {

    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(CakeMachineImpl.class);
        enhancer.setCallback(new StrawberryInterceptor());
        CakeMachineImpl cakeMachine = (CakeMachineImpl) enhancer.create();
        cakeMachine.makeCake();
    }
}

  执行结果:

蛋糕制作完成!!!
铺上美味的草莓~~~
草莓蛋糕完成啦!!!

----------------

nice,小明很开心前几天学的设计模式还没有忘记。可女神就是迟迟没下楼,明明是炎炎夏日,小明心里却凉飕飕的。

  

原文地址:https://www.cnblogs.com/wyy11/p/14674996.html