您现在的位置是:首页 >技术教程 >1. 设计模式之代理模式网站首页技术教程

1. 设计模式之代理模式

wlyang666 2023-07-25 00:00:03
简介1. 设计模式之代理模式

前言

我们都知道java是面向对象的,他追求万物皆对象,将核心业务逻辑封装到一个个对象中。这样在设计中确实符合高内聚,低耦合的理念。但是有个问题是真实业务场景中,我们有时候需要在一些方法中加入通用的处理逻辑,增强原来的业务功能。

比如:我想看项目中某一类方法的处理时间,一种方式是我在每个方法前后加打印日志计算时间的逻辑,这样可以实现,但是有几个缺点:

  1. 花很多人力做重复劳动,而且容易修改错改漏
  2. 如果计算时间逻辑(想加入的增强通用逻辑)有调整,又得重新改一遍
  3. 对原来的业务代码有侵入,极不推荐这样实现

代理,就是解决上面问题的很好的方案。我们将针对对象的访问,交给代理对象来控制,可以在代理对象的前后做一些计算时间、打印日志、事务控制等通用逻辑。

具体如何做,本节我们将会详细介绍。

代理中三种核心角色

  1. 抽象角色
    定义代理角色和真实角色的公共对外方法
  2. 真实角色
    实现抽象角色,定义真实角色所要实现的业务逻辑,供代理角色调用。关注真正的业务逻辑
  3. 代理角色
    实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作。将统一的流程控制放到代理角色中处理(比如日志切面,事务管理)

1. 静态代理

1.1 简介

静态代理,就是每次我们需要代理某一类抽象角色的时候,创建相关静态代理类,完成相关的增强功能可以实现功能的增强,但是需要维护很多的静态代理类。

1.2 定义抽象角色

package com.wanlong.design_pattern.structure.proxy;

public interface IRentHouse {
    void rentHouse();
}

1.3 定义真实角色

package com.wanlong.design_pattern.structure.proxy;

public class RentHouse implements IRentHouse {
    @Override
    public void rentHouse() {
        System.out.println("租房子");
    }
}

1.4 定义代理角色

package com.wanlong.design_pattern.structure.proxy;

public class RentHouseProxy implements IRentHouse {

    private IRentHouse rentHouse;

    public RentHouseProxy(IRentHouse rentHouse) {
        this.rentHouse = rentHouse;
    }

    @Override
    public void rentHouse() {
        System.out.println("交中介费");
        rentHouse.rentHouse();
        System.out.println("租房后维修清洁");
    }
}

1.5 测试

public class Mytest {

    @Test
    public void testStaticProxy() {

        RentHouse rentHouse = new RentHouse();

        RentHouseProxy rentHouseProxy = new RentHouseProxy(rentHouse);

        rentHouseProxy.rentHouse();
    }
}

1.6 运行结果分析

运行结果:

交中介费
租房子
租房后维修清洁

通过上面例子,可以看到:

  1. 通过对代理对象的访问,实现对真实对象的调用
  2. 代理对象增强了真实对象的功能,可以在调用真实对象前后做一些处理
  3. 真实对象和代理对象要实现相同的接口
  4. 缺点是当真实类很多的时候,需要创建很多的静态代理类,这样构建的代理类的代码量是非常大的

2.动态代理

2.1 jdk动态代理

2.1.1 简介

java自己实现的代理方式,好处是使用简单,方便,缺点是不能代理对象,要求被代理对象必须实现接口。

2.1.2 测试类

@Test
public void testJDKDynamicProxy() {

    //1.创建真实对象
    RentHouse rentHouse = new RentHouse();
    //2. 获取被代理实现类对象的Class对象
    Class<? extends IRentHouse> clazz = rentHouse.getClass();
    //第一个参数,被代理对象的类加载器
    ClassLoader classLoader = clazz.getClassLoader();
    //第二个参数,被代理对象实现的所有接口数组
    Class<?>[] interfaces = clazz.getInterfaces();
    //通过调用Proxy.newProxyInstance的方法来创建一个代理类对象来代理真实对象
    //第三个参数InvocationHandler的实现类,这里用了匿名内部类的方式
    IRentHouse proxyBean = (IRentHouse) Proxy.newProxyInstance(classLoader, interfaces, new InvocationHandler() {
        @Override
        //重写InvocationHandler的invoke方法,他有三个参数可以供我们使用
        //第一个参数proxy 可以通过proxy.getClass().getClassName() 来查看对象,是一个匿名的Proxy的代理类
        //第二个参数method 也就是被代理对象myClac对象的方法,可以通过method调用myClac的方法
        //第三个参数args 也就是真实对象方法执行时传入的实际参数
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("前置处理,比如交中介费------");
            //method.invoke的返回值就是真实对象的方法的返回值
            Object invoke = method.invoke(rentHouse, args);
            System.out.println("后置增强,比如打扫房间------");
            return invoke;
        }
    });
    //代理对象调用
    proxyBean.rentHouse();
}

2.1.3 运行结果分析

运行结果

前置处理,比如交中介费------
租房子
后置增强,比如打扫房间------

注意事项

  1. 通过动态代理实现了业务功能的增强
  2. 没有新建静态代理类,避免了后续维护静态代理类的代码烦恼
  3. jdk代理实现方式相对简单固定,调用方法Proxy.newProxyInstance,传入真实类的类加载器,真实对象的接口,实现InvocationHandler的匿名内部类
  4. jdk动态代理要求真实对象必须实现接口,所以如果目标对象没有实现接口,则只能选择其他动态代理方式

2.2 cglib动态代理

2.2.1 简介

上面提到了jdk动态代理使用简单,但是有缺陷是不能代理没有实现接口的类。但是如果真的的有需求要代理这种类,我们可以通过本节介绍的CGLib实现。在一些框架中,考虑到性能,一般可能两种代理方式都会存在。如果能用jdk动态代理,就用jdk动态代理。如果jdk动态代理没法实现,用cglib动态代理。比如spring框架。

2.2.2 定义抽象角色

public interface IRentHouse {
    void rentHouse();
    void say();
    void say2();
}

2.2.3 定义真实角色

public class RentHouse implements IRentHouse {

    @Override
    public void rentHouse() {
        System.out.println("租房子");
    }

    @Override
    public void say() {
        System.out.println("hello");
        say2();
    }

    @Override
    public void say2() {
        System.out.println("hello2");
    }

    public final void finalMethod() {
        System.out.println("final method");
    }

    public static void staticMethod() {
        System.out.println("static method");
    }

}

2.2.4 CGlib动态代理第一种方式

package com.wanlong.design_pattern.structure.proxy.dynamic;

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

import java.lang.reflect.Method;

public class MyIntercepter implements MethodInterceptor {

    private Object target;

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

    /**
     * @param o           代理对象
     * @param method      被代理对象的方法
     * @param objects     方法入参
     * @param methodProxy 代理方法
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("cglib before");
        //1. 调用代理类FastClass对象
        //Object result =  methodProxy.invokeSuper(o, objects);
        //2. 使用传对象的方式创建代理对象
        Object result = methodProxy.invoke(target, objects);
        System.out.println("cglib after");
        return result;
    }
}

2.2.5 CGlib动态代理第二种方式

public class MyIntercepter implements MethodInterceptor {

    private Object target;

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

    /**
     * @param o           代理对象
     * @param method      被代理对象的方法
     * @param objects     方法入参
     * @param methodProxy 代理方法
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("cglib before");
        //1. 调用代理类FastClass对象
        Object result =  methodProxy.invokeSuper(o, objects);
        //2. 使用传对象的方式创建代理对象
        //Object result = methodProxy.invoke(target, objects);
        System.out.println("cglib after");
        return result;
    }
}

2.2.6 测试类

@Test
public void testCglibDynamicProxy() {

    IRentHouse target=new  RentHouse();
    // 代理类class文件存入本地磁盘方便我们反编译查看源码
    System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "./code");
    // 通过CGLIB动态代理获取代理对象的过程
    Enhancer enhancer = new Enhancer();
    // 设置enhancer对象的父类
    enhancer.setSuperclass(RentHouse.class);
    // 设置enhancer的回调对象
    enhancer.setCallback(new MyIntercepter(target));
    // 创建代理对象
    RentHouse rentHouse = (RentHouse) enhancer.create();
    // 通过代理对象调用目标方法
    rentHouse.rentHouse();
    System.out.println("-----------");
    rentHouse.say();
    System.out.println("-----------");
    rentHouse.finalMethod();
    RentHouse.staticMethod();
    //final,static等方法不会代码增强,say()方法内部调用say2(),say2()方法也会进行增强。具体原因后面代理类字节码反编译解释。
}

2.2.7 运行结果分析

运行结果1:

cglib before
租房子
cglib after
-----------
cglib before
hello
hello2
cglib after
-----------
final method
static method

运行结果2:

cglib before
租房子
cglib after
-----------
cglib before
hello
cglib before
hello2
cglib after
cglib after
-----------
final method
static method

注意事项:

  1. 通过结果可以看到,通过代理对象,可以正常访问目标对象,并且在调用前后做一些事情
  2. 可以看到在上面的例子中,say方法调用了say2方法
  3. 第一种结果我们只拦截了say,say里面调用的方法没有拦截
  4. 第二种方式,say和say里面调用的say2都做了拦截,在前后打印了日志,另外可以留意一下拦截的顺序
  5. final,static等方法不会代码增强
  6. 一个动态代理类代理的是一个接口,一般就是对应的一类业务
  7. 一个动态代理类可以代理多个类,只要是实现了同一个接口即可。
  8. 关于CGlib更多回调函数的使用,比如懒加载,可以看我之前的博客

3. 代理实际应用场景

安全代理:屏蔽对真实角色的直接访问。
远程代理:通过代理类处理远程方法调用(RMI)
延迟加载:先加载轻量级的代理对象,真正需要再加载真实对象。

3.1 在框架中的应用

  1. Hibernate中延时加载的实现
  2. mybatis中实现拦截器插件
  3. AspectJ的实现
  4. spring中AOP的实现(日志拦截、声明式事务处理)
风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。