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

摘要:
前言Spring是Java程序员难以避免的框架。其核心思想是IOC和AOP。在Spring中,这两个核心思想基于设计模式实现,IOC思想基于工厂模式实现,AOP思想基于代理模式实现。值得强调的是,singleton范围是Spring中的默认范围。根据经验,原型范围应该用于有状态bean,而单例范围应该用于无状态bean。

前言
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动态代理》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇Java数组去重(利用数组,不借助集合)巧妙使用CSS创建可以打印的页面下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

# AMQP协议 0-9-1 简介

目录 AMQP是什么AMQP 0-9-1 模型简介 交换机和交换机类型 默认交换机 直连交换机 扇型交换机 主题交换机 头交换机 队列 队列名称 队列持久化 绑定 消费者 消息确认 拒绝消息 Negative Acknowledgements 预取消息 消息属性和有效载荷(消息主体) 消息确认 其他 AMQP 0-9-1 方法...

经典的JAVA面试题

Java基础方面: 0、作用域public,private,protected,以及不写时的区别答:区别如下:作用域 当前类 同一package 子孙类 其他packagepublic √        √                  √       √protected √  √                  √        ×friendly...

服务对外提供接口以供不同站点之间使用:Spring Cloud Feign使用记录及携带token请求

  在开发 Spring Cloud 微服务的时候,我们知道,服务之间都是以 HTTP 接口的形式对外提供服务的,因此消费者在进行调用的时候,底层就是通过 HTTP Client 的这种方式进行访问。当然我们可以使用JDK原生的 URLConnection、Apache 的 HTTP Client、Netty 异步 Http Client,Spring 的...

VueBlog

1、数据库建表时: timestamp有两个属性,分别是CURRENT_TIMESTAMP 和ON UPDATE CURRENT_TIMESTAMP两种,使用情况分别如下: 1.CURRENT_TIMESTAMP  当要向数据库执行insert操作时,如果有个timestamp字段属性设为  CURRENT_TIMESTAMP,则无论这个字段有木有set值...

Spring Cloud微服务安全实战_5-6_refresh token失效处理

access_token是客户端调用其他微服务调的凭证,access_token有效期不能太长(丢了风险很大),一般可以设置2小时,如果access_token失效了,就不能调用微服务了,上节说了access_token失效的处理---refresh_token来刷新令牌,refresh_token可以设置很长的有效期,比如一个月 下面是用refresh_...

shiro启用注解方式

shiro验证权限方式一种是基于url配置文件: 例如: <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityMan...