写点什么

设计模式【3.1】-- 浅谈代理模式之静态、动态、cglib 代理

发布于: 2021 年 03 月 03 日

* 代理模式:为其他对象提供一种代理以控制对这个对象的访问,在某种情况下,一个对象不适合或者不能够直接引用另一个对象,而代理对象可以在客户类和目标对象之间起到中介的作用。

* 可以这么理解:使用代理对象,是为了在不修改目标对象的基础上,增强主业务的逻辑。就相当于某个普通人(目标对象),他现在需要打官司,那么他可以自己学习法律,为自己辩护(相当于把业务代码逻辑自己来实现),这就是修改了目标对象,那么当然有一种更好的方法啦,那就是请律师(也相当于代理对象),业务代码(为自己辩护)可以由律师来实现。


代理一般可以分为三种:静态代理,*动态代理*,cglib 代理

1.静态代理

静态代理使用的时候,一般是定义接口或者父类,目标对象(被代理的对象)与代理对象都要实现相同的接口或者继承同样的父类。<br>下面实现静态代理<br>

代码结构:<br>

<br>

创建一个接口类 (IBuyDao.calss)买东西:

public interface IBuyDao {    public void buySomething();}
复制代码

然后创建一个实现了接口的目标类(BuyDao.calss )即要买东西的客户:

public class BuyDao implements IBuyDao {    @Override    public void buySomething() {        System.out.println("我是客户,我想买东西");    }}
复制代码

代理类(BuyDaoProxy):将目标对象当成属性传进去,对目标对象进行增强

public class BuyDaoProxy implements IBuyDao{    private IBuyDao target;    public BuyDaoProxy(IBuyDao target){        this.target = target;    }    @Override    public void buySomething() {        System.out.println("开始代理方法(购物)");        target.buySomething();        System.out.println("结束代理方法");    }}
复制代码

测试方法(Test.class):

public class Test {    public static void main(String [] args){        IBuyDao target = new BuyDao();        //应该写成这样        IBuyDao proxy = new BuyDaoProxy(target);        //下面的这样写就不算严格意义的代理了,代理应该是返回目标对象或接口对象(java一切皆对象)        //BuyDaoProxy proxy = new BuyDaoProxy(target);        proxy.buySomething();    }}
复制代码

结果如下:<br>

<br>

* 在这里有一个疑惑,就是如果 BuyDaoProxy.class 没有实现接口的话,也是可以跑起来,而且结果一样。假如改成这样子:

public class BuyDaoProxy {    private IBuyDao target;    public BuyDaoProxy(IBuyDao target){        this.target = target;    }    public void buySomething() {        System.out.println("开始代理方法(购物)");        target.buySomething();        System.out.println("结束代理方法");    }}
复制代码

个人理解:如果没有实现接口的话,也是可以实现的,这就相当于接口调用,但是一般我们使用代理都会是相同方法名字,使用接口的话,可以强制性使用相同的方法名,而不是随意起一个名字,不使用接口时使用相同方法名也是没有问题的,只是容易写错名字,特别是同一个代理有很多方法的时候。但是这样写就不能用 IBuyDao proxy = new BuyDaoProxy(target); ,那么这意义也就不能算是代理了,代理应该返回接口或目标对象<br>

实现多个接口的例子(新增加了一个学生买书的接口,以及实现类):<br>

<br>

接口:

public interface IStudent {    public void Buybook();}
复制代码

接口实现类:

public class Student implements IStudent{    @Override    public void Buybook() {        System.out.println("我是学生,我想买书");    }}
复制代码

代理类:


public class BuyDaoProxy implements IStudent,IBuyDao{    private IBuyDao target;    public Student student;    public BuyDaoProxy(IBuyDao target){        this.target = target;    }    public BuyDaoProxy(Student student){        this.student =student;    }    @Override    public void buySomething() {        System.out.println("开始代理方法(购物)");        target.buySomething();        System.out.println("结束代理方法");    }
@Override public void Buybook() { System.out.println("开始代理方法(买书)"); student.Buybook(); System.out.println("结束代理方法"); }}
复制代码

测试类:

public class Test {    public static void main(String [] args){        Student target = new Student();        BuyDaoProxy proxy = new BuyDaoProxy(target);        proxy.Buybook();    }}
复制代码

结果:<br>

<br>

个人理解:实现多个接口的时候,要是没有去实现多个接口,就很容易把名字写错,所以强制性使用接口,实现一致的名字,对目标类进行功能增强(在目标类方法之前或者之后处理)。<br>缺点:代理对象需要和目标对象实现一样的接口,所以目标类多了,或者接口增加方法,目标类以及代理的类都要维护。


2.动态代理(即 JDK 代理,接口代理)

* 代理对象不需要实现接口,但是目标对象一定要实现接口

* 使用的是 jdk 的 API,动态的创建代理对象


我们来看代理的方法源码:

public static Object newProxyInstance(ClassLoader loader,                                          Class<?>[] interfaces,                                          InvocationHandler h)        throws IllegalArgumentException    {        Objects.requireNonNull(h);        final Class<?>[] intfs = interfaces.clone();        final SecurityManager sm = System.getSecurityManager();        if (sm != null) {            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);        }        /*         * Look up or generate the designated proxy class.         */        Class<?> cl = getProxyClass0(loader, intfs);        /*         * Invoke its constructor with the designated invocation handler.         */        try {            if (sm != null) {                checkNewProxyPermission(Reflection.getCallerClass(), cl);            }            final Constructor<?> cons = cl.getConstructor(constructorParams);            final InvocationHandler ih = h;            if (!Modifier.isPublic(cl.getModifiers())) {                AccessController.doPrivileged(new PrivilegedAction<Void>() {                    public Void run() {                        cons.setAccessible(true);                        return null;                    }                });            }            return cons.newInstance(new Object[]{h});        } catch (IllegalAccessException|InstantiationException e) {            throw new InternalError(e.toString(), e);        } catch (InvocationTargetException e) {            Throwable t = e.getCause();            if (t instanceof RuntimeException) {                throw (RuntimeException) t;            } else {                throw new InternalError(t.toString(), t);            }        } catch (NoSuchMethodException e) {            throw new InternalError(e.toString(), e);        }    }
复制代码

从 return cons.newInstance(new Object[]{h}); 这句我们可以知道,我们需要去操作 h,h 其实就是 InvocationHandler h 这个参数,那么我们需要重新定义 InvocationHandler h

* ClassLoader loader:是一个类加载器,这个获取类加载器的方法是固定的,我们不能坐任何改变

* Class<?>[] interfaces :这是接口类的数组,使用泛型确认接口类型,这时候接口参数就只能是目标对象所实现的接口

* InvocationHandler h:重要的是这个参数,重写它的 invoke()方法,就可以实现对目标对象的接口的增强。

<br>类的结构如下(之所以实现两个接口,是因为多接口的时候更容易分清):

<br>

<br>代码如下:<br>

IBuyDao.java(买东西的接口)

public interface IBuyDao {    public void buySomething();}
复制代码

IPlayDao.java(玩的接口)

public interface IPlayDao {    void play();}
复制代码

StudentDao.java(实现了买东西,玩的接口的学生类)

public class StudentDao implements IBuyDao,IPlayDao {    @Override    public void buySomething() {        System.out.println("我是学生,我想买东西");    }    @Override    public void play() {        System.out.println("我是学生,我想出去玩");    }}
复制代码

MyProxy.java 代理类:

import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;public class MyProxy {    private Object target;    public MyProxy(Object target){        this.target=target;    }    public Object getProxyInstance(){        return Proxy.newProxyInstance(                target.getClass().getClassLoader(),                target.getClass().getInterfaces(),                new InvocationHandler() {                    //一个接口可能很多方法,要是需要针对某一个方法,那么需要在函数里判断method                    @Override                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {                        System.out.println("开始事务2");                        //执行目标对象方法                        Object returnValue = method.invoke(target, args);                        System.out.println("提交事务2");                        return returnValue;                    }                }        );    }}
复制代码

测试类(Test.java)

public class Test {    public static void main(String [] args){        StudentDao studentDao =new StudentDao();        IBuyDao target = studentDao;        System.out.println(target.getClass());        IBuyDao proxy = (IBuyDao) new MyProxy(target).getProxyInstance();        System.out.println(proxy.getClass());        // 执行方法   【代理对象】        proxy.buySomething();
System.out.print("=========================================================");
IPlayDao target2 = studentDao; System.out.println(target2.getClass()); IPlayDao proxy2 = (IPlayDao) new MyProxy(target2).getProxyInstance(); System.out.println(proxy2.getClass()); // 执行方法 【代理对象】 proxy2.play();
}}
复制代码

结果如下:<br>

<br>

个人理解:代理对象类不需要实现接口,通过对象的增强返回一个接口类对象(实际上是代理后产生的),然后再调用接口方法即可。缺点:目标对象一定要实现接口,否则就无法使用动态代理,因为方法参数有一个是接口名。


3.cglib 代理

Student.class:

package test;public class Student {	public void buy() {		System.out.println("我是学生,我想买东西");	}}
复制代码

MyProxy.class(代理类)


import java.lang.reflect.Method;import net.sf.cglib.proxy.Enhancer;import net.sf.cglib.proxy.MethodInterceptor;import net.sf.cglib.proxy.MethodProxy;
public class MyProxy implements MethodInterceptor { public Object target; public Object getInstance(Object target) { this.target = target; Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(this.target.getClass()); enhancer.setCallback(this); return enhancer.create(); } public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { // TODO Auto-generated method stub System.out.println("代理前-------"); proxy.invokeSuper(obj, args); System.out.println("代理后-------"); return null; }
}
复制代码

测试类(Test.class)

public class Test {	public static void main(String[] args){	    MyProxy myProxy =new MyProxy();		Student student = (Student)myProxy.getInstance(new Student());		student.buy();	}}
复制代码

结构结果:<br>

<br>

- cgilib 可以实现没有接口的目标类的增强,它的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,采用的是继承,所以不能对 final 修饰的类进行代理。


【作者简介】

秦怀,公众号【秦怀杂货店】作者,技术之路不在一时,山高水长,纵使缓慢,驰而不息。个人写作方向:Java 源码解析,JDBC,Mybatis,Spring,redis,分布式,剑指 Offer,LeetCode 等,认真写好每一篇文章,不喜欢标题党,不喜欢花里胡哨,大多写系列文章,不能保证我写的都完全正确,但是我保证所写的均经过实践或者查找资料。遗漏或者错误之处,还望指正。


2020年我写了什么?


开源编程笔记


平日时间宝贵,只能使用晚上以及周末时间学习写作,关注我,我们一起成长吧~


发布于: 2021 年 03 月 03 日阅读数: 13
用户头像

纵使缓慢,驰而不息。 2018.05.17 加入

慢慢走,比较快。公众号:秦怀杂货店

评论

发布
暂无评论
设计模式【3.1】-- 浅谈代理模式之静态、动态、cglib代理