代理那些事

2月前 253

什么是代理

古有明朝那些事,今有代理那些事。今天就聊聊代理的那些事。
Proxy,也就是代理的意思。在23种设计模式中有一种模式叫做代理模式,代理通俗的理解就是你不需要去做,让别人代替你去做。比如:抢春运的火车票,黄牛就是你的代理。在程序开发的过程中,AOP中代理也起到了重要的作用。此外,你想上国外的一些网站也是需要通过设置代理来进行实现。所以代理在不论在生活中还是技术领域中都有很多的应用。深入理解代理的概念和应用是十分有必要的。

先来一个Hello World吧

自接触语言、设计模式等相关课程的过程中,总会先从Hello World的demo学起,就像我们小时候咿呀学语一样。

  • 创建一个Hello接口
/**
 * @author hongchen
 * @date 2018/7/10
 */
public interface Hello {
    void say(String name);
}
  • 创建一个Hello接口的实现类
/**
 * @author hongchen
 * @date 2018/7/10
 */
public class HelloImpl implements Hello {
    @Override
    public void say(String name) {
        System.out.println("hello world" + name);
    }
}

在写完Hello接口的实现类后,突然又想在实现方法say()中的语句前后增添一些功能。最简单的方法就是把增加的功能写死在say()方法中,这一般是菜鸟程序员的想法。呵呵,作为资深码农我坚决不会这样做!我要用代理,我再写一个类,并且调用HelloImpl类中的say()方法并且在调用前后增加相应的功能不就可以了嘛,话不多说,立马行动起来!

/**
 * @author hongchen
 * @date 2018/7/10
 */
public class HelloProxy implements Hello{
    private HelloImpl helloImpl;
    public HelloProxy(HelloImpl helloImpl) {
        this.helloImpl = helloImpl;
    }
    @Override
    public void say(String name) {
        this.before();
        helloImpl.say(name);
        this.after();
    }
    public void before() {
        System.out.println("before");
    }
    public void after() {
        System.out.println("after");
    }
}

在这里HelloProxy实现了Hello接口,并且在类中声明了HelloImply属性,这样就可以在HelloProxy的say()方法中调用HelloImpl的say()方法,并且在方法的前后增加相应的功能。

  • 调试上述代码是否正确。
/**
 * @author hongchen
 * @date 2018/7/10
 */
public class Test {
    public static void main(String[] args) {
        HelloProxy helloProxy = new HelloProxy(new HelloImpl());
        helloProxy.say("hongchen.zhx");
    }
}

运行结果为:
avatar
原来这就是代理模式啊,这代理也是不足为奇嘛,很容易就搞明白了,不过这种代理模式为静态代理。那么什么是静态代理呢。

静态代理

所谓静态代理就是自己要为目标类写一个代理类,或者用工具为其生成的代理类,总之,就是程序运行前就已经存在的编译好的代理类,也是所有代理方式中效率最高的一种方式,所有类都已经编写完成,直接调用即可。
由于静态代理是在编译期就写死的代理,因此灵活性会显得很差劲。相比静态代理,动态代理具有更强的灵活性,因为它不用在我们设计实现的时候就指定某一个代理类来代理哪一个被代理对象,我们可以把这种指定延迟到程序运行时由JVM来实现。下面介绍动态代理。

动态代理

JDK动态代理

在该部分中,我们还会继续沿用上面的Hello接口和Hello接口实现类HelloImpl。

  • 实现InvocationHandler接口,方法调用会被转发到该类的invoke()方法。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/**
 * @author hongchen
 * @date 2018/7/10
 */
public class DynamicProxy implements InvocationHandler {
    private Object target;
    public DynamicProxy(Object target) {
        this.target = target;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before();
        Object result=method.invoke(target,args);
        after();
        return result;
    }
    public void before() {
        System.out.println("before");
    }
    public void after() {
        System.out.println("after");
    }
}
 proxy:被代理的真实对象
 method:被代理的真实对象的某个方法的Method对象
 args:Method对象所接受的参数

在InvocationHandler的实现类DynamicProxy实现类中,我们定义了一个Object类型的target变量,这个变量就是被代理的对象。该类通过构造函数初始化该变量,不过现在流行叫“注入”。其实也可以叫做“射入”,构造函数的初始化叫“正着射”,通过字节码获得该类的属性、方法的对象就叫“反着射”,即我们所熟知的反射机制。
由于DynamicProxy实现了InvocationHandler接口,那么必须实现该接口的invoke方法,这是是JVM给我们“射”进来的。在该方法中,直接通过反射去 invoke method。

  • 通过main()方法进行上述代码的测试。
public static void main(String[] args) {
    Hello hello = new HelloImpl();
    DynamicProxy dynamicProxy = new DynamicProxy(hello);
    Hello helloProxy = (Hello)Proxy.newProxyInstance(hello.getClass().getClassLoader(),
                                                     hello.getClass().getInterfaces(),
                                                     dynamicProxy);
    helloProxy.say("hongchen.zhx");
    }

输出结果:
avatar
在测试的过程中,我们首先把被代理的对象HelloImpl传入到我们的代理对象DynamicProxy中。然后再调用JDK提供给我们的Proxy类的工厂方法newProxyInstance()生成一个Hello接口的代理类。最后调用该代理类的say()方法。

public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) throws IllegalArgumentException
loader:一个ClassLoader对象,定义了由哪个ClassLoader对象来对生成的代理对象进行加载  
interface:一个interface对象的数组,表示的是我将要给我需要代理的对象提供一组什么接口,若我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样就可以调用这组接口中的方法了
h:一个InvocationHandler对象,表示的是当我这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上

CGLIB动态代理

由上述分析可知,JDK动态代理为我们提供了非常灵活的代理机制,但是JDK的动态代理是基于接口实现的。如果我们的被代理对象没有实现接口该怎么处理呢?CGLIB登场。
CGLIB(Code Generation Library)是一个基于ASM的字节码生成库,它允许我们在运行时对字节码进行修改和动态生成。CGLIB通过继承方式实现代理。

  • 创建一个没有实现任何接口的类HelloImpl
/**
 * @author hongchen
 * @date 2018/7/10
 */
public class HelloImpl {
    public void say(String name) {
        System.out.println("hello world" + name);
    }
}

由于该类没有实现任何接口,因此无法使用JDK动态代理,只能通过CGLIB来进行实现。

  • 实现MethodInterceptor接口,方法调用会被转发到该类的intercept()方法。
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;

/**
 * @author hongchen
 * @date 2018/7/10
 */
public class CglibProxy implements MethodInterceptor {
    @Override
    /*
     *object 表示被代理的对象
     *method 表示被拦截的方法
     *args 参数列表
     *methodProxy 表示对方法的代理,invokeSuper方法表示对被代理对象方法的调用
     */
    public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        before();
        Object result = methodProxy.invokeSuper(object,args);
        after();
        return result;
    }
    public void before() {
        System.out.println("before");
    }
    public void after() {
        System.out.println("after");
    }
}

值得注意的是,CGLIB给我们提供的是方法级别的代理,也可以理解为对方法的拦截,这不就是传说中的方法拦截器嘛,我们直接调用methodProxy的invokeSuper()方法,将被代理的对象object和方法参数传入即可。

  • 通过main()方法进行上述代码测试
public static void main(String[] args) {
    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(HelloImpl.class);
    enhancer.setCallback(new CglibProxy());
    HelloImpl helloImple = (HelloImpl)enhancer.create();
    helloImple.say("hongchen.zhx");
    }

输出结果:
avatar
上述代码中,我们通过CGLIB的Enhancer来指定要被代理的对象、代理对象,最终通过调用create()方法得到代理对象,对这个对象所有非final方法的调用都会转发给MethodInterceptor.intercept()方法,在intercept()方法里我们可以加入任何逻辑。
用MethodProxy.invokeSuper()方法,我们将调用转发给代理对象,具体到本例,就是HelloImpl的具体方法。CGLIB中MethodInterceptor的作用跟JDK代理中的InvocationHandler很类似,都是方法调用的中转站。

Java核心技术 阿里技术协会 string class void cglib 代理

作者

鸿琛
TA的文章

相关文章