Java设计模式之代理模式

301 阅读6分钟

Java代理模式

一、什么是代理模式?

代理模式是对象的结构模式。 代理模式(Proxy)就是给某个对象提供一个代理对象,通过代理对象控制对原对象的引用。

这样可以在原对象的基础上增强格外的功能。

生活中处处都是代理。 举个例子:某明星可以去拍电影,唱歌,但不能什么事都自己干,所以他/她请了个经纪人替他排期,给明星安排什么时候拍戏,什么时候录歌。(可能不太恰当~~)

二、静态代理

  • 身为明星,唱歌演戏不能少,所以有一个接口Star
public interface Star {
    void makingMovie();
    void singSong();
}
  • 某X明星(被代理对象),自然也要会
public class XStar implements Star{
    @Override
    public void makingMovie() {
        System.out.println("X拍电影");
    }

    @Override
    public void singSong() {
        System.out.println("X唱歌");
    }
}
  • 经纪人(代理对象)安排日常工作
public class Agent implements Star{
	//通过组合的方式,持有一个XStar对象引用
    private XStar xStar;
    public Agent(XStar xStar){
        this.xStar=xStar;
    }
	//代理对象安排时间演戏
    private void arrangeMovie(){
        System.out.println("经纪人安排了拍电影");
    }
	//代理对象安排时间唱歌
    private void arrangeSong(){
        System.out.println("经纪人安排了唱歌");
    }
    
    @Override
    public void makingMovie() {
    	//经纪人安排后,X明星去拍戏
        arrangeMovie();
        xStar.makingMovie();
    }

    @Override
    public void singSong() {
        arrangeSong();
        xStar.singSong();
    }
}

测试代码:

public class Test {
    public static void main(String[] args){
    	//创建代理对象并持有一个被代理对象
        Agent agent=new Agent(new XStar());
        //实际上就是经纪人安排后,x明星演戏
        agent.makingMovie();
        agent.singSong();
    }
}

结果:

经纪人安排了拍电影
X拍电影
经纪人安排了唱歌
X唱歌

透明代理(普通代理)

public class Agent implements Star{
    private XStar xStar;
    //在这里做了修改
    public Agent(){
        this.xStar=new XStar();
    }

    private void arrangeMovie(){
        System.out.println("经纪人安排了拍电影");
    }

    private void arrangeSong(){
        System.out.println("经纪人安排了唱歌");
    }
    @Override
    public void makingMovie() {
        arrangeMovie();
        xStar.makingMovie();
    }

    @Override
    public void singSong() {
        arrangeSong();
        xStar.singSong();
    }
}

这时XStar对象并不是外界传入的,所以xStar对外界是透明的。

总结: 静态模式在不改变目标对象(被代理对象)的前提下,实现了对其的扩展,但一旦目标接口增加了方法,代理对象和目标对象都要进行修改,增加了维护成本。例如:Star接口多了个void rap(),那么我的XStar和Agent都要修改。

三、动态代理

动态代理指:程序在整个运行过程中不存在目标类的代理类(或者说看不到,在JDK内部叫 $Proxy0。目标对象的代理对象只是由代理生成工具(如代理工厂类) 在程序运行时由 JVM 根据反射等机制动态生成的。代理对象与目标对象的代理关系在程序运行时才确立。

使用JDK的java.lang.reflect.Proxy类实现动态代理,通过其静态函数newProxyInstance,自动生成一个动态代理对象。

public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces, InvocationHandler h)
loader:目标类的类加载器,通过目标对象反射获取
interfaces:目标类实现的接口数组,通过目标对象反射获取
h:业务增强逻辑

InvocationHandler是一个接口,用于加强目标类的主业务逻辑,这个接口有一个方法invoke,具体的加强逻辑代码就写在该方法中,程序调用主业务逻辑时,会自动调用invoke方法

public Object invoke(Object proxy, Method method, Object[] args)
proxy:生成的代理对象
method:代表目标类的方法
args:代表目标类方法的参数
由于该方法有代理对象自动调用,所以不需要我们给出

第二个参数method,在反射中学过,它也有invoke方法,用来调用目标类的目标方法,这两个方法虽然同名,但没任何关系。 使用时一般写为:method.invoke(target,args),target为目标类对象,args是上一层传来的参数。

  • x明星现在厉害了,不用别人安排唱歌了,只需要一个经纪人来安排拍电影就行了。
public class Test {
    public static void main(String[] args){
    
        Star xStar=new XStar();
        System.out.println(xStar.getClass());
        Star agent=(Star) Proxy.newProxyInstance(xStar.getClass().getClassLoader(), xStar.getClass().getInterfaces(),new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            	//通过判断method的名称来进行下一步操作
                if(method.getName().equals("makingMovie")){
                    System.out.println("安排拍电影");
                    method.invoke(xStar,args);
                }else{
                	System.out.println("不需要安排唱歌");
                    method.invoke(xStar,args);
                }
                return null;
            }
        });

        agent.makingMovie();
        agent.singSong();
    }
}

结果:

安排拍电影
X拍电影
不需要安排唱歌
X唱歌
  • 还可以对代码进行封装:
class StarInvocation implements InvocationHandler{
    private XStar xStar;
    public StarInvocation(XStar xStar){
        this.xStar=xStar;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if(method.getName().equals("makingMovie")){
            System.out.println("安排拍电影");
            method.invoke(xStar,args);
        }else{
        	System.out.println("不需要安排唱歌");
            method.invoke(xStar,args);
        }
        return null;
    }
}

class StarProxy{
    private XStar xStar;
    private StarInvocation starInvocation;
    public StarProxy(XStar xStar,StarInvocation starInvocation){
        this.starInvocation=starInvocation;
        this.xStar=xStar;
    }

    public Object getProxy(){
        return Proxy.newProxyInstance(xStar.getClass().getClassLoader(),xStar.getClass().getInterfaces(),starInvocation);
    }
}

public class Test {
    public static void main(String[] args){
        XStar xStar=new XStar();
        Star agent=(Star) new StarProxy(xStar,new StarInvocation(xStar)).getProxy();
        agent.makingMovie();
        agent.singSong();
        }
}

注意:jdk动态代理的是接口,因此要强制类型转换为接口,而不是代理类,否则报错。上面也说了,动态代理是不需要代理类的。

总结:

  • 代理对象拥有目标对象的相同方法(参数二) ;用户调用代理对象方法时,都时在调用invoke方法 ;使用动态代理时目标对象必需要有接口
  • 使用静态代理时,如果目标接口有很多方法,我们都要一一实现。
  • 使用动态代理时,是利用JDKAPI动态生成代理对象,并默认实现接口全部方法。

四、CgLib代理

实现动态代理必须要有接口的,动态代理是基于接口来代理的(实现接口的所有方法),如果没有接口的话可以考虑cglib代理。cglib代理也叫子类代理,从内存中构建出一个子类来扩展目标对象的功能

  • c明星实例(目标对象)
public class CStar {
    public void sing(){
        System.out.println("sing");
    }
    public void dance(){
        System.out.println("dance");
    }
    public void rap(){
        System.out.println("rap");
    }
}
  • 经纪人(代理对象)
class CAgent implements MethodInterceptor{

    private CStar cStar;
    public CAgent(CStar cStar){
        this.cStar=cStar;
    }

    //代理对象获取函数
    public CStar getProxyInstance(){
        //工具类
        Enhancer en=new Enhancer();
        //设置需要代理的对象,也是父类
        en.setSuperclass(cStar.getClass());
        //设置代理对象,回调设计模式,设置回调接口对象
        en.setCallback(this);//回调自己
        //创建子类代理对象
        return (CStar) en.create();
    }

    //回调函数,进行逻辑增强
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("安排唱、跳、rap");
        method.invoke(cStar,objects);
        return null;
    }
}

测试代码:

public class Test1 {

    public static void main(String[] args){
        CStar cStar=new CStar();
        CAgent cAgent=new CAgent(cStar);
        CStar proxy=cAgent.getProxyInstance();
        proxy.dance();
    }
}

结果:

安排唱、跳、rap
dance

cglib-nodep-3.1.jar 百度网盘:pan.baidu.com/s/1VGb0UP9_… 提取码:o7wt

回调模式: Java的接口提供了一种很好的方式实现回调。 简单的说,在A类中调用B类的C方法,然后B类回调A类中的D方法。方法D被称为回调方法。例如:类CAgent调用类Enhancer的setCallback(this)方法,并将回调对象,也就是自身,传给了Enhance类,Enhance类会在后续过程中回调类CAgent的intercept()方法

五、参考

mp.weixin.qq.com/s/VkF5Drcol…

blog.csdn.net/hu_belif/ar…

www.jianshu.com/p/8ccdbe00f…

blog.itpub.net/69917606/vi…