黑马程序员-----基础加强-动态代理

来源:互联网 发布:欧舒丹护手霜味道知乎 编辑:程序博客网 时间:2024/05/01 05:12

------<a href="http://www.itheima.com" target="blank">Java培训、Android培训、iOS培训、.Net培训</a>、期待与您交流! -------



代理类的作用与原理

1、生活中的代理:就是常说的代理商,从厂商将商品卖给消费者,消费者不用很麻烦的到厂商在购买了。

2、程序中的代理:要为已经存在的多个具有相同接口的目标类的各个方法增加一些系统功能,如异常处理、日志、计算方法的运行时间、事物管理等等。

3、简单示例:编写一个与目标类具有相同接口的代理类,代理类的每个方法调用目标类的相同方法,并在调用方法时加上系统功能的代码,如:

目标类:                             代理类:

class X{                               Xproxy{

   void sayHello(){                        void sayHello(){

syso:Hello;                            startTime

}                                           X. sayHello();

}                                       endTime;}}

一般用接口来引用其子类,如:Collection coll = new ArrayList();

4、代理类的有点:

如果采用工厂模式和配置文件的方式进行管理,则不需要修改客户端程序,在配置文件中配置是使用目标类还是代理类。这样以后很容易切换,如果想要日志功能时,就配置代理类,否则配置目标类,这样,增加系统功能很容易,以后运行一段时间后,又想换掉系统功能也很容易。

5、获取代理类的构造方法和方法代码

[java] view plaincopy
  1. import java.lang.reflect.*;  
  2. import java.util.*;  
  3. public class ProxyTest {  
  4.     public static void main(String[] args) throws Exception{  
  5.         //获取代理类Proxy的Class对象,传入的是类加载器和相应的字节码对象  
  6.         Class clazzProxy1 = Proxy.getProxyClass(  
  7.                 Collection.class.getClassLoader(), Collection.class);  
  8.         System.out.println(clazzProxy1.getName());//$Proxy0  
  9.         System.out.println("---begin constructor list------");  
  10.         //获取代理类的构造方法,可能含有多个,得到数组  
  11.         Constructor[] constructors = clazzProxy1.getConstructors();  
  12.         //遍历数组,获取每个构造方法  
  13.         for(Constructor constructor : constructors){  
  14.             //先得到构造方法的名字,并装入字符串容器中  
  15.             String name = constructor.getName();  
  16.             StringBuilder sBul = new StringBuilder(name);  
  17.             sBul.append('(');  
  18.             //获取构造方法中的参数类型,并遍历  
  19.             Class[] clazzParams = constructor.getParameterTypes();  
  20.             for(Class clazzParam : clazzParams){  
  21.                 sBul.append(clazzParam.getName()).append(',');  
  22.             }  
  23.             //将最后一个逗号去除  
  24.             if(clazzParams != null && clazzParams.length!=0)  
  25.                 sBul.deleteCharAt(sBul.length()-1);  
  26.             sBul.append(')');  
  27.             System.out.println(sBul.toString());  
  28.         }  
  29.           
  30.         System.out.println("---begin method list------");  
  31.         //获取代理类的方法,存入数组  
  32.         Method[] methods = clazzProxy1.getMethods();  
  33.         //遍历数组,获取每个方法  
  34.         for(Method method : methods){  
  35.             //先得到方法的名字,并装入字符串容器中  
  36.             String name = method.getName();  
  37.             StringBuilder sBul = new StringBuilder(name);  
  38.             sBul.append('(');  
  39.             //获取方法中的参数类型,并遍历  
  40.             Class[] clazzParams = method.getParameterTypes();  
  41.             for(Class clazzParam : clazzParams){  
  42.                 sBul.append(clazzParam.getName()).append(',');  
  43.             }  
  44.             //将最后一个逗号去除  
  45.             if(clazzParams!=null && clazzParams.length!=0)  
  46.                 sBul.deleteCharAt(sBul.length()-1);  
  47.             sBul.append(')');  
  48.             System.out.println(sBul.toString());  
  49.         }  
  50. }  

AOP

简述:AOPAspect Oriented Program)即面向方面的编程。

系统中存在交叉业务,一个交叉业务就是要切入到系统中的一个方面,如下所示:
                              安全       事务         日志
StudentService  ------|----------|------------|-------------
CourseService  ------|----------|------------|-------------
MiscService       ------|----------|------------|-------------


用具体的程序代码描述交叉业务:
method1         method2          method3
{                      {                       { 
------------------------------------------------------切面
....            ....              ......
------------------------------------------------------切面
}                       }                       }


交叉业务的编程问题即为面向方面的编程(Aspect oriented program ,简称AOP),AOP的目标就是要使交叉业务模块化。可以采用将切面代码移动到原始方法的周围,这与直接在方法中编写切面代码的运行效果是一样的,如下所示:
------------------------------------------------------切面
func1         func2            func3
{             {                { 
....            ....              ......
}             }                }
------------------------------------------------------切面


使用代理技术正好可以解决这种问题,代理是实现 AOP 功能的核心和关键技术。

 

动态代理技术

一、概述:

1、要为系统中的各种接口的类增加代理功能,那将需要太多的代理类,这时就不能采用静态代理方式,需用动态代理技术。

2、动态代理类:JVM可在运行时,动态生成类的字节码,这种动态(不是代理,只是拿出来作为代理类)生成的类往往被用作代理类,即动态代理类。

注:JVM生成的动态类必须实现一或多个接口,所以JVM生成的动态代理类只能用作具有相同接口的目标类代理。

3CGLIB库可以动态生成一个类的子类,一个类的子类也可以作为该类的代理,所以,如果要为一个没有实现接口的类生成动态代理,那么可以使用CGLIB库。

4、代理类各个方法通常除了调用目标相应方法和对外返回目标返回的结果外,还可以再代理方法中的如下位置上加上系统功能代码:

1)在调用目标方法之前

2)在调用目标方法之后

3)在调用目标方法前后

4)在处理目标方法异常的catch块中。

二、分析JVM动态生成的类

1、创建动态类的实例对象:

1)用反射获得构造方法

2)编写一个最简单的InvocationHandler的类

3)调用构造方法创建动态类的实例对象,并将编写的InvocationHandler类的实例对象传进去。

示例:

第一、打印创建的对象和调用对象的无返回值的方法和getClass方法,演示调用其他没有返回值的方法报告的异常

第二、将创建的动态类的实例对象的代理改写成为匿名内部类的形式编写。

[java] view plaincopy
  1. package cn.itcast.test3;  
  2. import java.lang.reflect.*;  
  3. import java.util.*;  
  4. public class ProxyTest {  
  5.     public static void main(String[] args) throws Exception{  
  6. //创建动态代理类的三种方式  
  7.         //方式一:通过接口的子类创建对象  
  8.         Collection proxy1 = (Collection)  
  9.                 constructor.newInstance(new MyInvocationHandler());  
  10.         System.out.println(proxy1);//null  
  11.         System.out.println(proxy1.toString());//null  
  12.         proxy1.clear();//无异常  
  13.         //proxy1.size();  
  14.           
  15.         //方式二:匿名内部类  
  16.         Collection proxy2 = (Collection)  
  17.                 constructor.newInstance(new InvocationHandler(){  
  18.                     public Object invoke(Object proxy, Method method,  
  19.                             Object[] args) throws Throwable {  
  20.                         // TODO Auto-generated method stub  
  21.                         return null;  
  22.                     }  
  23.                 });  
  24.           
  25.         //方式三:  
  26.         //通过代理类的newProxyInstance方法直接创建对象  
  27.         Collection proxy3 = (Collection)Proxy.newProxyInstance(  
  28.             //定义代理类的类加载器  
  29.             Collection.class.getClassLoader(),  
  30.             //代理类要实现的接口列表  
  31.             new Class[]{Collection.class},  
  32.             //指派方法调用的调用处理程序  
  33.             new InvocationHandler() {  
  34.                 //创建集合,制定一个目标  
  35.                 ArrayList target = new ArrayList();  
  36.                 public Object invoke(Object proxy, Method method, Object[] args)  
  37.                         throws Throwable {  
  38.                     //测试程序运行时间  
  39.                     long beginTime = System.currentTimeMillis();  
  40.                     //调用目标方法,将其从return抽出来,加入代理所需的代码  
  41.                     Object retVal = method.invoke(target, args);  
  42.                     long endTime = System.currentTimeMillis();  
  43.                     //测试  
  44.                     System.out.println(method.getName() +   
  45.                             " run time of " +   
  46.                             (endTime - beginTime));  
  47.                     return retVal;  
  48.                 }  
  49.             }  
  50.             );  
  51.         //通过代理类调用目标方法,每调用一个目标的方法就会执行代理类的方法  
  52.         //当调用一次add方法时,就会找一次InvocationHandler这个参数的invoke方法  
  53.         proxy3.add("sdfd");  
  54.         proxy3.add("shrt");  
  55.         proxy3.add("rtbv");  
  56.         System.out.println(proxy3.size());  
  57.         System.out.println(proxy3.getClass().getName());  
  58.     }  
  59. }  

让jvm创建动态类及其实例对象,需要给它提供哪些信息:

  • 生成的类中有哪些方法,通过让其实现哪些接口的方式进行告知;
  • 产生的类字节码必须有个一个关联的类加载器对象
  • 生成的代理类中的方法的代码是怎样的,也得由我们提供。把我们的代码写在一个约定好了接口对象的方法中,把对象传给它,它调用我的方法,即相当于插入了我的代码。提供执行代码的对象就是那个 InvocationHandler 对象,它是在创建动态类的实例对象的构造方法时传递进去的。在上面的 InvocationHandler 对象的 invoke 方法中加一点代码,就可以看到这些代码被调用运行了。

 

三、问题:

1、在上面的方式一的代码中,调用无返回值的方法返回的是null,而调用有返回值方法的时候会报NullPointException异常的原因:

proxy1.size()方法中,size()会调用Object invoke()方法,而对其覆写时的返回值是null,而size()本身会返回int类型,两者返回的类型不相等,所以就会报错。

而对于proxy3.size()不报错,是因为size()返回值与代理对象中handler参数返回值是一致的,当前目标返回什么,代理就返回什么,所以不会报错。

注意:目标返回值和代理返回值必须是同一类型。

2、为何动态类的实例对象的getClass()方法返回了正确结果,而没调用invoke方法:

因为代理类从Object上继承了许多方法,其中只对三个方法(hansCodeequalstoString)进行开发,委托给handler去自行处理,对于它身上其他方法不会交给代理类去实现,所以对于getClass()方法,还是会有Object本身实现的。即proxy3.getClass(),该是什么结果还是什么结果,并不会交给invoke方法处理。

四、总结分析动态代理类的统计原理和结构:
动态代理的工作原理图

1、怎样将目标传进去:

1)直接在InvocationHandler实现类中创建目标类的实例对象,可看运行效果和加入日志代码,但是毫无意义。

2)为InvocationHandler实现类注入目标的实例对象,不能采用匿名内部类的形式了。

3)让匿名内的InvocationHandler实现类访问外面的方法中的目标类实例对象的final类型的引用变量。

2、动态代理的工作原理:

1Client(客户端)调用代理,代理的构造方法接受一个InvocationHandlerclient调用代理的各个方法,代理的各个方法请求转发给刚才通过构造方法传入的handler对象,又把各请求分发给目标的相应的方法。就是将handler封装起来,其中this引用了当前的放(发来什么请求就接受哪个方法)

2)将创建代理的过程改为一种更优雅的方式,eclipse重构出一个getProxy方法绑定接受目标,同时返回代理帝乡,让调用者更懒惰,更方便,调用者甚至不用接触任何代理的API

3、把系统功能代理模块化,即切面代码也改为通过参数形式提供,怎么把要执行的系统功能代码以参数的形式提供:

1)把要执行的代码装到一个对象的某个方法中,然后把此对象作为参数传递,接收者只要调用这个对象的方法,即等于执行了外接提供的代码。

2)为方法增加一个Advice参数。

代码示例:

[java] view plaincopy
  1. //创建接口Advice  
  2. import java.lang.reflect.Method;  
  3. /*接口中需要实现四个方法 
  4.  * 调用目标方法之前 
  5.  * 调用目标方法之后 
  6.  * 调用目标方法前后 
  7.  * 在处理目标方法异常的catch块中 
  8.  */  
  9. //这里只列出两个作为示例  
  10. public interface Advice {  
  11.     void beforeMethod(Method method);  
  12.     void afterMethod(Method method);  
  13. }  

[java] view plaincopy
  1. //创建实现Advice接口的子类  
  2. package cn.itcast.test3;  
  3. import java.lang.reflect.Method;  
  4. //实现Advice接口中方法的具体内容  
  5. public class MyAdvice implements Advice {  
  6.   
  7.     long beginTime = 0;  
  8.     public void beforeMethod(Method method) {  
  9.         // TODO Auto-generated method stub  
  10.         System.out.println("从这里开始");  
  11.         beginTime = System.currentTimeMillis();   
  12.     }  
  13.     public void afterMethod(Method method) {  
  14.         // TODO Auto-generated method stub  
  15.         long endTime = System.currentTimeMillis();  
  16.         System.out.println("从这里结束");  
  17.         System.out.println(method.getName() + " run time of " + (endTime-beginTime));  
  18.     }  
  19. }  
[java] view plaincopy
  1. //封装getProxy方法  
  2. package cn.itcast.test3;  
  3. import java.lang.reflect.*;  
  4. import java.util.*;  
  5. public class MyProxy {  
  6.     public static void main(String[] args)throws Exception {  
  7.         //创建目标对象,并进行操作测试  
  8.         final ArrayList target = new ArrayList();  
  9.         Collection proxy = (Collection)getProxy(target,new MyAdvice());  
  10.         proxy.add("sdf");  
  11.         proxy.add("wgcd");  
  12.         proxy.add("hgwe");  
  13.         System.out.println(proxy.size());  
  14.           
  15.     }  
  16.     //作为一个通用的方法,就使用Object  
  17.     //传入一个目标,并传入一个接口,此接口作为通信的契约,才能调用额外的方法  
  18.     private static Object getProxy(final Object target,final Advice advice) {  
  19.         Object proxy = Proxy.newProxyInstance(  
  20.                 target.getClass().getClassLoader(),  
  21.                 //这里的接口要和target实现相同的接口  
  22.                 target.getClass().getInterfaces(),  
  23.                 new InvocationHandler() {  
  24.                     public Object invoke(Object proxy, Method method, Object[] args)  
  25.                             throws Throwable {  
  26.                         //通过契约,使用其方法--before和after方法  
  27.                         advice.beforeMethod(method);  
  28.                         Object value = method.invoke(target, args);  
  29.                         advice.afterMethod(method);  
  30.                         return value;  
  31.                     }  
  32.                 }  
  33.                 );  
  34.         return proxy;  
  35.     }  
  36. }  

实现类似 spring 的可配置的 AOP 框架

需求:要求在不修改 Client 代码的情况下,可以修改我们创建的对象的类型,并且配置是否创建目标功能的代理类对象,那么我们可以选用   配置文件 + 工厂模式  的方法。我们的需求在配置文件中配置好以后,使用工厂类对象读取配置文件获得我们要创建的对象的类的名字的字符串,使用该字符串来在工厂中使用反射来创建我们我们需要的实例。
现在我们的配置选项可以设置为我们要创建的对象要么是一般目标类对象,要么是目标代理类对象,创建目标代理类对象的话,还需要指定 目标类 和 Advice 接口的实现类。
配置文件格式可以为:
[java] view plaincopy
  1. #xxxClassName=java.util.ArrayList  
  2. xxxClassName=cn.itcast.proxy.aopframework.ProxyFactoryBean  
  3. xxxClassName.target=java.util.ArrayList  
  4. xxxClassName.advice=java.util.ArrayList  

其中 xxxClassName 可以是 Collection 这种我们 Client  程序中需要的对象的类型的名字

config.properties 文件:

[java] view plaincopy
  1. Collection=java.util.ArrayList  
  2. #Collection=cn.itcast.proxy.aopframework.ProxyFactoryBean  
  3. Collection.target=java.util.ArrayList  
  4. Collection.advice=cn.itcast.proxy.MyAdvice  

定义一个bean工厂接口

定义一个bean工厂接口,专门负责生产指定的 javabean 的对象
[java] view plaincopy
  1. package cn.itcast.proxy.aopframework;  
  2.   
  3. public interface BeanFactory {  
  4.     Object getBean(String className) throws Exception;  
  5. }  


[java] view plaincopy
  1. 定义一个 ConfigurableBeanFactory 类  
  2.   
  3. 定义一个 ConfigurableBeanFactory 类实现 bean 工厂,它是根据配置文件产生 bean 类  

[java] view plaincopy
  1. package cn.itcast.proxy.aopframework;  
  2.   
  3. import java.io.IOException;  
  4. import java.io.InputStream;  
  5. import java.util.Properties;  
  6. public class ConfigurableBeanFactory implements BeanFactory {  
  7.   
  8.     private Properties properties;  
  9.   
  10.     public ConfigurableBeanFactory(InputStream propertiesInputStream) {  
  11.         // TODO Auto-generated constructor stub  
  12.         properties = new Properties();  
  13.         try {  
  14.             properties.load(propertiesInputStream);  
  15.         } catch (IOException e) {  
  16.             // TODO Auto-generated catch block  
  17.             e.printStackTrace();  
  18.         }  
  19.     }  
  20.   
  21.     @Override  
  22.     public Object getBean(String className) throws Exception {  
  23.         // 根据传入的类名去配置文件中拿到实际配置的类名  
  24.         String configClassName = properties.getProperty(className);  
  25.         Object beanObject = Class.forName(configClassName).newInstance();  
  26.         // 如果配置的对象是 代理工厂类对象,那么要返回就得是下面配置的目标和Advice组合形成的代理的对象  
  27.         if (beanObject instanceof ProxyFactoryBean) {  
  28.             ProxyFactoryBean proxyFactoryBeanObject = (ProxyFactoryBean) beanObject;  
  29.             String target = properties.getProperty(className + ".target");  
  30.             String advice = properties.getProperty(className + ".advice");  
  31.             proxyFactoryBeanObject.setTarget(target);  
  32.             proxyFactoryBeanObject.setAdvice(advice);  
  33.   
  34.             beanObject = proxyFactoryBeanObject.getBean(target);  
  35.         }  
  36.   
  37.         return beanObject;  
  38.     }  
  39.   
  40.     public static void main(String[] args) throws Exception {  
  41.         InputStream propertiesInputStream = ConfigurableBeanFactory.class  
  42.                 .getResourceAsStream("config.properties");  
  43.         BeanFactory beanFactory = new ConfigurableBeanFactory(  
  44.                 propertiesInputStream);  
  45.           
  46.         System.out.println(beanFactory.getBean("Collection").getClass().getName());  
  47.     }  
  48. }  

定义一个 ProxyFactoryBean 类

定义一个 ProxyFactoryBean 类它是一个产生 代理类对象的工厂,它本身就是 javabean ,而且实现了 BeanFactory 接口。


[java] view plaincopy
  1. package cn.itcast.proxy.aopframework;  
  2.   
  3. import java.lang.reflect.InvocationHandler;  
  4. import java.lang.reflect.Method;  
  5. import java.lang.reflect.Proxy;  
  6.   
  7. import cn.itcast.proxy.Advice;  
  8. public class ProxyFactoryBean implements BeanFactory{  
  9.     private String target;  
  10.     private String advice;  
  11.   
  12.     public ProxyFactoryBean() {  
  13.         super();  
  14.     }  
  15.   
  16.     public String getTarget() {  
  17.         return target;  
  18.     }  
  19.   
  20.     public void setTarget(String target) {  
  21.         this.target = target;  
  22.     }  
  23.   
  24.     public String getAdvice() {  
  25.         return advice;  
  26.     }  
  27.   
  28.     public void setAdvice(String advice) {  
  29.         this.advice = advice;  
  30.     }  
  31.   
  32.     @Override  
  33.     public Object getBean(String className) throws Exception {  
  34.         // TODO Auto-generated method stub  
  35.         Object target = Class.forName(this.target).newInstance();  
  36.         Advice advice = (Advice)Class.forName(this.advice).newInstance();  
  37.         return createDynamicProxyInstance(target, advice);  
  38.     }  
  39.       
  40.     private static Object createDynamicProxyInstance(final Object target, final Advice advice) {  
  41.         Object proxyObject = Proxy.newProxyInstance(target.getClass()  
  42.                 .getClassLoader(), target.getClass().getInterfaces(),  
  43.                 new InvocationHandler() {  
  44.   
  45.                     @Override  
  46.                     public Object invoke(Object proxy, Method method,  
  47.                             Object[] args) throws Throwable {  
  48.                         // TODO Auto-generated method stub  
  49.                           
  50.                         // 1.在 target 功能之前添加代理的额外功能  
  51.                         advice.beforeMethod(method);  
  52.                         // 2.调用 target 功能  
  53.                         Object returnValue = method.invoke(target, args);  
  54.                         // 3.在 target 功能之后添加代理的额外功能  
  55.                         advice.afterMethod(method);  
  56.                           
  57.                         return returnValue;  
  58.                     }  
  59.                 });  
  60.         return proxyObject;  
  61.     }  

0 0
原创粉丝点击