设计模式之代理模式

来源:互联网 发布:苹果mac没有声音 音频 编辑:程序博客网 时间:2024/06/10 17:13

代理模式是一种非常重要的设计模式,在java语言中有着广泛的应用,包括Spring AOP的核心设计思想,都和代理模式有着密切关系。
1、代理模式概念机分类
2、代理模式应用场景
3、掌握静态代理、动态代理运用
4、理解JDK动态代理实现原理

代理模式定义:
为其他对象提供一种代理,以控制这个对象的访问,代理对象其实是起到了一个中介的作用,可以去掉一些功能服务,添加一些额外的功能服务。

常见的几种代理模式:

1:远程代理:类似于客户端服务器这种模式,列一个为不同地理对象提供局域网代表对象。(类似于客户端和服务器端)
2:保护代理:控制对象的访问权限。(发帖功能)
3:智能代理:提供对目标对象额外的服务。(火车站)
4:虚拟代理:根据需要将资源消耗很大的对象进行延迟,真正需要的时候进行创建。(网页中图片的加载,先用一张虚拟的图片进行显示,等图片加载完成后再进行显示)

代理模式实现—静态代理
静态代理:代理和被代理对象在代理之前是确定的,他们都实现相同的接口或者继承相同的抽象类。

继承代理:子类继承实现方法(move方法)的父类,当子类要去实现相同方法时,调用父类的方法实现自己想要做的事(super.move()),完成代理

聚合代理:将代理的类作为属性,通过构造方法等方法将它的实例化对象传入后,再调用代理类的方法作为自己当前类完成要做的事。

代理模式就是一个类可以代替实现另一个类的功能,同时功能可以扩展或者缩减。静态代理有两种实现方法,一种是继承被代理类,然后在代理类中调用被代理类的方法,同时在逻辑前后可以扩展方法。另一种是聚合的方法,代理类中存入一个被代理类的引用,构造方法中初始化这个引用,然后调用这个引用的方法,也就是被代理类的方法。

注意:聚合比继承更合适代理模式
使用聚合的方式实现静态代理是合适的更好的方法,也就是代理类和被代理类同样实现同一个接口,然后代理类中存入一个被代理类的成员变量,真正调用的是这个成员变量的方法,这样可以实现多种功能的叠加,而且调整功能的顺序操作也会很简单,只需要客户端调整调用功能的顺序即可,如果采用继承的方式,就必须要实现多个功能顺序不同的代理类,这样代理类的数量会越来越多,不利于后面的维护工作。

案例–代理类功能的叠加
1. 继承的方式:如果使用继承的方式来实现我们代理功能的叠加,我们的代理类会无限的膨胀下去。
2. 聚合的方式:
由于代理类和被代理类都实现了相同的接口,那么代理类的构造参数就可以传入该相同的接口,这样在后面功能叠加的时候就可以传入其他功能的代理类,因为他们都实现了相同的父接口。从而达到功能叠加的作用。

eg:汽车类,先记录日志再记录时间

Car car = new Car();CarTimeProxy ctp = new CarTimeProxy(car);CarLogProxy clp = new CarLogProxy(ctp);clp.move();

先记录时间再记录日志

Car car = new Car();CarLogProxy clp = new CarLogProxy(car);CarTimeProxy ctp = new CarTimeProxy(clp);ctp.move();

聚合的方式比继承的方式灵活很多,通过聚合的方式,代理之间也是可以相互传递的,
相互组合。

注意:聚合代理优于继承代理,因为实现功能叠加的情况下,聚合代理通过互相代理可以实现功能重用,而继承代理必须写多个类来实现多功能叠加。

注意:静态代理只能代理一种类型的被代理类,换个类型deep就不行了,这时就需要用到动态代理

JDK动态代理(动态产生代理,实现对不同类,不同方法的代理)

动态的代理:在代理类和被代理类之间加入一个加入一个事务处理器(InvocationHandler类),java动态代理类位于java.long.reflect包下,一般涉及两个类
(1),Interface InvocationHandler:该接口中仅定义了一个方法public Object invoke(Object obj,Method method,Object[] args)在使用时,第一个参数obj一般指被代理类对象,method是被代理类的方法,args为该方法的参数数组。这个抽象方法在代理类中实现。
(2)Proxy是产生动态代理的一个类,我们通过static Object newProxyInstance(ClassLoader,Class[] interfaces,InvoactionHandler h):就可以动态地产生一个代理类,该函数返回代理类的一个实例,返回后的代理类可以当做被代理类使用(可使用被代理类在接口中声明过的方法)

动态代理的实现步骤

1:创建一个实现接口InvocationHandler的类,它必须实现invoke方法
2:创建被代理的类以及接口
3:调用Proxy的静态方法,创建一个代理类
newProxyInstance(ClassLoader loader,Class[] interfaces,InvocationHandler h)
4:通过代理调用方法

JDK动态代理newProxyInstance(类加载器,类实现的接口,InvocatioinHandle)
Proxy.newProxyInstance(cls.getClassLoader,cls.getInterface(),实现InvocatioinHandle的处理器)

作业:时间、日志的叠加

public static void main(String[] args) throws Throwable {        Car car = new Car();        InvocationHandler timehandler =new TimeHandler(car);        Class<?> cls = car.getClass();        /**         * loader 类加载器          * interfaces 实现接口          * h InvocationHandler         */        Moveable m = (Moveable) Proxy.newProxyInstance(cls.getClassLoader(), cls.getInterfaces(), timehandler);        InvocationHandler logHandler= new LogHandler(m);        Class<?> clsm =m.getClass();        Moveable m2 = (Moveable) Proxy.newProxyInstance(clsm.getClassLoader(), clsm.getInterfaces(), logHandler);        m2.move();    }

使用cglib动态产生代理

**注意:**JDK动态代理只能代理实现了接口的类,若没有实现接口的类不能实现JDK的动态代理。
JDK实现动态代理,即可以为任何对象增加功能

示例:代理类的实现

import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;/** *使用jdk提供的invocationHandler接口实现动态代理  * 实现对任何类(不仅限于Moveable)的记录时间操作。 */public class TimeHandler implements InvocationHandler{    private Object target; //定义一个Object,代表被代理的对象    public TimeHandler(Object target)    {        this.target=target;    }    /**     * 1. 实现InvocationHandler接口,并重写invoke方法     * @param proxyObj 生成的代理对象。     * @param method 要代理的方法     * @param args 代理方法的参数     * @return 代理方法返回的返回值     */    @Override    public Object invoke(Object proxyObj, Method method, Object[] args) throws Throwable    {        long start = System.currentTimeMillis();        System.out.println("小车开始行驶。。。");        Object returnObj= method.invoke(target); //执行原有方法。如果方法有参数,作为invoke第二参数传入        long end=System.currentTimeMillis();         System.out.println("小车结束行驶。。。时间"+(end-start));        return returnObj; //将被代理方法的返回值返回。    }}

CGLIB动态代理针对类来实现代理的,对指定目标类产生一个子类,通过方法拦截技术拦截所有父类方法的调用。

相关代码——(下):

@CglibProxy.java

public class CglibProxy implements MethodInterceptor {    private Enhancer enhancer=new Enhancer();       public Object getProxy(Class cls){        //设置创建子类的类        enhancer.setSuperclass(cls);        enhancer.setCallback(this);        return enhancer.create();    }    /**     * 参数:object:拦截所有目标类方法的调用,method:目标方法的反射对象,args:方法的参数,methodproxy:代理类的实例。     */    public Object intercept(Object object, Method method, Object[] args,            MethodProxy methodproxy) throws Throwable {        syso("日志开始...");        methodproxy.invokeSuper(object, args);        syso("日志结束...");        return null;    }}

模拟JDK动态实现代理类的步骤
动态代理思路
1.声明一段源码(动态产生代理)
2.编译源码(JDK Compiler API),产生新的类(代理类)
3.将这个类load到内存当中,产生一个新的对象(代理对象)
4.return 代理对象

代理模式-动态代理 : JDK的动态代理模式
在不改变原有类及方法的基础上,增加一些额外的业务逻辑, 这种方式叫做AOP(面向切面编程)

代理模式这种机制可以叫做AOP,在不改变原有代码的基础上,添加或者删除某些方法
例如:
要调用某个jar包中某个类的方法,但是不能改变源码,我们可以采用JDK的动态代理模式,在该方法的前后添加业务逻辑,如记录日志,权限控制等。

完善动态代理实现
首先得到系统编译器,通过编译器得到文件管理者,以获取文件,然后编译器执行编译任务,完成编译之后,将class文件加载到类加载器中,通过构造方法得到实例,然后调用newInstance()接收一个对象的实例并返回。
(1)拿到编译器 JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
(2)文件管理者 StandardJavaFileManager fileMgr = Compiler.getStandardFileManager(null,null,null);
(3)获取文件 Iterable units = fileMgr.getJavaFileObjects(filename);
(4)编译任务 CompilationTask t =compiler.getTask(null,fileMgr,null,null,null,units);
(5)load到内存
ClassLoader cl = ClassLoader.getSystemClassLoader();
Class c = cl.loadClass(”com.imooc.proxy.$Proxy0”);
(6)通过代理对象的构造器构造实例,并返回代理对象
Constructor ctr = c.getConstructor(infce);
return ctr.newInstance(new Car());

代码实例:

//需要编译的java文件String filename = System.getProperty("user.dir")+"/bin/com/imooc/proxy/$Proxy0.java";//编译//拿到编译器JavaCompiler complier = ToolProvider.getSystemJavaCompiler();//文件管理者StandardJavaFileManager fileMgr =         complier.getStandardFileManager(null, null, null);//获取文件Iterable units = fileMgr.getJavaFileObjects(filename);//编译任务CompilationTask t = complier.getTask(null, fileMgr, null, null, null, units);//进行编译t.call();fileMgr.close();//load 到内存ClassLoader cl = ClassLoader.getSystemClassLoader();//从内存中获取要处理的类Class c = cl.loadClass("com.imooc.proxy.$Proxy0");//给此类的构造方法传入参数Constructor ctr = c.getConstructor(InvocationHandler.class);//调用对象的方法return ctr.newInstance(h);

如何实现真正的动态代理,动态的指定业务逻辑呢?

1.需要创建一个事务处理器,首先创建一个接口也就是InvocationHandler,为了模拟JDK,这里把接口的名字和JDK事务处理器名称一样,同样写一个方法叫做invoke(),用来表示对某个对象的某个方法进行业务处理,所以需要把某个对象以及对象的方法作为invoke()方法的参数传递进来,invoke(Object obj,Method method),方法作为参数使用到了java反射,需要把此包引入。这样InvocationHandler接口就完成了。
2.创建事务处理实现类比如说时间代理TimerProxy,实现了InvocationHandler接口,这样结构就成了

public class TimerProxy implements InvocationHandler{    @Override    public void invoke(Object o, Method m) {          //业务逻辑          method.invoke(目标对象,参数);          //业务逻辑            }

需要将目标对象传入,没有参数可以不写参数,创建代理对象的构造方法,初始化目标对象

3.在Proxy类的newProxyInstance()方法中,除了要把目标Class接口作为参数外,还需要把事务处理器InvocationHandler 传进去,然后更改创建实例对象中硬编码的部分用事务处理器方法替代即可。难点在于字符串的拼接。

1、声明一段源码,源码动态产生动态代理
2、源码产生java文件,对java文件进行编译
3、得到编译文件 调用编译任务 生产class文件
4、把class文件load到内存之中,产生代理类对象返回

jdk代理整体思路:
1. InvocationHandler对象聚合了原始的父类对象, 并抽取了公共的代理控制逻辑。
2. Proxy聚合了handler对象,并生成新的class;在新的class定义中对接口中的每个方法调用handler对象的对应方法。

参考视频:http://www.imooc.com/learn/214

原创粉丝点击