zoukankan      html  css  js  c++  java
  • 动态代理之Cglib浅析

    什么是Cglib

    Cglib是一个强大的,高性能,高质量的代码生成类库。它可以在运行期扩展JAVA类与实现JAVA接口。其底层实现是通过ASM字节码处理框架来转换字节码并生成新的类。大部分功能实际上是ASM所提供的,Cglib只是封装了ASM,简化了ASM操作,实现了运行期生成新的class。

    Cglib的原理

    运行时动态的生成一个被代理类的子类(通过ASM字节码处理框架实现),子类重写了被代理类中所有非final的方法。在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势植入横切逻辑。

    Cglib优缺点

    优点:JDK动态代理要求被代理的类必须实现接口,当需要代理的类没有实现接口时Cglib代理是一个很好的选择。另一个优点是Cglib动态代理比使用java反射的JDK动态代理要快(Cglib的FastClass机制,解析参考http://www.cnblogs.com/cruze/p/3865180.html#lable3

    缺点:对于被代理类中的final方法,无法进行代理,因为子类中无法重写final函数

    Cglib类库

    net.sf.cglib.core:底层字节码处理类,他们大部分与ASM有关系。

    net.sf.cglib.transform:编译期或运行期类和类文件的转换

    net.sf.cglib.proxy:实现创建代理和方法拦截器的类

    net.sf.cglib.reflect:实现快速反射和C#风格代理的类

    net.sf.cglib.util:集合排序等工具类

    net.sf.cglib.beans:JavaBean相关的工具类

    最主要的一个接口CallBack接口,即回调接口,下面的拦截器、过滤器、延迟加载都继承于它,结构图如下:

    Cglib的拦截器和过滤器

    拦截器:实现MethodInterceptor接口的类,在intercept方法中实现对代理目标类的方法拦截。但同时Cglib为简化和提高性能提供了一些专门的回调类型如FixedValue(可以在实现的方法loadObject中指定返回固定值,而不调用目标类函数)、NoOp(把对回调方法的调用直接委派到这个方法的父类,即不进行拦截)

    过滤器:实现CallbackFilter接口的类,通过accept方法返回一个下标值,用于指定调用哪个拦截器进行拦截处理

    /**
     * @author longe
     *    被代理类
     */
    public class TDao {
        public void create() {
            System.out.println("create() is running !");
        }
    
        public void query() {
            System.out.println("query() is running !");
        }
    
        public void update() {
            System.out.println("update() is running !");
        }
    
        public void delete() {
            System.out.println("delete() is running !");
        }
    }
    
    /**
     * @author longe
     *    过滤器
     */
    public class TDaoCglibFilter implements CallbackFilter {
    
        public final static int NO_RESTRICTION = 0;
        public final static int RESTRICTION_CREATE = 1;
        
        
        /* (non-Javadoc)
         *    根据调用的方法返回使用的callbacks下标
         */
        @Override
        public int accept(Method method) {
            if(method.getName().startsWith("create")){
                return RESTRICTION_CREATE;
            }
            return NO_RESTRICTION;
        }
    }
    
    /**
     * @author longe
     * 拦截器
     */
    public class TDaoCglibProxy implements MethodInterceptor {
    
        private String name;
        private Object objT;
        
        public TDaoCglibProxy(String name) {
            this.name = name;
        }
        public TDaoCglibProxy(Object obj,String name) {
            this.name = name;
            this.objT = obj;
        }
        
        @Override
        public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
            if(!name.equals("张三")){
                System.out.println("没有权限!");
                return null;
            }
    //        return method.invoke(objT, args);    //通过反射进行调用
            return proxy.invokeSuper(obj, args);    //使用Cglib代理调用
        }
    
    
        public static void main(String[] args) {
            Enhancer en = new Enhancer();
            en.setSuperclass(TDao.class);
            TDaoCglibProxy callBack = new TDaoCglibProxy("张三1");
    //        指定两个callback,通过callBackFilter返回的下标值控制调用使用那一个callback处理
            en.setCallbacks(new Callback[] { NoOp.INSTANCE, callBack });
    //        方法过滤器,根据过滤器返回不同的值回调对应下标的Callback方法
            en.setCallbackFilter(new TDaoCglibFilter());
            TDao dao = (TDao) en.create();
            dao.create();
            dao.query();
            dao.update();
            dao.delete();
        }
        
    }
    代码示例
    执行输出如下:
    
    没有权限!
    query() is running !
    update() is running !
    delete() is running !

    可以看到只用create方法的调用被TDaoCglibProxy拦截器拦截了,从而判断出没有权限。而其他方法没有被TDaoCglibProxy拦截器拦截,是因为在Filter过滤器中返回的索引为0,即指定的回调callback是NoOp,索引直接调用了父类的实现方法(即被代理类的实现)

    Cglib的延迟加载

    LazyLoader:当实际对象需要延迟加载时,可以使用LazyLoader回调。一旦实际对象被装载,它将被每一个调用代理对象的方法使用(即实际对象被访问时才调用此回调callback的loadObject方法)

    Dispatcher:和LazyLoader回调具有相同的特点,区别是当代理方法被调用时,装载对象的方法也总是要被调用

    ProxyRefDispatcher:与Dispatcher一样,只不过可以把代理对象作为参数进行传递

    public class CglibLazyTest extends TestCase {
    
        public void testLazyBean() {
            TB tb = new TB();
            System.out.println("***************************************");
            System.out.println("cid's value: " + tb.getCid());
            System.out.println("***************************************");
            System.out.println("bean's username value: " + tb.getBean());
    //        LazyLoader 这里将返回同一个对象
            System.out.println("bean's username value: " + tb.getBean());
            
            
            System.out.println("***************************************");
            System.out.println("bean2's username value: " + tb.getBean2());
    //        这里将返回一个新的对象
            System.out.println("bean2's username value: " + tb.getBean2());
        }
    
        /**
         * @author longe
         *    通过实现LazyLoader接口延迟加载
         */
        class Lazy implements LazyLoader {
            @Override
            public Object loadObject() throws Exception {
                TestBean tb = new TestBean();
                System.out.println("lazy load ========= ");
                tb.setUserName("longe lazy");
                return tb;
            }
    
        }
    
        /**
         * @author longe
         *    通过实现Dispatcher接口延迟加载
         */
        class DispacherTest implements Dispatcher {
            @Override
            public Object loadObject() throws Exception {
                TestBean2 tb2 = new TestBean2();
                System.out.println("dispatcher load =========== ");
                tb2.setNo("longe dispatcher lazy");
                return tb2;
            }
    
        }
        /**
         * @author longe
         *    与Dispatcher一样,只不过可以把代理对象作为参数进行传递
         */
        class ProxyRefDispacherTest implements ProxyRefDispatcher {
            @Override
            public Object loadObject(Object proxy) throws Exception {
                return null;
            }
        }
    
        class TB {
            private String cid;
            private TestBean bean;
            private TestBean2 bean2;
            LazyLoader lazy = new Lazy();
            Dispatcher dis = new DispacherTest();
    
            public TB() {
                cid = "no lazy";
                // Enhancer en = new Enhancer();
                // en.setSuperclass(TestBean.class);
                // en.setCallback(lazy);
                // bean = (TestBean) en.create(new Class[]{CglibLazyTest.class},new
                // Object[]{new CglibLazyTest()});
                bean = (TestBean) Enhancer.create(TestBean.class, lazy);
                bean2 = (TestBean2) Enhancer.create(TestBean2.class, dis);
                System.out.println("TB construct end...");
            }
    
            public TestBean getBean() {
                return bean;
            }
    
            public String getCid() {
                return cid;
            }
    
            public void setCid(String cid) {
                this.cid = cid;
            }
    
            public void setBean(TestBean bean) {
                this.bean = bean;
            }
    
            public TestBean2 getBean2() {
                return bean2;
            }
    
            public void setBean2(TestBean2 bean2) {
                this.bean2 = bean2;
            }
        }
    
    }
    
    class TestBean {
        public TestBean() {
            System.out.println("TestBean construct end...");
        }
    
        private String userName;
    
        public String getUserName() {
            return userName;
        }
    
        public void setUserName(String userName) {
            this.userName = userName;
        }
    }
    
    class TestBean2 {
        public TestBean2() {
            System.out.println("TestBean2 construct end...");
        }
    
        private String no;
    
        public String getNo() {
            return no;
        }
    
        public void setNo(String no) {
            this.no = no;
        }
    }
    代码示例
    执行输出如下:

    TestBean construct end...
    TestBean2 construct end...
    TB construct end...
    ***************************************
    cid's value: no lazy
    ***************************************
    TestBean construct end...
    lazy load =========
    bean's username value: practices.model.proxy.cglib.TestBean@77521871
    bean's username value: practices.model.proxy.cglib.TestBean@77521871
    ***************************************
    TestBean2 construct end...
    dispatcher load ===========
    bean2's username value: practices.model.proxy.cglib.TestBean2@2ec2dfea
    TestBean2 construct end...
    dispatcher load ===========
    bean2's username value: practices.model.proxy.cglib.TestBean2@7bfa93a1

    上面的示例中TestBean对象使用了LazyLoader回调,TestBean2对象使用了Dispatcher回调。

    分析:

    1. 在TB的构造函数中创建了TestBean和TestBean2对象,此时这两个对象分别被创建,但是是空对象。
    2. 当tb.getBean().getUserName()第一次访问TestBean对象时,此时LazyLoader回调被调用,TestBean装载并进行初始化赋值,最终返回装载的新对象。
    3. 当tb.getBean().getUserName()第二次访问TestBean对象时,TestBean已经被装载过,此时LazyLoader回调不被调用。
    4. 当tb.getBean2().getNo()第一次访问TestBean2对象时,此时Dispatcher回调被调用,TestBean2装载并进行初始化赋值,最终返回装载的新对象。
    5. 当tb.getBean2().getNo()第二次访问TestBean2对象时,此时Dispatcher回调还是会被调用,TestBean2装载并进行初始化赋值,最终返回一个新装载的新对象

    Cglib中的Mixin

    net.sf.cglib.proxy.Mixin允许多个对象被绑定到一个单个的大对象。在代理中对方法的调用委托到下面对应的对象中。

    通过指定代理类型和实际的代理对象参数即可直接创建代理。可以绑定多个,但需要注意类型数组与对象数组的一一对应。如:

    public static void testCglibMixin() {
            System.out.println(MixinIfA.class.getName());
            Class[] ints = new Class[]{MixinIfA.class,MixinIfB.class};
            Object[] objs = new Object[]{new MixinAImpl(),new MixinBImpl()};
            Object obj = Mixin.create(ints,    objs);
            MixinIfA a = (MixinIfA) obj;
            MixinIfB b = (MixinIfB) obj;
            a.methodA();
            b.methodB();
        }

     public static Mixin create(Class[] interfaces, Object[] delegates)中第一个参数必须是接口类型,第二个参数是接口实现类对象实例

    OK,到这Cglib代理了解的差不多了,从Cglib的动态代理知道它可以动态的生成java类,那么这里顺带贴上一段简单的Cglib生成bean的代码.(供后续复习用...)

    /**
     * @author longe
     *    动态生成bean
     */
    public class CglibBean {
    
        public Object object;
        public BeanMap beanMap;
    
        public CglibBean() {
        }
    
        public CglibBean(Map<String, Class> propertyMap) {
            this.object = generateBean(propertyMap);
            this.beanMap = BeanMap.create(this.object);
        }
    
        public void putValue(String property, Object value) {
            beanMap.put(property, value);
        }
    
        public Object getValue(String property) {
            return beanMap.get(property);
        }
    
        public Object getObject() {
            return this.object;
        }
    
        public Object generateBean(Map<String, Class> propertyMap) {
            BeanGenerator generator = new BeanGenerator();
            Set<String> keySet = propertyMap.keySet();
            for (String key : keySet) {
                generator.addProperty(key, propertyMap.get(key));
            }
            return generator.create();
        }
    
        public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
            Map<String, Class> propertyMap = new HashMap<>();
            propertyMap.put("id", Class.forName("java.lang.Integer"));
            propertyMap.put("name", Class.forName("java.lang.String"));
            propertyMap.put("address", Class.forName("java.lang.String"));
            CglibBean cglibBean = new CglibBean(propertyMap);
            cglibBean.putValue("id", 101);
            cglibBean.putValue("name", "longe");
            cglibBean.putValue("address", "beijing");
            System.out.println(cglibBean.getValue("id"));
            System.out.println(cglibBean.getValue("name"));
            System.out.println(cglibBean.getValue("address"));
            Object object = cglibBean.getObject();
            Method[] ms = object.getClass().getDeclaredMethods();
            for (Method method : ms) {
                System.out.println(method.getName());
                if (method.getName().startsWith("get")) {
                    System.out.println("=====" + method.invoke(object, null));
                }
            }
    
        }
    }
    View Code

    Cglib异常

    java.lang.IllegalArgumentException: Superclass has no null constructors but no arguments were given
    at net.sf.cglib.proxy.Enhancer.emitConstructors(Enhancer.java:721)
    at net.sf.cglib.proxy.Enhancer.generateClass(Enhancer.java:499)
    at net.sf.cglib.core.DefaultGeneratorStrategy.generate(DefaultGeneratorStrategy.java:25)
    at net.sf.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:216)
    at net.sf.cglib.proxy.Enhancer.createHelper(Enhancer.java:377)
    at net.sf.cglib.proxy.Enhancer.create(Enhancer.java:285)
    at practices.model.proxy.cglib.CglibLazyTest.testLazyBean1(CglibLazyTest.java:21)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at junit.framework.TestCase.runTest(TestCase.java:176)
    at junit.framework.TestCase.runBare(TestCase.java:141)
    at junit.framework.TestResult$1.protect(TestResult.java:122)
    at junit.framework.TestResult.runProtected(TestResult.java:142)
    at junit.framework.TestResult.run(TestResult.java:125)
    at junit.framework.TestCase.run(TestCase.java:129)
    at junit.framework.TestSuite.runTest(TestSuite.java:252)
    at junit.framework.TestSuite.run(TestSuite.java:247)
    at org.junit.internal.runners.JUnit38ClassRunner.run(JUnit38ClassRunner.java:86)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:675)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)

    原因:

    • 代理目标对象不能是内部类(因为内部类的创建依赖外部类),如果是内部类,cglib代理内部会获取到一个有参构造函数(参数是外部类对象,如果实在需要代理一个内部类,可以通过传递构造参数实现)
    • Cglib代理默认创建一个缺省构造函数的目标对象,如果目标对象存在有参构造函数,Cglib进行代理时需要指定构造函数的参数,或者在目标对象上必须存在缺省构造函数,否则抛出异常(可以通过传递构造参数创建代理类)
  • 相关阅读:
    android Edittext自定义输入字符和类型
    让android webView使用系统默认浏览器内核直接解析,不弹出选择浏览器选项
    java对象中继承和变量初始化顺序浅析
    android判断pad还是手机
    我不知道自己想要什么
    计算机网络概述
    2020/2/27-28
    操作系统概述
    数据模型
    数据库系统概述
  • 原文地址:https://www.cnblogs.com/mr-long/p/5889054.html
Copyright © 2011-2022 走看看