黑马程序员 java基础加强--代理类
一、代理类
代理类的概念与作用:
1、 作用:
要为已存在的多个具有相同接口的目标类的各个方法增加一些系统功能,
例如:异常处理,日志,计算方法的运行时间,事务管理等等。
2、 实现方式:
编写一个与目标类具有相同接口的代理类(容易实现已实现类的切换), 代理类的每个方法调用目标类的相同方法,并在调用方法时加上
系统功能的代码。
3 、应用:
当采用工厂模式和配置文件的方式进行管理,则不需要修改客户端程序,在配置文件中配置使用目标类,或者代理类,这样以后就很容易切换,
譬如,想要日志功能时就配置代理类,否则配置目标类,增加系统功能就很容易,去掉也很容易这样提高了灵活性
代理架构图
二、AOP(Aspect oriented program):面向方面的编程
当系统中存在交叉业务,一个交叉业务就是要切入到系统中的一个方面,如下所示:
安全 事务 日志
StudentService ------|----------|------------|-------------
CourseService -------|----------|------------|-------------
MiscService ------|----------|------------|-------------
用具体的程序代码描述交叉业务:
method1 method2 method3
{ { {
------------------------------------------------------切面
.... .... ......
------------------------------------------------------切面
} } }
交叉业务的编程问题即为面向方面的编程,AOP的目的就是要使交叉业务模块化,
可以采用将切面代码移动到原始方法的周围,这与直接在方面写切面代码的运行效果是一样的,如下所示:
------------------------------------------------------切面
func1 func2 func3
{ { {
.... .... ......
} } }
------------------------------------------------------切面
使用代理技术正好可以解决这种问题,代理是实现AOP功能的核心和关键技术。
三、动态代理技术
要为系统中的各种接口的类增加代理功能,那将需要太多的代理类,全部采用静态代理方式,将是一件非常麻烦的事情,
所以JVM可以为我们生成动态代理类。
1、动态代理类:
JVM可以在运行期间动态生成出字节码,这种动态生成的类往往被用作代理类,即动态代理类。
2、JVM生成的动态类只能用作具有相同接口的目标类的代理
JVM生成的动态类必须实现一个或多个接口(因为要委托JVM生成动态代理类,为了明确生成类中的方法,通过接口可直接告诉虚拟机
要生成类中所含的方法),所以,JVM生成的动态类只能用作具有相同接口的目标类的代理
3 、CGLIB库:为没有接口的方法生成代理类
当目标类没有实现接口,要想生成动态代理类代理,可以使用CGLIB库,它可以动态生成一个没有接口的类的子类,一个类的子类也可
以用作该类的代理。
4、代理类的各个方法中通常除了要调用目标的相应方法和对外返回目标返回结果外,还可以在代理方法中的如下四个位置加上系统的功能代码
1> 在调用目标方法之前
2> 在调用目标方法之后
3> 在调用目标方法前后
4> 在处理目标方法异常的catch块中
3.1、创建动态类及查看其方法列表信息
1 import java.lang.reflect.Constructor; 2 import java.lang.reflect.InvocationTargetException; 3 import java.lang.reflect.Method; 4 import java.lang.reflect.Proxy; 5 import java.util.Collection; 6 7 public class ProxyTest2 { 8 public static void main(String[] args) throws SecurityException, NoSuchMethodException, IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException { 9 10 //创建一个动态类,参数为类加载器和接口,类加载器通常使用和接口相同的类加载器 11 Class clazzProxy1 = Proxy.getProxyClass(Collection.class.getClassLoader(),Collection.class); 12 System.out.println(clazzProxy1.getName()); 13 14 System.out.println("------begin constructors list-----------"); //获取构造函数的信息 15 Constructor[] constructors = clazzProxy1.getConstructors(); 16 17 for (Constructor constructor : constructors) { //遍历构造函数 18 String name = constructor.getName(); 19 StringBuilder sBuilder = new StringBuilder(name); //借助StringBuilder来打印指定格式 20 sBuilder.append('('); 21 Class[] clazzParams = constructor.getParameterTypes(); //获取构造函数的参数类型 22 for (Class clazzParam : clazzParams) { //遍历构造函数的参数类型 23 sBuilder.append(clazzParam.getName()).append(','); //获取构造函数参数类型名 24 } 25 if(clazzParams.length!=0) //如果参数类型数组长度为0,就不删除sBuilder最后的符号 26 // if(clazzParams!=null&&clazzParams.length!=0) 27 sBuilder.deleteCharAt(sBuilder.length()-1); //去掉末尾的',' 28 sBuilder.append(')'); 29 System.out.println(sBuilder.toString()); //只有一个构造函数:Proxy0(java.lang.reflect.InvocationHandler) 30 } 31 32 System.out.println("------begin Methods list-----------"); //获取该动态类的一般方法信息 33 34 Method[] methods = clazzProxy1.getMethods(); 35 for (Method method : methods) { 36 String name = method.getName(); 37 StringBuilder sBuilder = new StringBuilder(name); 38 sBuilder.append('('); 39 Class[] clazzParams = method.getParameterTypes(); 40 for (Class clazzParam : clazzParams) { 41 sBuilder.append(clazzParam.getName()).append(','); 42 } 43 if(clazzParams.length!=0) 44 sBuilder.deleteCharAt(sBuilder.length()-1); 45 sBuilder.append(')'); 46 System.out.println(sBuilder.toString()); 47 } 48 } 49 }
3.2、创建动态类的实例对象及调用其方法
这里使用了3种方法实现HandlerInvocation接口,其中第1种内部返回null,为了分析handler内部工作原理,一个是匿名内部类
创建动态类的实例对象的方式有两种:
1、直接调用Proxy类的静态方法newProxyInstance( )获取字节码和实例对象
2、通过构造函数的方式创建
1> 调用Proxy的静态方法getProxyClass( )获取一个动态代理类
2> 通过代理类调用getConstructor( )获取代理类的构造函数
3> 通过构造函数调用newInstance( )方法
1 import java.lang.reflect.Constructor; 2 import java.lang.reflect.InvocationHandler; 3 import java.lang.reflect.InvocationTargetException; 4 import java.lang.reflect.Method; 5 import java.lang.reflect.Proxy; 6 import java.util.ArrayList; 7 import java.util.Collection; 8 9 public class ProxyTest3 { 10 public static void main(String[] args) throws SecurityException, NoSuchMethodException, IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException { 11 12 //创建一个动态类,参数为类加载器和接口,类加载器通常使用和接口相同的类加载器 13 Class clazzProxy1 = Proxy.getProxyClass(Collection.class.getClassLoader(),Collection.class); 14 System.out.println(clazzProxy1.getName()); 15 16 //创建动态代理类的实例对象 17 System.out.println("------begin create instance-----------"); 18 Constructor constructor = clazzProxy1.getConstructor(InvocationHandler.class); 19 //动态类的构造方法接收一个参数:InvocationHandler,但它是个接口 20 21 //三种不同方式的InvocationHandler内部实现方式 22 23 //方式一:创建实现类的方式 24 //这里复写的invoke方法返回null,是为了做测试 25 class MyInvocationHandler1 implements InvocationHandler{ 26 @Override 27 public Object invoke(Object proxy, Method method, Object[] args) 28 throws Throwable { 29 // TODO Auto-generated method stub 30 return null; 31 } 32 } 33 34 Collection proxy1 = (Collection)constructor.newInstance(new MyInvocationHandler1()); 35 System.out.println(proxy1.toString()); 36 /*打印结果为null,第一种可能是对象没有创建成功,第二种可能是toString()的返回值为null.如果对象为null,当调用toString 37 时会报告空指针异常,所以是第二种可能,这里的toString方法调用了invoke方法,而返回null,返回的null即为调用toString方法后的值,所以打印null*/ 38 39 proxy1.clear(); //调用Collection中无返回值的方法,可以通过,同样返回了null,但null相当于返回void,所以可以通过 40 //proxy1.size(); //调用有返回值的方法产生异常空指针异常的原因,是因为其内部会调用handler的invoke方法,由于invoke方法返回null,而size方法要返回一个整数 41 42 //方式二:匿名内部类的方式 43 Collection proxy2 = (Collection)constructor.newInstance(new InvocationHandler(){ 44 @Override 45 public Object invoke(Object proxy, Method method, Object[] args) 46 throws Throwable { 47 // TODO Auto-generated method stub 48 return null; 49 } 50 }); 51 52 //方式三:使用Proxy类的newProxyInstance方法直接获取字节码和实例对象 53 //指定一个目标 54 55 Collection proxy3 = (Collection)Proxy.newProxyInstance( 56 Collection.class.getClassLoader(), 57 new Class[]{Collection.class}, 58 new InvocationHandler(){ 59 60 @Override 61 public Object invoke(Object proxy, Method method,Object[] args) throws Throwable { 62 63 ArrayList target = new ArrayList(); 64 65 //将系统功能抽取成为一个对象,通过接口方法调用,而不需知道内部的方法 66 long beginTime = System.currentTimeMillis(); 67 //在目标对象身上去执行代理执行的方法, 68 Object retVal = method.invoke(target, args); 69 long endTime = System.currentTimeMillis(); 70 System.out.println("The running time of "+method.getName()+" method is "+(endTime-beginTime)); 71 return retVal;//目标方法将值返回给代理的方法,add方法的返回值就从代理方法的返回值上取 72 } 73 } 74 ); 75 proxy3.add("xxx"); /*调用invoke方法,这句话的意思将代理对象proxy3,代理对象的方法add(),参数"xxx",传给invoke方法当使用目标方法的时候,返回一个值,代理类就会接收到这个返回值*/ 76 proxy3.add("yyy"); 77 System.out.println(proxy3.size()); 78 System.out.println(proxy3.getClass().getName()); 79 /*调用invoke方法,去执行目标,目标的getClass().getName()应该返回ArrayList,但结果却为Proxy0.。 80 原因:只有HashCode,equals和toString方法才委托给handler,即自动调用invoke 方法,其他方法有自己的实现。*/ 81 } 82 83 }
3.3、分析InvocationHandler对象的运行原理及上述代码出现的现象
动态生成的类实现了Collection接口(可以实现若干接口),生成的类有Collection接口中的所有方法和一个接受InvocationHandler参数的构造方法。
现象及原因
1、构造方法接受一个InvocationHandler对象,接受对象了要干什么用呢?该方法内部的代码会是怎样的呢?
接收它,以后用它,内部代码即为:this.handler = handler
2、 实现Collection接口的动态类中的各个方法的代码又是怎样的呢?InvocationHandler接口中定义的invoke方法接受的三个参数又是什么意思?图解说明如下:
Client程序调用objProxy.add(“abc”)方法时,涉及三要素:objProxy对象、add方法、“abc”参数:
Class Proxy$ { add(Object object) { return handler.invoke(Object proxy, Method method, Object[] args); } }
3、分析先前打印动态类的实例对象时,结果为什么会是null呢?打印实例对象结果为null,原因:
第一种可能是对象没有创建成功,第二种可能是toString()的返回值为null.如果对象为null,当调用toString时会报告空指针异常,
所以是第二种可能,这里的toString方法调用了invoke方法,而返回null,返回的null即为调用toString方法后的值,所以打印null
4、调用有基本类型返回值的方法时为什么会出现NullPointerException异常?
因为其内部会调用handler的invoke方法,由于invoke方法返回null,而size方法要返回一个整数
5、分析为什么动态类的实例对象的getClass()方法返回了正确结果呢?
因为调用调用代理对象的从Object类继承的hashCode, equals, 或toString这几个方法时,代理对象将调用请求转发给InvocationHandler对象,
对于其他方法,则不转发调用请求。
四、动态代理类的设计原理与结构
客户端调用代理类,代理的构造方法接收一个handler对象,客户端调用代理的各个方法,代理的各个方法会把调用请求转发给handler对象,
去找handler的invoke方法,在invoke方法内部可以加入要实现的功能,最后handler对象又会把各个请求分发给目标的相应方法。
每次使用代理类调用目标的各个方法,都会调用handler的invoke方法。
而invoke方法返回值即为目标方法的最终值,即invoke方法返回什么值,使用代理类调用目标方法后得到的就是什么值,我们也可以在invoke方
法内部对于返回值进行修改。
动态代理的工作原理图
五、编写可生成代理和可插入通告的通用方法
将要加入的系统功能以对象的形式提供进去,由外部传入,代理对某个目标进行作用也是以对象形式传进去,所以只需把目标和代理的要执行的
系统功能(通告)传进去即可。
创建一个接口,让代理去实现
import java.lang.reflect.Method; public interface Advice { void beginMethod(Method method); void afterMethod(Method method); }
为代理类实现接口的类,到时候直接为代理传进该类的对象即可
import java.lang.reflect.Method; public class MyAdvice implements Advice{ long beginTime = 0; @Override public void beginMethod(Method method) { // TODO Auto-generated method stub System.out.println("轻轻地我来了"); beginTime = System.currentTimeMillis(); } @Override public void afterMethod(Method method) { // TODO Auto-generated method stub System.out.println("我挥一挥衣袖,不带走一片云彩……"); long endTime = System.currentTimeMillis(); System.out.println("The running time of "+method.getName()+" method is "+(endTime-beginTime)); } }
实现可生成代理和可插入通告的通用方法
1 import java.lang.reflect.Constructor; 2 import java.lang.reflect.InvocationHandler; 3 import java.lang.reflect.InvocationTargetException; 4 import java.lang.reflect.Method; 5 import java.lang.reflect.Proxy; 6 import java.util.ArrayList; 7 import java.util.Collection; 8 9 public class ProxyTest { 10 public static void main(String[] args) throws SecurityException, NoSuchMethodException, IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException { 11 12 //使用Proxy类的newProxyInstance方法直接获取字节码和实例对象 13 //指定一个目标 14 ArrayList target = new ArrayList(); 15 Collection proxy3 = getProxy(target,new MyAdvice()); 16 proxy3.add("xxx"); 17 proxy3.add("yyy"); 18 System.out.println(proxy3.size()); 19 } 20 21 //可生成代理和插入通告的通用方法 22 private static Collection getProxy(final Object target,final Advice advice) { 23 Collection proxy3 = (Collection)Proxy.newProxyInstance( 24 Collection.class.getClassLoader(), 25 /*new Class[]{Collection.class},*/ 26 target.getClass().getInterfaces(),//需要一个和目标相同的接口 27 new InvocationHandler(){ 28 29 @Override 30 public Object invoke(Object proxy, Method method,Object[] args) throws Throwable { 31 32 /*//将系统功能抽取成为一个对象,通过接口方法调用,而不需知道内部的方法 33 long beginTime = System.currentTimeMillis(); 34 //在目标对象身上去执行代理执行的方法, 35 Object retVal = method.invoke(target, args); 36 long endTime = System.currentTimeMillis(); 37 System.out.println(method.getName()+"running time of"+(endTime - beginTime)); 38 return retVal;//目标方法将值返回给代理的方法,add方法 //的返回值就从代理方法的返回值上取 39 */ 40 advice.beginMethod(method); 41 Object retVal = method.invoke(target, args); 42 advice.afterMethod(method); 43 return retVal; 44 } 45 } 46 ); 47 return proxy3; 48 } 49 50 }