黑马程序员--java基础加强之代理(Proxy)

一、代理类的作用
   在开发中,一个已经完成的类如果需要添加一些功能时,不会去修改源代码,而是通过一些其他方法去处理。
   代理类就提供了这种方法,它可以为那些实现了同一个接口的类中的各个方法增加一些系统功能,比如异常处理、日志、计算方法运行的时间、事务管理等。
二、代理类的操作流程与原理
   编写一个与目标类实现同一接口的代理类,代理类的每个方法调用目标类的同一方法,并在调用方法时加上系统功能的代码。
   如果采用工厂模式或者配置文件的方式进行管理,则不需要修改客户端程序,只要在配置文件中配置是使用代理类还是使用目标类就行,这样方便于切换。
   比如想要日志功能就配置代理类,否则配置目标类,这样就可以在以后运行过程中非常方便的增加和取消系统功能。
三、AOP(Aspect oriented program)面向方面编程
   1、利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率;
   2、主要的功能是:日志记录,性能统计,安全控制,事务处理,异常处理等等,每一个功能都是一个交叉业务;
   3、主要的目的是:将交叉业务的代码从业务逻辑代码中划分出来进行模块化,然后再应用到需要的程序中。而代理是实现AOP的核心和关键技术。
四、动态代理技术
   1、JVM可以在运行期动态生成出类的字节码,这种动态生成的类往往被用作代理类,既动态代理类。
   2、JVM生成的动态代理类必须实现一个或多个接口,这样就可以知道其中有些什么方法。
   3、如果目标类没有实现接口,可以用第三方提供的CGLIB库,它可以动态生成类的子类,这个子类也可以作为目标类的代理类。
事例需求一:获取实现Collection接口的目标类的代理类,并获取其中的构造方法和成员方法

public class ProxyTest {
    public static void main(String[] args) {
        //需要用到java.lang.reflect.Proxy类中的getProxyClass方法获取代理类,该方法需要传入的参数是类加载器和接口的字节码
        Class clazzProxy = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);
        System.out.println(clazzProxy.getName());
        getCons(clazzProxy);//打印结果表明只有一个有参构造方法,需要传入的参数是实现InvocationHandler接口的子类对象。
        getMeths(clazzProxy);
    }
    //获取这个类的构造方法
    public static void getCons(Class clazzProxy){
        Constructor [] constructors = clazzProxy.getConstructors();
        System.out.println("----begin constructors---");
        for (Constructor constructor : constructors) {            
            System.out.println(constructor);
        }
    }
    
    //获取代理类的方法
    public static void getMeths(Class clazzProxy){
        Method [] methods = clazzProxy.getMethods();
        System.out.println("-------begin methods------");
        for (Method method : methods) {
            System.out.println(method);
        }
    }
}

事例需求二:接上例,创建动态类的实例对象并调用其方法

public class ProxyTest {
    public static void main(String[] args) throws Exception{
        
    //第一种获取该代理类对象实例的方法    
        Class clazzProxy = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);
        //clazzProxy.newInstance();//因为上例说明该代理类没有无参构造方法,所以不能用这个方法去创建实例对象
        //获取构造方法
        Constructor constructor = clazzProxy.getConstructor(InvocationHandler.class);
        //创建对象,参数类型为一个接口,所以必须使用内部类来实现这个接口
        Collection proxyCon1 = (Collection)constructor.newInstance(new InvocationHandler(){
            public Object invoke(Object arg0, Method arg1, Object[] arg2)throws Throwable {
                return null;
            }            
        });
        
    //第二种获取获取该代理类对象实例的方法,该方法接受三个参数一步到位    
        Collection proxyCon2 = (Collection)Proxy.newProxyInstance(
                //第一个参数,类加载器
                Collection.class.getClassLoader(), 
                //第二个参数,接口的字节码,可以传入多个,所以用Class数组
                new Class[]{Collection.class}, 
                //第三个参数,InvocationHandler接口的子类,匿名内部类
                new InvocationHandler(){
                    public Object invoke(Object proxy, Method method,
                            Object[] args) throws Throwable {
                        return null;
                    }
                }
                );
        System.out.println(proxyCon2);//null,说明toString()返回的是null。
        proxyCon2.clear();//调用无返回值类型的方法可以运行
        proxyCon2.size();//调用有返回值类型的方法就会报空指针异常
    }
}
    思考:为什么该例在调用有返回值类型的方法时会出异常?通过下个例子获取答案

事例需求三:接上例,为代理类挂上目标类

 

public class ProxyTest {
    public static void main(String[] args) throws Exception{
        Collection proxyCon2 = (Collection)Proxy.newProxyInstance(
                Collection.class.getClassLoader(), 
                new Class[]{Collection.class}, 
                new InvocationHandler(){
                    public Object invoke(Object proxy, Method method,Object[] args) throws Throwable {
                        //这里加上一个ArrayList作为目标类,并返回该 
                        ArrayList target = new ArrayList();
                        return method.invoke(target, args);
                    }
                }
                );
        proxyCon2.add("aaa");
        proxyCon2.add("bbb");
        proxyCon2.size();//在上面加入目标并将返回值类型改变后,调用该方法不会报异常。
        System.out.println(proxyCon2.size());//0,上面添加了2个元素,但是打印长度时却始终为0.如果将目标放到invoke外时,就会打印2.
        //上面2行代码说明在代理类对象每调用一次方法就会去执行InvocaHandler里的invoke方法一次。所以我们可以在invoke方法中可以加入一些系统功能方法。
    }
}    

事例需求四:做一个框架,将目标类和系统功能分别抽取出来封装成对象,然后作为参数传入InvocaHandler中。

public class ProxyTest {
    public static void main(String[] args) throws Exception{
        //因为方法的内部类要访问外部变量必须加final
        final ArrayList target = new ArrayList();
        Collection proxy = (Collection) getProxy(target,new MyAdvice());
        proxy.add("abc");
    }

    //获取代理类的方法,参数(目标类对象,系统功能类对象)
    private static Object getProxy(final Object target, final Advice advice) {
        Object proxy = Proxy.newProxyInstance(
                target.getClass().getClassLoader(), //参数一,目标类加载器
                target.getClass().getInterfaces(), //参数二,目标类的接口字节码
                new InvocationHandler(){//参数三,InvocationHandler子类对象
                    public Object invoke(Object proxy, Method method,Object[] args) throws Throwable {
                        advice.beforeMethod(method);//调用系统功能类的方法
                        Object retVal = method.invoke(target, args);
                        advice.afterMethod(method);
                        return retVal;
                    }
                }
                );
        return proxy;
    }
}
//定义一个系统功能类余姚实现的接口
public interface Advice{
    void beforeMethod(Method method){}
    void afterMethod(Method method){}
}
//系统功能类
class MyAdvice implements Advice{
    long beginTime;
    long endTime;
    public void beforeMethod(Method method) {
        System.out.println("计时开始");
        beginTime = System.currentTimeMillis();
    }

    public void afterMethod(Method method) {
        endTime = System.currentTimeMillis();
        System.out.println("计时结束");
        System.out.println("运行时间:"+(endTime - beginTime));
    }
    
}

 

 

 

posted @ 2013-04-11 21:30  郭彦君  阅读(200)  评论(0编辑  收藏  举报