[设计模式] - 代理模式 静态代理和动态代理(JDK和CGLib)实现

代理模式(Proxy Pattern)是指为其他对象提供一种代理,以控制这个对象的访问。代理模式属于结构型设计模式。
代理模式一般涉及到的角色有:
抽象角色:声明真实对象和代理对象的共同接口,对应代理接口(Subject);
真实角色:代理角色所代表的真实对象,是我们最终要引用的对象,对应委托类(RealSubject);
代理角色:代理对象角色内部含有对真实对象的引用,从而可以操作真实对象,同时代理对象提供与真实对象相同的接口以便在任何时刻都能代替真实对象。同时,代理对象可以在执行真实对象操作时,附加其他的操作,相当于对真实对象进行封装,对应代理类(ProxySubject)

静态代理 - 显式声明被代理对象

让我们举一个最长碰到的例子——找对象。
首先,我们需要一个抽象对象,那就是人。他本身包含一个找对象的方法。

public interface Person {
    void findPartner();
}

接下来,真是对象,也就是你。找对象的要求也不高,只求看对眼。

public class Son implements Person {
    @Override
    public void findPartner() {
        System.out.println("王八看绿豆");
    }
}

最后,你的妈妈就要出动了。他会根据你的要求和自己的要求来帮你完成这个方法。

public class Mother {
   private Son son;

    public Mother(Son son) {
        this.son = son;
    }

    public void findPartner(){
        System.out.println("帮助物色对象");
        son.findPartner();
        System.out.println("对上眼了。");
    }
}

而在外人看到的就是你的妈妈代理并扩展了你找对象的方法。

public class FindTest {

    public static void main(String[] args) {
        Mother mother = new Mother(new Son());

        mother.findPartner();
    }
}

最终结果

帮助物色对象
王八看绿豆
对上眼了。

静态代理类优缺点
优点:业务类只需要关注业务逻辑本身,保证了业务类的重用性。这是代理的共有优点。
缺点:
1)代理对象的一个接口只服务于一种类型的对象,如果要代理的方法很多,势必要为每一种方法都进行代理,静态代理在程序规模稍大时就无法胜任了。
2)如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。

动态代理

动态代理和静态对比基本思路是一致的,只不过动态代理功能更加强大,随着业务的扩展适应性更强。如果还以找对象为例,使用动态代理能够适应复杂的业务场景。 媒婆就属于需要面对复杂业务的职业。而实现的方式有JDK原生动态代理和CGLIB动态代理

jdk原生动态代理实现

首先创建一个顾客类,让他实现找对象方法,提出自己的需求。

public class Customer implements Person {
    @Override
    public void findPartner() {
        System.out.println("肤白貌美大长腿!");
    }
}

接着创建媒婆类,他需要实现 InvocationHandler这个接口中的invoke()方法,方法的调用都会转发到这个方法里。
接着是通过Proxy.newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler handler)方法来动态获得代理对象。

  1. loader,指定代理对象的类加载器;
  2. interfaces,代理对象需要实现的接口,可以同时指定多个接口;
  3. handler,方法调用的实际处理者,代理对象的方法调用都会转发到这里。
    newProxyInstance()会返回一个实现了指定接口的代理对象,对该对象的所有方法调用都会转发给InvocationHandler.invoke()方法。而在invoke()方法中我们还可以添加其他方法,如媒婆获取需求等等。
public class JDKMatchmaker implements InvocationHandler {

    private Object target;

    public Object getInstance(Object target){
        this.target = target;
        Class<?> clazz = target.getClass();

        return Proxy.newProxyInstance(clazz.getClassLoader(),clazz.getInterfaces(),this);

    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        before();
        Object o = method.invoke(this.target,args);
        after();

        return o;
    }

    private void before(){
        System.out.println("登记需求!");
        System.out.println("开始物色!");
    }

    private void after(){
        System.out.println("结束,收费。");
    }
}

接下来就是见证奇迹的时刻——测试

public class JDKTest {

    public static void main(String[] args) {
        Person person = (Person) new JDKMatchmaker().getInstance(new Customer());
        person.findPartner();
    }

}

结果

登记需求!
开始物色!
肤白貌美大长腿!
结束,收费。

Java动态代理为我们提供了非常灵活的代理机制,但Java动态代理是基于接口的,如果没有接口,我们就需要使用CGLib的方式了。

CGLIB动态代理

CGLIB (Code Generation Library)是一个基于 ASM 的字节码生成库,它允许我们在运行时对字节码进行修改和动态生成。CGLIB通过继承方式实现代理。
/CGLib媒婆上线/

public class CGLibMatchmaker implements MethodInterceptor {

    public Object getInstance(Class clazz){

        Enhancer enhancer = new Enhancer();

        enhancer.setSuperclass(clazz);
        enhancer.setCallback(this);

        return enhancer.create();

    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {

        before();
        Object obj = methodProxy.invokeSuper(o,objects);
        after();

        return obj;
    }

    private void before(){
        System.out.println("登记需求!");
        System.out.println("开始物色!");
    }

    private void after(){
        System.out.println("结束,收费。");
    }
}

媒婆类同样需要实现MethodInterceptorintercept(Object o, Method method, Object[] objects, MethodProxy methodProxy)的方法。通过getInstance()方法中,CGLIB的Enhancer来指定要代理的目标对象、实际处理代理逻辑的对象,最终通过调用create()方法得到代理对象获取代理对象。
通过调用MethodProxy.invokeSuper()方法,我们将调用转发给原始对象(不包括final修饰的方法,因为它不能被重载)。

CGLib和JDK动态代理对比

  • DK原生动态代理是Java原生支持的,不需要任何外部依赖,但是它只能基于接口进行代理;CGLIB通过继承的方式进行代理,无论目标对象有没有实现接口都可以代理,但是无法处理final的情况。
  • 他们都是在运行期生成字节码,JDK是直接写class字节码,CGLib是使用ASM框架写字节码,CGLib实现更为复杂,生成代理类比JDK效率低。
  • JDK调用代理方法是通过反射机制,而CGLib是通过fastClass机制直接调用方法,CGLib执行效率更高。

Spring中代理的使用

  1. 当Bean有实现接口时,Spring会使用JDK动态代理。
  2. 当Bean没有接口实现,会使用CGlib。

静态代理和动态代理的本质区别

  1. 静态代理只能通过手动完成代理操作,如被代理类有新的方法时,代理类需要加入该方法,违背开闭原则。
  2. 动态代理采用运行时动态生成代码方式,取消了对被代理类的扩展限制。

代理模式的优缺点

优点:

  • 代理对象与真实被调用的目标对象分离。
  • 降低系统耦合度,扩展性好。
  • 可以保护目标对象。
  • 增强目标对象功能。
    缺点:
  • 代理模式会造成系统设计中类数量增加。
  • 客户端和目标对象中加入代理对象会使请求变慢。
  • 增加系统复杂度。

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