Java设计模式之结构型模式 | 代理模式

33 阅读5分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 16 天,点击查看活动详情

觉得对你有益的小伙伴记得点个赞+关注

后续完整内容持续更新中

希望一起交流的欢迎发邮件至javalyhn@163.com

1. 代理模式的定义

代理,代理,就是给某一个对象提供一个代理,并由代理对象控制对原对象的引用

2. 代理模式使用场景

  • MyBatis的mapper到底是什么?怎么生成的?
    • 动态代理
    • UserMapper、CityMapper,mybatis帮我们写实现MapperProxy
  • Alibaba Seata的DataSourceProxy是什么?
  • DruidDataSource存在的Proxy模式
    • 监控链...

3. 代理模式角色说明

  1. Subject: 抽象主体角色(抽象类或接口)
  2. Proxy: 代理主体角色(代理对象类)
  3. RealSubject: 真实主体角色(被代理对象类)

4. 代理模式实现

image.png

4.1 静态代理

静态代理相当于包装模式。

ManTikTok

/**
 * 抽象主体。被代理角色能干什么
 */
public  interface ManTikTok {
   void tiktok();
}

LeiTikTok

/**
 * Subject  主体
 *
 *
 */
public class LeiTikTok implements ManTikTok {
    @Override
    public void tiktok() {
        System.out.println("雷,tiktok.... ");
    }
}

ZhangTikTok

public class ZhangTikTok implements ManTikTok {
    @Override
    public void tiktok() {
        System.out.println("张三,tiktok.... ");
    }
}

LiMingTiktokProxy

/**
 * 代理一般都是和被代理对象属于同一个接口
 */
public class LiMingTiktokProxy implements ManTikTok{

    private ManTikTok manTikTok; //被代理对象
    public LiMingTiktokProxy(ManTikTok manTikTok){
        this.manTikTok = manTikTok;
    }


    @Override
    public void tiktok() {
        //增强功能
        System.out.println("渲染直播间....");
        System.out.println("课程只要666,仅此一天....");
        manTikTok.tiktok();

    }
}

MainTest

/**
 * 代理的东西不一样,每一种不同的被代理类Person、Dog、Cat,创建不同的静态代理类
 *
 */
public class MainTest {

    public static void main(String[] args) {
        LiMingTiktokProxy proxy = new LiMingTiktokProxy(new LeiTikTok());
        proxy.tiktok();

        //静态代理就是装饰器
        //装饰器模式是代理模式的一种

    }
}

image.png

4.2 JDK动态代理

代理类所在包:java.lang.Reflt.Proxy

image.png

ManTikTok

public  interface ManTikTok {
   void tiktok();
}

SellTikTok

public interface SellTikTok {

    void sell();
}

LeiTikTok

public class LeiTikTok  implements ManTikTok,SellTikTok {
//    @Override
    public void tiktok() {
        System.out.println("雷丰阳,tiktok.... ");
    }

    @Override
    public void sell() {
        System.out.println("雷丰阳,只要666,赶紧来包...");
    }

    public void haha(){
        System.out.println("hahaha ,......");
    }
}

JdkTiktokProxy<T>

public class JdkTiktokProxy<T> implements InvocationHandler {


    private T target;
    //接受被代理对象
    JdkTiktokProxy(T target){
        this.target = target;
    }
    /**
     * 获取被代理对象的  代理对象
     * @param t
     * @param <T>
     * @return
     */
    public static<T> T getProxy(T t) {

        /**
         * ClassLoader loader, 当前被代理对象的类加载器
         * Class<?>[] interfaces, 当前被代理对象所实现的所有接口
         * InvocationHandler h,
         *  当前被代理对象执行目标方法的时候我们使用h可以定义拦截增强方法
         */
        Object o = Proxy.newProxyInstance(
                t.getClass().getClassLoader(),
                t.getClass().getInterfaces(), //必须接口
                new JdkTiktokProxy(t));
        return (T)o;
    }


    /**
     * 定义目标方法的拦截逻辑;每个方法都会进来的
     *
     * @param proxy
     * @param method
     * @param args
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy,
                         Method method,
                         Object[] args) throws Throwable {

        //反射执行
        System.out.println("真正执行被代理对象的方法");
        Object invoke = method.invoke(target, args);
        System.out.println("返回值:一堆美女");
        return invoke;
    }
}

MainTest

/**
 * 动态代理模式:
 * JDK要求被代理对象必须有接口
 *
 * 代理对象和目标对象的相同点在于都是同一个接口
 */
public class MainTest {
    public static void main(String[] args) {
        ManTikTok leiTikTok = new LeiTikTok();


        /**
         * 动态代理机制。
         */
        ManTikTok proxy = JdkTiktokProxy.getProxy(leiTikTok);
        proxy.tiktok();

        ((SellTikTok)proxy).sell();


        //proxy只能转成接口类
//        ((LeiTikTok)proxy).haha();
        System.out.println(Arrays.asList(proxy.getClass().getInterfaces()));

    }
}

image.png

能不能代理被代理对象本类自己的方法?

答案是不能

JDK动态代理只能实现被代理类实现的接口中的方法

详解JDK动态代理中的方法参数

image.png

  • ClassLoader loader, 当前被代理对象的类加载器
  • Class<?>[] interfaces, 当前被代理对象所实现的所有接口
  • InvocationHandler h, 当前被代理对象执行目标方法的时候我们使用h可以定义拦截增强方法

前两个参数很好理解,第三个参数也不难,就是增强被代理对象实现的接口中的方法。我相信知道Spring中AOP切面编程的小伙伴应该很好理解。

那么如何实现第三个参数呢,我们可以直接用匿名内部类,也可以让当前类实现InvocationHandler接口,重写invoke方法

  1. 匿名内部类
Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("JDK代理开始");
                        //反射机制调用目标对象方法
                        Object returnVal = method.invoke(target, args);
                        System.out.println("JDK代理结束");
                        return returnVal;
                    }
                });
  1. 实现InvocationHandler
public class JdkTiktokProxy<T> implements InvocationHandler {
    //......
    ```
    /**
     * 定义目标方法的拦截逻辑;每个方法都会进来的
     *
     * @param proxy
     * @param method
     * @param args
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy,
                         Method method,
                         Object[] args) throws Throwable {

        //反射执行
        System.out.println("真正执行被代理对象的方法");
        Object invoke = method.invoke(target, args);
        System.out.println("返回值:一堆美女");
        return invoke;
    }
    
}
//这是调用的做法
Proxy.newProxyInstance(
        t.getClass().getClassLoader(),
        t.getClass().getInterfaces(), //必须接口
        new JdkTiktokProxy(t));

如果调用了被代理类中未实现接口的方法则会报错

image.png

4.3 cglib动态代理

cglib实现要导入下面的依赖

<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>

LeiTikTok

/**
 * Subject  主体
 *
 *
 */
public class LeiTikTok  {

    public void tiktokHaha() {
        System.out.println("雷,tiktok.... haha....");
    }
}
public class CglibProxy {

    //为任意对象创建代理
    public static<T> T createProxy(T t){
        //1、创建一个增强器
        Enhancer enhancer = new Enhancer();

        //2、设置要增强哪个个类的功能。增强器为这个类动态创建一个子类
        enhancer.setSuperclass(t.getClass());

        //3、设置回调
        enhancer.setCallback(new MethodInterceptor() {
            @Override
            public Object intercept(Object obj,
                                    Method method,  //为了能获取到原方法的一些元数据信息
                                    Object[] args,
                                    MethodProxy proxy) throws Throwable {
                //编写拦截的逻辑
                System.out.println("cglib上场le .......xxx");
                //当前方法的信息
//                method.get
//                method.getAnnotation()
                //目标方法进行执行
                Object invoke = proxy.invokeSuper(obj,args);
                return invoke;
            }
        });

        Object o = enhancer.create();
        return (T) o;
    }

}

CglibTest

public class CglibTest {

    public static void main(String[] args) {

        //原来的对象都不用new
        LeiTikTok tikTok = new LeiTikTok();

        LeiTikTok proxy = CglibProxy.createProxy(tikTok);

        proxy.tiktokHaha();
    }
}

image.png

我们可以通过method参数获取很多目标方法的信息

image.png

最终是依赖proxy来调用目标方法

5. 代理模式优点

  1. 职责清晰: 真实的角色就是实现真实的逻辑,不用关心其他非本职责的事务,通过代理完成这些事务。
  2. 高扩展性: 具体主题角色是会随时发生变化的,只要他实现了接口,不管他如何变化,代理类就可以再不做任何修改的情况下使用。
  3. 智能化。