设计模式——代理模式1.代理模式概述2.代理模式实例

1. 代理模式概述

在代理模式中,使用一个类来代表另一个类的功能。
在代理模式中,我们创建具有现有对象能力的对象,以控制对这个对象的访问,向外界提供功能接口。

(1) 适用情况

通常,可以通过代理对象来对被代理对象进行功能增强、安全控制等等,完成一些代理对象本身不具备的能力。

(2) 优点

职责清晰,且具有高拓展性。

(3) 缺点

增加了系统的复杂度,且代理对象可能会导致响应速度变慢。

2. 代理模式实例

我们现在有一个类UserDao,里面实现了save方法用来保存数据。但是为了考虑到数据库的ACID特性,需要引入事务,这里就可以考虑使用代理模式。

代理模式主要有三种实现方式:静态代理,JDK动态代理和CGLib动态代理。

(1) 实现被代理对象的接口和类

public interface UserDao {
    void save();
}
复制代码
public class UserDaoImpl implements UserDao {
    @Override
    public void save() {
        System.out.println("保存数据");
    }
}
复制代码

(2) 静态代理

public class UserDaoProxy implements UserDao {
    UserDao userDao;

    public UserDaoProxy(UserDao userDao) {
        this.userDao = userDao;
    }

    @Override
    public void save() {
        System.out.println("开启事务");
        userDao.save();
        System.out.println("提交事务");
    }
}
复制代码
public class StaticDemo {
    public static void main(String[] args) {
        // 创建被代理对象
        UserDao userDao = new UserDaoImpl();

        // 创建代理对象
        UserDaoProxy proxy = new UserDaoProxy(userDao);

        System.out.println("调用被代理对象方法:");
        userDao.save();

        System.out.println("\n调用代理对象方法:");
        proxy.save();
    }
}
复制代码

静态代理实现简单,但是却要求代理类和被代理类实现同一接口,那么当接口发生变动时,代理类和被代理类都需要进行修改。

(3) JDK动态代理

public class ProxyDemo {
    public static void main(String[] args) {
        // 创建被代理对象
        UserDao userDao = new UserDaoImpl();

        // 创建代理对象
        UserDao proxy = (UserDao) Proxy.newProxyInstance(userDao.getClass().getClassLoader(),
            userDao.getClass().getInterfaces(), new InvocationHandler() {
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    System.out.println("开启事务");
                    Object invoke = method.invoke(userDao, args);
                    System.out.println("提交事务");
                    return invoke;
                }
            });

        System.out.println("调用被代理对象方法:");
        userDao.save();

        System.out.println("\n调用代理对象方法:");
        proxy.save();
    }
}
复制代码

JDK动态代理主要涉及到两个类:java.lang.reflect.Proxy和java.lang.reflect.InvocationHandler。

Proxy类通过代理类的类加载器实现的所有接口,以及InvocationHandler类的对象来创建代理对象。

InvocationHandler类中的invoke方法定义了代理对象调用方法时希望执行的动作,其中需要使用反射的方式来调用被代理对象中的方法。

值得注意的是,生成的代理对象不能强转为类类型,只能是接口类型,否则会抛出java.lang.ClassCastException: com.sun.proxy.$Proxy0 cannot be cast to xxx异常。

(4) 动态代理之cglib:

public class CgLibDemo {
    public static void main(String[] args) {
        // 创建被代理对象
        UserDao userDao = new UserDaoImpl();

        // 创建增强器
        Enhancer enhancer = new Enhancer();
        // 注意这里是类,不是接口
        enhancer.setSuperclass(UserDaoImpl.class);
        enhancer.setCallback(new MethodInterceptor() {
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                System.out.println("开启事务");
                // 注意这里不是反射,是方法调用,里边才是反射
                Object proxy = methodProxy.invokeSuper(o, objects);
                System.out.println("提交事务");
                return proxy;
            }
        });

        // 创建代理对象
        UserDao proxy = (UserDao) enhancer.create();

        System.out.println("调用被代理对象方法:");
        userDao.save();

        System.out.println("\n调用代理对象方法:");
        proxy.save();
    }
}
复制代码

CgLib是一种功能强大、高性能的代码生成包。
它为没有实现接口的类提供代理,为JDK的动态代理提供了很好的补充。
SpringAOP中就广泛的使用了CgLib。

值得注意的是,增强器在设置Superclass时设置的是类类型,而不是接口类型。否则会抛出java.lang.NoSuchMethodError: java.lang.Object.save()V异常。

并且,在MethodInterceptor对象的intercept方法中,并不是反射,而是调用invokeSuper方法。如果错误的写成invoke方法,会导致递归调用自身,最终抛出java.lang.StackOverflowError异常。

运行结果:

3. 一些思考

JDK动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。

而cglib动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。

通常情况下,可以使用JDK动态代理来实现代理模式。但是上边提到过,使用JDK动态代理要求被代理的类有实现的接口,如果没有的话,CgLib就是一个更好的选择,它使用的是方法拦截的技术,比使用java反射的JDK动态代理有更高的性能。

参考引用:

代理模式:www.runoob.com/design-patt…
CGLIB(Code Generation Library) 介绍与原理
www.runoob.com/w3cnote/cgl…
Java动态代理详解:www.cnblogs.com/whirly/p/10…