1、代理
生活中的代理:代理架构图:
2、面向方面编程(AOP)
系统中存在交叉业务,
比如,处理学生、课程和管理的三种业务,有各自的CRUD。
但是无论这些类的各自的业务如何不同,都会涉及到安全、事务管理和日志三个方面的内容。
安全、事物、日志贯穿在这三个模块中,所以他们就是交叉业务。
交叉业务:就是不同模块都具有的功能。如图
安全、食物、日志这三个功能贯穿在不同的模块中,所以它们就是交叉业务。
用代码描述交叉业务:
交叉业务的编程问题即为面向方面的编程(Aspect oriented program,简称AOP),
AOP的目标就是要使交叉业务模块化。
可以采用将切面代码移动到原始方法的外面,这与直接在方法中编写切面代码的运行效果是一样的,
如下图所示:
使用代理技术正好可以解决这种问题,代理是实现AOP功能的核心和关键技术。
如果将切面的代码移到原始方法的外面,原始方法看做目标类的方法,
那么,移动以后的切面代码+原始代码,就是代理类对应的方法。
所以,代理是实现AOP的核心和关键技术。只要是AOP,就一定会涉及代理技术。
3、代理的分类
按照是否是在程序运行期间产生代理类,可以将代理分为静态代理和动态代理
<1>静态代理:就是手动为每一个目标类的每一个方法都增加交叉业务,也就是手动为每一个目标类增加代理类。
缺点:如果目标类数量非常多或者目标类中的功能非常多,直接使用静态代理的方式来为目标类增加交叉业务会非常的繁琐。
<2>动态代理:通过特定的设置,在程序运行期间指示JVM动态地生成类的字节码。
这种动态生成的类往往被用作代理类,即动态代理类。
也就是运行时做编译的事情并且把生成的字节码加载成这个类的Class对象.。
4、动态代理类
要为系统中的各种接口的类增加代理功能,那将需要太多的代理类,全部采用静态代理方式,将是一件非常麻烦的事情。
JVM可以在运行期动态生成出类的字节码,这种动态生成的类往往被用作代理类,即动态代理类。
JVM生成的动态类必须实现一个或多个接口,所以,JVM生成的动态类只能用作具有相同接口的目标类的代理。
CGLIB库可以动态生成一个类的子类,一个类的子类也可以用作该类的代理,所以,如果要为一个没有实现接口的类生成动态代理类,那么可以使用CGLIB库。
代理类的各个方法中通常除了要调用目标的相应方法和对外返回目标返回的结果外,还可以在代理方法中的如下四个位置加上系统功能代码:
1,在调用目标方法之前。
2,在调用目标方法之后。
3,在调用目标方法前后。
4,在处理目标方法异常的catch块中。
例子:
class proxy{ //代理类 void sayHello(){ ………. //前 try{ target.sayHello(); //调用目标类的方法 } catch(Exception e){ ……….. //处理异常的catch块中 } …………. //后 } } //代理类添加的系统功能,可以在调用目标方法之前、后,或是catch块中5、创建动态类及查看其方法列表信息
编码列出动态类中的所有方法和参数签名。
package mypkg; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.lang.reflect.Proxy;//动态代理类 import java.util.Collection; public class ProxyDemo { public static void main(String[] args) { //getProxyClass方法运行时期动态生成代理类的Class对象,并指定类加载器和接口 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); } } }
6、创建动态代理类的实例对象及调用其方法
创建动态代理类的实例对象:
用反射获得构造方法。
编写一个最简单的InvocationHandler类。
调用构造方法创建动态类的实例对象,并将编写的InvocationHandler类的实例对象传进去。
在创建动态代理类的实例对象中,参数用匿名内部类的形式编写,锻炼大家习惯匿名内部类。
总结思考:
让JVM创建动态类及其实例对象,需要给它提供哪些信息?
三个方面:
1,生成的类中有哪些方法,通过让其实现哪个接口的方式进行告知;
2,产生的类字节码必须有一个关联的类加载器对象;
3,生成的类中的方法的代码是怎样的,也得由我们提供。
把我们的代码写在一个约定好了接口对象的方法中,把对象传给它,它调用我的方法,即相当于插入了我的代码(即相当于在代理类的方法中插入了目标类的代码)。
提供执行代码的对象就是那个InvocationHandler对象,它是在创建动态类的实例对象的构造方法时传递进去的(Proxy类的构造函数)。
在上面的InvocationHandler对象的invoke方法中加一点代码,就可以看到这些代码被调用运行了。
代码示例:
package mypkg; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.Collection; public class ProxyDemo2{ public static void main(String[] args) throws Exception { //动态生成代理类,getProxyClass获得代理类的Class对象,指定加载器和接口 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();//执行有返回值的方法,会报告异常 } }
7、完成InvocationHandler对象的内部功能
用Proxy.newProxyInstance方法可以直接一步就创建出代理对象。
newProxyInstance( ClassLoader loader, Class<?>[] interfaces, InvocationHandler h):
返回一个指定接口的代理类实例,该接口可以将方法调用,指派到指定的调用处理程序h中。
就相当于,目标类中的方法代码在InvocationHandler中,代理类需调用它。
代码示例:
package mypkg; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.ArrayList; import java.util.Collection; public class ProxyDemo3{ public static void main(String[] args) throws Exception { //newProxyInstance( ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) Collection proxy = (Collection)Proxy.newProxyInstance( Collection. class.getClassLoader(), new Class[]{Collection.class}, new InvocationHandler() { //匿名内部类 ArrayList target = new ArrayList(); public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { long beginTime = System.currentTimeMillis(); Object retVal = method.invoke(target,args); long endTime = System.currentTimeMillis(); System. out.println(method.getName()+"running out of "+(endTime - beginTime)); return retVal; } } ); proxy.add("zxx"); proxy.add("lhm"); proxy.add("bxd"); System. out.println(proxy.size()); //结果:3 } }8、分析InvocationHandler对象的运行原理
//$Proxy0 代表代理类对象,这里只是概念展示 $Proxy0 implements Collection { InvocationHandler handler; public $Proxy0(InvocationHandler handler)//构造函数接收InvocationHandler对象 { this.handler = handler; } }实现Collection接口的动态代理类中的各个方法的代码又是怎样的呢?
代理类中的方法,就是通过InvocationHandler对象来调用目标类的相应方法。
//$Proxy0 代表代理类对象,这里只是概念展示 $Proxy0 implements Collection { InvocationHandler handler; public $Proxy0(InvocationHandler handler){ this.handler = handler; } //生成的Collection接口中的方法的运行原理 int size() { //handler对象的invoke方法中,封装了要具体执行的代码(目标类代码)。 return handler.invoke(this,this.getClass().getMethod("size"),null); } void clear(){ handler.invoke(this,this.getClass().getMethod("clear"),null); } boolean add(Object obj){ handler.invoke(this,this.getClass().getMethod("add"),obj); } }9、通过实例深入研究InvocationHandler
import java.lang.reflect.Constructor; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.Collection; public class ProxyDemo4 { public static void main(String[] args) throws Exception { //动态生成代理类的Class实例,此代理类实现Collection接口并调用目标类的方法 Class clazzProxy = Proxy.getProxyClass(Collection. class.getClassLoader(), Collection.class ); System.out.println("--------begin create instance object-------"); //获得此代理类的构造方法 Constructor constructor = clazzProxy.getConstructor(InvocationHandler. class) ; //生成一个代理类对象,InvocationHandler用匿名内部类。invoke方法封装了要执行的目标代码 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(); //执行有返回值的方法,会报告空指针异常 System.out.println(proxy.getClass()); //class $Proxy0 } }(1)分析上面打印动态类的实例对象时,结果为什么会是null呢?
package mypkg; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.ArrayList; import java.util.Collection; interface Advice { void beforeAdvice(Method method); void afterAdvice(Method method); } //封装了切面代码: class MyAdvice implements Advice { private long beginTime = 0; public void afterAdvice(Method method) { long endTime = System.currentTimeMillis(); System.out.println(method.getName()+"running out of"+(endTime - beginTime)); } public void beforeAdvice(Method method) { beginTime = System.currentTimeMillis(); } } public class ProxyTest { public static void main(String[] args) throws Exception { final ArrayList target = new ArrayList(); //目标类 //通过自定义的getProxy方法,生成代理类对象,并添加切面代码。 Collection collection = (Collection) getProxy(target,new MyAdvice()); collection.add("zxx" ); //结果:add running out of 0 } //实现框架功能,生成代理只需要传递target目标类,和封装了系统功能的对象MyAdvice public static Object getProxy(final ArrayList target,final Advice advice) { Object proxy = Proxy.newProxyInstance( //生成代理类对象 target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { advice.beforeAdvice(method); //切面代码 Object retVal = method.invoke(target ,args);//调用目标类的方法 advice.afterAdvice(method); //切面代码 return retVal; } } ); return proxy; } }11、实现AOP功能的封装与配置