我是靠谱客的博主 认真硬币,最近开发中收集的这篇文章主要介绍黑马程序员-----Java代理机制的学习笔记,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

-------android培训、java培训、期待与您交流! ----------

内容源自 张孝祥老师的Java高新技术.
以下是我看视频的笔记:
---------------------------------------------------------------------------
这些笔记对应张孝祥老师的Java高新技术的如下视频:
49.分析代理类的作用与原理及AOP概念
50.创建动态类及查看其方法列表信息
51.创建动态类的实例对象及调用其方法
52.完成InvocationHandler对象的内部功能
53.分析InvocationHandler对象的运行原理
54.总结分析动态代理类的设计原理与结构
55.编写可生成代理和插入通告的通用方法
56.实现类似spring的可配置的AOP框架


-------------------------------------------------------
49.分析代理类的作用与原理及AOP概念


比如:
    要为已存在的多个具有相同接口的目标类的各个方法
        增加一些系统功能:
    如: 异常处理, 日志, 计算方法的运行时间, 事务管理, 等等.
如何处理这种要求呢?


使用代理机制来处理这种问题.
如下
class X{
    void sayHello(){syso:helloworld;}
}
代理类:
class XProxy{
    void sayHello(){
        starttime
        X.sayHello() //这里调用X的方法
        endtime
    }
}
即, 
做一个X的代理XProxy.
即, 其中的方法和X一样.
    在X代理XProxy的方法里面, 调用X的方法.
即, 在调用原来的方法的前后可以加上一些功能.




然后, 客户端, 调用的是代理.
    代理和目标类 有着相同的 接口.
    即, 我们会把是先把目标类中的要用上代理的方法做成一个接口.
    然后, 代理只需要去实现这个接口就可以了.
然后, 在调用时, 客户端实际上是就是调用代理的这个接口的方法.


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


AOP(aspect oriented program), 面向方面的编程.


系统中存在交叉业务
如多个类, 各处理各的事, 
    但不管处理什么事, 都要有 安全, 事务, 日志 等功能.
即, 安全, 事务, 日志 等功能, 要贯穿在好多模块里面.
即, 安全, 事务, 日志就是这些类的就交叉业务.
                  安全   事务   日志
StudentService -----|------|------|-------
CourseService  -----|------|------|-------
MiscService    -----|------|------|-------


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


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


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


只要涉及AOP, 就会涉及到代理技术.


要为系统中的各种接口的类, 增加代理功能, 
    那将需要太多的代理类, 
全部采用静态代理方式, 将是一件非常麻烦的事情.


JVM可以在运行期动态生成出类的字节码, 
    这种动态生成的类 往往被用作代理类, 即动态代理类.
注意:
    动态生成的类  不一定是代理类. 
    只是多用来生成代理类而已.


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


如果目标类没有接口的话, 怎么办?
    有一个第三方的开源类库:CGLIB
        其可以动态生成一个类的子类, 
            一个类的子类也可以用作该类的代理
                ----这个就是项妙的地方了.
                ---- 在子类中重写父类的方法,
                    并在重写的方法中调用父类的方法
.


代理类中的方法, 所做的功能可以添加的位置有:
    1 在调用目标方法 之前
    2 在调用目标方法 之后
    3 在调用目标方法 之前 和 之后 都加
    4 在调用目标方法 异常的catch块中


-------------------------------------------------------
50.创建动态类及查看其方法列表信息


我们现在, 用JVM的接口来生成动态代理类
    ----要求目标类要用接口.


Proxy
全称: java.lang.reflect.Proxy
    ----即, 其用的是反射的机制.
里面全是 static方法
    ----即, 这是一个工具类.
其有一个方法:
    Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces) 
        即, 动态得到一个代理类.
        ClassLoader loader: 指定类加载器来加载这个所生成的代理类.
        Class<?>... interfaces: 指定这个代理类所实现的接口的Class对象.
        
        如:
        我们要生成一个动态代理类来实现Collection接口, 
            其所用的类加载器, 通常是Collection类的加载器.
            -----即, 我们一般是这样做的.


应用:
    public static void main(String[] args) {
        // 生成实现了Collection接口的动态代理类
        // 并打印其构造方法
        //   打印其所有方法
        
        //1 生成一个动态代理类
        Class<?> clazzProxy = 
                Proxy.getProxyClass(Collection.class.getClassLoader(), 
                        Collection.class);
        
        //2 获取这个动态代理类的名称: com.sun.proxy.$Proxy0
        System.out.println(clazzProxy.getName());
        
        //3 获取这个动态代理类的构造方法, 并打印出来
        //com.sun.proxy.$Proxy0(java.lang.reflect.InvocationHandler)
        Constructor[] constructors = clazzProxy.getConstructors();
        System.out.println("---------------构造方法-------------");
        for (Constructor constructor : constructors){
            StringBuilder str = new StringBuilder();
            str.append(constructor.getName());
            str.append("(");
            Class[] clazzParas = constructor.getParameterTypes();
            for (Class clazzPara : clazzParas){
                str.append(clazzPara.getName() + ",");
            }
            //注意, 这一步是必须的, 因为我们不能保证构造方法有参数
            //注意, 下面这两个条件, 我只是为了保险起见才这么做的.
            if (clazzParas!=null && clazzParas.length!=0){
                str.deleteCharAt(str.length()-1);
            }
            str.append(")");
            System.out.println(str);
        }
        
        //4 获取这个动态代理类的方法, 并打印出来
        Method[] methods = clazzProxy.getMethods();
        System.out.println("---------------一般方法-------------");
        for (Method method : methods){
            StringBuilder str = new StringBuilder();
            str.append(method.getName());
            str.append("(");
            Class[] clazzParas = method.getParameterTypes();
            for (Class clazzPara : clazzParas){
                str.append(clazzPara.getName() + ",");
            }
            //注意, 这一步是必须的, 因为我们不能保证构造方法有参数
            //注意, 下面这两个条件, 我只是为了保险起见才这么做的.
            if (clazzParas!=null && clazzParas.length!=0){
                str.deleteCharAt(str.length()-1);
            }
            str.append(")");
            str.append(":" + method.getReturnType().getName());
            System.out.println(str);
        }
    }
得到结果:
com.sun.proxy.$Proxy0  -----动态代理类放在com.sun.proxy包中---名称带$表示复合类, 0表示序号
---------------构造方法-------------
com.sun.proxy.$Proxy0(java.lang.reflect.InvocationHandler)
---------------一般方法-------------
add(java.lang.Object):boolean
remove(java.lang.Object):boolean
equals(java.lang.Object):boolean ------- Collection里已有
toString():java.lang.String
hashCode():int ------- Collection里已有
clear():void
contains(java.lang.Object):boolean
isEmpty():boolean
iterator():java.util.Iterator
size():int
toArray([Ljava.lang.Object;):[Ljava.lang.Object;
toArray():[Ljava.lang.Object;
spliterator():java.util.Spliterator
addAll(java.util.Collection):boolean
stream():java.util.stream.Stream
forEach(java.util.function.Consumer):void
containsAll(java.util.Collection):boolean
removeAll(java.util.Collection):boolean
removeIf(java.util.function.Predicate):boolean
retainAll(java.util.Collection):boolean
parallelStream():java.util.stream.Stream ------- 代理类自身的
isProxyClass(java.lang.Class):boolean ------- 代理类自身的
getInvocationHandler(java.lang.Object):java.lang.reflect.InvocationHandler ------- 代理类自身的
newProxyInstance(java.lang.ClassLoader,Ljava.lang.Class;,java.lang.reflect.InvocationHandler):java.lang.Object ------- 代理类自身的
getProxyClass(java.lang.ClassLoader,[Ljava.lang.Class;):java.lang.Class ------- 代理类自身的
wait():void
wait(long,int):void
wait(long):void
getClass():java.lang.Class
notify():void
notifyAll():void


一个是:
    我们看到生成的动态代理类的名称为:
        com.sun.proxy.$Proxy0   ---而Proxy是放在java.lang.reflect包中的.
        -----动态代理类放在com.sun.proxy包中
        -----名称带$表示复合类, 
        -----Proxy 表示这是一个代理类
        -----0表示序号


一个是:
    我们在命名一个字节码变量时, 常常用 clazz来命名.
        这是一个习惯.


一个是:
    其构造方法:
        com.sun.proxy.$Proxy0(java.lang.reflect.InvocationHandler)
    只有一个.
    参数为: InvocationHandler类型.
        后面会说到.


一个是:
    这个类中的方法, 不仅仅有Collection的方法, 
        还有一些代理类自己的方法:
        parallelStream():java.util.stream.Stream
        isProxyClass(Class):boolean
        getInvocationHandler(Object):InvocationHandler
        newProxyInstance(ClassLoader,[Ljava.lang.Class;,InvocationHandler):Object
        getProxyClass(ClassLoader,[Ljava.lang.Class;):Class
        
        其中 [Ljava.lang.Class 相当于 Class[].


一个是:
    InvocationHandler 是一个接口, 其只有一个方法:
        public Object invoke(Object proxy, //动态代理类对象
                                           //invoke内部可能会用到动态代理类对象
                            Method method, //所调用的方法的Method对象
                            Object[] args) //方法的参数
        -----在这个方法里, 我们可 实现这些系统的功能.
    具体的内容下面就要讲到了.


-------------------------------------------------------
51.创建动态类的实例对象及调用其方法


通过上面, 我们仅仅得到了动态代理类的Class对象.
现在,
    我们利用 动态代理类 来创建其实例对象.


如何来创建 动态代理类的对象呢?
    当然 只有两个方法:
    一种: Class::newInstance()
            ---用无参构造方法
            ---但我们知道, 动态代理类没有无参构造方法.
            ---所以不能采用这种方法.
    一种: $Proxy0 的构造方法.
            然后调用它来创建对象.
            ---下面我们说的就是这一种.
后面还会介绍一种:
    Proxy.newProxyInstance(ClassLoader, Class<?>[], InvocationHandler):Object ---后面基本只用这种
        这是一个 static 方法.
        此方法是综合以下两个步骤:
            Proxy.getProxyClass(ClassLoader, Class<?>... interfaces):Class<?> 
          和
            $Proxy0(InvocationHandler): Object


动态代理类的构造方法只有一种:
    $Proxy0(InvocationHandler)
        即, 要传向一个 InvocationHandler 对象.
        但这个是一个 接口, 不能 new InvocationHandler()!


所以, 我们常常是用匿名类的方法来实现:
    Collection<?> col = (Collection<?>) constructor.newInstance(new InvocationHandler(){
        public Object invoke(Object proxy, Method method, Object[] args)
                throws Throwable {
            return null;
        }
    });
完整代码如下:
    //创建 动态代理类的 实例对象
    try {
        //1 获得构造方法
        //  这个clazzProxy就是刚才我们上面:
        //       用Proxy.getProxyClass(_,_)得到的动态代理类Class对象
        Constructor constructor = clazzProxy.getConstructor(InvocationHandler.class);
        //2 调用这个构造方法
        //  一个是: 获得的对象类型, 我们要用那个接口Collection来接收.
        //  一个是: 这里有类型强制转换
        //  一个是: 现在暂时将invoke方法返回null, 即没有实现其中功能.
        Collection<?> col = (Collection<?>) constructor.newInstance(new InvocationHandler(){
            public Object invoke(Object proxy, Method method, Object[] args)
                    throws Throwable {
                return null;
            }
        });
        //3 使用这个 动态代理类对象col
        System.out.println(col);  //null
        System.out.println(col.toString()); //null
        System.out.println(col.size()); //异常 java.lang.NullPointerException
    } catch (Exception e){
        e.printStackTrace();
    }
注意:
    System.out.println(col);  //null
        得到null, 并不代表着col就为null, 有可能其toString为null.
    System.out.println(col.toString()); //null
        我们看到, 确实是toString为"null".
        如果是 col为null, 其会指空指针异常
    System.out.println(col.size()); //异常 java.lang.NullPointerException
        这是因为, 这是一个接口啊, 这个Collection没有指向任何实际集合啊.
        所以, 不能得到集合的大小, 所以报异常.








-------------------------------------------------------
52.完成InvocationHandler对象的内部功能


总结下:
    我们上面要创建一个动态代理类, 需要三个信息:
    一个是: 该代理类的加载器     ----我们要指定
    一个是: 该代理类实现的接口   ----我们要指定, 如Collection接口
    一个是: 用该代理类创建实例对象---我们要指定InvocationHandler对象
                                        ---在这里, 我们可以做一些系统的功能.
上面, 将这三个信息, 分成几个部分来完成.
而实际上, 
    Proxy中有一个static方法newProxyInstance可以一步到位:
        Object newProxyInstance(ClassLoader loader, 
                                Class<?>[] interfaces, 
                                InvocationHandler h) 
    其参数就是我们上面所说的那三个:
    ClassLoader loader:    该代理类的加载器     ----我们要指定
    Class<?>[] interfaces: 该代理类实现的接口   ----我们要指定, 如Collection接口
    InvocationHandler h:   用该代理类创建实例对象---我们要指定InvocationHandler对象
                                        ---在这里, 我们可以做一些系统的功能.
        因为实现的接口可能不止一个, 所以是一个数组.
        因为最后要还传入一个InvocationHandler对象, 
            所以, 那么得用数组, 而不能用可变参数.




现在, 我们用这个方式来写一个:
public static void main(String[] args) {
    //注意, 这里将类型改为final
    final ArrayList<String> list = new ArrayList<String>();
    //创建 动态代理类的实例----其已经和这个list对象挂钩了
    Collection<String> proxy = 
            (Collection<String>) Proxy.newProxyInstance(Collection.class.getClassLoader(), 
                new Class[]{Collection.class}, 
                new InvocationHandler(){
                    //目录标对象记录到这里来
                    private Object target = list; //要求这个list是final的.
                    //这个功能, 是用于计算目标类方法的运行时间
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        long startTime = System.currentTimeMillis();
                        Object retVal = method.invoke(target, args);
                        long endTime = System.currentTimeMillis();
                        System.out.println("方法"+method.getName()+"运行时间为:"+(endTime-startTime));
                        return retVal;
                    }
                }
            );
    //调用动态代理类对象方法 来 调用list的方法
    proxy.add("hello");
    proxy.add("world");
    proxy.add("nihao");
    System.out.println(proxy.size());;
}


这里几点注意处:
一个是:
    InvocationHandler对象, 同样也是用匿名内部类实现的, 如下:
        new InvocationHandler(){
            //目录标对象记录到这里来
            private Object target = list; //要求这个list是final的.
            //这个功能, 是用于计算目标类方法的运行时间
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                long startTime = System.currentTimeMillis();
                Object retVal = method.invoke(target, args);
                long endTime = System.currentTimeMillis();
                System.out.println("方法"+method.getName()+"运行时间为:"+(endTime-startTime));
                return retVal;
            }
        }
    其中, 我们得设一个域: private Object target = list; //要求这个list是final的.
        用于记录 这个动态代理类对象 所要 代理的 那个目标对象 是哪一个.
    因为, 我们设立 代理的目标就是, 对目标类进行AOP编程的.
        代理类对象, 要和 目标类对象 一一对应.


    注意, 这里, 我们是直接将list传给这个target数据域的.
    因为匿名内部类不能有构造方法.
        而且, 刚好在匿名内部类内部是可以直接访问其外的方法的局部变量的.
    所以就这么做了.


一个是:
    见下面讲解 动态代理类 的原理部分吧.


-------------------------------------------------------
53.分析InvocationHandler对象的运行原理




JVM所自动生成的动态代理类的内部实质是:
class $Proxy0{
    //一个数据域
    private InvocationHandler handler;
    //构造方法
    public $Proxy0(InvocationHandler handler)
    {
        this.handler = handler;
    }
    //一般方法:
    int size(){
        return handler.invoke(this,   //动态代理类对象
                            this.getClass().getMethods("size"),  //size这个Method对象
                            null);  //参数
    }
    ...
}


注意:
一个是: 
    内部一个数据域: 
        private InvocationHandler handler;
    用于记录, 这个代理类对象对应的 InvocationHandler 是哪一个.
    这个 数据域 , 我们得在构造方法中对它进行赋值.


一个是:
    我们调用:
        proxy.add("haha");
            转为三个要素:
            一个是 col---动态代理类的对象
            一个是 add ---所调用的方法
            一个是 "haha" ---参数.
    所以, 
        InvocationHandler的invoke方法的参数, 也对应这三个.
            handler.invoke(proxy,   //动态代理类对象
                           this.getClass().getMethods("add"),  //size这个Method对象
                           "haha");  //参数
        还把这个结果返回.
    proxy.size()的机制也一样.


一个是:
    如果 我们在invoke内部这么调用:
        return method.invoke(proxy, args);
    结果呢, 会是一个死循环.
    要注意:
        动态代理类 和 目标类, 都实现了 共同的接口.
        所以, 如果 method.invoke(proxy, args)
            那么, 就是代理类对象不断调用自身了, 所以会死循环.


一个是:
    在invoke方法中, 
        我们可以加之前, 之后, 前后, 异常处理, 
        我们也可以对这个参数args进行修改.
    我们 在invoke中, 可以用到代理类对象, 
            为什么 要传入这个代理类对象呢?
            因为 我们在invoke中, 会进行一些操作, 
                    可能会用到动态代理类对象
            所以, 我们invoke的参数中, 保留了Object proxy, 以备用.


    而, 其内部, 必然会 用到 目标类对象.
        而目标类对象, 我们通常是设一个数据域来处理.


一个是:
    在我们最上面的例子中:
        public Object invoke(Object proxy, Method method, Object[] args)
                throws Throwable {
            return null;
        }
    invoke直接返回null.
        也正是这个原因, 导致当时调用 col.size()会抛出异常.
        因为 size应返回int型的, 
        而它, 却返回了null了.
        所以 抛异常.
    如果col有指向具体的集合的话, size会返回0,1,等对应的包装器对象, 
        所以不会返回null的, 所以不会异常的的.


一个是:
    我们调用 动态代理类对象的 proxy.getClass(),
                我们会返回$Proxy0啊, 
        而不是 目标的 Class对象: java.util.Arraylist.
    为什么呢?
        因为 动态代理类 只对 其所实现的接口的那些方法去调用invoke方法
    而对于Object的几个方法, 
        只有 hashCode, equals, toString才派发给handler去invoke目标类方法去处理, 
        其它的 方法, 如getClass, wait之类, 保留动态代理类自身的内容的.
            即, 不会去调用invoke实用目标的方法的.


    比如, 上面我们调用col.toString, 打印的null , 
        即, 对toString, 其会去目标那里去实现.


    -----注意一点: Object 不是 接口.
        那为什么会对 toString去调用invoke呢?----我已查证, Collection中没有toString.


总结下, 动态代理类的机制:


像GUI中的注册监听器一样, 
    当外界有 事件发生时, 两件事:
        控制内部会得到一个事件对象, 
        触发注册到控制内部的事件监听器.
    然后, 事件监听器调用自己内部的方法来完成对事件的影应.
----不过, 这个说法勉强.
    因为GUI的事件监听器, 其中不需要我们直接传一个target进去的.
        而是, 而是通过它的响应方法的参数来传进去的.
----而这个代理类的 InvoketionHandler的target对象, 需要我们手动传进去. 麻烦.




动态代理类是这样的:
    proxy调用接口方法时, 
        实际上是 注册到自身的handler上的invoke来响应.
    在handler中, 其包含的内容有:
        一个是: 目标对象要用---否则怎么调得动方法呢
        一个是: invoke方法要有.




总之:
    最终是要调用 InvocationHandler对象的invoke方法的.
    所以 invoke要知道几个要素:
        一个是 target, 即, 要知道其调用的是哪一个目标类
        一个是 proxy, 即, 要知道其代理类对象是哪一个.
                    invoke内部可能会用到动态代理类对象--如取一些信息之类
        一个是 method, 得知道自己要调接口的哪一个方法
        一个是 agrs, 得知道要往这个方法传哪些参数.
    下面三个元素, 以invoke的参数形式传进去, 
    而第一个元素, 通常是以 InvocationHandler的数据成员形式存在.






-------------------------------------------------------
54.总结分析动态代理类的设计原理与结构
55.编写可生成代理和插入通告的通用方法






其工作原理图为:



中间那个圈 要注意.
我们上面是将这个圈的代码直接写死了.---这叫硬编码.
    这样的话, 这个代理类只能处理这种 代码.
    不灵活.
所以, 我们可以把这个圈里的东西封装成一个方法.
    ----提高了代码的复用性.


即, 下面, 我们就要 让这种系统功能的代码 以参数的形式传给它.
    通常, 我们是传一个对象给 InvocationHandler中的一个数据域.
    然后 invoke方法中, 去调用 这个数据域的方法就可以了.


通常, 我们会把这个东西设为一个接口/抽象类.
    名称称为Advice
在这个接口中, 设几个方法:
    前面执行的操作, 
    后面执行的操作,
    异常处理中执行的操作.
其实有多个方法的话, 那么改为抽象类更好一些.
如下:
    public abstract class Advice {  //视频是将其做interface处理.
                        //这个会不会有影响? ---至少, 在我这里, 代码是可以运行的.
                        //这个是不是抽象类没关系, 这只是AOP所执行的方法而已. 
                        //而代理类那些则必须是接口!
        public void front(Object proxy, Method method, Object[] args){}
        public void behind(Object proxy, Method method, Object[] args){}
        public void exceptionProcess(Object proxy, Method method, Object[] args, Throwable exception){}
    }
为什么这里的方法参数要这些?
因为, 在我们要添加的系统功能的这些方法, 可能会用到这些信息.---当然, 可以不用, 需要时再写.
因为, 我们这里要做一个框架, 即要有广泛的适应性, 所以建议把invoke方法的参数都写上去.




即, 要在 InvocationHandler中设置两个数据域.
    一个是 Object target; 域, 以放目标对象.
    一个是 Advice advice; 域, 以放一些处理的方法.
而invoke应当写成这样:
    public Object invoke(Object proxy, Method method, Object[] args) 
            throws Throwable {
        Object retValue = null;
        try{
            advice.front(proxy, method, args);
            retValue = method.invoke(target, args);
            advice.behind(proxy, method, args);
        } catch (Exception e){
            advice.exceptionProcess(proxy, method, args, e);
        }
        return retValue;
    }
即, 也要用一些异常处理的操作.---这个纯粹是我写的.




好了, 看下我的代码:
先写一个MyAdvice:
    ----即, 我们使用代理的最关键部分---AOP的核心.
public class MyAdvice extends Advice{
    
    private long startTime;
    private long endTime;
    
    @Override
    public void front(Object proxy, Method method, Object[] args) {
        startTime = System.currentTimeMillis();
        System.out.println("开始执行"+method.getName());
    }
    @Override
    public void behind(Object proxy, Method method, Object[] args) {
        endTime = System.currentTimeMillis();
        System.out.println("执行完成"+method.getName()+", 执行时间:"+(endTime-startTime)+"毫秒.");
    }
    @Override
    public void exceptionProcess(Object proxy, Method method, Object[] args, Throwable exception) {
        //还没有想好要怎么处理它
    }
}
    --------当然, 我们也可以用 匿名内部类的形式来写这一个类.
    呵呵.






然后是写一个 获得动态代理对象的方法:
    ------这是我们这一单元的核心部分了.
    ------这个代码是可以重用的.
    ------我把很对类型都改为Object类型了.---这样, 可以目标对象的类型可以随意.
    我们可以看到:
        参数两个:
            一个是 目标;----要在谁的方法这前做
            一个是 建议;----即要做什么事
    public static Object getProxy(final Object obj, final Advice adv) {
        return Proxy.newProxyInstance(
                obj.getClass().getClassLoader(), 
                obj.getClass().getInterfaces(),  //获取其所有接口.
                new InvocationHandler(){
                    //目录标对象记录到这里来
                    private Object target = obj;
                    //记录这个Advice对象
                    private Advice advice = adv;
                    //实现invoke方法
                    public Object invoke(Object proxy, Method method, Object[] args) 
                            throws Throwable {
                        Object retVal = null;
                        try{
                            advice.beforeMethod(method);
                            retVal = method.invoke(target, args);
                            advice.afterMethod(method);
                        } catch (Throwable t){
                             advice.exceptionMethod(method, t);
                        }
                        return retVal;
                    }
                }
        );
    }


然后是, 进行一个调用:
    public static void main(String[] args) {
        //注意, 这里将类型改为final
        final ArrayList<String> list = new ArrayList<String>();
        //创建 动态代理类的实例----其已经和这个list对象挂钩了
        Collection<String> proxy = 
                (Collection<String>) getProxy(list, new MyAdvice());
        //调用动态代理类对象方法 来 调用list的方法
        proxy.add("hello");
        proxy.add("world");
        proxy.add("nihao");
        System.out.println(proxy.size());;
    }




以后写Spring, 其实就是写MyAdvice.
即, 我们传入目标, 还有这个Advice对象 就行了.
这就是一个小小的框架.


即, 给你目标, 给你系统功能, 你得给我一个代理.


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


如果, 我们要做一个类似于Spring框的架的AOP, 
    其大概的实现思路是怎么样的?


Object obj = BeanFacktory.getBean("XXX");
        "XXX"---是指Bean的名字, 如java.util.Array
        这个"XXX"是由一个配置文件来提供的.


这个BeanFacktory.getBean(__)内部怎么写?
两种可能:
一种是 如果是 cn.itcast.ProxyFactoryBean这个特殊的类的话, 
    那么 调用其方法来创建代理.
一种是: 不是这种特殊的类.
    则用 BeanFacktory.getBean(__)来创建Bean对象.


比如配置文件如下:
#xxx=java.util.ArrayList
xxx=cn.itcast.ProxyFactoryBean
xxx.target=java.util.ArrayList
xxx.advic=cn.itcast.MyAdvice


如果是 ArrayList的话, 
    BeanFacktory.getBean("xxx")返回ArrayList的javabean对象
如果是 ProxyFactoryBean的话, 
    那么做调用 ProxyFactoryBean
    则 还会取下其target和advice是谁, 
    然后生成 一个其对应的动态代理类对象 返回.


那么:
obj就是创建出的javaBean对象.
        或者是 一个代理.




BeanFactory构造方法 接受一个配置文件.
    即有一个数据域: Properties型的数据域.




对javabean而言, 一个重要的特性, 其必须有一个不带参数的构造方法.




public class BeanFactory {
    //因为BeanFactory是根据配置文件来产生javabean的,
    //所以要有一个域来保留这个配置文件的信息
    private Properties properties;
    //构造方法
    public BeanFactory(InputStream inputStream){
        properties = new Properties();
        try {
            properties.load(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        } 
    }
    //非静态方法---因为每一个配置文件对应一个对象
    public Object getBean(){
        String clazzName = properties.getProperty("className");
        Class<?> clazz = null;
        Object obj = null;
        try {
            clazz = Class.forName(clazzName);
            obj = clazz.newInstance();
            if (obj instanceof ProxyBeanFactory){
                Advice advice = (Advice)(Class.forName(properties.getProperty("advice")).newInstance());
                Object target = Class.forName(properties.getProperty("target")).newInstance();
                ProxyBeanFactory proxyFactory = (ProxyBeanFactory)obj;
                proxyFactory.setAdvice(advice);
                proxyFactory.setTarget(target);
                obj = proxyFactory.getProxy();
                return obj;
            }
            return obj;
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException();
        } 
    }
    
    public static void main(String[] args) throws Exception{
        InputStream config = 
                BeanFactory.class.getResourceAsStream("config.properties");
        BeanFactory beanFactory = new BeanFactory(config);
        config.close();  //用完, 就要关掉.
        
        Object obj = beanFactory.getBean();
        System.out.println(obj.getClass());
        System.out.println(obj);
    }
}




public class ProxyBeanFactory {
    private Advice advice;
    private Object target;
    public Advice getAdvice() {
        return advice;
    }
    public void setAdvice(Advice advice) {
        this.advice = advice;
    }
    public Object getTarget() {
        return target;
    }
    public void setTarget(Object target) {
        this.target = target;
    }
    //与之前写的那个getProxy不一样了
    //这里没有参数, 而是把advice和target做为数据成员处理.
    public Object getProxy() {
        return Proxy.newProxyInstance(
                target.getClass().getClassLoader(), 
                target.getClass().getInterfaces(),
                new InvocationHandler(){
                    //目录标对象记录到这里来
                    private Object target = ProxyBeanFactory.this.target;
                    //记录这个Advice对象
                    private Advice advice = ProxyBeanFactory.this.advice;
                    //实现invoke方法
                    public Object invoke(Object proxy, Method method, Object[] args) 
                            throws Throwable {
                        Object retVal = null;
                        try{
                            advice.beforeMethod(method);
                            retVal = method.invoke(target, args);
                            advice.afterMethod(method);
                        } catch (Throwable t){
                             advice.exceptionMethod(method, t);
                        }
                        return retVal;
                    }
                }
        );
    }


}


这个就是 Spring的BeanFactory的原理了.

这上面讲了 Spring 的 两个框架:
    一个是 getProxy 方法
    一个是 BeanFactory和ProxyBeanFactory两个框架了.
道理其实就是这么简单.

最后

以上就是认真硬币为你收集整理的黑马程序员-----Java代理机制的学习笔记的全部内容,希望文章能够帮你解决黑马程序员-----Java代理机制的学习笔记所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(38)

评论列表共有 0 条评论

立即
投稿
返回
顶部