Java动态代理

Posted by BY KiloMeter on March 22, 2019

Java的代理在很多方面都有所应用,比较有名的就是Spring AOP,底层的实现就是使用的动态代理,Java中动态代理的实现由两种,分别是JDK动态代理和CGlib动态代理,下面就介绍下这两种代理的区别和优缺点。

介绍动态代理之前,先介绍下无代理和静态代理。

无代理

//定义接口
public interface person {
    void eat(String food);
}
//实现类
public class PersonImpl implements Person {
    @Override
    public void eat(String food) {
        System.out.println("I eat:"+food);
    }
}
//调用
public static void main(String[] args) {
        Person person = new PersonImpl();
        person.eat("dinner");
    }
//打印结果 I eat:dinner

在上面的例子中,如果在吃东西前后加上一些逻辑该怎么办呢?比如吃法前打扫,吃饭后收拾下,当然可以在eat方法中把这些逻辑加上,不过这样牺牲了许多灵活性和拓展性。此外,如果要在其他地方打扫,代码也无法做到复用。

因此,在这里把eat和打扫的代码给分开,不要耦合到一块,这样就是静态代理。

静态代理

看下静态代理的例子

//定义接口
public interface person {
    void eat(String food);
}
//实现类
public class PersonImpl implements Person {
    @Override
    public void eat(String food) {
        System.out.println("I eat:"+food);
    }
}
//静态代理类
public class staticProxy implements Person{
    private Person person;
    public staticProxy(Person person){
        this.person = person;
    }
    @Override
    public void eat(String food) {
        before();
        person.eat(food);
        after();
    }

    private void after() {
        System.out.println("打扫");
    }

    private void before() {
        System.out.println("收拾");
    }
}
//调用
public static void main(String[] args) {
        StaticProxy staticProxy = new StaticProxy(new PersonImpl());
        staticProxy.eat("dinner");
    }
//打印结果
收拾
I eat:dinner
打扫

上面的静态代理中,通过把真正的对象new PersonImpl()委托给同样实现了Person接口的静态代理对象staticProxy,因为实现了同样的接口,所以可以直接调用staticProxy的方法,但是在staticProxy的方法内部实际上调用的是被代理对象的方法,然后代理对象为我们加上了before()和after()的逻辑代码。如果以后要修改逻辑代码,只需要修改下before和after方法即可,此外,如果在其他方法中也需要复用before()和after()方法的话,可以让staticProxy实现其他的接口,然后和这里的eat逻辑一样调用即可。这样就不会影响到eat()方法。

但是也存在着问题,如上面所讲,如果要复用before和after逻辑时,总要实现新的接口,然后再实现该接口的代理方法,但是代理类的调用逻辑总是相似的,为了实现相似的功能而添加了很多包装代码,此外,如果接口发生了改变,不光是被代理对象,代理对象也要跟着修改,这时就引出了下面的动态代理

动态代理

JDK动态代理

public class JDKproxy implements InvocationHandler {
    private Object target;
    public JDKproxy(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;
    }
    private void after() {
        System.out.println("JDK打扫");
    }

    private void before() {
        System.out.println("JDK收拾");
    }
}

//调用
public static void main(String[] args) {
        Person person = new PersonImpl();
        JDKproxy jdKproxy = new JDKproxy(person);
        Person personproxy = (Person) Proxy.newProxyInstance(
                person.getClass().getClassLoader(),
                person.getClass().getInterfaces(),
                jdKproxy
        );
        personproxy.eat("dinner");
    }
//打印结果
JDK收拾
I eat:dinner
JDK打扫

上面的代码中,通过JDKproxy包装person,然后用JDK的代理工厂方法实例了了一个具体的代理类,最后调用代理的方法。

在这里,因为代理类JDKproxy中保存的是Object,因此无论是什么类,只要实现了特定的接口,都能够使用JDKproxy进行包装,从而使before()和after()方法能够得到复用。

但是JDK的动态代理还是有一个致命的缺点,那就是要代理的类需要实现一个特定的接口,如果被代理的方法不是通过某个接口实现的话,就无法实现代理,下面就引出了CGLib动态代理。

CGLib动态代理

CGLib动态代理是一个类库,可以在运行期间动态生成字节码,从而生成代理类

public class CGLibProxy implements MethodInterceptor {
    private static CGLibProxy instance = new CGLibProxy();

    private CGLibProxy(){}

    public static CGLibProxy getInstance(){
        return instance;
    }
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        before();
        Object result = methodProxy.invokeSuper(o,objects);
        after();
        return result;
    }
    public <T> T getProxy(Class<T> tclass){
        return (T) Enhancer.create(tclass,this);
    }

    private void after() {
        System.out.println("CGL打扫");
    }

    private void before() {
        System.out.println("CGL收拾");
    }
}
//调用
public static void main(String[] args) {
        Person person = CGLibProxy.getInstance().getProxy(PersonImpl.class);
        person.eat("dinner");
    }
//结果
CGL收拾
I eat:dinner
CGL打扫

CGLib动态代理的优点很明显,就是可以为没有实现接口的类的方法进行包装。

但是,JDK的动态代理会拦截并包装实现了接口的方法,但是CGLib的包装是无须提供接口的,因此CGLib是对所有的方法进行包装的。

CGLib的背后是生成了一个被代理对象类的子类,因此被代理对象的final方法无法被包装

Spring AOP实现原理

SpringAOP的底层就是使用了动态代理,JDK动态代理和CGLib动态代理都有使用到。如果被代理类有实现接口,那么使用JDK动态代理,否则使用CGLib动态代理。