首页 > 编程语言 > 详细

浅谈Spring中JDK动态代理与CGLIB动态代理

时间:2019-05-23 22:07:54      阅读:125      评论:0      收藏:0      [点我收藏+]

前言
Spring是Java程序员基本不可能绕开的一个框架,它的核心思想是IOC(控制反转)和AOP(面向切面编程)。在Spring中这两个核心思想都是基于设计模式实现的,IOC思想的实现基于工厂模式,AOP思想的实现则是基于代理模式。

代理模式:代理类和被代理类实现共同的接口(或继承),代理类中存有指向被代理类的索引,实际执行时通过调用代理类的方法、实际执行的是被代理类的方法。
代理解决的问题当两个类需要通信时,引入第三方代理类,将两个类的关系解耦,让我们只了解代理类即可,而且代理的出现还可以让我们完成与另一个类之间的关系的统一管理,但是切记,代理类和委托类要实现相同的接口,因为代理真正调用的还是委托类的方法。
代理模式常见的实现有两种,静态代理和动态代理。

静态代理与动态代理
静态代理,是编译时增强,AOP 框架会在编译阶段生成 AOP 代理类,在程序运行前代理类的.class文件就已经存在了。常见的实现:JDK静态代理,AspectJ 。
动态代理,是运行时增强,它不修改代理类的字节码,而是在程序运行时,运用反射机制,在内存中临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。常见的实现:JDK、CGLIB、Javassist(Hibernate中的使用动态代理)

不过Spring AOP的实现没有用到静态代理,而是采用了动态代理的方式,有两种,JDK动态代理和CGLIB动态代理。下面简述二者的差异。

Spring中如何判断使用哪种动态代理方式?
version:spring-aop-5.0.7.RELEASE.jar
class:org.springframework.aop.framework.ProxyFactoryBean
类源码上对类的说明:

implementation that builds an AOP proxy based on beans in Spring
翻译一下:
在Spring中基于bean构建AOP代理的实现

这个类就是Spring中对于bean构建AOP代理的实现,跟踪下源码流程,翻译下执行流程:

/**
* Return a proxy. Invoked when clients obtain beans from this factory bean.
* Create an instance of the AOP proxy to be returned by this factory.
* The instance will be cached for a singleton, and create on each call to
* {@code getObject()} for a proxy.
* @return a fresh AOP proxy reflecting the current state of this factory
*/
@Override
@Nullable
public Object getObject() throws BeansException {
//初始化拦截器链
initializeAdvisorChain();
//Spring中有singleton类型和prototype类型这两种不同的Bean
//是否是singleton类型,是,返回singleton类型的代理对象
if (isSingleton()) {
return getSingletonInstance();
}
//否,返回prototype类型的代理对象
else {
if (this.targetName == null) {
logger.warn("Using non-singleton proxies with singleton targets is often undesirable. " +
"Enable prototype proxies by setting the ‘targetName‘ property.");
}
return newPrototypeInstance();
}
}

singleton作用域:当把一个Bean定义设置为singleton作用域是,Spring IoC容器中只会存在一个共享的Bean实例,并且所有对Bean的请求,只要id与该Bean定义相匹配,则只会返回该Bean的同一实例。值得强调的是singleton作用域是Spring中的缺省作用域。
prototype作用域:prototype作用域的Bean会导致在每次对该Bean请求(将其注入到另一个Bean中,或者以程序的方式调用容器的getBean()方法)时都会创建一个新的Bean实例。根据经验,对有状态的Bean应使用prototype作用域,而对无状态的Bean则应该使用singleton作用域。
对于具有prototype作用域的Bean,有一点很重要,即Spring不能对该Bean的整个生命周期负责。具有prototype作用域的Bean创建后交由调用者负责销毁对象回收资源。
简单的说:
singleton只有一个实例,也即是单例模式。
prototype访问一次创建一个实例,相当于new。
由于singleton作用域是Spring中的缺省作用域,则继续追踪getSingletonInstance()方法。

/**
* Return the singleton instance of this class‘s proxy object,
* lazily creating it if it hasn‘t been created already.
* @return the shared singleton proxy
*/
private synchronized Object getSingletonInstance() {
if (this.singletonInstance == null) {
this.targetSource = freshTargetSource();
if (this.autodetectInterfaces && getProxiedInterfaces().length == 0 && !isProxyTargetClass()) {
// Rely on AOP infrastructure to tell us what interfaces to proxy.
Class<?> targetClass = getTargetClass();
if (targetClass == null) {
throw new FactoryBeanNotInitializedException("Cannot determine target class for proxy");
}
setInterfaces(ClassUtils.getAllInterfacesForClass(targetClass, this.proxyClassLoader));
}
// Initialize the shared singleton instance.
super.setFrozen(this.freezeProxy);
//获取代理对象实例
this.singletonInstance = getProxy(createAopProxy());
}
return this.singletonInstance;
}

很明显,核心就在getProxy(createAopProxy())方法中的createAopProxy(),追踪createAopProxy()创建AOP代理实例方法

/**
* Subclasses should call this to get a new AOP proxy. They should <b>not</b>
* create an AOP proxy with {@code this} as an argument.
*/
protected final synchronized AopProxy createAopProxy() {
//active:在创建第一个AOP代理时设置为true
if (!this.active) {
//激活此代理配置。
activate();
}
//this:AdvisedSupport config,AOP形式配置
//获取AOP代理工厂,以指定AOP形式配置创建代理实例
return getAopProxyFactory().createAopProxy(this);
}

很明显,createAopProxy(AdvisedSupport config)就是创建AOP代理实例,不过这里戳进去是接口,Spring中对默认实现类是org.springframework.aop.framework.DefaultAopProxyFactory,看看里面的实现逻辑

@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
Class<?> targetClass = config.getTargetClass();
if (targetClass == null) {
throw new AopConfigException("TargetSource cannot determine target class: " +
"Either an interface or a target is required for proxy creation.");
}
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
return new ObjenesisCglibAopProxy(config);
}
else {
return new JdkDynamicAopProxy(config);
}
}

终于看到JDK和CGLIB的字样了,这个方法决定了是使用JDK动态代理还是CGLIB动态代理。下面对if中的判断逻辑逐个翻译解释

config.isOptimize():是否优化,看到否的逻辑是JDK,就可以知道Spring认为CGLIB动态代理的性能更高点。。。
config.isProxyTargetClass():是否直接代理目标类以及任何接口
hasNoUserSuppliedProxyInterfaces(config):是否没有指定代理接口
targetClass.isInterface():确定指定的对象是否表示接口类型
Proxy.isProxyClass(targetClass):是否是代理类
再看看这个类的说明:

In general, specify {@code proxyTargetClass} to enforce a CGLIB proxy,or specify one or more interfaces to use a JDK dynamic proxy.
谷歌翻译一下
通常,指定{@code proxyTargetClass}来强制执行CGLIB代理,或指定一个或多个接口以使用JDK动态代理

结合类说明和判断逻辑,可以得出结论:

在代理对象不是借口类型或不是代理类时,指定proxyTargetClass=true后,执行CGLIB代理
代理对象是接口类型或是代理类,使用JDK代理
两种动态代理的使用
下方代码是使用JDK动态代理和CGLIB动态代理的示例,这里不探究其底层实现,而是从API的使用比较二者的差异。

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;

import java.lang.reflect.Proxy;

public class ProxyService {

/**
* jdk动态代理
*
* @param object 被代理类对象
* @return 代理实例
*/
public static Object jdkProxyObject(Object object) {
//拦截器
SimpleInterceptor interceptor = new SimpleInterceptor();
return Proxy.newProxyInstance(
object.getClass().getClassLoader(),
object.getClass().getInterfaces(),
(proxy, method, args) -> {
//拦截器 - 前置处理
interceptor.before();
Object result = method.invoke(object, args);
//拦截器 - 后置处理
interceptor.after();
return result;
});
}

/**
* cglib动态代理
*
* @param object 被代理类对象
* @return 代理实例
*/
public static Object cglibProxyObject(Object object) {
//模拟拦截器
SimpleInterceptor interceptor = new SimpleInterceptor();
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(object.getClass());
enhancer.setCallback((MethodInterceptor) (o, method, objects, methodProxy) -> {
//拦截器 - 前置处理
interceptor.before();
Object result = method.invoke(object, objects);
//拦截器 - 后置处理
interceptor.after();
return result;
});
return enhancer.create();
}

}

public class SimpleInterceptor {

public void before() {
System.out.println("-----" + this.getClass().getSimpleName() + "do before" + "-----");
}

public void after() {
System.out.println("-----" + this.getClass().getSimpleName() + "do after" + "-----");
}

}

JDK动态代理

使用JDK动态代理需要使用:
Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
源码方法说明:

Returns an instance of a proxy class for the specified interfaces that dispatches method invocations to the specified invocation handler
谷歌翻译一下:
返回指定接口的代理类的实例,该接口将方法调用分派给指定的调用处理程序。

参数说明:

ClassLoader loader: the class loader to define the proxy class,用于定义代理类的类加载器
Class<?>[] interfaces: the list of interfaces for the proxy class,代理类的接口列表
InvocationHandler h: to implement,由代理实例的调用处理程序实现的接口
根据说明传参,可以估摸出,jdk动态代理的实现原理是实现代理对象的接口生成兄弟类。所以使用jdk动态代理必须满足以下条件:
1. 代理对象必须实现一个或多个接口
2. 返回的代理实例是指定接口的代理类的实例,也就是必须以对象实现的接口接收实例,而不是代理类

CGLIB动态代理

使用Spring cglib动态代理需要使用:
org.springframework.cglib.proxy.Enhancer
由于spring的Sources下载下来并没有Javadoc,没法展示源码上的方法说明。。。
不过从enhancer.setSuperclass(Class superclass) 可以看出cglib代理的特点:

代理对象不能被final修饰,因为cglib代理的实现原理是操作字节码生成代理对象子类,而被final修饰的类不能被继承
因为是子类,所以不必像jdk代理一样必须以对象实现的接口接收实例,代理对象类同样可以接收代理实例
实现原理
JDK动态代理:基于反射,生成实现代理对象接口的匿名类,通过生成代理实例时传递的InvocationHandler处理程序实现方法增强。
CGLIB动态代理:基于操作字节码,通过加载代理对象的类字节码,为代理对象创建一个子类,并在子类中拦截父类方法并织入方法增强逻辑。底层是依靠ASM(开源的java字节码编辑类库)操作字节码实现的。
性能比较
性能比较分为两个部分:生成代理实例性能、代理实例运行性能
由于看到网上有博客提到jdk版本升级会提高动态代理的性能,秉持着实事求是的原则,必须要测试下版本升级后的比较结果,测试的jdk版本为jdk1.8.0_171和jdk-10.0.1。
count数分别定义为100、1000、10000,100000,为避免干扰,方法都是单独执行,每个count执行三次,结果聚合,方便比较。

定义一个简单的service及实现

public interface SimpleService {
void consumer();
}

import java.util.Date;

public class SimpleServiceImpl implements SimpleService {
@Override
public void consumer() {
new Date();
}
}

比较生成代理实例性能
JDK

public class JdkTest {

public static void main(String[] args) {
/*----------jdk----------*/
int count = 100;
long jdkStart = System.currentTimeMillis();
for (int j = 0; j < count; j++) {
SimpleService service = new SimpleServiceImpl();
SimpleService proxy = (SimpleService) ProxyService.jdkProxyObject(service);
}
long jdkEnd = System.currentTimeMillis();

System.out.println("==================================================");
System.out.println("java.version:" + System.getProperty("java.version"));
System.out.println("new count:" + count);
System.out.println("jdk new proxy spend time(ms):" + (jdkEnd - jdkStart));
}

}

CGLIB

public class CglibTest {
public static void main(String[] args) {
/*----------cglib----------*/
int count = 100000;
long cglibStart = System.currentTimeMillis();
for (int j = 0; j < count; j++) {
SimpleService service = new SimpleServiceImpl();
SimpleService proxy = (SimpleService) ProxyService.cglibProxyObject(service);
}
long cglibEnd = System.currentTimeMillis();

System.out.println("==================================================");
System.out.println("java.version:" + System.getProperty("java.version"));
System.out.println("new count:" + count);
System.out.println("cglib new proxy spend time(ms):" + (cglibEnd - cglibStart));
}
}

count分别为100,1000,10000,100000,打印结果聚合:
java.version:1.8.0_161

==================================================
java.version:1.8.0_171
new count:100
jdk new proxy spend time(ms):143
jdk new proxy spend time(ms):164
jdk new proxy spend time(ms):154
cglib new proxy spend time(ms):412
cglib new proxy spend time(ms):452
cglib new proxy spend time(ms):428

==================================================
java.version:1.8.0_171
new count:1000
jdk new proxy spend time(ms):151
jdk new proxy spend time(ms):158
jdk new proxy spend time(ms):172
cglib new proxy spend time(ms):489
cglib new proxy spend time(ms):415
cglib new proxy spend time(ms):431

==================================================
java.version:1.8.0_171
new count:10000
jdk new proxy spend time(ms):214
jdk new proxy spend time(ms):218
jdk new proxy spend time(ms):227
cglib new proxy spend time(ms):468
cglib new proxy spend time(ms):486
cglib new proxy spend time(ms):650

==================================================
java.version:1.8.0_171
new count:100000
jdk new proxy spend time(ms):278
jdk new proxy spend time(ms):299
jdk new proxy spend time(ms):304
cglib new proxy spend time(ms):612
cglib new proxy spend time(ms):632
cglib new proxy spend time(ms):684

java.version:10.0.1

==================================================
java.version:10.0.1
new count:100
jdk new proxy spend time(ms):92
jdk new proxy spend time(ms):93
jdk new proxy spend time(ms):85
cglib new proxy spend time(ms):288
cglib new proxy spend time(ms):330
cglib new proxy spend time(ms):347

==================================================
java.version:10.0.1
new count:1000
jdk new proxy spend time(ms):88
jdk new proxy spend time(ms):94
jdk new proxy spend time(ms):105
cglib new proxy spend time(ms):339
cglib new proxy spend time(ms):306
cglib new proxy spend time(ms):341

==================================================
java.version:10.0.1
new count:10000
jdk new proxy spend time(ms):128
jdk new proxy spend time(ms):132
jdk new proxy spend time(ms):125
cglib new proxy spend time(ms):376
cglib new proxy spend time(ms):409
cglib new proxy spend time(ms):446

==================================================
java.version:10.0.1
new count:100000
jdk new proxy spend time(ms):170
jdk new proxy spend time(ms):220
jdk new proxy spend time(ms):196
cglib new proxy spend time(ms):530
cglib new proxy spend time(ms):555
cglib new proxy spend time(ms):633

对比结果,生成代理实例性能:JDK > CGLIB;JDK版本升级后对动态代理生成实例性能有提升。

比较生成代理实例性能
JDK

public class JdkTest {

public static void main(String[] args) {
/*----------jdk----------*/
int count = 100;
long jdkStart = System.currentTimeMillis();
SimpleService service = new SimpleServiceImpl();
SimpleService proxy = (SimpleService) ProxyService.jdkProxyObject(service);
for (int j = 0; j < count; j++) {
proxy.consumer();
}
long jdkEnd = System.currentTimeMillis();

System.out.println("==================================================");
System.out.println("java.version:" + System.getProperty("java.version"));
System.out.println("new count:" + count);
System.out.println("jdk proxy consumer spend time(ms):" + (jdkEnd - jdkStart));
}

}

CGLIB

public class CglibTest {

public static void main(String[] args) {
/*----------cglib----------*/
int count = 100;
long cglibStart = System.currentTimeMillis();
SimpleService service = new SimpleServiceImpl();
SimpleService proxy = (SimpleService) ProxyService.cglibProxyObject(service);
for (int j = 0; j < count; j++) {
proxy.consumer();
}
long cglibEnd = System.currentTimeMillis();

System.out.println("==================================================");
System.out.println("java.version:" + System.getProperty("java.version"));
System.out.println("new count:" + count);
System.out.println("cglib proxy consumer spend time(ms):" + (cglibEnd - cglibStart));
}
}

count分别为100,1000,10000,100000,打印结果聚合:
java.version:1.8.0_161

==================================================
java.version:1.8.0_171
new count:100
jdk proxy consumer spend time(ms):148
jdk proxy consumer spend time(ms):137
jdk proxy consumer spend time(ms):168
cglib proxy consumer spend time(ms):464
cglib proxy consumer spend time(ms):447
cglib proxy consumer spend time(ms):438

==================================================
java.version:1.8.0_171
new count:1000
jdk proxy consumer spend time(ms):141
jdk proxy consumer spend time(ms):170
jdk proxy consumer spend time(ms):150
cglib proxy consumer spend time(ms):408
cglib proxy consumer spend time(ms):410
cglib proxy consumer spend time(ms):422

==================================================
java.version:1.8.0_171
new count:10000
jdk proxy consumer spend time(ms):179
jdk proxy consumer spend time(ms):173
jdk proxy consumer spend time(ms):165
cglib proxy consumer spend time(ms):416
cglib proxy consumer spend time(ms):463
cglib proxy consumer spend time(ms):415

==================================================
java.version:1.8.0_171
new count:100000
jdk proxy consumer spend time(ms):176
jdk proxy consumer spend time(ms):158
jdk proxy consumer spend time(ms):187
cglib proxy consumer spend time(ms):637
cglib proxy consumer spend time(ms):459
cglib proxy consumer spend time(ms):436

java.version:10.0.1

==================================================
java.version:10.0.1
new count:100
jdk proxy consumer spend time(ms):79
jdk proxy consumer spend time(ms):90
jdk proxy consumer spend time(ms):85
cglib proxy consumer spend time(ms):281
cglib proxy consumer spend time(ms):352
cglib proxy consumer spend time(ms):338

==================================================
java.version:10.0.1
new count:1000
jdk proxy consumer spend time(ms):89
jdk proxy consumer spend time(ms):97
jdk proxy consumer spend time(ms):106
cglib proxy consumer spend time(ms):304
cglib proxy consumer spend time(ms):303
cglib proxy consumer spend time(ms):359

==================================================
java.version:10.0.1
new count:10000
jdk proxy consumer spend time(ms):113
jdk proxy consumer spend time(ms):99
jdk proxy consumer spend time(ms):108
cglib proxy consumer spend time(ms):347
cglib proxy consumer spend time(ms):339
cglib proxy consumer spend time(ms):349

==================================================
java.version:10.0.1
new count:100000
jdk proxy consumer spend time(ms):106
jdk proxy consumer spend time(ms):102
jdk proxy consumer spend time(ms):107
cglib proxy consumer spend time(ms):342
cglib proxy consumer spend time(ms):380
cglib proxy consumer spend time(ms):385

对比结果,代理实例运行性能:JDK > CGLIB;JDK版本升级后对动态代理的代理实例运行性能有提升。
说实话看到对比结果我是震惊的,我之前一直认为CGLIB动态代理的性能应该优于JDK动态代理,通过对比结果,可以看出,动态代理总体性能:JDK > CGLIB,难怪Spring默认的动态代理方式为JDK。

小结
Spring中动态代理选择逻辑:

 

JDK动态代理特点:

代理对象必须实现一个或多个接口
以接口形式接收代理实例,而不是代理类
CGLIB动态代理特点:

代理对象不能被final修饰
以类或接口形式接收代理实例
JDK与CGLIB动态代理的性能比较:

生成代理实例性能:JDK > CGLIB
代理实例运行性能:JDK > CGLIB

转自:https://blog.csdn.net/wangzhihao1994/article/details/80913210

浅谈Spring中JDK动态代理与CGLIB动态代理

原文:https://www.cnblogs.com/xkzhangsanx/p/10914750.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!