zoukankan      html  css  js  c++  java
  • Java中jdk代理和cglib代理

    代理模式


    给某一个对象提供一个代理,并由代理对象控制对原对象的引用。在一些情况下,一个客户不想或者不能够直接引用一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。

    在Java中代理模式从实现方式上可以分为两个类别:静态代理和动态代理

    静态代理: 也就是我们学习设计模式之代理模式时常见的事例,具体不在赘述,参见:【Java设计模式-13代理模式】

    动态代理: 在静态代理中,因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类。同时,一旦接口增加方法,目标对象与代理对象都要维护。那么如何解决这种问题呢?答案就是动态代理,下面会使用两种动态代理(jdk代理、cglib代理)分别实现同一个事例来体会一下动态代理的实现。

    动态代理

    动态代理具有以下特性:

    1.代理对象不需要实现接口

    2.代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象

    jdk代理

    只支持对接口的的实现,其实现主要通过java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口。

    Proxy类主要用来获取动态代理对象,InvocationHandler接口用来约束调用者实现。
    JDK的动态代理用起来非常简单,但它有一个限制,就是使用动态代理的对象必须实现一个或多个接口

    package com.lkf.parttern.proxy.dynamic;
    
    /**
     * 账户操作接口
     *
     * @author kaifeng
     */
    public interface Account {
    
        /**
         * 查询余额
         */
        void queryAccountBalance();
    
        /**
         * 充值话费
         */
        void updateAccountBalance();
    }
    
    package com.lkf.parttern.proxy.dynamic;
    
    /**
     * 账户操作实现
     *
     * @author kaifeng
     */
    public class AccountImpl implements Account {
        /**
         * 查询余额
         */
        @Override
        public void queryAccountBalance() {
            System.out.println("【AccountImpl::queryAccountBalance】-查询账户余额");
        }
    
        /**
         * 充值话费
         */
        @Override
        public void updateAccountBalance() {
            System.out.println("【AccountImpl::updateAccountBalance】-话费充值");
        }
    }
    

    代理对象实现接口InvocationHandler

    package com.lkf.parttern.proxy.dynamic.jdk;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    
    /**
     * 这里使用的是Jdk的动态代理,其必须实现接口,这也是jdk代理的缺陷,不过cglib代理会修补这个缺陷
     *
     * @author kaifeng
     */
    public class JDKAccountProxyFactory implements InvocationHandler {
    
        private Object target;
    
        /**
         * 代理方式实例化对象
         */
        public Object bind(Object target) {
            this.target = target;
            return Proxy.newProxyInstance(target.getClass().getClassLoader(),
                    target.getClass().getInterfaces(), this);
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            boolean objFlag = method.getDeclaringClass().getName().equals("java.lang.Object");
    
            Object result = null;
            if (!objFlag) {
                System.out.println("【JDKAccountProxyFactory::invoke】-代理before");
            }
            //真实方法调用
            result = method.invoke(this.target, args);
            if (!objFlag) {
                System.out.println("【JDKAccountProxyFactory::invoke】-代理after");
            }
            return result;
        }
    }
    
    
    package com.lkf.parttern.proxy.dynamic.jdk;
    
    import com.lkf.parttern.proxy.dynamic.Account;
    import com.lkf.parttern.proxy.dynamic.AccountImpl;
    
    /**
     * jdk动态代理测试
     */
    public class JDKAccountProxyFactoryTest {
        public static void main(String[] args) {
            Account account = (Account) new JDKAccountProxyFactory().bind(new AccountImpl());
            account.queryAccountBalance();
            System.out.println("***************************");
            account.updateAccountBalance();
        }
    }
    
    
    【JDKAccountProxyFactory::invoke】-代理before
    【AccountImpl::queryAccountBalance】-查询账户余额
    【JDKAccountProxyFactory::invoke】-代理after
    ***************************
    【JDKAccountProxyFactory::invoke】-代理before
    【AccountImpl::updateAccountBalance】-话费充值
    【JDKAccountProxyFactory::invoke】-代理after

    CGLIB代理

    对于上面说到JDK仅支持对实现接口的委托类进行代理的缺陷,CGLIB解决了这个问题,使其委托类也可是非接口实现类。
    CGLIB内部使用到ASM,所以我们下面的例子需要引入cglib-3.2.5.jar

    Cglib的原理是对指定的目标类动态生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对final修饰的类和final方法进行代理。

    营业员实体类,没有实现接口的普通类

    package com.lkf.parttern.proxy.dynamic;
    
    import java.util.UUID;
    
    /**
     * 营业员
     *
     * @author kaifeng
     */
    public class SalesPerson {
        private String jobNum = String.valueOf(UUID.randomUUID());
    
        public String getJobNum() {
            return jobNum;
        }
    }
    

    cglib动态代理实现了接口MethodInterceptor

    package com.lkf.parttern.proxy.dynamic.cglib;
    
    import net.sf.cglib.proxy.Enhancer;
    import net.sf.cglib.proxy.MethodInterceptor;
    import net.sf.cglib.proxy.MethodProxy;
    
    import java.lang.reflect.Method;
    
    /**
     * cglib动态生成代理对象
     *
     * @author kaifeng
     */
    public class CglibAccountProxyFactory implements MethodInterceptor {
    
        private Object target;
    
        /**
         * 获取对象实例
         */
        public Object getInstance(Object target) {
            this.target = target;
            return Enhancer.create(this.target.getClass(), this);
        }
    
        /**
         * 方法拦截
         * <p>
         * 一般使用proxy.invokeSuper(obj,args)方法
         * proxy.invoke(obj,args),这是执行生成子类的方法。
         * 如果传入的obj就是子类的话,会发生内存溢出,因为子类的方法不进入intercept方法,而这个时候又去调用子类的方法,两个方法直接循环调用了
         * </p>
         */
        @Override
        public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
            // 排除Object类中的toString等方法
            System.out.println(method.getDeclaringClass().getName());
            boolean objFlag = method.getDeclaringClass().getName().equals("java.lang.Object");
            if (!objFlag) {
                System.out.println("【CglibAccountProxyFactory::intercept】-代理before");
            }
            Object result = null;
            result = methodProxy.invokeSuper(obj, args);
            if (!objFlag) {
                System.out.println("【CglibAccountProxyFactory::intercept】-代理after");
            }
            return result;
        }
    
    }
    

    cglib动态代理测试

    package com.lkf.parttern.proxy.dynamic.cglib;
    
    import com.lkf.parttern.proxy.dynamic.Account;
    import com.lkf.parttern.proxy.dynamic.AccountImpl;
    import com.lkf.parttern.proxy.dynamic.SalesPerson;
    
    /**
     * cglib动态代理测试
     *
     * @author kaifeng
     */
    public class CglibAccountProxyFactoryTest {
        public static void main(String[] args) {
            // 下面是用cglib的代理
            // 1.支持实现接口的类
            Account account = (Account) new CglibAccountProxyFactory().getInstance(new AccountImpl());
            account.queryAccountBalance();
    
            // 2.支持未实现接口的类
            SalesPerson salesPerson = (SalesPerson) new CglibAccountProxyFactory().getInstance(new SalesPerson());
            System.out.println("我的工号是:" + salesPerson.getJobNum());
        }
    }
    
    com.lkf.parttern.proxy.dynamic.AccountImpl
    【CglibAccountProxyFactory::intercept】-代理before
    【AccountImpl::queryAccountBalance】-查询账户余额
    【CglibAccountProxyFactory::intercept】-代理after
    com.lkf.parttern.proxy.dynamic.SalesPerson
    【CglibAccountProxyFactory::intercept】-代理before
    【CglibAccountProxyFactory::intercept】-代理after
    我的工号是:5cee95e6-9032-463c-8a4a-139d1f882b4f

    总结

    • Cglib代理,也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能的扩展.
    • JDK的动态代理有一个限制,就是使用动态代理的对象必须实现一个或多个接口,如果想代理没有实现接口的类,就可以使用Cglib实现.
    • Cglib是一个强大的高性能的代码生成包,它可以在运行期扩展java类与实现java接口.它广泛的被许多AOP的框架使用,例如Spring AOP,为他们提供方法的interception(拦截)
    • Cglib包的底层是通过使用一个小而块的字节码处理框架ASM来转换字节码并生成新的类.不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉.

    在Spring的AOP编程中:
    如果加入容器的目标对象有实现接口,会使用JDK代理;
    如果目标对象没有实现接口,会使用Cglib代理

  • 相关阅读:
    使用 @Autowired 的时候,到底是写接口还是实现类?
    socket的简单例子
    java 将文件夹所有的文件合并到指定的文件夹下
    java 复制某一文件夹下的所有文件到另一个文件夹下
    java Date日期总结的一些转换的方法
    java 可排序的数值得字符串,转化成list集合后进行的排序的几种方法
    java 查看文件的大小的方法
    java 从一个总的list集合中,去掉指定的集合元素,得到新的集合
    java 可拆成数组的字符串,去掉重复元素的一种方法
    将博客搬至CSDN
  • 原文地址:https://www.cnblogs.com/liukaifeng/p/10052615.html
Copyright © 2011-2022 走看看