JAVA设计模式 - 代理模式

8 篇文章 0 订阅
6 篇文章 0 订阅

在对象的一个业务方法完成之后, 有时候我们可能需要去添加一些新的功能(前置校验等). 但我们又不想更改原来的代码 , 代理模式就为我们提供了一种解决方案 .

1 . 代理模式的定义

代理模式就是在不改变原有代码的基础上 , 实现对目标对象的功能扩展 .

以现实的情况为例 , 目前有一个艺人, 她会表演和唱歌 . 现在想扩展一个行为来为这个艺人来接通告 , 通常情况下我们是不想把这个业务交给原来的艺人去做的, 毕竟也不是她的职责 . 这时候我们就可以引入一个代理对象 , 即经纪人 . 经纪人去来完成收发通告的一些扩展业务 , 但需要表演时 , 仍然要经纪人(代理对象)通知艺人(目标对象)来完成功能 . 

代理模式分为静态代理 , 动态代理(根据实现方式又分为JDK动态代理和CGLIB动态代理) . 

2 . 代理模式的特点

不影响原有业务代码实现新功能扩展

3 . 代理模式的主要角色

  • 抽象接口 : 即目标对象实现的接口 , 在静态代理和JDK代理中是必备角色
  • 目标对象 : 就是实现了具体业务的类 , 在代理模式下仍然需要目标对象来完成原本业务
  • 代理对象 : 目标对象的代理类 , 对目标对象的原有业务增加扩展操作

4 . 代理模式的应用场景

最经典的就是Spring中的AOP , 它的原理就是通过动态代理来完成

5 . 代码实现

5.1 静态代理

静态代理就是通过实现与目标相同的接口 , 并注入原本的代理目标对象 . 在重写的方法中完成扩展 , 并调用目标对象来实现原有操作

package com.xbz.xstudy.shejimoshi.proxy.targetObject;

/**
 * @title 需要代理的目标接口(艺人)
 * @author Xingbz
 * @createDate 2019-4-23
 */
public interface ActorInterface {
    void show();
}
package com.xbz.xstudy.shejimoshi.proxy.targetObject;

/**
 * @title 需要代理的目标实现对象(艺人)
 * @author Xingbz
 * @createDate 2019-4-23
 */
public class ActorInterfaceImpl implements ActorInterface {

    @Override
    public void show() {
        System.out.println("艺人完成了一场表演 ~ ");
    }
}
package com.xbz.xstudy.shejimoshi.proxy.staticState;

import com.xbz.xstudy.shejimoshi.proxy.targetObject.ActorInterface;

/**
 * @title 静态代理对象(经纪人)
 * @author Xingbz
 * @createDate 2019-4-23
 */
public class ActorProxy implements ActorInterface {

    private ActorInterface actor;

    public ActorProxy(ActorInterface actor) {
        this.actor = actor;
    }

    @Override
    public void show() {
        System.out.println("[静态代理]经纪人为艺人接了通告");
        actor.show();//通知艺人自身来完成行为
        System.out.println("[静态代理]经纪人为艺人完成通告");
    }
}
package com.xbz.xstudy.shejimoshi.proxy.staticState;

import com.xbz.xstudy.shejimoshi.proxy.targetObject.ActorInterface;
import com.xbz.xstudy.shejimoshi.proxy.targetObject.ActorInterfaceImpl;

/**
 * @title 静态代理测试类
 * @author Xingbz
 * @createDate 2019-4-22
 */
public class Demo {
    public static void main(String[] args){
        ActorInterface actor = new ActorInterfaceImpl();
        actor.show();//正常执行方法

        ActorInterface actorProxy = new ActorProxy(actor);
        actorProxy.show();//实际还是actor自身的操作, 但在方法执行前后会有一些经纪人的代理操作
    }
}

静态代理的优点 : 可以做到不对目标对象进行任何修改的前提下 , 将目标对象实现功能扩展

静态代理的缺点 : 因为代理对象需要实现与目标对象一样的接口 , 会导致代理类非常繁多 , 不易维护 . 并且如果接口增加方法 , 目标对象和代理对象都需要变动

5.2 JDK动态代理

JDK动态代理就是指动态的在内存中创建代理对象 . 不用实现任何借口 , 通过传入的目标对象和指定接口 , 利用反射调用JDK API动态的创建代理类

package com.xbz.xstudy.shejimoshi.proxy.dynamic.jdk;

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

/**
 * @title 动态代理Handler
 * @author Xingbz
 * @createDate 2019-4-23
 */
public class ProxyHandler implements InvocationHandler {

    private Object targetObject;//用于接收代理目标实际对象

    public ProxyHandler(Object targetObject) {
        this.targetObject = targetObject;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("[JDK动态代理]经纪人为艺人接了通告");
        method.invoke(targetObject, args);//通知艺人自身来完成行为
        System.out.println("[JDK动态代理]经纪人为艺人完成通告");
        return proxy;
    }
}
package com.xbz.xstudy.shejimoshi.proxy.dynamic.jdk;

import com.xbz.xstudy.shejimoshi.proxy.ProxyClassOutUtil;
import com.xbz.xstudy.shejimoshi.proxy.targetObject.ActorInterface;
import com.xbz.xstudy.shejimoshi.proxy.targetObject.ActorInterfaceImpl;

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

/**
 * @title JDK动态代理测试类
 * @author Xingbz
 * @createDate 2019-4-23
 */
public class Demo {
    public static void main(String[] args){
        ActorInterface actor = new ActorInterfaceImpl();

        InvocationHandler actorHandler= new ProxyHandler(actor);

        ActorInterface actorProxy = (ActorInterface) Proxy.newProxyInstance(ActorInterface.class.getClassLoader(), new Class[]{ActorInterface.class}, actorHandler);

        actorProxy.show();
    }
}

 JDK动态代理的优点 : 无需实现接口 , 免去编写很多代理类的繁琐

JDK动态代理的缺点 : 代理对象不需实现接口 , 但目标对象必须实现接口 , 如果目标对象没有接口的话就不能使用JDK动态代理了

5.3 CGLIB动态代理

cglib是通过扫描该类及其父类中的所有public方法 , 通过asm动态生成该类的子类字节码 . 在子类中重写了该类的所有方法 , 加入扩展逻辑 , 然后返回该子类的实例作为代理类 . 即是说 , cglib是通过该类的子类作为代理类来实现代理操作的 , 所以代理目标中的非public方法及static/final修饰的方法 , 不能实现代理 . 

asm是一个小而快的字节码处理框架 , 它负责生成从代理目标类中扫描出的方法字节码 , 并将这些字节码暂存在内存中 . 然后我们通过某种方式将这些字节码转换为class,最勇利用反射创建代理类的实例返回 . 

使用该方法需要引入cglib和asm依赖 , spring已经内嵌了这两个jar包 . 如果项目中有用到spring则无需再引入 . 

package com.xbz.xstudy.shejimoshi.proxy.targetObject;

/**
 * @title 未实现接口的代理目标对象(主播)
 * @author Xingbz
 * @createDate 2019-4-23
 */
public class Anchor {
    public void sing(String song) {
        System.out.println("主播唱了一首歌 : " + song);
    }
}
package com.xbz.xstudy.shejimoshi.proxy.dynamic.cglib;

import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * @title CGLIB动态代理Inerceptor
 * @description
 * @author Xingbz
 * @createDate 2019-4-23
 */
public class MyMethodInerceptor implements MethodInterceptor {

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("[CGLIB动态代理]助理为主播选了一首歌");
        Object result = methodProxy.invokeSuper(o, objects);
        System.out.println("[CGLIB动态代理]助理带头为主播刷礼物");
        return result;
    }
}
package com.xbz.xstudy.shejimoshi.proxy.dynamic.cglib;

import com.xbz.xstudy.shejimoshi.proxy.ProxyClassOutUtil;
import com.xbz.xstudy.shejimoshi.proxy.targetObject.Anchor;
import org.springframework.cglib.proxy.Enhancer;

/**
 * @title CGLIB动态代理测试类
 * @author Xingbz
 * @createDate 2019-4-23
 */
public class Demo {
    public static void main(String[] args){
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Anchor.class);
        enhancer.setCallback(new MyMethodInerceptor());

        Anchor anchor = (Anchor) enhancer.create();
        anchor.sing("今天是个好日子");
    }
}

CGLIB动态代理的优点 : 无需目标对象实现接口

CGLIB动态代理的缺点 : 非public , 或statsi/final修饰的方法无法被代理

 6 . 总结

三种代理各有优缺点和适用范围 , 主要看目标是否实现了接口 . 在Spring框架的AOP中 , 如果纳入容器的bean有实现接口 , 则用JDK代理 . 如果没有实现接口 , 则用CGLIB代理

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值