WHAT
Proxy代理模式是一种结构设计模式,主要解决的问题是:直接访问对象时带来的问题,给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。。
其主要目的就是为其他对象提供一个代理以控制对某个对象的访问,代理类负责为委托类预处理消息,过滤消息并做转发,以及进行消息被委托类执行后的后续处理。
WHY
隔离作用:在某些情况下,client不想直接调用委托对象,而代理对象可以在客户类和委托对象之间起到中间作用,其特征是代理类和委托类实现相同的接口。
开闭原则,增加功能:代理类处理是client和委托类之间的中介之外,还可以通过扩展委托类的功能,这样我们只需要修改代理类而不用再修改委托类,妇科代码设计的开闭原则。代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后对返回结果的处理等。代理类本身并不真正实现服务,而是通过调用委托类的相关方法,来提高特定的服务。真正的业务功能还是由委托来来实现,但是可以在业务功能执行的前后加入一些公共的服务。例如缓存、日志这些功能,就可以使用代理类完成,而没必要打开已经封装好的委托类。
按照代理的创建时期,代理类可以分为2种:
静态代理:由自己创建代理类或特定工具自动生成源代码再对其编译,在程序运行前代理的.class文件就已经存在。
动态代理:在程序运行时运用反射机制动态创建而成。
HOW
静态代理例子:
1、具体实现
1 public class UserManagerImpl implements UserManager { 2 3 @Override 4 public void addUser(String userId, String userName) { 5 System.out.println("UserManagerImpl.addUser"); 6 } 7 8 @Override 9 public void delUser(String userId) { 10 System.out.println("UserManagerImpl.delUser"); 11 } 12 13 @Override 14 public String findUser(String userId) { 15 System.out.println("UserManagerImpl.findUser"); 16 return "张三"; 17 } 18 19 @Override 20 public void modifyUser(String userId, String userName) { 21 System.out.println("UserManagerImpl.modifyUser"); 22 23 } 24 }
//代理类
public class UserManagerImplProxy implements UserManager { // 目标对象 private UserManager userManager; // 通过构造方法传入目标对象 public UserManagerImplProxy(UserManager userManager){ this.userManager=userManager; } @Override public void addUser(String userId, String userName) { try{ //添加打印日志的功能 //开始添加用户 System.out.println("start-->addUser()"); userManager.addUser(userId, userName); //添加用户成功 System.out.println("success-->addUser()"); }catch(Exception e){ //添加用户失败 System.out.println("error-->addUser()"); } } @Override public void delUser(String userId) { userManager.delUser(userId); } @Override public String findUser(String userId) { userManager.findUser(userId); return "张三"; } @Override public void modifyUser(String userId, String userName) { userManager.modifyUser(userId,userName); } }
//测试
public class Client { public static void main(String[] args){ //UserManager userManager=new UserManagerImpl(); UserManager userManager=new UserManagerImplProxy(new UserManagerImpl()); userManager.addUser("1111", "张三"); } }
优点:可以做到符合开闭原则的情况下对目标对象进行功能的扩展。
缺点:代理类和委托类实现了相同的接口,代理类通过委托类实现了相同的方法,这样就实现了大量的代码复用,如果接口增加一个方法,除了所有实现类要实现这个方法外,所有代理类也需要实现此方法。出现很多重复代码,而且不容易维护。
代理对象只服务与一种类型的对象,如果要服务多类型的对象,势必要为每一种对象都进行代理,静态代理在程序规模稍大时就无法胜任了。
eg:代理可以对实现类进行统一的管理,如在调用具体实现类之前,需要打印日志等信息,这样我们只需要添加一个代理类,在代理类中添加打印日志的功能,然后调用实现类,这样就避免了修改具体实现类。满足我们所要求的开闭原则,但是如果很多类都需要打印日志功能的话就需要实现很多的代理类,也就是说静态代理类智能为特定的接口服务,如香味多个接口服务则需要建立很多个代理类。
动态代理:
为了弥补静态代理的不足我们引入动态代理,通过一个代理类完成全部的代理功能,通过反射机制实现动态代理,并且能够代理各种类型的对象。
在java中要想实现动态代理机制,需要java.lang.reflect.InvocationHandler接口和java.lang.reflect.Proxy类的支持。
java.lang.reflect.InvocationHandler接口的定义如下:
1 //Object proxy:被代理的对象 2 //Method method:要调用的方法 3 //Object[] args:方法调用时所需要参数 4 public interface InvocationHandler { 5 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable; 6 }
java.lang.reflect.Proxy类的定义如下:
//CLassLoader loader:类的加载器 //Class<?> interfaces:得到全部的接口 //InvocationHandler h:得到InvocationHandler接口的子类的实例 public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException
eg:
具体实现类:
1 public class UserManagerImpl implements UserManager { 2 3 @Override 4 public void addUser(String userId, String userName) { 5 System.out.println("UserManagerImpl.addUser"); 6 } 7 8 @Override 9 public void delUser(String userId) { 10 System.out.println("UserManagerImpl.delUser"); 11 } 12 13 @Override 14 public String findUser(String userId) { 15 System.out.println("UserManagerImpl.findUser"); 16 return "张三"; 17 } 18 19 @Override 20 public void modifyUser(String userId, String userName) { 21 System.out.println("UserManagerImpl.modifyUser"); 22 23 } 24 25 }
动态创建代理对象的类:
1 //动态代理类只能代理接口(不支持抽象类),代理类都需要实现InvocationHandler类,实现invoke方法。该invoke方法就是调用被代理接口的所有方法时需要调用的,该invoke方法返回的值是被代理接口的一个实现类 2 3 public class LogHandler implements InvocationHandler { 4 5 // 目标对象 6 private Object targetObject; 7 //绑定关系,也就是关联到哪个接口(与具体的实现类绑定)的哪些方法将被调用时,执行invoke方法。 8 public Object newProxyInstance(Object targetObject){ 9 this.targetObject=targetObject; 10 //该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例 11 //第一个参数指定产生代理对象的类加载器,需要将其指定为和目标对象同一个类加载器 12 //第二个参数要实现和目标对象一样的接口,所以只需要拿到目标对象的实现接口 13 //第三个参数表明这些被拦截的方法在被拦截时需要执行哪个InvocationHandler的invoke方法 14 //根据传入的目标返回一个代理对象 15 return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(), 16 targetObject.getClass().getInterfaces(),this); 17 } 18 @Override 19 //关联的这个实现类的方法被调用时将被执行 20 /*InvocationHandler接口的方法,proxy表示代理,method表示原对象被调用的方法,args表示方法的参数*/ 21 public Object invoke(Object proxy, Method method, Object[] args) 22 throws Throwable { 23 System.out.println("start-->>"); 24 for(int i=0;i<args.length;i++){ 25 System.out.println(args[i]); 26 } 27 Object ret=null; 28 try{ 29 /*原对象方法调用前处理日志信息*/ 30 System.out.println("satrt-->>"); 31 32 //调用目标方法 33 ret=method.invoke(targetObject, args); 34 /*原对象方法调用后处理日志信息*/ 35 System.out.println("success-->>"); 36 }catch(Exception e){ 37 e.printStackTrace(); 38 System.out.println("error-->>"); 39 throw e; 40 } 41 return ret; 42 } 43 44 }
被代理对象targetObject通过参数传递进来,我们通过targetObject.getClass().getClassLoader()获取ClassLoader对象,然后通过targetObject.getClass().getInterfaces()获取它实现的所有接口,然后将targetObject包装到实现了InvocationHandler接口的LogHandler对象中。通过newProxyInstance函数我们就获得了一个动态代理对象
1 public class Client { 2 3 public static void main(String[] args){ 4 LogHandler logHandler=new LogHandler(); 5 UserManager userManager=(UserManager)logHandler.newProxyInstance(new UserManagerImpl()); 6 //UserManager userManager=new UserManagerImpl(); 7 userManager.addUser("1111", "张三"); 8 } 9 }
优点:接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler.invoke).这样在接口方法数量比较多的时候我们可以进行灵活处理,而不需要像静态代理一样每一个方法进行中转,动态代理使类职责更加单一、复用性更强。
代理对象就是把被代理对象包装一层,在其内部做一些额外的工作,比如用户需要上facebook,而普通网络无法直接访问,网络代理帮助用户先翻墙,然后再访问facebook。这就是代理的作用了。
纵观静态代理与动态代理,它们都能实现相同的功能,而我们看从静态代理到动态代理的这个过程,我们会发现其实动态代理只是对类做了进一步抽象和封装,使其复用性和易用性得到进一步提升而这不仅仅符合了面向对象的设计理念,其中还有AOP的身影,这也提供给我们对类抽象的一种参考。关于动态代理与AOP的关系,个人觉得AOP是一种思想,而动态代理是一种AOP思想的实现!
JDK动态代理和CGLIG代理的区别:
1、JDK动态代理只能对实现了接口的类生成代理,而不是针对类。
CGLIB是针对类实现代理,主要是指定的类生成一个子类,覆盖其中的方法,继承。
2、在Spring中使用的是JDK还是CGLIB
当Bean实现接口时,Spring就会用JDK的动态代理
当Bean没有实现接口时,Spring使用CGLIB实现
可以强制使用CGLIB(增加配置<aop:aspectj-autoproxy proxy-target-class="true"/>)
3、性能
使用CGLIB实现动态代理,CGLIB地址采用ASM字节码生产框架,使用字节码技术生成代理类,在jdk6之前比使用反射效率要搞,但是需要注意,CGLIB不能对声明为final的方法进行代理,因为CGLIB原理是动态生成被代理的子类
在jdk6 jdk7 jdk8逐步对JDK动态代理优化之后,在调用次数较少的情况下JKD代理效率高于CGLIB代理效率,只有当大量调用的时候,jdk6 和jdk7比CGLIB代理效率低一些,但是到jdk8的时候,jdk代理效率高于CGLIB代理。