黑马程序员-代理类的作用与原理及AOP
代理的概念与作用
程序中的代理
要为已存在的多个具有相同接口的目标类的各个方法增加一些系统功能,例如,异常处理、日志、计算方法的运行时间、事务管理、等等,如何做?
编写一个与目标类具有相同接口的代理类,代理类的每个方法调用目标类的相同方法,并在调用方法时加上系统功能的代码。
如果采用工厂模式和配置文件的方式进行管理,则不需要修改客户端程序,在配置文件中配置是使用目标类、还是代理类,这样以后很容易切换,譬如,想要日志功能时就配置代理类,否则配置目标类,这样,增加系统功能很容易,以后运行一段时间后,又想去掉系统功能也很容易。
AOP
系统中存在交叉业务,一个交叉业务就是要切入到系统中的一个方面。
交叉业务的编程问题即为面向对象的编程(Aspect oriented program,简称AOP),AOP的目标就是要使交叉业务模块化。可以采用将切面代码移动到原始方法的周围,这与直接在方法中编写切面代码的运行效果是一样的。
使用代理技术正好可以解决这种问题,代理是实现AOP功能的核心和关键技术。
注意:安全,事务,日志等功能要贯穿到好多个模块中,所以,它们就是交叉业务。不要把供货商暴露给你的客户。
动态代理技术
要为系统中的各种接口的类增加代理功能,那将需要太多的代理类,全部采用静态代理方式,将是一件非常麻烦的事情!
1. JVM可以在运行期动态生成出类的字节码,这种动态生成的类往往被用作代理类,即动态代理类。
2. JVM生成的动态类必须实现一个或多个接口,所以,JVM生成的动态类只能用作
3. CGLIB库可以动态生成一个类的子类,一个类的子类也可以用作该类的代理,所以,如果要为一个没有实现接口的类生成动态代理类,那么可以使用CGLIB库。
4. 代理类的各个方法中通常除了要调用目标的相应方法和对外返回目标返回的结果外,还可以在代理方法中的如下四个位置加上系统功能代码:
- 在调用目标方法之前
- 在调用目标方法之后
- 在调用目标方法前后
- 在处理目标方法异常的catch块中
示例:
Class proxy{ void sayHello(){ ………. try{ target.sayHello(); }catch(Exception e){ ……….. } …………. } }
创建动态类及查看其方法列表信息
创建实现了Collection接口的动态类和查看其名称,分析Proxy.getProxyClass方法的各个参数。
public class ProxyTest { public static void main(String[] args) { Class clazzProxy = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class ); System. out.println(clazzProxy.getName()); System. out.println("--------begin constructors list-------"); Constructor[] constructors = clazzProxy.getConstructors(); for(Constructor constructor : constructors){ String name = constructor.getName(); StringBuilder sBuilder = new StringBuilder(name); sBuilder.append( "("); Class[] clazzParams = constructor.getParameterTypes(); for(Class clazzParam : clazzParams){ sBuilder.append(clazzParam.getName()).append( ","); } if(clazzParams != null && clazzParams.length != 0){ sBuilder.deleteCharAt(sBuilder.length() - 1); } sBuilder.append( ")"); System. out.println(sBuilder); } System. out.println("--------begin methods list-------" ); Method[] methods = clazzProxy.getMethods(); for(Method method : methods){ String name = method.getName(); StringBuilder sBuilder = new StringBuilder(name); sBuilder.append( "("); Class[] clazzParams = method.getParameterTypes(); for(Class clazzParam : clazzParams){ sBuilder.append(clazzParam.getName()).append( ","); } if(clazzParams != null && clazzParams.length != 0){ sBuilder.deleteCharAt(sBuilder.length() - 1); } sBuilder.append( ")"); System. out.println(sBuilder); } } }
运行结果:
$Proxy0 --------begin constructors list------- $Proxy0(java.lang.reflect.InvocationHandler) --------begin methods list------- hashCode() add(java.lang.Object) clear() equals(java.lang.Object) toString() contains(java.lang.Object) isEmpty() addAll(java.util.Collection) iterator() size() toArray() toArray([Ljava.lang.Object;) remove(java.lang.Object) containsAll(java.util.Collection) removeAll(java.util.Collection) retainAll(java.util.Collection) isProxyClass(java.lang.Class) getProxyClass(java.lang.ClassLoader,[Ljava.lang.Class;) getInvocationHandler(java.lang.Object) newProxyInstance(java.lang.ClassLoader,[Ljava.lang.Class;,java.lang.reflect.InvocationHandler) getClass() wait(long,int) wait() wait(long) notify() notifyAll()
创建动态类的实例对象及调用其方法
编写一个最简单的InvocationHandler类。
调用构造方法创建动态类的实例对象,并将编写的InvocationHandler类的实例对象传进去。将创建动态类的实例对象的代理改为匿名内部类的形式。
思考总结:
让JVM创建动态类及其实例对象,需要给它提供哪些信息?
三个方面:
- 生成的类中有哪些方法,通过让其实现哪些接口的方式进行告知。
- 产生的类字节码必须有一个关联的类加载器对象。
- 生成的类中的方法的代码是怎样的,也得由我们提供。把我们的代码写在一个约定好了接口对象的方法中,把对象传给它,它调用我的方法,即相当于插入了我的代码。提供执行代码的对象就是那个InvocationHandler对象,它是在创建动态类的实例对象的构造方法时传递进去的。在上面的InvocationHandler对象的invoke方法中加一点代码,就可以看到这些代码被调用运行了。
public static void main(String[] args) throws Exception { Class clazzProxy = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class ); System. out.println("--------begin create instance object-------"); Constructor constructor = clazzProxy.getConstructor(InvocationHandler.class) ; Collection proxy = (Collection)constructor.newInstance(new InvocationHandler(){ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { return null ; } }); System. out.println(proxy); //结果:null proxy.clear(); //执行没有返回值的方法,不会报告异常 proxy.size(); //执行有返回值的方法,会报告异常 }