静态代理,JDK动态代理和CGLIB代理入门学习

之前面试时面试官问我:“你知道spring中有哪几种代理吗?” 啊?代理?啥子代理?VPN代理吗?嘿嘿,面试官你要种子直说啊......被刷下来了。好吧,入门学习下代理。

为什么需要代理?参考大佬的博客:

代理(Proxy)是一种设计模式,提供了对目标对象另外的访问方式;即通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能.
这里使用到编程中的一个思想:不要随意去修改别人已经写好的代码或者方法,如果需改修改,可以通过代理的方式来扩展该方法。

就是说代理对象可以在执行目标对象的方法的前后,加加日志啊,判断参数啊...之类的。

代理有哪些?

代理有:静态代理,JDK动态代理,CGLIB代理。

静态代理:代理对象和目标对象实现同一个接口或继承同一个父类。这里以实现接口为例子。

学员接口:

public interface StudentInterface {
    //保存数据
    void saveData();
}

学员接口实现(目标对象):

public class StudentImp implements StudentInterface {
    @Override
    public void saveData() {
        System.out.println("I have been saved data.");
    }
}

静态代理对象:

public class StaticProxyBean implements StudentInterface {
    StudentInterface studentInterface;

    public StaticProxyBean(StudentInterface studentInterface) {
        this.studentInterface = studentInterface;
    }

    @Override
    public void saveData() {
        System.out.println("start proxy.");
        studentInterface.saveData();
        System.out.println("end proxy.");
    }
}

测试:

public class TestStaticProxy {
    public static void main(String[] args) {
        StudentInterface userDao = new StudentImp();
        //代理对象 代理 目标对象
        StaticProxyBean staticProxyBean = new StaticProxyBean(userDao);
        staticProxyBean.saveData();
    }
}

静态代理很简单,不需要引入任何的jar包,但是它有缺点:代理对象需要实现所有的目标对象方法。就比如目标对象StaticProxyBean有deleteData()方法,findData()方法,代理对象StaticProxyBean也得实现。代码很多。如何解决呢?JDK动态代理。


动态代理:

1.代理对象,不需要实现接口
2.代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象(需要我们指定创建代理对象/目标对象实现的接口的类型)。

也就是说,1.代理对象不需要实现StudentInterface接口了,但它要实现jdk的InvocationHandler接口。2.java会生成class字节码级别的代理对象,也就是说,代理对象是一个新的类。我们在文后会给出。

JDK代理对象,参考第二位大佬的博客

public class DynamicProxyBean implements InvocationHandler {

    private Object target;

    public Object bind(Object target) {
        this.target = target;

        //loader:一个ClassLoader对象,定义了由哪个ClassLoader对象来生成代理对象进行加载
        //interfaces:一个Interface对象的数组,表示的是我将要给我需要代理的对象提供一组什么接口,如果我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样我就能调用这组接口中的方法了
        //h:一个InvocationHandler对象,表示的是当我这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上,间接通过invoke来执行
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //输出代理的方法
        System.out.println("==========" + method.getName() + "=============");
        //方法执行前后 打印当前时间
        System.out.println("begin:" + DateTimeEx.TimeStampToString(System.currentTimeMillis(), "yyyy-MM-dd hh:mm:ssSSS"));
        Object invoke = method.invoke(target, args);
        System.out.println("end:" + DateTimeEx.TimeStampToString(System.currentTimeMillis(), "yyyy-MM-dd hh:mm:ssSSS"));
        return invoke;
    }
}

测试:

public class TestDynamicProxy {
    public static void main(String[] args) {
        StudentInterface dao = new StudentImp();

        //通过bind方法获得被代理后的对象
        dao = (StudentInterface) new DynamicProxyBean().bind(dao);
        dao.saveData();
    }
}

好吧,JDK动态代理其实也有缺点,就是目标对象必须要实现接口,也就是例子中StudentImp实现了StudentInterface接口,否则不能使用动态代理。那么这时候CGLIB代理就来了。


CGLIB代理:

1.需要引入cglib的jar文件。
2.引入功能包后,就可以在内存中动态构建子类。
3.代理的类不能为final,否则报错。
4.目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法。
5.如果方法为static,private则无法进行代理。

CGLIB代理对象:

public class CgLibProxyBean implements MethodInterceptor {

    private Object target;

    public Object createProxyObject(Object target) {
        this.target = target;

        //增强器,动态代码生成器
        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("==========" + method.getName() + "=============");
        //方法执行前后 打印当前时间
        System.out.println("begin:" + DateTimeEx.TimeStampToString(System.currentTimeMillis(), "yyyy-MM-dd hh:mm:ssSSS"));
        Object invoke = methodProxy.invokeSuper(o, objects);
        System.out.println("end:" + DateTimeEx.TimeStampToString(System.currentTimeMillis(), "yyyy-MM-dd hh:mm:ssSSS"));
        return invoke;
    }
}

测试:

public class TestCglibProxy {
    public static void main(String[] args) {
        CgLibProxyBean cgLibProxyBean = new CgLibProxyBean();
        StudentInterface userDao = new StudentImp();
        userDao = (StudentInterface) cgLibProxyBean.createProxyObject(userDao);
        userDao.saveData();
    }
}

测试结果就不贴图片了,都一样。

看到这同学可能会问,CGLIB就没有缺点了吗?有的,慢,他比JDK动态代理慢。所以Spring AOP需要代理的时候,先会判断能否使用JDK动态代理,不能的话,才使用CGLIB代理。

最后贴一个JDK动态代理生成的字节码文件,参考http://www.importnew.com/26116.html

public class DynamicProxyUtil {
    public static void main(String[] args) throws IOException {
        byte[] classFile = ProxyGenerator.generateProxyClass("TestProxyGen", StudentImp.class.getInterfaces());
        File file = new File("C:\\Users\\Administrator\\Desktop\\TestProxyGen.class");
        FileOutputStream fos = new FileOutputStream(file);
        fos.write(classFile);
        fos.flush();
        fos.close();
    }
}
public final class TestProxyGen extends Proxy implements StudentInterface {
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;

    public TestProxyGen(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return ((Boolean)super.h.invoke(this, m1, new Object[]{var1})).booleanValue();
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

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

    public final void saveData() 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  {
        try {
            return ((Integer)super.h.invoke(this, m0, (Object[])null)).intValue();
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    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("proxy_test.static_proxy.StudentInterface").getMethod("saveData");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

它实现了StudentInterface接口,m3获取了saveData()方法,通过反射执行saveData()方法。

说实话,代理还是有很多不懂,都是从网上看的其他大佬的博客,自己先拼凑记录下来。

完--

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值