JAVA动态代理

从静态代理开始

假设我们有个业务类,类的接口和实现是这么定义的:

public interface Player {
    void play();
}
public PlayerImpl implements Player {
    @Override
    public void play(){
        System.out.println("playing...");
    }
}

如果我想在玩游戏之前和游戏结束之后,记录日志,可是不允许改动PlayerImpl的代码,该怎么办呢?
这时候可以构造一个代理类来做这件事情:

public PlayerProxy implements Player {
    private Player player;
    public PlayerProxy(){
        this.player = new PlayerImpl();
    }
    @Override
    public void play(){
        beforeLog();
        player.play();
        afterLog();
    }

    private void beforeLog(){
        System.out.println("init...");
    }
    private void afterLog(){
        System.out.println("destroy...");
    }
}

这就是静态代理,我们可以在不改动源码的情况下,通过代理对类功能进行扩展,很符合开闭原则吧:)

动态代理

静态代理的方式很直观,但是缺点也很明显——它只能针对特定类进行代理,扩展性很差,如果有一百个类要代理,难道要写一百个代理类吗?显然我们应该拥有更加通用和高效的解决方案,就是动态代理啦。

JDK动态代理

JDK动态代理,需要实现InvocationHandler接口;下面我们用JDK代理的方式重写上面的代理类:

public PlayerProxyFactory implements InvocationHandler {

    private Object target;

    public PlayerProxyFactory(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 Object buildProxy(){
        return Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                this
        );
    }
    // 省略beforeLog和afterLog方法
}

实际使用:

public static void main(String[] args) {
    Player player = new PlayerImpl();
    // 构造实现
    PlayerProxyFactory proxy = new PlayerProxyFactory(player);
    // 生成代理
    Player playerProxy = (Player) proxy.buildProxy();
    playerProxy.play();
}

可以看出来,动态代理可以通过接口自动构造代理类(其实就是一个工厂)。

cglib动态代理

JDK InvocationHandler虽好,却仍有一个缺陷,无法代理未实现接口的类。如果刚好有这样的需求,该怎么解决呢?这就得靠cglib了。
cglib是一个在运行期动态生成字节码的工具。我承认我第一次看到这个介绍的时候被吓到了,不过撇开原理不说,使用起来与InvocationHandler非常类似:

public class CGLibProxy implements MethodInterceptor {
    @Override
    public Object intercept(Object target, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        before();
        Object result = proxy.invokeSuper(target, args);
        after();
        return result;
    }
    public  T getProxy(Class clazz) {
        return (T) Enhancer.create(clazz, this);
    }
    // 省略beforeLog和afterLog方法
}

实际使用:

public static void main(String[] args) {
    Player playerProxy = new CGLibProxy().getProxy(PlayerImpl.class);
    playerProxy.play();
}

JAVASSIST

Javassist是一个开源的分析、编辑和创建Java字节码的类库。是由东京工业大学的数学和计算机科学系的 Shigeru Chiba (千叶 滋)所创建的。它已加入了开放源代码JBoss 应用服务器项目,通过使用Javassist对字节码操作为JBoss实现动态"AOP"框架。

Javassist是一个十分简单粗暴的工具,它的用法简单来看是这样的:

public static void main(String[] args) throws Exception {  
    ClassPool pool = ClassPool.getDefault();  
    //创建Programmer类       
    CtClass cc= pool.makeClass("com.samples.Programmer");  
    //定义code方法  
    CtMethod method = CtNewMethod.make("public void code(){}", cc);  
    //插入方法代码  
    method.insertBefore("System.out.println(\"I'm a Programmer,Just Coding.....\");");  
    cc.addMethod(method);  
    //保存生成的字节码  
    cc.writeFile("f://myProxy");
}

这么灵活,写个代理还不是分分钟......

JAVA动态代理比较

JDK动态代理 CGLIB JAVASSIST
实现原理 利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理 利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理 通过修改字节码生成类来处理
目标类是否需要实现接口

你可能感兴趣的:(JAVA动态代理)