夯实spring(十五):java动态代理与CGLIB代理

spring中用到代理的很多,比如上篇文章中的lookup-method和replaced-method,以及后面要学的aop、spring中的事务、spring中解析@configuration注解等等,这些都是依靠代理来实现的,所以现在来把代理学一下。

1,为什么要用代理

看一个案例

public interface Service {
    void m1();
    void m2();
}
public class Service1 implements Service {
    @Override
    public void m1() {
        System.out.println("我是Service1中的m1方法!");
    }
    @Override
    public void m2() {
        System.out.println("我是Service1中的m2方法!");
    }
}
public class Service2 implements Service{
    @Override
    public void m1() {
        System.out.println("我是Service2中的m1方法!");
    }
    @Override
    public void m2() {
        System.out.println("我是Service2中的m2方法!");
    }

}

测试输出

public class Main {
    public static void main(String[] args) {
        Service1 service1 = new Service1();
        service1.m1();
        service1.m2();

        Service2 service2 = new Service2();
        service2.m1();
        service2.m2();
        
    }
}
我是Service1中的m1方法!
我是Service1中的m2方法!
我是Service2中的m1方法!
我是Service2中的m2方法!

上面的代码很简单。现在我们需要调用IService接口中的任何方法的时候,需要记录方法的耗时

Service接口有2个实现类Service1和Service2,我们可以在这两个类的所有方法中加上统计耗时的代码,但是如果Service接口有几十个实现,是不是要修改很多代码,所有被修改的方法需重新测试?是不是非常麻烦,不过上面这种修改代码的方式倒是可以解决问题,只是增加了很多工作量(编码 & 测试)

但是突然有一天需求又改了。 此时是不是又要去一个修改上面的代码?又要去测试?此时的系统是难以维护。
还有假如上面这些类都是第三方以jar包的方式提供给我们的,此时这些类都是class文件,此时我们无法去修改源码。

我们可以为Service接口创建一个代理类,通过这个代理类来间接访问IService接口的实现类,在这个代理类中去做耗时及或其他需求的代码。

public class ServiceProxy implements Service{
    //被代理的对象
    private Service target;

    public ServiceProxy(Service target) {
        this.target = 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.m2();
        long endTime = System.nanoTime();
        System.out.println(this.target.getClass() + ".m1()方法耗时(纳秒):" + (endTime - starTime));
    }
}

此时测试输出

public class Main {
    public static void main(String[] args) {
        Service1 service1 = new Service1();
        Service2 service2 = new Service2();
        Service proxy1 = new ServiceProxy(service1);
        Service proxy2 = new ServiceProxy(service2);
        
        proxy1.m1();
        proxy1.m2();
        
        proxy2.m1();
        proxy2.m2();

    }
}
我是Service1中的m1方法!
class com.chen.netty.ss.Service1.m1()方法耗时(纳秒):284900
我是Service1中的m2方法!
class com.chen.netty.ss.Service1.m1()方法耗时(纳秒):43200
我是Service2中的m1方法!
class com.chen.netty.ss.Service2.m1()方法耗时(纳秒):31000
我是Service2中的m2方法!
class com.chen.netty.ss.Service2.m1()方法耗时(纳秒):20000

上面实现中我们没有去修改Service1和Service2中的方法,只是给Service接口创建了一个代理类,通过代理类去访问目标对象,需要添加的一些共有的功能都放在代理中,当有其他需求的时候,我们只需修改ServiceProxy的代码,方便系统的扩展和测试。

假如现在我们需要给系统中所有接口都加上统计耗时的功能,若按照上面的方式,我们需要给每个接口创建一个代理类,此时代码量和测试的工作量也是巨大的,那么我们可以写一个通用的代理类。

2,jdk动态代理详解

jdk中为实现代理提供了支持,主要用到2个类:

java.lang.reflect.Proxy
java.lang.reflect.InvocationHandler

jdk自带的代理使用有个限制,只能为接口创建代理类,如果需要给具体的类创建代理类,需要用cglib

使用方式:

public class JdkProxy {
    public static void main(String[] args) {
        // 1. 创建代理类的处理器,执行被代理类的方法都会经过这个方法
        InvocationHandler proxyHandler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("InvocationHandler#invoke:"+method.getName());
                return null;
            }
        };

        // 2. 创建代理实例,注意
        Service proxyService = (Service) Proxy.newProxyInstance(Service.class.getClassLoader(), new Class[]{Service.class}, proxyHandler);

        //3. 调用代理的方法
        proxyService.m1();
        proxyService.m2();

    }
}
InvocationHandler#invoke:m1
InvocationHandler#invoke:m2

	//newProxyInstance参数说明
	ClassLoader loader  类加载器
	Class<?>[] interfaces  需要代理的接口数组,你需要给哪些接口创建代理
	InvocationHandler h  代理处理器,即你需要怎么实现代理
    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)

现在为上面的使用jdk动态代理创建代理

public class computeTimeInvocationHandler implements InvocationHandler {

    private Object target;

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

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

    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 computeTimeInvocationHandler(target));
    }
}
public class Main {
    public static void main(String[] args) {
        Service1 service1 = new Service1();
        Service proxy = computeTimeInvocationHandler.createProxy(service1, Service.class);
        proxy.m1();
        proxy.m2();

    }
}

我是Service1中的m1方法!
class com.chen.netty.ss.Service1m1方法耗时(纳秒):191900
我是Service1中的m2方法!
class com.chen.netty.ss.Service1m2方法耗时(纳秒):30900

当我们再为其他接口,也需要统计耗时的功能,我们无需去创建新的代理类即可实现同样的功能
如:为Service2创建

public class Main {
    public static void main(String[] args) {
        Service2 service2 = new Service2();
        Service proxy = computeTimeInvocationHandler.createProxy(service2, Service.class);
        proxy.m1();
        proxy.m2();

    }
}
我是Service2中的m1方法!
class com.chen.netty.ss.Service2m1方法耗时(纳秒):158300
我是Service2中的m2方法!
class com.chen.netty.ss.Service2m2方法耗时(纳秒):31100

小结:

  • jdk中的Proxy只能为接口生成代理类,如果你想给某个类创建代理类,那么Proxy是不可以的,此时需要用到cglib了。
  • 通过Proxy创建代理对象,当调用代理对象任意方法时候,会被InvocationHandler接口中的invoke方法进行处理,这个接口方法内容是最重要的。

3,cglib代理

jdk动态代理只能为接口创建代理,使用上有局限性。实际的场景中我们的类不一定有接口,此时如果我们想为普通的类也实现代理功能,我们就需要用到cglib来实现了。

cglib是一个强大、高性能的字节码生成库,它用于在运行时扩展Java类和实现接口;本质上它是通过动态的生成一个子类去覆盖所要代理的类(非final修饰的类和方法)。Enhancer可能是CGLIB中最常用的一个类,和jdk中的Proxy不同的是,Enhancer既能够代理普通的class,也能够代理接口。Enhancer创建一个被代理对象的子类并且拦截所有的方法调用(包括从Object中继承的toString和hashCode方法)。Enhancer不能够拦截final方法,例如Object.getClass()方法,这是由于Java final方法语义决定的。同样,Enhancer也不能对final类进行代理操作。

spring已将第三方cglib jar包中所有的类集成到springjar包中,我们可以在spring依赖中直接使用。

使用:

public interface Service {
    void m1();
    void m2();
}
public class CglibMain {
    public static void main(String[] args) {
        //使用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);
                //Object result = method.invoke(o,objects);
                return result;
            }
        });
        //4.获取代理对象,调用enhancer.create方法获取代理对象,这个方法返回的是Object类型的,所以需要强转一下
        Service1 proxy = (Service1) enhancer.create();
        //5.调用代理对象的方法
        proxy.m1();
        proxy.m2();
    }
}

Object result = methodProxy.invokeSuper(o,objects);可以调用被代理类,也就是Service1类中的具体的方法,意思可以看出是调用父类,实际对某个类创建代理,cglib底层通过修改字节码的方式为Service1类创建了一个子类。

调用方法:public void com.chen.netty.ss.Service1.m1()
我是Service1中的m1方法!
调用方法:public void com.chen.netty.ss.Service1.m2()
我是Service1中的m2方法!

从输出中可以看出Service1中的2个方法都被MethodInterceptor中的invoke拦截处理了。

总结:

  • Java动态代理只能够对接口进行代理,不能对普通的类进行代理(因为所有生成的代理类的父类为Proxy,Java类继承机制不允许多重继承);CGLIB能够代理普通类;
  • Java动态代理使用Java原生的反射API进行操作,在生成类上比较高效;CGLIB使用ASM框架直接对字节码进行操作,在类的执行过程中比较高效
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值