天天看点

黑马程序员--静态代理和动态代理

——Java培训、Android培训、iOS培训、.Net培训、期待与您交流! ——-

一、静态代理

1,生活中的代理:

四川人从成都的代理商手中买联想电脑和直接跑到北京传智播客旁边来找联想总部买电脑,你觉得最终的主体业务目标有什么区别吗?基本上一样吧,都解决了核心问题,但是,一点区别都没有吗?从代理商那里买真的一点好处都没有吗?虽然多花了几百元钱,但不用坐车到北京,节约了时间和金钱。

2,程序中的代理:

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

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

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

代码示例:

[java] view plaincopy
package cn.itcast.proxy;  

public class Test {   
    public static void main(String[] args) {  
        Subject subject = new ProxyRealSubject();  
        subject.action();  
    }  
}  

//接口  
interface Subject {  
    void action();  
}  
//被代理类  
class RealSubject implements Subject {  

    @Override  
    public void action() {  
        System.out.println("------------");  
        System.out.println("------------");  
        System.out.println("这是被代理的类");  
        System.out.println("------------");  
        System.out.println("------------");  
    }  

}  
//代理类:  
class ProxyRealSubject implements Subject{  
    Subject subject;  
    public ProxyRealSubject() {  
        System.out.println("这是代理类");  
        subject = new RealSubject();  
    }  

    @Override  
    public void action() {  
        System.out.println("代理类开始");  
        subject.action();  
        System.out.println("代理类结束");  
    }  
}  
           

面向方面的编程:

1.系统中存在交叉业务,一个交叉业务就是要切入到系统中的一个方面

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

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

二、动态代理:

著名的框架spring中的核心技术aop就使用了动态代理技术。

出现的原因:要为系统中的各种接口的类增加代理功能,那将需要太多的代理类,全部采用静态代理方式,将是一件非常麻烦的事情!写成百上千个代理类,是不是太累!

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

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

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

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

1.在调用目标方法之前

2.在调用目标方法之后

3.在调用目标方法前后

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

创建动态类的实例对象的步骤:

1、用反射获得构造方法

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

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

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

5、将创建动态类的实例对象的代理改成匿名内部类的形式编写,锻炼大家习惯匿名内部类。

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

三个方面:

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

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

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

题目: 写一个ArrayList类的代理,实现和ArrayList中完全相同的功能,并可以计算每个方法运行的时间。

package com.itheima;

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 Test4 {

    /**
     * 4、 写一个ArrayList类的代理,实现和ArrayList中完全相同的功能, 并可以计算每个方法运行的时间。
     * 
     * 思路: 1.写一个获得代理类的通用方法: public static Object getProxy(final Object target,
     * final Advice advice){} target是被代理的类,Advice是需要插入系统中的功能接口。
     * 
     * 2.定义一个接口Advice,为插入的系统功能定义规则interface Advice{}
     * 
     * 3. 实现Advice接口,实现具体的系统功能,即:计算每个方法运行的时间。 class MyAdvice implements
     * Advice{实现方法}
     * 
     * @author 鲁中需
     */
    public static void main(String[] args) {
        // 获取ArrayList的代理
        final ArrayList list = new ArrayList();
        Collection proxy = (Collection) getProxy(list, new MyAdvice());
        // 演示效果:
        proxy.add("flx");
        proxy.add("bxd");
        proxy.add("zxx");
        proxy.add("lxz");
        System.out.println(list.size());
    }

    // 获得代理类的通用方法:
    public static Object getProxy(final Object target, final Advice advice) {
        Object proxy = Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() {

            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                // 在调用方法前,调用beforeMethod(Method method)
                advice.beforeMethod(method);
                Thread.sleep();
                Object retVal = method.invoke(target, args);
                // 在调用方法后,调用afterMethod(Method method)
                advice.afterMethod(method);
                return retVal;
            }
        });
        return proxy;

    }
}

// 定义一个接口Advice,为插入的系统功能定义规则
interface Advice {
    public abstract void beforeMethod(Method method);

    public abstract void afterMethod(Method method);
}

// 实现Advice接口,实现具体的系统功能,即:计算每个方法运行的时间。
class MyAdvice implements Advice {

    long beginTime = ;

    @Override
    public void beforeMethod(Method method) {
        System.out.println("调用方法前的功能");
        beginTime = System.currentTimeMillis();
    }

    @Override
    public void afterMethod(Method method) {
        System.out.println("调用方法后的功能");
        long costTime = System.currentTimeMillis() - beginTime;
        System.out.println("方法:" + method + " running time of " + costTime + "毫秒!");
    }
}
           

1、动态生成的类实现了Collection接口(可以实现若干接口),生成的类有Collection接口中的所有方法和一个如下接受InvocationHandler参数的构造方法。

2、构造方法接受一个InvocationHandler对象,接受对象了要干什么用呢?该方法内部的代码会是怎样的呢?

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

3.分析先前打印动态类的实例对象时,结果为什么会是null呢?调用有基本类型返回值的方法时为什么会出现NullPointerException异常?

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

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

继续阅读