代理

2019-11-08  本文已影响0人  小马蛋

一、概念

代理模式是设计模式的一种,提供了对目标对象的另外访问模式,即通过代理对象访问目标对象,特点是,可以在目标对象实现的基础上增强额外功能操作,扩展目标对象功能或者改变目标对象的功能操作,代理模式中,用户不直接操作目标对象,而是通过访问代理对象,在代理方法中,由代理对象进行相关处理。

proxy.jpeg

二、分类

Java代理可以分为静态代理和动态代理。

动态代理又细分为两个不同的实现类库,一是jdk动态代理。二是cglib动态代理。

1)静态代理

静态代理在使用时,需要定义接口或者父类,被代理对象与代理对象一起实现相同的接口或者是继承相同父类。


// 枪接口
interface Gun {
    void fire();
}

目标类(被代理类)


// 定义手枪类(被代理目标)
class Pistol implements Gun{
    @Override
    public void fire(){
        System.out.println("biu~");
    }
}

代理类:


//代理类(代理对象)
class ProxyFactory implements Gun{
    //接收保存目标对象
    Gun gun;

    //创建代理类的对象时,实际传入一个被代理类的对象
    public ProxyFactory(Gun gun){
        this.gun = gun;
    }

    @Override
    public void fire() {
        System.out.println("开火之前需要先进行瞄准");
        gun.fire();
        System.out.println("验枪,退子弹");
    }
}

测试:

public class Test {
    public static void main(String[] args) {
        Pistol pistol = new Pistol();
        ProxyFactory pro = new ProxyFactory(pistol);
        pro.fire();
    }
}

输出:

开火之前需要先进行瞄准
biu~
验枪,退子弹

特点:

① 在不修改目标对象的情况下,对目标功能进行扩展。
② 因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类,类太多。同时,一旦接口增加方法,目标对象与代理对象都要增加,随着项目的变迁,会导致代码极为臃肿且难以维护。

2)jdk动态代理

不需要代理类实现接口或者继承类,通过Proxy类的静态方法newProxyInstance生成代理类实例,static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler invocationHandler ); 方法接收三个参数:

1、代理类的类加载器
2、代理类实现的接口列表
3、代理类(implements InvocationHandler)实例

接口:

public interface Gun {
    void fire();
    void firefire();
}

目标类,实现Gun接口:

public class Pistol implements Gun {
    @Override
    public void fire() {
        System.out.println("biu~");
    }
    @Override
    public void firefire() {
        System.out.println("biu~biu~");
    }
}

代理类,实现InvocationHandler接口:

public class GunProxy implements InvocationHandler {
    private Gun gun;

    public GunProxy(Gun gun) {
        this.gun = gun;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
         System.out.println("开火之前需要先进行瞄准");
         Object result = method.invoke(gun, args);
         System.out.println("验枪,退子弹");
         return result;
    }
}

测试:

@Test
public void test0() {
    Gun gun = new Pistol();
    GunProxy gunProxy = new GunProxy(gun);
    gun = (Gun)(Proxy.newProxyInstance(GunProxy.class.getClassLoader(), new Class[]{Gun.class}, gunProxy));
    gun.fire();
    gun.firefire();
}

输出:

开火之前需要先进行瞄准
biu~
验枪,退子弹

开火之前需要先进行瞄准
biu~biu~
验枪,退子弹

两个方法fire()和firefire() 代理类无需增加方法,即可实现代理操作。

jdk动态代理特点:

① 无惧接口实现类的增多而导致的代理类跟进维护。jdk动态代理帮我们做了代理类和被代理类的绑定操作。
② 代理类无需继承和实现目标类继承和实现的类、接口,代理类只需实现InvocationHandler接口即可。
③ 在静态代理中,目标代理类的方法一旦增加或者修改,需要我们手动维护代理类,太过繁琐。而动态代理则无须手工干预。

3)cglib动态代理

使用cglib代理有几个前提:

① 目标类不能为final
② 目标对象的方法如果为final/static,不会被拦截
③ 需要额外的jar包

手枪类:

public class Pistol {
    public void fire() {
        System.out.println("biu~");
    }

    public void firefire() {
        System.out.println("biu~biu~");
    }
}

代理类或拦截器类:

public class GunProxy implements MethodInterceptor {
    private Object target;

    public GunProxy(Object target) {
        this.target = target;
    }

    public Object getProxyInstance(){
        //1.工具类
        Enhancer en = new Enhancer();
        //2.设置父类
        en.setSuperclass(target.getClass());
        //3.设置回调函数
        en.setCallback(this);
        //4.创建子类(代理对象)
        return en.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("开火之前需要先进行瞄准");
        //执行目标对象的方法
        Object returnValue = method.invoke(target, objects);
        System.out.println("验枪,退子弹");
        return returnValue;
    }
}

测试:

@Test
public void test0() {
    Pistol pistol = new Pistol();
    Pistol gunProxy = (Pistol) new GunProxy(pistol).getProxyInstance();
    gunProxy.fire();
    gunProxy.firefire();
}

输出:

开火之前需要先进行瞄准
biu~
验枪,退子弹

开火之前需要先进行瞄准
biu~biu~
验枪,退子弹

两个方法fire()和firefire() 代理类无需增加方法,即可实现代理操作。

cglib动态代理特点:

① 目标对象可以是类,使用字节码技术,类不能被final关键字修饰,因为产生的代理对象是继承目标类的,而被final修饰的类,是不能被继承的。
② 目标对象的方法如果为final/static,不会被拦截
③ 目标类无需实现接口,爽!

三、结论

正常情况下推荐使用jdk动态代理和cglib动态代理,减少手工干预,降低代码复杂度。另外提一嘴,Spring 同时利用jdk动态代理和cglib动态代理,MyBatis核心Dao实例返回的就是通过jdk动态代理生成的代理类实例。

上一篇下一篇

猜你喜欢

热点阅读