一、代理的概念:

定义:为其他对象提供一种代理,以控制对这个对象的访问。

关于Java中的代理,有一种常用的设计模式---代理模式;对于代理,根据创建代理类的时间点的不同,又可以分为:静态代理和动态代理。

代理模式,是一种常用的Java设计模式,其特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。简单地说,在访问实际对象时,是通过代理对象来访问的,代理模式就是在访问实际对象时引入一定程度的间接性,因为这种间接性可以附加多种用途,在实际开发中这种间接性会带来很多的便捷。

二、代理的作用:

1. 远程代理:为位于两个不同地址空间对象的访问提供一种实现机制,可以将一些消耗资源较多的对象和操作移至性能更好的计算机上,提高系统的整体运行效率。

2. 虚拟代理:通过一个消耗资源较少的对象来代表一个消耗资源较多的对象,可以在一定程度上节省系统的运行开销。

3. 缓冲代理:为某个操作的结果提供临时的缓存存储空间,以便在后续使用中能够共享这些结果,优化系统性能,缩短执行时间。

4. 保护代理:可以控制对一个对象的访问权限,为不同的用户提供不同级别的使用权限。

5. 智能引用:要为一个对象的访问(引用)提供一些额外的操作时可以使用。

三、静态代理:指预先确定了代理者与被代理者的关系,在编译期间就确定了代理类与被代理类之间的依赖关系。

静态代理,由程序员创建或特定工具自动生成源代码,也就是在编译时就已经将接口、被代理类、代理类等确定下来。在程序运行之前,代理类的 .class 文件就已经生成。

代理模式最主要的就是有一个公共接口、一个具体的类、一个代理类,代理类持有具体类的实例,代为执行具体类实例方法。上面说到,代理模式就是在访问实际对象时引入一定程度的间接性,因为这种间接性,可以附加多种用途。这里的间接性就是指不直接调用实际对象的方法,那么我们在代理过程中就可以加上一些其他的用途。

/*** 共用接口*/
public interface Person {public void working(String work);      // 工作public void goOutMethod(String method);        // 出行方式
}
/*** 学生类*/
public class Student implements Person {@Overridepublic void working(String work) {// TODO Auto-generated method stubSystem.out.println("工作内容是:"+work);}@Overridepublic void goOutMethod(String method) {// TODO Auto-generated method stubSystem.out.println("出行方式是:"+method);}
}
/*** 老师类*/
public class Teacher implements Person {@Overridepublic void working(String work) {// TODO Auto-generated method stubSystem.out.println("工作内容是:"+work);}@Overridepublic void goOutMethod(String method) {// TODO Auto-generated method stubSystem.out.println("工作内容是:"+method);}
}
/*** 代理类*/
public class ProxyClass implements Person {Person person;   //要代理的对象// 代理类构造方法public ProxyClass(Person per) {this.person = per;}@Overridepublic void working(String work) {// TODO Auto-generated method stubperson.working(work);}@Overridepublic void goOutMethod(String method) {// TODO Auto-generated method stubperson.goOutMethod(method);}
}
/*** 创建代理对象的静态代理工厂类*/
public class ProxyFactory {public static Person getProxyStudent(){return new ProxyClass(new Student());}public static Person getProxyTeacher(){return new ProxyClass(new Teacher());}
}
/*** 创建静态代理类实际的调用类*/
public class MainClass {public static void main(String[] args) {System.out.println("=================================");ProxyFactory.getProxyStudent().goOutMethod("坐公交车");ProxyFactory.getProxyStudent().working("学习");System.out.println("---------------------------------");ProxyFactory.getProxyStudent().goOutMethod("骑自行车");ProxyFactory.getProxyStudent().working("玩游戏");System.out.println("=================================");ProxyFactory.getProxyTeacher().goOutMethod("私家车");ProxyFactory.getProxyTeacher().working("教书");System.out.println("---------------------------------");ProxyFactory.getProxyStudent().goOutMethod("摩托车");ProxyFactory.getProxyStudent().working("课外辅导");}
}

以上代码运行结果如下:

四、动态代理

动态代理与静态代理的区别在于,动态代理的代理者与被代理者的关系是动态的,是在运行时确定的;

代理类在程序运行时创建的代理方式被称为动态代理。例如以上静态代理的例子中,代理类 ProxyClass 是开发人员自己定义好的,在程序运行之前就已经编译完成的。而动态代理,代理类并不是在Java代码中定义的,而是在运行时,根据我们在Java代码中的 “指示” ,动态生成的。相对于静态代理,动态代理的优势在于,可以很方便的对代理类的函数/方法进行统一的处理,而不用修改每个代理类中的方法。

动态代理的优势在于,其具有灵活性、较强的可扩展性。

在业务中使用动态代理,一般是为了给需要实现的方法添加预处理或者添加后续操作,但是不干预实现类的正常业务,把一些基本业务和主要的业务逻辑分离开来。

五、动态代理的实现

Java动态代理有两种实现方式:① 基于JDK的动态代理;② 基于CGLIB的动态代理;

(1) 基于JDK的动态代理:JDK提供了:①. InvocationHandler(接口);②. Proxy(类);

这个类和接口是实现动态代理必须用到的。通过这个类和这个接口可以生成JDK动态代理类和动态代理对象。

InvocationHandler 接口是交由动态代理类实现的,负责处理被代理对象的操作的,而 Proxy是用来创建动态代理类实例对象的,因为只有得到了这个对象,才能调用那些需要代理的方法。

创建一个动态代理对象的步骤:
                * 创建一个 InvocationHandler对象:

// 创建一个 InvocationHandler 对象
InvocationHandler stuHandler = new MyInvocationHandler<Person>(stu);

* 使用 Proxy 类的 getProxyClass 静态方法生成一个动态代理类 stuProxyClass:

Class<?> stuProxyClass = Proxy.getProxyClass(Person.class.getClassLoader(), new Class<?>[] {Person.class});

* 获取 stuProxyClass 中一个带 InvocationHandler 参数的构造器 constructor:

Constructor<?> constructor = PersonProxy.getConstructor(InvocationHandler.class);

* 通过构造器 constructor 来创建一个动态实例 stuProxy:

Person stuProxy = (Person) constructor.newInstance(stuHandler);

至此,一个动态代理对象就创建完毕了;当然,上面四个步骤可以通过 Proxy 类的 newProxyInstance()方法来简化:

// 创建一个与代理对象相关联的 InvocationHandler
InvocationHandler stuHandler = new MyInvocationHandler<Person>(stu);// 创建一个代理对象 stuProxy,代理对象的每个执行方法都会替换执行 Invocation 中的 invoke()方法
Person stuProxy = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class<?>[] {Person.class}, stuHandler);

案例:

/*** 创建需要动态代理的接口*/
public interface Subject {void eatting(String param);void sleeping(String param);void running(String param);
}
/*** 创建接口的实现类*/
public class SubjectImpl implements Subject {@Overridepublic void eatting(String param) {// TODO Auto-generated method stubSystem.out.println(param+"正在吃饭....");}@Overridepublic void sleeping(String param) {// TODO Auto-generated method stubSystem.out.println(param+"正在睡觉....");}@Overridepublic void running(String param) {// TODO Auto-generated method stubSystem.out.println(param+"正在跑步....");}
}
/*** 创建SubjectImpl的代理类*/
public class SubjectProxy implements InvocationHandler {private Subject subject;// 构造方法public SubjectProxy(subjectsubj) {super();subject= subj;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// TODO Auto-generated method stubSystem.out.println("======================================= Begin =======================================");System.out.println("method="+method);System.out.println("args="+args);String name = method.getName();System.out.println("Name="+name);Object clazz = method.getClass();System.out.println("Clazz="+clazz);Object invoke = method.invoke(subject, args);System.out.println("======================================= End =======================================");return invoke;}
}

创建 SubjectProxy 类,实现 InvocationHandler 接口,这个类中持有一个被代理对象的实例:subject。InvocationHandler中有一个 invoke() 方法,所有执行代理对象的方法都会被替换成执行 invoke() 方法。

再在 invoke() 方法中执行被代理对象 subject 的相应方法。当然,在代理过程中,我们在真正执行被代理对象的方法前加入自己其他处理。这也是 Spring 的 AOP 实现的主要原理,这里还涉及到一个很重要的关于 Java反射 方面的基础知识。

对以上代理类中部分代码的说明:

proxy:代表的是代理对象;

method:调用类所调用的被代理类中的方法;也就是正在执行的方法;

args:代表当前执行方法传入的实际参数;

invoke()方法:通过反射,调用被代理类中的实际方法;

返回值invoke:为被代理类中,被调用方法执行结果的返回值,若没有,则为null。

/*** 创建动态代理类实际的调用类*/
public class MainClass {public static void main(String[] args) {Subject subject = new SubjectImpl();InvocationHandler subjectProxy = new SubjectProxy(subject);Subject proxyInstance = (Subject) Proxy.newProxyInstance(subjectProxy.getClass().getClassLoader(), subject.getClass().getInterfaces(), subjectProxy);proxyInstance.sleeping("Gavin Lee");proxyInstance.eatting("James Wei");proxyInstance.running("Even Liu");}
}

对以上调用类中部分代码的说明:

subjectProxy.getClass().getClassLoader():获取代理类的类加载器;

subject.getClass().getInterfaces():获取被代理类的接口;如果有多个,则以数组的形式传入;

subjectProxy:代理类实例对象;

执行结果如下:


       从结果可以看出,实际上在Subject类中只会输出一条结果,但是在被代理后,实际调用的方法是 SubjectProxy 的 invoke 方法,这样可以在不修改业务类的情况下对业务类增加如日志等其他操作,甚至可以直接修改有返回值方法的返回值。

所以,JDK动态代理实现的原理如下:

JDK的动态代理实现方法是依赖于接口的,首先使用接口来定义好操作的规范,然后通过 Proxy 类产生的代理对象调用被代理对象的操作,而这个操作又被分发给 InvocationHandler 接口的 invoke() 方法来具体执行:

@Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { ...}

前面提到,动态代理的优势在于可以很方便的对代理类的函数/方法进行统一的处理,而不用修改每个代理类中的方法;是因为所有被代理执行的方法都是通过在 InvocationHandler 中的 invoke() 方法调用的,所以我们只要在 invoke()方法中统一处理,就可以对所有被代理的方法进行相同的操作了。

动态代理的过程,代理对象和被代理对象的关系不像静态代理那样一目了然,清晰明了。因为动态代理的过程中,我们并没有实际看到代理类,也没有很清晰地看到代理类的具体样子,而且动态代理中被代理对象和代理对象是通过 InvocationHandler 来完成的代理过程的。

(2) 基于CGLIB (Code Generation Library) 的动态代理:

基于JDK 的动态代理一定要实现一个接口,只能针对实现了接口的类做动态代理,而不能对没有实现接口的类做动态代理;而绝大部分情况是基于 POJO 类的动态代理,那么CGLIB 就是一个较好的选择;CGLib是一个强大、高性能的Code生成类库,它可以在程序运行期间动态扩展类或者接口,它的底层是使用Java字节码操作框架ASM实现的。在 Hibernate 框架中 PO 的字节码生产工作就是靠 CGLIB 来完成的。

① 引入 CGLIB 的 jar 包;

② 创建代理类:被代理的类没有实现任何接口;

public class CGsubject {public void sayHello(){System.out.println("hello world");}
}

如果直接对这个类创建对象,那么调用 sayHello 方法,控制台就会输出 hello world,现在我们还是要对输出添加前、后置的 log 输出,来打印输出前和输出后的时间。

③ 实现 MethodInterceptor 接口,对方法进行拦截处理。

public class HelloInterceptor implements MethodInterceptor{@Overridepublic Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {System.out.println("begin time -----> "+ System.currentTimeMillis());Object o1 = methodProxy.invokeSuper(o, objects);System.out.println("end time -----> "+ System.currentTimeMillis());return o1;}
}

对以上类拦截器中部分代码的说明:

Object o:被代理对象的实例;

methodProxy:代理方法;

invokeSuper()方法:调用被拦截的方法;

④ 定义动态代理工厂,生成动态代理:

public class ProxyFactory {public static Object getGcLibDynProxy(Object target){Enhancer enhancer=new Enhancer();enhancer.setSuperclass(target.getClass());enhancer.setCallback(new HelloInterceptor ());Object targetProxy= enhancer.create();return targetProxy;}
}

⑤ 客户端调用

  public static void main(String[] args) {Frank cProxy= (Frank) ProxyFactory.getGcLibDynProxy(new CGsubject ());cProxy.sayHello();}

可见,通过 CGLib 对没有实现任何接口的类做了动态代理,达到了和前面一样的效果。

所以,CGLib 动态代理实现的原理如下:

动态生成一个要代理类的字类,字类重写要代理的类的所有不是 final 的方法。在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。它比使用Java反射的JDK动态代理要快。

CGLib底层:使用字节码处理框架ASM,来转换字节码并生成新的类。不鼓励直接使用ASM,因为它要求你必须对JVM内部结构,包括class文件的格式和指令都很熟悉

CGLib的缺点:对于 final 方法无法进行代理。

五、动态代理在AOP中的应用:

AOP是一种编程范式,其目标是通过隔离切面耦合来增加程序的模块化【维基百科】。

AOP是OOP的补充,其地位及其重要性远不及OOP,总体来说,OOP面向名词领域,而AOP面向动词领域,例如对一个人的设计肯定是使用OOP,例如这个人有手、脚、眼等属性。而对这个人吃、穿、住、行等动作就会涉及到AOP,要理解AOP首先要理解什么是切面耦合(cross-cuttiing concerns)。例如,要求为一个程序中所有方法名称以 test 开头的方法打印一句 log,这个行为就是一个滇西那个的 cross-cutting 场景。首先这个打印 log 和具体的业务毫无关系,然后其处于分散在整个程序当中的各个模块,如果按照原始的方法开发,一旦后期需求发生变动将会机器繁琐;所以就需要将这个切面耦合封装隔离,不要将其混入业务代码当中。

例如在上面JDK动态代理的例子中,如果需要在打印出各自正在做的事之后,插入一条紧急通知:立即集合!!! 的命令;若不使用代理的话,就需要将该通知写在相应的业务逻辑里面,在SubjectImpl的每个业务方法中添加该通知;而在使用了动态代理之后,就只需要在 InvocationHandler里面的 invoke()方法中添加该通知就可以了,使该新插入的通知不会侵入到业务代码当中,在以后的维护过程中对业务毫无影响。

添加代码:

/*** 创建SubjectImpl的代理类*/
public class SubjectProxy implements InvocationHandler {private Subject subject;// 构造方法public SubjectProxy(Subject subj) {super();subject = subj;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// TODO Auto-generated method stubSystem.out.println("======================================= Begin =======================================");System.out.println("method="+method);System.out.println("args="+args);String name = method.getName();System.out.println("Name="+name);Object clazz = method.getClass();System.out.println("Clazz="+clazz);Object invoke = method.invoke(subject, args);System.out.println("紧急通知:紧急集合!!!");System.out.println("======================================= End =======================================");return invoke;}
}

运行结果:

所以,AOP主要用于:日志记录,性能统计,安全控制,事务处理,异常处理等场景。

总结:静态代理比动态代理更符合OOP原则,在日常开发中使用也较多;动态代理在开发框架时使用较多。

Java中的代理(静态代理和动态代理)相关推荐

  1. Java中三种代理方式—— 静态代理与两种动态代理的实现机制

    个人博客请访问 http://www.x0100.top 1.代理模式 代理(Proxy)是一种设计模式,提供了对目标对象另外的访问方式;即通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现 ...

  2. 面试官:什么是静态代理?什么是动态代理?注解、反射你会吗?

    前言 开场 一位穿着蓝色衬衫,牛仔裤,拿着一个白色保温杯的中年男子急匆匆地坐在你对面,看样子是项目上的东西很急,估摸面试时间不会太长,这样一想心情放松了许多-(后来我就被打脸了) ​ ​ 面试开始 面 ...

  3. 面试篇之HR问什么是静态代理?什么是动态代理?

    何为代理? Java中的代理,开源理解为通过代理去访问实际的目标对象,比如呢?我们平常买卖二手车的中间商,就可以看作一个代理类,不过你也可以直接去和二手车的主人买卖. 那这种情况,在Java中就被称之 ...

  4. 仿照java的jdk动态代理实现go语言动态代理

    仿照java的jdk动态代理实现go语言动态代理 通过学习java的jdk动态代理和Cglib动态代理,仿照jdk动态代理用go实现了一个简单的动态代理 结构型模式 代理模式 代理模式中分为静态代理和 ...

  5. 代理的理解(动态代理与静态代理)

    静态代理与动态代理(JDK.CGLIB) 问题引入 什么是代理 什么是静态代理? 什么是动态代理? 静态代理和动态代理的区别是什么? JDK动态代理和CGLIB动态代理的区别? 代理 代理就是帮人做事 ...

  6. [动态代理三部曲:上] - 动态代理是如何坑掉了我4500块钱

    前言 不知道,起这个名字算不算是标题党呢?不过如果小伙伴们可以耐心看下去,因为会觉得不算标题党~ 这是一个系列文章,目的在于通过动态代理这个很基础的技术,进而深入挖掘诸如:动态生成class:Clas ...

  7. 动态代理,动态代理设计模式 ,JDK动态代理,cglib动态代理

    为什么80%的码农都做不了架构师?>>>    一:在看此篇代码示例前,先看静态代理, 链接地址:http://my.oschina.net/dyyweb/blog/656760   ...

  8. 利用代码分别实现jdk动态代理和cglib动态代理_面试之动态代理

    大家好!我是CSRobot,从今天开始,我将会发布一些技术文章,内容就是结合春招以来的面试所遇到的问题进行分享,首先会对知识点进行一个探讨和整理,在最后会给出一些面试题并作出解答,希望可以帮助到大家! ...

  9. 设计模式之代理:手动实现动态代理,揭秘原理实现

    作者:青石路 cnblogs.com/youzhibing/p/10464274.html 什么是代理模式 所谓代理,就是一个人或者一个机构代表另一个人或者另一个机构采取行动.在一些情况下,一个客户不 ...

  10. 代理模式之jdk动态代理的实现

    学习动态代理之前需要了解静态代理 并且牢记静态代理的缺点 //首先被代理的类需要实现一个接口 public interface ProxyInterface {public void say(Stri ...

最新文章

  1. Scala进阶之路-正则表达式案例
  2. 每日一则 LeetCode: Add Two Numbers
  3. Julia与R/Python/MATLAB比较及Julia中的Text Analysis模块
  4. 【原创】全面剖析飞凌2440,6410开发板选型指南
  5. 95-130-022-源码-source-基于socket的source源码分析SocketTextStreamFunction
  6. 突破XXX,冲上Google+
  7. 国密SM4对称算法实现说明(原SMS4无线局域网算法标准)
  8. spring mvc 解决csrf跨站请求攻击
  9. FIT2CLOUD安装
  10. 软件人才争夺战日趋白热化
  11. 机器学习实战之信用卡诈骗(三)
  12. [转载]C++URL编码转换
  13. Altium Designer之如何显示标题栏内容
  14. BIM技术在住宅园区物业管理中的应用及其优势
  15. 外连接 及 无用的外连接
  16. 笔记本Max-Q架构CPU到底有什么秘密
  17. 首拆:iFixit完整版 iPhone X拆解报告
  18. 范式存在定理及其证明
  19. 2021年7月初青海湖小环线之行
  20. java毕业设计二次元信息分享平台mybatis+源码+调试部署+系统+数据库+lw

热门文章

  1. VMware更改虚拟机网卡的MAC地址
  2. bzoj 1814 Fornula 1
  3. 用MATLAB写斐波那契数列
  4. 50万奖金池:欢迎全球学子报名参加中国移动第二届梧桐杯大数据应用创新大赛湖北赛道
  5. 断舍离------活成自己喜欢的样子
  6. Vue-Router给当前url添加参数时报错 Navigation Duplicated Avoided redundant navigation to current location
  7. 数据湖统一存储在 OPPO 的实践
  8. 实验七 磁盘调度算法的模拟与实现
  9. fiddler+LR11录制脚本
  10. 关于hping打流测试工具