Java的三种代理模式

1.代理模式

代理(Proxy)是一种设计模式,提供了对目标对象另外的访问方式;即通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能.
这里使用到编程中的一个思想:不要随意去修改别人已经写好的代码或者方法,如果需改修改,可以通过代理的方式来扩展该方法

举个例子来说明代理的作用:假设我们想邀请一位明星,那么并不是直接连接明星,而是联系明星的经纪人,来达到同样的目的.明星就是一个目标对象,他只要负责活动中的节目,而其他琐碎的事情就交给他的代理人(经纪人)来解决.这就是代理思想在现实中的一个例子

用图表示如下:

代理模式的关键点是:代理对象与目标对象.代理对象是对目标对象的扩展,并会调用目标对象

1.1.静态代理(类似于装饰者模式)

静态代理在使用时,需要定义接口或者父类,被代理对象与代理对象一起实现相同的接口或者是继承相同父类.

下面举个案例来解释:
模拟保存动作,定义一个保存动作的接口:IUserDao.java,然后目标对象实现这个接口的方法UserDao.java,此时如果使用静态代理方
式,就需要在代理对象(UserDaoProxy.java)中也实现IUserDao接口.调用的时候通过调用代理对象的方法来调用目标对象.
需要注意的是,代理对象与目标对象要实现相同的接口,然后通过调用相同的方法来调用目标对象的方法

代码示例:
接口:IUserDao.java

  1. /**
  2. * 接口
  3. */
  4. public interface IUserDao {
  5.  
  6. void save();
  7. }
  1.  

目标对象:UserDao.java

  1. /**
  2. * 接口实现
  3. * 目标对象
  4. */
  5. public class UserDao implements IUserDao {
  6. public void save() {
  7. System.out.println("----已经保存数据!----");
  8. }
  9. }
  1.  

代理对象:UserDaoProxy.java

  1. /**
  2. * 代理对象,静态代理
  3. */
  4. public class UserDaoProxy implements IUserDao{
  5. //接收保存目标对象
  6. private IUserDao target;
  7. public UserDaoProxy(IUserDao target){
  8. this.target=target;
  9. }
  10.  
  11. public void save() {
  12. System.out.println("开始事务...");
  13. target.save();//执行目标对象的方法
  14. System.out.println("提交事务...");
  15. }
  16. }
  1.  

测试类:App.java

  1. /**
  2. * 测试类
  3. */
  4. public class App {
  5. public static void main(String[] args) {
  6. //目标对象
  7. UserDao target = new UserDao();
  8.  
  9. //代理对象,把目标对象传给代理对象,建立代理关系
  10. UserDaoProxy proxy = new UserDaoProxy(target);
  11.  
  12. proxy.save();//执行的是代理的方法
  13. }
  14. }
  1.  

静态代理总结:
1.可以做到在不修改目标对象的功能前提下,对目标功能扩展.
2.缺点:

  • 因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类,类太多.同时,一旦接口增加方法,目标对象与代理对象都要维护.

如何解决静态代理中的缺点呢?答案是可以使用动态代理方式

1.2.动态代理

      参考:http://www.cnblogs.com/qlqwjy/p/7151748.html

动态代理有以下特点:
1.代理对象,不需要实现接口
2.代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象(需要我们指定创建代理对象/目标对象实现的接口的类型)
3.动态代理也叫做:JDK代理,接口代理

JDK中生成代理对象的API
代理类所在包:java.lang.reflect.Proxy
JDK实现代理只需要使用newProxyInstance方法,但是该方法需要接收三个参数,完整的写法是:

  1. static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h )

注意该方法是在Proxy类中是静态方法,且接收的三个参数依次为:

  • ClassLoader loader,:指定当前目标对象使用类加载器,获取加载器的方法是固定的
  • Class<?>[] interfaces,:目标对象实现的接口的类型,使用泛型方式确认类型
  • InvocationHandler h:事件处理,执行目标对象的方法时,会触发事件处理器的方法,会把当前执行目标对象的方法作为参数传入

代码示例:
接口类IUserDao.java以及接口实现类,目标对象UserDao是一样的,没有做修改.在这个基础上,增加一个代理工厂类
(ProxyFactory.java),将代理类写在这个地方,然后在测试类(需要使用到代理的代码)中先建立目标对象和代理对象的联系,然后代用代理
对象的中同名方法

代理工厂类:ProxyFactory.java

  1. /**
  2. * 创建动态代理对象
  3. * 动态代理不需要实现接口,但是需要指定接口类型
  4. */
  5. public class ProxyFactory{
  6.  
  7. //维护一个目标对象
  8. private Object target;
  9. public ProxyFactory(Object target){
  10. this.target=target;
  11. }
  12.  
  13. //给目标对象生成代理对象
  14. public Object getProxyInstance(){
  15. return Proxy.newProxyInstance(
  16. target.getClass().getClassLoader(),
  17. target.getClass().getInterfaces(),
  18. new InvocationHandler() {
  19. @Override
  20. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  21. System.out.println("开始事务2");
  22. //运用反射执行目标对象方法
  23. Object returnValue = method.invoke(target, args);
  24. System.out.println("提交事务2");
  25. return returnValue;
  26. }
  27. }
  28. );
  29. }
  30.  
  31. }
  1.  

测试类:App.java

  1. /**
  2. * 测试类
  3. */
  4. public class App {
  5. public static void main(String[] args) {
  6. // 目标对象
  7. IUserDao target = new UserDao();
  8. // 【原始的类型 class cn.itcast.b_dynamic.UserDao】
  9. System.out.println(target.getClass());
  10.  
  11. // 给目标对象,创建代理对象
  12. IUserDao proxy = (IUserDao) new ProxyFactory(target).getProxyInstance();
  13. // class $Proxy0 内存中动态生成的代理对象
  14. System.out.println(proxy.getClass());
  15.  
  16. // 执行方法 【代理对象】
  17. proxy.save();
  18. }
  19. }

结果:

class ReflectTest.UserDao
class com.sun.proxy.$Proxy0
开始事务2
----已经保存数据!----
提交事务2

如果我们想查看生产的代理类:

  1. System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true" );

总结:
  代理对象不需要实现接口,但是目标对象一定要实现接口,否则不能用动态代理

1.3.Cglib代理(基于继承的方式实现)

aaarticlea/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHMAAAAtCAIAAAASpcM2AAAEmElEQVRoge2az2vbZhjH/Qfs1tsOPY2eUnroZRP0PnrZLSRs2WALb3soPWRZwUlTupA186iDIG56MElpoUsYWe3CIkaKKSlpt946Gg+1ShwrdqzIfZ0IZG3MdXEPsqXXfl9JsaP3tTP04SWI95Gl5/3q/RG9X4VqAXQIdTuB/y2Oyoqy+nD95Z2VPz3Lw/WXoqyyTPpY4KhsYu1F+nXGMIx/McrlsnVsGEb6dSax9oJl0scCR2Xnlh+XSqVqtfoOoVqtlstl7dV6pVKxakql0tzyY4Y5d4rEcyGOlxjdzVFZfnEVQvgWoVKp6LpeenQTznxsGIZVDyHkF1cZ5Vur1WoCCJkAoa1ojygbubuiqup/CJqmFX+PvPnh1MHBwZu/Vg3DMOtVVY3cXWGUb03iubpkAsDVc48yxVHZyXhCUZR/Gui6vpeKqdc+VBRl77fJ4tQpXdfNkKIok/EEy6TrCMCtC7pH6eOo7HhsKZ/P6w00TVPHTph/zQNN08xQPp8fjy0RLiHxXKh1ZGJ1AghxvFCvBYI9mj1laVtYieesRAi5mWEBHObeh8BR2dHoPVmWtQYQQvWns7Is7333AYQQQpjL5QqFwv7+vizLo9F72AUkHjTys0Ym2raaHau3xNTUjBPOtC/MkedRr6h9TVJu9V/6NoM4Knt+6le0bG1tZTKZ/ORH2eT1TCaTy+Wu8PcjCw92dnY2NzcvTceJFyH10JYugfYt4rG1IrV2JAG4dS5CtPlpYbm5PMxOcFT29DcxtAxN3L54Y/7z8VuDY7GLN+bjy6uFQmFwLPbjQkIUxeHrsdbfSzxnNa01Z7NRlnDuyrrQ5nxgpUHOjZWyQ+GZocWv0SI8eS48eZ5MPUum/hClLVEUv7p2O7KQSKfTQ+EZl3ZJPGfNBnzTpFZrW1l78Uf+ibKOPKPYPe3cmCnbPzJ9Zq4fLQPh2cHw7EB49sJUPJvNfjkxNz3/IJvNbmxs9I9MkzRoDGMArJytsY1UtNdnHdYepAs6RZu7J5YbK2WfffsJXl41s7u7qyiKJEmfXfret4zo4bN0Hjgquwb6/r55Hi1roO9y9Ge0FItFCOH29van4CqrhDuH7SuYs7KPBk7iRcEoFou/JFf6R/HZoJeoz0FM38kclU2mng5PRM99MeJZhieiydRThjkfD4Kdb1oEytIiUJYWgbK0CHwwWgQ+GC16wQcTgL1RYr/qd9MQ8OOlohd8sCMoa29DkIToOEpVWYY+WENZm0MqKwB0AwffU+886gO94IN1rGzLRTo2xahYZjR9MMJ4QwwC3moOOhugG3rYjqML7v2u3agfjhk9Hwy3S9AWoNv6ZGXtcz1HK7J97U/UD8eMmg+Gj7CWGsGzz+INJXhi6CPC6TDqh2NGzQejoizhJj7OATZ+OGbUfLCmzAWel9qeDZpOJTWE2ECCN3OE6BEcM3o+GDqK8NXgECsY8FrA7BUOOc9S5yhRPxyz7vlg3f46yA0/3gFZ+mCIZ+3xGUaX8cUxY+uDIatsj8rqn2MW+GC0CHa+aREoS4tAWVoEytIiUJYWgbK08FtZxp+l9TDvAcdw7CP5APtwAAAAAElFTkSuQmCC" alt="" />

  上面的静态代理和动态代理模式都是要求目标对象是实现一个接口的目标对象,但是有时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候就可以使用以目标对象子类的方式类实现代理,这种方法就叫做:Cglib代理

Cglib代理,也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能的扩展.

  • JDK的动态代理有一个限制,就是使用动态代理的对象必须实现一个或多个接口,如果想代理没有实现接口的类,就可以使用Cglib实现.
  • Cglib是一个强大的高性能的代码生成包,它可以在运行期扩展java类与实现java接口.它广泛的被许多AOP的框架使用,例如Spring AOP和synaop,为他们提供方法的interception(拦截)
  • Cglib包的底层是通过使用一个小而块的字节码处理框架ASM来转换字节码并生成新的类.不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉.

Cglib子类代理实现方法:
1.需要引入cglib的jar文件,但是Spring的核心包中已经包括了Cglib功能,所以直接引入pring-core-3.2.5.jar即可.
2.引入功能包后,就可以在内存中动态构建子类
3.代理的类不能为final,否则报错
4.目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法.

5.如果方法为static,private则无法进行代理。

代码示例:
目标对象类:UserDao.java

  1. /**
  2. * 目标对象,没有实现任何接口
  3. */
  4. public class UserDao {
  5.  
  6. public void save() {
  7. System.out.println("----已经保存数据!----");
  8. }
  9. }
  1.  

Cglib代理工厂:ProxyFactory.java

  1. /**
  2. * Cglib子类代理工厂
  3. * 对UserDao在内存中动态构建一个子类对象
  4. */
  5. public class ProxyFactory implements MethodInterceptor{
  6. //维护目标对象
  7. private Object target;
  8.  
  9. public ProxyFactory(Object target) {
  10. this.target = target;
  11. }
  12.  
  13. //给目标对象创建一个代理对象
  14. public Object getProxyInstance(){
  15. //1.工具类
  16. Enhancer en = new Enhancer();
  17. //2.设置父类
  18. en.setSuperclass(target.getClass());
  19. //3.设置回调函数
  20. en.setCallback(this);
  21. //4.创建子类(代理对象)
  22. return en.create();
  23.  
  24. }
  25.  
  26. @Override
  27. public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
  28. System.out.println("开始事务...");
  29.  
  30. //执行目标对象的方法
  31. Object returnValue = method.invoke(target, args);
  32.  
  33. System.out.println("提交事务...");
  34.  
  35. return returnValue;
  36. }
  37. }

或者:

  1. /**
  2. * Cglib子类代理工厂
  3. * 对UserDao在内存中动态构建一个子类对象
  4. */
  5. public class ProxyFactory {
  6. //维护目标对象
  7. private Object target;
  8.  
  9. public ProxyFactory(Object target) {
  10. this.target = target;
  11. }
  12.  
  13. //给目标对象创建一个代理对象
  14. public Object getProxyInstance(){
  15. //1.工具类
  16. Enhancer en = new Enhancer();
  17. //2.设置父类
  18. en.setSuperclass(target.getClass());
  19. //3.设置回调函数
  20. en.setCallback(new MethodInterceptor(){
  21.      public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
  22.    System.out.println("开始事务...");
  23.  
  24.    //执行目标对象的方法
  25.    Object returnValue = method.invoke(target, args);
  26.  
  27.    System.out.println("提交事务...");
  28.  
  29.    return returnValue;
  30.      }
  31. });
  32. //4.创建子类(代理对象)
  33. return en.create();
  34.  
  35. }
  36. }

  

  1.  

测试类:

  1. /**
  2. * 测试类
  3. */
  4. public class App {
  5.  
  6. @Test
  7. public void test(){
  8. //目标对象
  9. UserDao target = new UserDao();
  10.  
  11. //代理对象
  12. UserDao proxy = (UserDao)new ProxyFactory(target).getProxyInstance();
  13.  
  14. //执行代理对象的方法
  15. proxy.save();
  16. }
  17. }

 

1.4  在Spring的AOP编程中代理的选择方式:

 
AOP调用时序图;
  

查看DefaultAopProxyFactory的源码:
  1. //
  2. // Source code recreated from a .class file by IntelliJ IDEA
  3. // (powered by Fernflower decompiler)
  4. //
  5.  
  6. package org.springframework.aop.framework;
  7.  
  8. import java.io.Serializable;
  9. import java.lang.reflect.Proxy;
  10. import org.springframework.aop.SpringProxy;
  11.  
  12. public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {
  13. public DefaultAopProxyFactory() {
  14. }
  15.  
  16. public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
  17. if (!config.isOptimize() && !config.isProxyTargetClass() && !this.hasNoUserSuppliedProxyInterfaces(config)) {
  18. return new JdkDynamicAopProxy(config);
  19. } else {
  20. Class<?> targetClass = config.getTargetClass();
  21. if (targetClass == null) {
  22. throw new AopConfigException("TargetSource cannot determine target class: Either an interface or a target is required for proxy creation.");
  23. } else {
  24. return (AopProxy)(!targetClass.isInterface() && !Proxy.isProxyClass(targetClass) ? new ObjenesisCglibAopProxy(config) : new JdkDynamicAopProxy(config));
  25. }
  26. }
  27. }
  28.  
  29. private boolean hasNoUserSuppliedProxyInterfaces(AdvisedSupport config) {
  30. Class<?>[] ifcs = config.getProxiedInterfaces();
  31. return ifcs.length == 0 || ifcs.length == 1 && SpringProxy.class.isAssignableFrom(ifcs[0]);
  32. }
  33. }

Spring中强制使用Cglib代理

  1. <aop:aspectj-autoproxy proxy-target-class="true" />

SpringBoot中强制使用Cglib代理

总结:
  如果加入容器的目标对象有实现接口,用JDK代理
  如果目标对象没有实现接口,用Cglib代理   
  如果目标对象实现了接口,且强制使用cglib代理,则会使用cglib代理。

    参考:http://www.cnblogs.com/qlqwjy/p/8533261.html

Java的三种代理模式(Spring动态代理对象)的更多相关文章

  1. 23种java设计模式之装饰者模式及动态代理

    设计模式不管对于何种语言都是存在的,这里介绍的是java的模式 装饰者模式是在二次开发中应用比较多的一款模式,当然了用反射也是可以实现的,今天介绍的是装饰模式,有兴趣的朋友可以自己去了解一下反射是怎么 ...

  2. (转)轻松学,Java 中的代理模式及动态代理

    背景:讲到反射机制,肯定会想到动态代理. 轻松学,Java 中的代理模式及动态代理 代理模式可以在不修改被代理对象的基础上,通过扩展代理类,进行一些功能的附加与增强.值得注意的是,代理类和被代理类应该 ...

  3. Java设计模式-代理模式之动态代理(附源代码分析)

    Java设计模式-代理模式之动态代理(附源代码分析) 动态代理概念及类图 上一篇中介绍了静态代理,动态代理跟静态代理一个最大的差别就是:动态代理是在执行时刻动态的创建出代理类及其对象. 上篇中的静态代 ...

  4. java代理模式及动态代理类

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

  5. JAVA代理模式与动态代理模式

    1.代理模式 所谓代理,就是一个人或者一个机构代表另一个人或者另一个机构采取行动.在一些情况下,一个客户不想或者不能够直接引用一个对象,而代理对象可以在客户端和目标对象之前起到中介的作用.代理模式给某 ...

  6. Java代理模式之动态代理

    动态代理类的源码是程序在运行期间由JVM根据反射等机制动态生成的,所以不存在代理类的字节码文件.代理角色和真实角色的联系在程序运行时确定! Java中有两种动态代理,一种是JDK自带的,另一种的CGL ...

  7. java设计模式---三种工厂模式之间的区别

    简单工厂,工厂方法,抽象工厂都属于设计模式中的创建型模式.其主要功能都是帮助我们把对象的实例化部分抽取了出来,优化了系统的架构,并且增强了系统的扩展性. 本文是本人对这三种模式学习后的一个小结以及对他 ...

  8. java设计模式---三种工厂模式

    工厂模式提供创建对象的接口. 工厂模式分为三类:简单工厂模式(Simple Factory), 工厂方法模式(Factory Method)和抽象工厂模式(Abstract Factory).GOF在 ...

  9. java设计模式三种工厂模式简单介绍

    一.简单工厂模式 概述:简单工厂模式的创建意图就是,把对类的创建初始化全都交给一个工厂来执行,而用户不需要去关心创建的过程是什么样的,只用告诉工厂我想要什么就行了.而这种方法的缺点也很明显,违背了设计 ...

随机推荐

  1. 运行gunicorn失败:[ERROR] Connection in use: ('0.0.0.0', 8000)

    参考:https://pdf-lib.org/Home/Details/5262 执行命令:gunicorn -w 4 -b 0.0.0.0:8000 wsgi:app,遇到如下错误: [2019-0 ...

  2. Linux内核分析第六次作业

    分析system_call中断处理过程 一.先在实验楼的虚拟机中MenuOs增加utsname和utsname-asm指令. 具体实现如下: 1.克隆最新新版本的menu,之后进入menu 2.进入t ...

  3. java:try...catch...finally

    try...catch...finally 规则: 可以没有 finally 块 如果没有 catch 块,则必须跟一个 finally 块 当在 try 块或 catch 块中遇到 return 语 ...

  4. liunx top命令详解

    1,当前服务器时间,up,服务器离上一次重启过了多久,多少个用户在使用,cpu平均负载,grep 'core id' /proc/cpuinfo | sort -u | wc -l  ,一般来说4个, ...

  5. flex 布局压缩问题

    在 flex 布局中,当有一个元素宽度过长时,另一个元素宽度会被压缩, 如下图: 解决办法:在不想被压缩的元素上加上样式 flex-shrink: 0; 效果图:

  6. Excel技巧--实现交叉查询

    如上图,要实现某个地区和某个产品的销售额查询显示.可以使用Match和Index函数的使用来实现: 1.产品名称和城市栏,制作成列表可选:使用“数据”-->“数据验证”的方法. 2.先在旁边空位 ...

  7. Hibernate复习

    第一天 Hibernate是一个持久层的ORM框架.两个配置文件, 类名.hbm.xml类的属性和表的列对应 hibernate.cfg.xml核心配置文件 Hibernate相关API: Confi ...

  8. 解决mysqli的中文乱码问题

    有时候我们向服务器传汉字,出现内容为空,或???的问题,是因为mysqli对utf8的解码属于 Unicode码,会解析为 Unicode 码:所以我们要对内设置成gbk码, 尽管gbk码,很古来,但 ...

  9. 编译CDH的spark1.5.2

    手动安装mvn大于3.3.3版本 下载解压,修改~/.bash_rc export MAVEN_HOME=/usr/local/apache-maven-3.3.9 export PATH=$MAVE ...

  10. Linux背背背(2)

    目录: 1.简单命令 2.目录切换命令 3.扩展命令 简单命令 ls 语法1:#ls [路径]            表示列出指定路径下的文件夹和文件的名字,如果路径没有指定则列出当前路径下的 语法2 ...