黑马程序员——【Java高新技术】——代理

一、“代理概述”及“AOP概念”

  (一)代理概述

  1、问题:要为已存在的多个具有相同接口的目标类的各个方法增加一些系统功能,例如,异常处理、日志、计算方法的运行时间、事务管理等等,如何去做?

  解答:编写一个与目标类具有相同接口的代理类,代理类的每个方法调用目标类的相同方法,并在调用方法时加上系统功能的代码。

  2、代理原理图,如下:

  

  3、代理的优点

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

  (二)AOP概念

  1、问题引入:

  (1)系统中存在交叉业务,一个交叉业务就是要切入到系统中的一个方面,如下所示:

              安全       事务        日志

  StudentService     ———|——————|——————|—————       

  CourseService      ———|——————|——————|—————       

  MiscService        ———|——————|——————|—————         

  (2)用具体的程序代码描述交叉业务:

  method1         method2          method3

  {              {                {

  ------------------------------------------切面

  ....            ....              ......

  ------------------------------------------切面

  }              }                }

  2、AOP概念

  (1)定义:交叉业务的编程问题即为面向方面的编程(Aspect oriented program ,简称AOP),AOP的目标就是要使交叉业务模块化。

  (2)可以采用将切面代码移动到原始方法的周围,这与直接在方法中编写切面代码的运行效果是一样的,如下所示:

  ----------------------------------------切面

  func1          func2            func3

  {             {                {

   ....           ....               ....

  }             }                }

  -----------------------------------------切面

  总结:(1)使用代理技术正好可以解决这种交叉业务模块化的问题,代理是实现AOP功能的核心和关键技术。(2)安全,事务,日志等功能要贯穿到好多个模块中,所以,它们就是交叉业务。

二、动态代理技术

  1、手动增加代理类存在的问题?

  要为系统中的各种接口的类增加代理功能,则需要太多的代理类,全部采用静态代理方式,就要写成百上千个代理类,将是一件工作量巨大且非常麻烦的事情。

  2、如何解决上述存在的问题?

  JVM可以在“运行期”动态生成出类的字节码,这种动态生成的类往往被用作代理类,即动态代理类。

  3、动态类需注意细节:

  JVM生成的动态类必须实现一个或多个接口,这样JVM就知道该实现什么方法。所以,JVM生成的动态类只能用作具有相同接口的目标类的代理。

  4、如果一个目标类自身没有实现接口,如何让JVM动态生成的代理类与目标类有相同的方法列表呢?

  生成的代理类的方法声明要不要和目标类的方法一样?要。但目标类自身并没有实现接口,那通过什么样的方式告诉JVM生成的代理类与目标类有相同的方法列表,JVM干不了这件事情,因为没接口。

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

  5、在代理方法中什么位置可以插入系统功能代码?

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

  (1)在调用目标方法之前

  (2)在调用目标方法之后

  (3)在调用目标方法前后

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

三、JVM动态生成的类

  (一)创建动态类及查看其方法列表信息

  1、要求:

  (1)创建实现了Collection接口的动态类和查看其名称,分析Proxy.getProxyClass方法的各个参数。

  (2)编码列出动态类中的所有构造方法和参数签名

  (3)编码列出动态类中的所有方法和参数签名

  2、示例代码:

 1 import java.lang.reflect.Constructor;
 2 import java.lang.reflect.Method;
 3 import java.lang.reflect.Proxy;
 4 import java.util.Collection;
 5 public class ProxyTest {
 6     /**
 7      * @param args
 8      * @throws SecurityException
 9      * @throws NoSuchMethodException
10      * @throws Exception
11      * @throws IllegalArgumentException
12      * @throws IllegalAccessException
13      * @throws InstantiationException
14      */
15     public static void main(String[] args) throws NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, Exception {
16         Class clazzProxy1 = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);
17         System.out.println(clazzProxy1.getName());
18
19         System.out.println("");
20         Constructor[] constructors = clazzProxy1.getConstructors();
21         for(Constructor constructor : constructors){
22             String name = constructor.getName();
23             StringBuilder sb = new StringBuilder(name);
24             sb.append("(");
25             Class[] clazzParams = constructor.getParameterTypes();
26             for(Class clazzParam : clazzParams ){
27                 sb.append(clazzParam.getName()).append(",");
28             }
29             if( clazzParams!=null && clazzParams.length!=0)
30                 sb.deleteCharAt(sb.length()-1);
31             sb.append(")");
32             System.out.println(sb);
33         }
34
35         System.out.println("");
36         Method[] methods = clazzProxy1.getMethods();
37         for(Method method : methods){
38             String name = method.getName();
39             StringBuilder sb = new StringBuilder(name);
40             sb.append("(");
41             Class[] clazzParams = method.getParameterTypes();
42             for(Class clazzParam : clazzParams ){
43                 sb.append(clazzParam.getName()).append(",");
44             }
45             if( clazzParams!=null && clazzParams.length!=0)
46                 sb.deleteCharAt(sb.length()-1);
47             sb.append(")");
48             System.out.println(sb);
49         }
50     }
51 }

  (二)创建动态类的实例对象及调用其方法

  1、创建动态类的实例对象有三种方式:

  (1)首先通过Proxy类的getProxyClass(ClassLoader loader, Class<?>... interfaces)方法,获取代理类的对象;然后通过反射获得构造方法;最后通过接口InvocationHandler的子类创建对象;

  (2)首先通过Proxy类的getProxyClass(ClassLoader loader, Class<?>... interfaces)方法,获取代理类的对象;通过反射获得构造方法;通过给构造方法的newInstance()传入InvocationHandler的匿名内部类来创建对象;

  (3)直接通过Proxy类的newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)方法,创建对象。

  2、创建动态类的实例对象的代码实现

 1 import java.lang.reflect.Constructor;
 2 import java.lang.reflect.InvocationHandler;
 3 import java.lang.reflect.Method;
 4 import java.lang.reflect.Proxy;
 5 import java.util.ArrayList;
 6 import java.util.Collection;
 7 public class ProxyTest {
 8     /**
 9      * @param args
10      * @throws SecurityException
11      * @throws NoSuchMethodException
12      * @throws Exception
13      * @throws IllegalArgumentException
14      * @throws IllegalAccessException
15      * @throws InstantiationException
16      */
17     public static void main(String[] args) throws NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, Exception {
18         Class clazzProxy1 = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);
19         Constructor constructor = clazzProxy1.getConstructor(InvocationHandler.class);
20
21         System.out.println("");
22         //System.out.println("");
23         class MyInvocationHandler1 implements InvocationHandler{
24             @Override
25             public Object invoke(Object proxy, Method method, Object[] args)
26                     throws Throwable {
27                 // TODO Auto-generated method stub
28                 return null;
29             }
30         }
31         Collection proxy1 = (Collection)constructor.newInstance(new MyInvocationHandler1());
32         System.out.println(proxy1);
33
34         // System.out.println("");
35         Collection proxy2 = (Collection)constructor.newInstance(new InvocationHandler(){
36             @Override
37             public Object invoke(Object proxy, Method method, Object[] args)
38                     throws Throwable {
39                 // TODO Auto-generated method stub
40                 return null;
41             }
42         });
43
44         // System.out.println("");
45         Collection proxy3 = (Collection)Proxy.newProxyInstance(
46                 Collection.class.getClassLoader(),
47                 new Class[]{Collection.class},
48                 new InvocationHandler(){
49                     ArrayList target = new ArrayList();
50                     @Override
51                     public Object invoke(Object proxy, Method method,
52                             Object[] args) throws Throwable {
53                         long beginTime = System.currentTimeMillis();
54                         Object retVal = method.invoke(target, args);
55                         long endTime = System.currentTimeMillis();
56                         System.out.println(method.getName()+"run time"+(endTime-beginTime));
57                         return retVal;
58                     }
59                 }
60                 );
61         proxy3.add("zxx");
62         proxy3.add("flx");
63         proxy3.add("lhm");
64         proxy3.add("bxd");
65         proxy3.add("yzz");
66         System.out.println(proxy3.size());
67     }
68 }

  (三)总结思考

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

  解答:主要包括三个方面的信息:

  (1)生成的类中有哪些方法,通过让其实现哪些接口的方式进行告知;

  (2)产生的类字节码必须有个一个关联的类加载器对象;

  (3)生成的类中的方法的代码是怎样的,也得由我们提供。把我们的代码写在一个约定好了接口对象的方法中,把对象传给它,它调用我的方法,即相当于插入了我的代码。提供执行代码的对象就是那个InvocationHandler对象,它是在创建动态类的实例对象的构造方法时传递进去的。在上面的InvocationHandler对象的invoke方法中加一点代码,就可以看到这些代码被调用运行了。

  * 用Proxy.newInstance方法直接一步就创建出代理对象。

四、动态生成的类的内部代码分析

  在上面“创建动态类的实例对象”的代码中,动态生成的类实现了Collection接口(可以实现若干接口),生成的类有Collection接口中的所有方法和一个如下接受InvocationHandler参数的构造方法。

  1、问题:构造方法接受一个InvocationHandler对象,接收这个对象要干什么用呢?该方法内部的代码是怎样的呢?

  (1)构造方法接收一个参数,为了记住这个参数,以后运用它。

  (2)内部代码:

1 $Proxy0 implements Collection{
2     InvocationHandler handler;
3     public $Proxy0(InvocationHandler handler){
4         this.handler = handler;
5     }
6 }

  2、问题:实现Collection接口的动态类中的各个方法的代码又是怎样的呢? InvocationHandler接口中定义的invoke方法接收的三个参数又是什么意思?

 1 (1)$Proxy0 implements Collection{
 2     InvocationHandler handler;
 3     public $Proxy0(InvocationHandler handler){
 4         this.handler = handler;
 5     }
 6     //生成的Collection接口中的方法的运行原理
 7     int size(){
 8         return handler.invoke(this, this.getClass().getMethod("size"), null);
 9     }
10     void clear(){
11         handler.invoke(this, this.getClass().getMethod("clear"), null);
12     }
13     boolean add(Object obj){
14         handler.invoke(this, this.getClass().getMethod("add"), obj);
15     }
16 }

  (2)InvocationHandler接口中定义的invoke方法接收的三个参数意义,如下图说明:

  

  说明:客户端调用了代理对象objProxy,调用了代理对象的add()方法,为该方法传递了字符串参数"abc"。

  3、为什么动态类的实例对象的getClass()方法返回了正确结果呢?

  调用调用代理对象的从Object类继承的hashCode, equals, 或toString这几个方法时,代理对象将调用请求转发给InvocationHandler对象,对于其他方法,则不转发调用请求。

五、动态生成的类成为目标类的代理

  1、动态代理的工作原理图

  

  2、eclipse重构出一个getProxy方法绑定接收目标同时返回代理对象,怎样将目标类作为参数传进去?

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

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

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

  3、在上面将目标类作为参数传入之后,将系统功能代码模块化,即将切面代码也改为通过参数形式提供,怎样把要执行的系统功能代码以参数形式提供?

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

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

  4、将目标类和系统功能作为参数传递给getProxy()方法,实现示例代码如下:

  (1)创建ProxyTest类

 1 import java.lang.reflect.Constructor;
 2 import java.lang.reflect.InvocationHandler;
 3 import java.lang.reflect.Method;
 4 import java.lang.reflect.Proxy;
 5 import java.util.ArrayList;
 6 import java.util.Collection;
 7 public class ProxyTest {
 8     /** @param args
 9      * @throws SecurityException
10      * @throws NoSuchMethodException
11      * @throws Exception
12      * @throws IllegalArgumentException
13      * @throws IllegalAccessException
14      * @throws InstantiationException
15      */
16     public static void main(String[] args) throws NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, Exception {
17         final ArrayList target = new ArrayList();//将目标抽取出来,方法里面的内部类要访问局部变量必须添加final关键字
18         Collection proxy3 = (Collection)getProxy(target,new MyAdvice());//抽取出来的方法
19         proxy3.add("zxx");
20         proxy3.add("flx");
21         proxy3.add("lhm");
22         System.out.println(proxy3.size());
23     }
24     private static Object getProxy(final Object target,final Advice advice) { /*做成通用的方法,返回Object*/
25         Object proxy3 = Proxy.newProxyInstance(
26                 /*Collection.class.getClassLoader(),        //第一个参数*/
27                 target.getClass().getClassLoader(),            //    代理类的类加载器与目标类的类加载器相同,与目标类有关。
28
29                 /*new Class[]{Collection.class},        //第二个参数*/
30                 target.getClass().getInterfaces(),    //与target实现相同的接口,代理类要实现的接口也是目标类实现的接口,与目标类有关
31
32                 new InvocationHandler(){        //第三个参数,
33                     @Override
34                     public Object invoke(Object proxy, Method method,
35                             Object[] args) throws Throwable {
36                         /*
37                         long beginTime = System.currentTimeMillis();    //将系统功能抽取为一个对象
38                         Object retVal = method.invoke(target, args);
39                         long endTime = System.currentTimeMillis();    //将系统功能抽取为一个对象
40                         System.out.println(method.getName()+"run time"+(endTime-beginTime));
41                         return retVal;
42                         */
43                         advice.beforeMethod(method);
44                         Object retVal = method.invoke(target, args);
45                         advice.afterMethod(method);
46                         return retVal;
47                     }
48                 }
49                 );
50         return proxy3;
51     }
52 }

  (2创建Advice接口

import java.lang.reflect.Method;
public interface Advice {
    //一般来说,这个建议的接口应该有四个方法,这四个方法可以分别插入:
    /* 代理类的各个方法中通常除了要调用目标的相应方法和对外返回目标返回的结果外,
     * 还可以在代理方法中的如下四个位置加上系统功能代码:
     *    1.在调用目标方法之前
     *    2.在调用目标方法之后
     *    3.在调用目标方法前后
     *    4.在处理目标方法异常的catch块中
     * */
    void beforeMethod(Method method);
    void afterMethod(Method method);
}

  (3)创建Advice接口的子类MyAdvice

 1 import java.lang.reflect.Method;
 2 public class MyAdvice implements Advice {
 3     long beginTime = 0;
 4     public void beforeMethod(Method method) {
 5         // TODO Auto-generated method stub
 6         System.out.println("到黑马程序员训练营来学习了!");
 7         beginTime = System.currentTimeMillis();    //将系统功能抽取为一个对象
 8     }
 9
10     public void afterMethod(Method method) {
11         // TODO Auto-generated method stub
12         System.out.println("从黑马程序员训练营毕业工作了!");
13         long endTime = System.currentTimeMillis();    //将系统功能抽取为一个对象
14         System.out.println(method.getName()+" method run of time "+(endTime-beginTime));
15         System.out.print(System.lineSeparator() );
16     }
17 }

六、实现AOP功能的封装与配置

时间: 2024-10-20 05:39:25

黑马程序员——【Java高新技术】——代理的相关文章

黑马程序员——Java高新技术代理

代理 普通代理 很多时候,我们使用别人代码往往会发现别人代码的功能并不是十分符合我们的需求,调用别人的方法的时候,总是先new一个对象,然后我们的前处理做完,然后调用别人代码的方法,再加入后处理,这样做往往十分麻烦.代理就为其他类提供了一种控制其对象的方法.代理类和委托类必须实现同一个接口,这样代理类才能在需要的时候代替委托类对象,执行委托类的方法. interface Solution{ public void doSomething(); } //委托类Demo实现了接口 class Dem

黑马程序员——java高新技术(新特性、反射、泛型)

------Java培训.Android培训.iOS培训..Net培训.期待与您交流! ------- java高新技术 第一部分——JDK1.5新特性 1.增强for循环 格式: for(元素类型 变量名 : Collection集合 & 数组 ) { }//增强for循环括号里写两个参数,第一个是声明一个变量,第二个就是需要迭代的容器 高级for循环和传统for循环的区别: 高级for循环在使用时,必须要明确被遍历的目标.这个目标,可以是Collection集合或者数组,如果遍历Collec

黑马程序员——Java高新技术——反射机制

点击打开链接 点击打开链接 点击打开链接 android培训.<a">点击打开链接 点击打开链接 java培训.期待与您交流!">点击打开链接 点击打开链接 反射的基石--Class类 Java程序中的各个java类属于同一类事物,描述这类事物的java类名就是Class. Class类没有构造函数,不能new对象.怎么得到Class类的实例,有3中方法: ①类名.Class    Class  c1=Date.class; ②对象.getClass 获取对象所属的字

黑马程序员——Java高新技术——反射的复写

由于第一段视频学习效果不理想,希望重新看一遍反射视频,并多方面寻找资料,重新写一遍总结,以期java能力早日提高. Java——反射 一.Class类 Class 类的实例表示正在运行的 Java 应用程序中的类和接口.所以,Class可以提供方法获得动态的java类中的各个属性: (由定义可以知道Class创建的思路就是获得某一个特定java类的信息然后传给Class的对象,那么具体怎么做呢?) 这里说得java类的信息指的就是该java类的计算机的字节码:传给 Class cls1: 所以有

黑马程序员----Java高新技术之反射学习总结

------- android培训.java培训.期待与您交流! ---------- 反射的概念. 1.Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类中的所有属性和方法:对于任意一个对象,都能够调用它的任意一个方法和属性:这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制. 精妙的总结就是:反射就是将Java类中的各个成分映射成相应的Java类. 3.在Java中,描述事物的各种类同样也是一种事物,也可以用面向对象的方法来描述,即也有一个类来描述众多的J

黑马程序员——java高新技术——网络编程

点击打开链接 点击打开链接 点击打开链接 android培训.<a">点击打开链接 点击打开链接 java培训.期待与您交流!">点击打开链接 点击打开链接 网络编程 网络模型 l   OSI参考模型 l   TCP/IP参考模型 网络通讯要素 l   IP地址:网络中设备的标识,不易记忆,可用主机名,本地回环地址,127.0.0.1  主机名:localhost l   端口号:用于标识进程的逻辑地址,不同进程的标识,有效端口:0~65535,其中0~1024系统使

黑马程序员——JAVA高新技术——反射

----------android培训.java培训.java学习型技术博客.期待与您交流!------------ 一.对于反射的概念 对于JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法:对于任意一个对象,都能够调用它的任意一个方法和属性:这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制. JAVA反射(放射)机制:"程序运行时,允许改变程序结构或变量类型,这种语言称为动态语言".从这个观点看,Perl,Python,Ruby

黑马程序员——java高新技术——IO其他流对象

点击打开链接 点击打开链接 点击打开链接 android培训.<a">点击打开链接 点击打开链接 java培训.期待与您交流!">点击打开链接 点击打开链接 IO其他对象 PrintStream:字节打印流.为其他输出流添加了功能,提供了打印方法,可以将各种数据类型的数据原样打印. 构造函数可以接受的参数类型:file对象,字符串路径,字节输出流. 方法:println():打印各种基本数据类型. PrintWrite:字符打印流.构造函数可以接受的参数类型:file

黑马程序员_Java高新技术

1  JDK5的新特性 1.1 静态导入       在API中那些不需要new对象的类,可以在类文件的开头,import static java.lang.Math.*;这里把Math中的所有的静态方法都导入了,在类中不需要调用Math类就能直接用Math的方法了 package cn.wjd.staticimport; import static java.lang.Math.*; public class StaticImport { public static void main(Str

黑马程序员_高新技术_1_Java反射

------- android培训.java培训.期待与您交流! ---------- 0.反射知识体系 下图为反射整体的知识体系,把握住此图也就全局上掌握住反射所有内容. 1.反射概论 1)反射概念 其实字面上可以这么理解反射,平时使用类时都是由类new出对象,而反射则是通过对象"反射"出类的信息,好比一个人照镜子可以看到人类的特征,而看出机制就是镜子反射. 2)Java对象两种类型 Java程序中的许多对象在运行时会出现两种类型:编译时类型和运行时类型.如下代码: Person p