设计模式-代理模式

来源:互联网 发布:网络布线教程 编辑:程序博客网 时间:2024/06/08 11:36

简介

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

代理模式的思想是为了提供额外的处理或者不同的操作而在实际对象与调用者之间插入一个代理对象。这些额外的操作通常需要与实际对象进行通信。

代理模式是一种设计模式,简单说即是在 不改变源码 的情况下,实现对 目标对象功能扩展

代理模式中的角色

  • 公共接口:目标对象和代理对象共同实现的接口
  • 目标对象:
  • 代理对象:

角色关系UML:

这里写图片描述

代理模式是java中最常用的设计模式之一,尤其是在spring框架中广泛应用。对于java的代理模式,一般可分为:静态代理、动态代理、以及CGLIB实现动态代理。

对于上述三种代理模式,分别进行说明。

场景:
UserDao:实现用户数据访问的接口
UserDaoImpl:实现UserDao接口,实现用户数据访问的具体逻辑
UserDaoProxy:实现UserDao接口,实现对UserDaoImpl的代理,添加事务管理

静态代理

静态代理其实就是在程序运行之前,提前写好被代理方法的代理类,编译后运行。在程序运行之前,class已经存在。

UML图如下:

这里写图片描述

java代码:

接口:

package demo4;/** *  * @ClassName: UserDao * @Description: 用户数据访问接口 * @author cheng * @date 2017-8-11 下午12:35:45 */public interface UserDao {    /**     *      * @Title: save     * @Description:保存     */    void save();}

目标对象:

package demo4;/** *  * @ClassName: UserDaoImpl * @Description: 用户数据访问实现 * @author cheng * @date 2017-8-11 下午12:36:45 */public class UserDaoImpl implements UserDao {    /**     * 重写     */    @Override    public void save() {        System.out.println("保存用户信息");    }}

代理对象:

package demo4;/** *  * @ClassName: UserDaoProxy * @Description: 代理 * @author cheng * @date 2017-8-11 下午12:37:42 */public class UserDaoProxy implements UserDao {    private UserDao userDao;    public UserDaoProxy(UserDao userDao) {        this.userDao = userDao;    }    /**     * 重写     */    @Override    public void save() {        System.out.println("开始事务");        userDao.save();        System.out.println("提交事务");    }}

测试:

package demo4;/** *  * @ClassName: ClientTest * @Description: 测试 * @author cheng * @date 2017-8-11 下午12:40:31 */public class ClientTest {    public static void main(String[] args) {        // 目标对象        UserDao userDaoImpl = new UserDaoImpl();        // 代理对象        UserDao userDaoProxy = new UserDaoProxy(userDaoImpl);        // 执行代理对象的方法        userDaoProxy.save();    }}

运行结果:
这里写图片描述

总结:其实这里做的事情无非就是,创建一个代理类UserDaoProxy,继承了UserDao接口并实现了其中的方法。只不过这种实现特意 包含 了目标对象的方法,正是这种特征使得看起来像是 “扩展” 了目标对象的方法。假使代理对象中只是简单地对save方法做了另一种实现而没有包含目标对象的方法,也就不能算作代理模式了。所以这里的 包含 是关键。

缺点:这种实现方式很直观也很简单,但其缺点是代理对象必须 提前写出 ,如果接口层发生了变化,代理对象的代码也要进行维护。如果能在运行时动态地写出代理对象,不但减少了一大批代理类的代码,也少了不断维护的烦恼,不过运行时的效率必定受到影响。这种方式就是接下来的动态代理。

动态代理(JDK代理)

动态代理主要是通过反射机制,在运行时动态生成所需代理的class.

UML图如下:

这里写图片描述

java代码:

接口:

package demo4;/** *  * @ClassName: UserDao * @Description: 用户数据访问接口 * @author cheng * @date 2017-8-11 下午12:35:45 */public interface UserDao {    /**     *      * @Title: save     * @Description:保存     */    void save();}

目标对象:

package demo4;/** *  * @ClassName: UserDaoImpl * @Description: 用户数据访问实现 * @author cheng * @date 2017-8-11 下午12:36:45 */public class UserDaoImpl implements UserDao {    /**     * 重写     */    @Override    public void save() {        System.out.println("保存用户信息");    }}

动态代理对象处理器(注意:不是动态代理对象):

package demo4;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;/** *  * @ClassName: UserDaoDynamicProxy * @Description: 动态代理对象处理器 * @author cheng * @date 2017-8-11 下午02:26:59 */public class UserDaoDynamicProxyHandler implements InvocationHandler {    private UserDao userDao;    public UserDaoDynamicProxyHandler(UserDao userDao) {        this.userDao = userDao;    }    /**     * 重写接口中的方法,实现目标对象的方法代理     */    @Override    public Object invoke(Object proxy, Method method, Object[] args)            throws Throwable {        System.out.println("==================开始事务=======================");        // 执行目标对象的方法        Object result = method.invoke(userDao, args);        System.out.println("==================提交事务=======================");        return result;    }}

测试:

package demo4;import java.lang.reflect.Proxy;/** *  * @ClassName: ClientTest * @Description: 测试 * @author cheng * @date 2017-8-11 下午12:40:31 */public class ClientTest {    public static void main(String[] args) {        //目标对象        UserDao userDaoImpl = new UserDaoImpl();        //动态代理对象处理器        UserDaoDynamicProxyHandler userDaoDynamicProxyHandler = new UserDaoDynamicProxyHandler(userDaoImpl);        //生成与目标对象类型相同的动态代理对象,即目标对象实现的接口类型        UserDao userDaoDynamicProxy = (UserDao) Proxy.newProxyInstance(                UserDaoImpl.class.getClassLoader(), //ClassLoader loader: 指定当前目标对象使用类加载器,写法固定                UserDaoImpl.class.getInterfaces(), //Class<?>[] interfaces: 目标对象实现的接口的类型,写法固定                userDaoDynamicProxyHandler);//InvocationHandler h :事件处理接口,需传入一个实现类,一般直接使用匿名内部类        //执行动态代理对象的方法        userDaoDynamicProxy.save();    }}

运行结果:
这里写图片描述

调用Proxy类的静态方法newProxyInstance即可,该方法会返回代理类对象

static Object newProxyInstance(    ClassLoader loader, //指定当前目标对象使用类加载器,写法固定=>目标对象类名.class.getClassLoader() 或者 目标对象.getClass().getClassLoader()    Class<?>[] interfaces,//目标对象实现的接口的类型,写法固定=>目标对象类名.class.getInterfaces() 或者 目标对象.getClass().getInterfaces()    InvocationHandler h //事件处理接口,需传入一个实现类,一般直接使用匿名内部类)

Invocation直接使用匿名内部类实现,测试代码如下,运行结果不变

public static void main(String[] args) {    // 目标对象.此处必须加final修饰符    final UserDao userDaoImpl = new UserDaoImpl();    // 生成动态代理对象    UserDao userDaoDynamicProxy = (UserDao) Proxy.newProxyInstance(            userDaoImpl.getClass().getClassLoader(),             userDaoImpl.getClass().getInterfaces(),             new InvocationHandler() {                /**                 * 重写接口中的方法,实现目标对象的方法代理                 */                @Override                public Object invoke(Object proxy, Method method,                        Object[] args) throws Throwable {                    System.out.println("==================开始事务=======================");                    // 执行目标对象的方法                    Object result = method.invoke(userDaoImpl, args);                    System.out.println("==================提交事务=======================");                    return result;                }            });    //执行动态代理对象的方法    userDaoDynamicProxy.save();}

无论是动态代理还是静态带领,都需要定义接口,然后才能实现代理功能。这同样存在局限性,因此,为了解决这个问题,出现了第三种代理方式:cglib代理。

cglib代理

CGLib采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。JDK动态代理与CGLib动态代理均是实现Spring AOP的基础。

前提条件:

  • 需要引入cglib的jar文件,由于Spring的核心包中已经包括了Cglib功能,所以也可以直接引入 spring-core.jar
  • 目标类不能为final
  • 目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法

UML图如下:

这里写图片描述

java代码:

目标对象(不用实现接口):

package demo5;/** *  * @ClassName: UserDaoImpl * @Description: 目标对象 * @author cheng * @date 2017-8-11 下午03:26:45 */public class UserDaoImpl {    /**     *      * @Title: save     * @Description:目标对象中的方法     */    public void save() {        System.out.println("保存用户信息");    }}

生成代理对象的工厂:

package demo5;import java.lang.reflect.Method;import net.sf.cglib.proxy.Enhancer;import net.sf.cglib.proxy.MethodInterceptor;import net.sf.cglib.proxy.MethodProxy;/** *  * @ClassName: ProxyFactory * @Description: cglib子类代理工厂 * @author cheng * @date 2017-8-11 下午03:31:19 */public class ProxyFactory implements MethodInterceptor {    // 持有目标对象的引用    private Object target;    public ProxyFactory(UserDaoImpl target) {        this.target = target;    }    // 给目标对象创建一个代理对象    public Object getProxyInstance() {        // 1.工具类        Enhancer en = new Enhancer();        // 2.设置父类        en.setSuperclass(target.getClass());        // 3.设置回调函数        en.setCallback(this);        // 4.创建子类(代理对象)        return en.create();    }    /**     * 重写接口中的方法,实现目标对象方法的拦截     */    @Override    public Object intercept(Object obj, Method method, Object[] args,            MethodProxy proxy) throws Throwable {        System.out.println("====================开始事务===================");        // 执行目标方法        Object result = method.invoke(target, args);        System.out.println("====================提交事务===================");        return result;    }}

测试:

package demo5;/** *  * @ClassName: ClientTest * @Description: 测试 * @author cheng * @date 2017-8-11 下午03:40:50 */public class ClientTest {    public static void main(String[] args) {        // 目标对象        UserDaoImpl userDaoImpl = new UserDaoImpl();        // 代理对象        UserDaoImpl userDaoProxy = (UserDaoImpl) new ProxyFactory(userDaoImpl).getProxyInstance();        // 执行代理对象中的方法        userDaoProxy.save();    }}

运行结果:
这里写图片描述

代理对象的生成过程由Enhancer类实现,大概步骤如下:

1、生成代理类Class的二进制字节码;

2、通过Class.forName加载二进制字节码,生成Class对象;

3、通过反射机制获取实例构造,并初始化代理类对象。

总结:

三种代理模式各有优缺点和相应的适用范围,主要看目标对象是否实现了接口。以Spring框架所选择的代理模式举例

在Spring的AOP编程中:

如果加入容器的目标对象有实现接口,用JDK代理

如果目标对象没有实现接口,用Cglib代理

原创粉丝点击