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

  • 代理模式:为其他对象提供一种代理以控制对这个对象的访问,在某种情况下,一个对象不适合或者不能够直接引用另一个对象,而代理对象可以在客户类和目标对象之间起到中介的作用。
  • 可以这么理解:使用代理对象,是为了在不修改目标对象的基础上,增强主业务的逻辑。就相当于某个普通人(目标对象),他现在需要打官司,那么他可以自己学习法律,为自己辩护(相当于把业务代码逻辑自己来实现),这就是修改了目标对象,那么当然有一种更好的方法啦,那就是请律师(也相当于代理对象),业务代码(为自己辩护)可以由律师来实现。

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

1.静态代理

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

代码结构:



创建一个接口类 (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();
    }
}

结果如下:



  • 在这里有一个疑惑,就是如果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); ,那么这意义也就不能算是代理了,代理应该返回接口或目标对象

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



接口:

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();
    }
}

结果:



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

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()方法,就可以实现对目标对象的接口的增强。

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




    代码如下:

    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();

    }
}

结果如下:



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

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();
	}
}

结构结果:

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

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

2020年我写了什么?

开源编程笔记

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

posted @ 2021-03-03 00:09  第十六封  阅读(117)  评论(0编辑  收藏  举报