代理模式图文详解!静态代理和动态代理cglib 、jdk InvocationHandler接口的原理

1、代理模式概念

代理模式就是为被访问的对象提供一层代理,来控制对这个对象的访问,例子:人们通常不会直接从工厂买商品,而是工厂授权一些代理商。消费者通过代理商来购买商品,代理商可以在商品中间做一些加工、涨价之类的动作。主要应用有在方法前后加日志,效验参数之类的

2、静态代理

// 接口,对外销售的接口
public interface AgencySubject {
    void sell();
}
// 超市类,需要实现销售的接口
public class SuperMarket implements AgencySubject {
	// 被代理的对象
    private MilkFactory milkFactory;
    public SuperMarket(MilkFactory milkFactory) {
        this.milkFactory = milkFactory;
    }
    @Override
    public void sell() {
        System.out.println("加价20元");
        milkFactory.sell();
    }
}
//牛奶工厂类,需要实现销售的接口
public class MilkFactory implements AgencySubject {
    @Override
    public void sell() {
        System.out.println("出售商品,20元");
    }
}
//人类,去超市买东西
public class People  {
    public static void main(String[] args) {
        SuperMarket sm = new SuperMarket(new MilkFactory());
        sm.sell();
    }
}

类图:
在这里插入图片描述
这就是静态代理的实现,用户通过超市买东西,超市在向牛奶工厂进货时,在原来的基础上添加了一些价格。但是超市不能只代理牛奶工厂吧,超市什么都有买。这种静态是每次只能代理一个类,所以在这个基础上又有了动态代理。

3、JDK动态代理

用Java实现动态代理有两种方式,一是继承cglib类,二是实现InvocationHandler接口,首先我们看jdk的动态代理实现,下面是实现的代码,我们会发现编译之后会多出一个$Proxy0.class类,这个类就是真的调用的代理类

public class SuperMarket  implements InvocationHandler {
    /** 被代理的对象      */
    private Object target;
    /** 获取代理后的实例方法*/
    public Object getInstance(Object target){
        this.target = target;
        Class<?> c = target.getClass();
        /** 返回实例化的代理对象,将需要代理的对象target,传入newProxyInstance方法,生成新的代理类$Proxy0.class。
        参数一:ClassLoader类加载器 ,
        参数二:要实现的接口,
        参数三:InvocationHandler:就是当前对象,当前对象实现了InvocationHandler*/
        return Proxy.newProxyInstance(c.getClassLoader(),c.getInterfaces(),this);
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("加价100元");
        Object obj = method.invoke(target,args);
        return obj;
    }
}
public class CookieFactory implements AgencySubject {
    @Override
    public void sell() {
        System.out.println("卖一些饼干100块");
    }
}
public class MilkFactory implements AgencySubject {
    @Override
    public void sell() {
        System.out.println("出售商品,20元");
    }
}
public class People {
    public static void main(String[] args) throws IOException {
        // 先实例化一个对象,将CookieFactory传进去
        AgencySubject as = (AgencySubject) new SuperMarket().getInstance(new CookieFactory());
        as.sell();
         // 在实例化一个对象,将MilkFactory传进去
        AgencySubject as2 = (AgencySubject) new SuperMarket().getInstance(new MilkFactory());
        as2.sell();
    }
}

然后我们看下Proxy.newProxyInstance方法的源码
在这里插入图片描述
其中的getProxyClass0方法,注:class文件中方法、字段等都需要引用CONSTANT_Utf8_info型常量来描述名称,所以CONSTANT_Utf8_info型常量的最大长度即u2类型能表达的最大值,也是Java中方法、字段名的最大长度。u2类型,表示2个字节,16位,能表示的最大值为 2的16次方,也就是65536,有兴趣的可以看下
在这里插入图片描述
然后我们通过动图在看get方法里的实现
在这里插入图片描述
可以看到最终在ProxyClassFactory中调用ProxyGenerator.generateProxyClass方法,由该方法生成的代理类,也就是 $Proxy0.class

在这里插入图片描述
接下来我们打开$Proxy0.class看看,可以发现生成的代理类也实现了我们的代理接口(AgencySubject),并继承了Proxy
在这里插入图片描述
而父类的h属性是在什么时候放进去的呢,我们在debug看看,在上面newProxyInstance方法最后返回的是一个 cons.newInstance(new Object[]{h});在这里插入图片描述

我们进到这个方法里面看看,又看到调用了DelegatingConstructorAccessorImpl的newInstance,通过类名可以看出这是一个委派模式
在这里插入图片描述
debug经过DelegatingConstructorAccessorImpl类又进到了NativeConstructorAccessorImpl中,发现实际调用的就是$proxy0.class的构造方法
在这里插入图片描述

而$proxy0.class的构造方法我们在上面已经截图说过了,调用的就是父类的构造方法,而下图就可以看出将h属性实例化了
在这里插入图片描述
最终的流程就是调用的SuperMarket的invoke方法。jdk动态代理的说明就到这里

4、cglib

说完了jdk的动态代理我们在来说说cglib代理的实现,它是通过动态继承目标对象,不需要实现接口就能动态代理。cglib需要导入依赖包

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>2.2.2</version>
</dependency>

代码

public class SuperMarket implements MethodInterceptor {
    public Object getInstance(Class<?> clazz) throws Exception {
        Enhancer enhancer = new Enhancer();
        // 设置需要继承的类
        enhancer.setSuperclass(clazz);
        // 设置回调方法,也就是本身的intercept
        enhancer.setCallback(this);
        return enhancer.create();
    }
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("cglib加价50块");
        //这里需要调用父类的方法
        Object obj = methodProxy.invokeSuper(o,objects);
        return obj;
    }
}
public class People {
    public static void main(String[] args) throws Exception {
        CookieFactory sm = (CookieFactory) new SuperMarket().getInstance(CookieFactory.class);
        sm.sell();
    }
}

通过运行我们发现cglib会自动生成3个类,
在这里插入图片描述
并且发现调用的是CookieFactory$$EnhancerByCGLIB$$ea5597e8.class
在这里插入图片描述
反编译之后看到代理类继承了CookieFactory,重写了他的方法,并有代理的方法CGLIB$sell$0和被代理的sell方法,所以我们在intercept方法中调用的时候需要调用的是methodProxy.invokeSuper方法,而不是methodProxy.invoke
在这里插入图片描述在这里插入图片描述在这里插入图片描述
然后我们看到MethodProxy类中首先都调用了init方法,并且各自调用的类都不一样
在这里插入图片描述

然后我们在看init的方法,发现它对f1和f2都进行了初始化,而
f1为CookieFactory$$FastClassByCGLIB$$713eb4c2.class,
f2为CookieFactory$$EnhancerByCGLIB$$ea5597e8$$FastClassByCGLIB$$c13d5a11.class
在这里插入图片描述
在invoke方法里面选择调用的是那个方法
在这里插入图片描述

而var10001是7,就调用到了CGLIB$sell$0,上面已经提到过了CGLIB$sell$0方法,调用的是父类的sell,也就是CookieFactory,到此呢,就完成了调用。
在这里插入图片描述
在这里插入图片描述
由此可见,cglib生成了3个类FastClass,说明其生成规则比jdk更复杂,但是可以不用实现接口了。
因为生成类的过程比较复杂,所以生成的效率低下,但是生成的是fastClass,所有执行的效率非常快,比jdk的反射调用效率要高

5、总结

JDK 调用代理方法,实现了被代理对象的接口,是通过反射机制调用,所以调用效率不高,cglib是通过继承实现的动态代理,生成效率低,执行效率高。而在spring中的AOP是怎么选择的呢,当类又实现的接口就使用jdk的,而没有实现接口时就用cglib,可以强制使用cglib,就添加下面的配置

<aop:aspectj-autoproxy proxy-target-class="true"/>

如有问题,可留言,加博主qq:244156219,微信:zuo995518,可以拉你进技术交流群,大家一起进步。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值