设计模式之代理模式

185 阅读4分钟

代理模式(Proxy Pattern)是指为其他对象提供一种代理,以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用

代理模式的应用场景

生活中的租房中介、售票黄牛、婚介等,都是代理模式的实际体现。当无法或者不想直接引用某个对象或访问某个对象存在困难是,可以通过代理对象来间接访问。使用代理对象主要有两个目的:一是保护目标对象,二是增强目标对象

静态代理

在我们开发项目的三层架构上,其实就是一个经典的静态代理模式的使用,我们从中找一个小例子,比如要给某个方法添加日志打印功能

//接口
public interface PersonService {
    boolean login(String username, String password);
}
//实现类
public class PersonServiceImpl implements PersonService {
    @Override
    public boolean login(String username, String password) {
        System.out.println("PersonServiceImpl - login");
        return true;
    }
}

在不修改目标类的目标方法代码的前提下,为目标方法增加额外功能

//代理类
public class PersonProxy implements PersonService {
    private PersonService service;
    public void setService(PersonService service) {
        this.service = service;
    }
    @Override
    public boolean login(String username, String password) {

        System.out.println("日志-------------------------1");
        boolean result = service.login(username, password);
        System.out.println("日志-------------------------2");
        return result;
    }
}

代码调用

public static void main(String[] args) {
    //创建目标对象
    PersonService service = new PersonServiceImpl(); 
    //创建代理对象
    PersonProxy proxy = new PersonProxy();
    //把目标类对象传递给代理类
    proxy.setService(service); 
    proxy.login("123", "123");
}

这就是一个静态代理的实现,非常易于理解和实现,但是我们不难发现,每一个代理类只能为一个接口服务,这样的话,程序在开发过程中必然会产生很多的代理类,如果都是为了给接口方法添加一个日志功能,那么就会出现很多重复代码。

如何解决上述问题呢,解决上述问题最好的做法就是通过一个代理类来完成所有的代理功能,那么此时就要用上动态代理了

动态代理

动态代理,在程序运行时,运用反射机制动态创建而成。动态代理类的字节码在程序运行过程中由Java的反射机制动态生成,无需程序员手动编写其代码,这样不仅简化了编程过程,还提高了软件系统的可扩展性

JDK自带动态代理

//jdk动态代理类
public class JDKDynamicProxy implements InvocationHandler {

    private PersonService target;

    public Object getInstance(PersonService target) {

        this.target = target;
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("日志-------------------------1");
        Object result = method.invoke(target, args);
        System.out.println("日志-------------------------2");
        return result;
    }
}

代码调用

public static void main(String[] args) {
    JDKDynamicProxy proxy = new JDKDynamicProxy();
    PersonService service = (PersonService) proxy.getInstance(new PersonServiceImpl());
    service.login("123", "123");
}

一些细节

JDK实现动态代理,代理类跟目标对象一样需要实现接口

CGLib实现代理

//cglib动态代理类
public class CGLibDynamicProxy implements MethodInterceptor {

    public Object getInstance(Object target) { //获取实例
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(target.getClass());//设置代理目标
        enhancer.setCallback(this);//回调方法
        return enhancer.create(); //创建代理对象
    }

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

        System.out.println("日志-------------------------1");
        Object result =  methodProxy.invokeSuper(o, objects);
        System.out.println("日志-------------------------2");
        return result;
    }
}

代码调用

public static void main(String[] args) {
    CGLibDynamicProxy proxy = new CGLibDynamicProxy();
    PersonService service = (PersonService) proxy.getInstance(new PersonServiceImpl());
    service.login("123", "123");
}

一些细节

CGLib代理的目标对象不需要实现任何接口,因为它是动态继承目标对象实现动态代理的

CGLib和JDK动态代理对比

  1. JDK动态代理实现了被代理对象的接口,CGLib代理继承了被代理对象
  2. JDK动态代理和CGLib代理都是在运行期生成字节码,但是CGLib代理实现复杂,生成代理类比JDK动态代理效率低
  3. JDK动态代理调用代理方法时通过反射机制调用的,CGLib代理是其自身机制直接调用方法,CGLib代理的执行效率更高

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

  1. 静态代理只能通过手动完成代理操作,如果被代理类增加了新的方法,代理类需要同步增加,违背了开闭原则
  2. 动态代理采用在运行时动态生成代码的方法,取消了对被代理类的扩展机制,遵循了开闭原则
  3. 若动态代理要对目标类的增强逻辑进行扩展,结合策略模式,只需要新增策略类便可完成,无需修改代理类的代码

代理模式的优缺点

优点

  1. 代理模式能将代理对象与真实被调用目标对象分离
  2. 在一定程度上降低了系统的耦合性,扩展性好
  3. 可以起到保护目标对象的作用
  4. 可以增强目标对象的功能

缺点

  1. 代理模式会造成系统设计中类的数量增加
  2. 在客户端和目标对象中增加一个代理对象,会导致请求处理速度便慢
  3. 增加了系统的复杂度