Java设计模式—代理模式之动态代理

前面学习了代理模式的静态代理,今天说说动态代理。首先谈谈静态代理与动态代理的区别。

静态代理:代理类和真实主题类都是事先存在的,在程序运行之前代理的.class文件就已经生成了。
动态代理:代理类是在系统运行时根据实际动态创建的,可以让同一个代理类能够代理多个不同的真实主题类。试想,如果需要为不同的真实主题类提供代理类,使用静态代理的方法就必须创建多个代理类,这将导致系统中类的个数增加。这就是为什么要使用动态代理的原因

下面看看怎么实现动态代理:
从JDK1.3开始,Java语言提供了动态代理类的支持,Java实现动态代理类需要使用java.lang.reflect包中一些类,下面先对所用的到的类及方法作简单说明,可以根据后面的实例一起参考。
1.Proxy类:
Proxy类中有个静态方法public static Object new ProxyInstance(ClassLoader loader,Class <?> [] interfaces, InvocationHandler h)
该方法返回有个动态创建的代理类的实例。先来看看这三个参数:
①第一个参数loader表示代理类的类加载器,通常使用 抽象主题角色.class.getClassLoader()
②第二个参数interfaces表示代理类所实现的接口列表(同真实主题类的接口列表一致),通常使用真实主题对象.getClass().getInterfaces()表示
③第三个参数表示程序调用类的对象,这个对象请看动态代理用到的另一个接口

2.InvocationHandler接口:
先看javaAPI文档对这个接口的描述:

InvocationHandler is the interface implemented by the invocation handler of a proxy instance. 

Each proxy instance has an associated invocation handler. When a method is invoked on a proxy instance, 
the method invocation is encoded and dispatched to the invoke method of its invocation handler.

每一个动态代理类都必须要实现InvocationHandler这个接口,该接口作为代理实例的调用处理者的公共父类,并且每个代理类的实例都关联到了一个handler(为动态代理类设置一个带真实主题对象为参数的构造方法),当我们通过代理对象调用一个方法的时候,这个方法就会自动调用此接口中的invoke方法来处理。我们来看看InvocationHandler这个接口的唯一一个方法 invoke 方法:
public Object invoke(Object proxy,Method method,Object[] args)
该方法用于处理对代理类实例的方法调用并返回相应的结果,当一个代理实例中的业务方法被调用时将自动调用该方法。来看看这三个参数:
①第一个参数proxy表示代理类的实例
②第二个参数method表示需要代理的方法
③第三个参数args表示代理方法的参数数组

下面通过一个简单的实例学习怎么使用动态代理模式:
为公司OA系统数据访问层DAO增加方法调用日志,记录每一个方法被调用的时间和调用结果。


/**
 * @description:抽象主题角色:抽象用户DAO类
 */
public interface AbstractUserDAO {
    public Boolean findUserById(String userId);
}
/**
 * @description:抽象主题角色:抽象文档DAO类
 */
public interface AbstractDocumentDao {
    public Boolean deleteDocumentById(String documentId);
}
/**
 * @description:真实主题角色:用户DAO类
 */
public class UserDAO implements AbstractUserDAO{
    @Override
    public Boolean findUserById(String userId) {
        if (userId.equalsIgnoreCase("张无忌")){
            System.out.println("查询ID为" + userId + "的用户信息成功");
            return true;
        }else {
            System.out.println("查询ID为" + userId + "的用户信息失败");
            return false;
        }
    }
}
/**
 * @description:真实主题角色:文档DAO类
 */
public class DocumentDAO implements AbstractDocumentDao{
    @Override
    public Boolean deleteDocumentById(String documentId) {
        if (documentId.equalsIgnoreCase("D001")){
            System.out.println("删除ID为" + documentId + "的文档信息成功");
            return true;
        }else {
            System.out.println("删除ID为" + documentId + "的文档信息失败");
            return false;
        }
    }
}

这四个类和静态代理是相同的,都是抽象主题角色和真实主题角色,真实主题角色继承了抽象主题角色的接口,实现了调用方法。下面来看调用处理类:

/**
 * @description:自定义请求调用处理程序类
 */
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Calendar;
import java.util.GregorianCalendar;

public class DAOLogHandler implements InvocationHandler {
    private Calendar calendar;
    private Object object;

    public DAOLogHandler(){
    }

    //自定义一个构造函数,使用真实主题对象作为参数来构造一个指定的调用处理类
    public DAOLogHandler(Object o){
        this.object = o;
    }

    public Object invoke(Object proxy, Method method,Object[] args)throws Throwable{
        beforeInvoke();
        Object result = method.invoke(object,args); //真实主题对象和参数数组
        System.out.println("Second Parameter method:" + method);
        afterInvoke();
        return result;
    }

    //记录方法开始调用的时间
    public void beforeInvoke(){
        calendar = new GregorianCalendar();
        int hour = calendar.get(Calendar.HOUR_OF_DAY);
        int minute = calendar.get(Calendar.MINUTE);
        int second = calendar.get(Calendar.SECOND);
        String time = hour + ":" + minute + ":" + second;
        System.out.println("调用时间:" + time);
    }

    public void afterInvoke(){
        System.out.println("方法调用结束!");
    }
}

这个类中的invoke方法其实相当于静态代理方法中代理类的方法,客户端动态创建一个代理实例之后,调用真实对象的方法就会自动调用此invoke方法。

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

public class Client {
    public static void main(String args[]){
        InvocationHandler handler = null;
        AbstractUserDAO userDAO = new UserDAO();
        handler = new DAOLogHandler(userDAO);
        AbstractUserDAO proxy_user;

        //动态创建一个AbstractUserDAO的实例,使用代理类的父类作为类加载器(多态)
        proxy_user = (AbstractUserDAO)Proxy.newProxyInstance(AbstractUserDAO.class.getClassLoader(),userDAO.getClass().getInterfaces(),handler);
        proxy_user.findUserById("张无忌");
        //proxy_user返回的既不是AbstractUserDAO类型的对象,也不是InvocationHandler类型的对象
        System.out.println(proxy_user.getClass().getName());

        System.out.println("---------------------------------");
        AbstractDocumentDao daoDAO = new DocumentDAO();
        handler = new DAOLogHandler(daoDAO);
        AbstractDocumentDao proxy_document;

        proxy_document = (AbstractDocumentDao)Proxy.newProxyInstance(AbstractDocumentDao.class.getClassLoader(),daoDAO.getClass().getInterfaces(),handler);
        proxy_document.deleteDocumentById("D002");
        System.out.println(proxy_document.getClass().getName());
        }
    }/**
      *调用时间:9:40:35
      * 查询ID为张无忌的用户信息成功
      * Second Parameter method:public abstract java.lang.Boolean net.fuqian.learn.java.大话设计模式.代理模式.DynamicProxy.AbstractUserDAO.findUserById(java.lang.String)
      * 方法调用结束!
      * com.sun.proxy.$Proxy0
      * ---------------------------------
      * 调用时间:9:40:36
      * 删除ID为D002的文档信息失败
      * Second Parameter method:public abstract java.lang.Boolean net.fuqian.learn.java.大话设计模式.代理模式.DynamicProxy.AbstractDocumentDao.deleteDocumentById(java.lang.String)
      * 方法调用结束!
      * com.sun.proxy.$Proxy1
      */

其实,实现过程就是Proxy类中的newProxyInstance静态方法封装了如何利用该方法的第一个参数:代理类的类加载器和第二个参数实现的接口列表来动态创建一个代理实例,中间生成了$Proxy0类从上面代码中 System.out.println(proxy_document.getClass().getName())输出可以看出。然后该类持有一个带调用处理类为参数的构造方法。更深层次的理解可以参考:https://www.cnblogs.com/gonjan-blog/p/6685611.html

但是,JDK动态代理只能对接口进行代理,对于普通类和抽象类,JDK动态代理无法实现,这就是JDK动态代理的缺点。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值