设计模式十六--代理模式

199 阅读4分钟

设计模式

代理模式

代理模式就是为我们的对象提供一个中介秘书,通过这个中介秘书来访问目标对象,从而来扩展目标对象的功能。

静态代理

比如我们想请刘亦菲吃饭,而刘亦菲每天面对那么多的应酬,没空直接和我们联系,这时候刘亦菲就请了个代理即经纪人。我们想预约刘亦菲吃饭需要找她的经纪人预约,吃完饭后刘亦菲的经纪人再帮 她记录下来。

抽象主题类Star,刘亦菲可以抽象出明星,具有吃饭的接口

package com.wangscaler.proxy;

/**
 * @author wangscaler
 * @date 2021.06.30 11:20
 */
public interface Star {
    public void eat();
}

真实主题类:LiuYiFei

package com.wangscaler.proxy;

/**
 * @author wangscaler
 * @date 2021.06.30 11:20
 */
public class LiuYiFei implements Star {
    public void eat() {
        System.out.println("陪同刘亦菲吃饭");
    }
}

代理类:即经纪人Agent,虽然实现了eat,但是调用的是LiuYiFei的方法

package com.wangscaler.proxy;

/**
 * @author wangscaler
 * @date 2021.06.30 11:20
 */
public class Agent implements Star {
    private LiuYiFei liuYiFei;

    public Agent(LiuYiFei liuYiFei) {
        this.liuYiFei = liuYiFei;
    }

    public void eat() {
        System.out.println("预约刘亦菲吃饭");
        liuYiFei.eat();
        System.out.println("记录此次事件");
    }
}

main

package com.wangscaler.proxy;

/**
 * @author wangscaler
 * @date 2021.06.30 11:20
 */
public class StaticProxy {
    public static void main(String[] args) {
        Agent agent = new Agent(new LiuYiFei());
        agent.eat();
    }
}

静态代理目标对象和代理对象都得实现一样的接口,一旦增加新的方法,目标对象和代理对象都要维护。

动态代理

真正的代理对象由JDK在运行时为我们动态的来创建,所以又叫JDK代理、接口代理。与静态代理相比,只需要目标对象实现接口,代理对象无需实现接口。

此时抽象主题类Star和真实主题类LiuYiFei不变,

添加动态代理ProxyFactory

package com.wangscaler.proxy;

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

/**
 * @author wangscaler
 * @date 2021.06.30 14:07
 */
public class ProxyFactory {
    private Object target;

    public ProxyFactory(Object target) {
        this.target = target;
    }

    public Object getProxyInstance() {
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() {
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("预约");
                Object object = method.invoke(target, args);
                System.out.println("记录此次事件");
                return object;
            }
        });
    }
}

main

package com.wangscaler.proxy;

/**
 * @author wangscaler
 * @date 2021.06.30 14:07
 */
public class DynamicProxy {
    public static void main(String[] args) {
        Star liuYiFei = new LiuYiFei();
        Star object = (Star) new ProxyFactory(liuYiFei).getProxyInstance();
        object.eat();
    }
}

这时候我们想增加方法只需要Star

package com.wangscaler.proxy;

/**
 * @author wangscaler
 * @date 2021.06.30 11:20
 */
public interface Star {
    public void eat();

    public void play();
}

添加接口,并在LiuYiFeia实现即可。

package com.wangscaler.proxy;

/**
 * @author wangscaler
 * @date 2021.06.30 11:20
 */
public class LiuYiFei implements Star {
    public void eat() {
        System.out.println("陪同刘亦菲吃饭");
    }

    public void play() {
        System.out.println("陪同刘亦菲拍戏");
    }
}

像上述目标对象需要实现接口就用JDK代理,那么不需要实现接口的单独对象怎么处理呢?使用Cglib代理!当然Cglib也可以用于接口的类的方法。

Cglib代理

代理类不能为final,否则会报错IllegalArgumentException

首先引入cglib的依赖打开maven仓库,引入最新版本。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>DesignPattern</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>3.3.0</version>
        </dependency>
    </dependencies>
</project>

因为cglib不能实现接口,所以LiuYiFei修改成下面

package com.wangscaler.proxy;

/**
 * @author wangscaler
 * @date 2021.06.30 15:34
 */
public class LiuYiFei1 {
    public void eat() {
        System.out.println("陪同刘亦菲吃饭");
    }

    public void play() {
        System.out.println("陪同刘亦菲拍戏");
    }
}

然后创建ProxyFactory1去实现intercept。 enhancer.setCallback(this); 这句话的作用就是让程序在回调intercept方法

package com.wangscaler.proxy;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;


/**
 * @author wangscaler
 * @date 2021.06.30 14:07
 */
public class ProxyFactory1 implements MethodInterceptor {
    private Object target;

    public ProxyFactory1(Object target) {
        this.target = target;
    }

    public Object getProxyInstance() {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(target.getClass());
        enhancer.setCallback(this);
        return enhancer.create();
    }

    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("预约");
        Object object = method.invoke(target,objects);
        System.out.println("记录此次事件");
        return object;
    }
}

main

package com.wangscaler.proxy;

/**
 * @author wangscaler
 * @date 2021.06.30 15:34
 */
public class CglibProxy {
    public static void main(String[] args) {
        LiuYiFei1 liuYiFei1 = new LiuYiFei1();
        LiuYiFei1 object = (LiuYiFei1) new ProxyFactory1(liuYiFei1).getProxyInstance();
        object.eat();
        object.play();
    }
}

总结

代理模式可以对目标对象起到中介作用、保护作用、增强作用,一方面降低了程序的耦合性,一方面增加了程序的扩展性。

Cglib生成的包名为我们程序的包名,而JDK的代理却是底层的包名。

代理模式的主要角色如下。

  1. 抽象主题类:通过接口或抽象类声明真实主题和代理对象实现的业务方法,如上述的Star类。
  2. 真实主题类:就是目标对象,业务真正要调用的对象,实现抽象主题类的方法,如上述的LiuYiFei。
  3. 代理类:内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。所以Spring Aop常使用代理。

何时使用:在访问一个类时做一些控制。

参考资料