代理模式:

  代理模式是常用的java设计模式,他的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。通过代理模式,可以延迟创建对象,限制访问某个对象,也就是说,提供一组方法给普通用户,特别方法给管理员用户。

UML图:

简单结构示意图:

  为了保持行为的一致性,代理类和委托类通常会实现相同的接口,所以在访问者看来两者没有丝毫的区别。

按照代理的创建时期,代理类可以分为两种:

  • 静态代理:由程序员创建或特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在了。
  • 动态代理:在程序运行时,运用反射机制动态创建而成。

静态代理:

  为了帮助理解代理模式,来看一下静态代理的示例代码(代码摘自里):

Count.java

 /**
* 定义一个账户接口
* @author Administrator
*/
public interface Count {
// 查看账户方法
public void queryCount();
// 修改账户方法
public void updateCount();
}

CountImpl.java

 /**
* 委托类(包含业务逻辑)
* @author Administrator
*/
public class CountImpl implements Count { @Override
public void queryCount() {
System.out.println("查看账户方法...");
} @Override
public void updateCount() {
System.out.println("修改账户方法...");
}
}

CountProxy.java

 public class CountProxy implements Count {
private CountImpl countImpl;
/**
* 覆盖默认构造器
* @param countImpl
*/
public CountProxy(CountImpl countImpl) {
this.countImpl = countImpl;
} @Override
public void queryCount() {
System.out.println("事务处理之前");
// 调用委托类的方法;
countImpl.queryCount();
System.out.println("事务处理之后");
} @Override
public void updateCount() {
System.out.println("事务处理之前");
// 调用委托类的方法;
countImpl.updateCount();
System.out.println("事务处理之后");
}
}

TestCount.java

 /**
*测试Count类
* @author Administrator
*/
public class TestCount {
public static void main(String[] args) {
CountImpl countImpl = new CountImpl();
CountProxy countProxy = new CountProxy(countImpl);
countProxy.updateCount();
countProxy.queryCount();
}
}

  以上静态代理的代码结合前面的结构图和UML图,相信不难理解代理模式的基本原理。

JDK动态代理

  为了提高代理的灵活性和可扩展性,减少重复代码,我们可以使用JDK提供的动态代理。

实现JDK动态代理的步骤:
  1. 通过实现 InvocationHandler 接口创建自己的调用处理器;
  2. 通过为 Proxy 类指定 ClassLoader 对象和一组 interface 来创建动态代理类;
  3. 通过反射机制获得动态代理类的构造函数,其唯一参数类型是调用处理器接口类型;
  4. 通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数被传入。
// InvocationHandlerImpl 实现了 InvocationHandler 接口,并能实现方法调用从代理类到委托类的分派转发
// 其内部通常包含指向委托类实例的引用,用于真正执行分派转发过来的方法调用
InvocationHandler handler = new InvocationHandlerImpl(..); // 通过 Proxy 为包括 Interface 接口在内的一组接口动态创建代理类的类对象
Class clazz = Proxy.getProxyClass(classLoader, new Class[] { Interface.class, ... }); // 通过反射从生成的类对象获得构造函数对象
Constructor constructor = clazz.getConstructor(new Class[] { InvocationHandler.class }); // 通过构造函数对象创建动态代理类实例
Interface Proxy = (Interface)constructor.newInstance(new Object[] { handler });

  实际使用过程更加简单,因为 Proxy 的静态方法 newProxyInstance 已经为我们封装了步骤 2 到步骤 4 的过程:

// InvocationHandlerImpl 实现了 InvocationHandler 接口,并能实现方法调用从代理类到委托类的分派转发
InvocationHandler handler = new InvocationHandlerImpl(..); // 通过 Proxy 直接创建动态代理类实例
Interface proxy = (Interface)Proxy.newProxyInstance( classLoader,
new Class[] { Interface.class },
handler );

JAVA示例代码:

public class TraceHandler implements InvocationHandler{
private Object target = null;
public TraceHandler(Object t) {
this.target = t;
} @Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.print(target);
System.out.print("." + method.getName() + "(");
if(args != null) {
for(int i = 0; i < args.length; ++i) {
System.out.print(args[i]);
if(i < args.length-1) System.out.print(", ");
}
}
System.out.println(")");
return method.invoke(target, args);
}
}

Test.java

public void test() {
Object[] elements = new Object[1000];
for (int i = 0; i < elements.length; i++) {
Integer val = i+1;
TraceHandler handler = new TraceHandler(val);
elements[i] = Proxy.newProxyInstance(null, new Class[]{Comparable.class}, handler);
} Integer key = new Random().nextInt(1000) + 1;
int result = Arrays.binarySearch(elements, key);
if (result > 0) {
System.out.println(elements[result]);
}
}

  Proxy 静态方法生成动态代理类同样需要通过类装载器来进行装载才能使用,它与普通类的唯一区别就是其字节码是由 JVM 在运行时动态生成的而非预存在于任何一个 .class 文件中。每次生成动态代理类对象时都需要指定一个类装载器对象。

动态生成的代理类本身的一些特点:

  1. 包:如果所代理的接口都是 public 的,那么它将被定义在顶层包(即包路径为空),如果所代理的接口中有非 public 的接口(因为接口不能被定义为 protect 或 private,所以除 public 之外就是默认的 package 访问级别),那么它将被定义在该接口所在包(假设代理了 com.ibm.developerworks 包中的某非 public 接口 A,那么新生成的代理类所在的包就是 com.ibm.developerworks),这样设计的目的是为了最大程度的保证动态代理类不会因为包管理的问题而无法被成功定义并访问;
  2. 类修饰符:该代理类具有 final 和 public 修饰符,意味着它可以被所有的类访问,但是不能被再度继承;
  3. 类名:格式是“$ProxyN”,其中 N 是一个逐一递增的阿拉伯数字,代表 Proxy 类第 N 次生成的动态代理类,值得注意的一点是,并不是每次调用 Proxy 的静态方法创建动态代理类都会使得 N 值增加,原因是如果对同一组接口(包括接口排列的顺序相同)试图重复创建动态代理类,它会很聪明地返回先前已经创建好的代理类的类对象,而不会再尝试去创建一个全新的代理类,这样可以节省不必要的代码重复生成,提高了代理类的创建效率。
代理类实例的一特点:       
       在代理类实例上调用其代理的接口中所声明的方法时,这些方法最终都会由调用处理器的 invoke 方法执行,此外,值得注意的是,代理类的根类 java.lang.Object 中有三个方法也同样会被分派到调用处理器的 invoke 方法执行,它们是 hashCode,equals 和 toString。
       当代理的一组接口有重复声明的方法且该方法被调用时,代理类总是从排在最前面的接口中获取方法对象并分派给调用处理器,而无论代理类实例是否正在以该接口(或继承于该接口的某子接口)的形式被外部引用,因为在代理类内部无法区分其当前的被引用类型。

被代理的接口的特点:

  1. 要注意不能有重复的接口,以避免动态代理类代码生成时的编译错误。
  2. 这些接口对于类装载器必须可见,否则类装载器将无法链接它们,将会导致类定义失败。
  3. 需被代理的所有非 public 的接口必须在同一个包中,否则代理类生成也会失败。
  4. 接口的数目不能超过 65535,这是 JVM 设定的限制。

异常处理的特点:

  代理类并不能抛出所有的异常,因为子类覆盖父类或实现父接口的方法时,抛出的异常必须在原方法支持的异常列表之内。
      如果代理产生了接口方法中不支持的异常,它将会抛出 UndeclaredThrowableException 异常。这个异常是一个 RuntimeException 类型,所以不会引起编译错误。通过该异常的 getCause 方法,还可以获得原来那个不受支持的异常对象,以便于错误诊断。

基于CGLIB的动态代理

  由于JDK的动态代理依靠接口实现,如果有些类并没有实现接口,则不能使用JDK代理,这就要使用cglib动态代理了。

CGlib概述:

  • cglib(Code Generation Library)是一个强大的,高性能,高质量的Code生成类库。它可以在运行期扩展Java类与实现Java接口。
  • cglib封装了asm,可以在运行期动态生成新的class。
  • cglib用于AOP,jdk中的proxy必须基于接口,cglib却没有这个限制。

  JDK的动态代理机制只能代理实现了接口的类,而不能实现接口的类就不能实现JDK的动态代理,cglib是针对类来实现代理的,他的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对final修饰的类进行代理。 不多说,直接上代码!

有一个Manager类:

public class Manager {

    public void query() {
System.out.println("query...");
} public void insert() {
System.out.println("insert...");
} public String update() {
System.out.println("update....");
return "I'm update";
} public void delete() {
System.out.println("delete....");
}
}

我们想要在这个类中的每个方法前面和后面都打印一句话,这时候我们就可以使用代理了,让我们来看一下这个代理可以怎么写:

public class AuthProxy implements MethodInterceptor{

    @Override
public Object intercept(Object arg0, Method method, Object[] args,
MethodProxy proxy) throws Throwable {
System.out.println("Before...");
Object result = proxy.invokeSuper(arg0, args);
System.out.println("After....");
return result;
}
}

  如上,CGLIB实现的代理类必须实现MethodInterceptor接口,该接口中只有一个方法需要实现,即intercept方法。通过MethodProxy中的invokeSuper即可执行被代理类的方法。我们继续往下看。

public static Manager getInstace(AuthProxy auth) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Manager.class);
enhancer.setCallback(auth);
return (Manager) enhancer.create();
}

  通过以上代码我们就能得到一个Manager的代理类,被AuthProxy代理。

public void AuthProxyTest() {
AuthProxy auth = new AuthProxy();
Manager manager = ManagerFactory.getInstace(auth);
manager.delete();
System.out.println();
manager.query();
System.out.println();
String result = manager.update();
System.out.println("result: " + result);
}

下面是一个可以代理不同类的代理生成工厂:

public class ManagerFactory2 {

    public static Manager getInstace(Class clasz, AuthProxy auth) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(clasz);
enhancer.setCallback(auth);
return (Manager) enhancer.create();
}
}

对这个工厂进行通用化扩展:

public class ManagerFactory {

    public static Manager getInstace(Class clasz, AuthProxyFilter filter, AuthProxy auth, Object...args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(clasz); Callback[] callback = new Callback[args.length+1];
System.out.println("length: " + callback.length);
callback[0] = auth;
for (int i = 0; i < args.length; i++) {
if (args[i] instanceof Callback) {
callback[i+1] = (Callback) args[i];
}else {
callback[i+1] = NoOp.INSTANCE;
}
} enhancer.setCallbacks(callback);
enhancer.setCallbackFilter(filter);
return (Manager) enhancer.create();
}
AuthProxyFilter.java
public class AuthProxyFilter implements CallbackFilter{

    @Override
public int accept(Method method) {
if ("query".equals(method.getName())) {
return 1;
}
return 0;
}
}

=====================华丽的分割线==================================

源码请猛戳{ 这里

===============================================================

参考资料:

http://www.cnblogs.com/jqyp/archive/2010/08/20/1805041.html

http://www.blogjava.net/stone2083/archive/2008/03/16/186615.html

Java动态代理探讨的更多相关文章

  1. [转]Java动态代理

    动态代理在Java中有着广泛的应用,比如Spring AOP,Hibernate数据查询.测试框架的后端mock.RPC,Java注解对象获取等.静态代理的代理关系在编译时就确定了,而动态代理的代理关 ...

  2. java动态代理基本原理及proxy源码分析一

    本系列文章主要是博主在学习spring aop的过程中了解到其使用了java动态代理,本着究根问底的态度,于是对java动态代理的本质原理做了一些研究,于是便有了这个系列的文章 为了尽快进入正题,这里 ...

  3. Java 动态代理机制详解

    在学习Spring的时候,我们知道Spring主要有两大思想,一个是IoC,另一个就是AOP,对于IoC,依赖注入就不用多说了,而对于Spring的核心AOP来说,我们不但要知道怎么通过AOP来满足的 ...

  4. Java动态代理全面分析

    代理模式 解说:给某一个对象提供一个代理,并由代理对象控制对原对象的引用: 代理模式需要以下几个角色: 1  主题:规定代理类和真实对象共同对外暴露的接口: 2  代理类:专门代理真实对象的类: 3 ...

  5. JAVA动态代理模式(从现实生活角度理解代码原理)

    所谓动态代理,即通过代理类:Proxy的代理,接口和实现类之间可以不直接发生联系,而可以在运行期(Runtime)实现动态关联. java动态代理主要是使用java.lang.reflect包中的两个 ...

  6. Java 动态代理作用是什么?

    Java 动态代理作用是什么?   1 条评论 分享   默认排序按时间排序 19 个回答 133赞同反对,不会显示你的姓名 Intopass 程序员,近期沉迷于动漫ING 133 人赞同 ① 首先你 ...

  7. java动态代理原理

    我们经常会用到Java的动态代理技术, 虽然会使用, 但是自己对其中的原理却不是很了解.比如代理对象是如何产生的, InvocationHandler的invoke方法是如何调用的?今天就来深究下Ja ...

  8. java 动态代理示例,带主要注释

    Java proxy是基于反射,仅仅支持基于接口的动态代理. java 动态代理是一切架构的基础,必须了解. 废话少说,先上代码获得感性认识. 示例代码有主要注释. 接口: public interf ...

  9. java动态代理浅析

    最近在公司看到了mybatis与spring整合中MapperScannerConfigurer的使用,该类通过反向代理自动生成基于接口的动态代理类. 于是想起了java的动态代理,然后就有了这篇文章 ...

随机推荐

  1. MySQLNonTransientConnectionException

    将mysql-connector-java和druid升级到最新版本: 将驱动设置为driver-class-name: com.mysql.cj.jdbc.Driver url要加上时区设置:url ...

  2. Mysql 获取当天,昨天,本周,本月,上周,上月的起始时间

    转自: http://www.cppblog.com/tx7do/archive/2017/07/19/215119.html -- 今天 SELECT DATE_FORMAT(NOW(),'%Y-% ...

  3. ABAP-动态ALV

    1.参数定义 "ALV type-pools:slis,rsds,vrm. data:gt_fieldcat type lvc_t_fcat with header line, gt_eve ...

  4. Windows7 64bit+python3.6环境下安装OpenCV3.3

    安装opencv3.3 打开windows的Python扩展包网址 根据自己的系统选择下载,这里我选择的是 通过pip3安装该whl文件,使用如下命令  pip3 install 该whl的绝对路径 ...

  5. Hibernate Annotation 设置字段的默认值

    很简单,不过有点莫名其妙的意思 @Column(name="powerLoad",columnDefinition="bit(1) default 1 ") p ...

  6. python中的__name__=='__main__'如何简单理解(一)

    1. 摘要: 通俗的理解_name_ == '_main_':假如你叫小明.py,在朋友眼中,你是小明(_name_ == '小明'):在你自己眼中,你是你自己(_name_ == '_main_') ...

  7. sudoers的权限被改,又忘记了root密码,又不能重启。这么做。

    报下面这个错 sudo: /etc/sudoers is world writablesudo: no valid sudoers sources found, quittingsudo: unabl ...

  8. zabbix出现中文不能选的情况

    像这里一样,有些选项是选不了的,这个时候我们要做的第一步就是,找到这个配置文件. 如果不知道在哪里的话可以用find命令查找. sudo find / -name locales.inc.php 找到 ...

  9. Path-O-LOGIC Keynote

    [Path-O-LOGIC Keynote] 1. OnSpawned()OnSpawned(SpawnPool pool) 2. OnDespawned()OnDespawned(SpawnPool ...

  10. 唯快不破:Web应用的13个优化步骤

    https://mp.weixin.qq.com/s?__biz=MjM5NzA1MTcyMA==&mid=2651163004&idx=2&sn=2b1be8014abf19 ...