Java设计模式 之代理模式

文章目录

  • 代理模式思想
  • 代理模式涉及3个角色
  • 静态代理
  • JDK动态代理
  • Cglib动态代理
  • JDK和Cglib动态代理区别
  • 代理模式的使用场景

代理模式思想

代理模式是一种结构型设计模式。
思想:给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。

代理模式涉及3个角色

  • 抽象对象(Subject):代理和目标的共同接口。共同的接口使得任何可以使用目标对象的地方都可以使用代理对象。
  • 目标对象(Target):要被代理的对象,也称为委托者。
  • 代理对象(Proxy):内部包含了一个目标对象的引用,从而代替目标对象为客户提供服务。

静态代理

静态代理模式可以在不修改目标对象的功能前提下,对目标功能扩展。

案例:西门庆找潘金莲,那潘金莲不好意思答复呀,咋办,找那个王婆做代理。
Java设计模式 之代理模式_第1张图片
KindWomen:抽象角色。
JiaShi:目标角色,委托类。
WangPo:代理角色。

代码实现:
抽象出一种类型的女人,王婆和潘金莲都属于这个类型的女人。

public interface KindWomen {
    public void makeEyesWithMan(); //抛媚眼
    public void happyWithMan();
}

具体到一种类型的女人,这里是潘金莲,是一个年轻漂亮的姑娘,具体被代理的人。

public class PanJinLian implements KindWomen {
    @Override
    public void makeEyesWithMan() {
        System.out.println("潘金莲抛媚眼");
    }

    @Override
    public void happyWithMan() {
        System.out.println("潘金莲在和男人做那个.....");
    }
}

定义王婆,王婆也是这种类型的女人,但是她太老了,是个男人都看不上,但是她有智慧有经验呀,她可以作为这一类女人的代理,让年轻的姑娘代替干。即代理类。

public class WangPo implements KindWomen {
    private KindWomen kindWomen;

	// 她可以是KindWomen的任何一个女人的代理,只要你是这一类型
    public WangPo(KindWomen kindWomen){
        this.kindWomen = kindWomen;
    }

    @Override
    public void makeEyesWithMan() {
    	// 王婆这么大年龄了,谁看她抛媚眼,还是让年轻的女人吧
        this.kindWomen.makeEyesWithMan();
    }

    @Override
    public void happyWithMan() {
    	// 自己老了,干不了,可以让年轻的代替
        this.kindWomen.happyWithMan();
    }
}

定义西门庆,男主角,需要找一个女人HappyHappy。

public class XiMenQing {
    public static void main(String[] args) {
        // 西门庆不知道去哪里找,于是找到了王婆,王婆找到了潘金莲
        WangPo wangPo = new WangPo(new PanJinLian());
        // 虽然表面上时王婆在做,实际上爽的是潘金莲
        wangPo.makeEyesWithMan();
        wangPo.happyWithMan();
    }
}

西门庆Happy过程。
在这里插入图片描述
假如西门庆想换个新鲜的,再定义一个贾氏,又是一个年轻漂亮的姑娘,具体被代理的人。

public class JiaShi implements KindWomen {
    @Override
    public void makeEyesWithMan() {
        System.out.println("贾氏抛媚眼");
    }

    @Override
    public void happyWithMan() {
        System.out.println("贾氏正在Happy中......");
    }
}

西门庆又找到王婆,说和潘金莲Happy已经腻了,想换个新的,于是王婆让贾氏做为代理人了。

public class XiMenQing {
    public static void main(String[] args) {
        // 让王婆作为贾氏的代理人
        WangPo wangPo = new WangPo(new PanJinLian());
        wangPo.makeEyesWithMan();
        wangPo.happyWithMan();
    }
}

西门庆再次Happy过程。
在这里插入图片描述
总结:代理模式主要使用了 Java 的多态,干活的是被代理类,代理类主要是接活,你让我干活,好,我交给幕后的类去干,你满意就成,那怎么知道被代理类能不能干呢?同根就成,大家知根知底,你能做啥,我能做啥都清楚的很,同一个接口呗。

JDK动态代理

动态代理类的原理是在程序运行期间由JVM根据反射等机制动态的生成,所以不存在代理类的字节码文件。代理类和目标类(委托类)的关系是在程序运行时确定。

java.lang.reflect.Proxy是 Java 动态代理机制生成的所有动态代理类的父类,它提供了一组静态方法来为一组接口动态地生成代理类及其对象。
java.lang.reflect.ProxynewProxyInstance()方法要求传入3个参数,其中最后一个参数是一个调用处理器对象,即实现了java.lang.reflect.InvocationHandler这个接口的类的实例。InvocationHandler定义了一个invoke()方法,用于集中处理在动态代理类对象上的方法调用,通常在该方法中实现对目标类(委托类)的代理访问。每次生成动态代理类对象时都要指定一个对应的调用处理器对象。

步骤

  • 创建被代理的接口和接口实现类;
  • 创建 InvocationHandler 接口的实现类,在 invoke 方法中实现代理逻辑;
  • 通过 Proxy 的静态方法 newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)创建一个代理对象;
  • 使用代理对象。

Java设计模式 之代理模式_第2张图片

代码实现
被代理的接口和接口实现类:

// 抽象角色
public interface Car {
    void drive();
}

// 目标类(委托类)
public class KIACar implements Car {
    @Override
    public void drive() {
        System.out.println("起亚汽车");
    }
}

动态代理生成工厂类;实现代理逻辑必须实现java.lang.reflect.InvocationHandler接口:

import java.lang.reflect.Proxy;

public class JDKProxyFactory {
    // 封装一个目标对象
    private Object target;

    // 生成代理类
    public Object getProxy(Object target) {
        this.target = target;
        // 第一个参数:指定代理类的类加载器,传入target自身的类加载器即可;
        // 第二个参数:代理类需要实现的接口,传入被代理类实现的接口,这样生成的代理类和被代理类就实现了相同的接口;
        // 第二个参数:InvocationHandler的实现类, 即代理类的实现类, 用来编写代理类的业务逻辑并调用被代理类的方法。
        return Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                (proxy, method, args)->{
                    System.out.println("启动车辆");
                    // 反射调用被代理类的方法
                    Object object = method.invoke(target, args);
                    System.out.println("停止车辆");
                    return object;
                }
        );
    }

    public static void main(String[] args) {
        JDKProxyFactory factory = new JDKProxyFactory();
        // 获取代理类
        Car car = (Car)factory.getProxy(new KIACar());
        // 通过代理类来调用被代理类方法
        car.drive();
    }
}

运行结果:
在这里插入图片描述
可以看到,代理工厂和目标是完全松耦合的,根据传入的目标的类型不同,代理工厂可以生成不同类型的代理。这解决了静态代理模式代理类和目标耦合性强的问题。

Proxy.newProxyInstance源码分析:

// 方法名和入参
public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
    // 被代理类接口的Class对象拷贝一份
    final Class<?>[] intfs = interfaces.clone();
    // 通过类加载器和被代理类接口的Class对象,从缓存中获取或重新生成指定的代理类的Class对象
    Class<?> cl = getProxyClass0(loader, intfs);
    // 获取代理类的构造函数
    final Constructor<?> cons = cl.getConstructor(constructorParams);
    // 生成代理对象
    return cons.newInstance(new Object[]{h});

总结
通过类加载器和被代理类接口的Class字节码,动态生成被代理类的Class对象字节码,然后通过这个Class对象获取构造函数后,实例化这个代理类并返回。
newInstance()就是把new这个方式分解为两步,首先调用class的加载方法加载某个类,然后实例化。

newInstance案例

// newInstance:弱类型。效率低。只能调用无参构造。----类加载机制
// new:强类型。相对高效。能调用任何public构造。 ----
@Test
public void getInstance() throws ClassNotFoundException, InstantiationException, IllegalAccessException {
    String classValue = "com.sunhui.test.Student";
    Class clazz = Class.forName(classValue);             //初始化该类
    Student student = (Student) clazz.newInstance();     //实例化该类
    student.setUserName("sunhui");
    student.setPassword("sh152332");
    System.out.println("UserName:"+student.getUserName()+"\nPassword:"+student.getPassword());
} 

Cglib动态代理

Cglib代理,也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能的扩展。
JDK的动态代理有一个限制,就是使用动态代理的对象必须至少实现一个接口,如果想代理没有实现接口的类,就可以使用Cglib实现。
Cglib是一个强大的高性能的代码生成包,它可以在运行期扩展java类与实现java接口。它广泛的被许多AOP的框架使用,例如Spring AOP。
Cglib包的底层是通过使用一个小而块的字节码处理框架ASM来转换字节码并生成新的类。因此,要使用Cglib,必须引入cglib.jar和asm.jar。另外Spring也集成了cglib功能,使用Spring框架的项目中可以直接使用Cglib的功能。

JDK动态代理类是必须实现了接口的,而cglib不需要实现接口,但是必须保证类不含有final关键字,否则是无法代理的,cglib是Spring核心框架提供的。

1、步骤:

  • 创建被代理的类;
  • 创建 MethodInterceptor 接口的实现类,在 intercept 方法中实现代理逻辑;
  • 通过 Enhancer 加载创建一个代理对象;
  • 使用代理对象。

2、案例:
被代理类:

public class BydCar {
    public void drive() {
        System.out.println("比亚迪汽车");
    }
}

实现代理逻辑的客户端;实现代理逻辑必须实现 org.springframework.cglib.proxy.MethodInterceptor 接口。

import java.lang.reflect.Method;import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;public class CglibProxyHandler implements MethodInterceptor{
    
    private Object target; // 业务类对象,供代理方法中进行真正的业务方法调用
    
    // 生成代理类
    public Object getProxy(Object target){
        this.target = target; // 给业务对象赋值
        Enhancer enhancer = new Enhancer(); // 创建加载器,用来创建动态代理类
        enhancer.setSuperclass(this.target.getClass()); // 为加载器指定要代理的业务类
        enhancer.setCallback(this); // 设置回调,对于代理类上所有方法的调用,都会调用CallBack,CallBack则需要实现intercept()方法来实现
        return enhancer.create(); // 创建动态代理类对象并返回
    }// 实现回调方法
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("汽车启动");
        proxy.invokeSuper(obj, args); // 调用真正的业务类方法
        System.out.println("汽车停止");
        return null;
    }
    
    public static void main(String[] args) {
        CglibProxyHandler cglibProxyHandler = new CglibProxyHandler();
        BydCar car = (BydCar) cglibProxyHandler.getProxy(new BydCar());
        car.drive();
    }
}

3、cglib动态代理遇到的坑:
在通过MethodProxy调用真正业务类方法的时候不要使用MethodProxy.invoke()方法,这个方法会一直循环调用,导致栈溢出;所以invoke会导致OOM的问题。
一定要使用MethodProxy.invokeSuper()方法来调用真正业务类方法。

JDK和Cglib动态代理区别

  • JDK动态代理只能代理接口的实现类;
  • cglib动态代理既能代理一个基本实现类,也能代理接口的实现类;
  • JDK动态代理是通过接口中的方法名,在动态生成的代理类中调用业务实现类的同名方法;
  • cglib动态代理是通过继承业务类,生成的动态代理类是业务类的的子类,通过重写业务方法进行代理;

代理模式的使用场景

  • 当我们想要隐藏某个类时,可以为其提供代理类。
  • 当一个类需要对不同的调用者提供不同的调用权限时,可以使用代理类来实现(代理类不一定只有一个,我们可以建立多个代理类来实现,也可以在一个代理类中金进行权限判断来进行不同权限的功能调用)。
  • 当我们要扩展某个类的某个功能时,可以使用代理模式,在代理类中进行简单扩展(只针对简单扩展,可在引用委托类的语句之前与之后进行)。

你可能感兴趣的:(#,Java设计模式)