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,可以拉你进技术交流群,大家一起进步。