(Java基础篇)八、Java中的两种代理(JDK与CGLib)

动态代理

  代理模式可以避免对象直接暴露,静态代理每次需要新的业务均需要预添加额外的代理类。动态代理则是在编译或运行生成的,大大降低了代理类与被代理类的耦合,提高扩展性和复用性。首先用一段代码先看看JDK动态代理的用法。

JDK动态代理

  JDK动态代理是对接口的代理,它与被代理类是平级的,有点类似于acessor的感觉,它的核心InvocationHandler就是reflect包下的。通过后续生成的代理类可以发现,它继承了被代理类的接口,这也就是说JDK能且只能代理接口。

  我们下面的例子说明,Food类是所有实物类的底层接口,它有一个方法cook()用于模拟生产食物,我们为了生产不同的食物创建一个它的实现类,添加一个name属性标志一下食物名称。当然厨师也分不同等级的厨师,要验证过身份才能开始做食物。但是我们不想破坏原本的代码,因此考虑使用代理,静态代理每次产生一个FoodImpl都要重新写一下,所以我们用动态代理,只需要额外写一个自己的InvocationHandler就行了。

// 底层接口
public interface Food {
    // 制作食物方法
    public void cook();
}
// 某一个实现类
public class FoodImpl implements Food{

    private String name;

    public FoodImpl(String name) {
        this.name = name;
    }

    @Override
    public void cook() {
        System.out.println("做了一份 " + name + " 给你吃");
    }
}
// 代理逻辑类
public class FoodInvocationHandler implements InvocationHandler {

    private Object target;

    public Object getProxyInstance(Object target) {
        this.target = target;
        Class clazz = this.target.getClass();
        return Proxy.newProxyInstance(clazz.getClassLoader(),
                clazz.getInterfaces(),
                this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] objects) throws Throwable {
    	// 这里模拟鉴权
        System.out.println("我先来校验一下权限信息:是高级厨师可以开始工作");
        Object result = method.invoke(target, objects);
        System.out.println("工作结束了");
        return result;
    }
}
// 具体测试类 
public class TestJDKProxy {
    public static void main(String[] args) {
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

        Food cakeProxy = (Food) new FoodInvocationHandler().getProxyInstance(new FoodImpl("蛋糕"));
        Food noodleProxy = (Food) new FoodInvocationHandler().getProxyInstance(new FoodImpl("面条"));

        cakeProxy.cook();
        noodleProxy.cook();
    }
}

  这个例子基本揭示了JDK动态代理的用法:

  1. 创建底层接口(Food)
  2. 创建实现类(FoodImpl)
  3. 创建代理逻辑类(FoodInvocationHandler)
  4. 使用Proxy.newInstance()创建JDK动态代理类

  有了这个例子,我们再思考一下,如果此时想横向扩展业务,想要专门设立几个厨房做不同的食物,例如:专门负责油炸食品的、专门负责烘焙的等。如果使用静态代理那么就是写好FriedFoodImpl和BakeFoodImpl并且在写两个他们呢的代理类。但是现在用的是动态代理,我们做完第一步,也就是写好FriedFoodImpl和BakeFoodImpl两个实现类就行了,是不是很方便。

public class FriedFoodImpl implements Food{

    private String name;

    public FriedFoodImpl(String name) {
        this.name = name;
    }
	
	// 拓展业务,这里也就是用专门的方法做食物
    @Override
    public void cook() {
        System.out.println("我先热个油再炸");
        System.out.println("我正在炸" + name);
    }
}

public class BakeFoodImpl implements Food{

    private String name;

    public BakeFoodImpl(String name) {
        this.name = name;
    }

    @Override
    public void cook() {
        System.out.println("我先开个烤箱再烤");
        System.out.println("我正在烤" + name);
    }
}

public class TestJDKProxy {
    public static void main(String[] args) {
    	// 保存代理类,用于查看
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

        Food cakeProxy = (Food) new FoodInvocationHandler().getProxyInstance(new FoodImpl("蛋糕"));
        Food noodleProxy = (Food) new FoodInvocationHandler().getProxyInstance(new FoodImpl("面条"));

        cakeProxy.cook();
        noodleProxy.cook();

        Food chips = (Food) new FoodInvocationHandler().getProxyInstance(new FriedFoodImpl("薯条"));
        Food cake = (Food) new FoodInvocationHandler().getProxyInstance(new BakeFoodImpl("蛋糕"));

        chips.cook();
        cake.cook();
    }
}

JDK动态代理测试
  写好这两个实现类之后,还是使用原本的FoodInvocationHandler创建代理类就可以实现代理。在测试类中我们手动保存了生成的代理类(不开启是不会保存的),我们现在去看一下。

public final class $Proxy0 extends Proxy implements Food {
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
    	// 省略
    }

    public final String toString() throws  {
    	// 省略
    }

    public final void cook() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() throws  {
    	// 省略
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("********.Food").getMethod("cook");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

  通过继承和实现关系可以发现,这个代理类似乎和FoodImpl或是其他额外两个实现类毫无关系,这是为啥,而且我们的代理类明明有三个,这怎么只有一个。原因其实很简单:JDK动态代理不会帮你主动调你的cook,而是会去调FoodInvocationHandler的invoke方法,这就回答了为什么代理类和实现类没什么关系的问题。其实代理类的构造函数是把FoodInvocationHandler给传给了Proxy的protected属性h,而FoodInvocationHandler中我们自己创建了一个target用于记录实现类。而FoodInvocationHandler的invoke方法用的是反射的思想来,只不过我们提前在代理类中告诉了Method是什么(m3),这也就解释了为什么所有代理类生成的动态代理对象保存成代码只有一份。

  JDK动态代理更像是生成了一个"代理逻辑“的静态代理,通过FoodInvocationHandler真正触及到被代理对象,或者说是对Food类的代理,因为里面的Method(m3)是通过接口获得的,只要是实现了这个接口的类都可以代理。

CGLIB动态代理

  CGLIB与JDK动态代理不一样,它直接代理的实体类,通过继承的方法实现代理。创建代理时首先创建一个enhancer,通过将这个enhancer的父类设置为被代理的实体类来实现动态代理,然后使用enhancer的create方法创建,但是这个里面会调用super的无参构造器,因此要求被代理对象有无参构造器,其他别无差别。

public class FoodMethodInterceptor implements MethodInterceptor {

    // 目标对象
    private Object target;

    public FoodMethodInterceptor(Object target) {
        super();
        this.target = target;
    }

    public Object createProxy() {

        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(target.getClass());
        enhancer.setCallback(this);

        return enhancer.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("我先来校验一下权限信息:是高级厨师可以开始工作");
        Object result = method.invoke(target, objects);
        System.out.println("工作结束了");
        return result;
    }
}

public class TestCglibProxy {
    public static void main(String[] args) {

        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "这里写保存路径");

        Food chips = (Food) new FoodMethodInterceptor(new FriedFoodImpl("薯条")).createProxy();
        Food cake = (Food) new FoodMethodInterceptor(new BakeFoodImpl("蛋糕")).createProxy();

        chips.cook();
        cake.cook();
    }
}

  这个是CGLIB的动态代理示例,CGLIB源码有点复杂,就先不深入研究了,等如果以后有时间再研究研究,不过看生成的代理类,大概是继承+工厂模式。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值