设计模式四—"代理模式"(动态代理)
动态代理
动态代理就是通过使用字节码动态生成加载技术,在运行时生成并加载类。
常用的动态代理技术包括JDK动态代理,CGLIB等,其中JDK自带的动态代理使用简单,但是功能较弱,CGLIB是高级的字节码生成库,性能更好,而且功能强大。
先说说JDK动态代理的使用
动态代理类的字节码在程序运行时由Java反射机制动态生成,无需程序员手工编写它的源代码。动态代理类不仅简化了编程工作,而且提高了软件系统的可扩展性,因为Java 反射机制可以生成任意类型的动态代理类。java.lang.reflect 包中的Proxy类和InvocationHandler 接口提供了生成动态代理类的能力。
JDK动态代理的使用核心是实现一个用来处理方法调用的接口InvocationHandler,在java.lang.reflect包中
1 public interface InvocationHandler { 2 public Object invoke(Object proxy, Method method, Object[] args) 3 throws Throwable; 4 }
Object proxy指被代理的对象。
Method method指被调用的方法。
Object[] args方法调用传递的参数。
一个实现了InvocationHandler接口的类,就是我们使用的代理类。
Proxy类:Proxy类是专门完成代理的操作类,可以通过此类为一个或多个接口动态地生成实现类,此类提供了如下的操作方法:
使用的核心方法是该类提供的
1 public static Object newProxyInstance(ClassLoader loader, 2 Class<?>[] interfaces, 3 InvocationHandler h) 4 throws IllegalArgumentException 5 { 6 if (h == null) { 7 throw new NullPointerException(); 8 } 9 10 /* 11 * Look up or generate the designated proxy class. 12 */ 13 Class<?> cl = getProxyClass(loader, interfaces); 14 15 /* 16 * Invoke its constructor with the designated invocation handler. 17 */ 18 try { 19 Constructor cons = cl.getConstructor(constructorParams); 20 return cons.newInstance(new Object[] { h }); 21 } catch (NoSuchMethodException e) { 22 throw new InternalError(e.toString()); 23 } catch (IllegalAccessException e) { 24 throw new InternalError(e.toString()); 25 } catch (InstantiationException e) { 26 throw new InternalError(e.toString()); 27 } catch (InvocationTargetException e) { 28 throw new InternalError(e.toString()); 29 } 30 }
ClassLoader loader:用来定义代理类的的类加载器
Class<?>[] interfaces:代理类需要实现的一系列接口
InvocationHandler h:实现分发方法调用的的InvocationHandler
这个工厂方法根据需求生成指定的代理类。
实现一个示例,还是一个与静态代理例子的类似的sample
(1)主题接口IDBQuery
1 package com.sample; 2 3 public interface IDBQuery { 4 public String query(); 5 public void insert(); 6 public void update(); 7 public void delete(); 8 }
(2)真实主题类,封装了实际业务逻辑,无聊的模拟了一个权限验证所需要的身份证明“admin”
1 package com.sample; 2 3 public class DBQuery implements IDBQuery { 4 private String authority = "admin"; 5 public DBQuery(){ 6 try{ 7 Thread.sleep(10000);//simulate ops creating connections between 8 } 9 catch(Exception ie){ 10 ie.printStackTrace(); 11 } 12 } 13 14 public String query() { 15 return "this is a record"; 16 } 17 public void update() { 18 System.out.println("update a record"); 19 } 20 21 public void insert() { 22 System.out.println("insert a record"); 23 } 24 25 public void delete() { 26 System.out.println("delete a record"); 27 } 28 29 public String getAuthority() { 30 return authority; 31 } 32 33 public void setAuthority(String authority) { 34 this.authority = authority; 35 } 36 }
(3)替换静态代理中代理类的JDK动态代理,在方法调用时加上身份检测,当然了,还可以根据身份决定方法能否被调用即method.getName()来判断
1 package com.sample; 2 3 import java.lang.reflect.InvocationHandler; 4 import java.lang.reflect.Method; 5 import java.lang.reflect.Proxy; 6 7 public class JdkDBProxy implements InvocationHandler { 8 9 private Object targetObject = null; 10 11 public Object createProxyInstance(Object targetObject) { 12 this.targetObject = targetObject; 13 return Proxy.newProxyInstance(this.targetObject.getClass().getClassLoader(), 14 this.targetObject.getClass().getInterfaces(), 15 this); 16 } 17 18 public Object invoke(Object proxy, Method method, Object[] args) 19 throws Throwable { 20 DBQuery dbQuery = (DBQuery) this.targetObject; 21 Object result = null; 22 if (dbQuery.getAuthority().equals("admin")) { 23 System.out.println("call method:" + method.toString()); 24 result = method.invoke(targetObject, args); 25 } 26 return result; 27 } 28 }
(4)测试使用动态代理
1 package com.sample; 2 3 public class Test { 4 public static void main(String[] args) { 5 JdkDBProxy jdkProxy = new JdkDBProxy(); 6 long begTime = System.currentTimeMillis(); 7 IDBQuery dbQuery = (IDBQuery)jdkProxy.createProxyInstance(new DBQuery()); 8 long endTime = System.currentTimeMillis(); 9 System.out.println("create dynamic proxy cost:" + (endTime-begTime)); 10 System.out.println(dbQuery.query()); 11 dbQuery.insert(); 12 dbQuery.update(); 13 dbQuery.delete(); 14 } 15 16 }
执行结果
create dynamic proxy cost:10009 call method:public abstract java.lang.String com.sample.IDBQuery.query() this is a record call method:public abstract void com.sample.IDBQuery.insert() insert a record call method:public abstract void com.sample.IDBQuery.update() update a record call method:public abstract void com.sample.IDBQuery.delete() delete a record