代理详解(java代理和CGLIB动态代理)

发布时间 2023-04-13 15:06:46作者: 程序员hg

  【代理】大家都知道,特别是在spring中aop、spring中的事务、spring解析注解@Configuration,以及最原始的解析spring.xml的配置,这些都是使用代理来进行实现的,所以今天进行总结下代理。

  开始之前,我们需要解决一下3个问题:

    1.为什么需要使用代理?

    2.jdk代理怎么玩?

    3.cglib代理怎么玩?

  案例剖析:

  1.创建一个接口。有3个方法。

public interface IService {

    void m1();

    void m2();

    void m3();
}

  2.Aservice实现类

public class Aservice implements IService {

    @Override
    public void m1() {
        System.out.println("我是ServiceA中的m1方法!");
    }

    @Override
    public void m2() {
        System.out.println("我是ServiceA中的m2方法!");
    }

    @Override
    public void m3() {
        System.out.println("我是ServiceA中的m3方法!");
    }
}

  3.Bservice实现类

public class Bservice implements IService {
    @Override
    public void m1() {
        System.out.println("我是ServiceB中的m1方法!");
    }

    @Override
    public void m2() {
        System.out.println("我是ServiceB中的m2方法!");
    }

    @Override
    public void m3() {
        System.out.println("我是ServiceB中的m3方法!");
    }
}

  4.测试类

public class ProxyTest {

    @Test
    public void test() {
        IService serviceA = new Aservice();
        IService serviceB = new Bservice();

        serviceA.m1();
        serviceA.m2();
        serviceA.m3();

        serviceB.m1();
        serviceB.m2();
        serviceB.m3();
    }
}

  5.输出结果如下:

  我是ServiceA中的m1方法!
  我是ServiceA中的m2方法!
  我是ServiceA中的m3方法!
  我是ServiceB中的m1方法!
  我是ServiceB中的m2方法!
  我是ServiceB中的m3方法!

  

比较好的方式就是:我们可以使用Iservice接口创建一个代理类,通过这个代理类来间接的访问Iservice接口的实现类,在代理类中做接口耗时以及发送到监控的代码:做法如下:

  

public class ServiceProxy implements IService {

    /**
     * 目标对象,被代理的对象
     */
    private IService target;

    @Override
    public void m1() {
        long starTime = System.nanoTime();
        this.target.m1();
        long endTime = System.nanoTime();
        System.out.println(this.target.getClass() + ".m1()方法耗时(纳秒):" + (endTime - starTime));
    }

    @Override
    public void m2() {
        long starTime = System.nanoTime();
        this.target.m1();
        long endTime = System.nanoTime();
        System.out.println(this.target.getClass() + ".m2()方法耗时(纳秒):" + (endTime - starTime));
    }

    @Override
    public void m3() {
        long starTime = System.nanoTime();
        this.target.m1();
        long endTime = System.nanoTime();
        System.out.println(this.target.getClass() + ".m3()方法耗时(纳秒):" + (endTime - starTime));
    }
}

 

ServiceProxy是Iservice的接口的代理类,target是被代理的对象,即实际需要访问的对象,也实现了Iservice接口,并且3个方法进行统计耗时的代码,当我们需要进行访问Iservice的其他的实现类的时候,可以使用ServiceProxy来进行简介的访问,用法如下:

  


@Test
public void test2() {
    IService serviceA = new ServiceProxy(new Aservice());
    IService serviceB = new ServiceProxy(new Bservice());

    serviceA.m1();
    serviceA.m2();
    serviceA.m3();

    serviceB.m1();
    serviceB.m2();
    serviceB.m3();
}

  输出结果如下:

  我是ServiceA中的m1方法!
  class com.hg.代理.Aservice.m1()方法耗时(纳秒):86300
  我是ServiceA中的m1方法!
  class com.hg.代理.Aservice.m2()方法耗时(纳秒):37900
  我是ServiceA中的m1方法!
  class com.hg.代理.Aservice.m3()方法耗时(纳秒):37000
  我是ServiceB中的m1方法!
  class com.hg.代理.Bservice.m1()方法耗时(纳秒):51500
  我是ServiceB中的m1方法!
  class com.hg.代理.Bservice.m2()方法耗时(纳秒):36100
  我是ServiceB中的m1方法!
  class com.hg.代理.Bservice.m3()方法耗时(纳秒):38900

 我们可以看到并没有去修改ServiceA和ServiceB的方法,只是给Iservice接口创建了一个代理类,通过代理类去访问目标对象,需要添加一个公共的方法都放在了代理中,当领导有其他需求的时候只需要修改ServiceProxy的代码,方便系统扩展和测试。

 如果现在我们需要给系统的每一个接口都加上统计耗时的功能,如果按照上面的方式,那么每一接口都要创建代理类,此时代码量和工作量巨大,我们强烈的需要一个通用的代理类对象。

Jdk代理详解:

  jdk中自带的代理类  

    java.lang.reflect.Proxy

    java.lang.reflect.InvocationHandler

   第一种方式:使用步骤就是:

1.调用Proxy.getProxyClass方法获取代理类的Class对象

2.使用InvocationHandler接口创建代理类的处理器

3.通过代理类和InvocationHandler创建代理对象

4.上面已经创建好代理对象了,接着我们就可以使用代理对象了

@Test
public void test3() throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
    // 1. 获取接口对应的代理类
    Class<?> proxyClass = Proxy.getProxyClass(this.getClass().getClassLoader(), IService.class);
    // 2. 创建代理类的处理器
    InvocationHandler invocationHandler = new InvocationHandler() {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("我是InvocationHandler,被调用的方法是:" + method.getName());
            return null;
        }
    };

    // 3. 创建代理实例
    IService proxyService = (IService) proxyClass.getConstructor(InvocationHandler.class).newInstance(invocationHandler);

    // 4. 调用代理的方法
    proxyService.m1();
    proxyService.m2();
    proxyService.m3();
}

  第二种方式:步骤如下

1.使用InvocationHandler接口创建代理类的处理器

2.使用Proxy类的静态方法newProxyInstance直接创建代理对象

3.使用代理对象

代码如下:

  

@Test
public void test4() {
    //1.创建代理类的处理器
    InvocationHandler invocationHandler = new InvocationHandler() {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("我是InvocationHandler,被调用的方法是:" +
                    method.getName());
            return null;
        }
    };

    // 2. 创建代理实例
    IService proxyService = (IService) Proxy.newProxyInstance(IService.class.getClassLoader(), new Class[]{IService.class}, invocationHandler);
    // 3. 调用代理的方法
    proxyService.m1();
    proxyService.m2();
    proxyService.m3();
}

这两种效果一样的。

案例:任意接口中的方法耗时统计

  

public class CostTimeInvocationHandler implements InvocationHandler {

    /**
     * 目标类对象
     */
    private Object target;

    public CostTimeInvocationHandler(Object target) {
        this.target = target;
    }

    /**
     * 执行方法
     *
     * @param proxy  proxy
     * @param method method
     * @param args   args
     * @return Object
     * @throws Throwable e
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        long starTime = System.nanoTime();
        Object result = method.invoke(this.target, args);
        long endTime = System.nanoTime();
        System.out.println(this.target.getClass() + ".m1()方法耗时(纳秒):" + (endTime - starTime));
        return result;
    }

    /**
     * 用来创建targetInterface接口的代理对象
     *
     * @param target 需要被代理的对象
     * @param targetInterface 被代理的接口
     * @param <T>
     * @return
     */
    public static <T> T createProxy(Object target, Class<T> targetInterface) {
        if (!targetInterface.isInterface()) {
            throw new IllegalStateException("targetInterface必须是接口类型!");
        } else if (!targetInterface.isAssignableFrom(target.getClass())) {
            throw new IllegalStateException("target必须是targetInterface接口的实现类!");
        }
        return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(), new CostTimeInvocationHandler(target));
    }
}

  测试代码:

  

@Test
public void test5() {

    IService serviceA = CostTimeInvocationHandler.createProxy(new Aservice(), IService.class);
    IService serviceB = CostTimeInvocationHandler.createProxy(new Bservice(), IService.class);

    serviceA.m1();
    serviceA.m2();
    serviceA.m3();
    serviceB.m1();
    serviceB.m2();
    serviceB.m3();
}

我是ServiceA中的m1方法!
class com.hg.代理.Aservice.m1()方法耗时(纳秒):367500
我是ServiceA中的m2方法!
class com.hg.代理.Aservice.m1()方法耗时(纳秒):34800
我是ServiceA中的m3方法!
class com.hg.代理.Aservice.m1()方法耗时(纳秒):28200
我是ServiceB中的m1方法!
class com.hg.代理.Bservice.m1()方法耗时(纳秒):49500
我是ServiceB中的m2方法!
class com.hg.代理.Bservice.m1()方法耗时(纳秒):26700
我是ServiceB中的m3方法!
class com.hg.代理.Bservice.m1()方法耗时(纳秒):23500

可以看到正常运行。当我们需要创建一个新的接口的时候不需要新建一个代理类,只需要使用CostTimeInvocationHandler.createProxy 去创建一个新的代理对象就行了,方便了很多。

Proxy使用的注意事项:

  1.jdk代理只能给接口生成代理类,如果想给某一个类生成代理对象那么就可以考虑使用cglib代理

  2.Proxy类中提供的几个常用的静态方法需要掌握

  3.通过Proxy创建代理对象,当调用代理对象任意方法的时候,会被invocationHandler接口中的invoke方法进行处理,接口是关键。

cglib代理:

   在Java中,cglib是一个强大的代码生成库,它可以用来生成Java类的子类,同时能够拦截被代理类的方法调用并添加额外的功能。

  在cglib中,代理生成的方式分为两种:基于接口的动态代理和基于类的动态代理,其中基于类的动态代理是借助于ASM框架实现的,下面我们来详细了解一下cglib的基于类的动态代理原理。

  cglib基于类的动态代理是通过继承被代理类来实现的,它的代理机制可以分为两步:

    1.生成代理对象的子类: 在这一步中,cglib会在内存中动态生成一个被代理类的子类,同时该子类会重写被代理类中的所有非final方法,并在方法前后插入增强逻辑。

      这里需要注意的是,为了避免出现循环调用,cglib还会将被代理类的非private方法和代理类中重写的方法区分开来,并且调用它们的时候使用不同的方法索引。

    2.代理对象实例化: 在这一步中,cglib会创建代理对象的子类实例,并将被代理对象的引用传递给代理类,从而通过代理类调用被代理类中的方法时,就会自动执行代理类中添加的增强逻辑。

总而言之,cglib生成代理子类的过程中,就是使用ASM框架生成代理类的字节码,并通过字节码重写非final方法的实现,从而添加额外的逻辑增强,完成了对代理对象行为的扩展。与其他代理框架相比,cglib代理的效率更高,并且可以代理没有实现接口的类。同时,由于cglib生成代理类强制覆盖被代理类的所有非final方法,因此cglib代理的约束条件更严格,需要被代理类存在无参构造器才能生成代理子类,这是值得注意的。 

CGLIB案例一:

  1、定义一个普通的类

public class Service1 {
    public void m1() {
        System.out.println("我是m1方法");
    }

    public void m2() {
        System.out.println("我是m2方法");
    }
}

  2、使用代理

@Test
public void test() {
    //使用Enhancer来给某个类创建代理类,步骤
    //1.创建Enhancer对象
    Enhancer enhancer = new Enhancer();
    //2.通过setSuperclass来设置父类型,即需要给哪个类创建代理类
    enhancer.setSuperclass(Service1.class);
    /*3.设置回调,需实现org.springframework.cglib.proxy.Callback接口,
    此处我们使用的是org.springframework.cglib.proxy.MethodInterceptor,也是一个接口,实现了Callback接口,
    当调用代理对象的任何方法的时候,都会被MethodInterceptor接口的invoke方法处理*/
    enhancer.setCallback(new MethodInterceptor() {
        /**
         *  代理对象方法拦截器
         * @param o 代理对象
         * @param method 被代理的类的方法,即Service1中的方法
         * @param objects   调用方法传递的参数
         * @param methodProxy   方法代理对象
         * @return
         * @throws Throwable
         */
        @Override
        public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
            System.out.println("调用方法:" + method);
            //可以调用MethodProxy的invokeSuper调用被代理类的方法
            Object result = methodProxy.invokeSuper(o, objects);
            return result;
        }
    });

    //4.获取代理对象,调用enhancer.create方法获取代理对象,这个方法返回的是Object类型的,所以需要强转一下
    Service1 proxy = (Service1) enhancer.create();

    //5.调用代理对象的方法
    proxy.m1();
    proxy.m2();

}

案例2:

  1、创建普通类对象,但是方法m1中进行调用了m2();

public class Service2 {
    public void m1() {
        System.out.println("我是m1方法");
        this.m2(); //@1
    }

    public void m2() {
        System.out.println("我是m2方法");
    }
}

  2、

@Test
public void test2() {
    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(Service2.class);
    enhancer.setCallback(new MethodInterceptor() {
        @Override
        public Object intercept(Object o, Method method, Object[] objects,
                                MethodProxy methodProxy) throws Throwable {
            System.out.println("调用方法:" + method);
            Object result = methodProxy.invokeSuper(o, objects);
            return result;
        }
    });
    Service2 proxy = (Service2) enhancer.create();
    proxy.m1(); //@1
}

  输出结果:

调用方法:public void com.hg.代理.cglib.Service2.m1()
我是m1方法
调用方法:public void com.hg.代理.cglib.Service2.m2()
我是m2方法

只是调动了m1()方法,但是出现了2次方法调用。