- 👏作者简介:大家好,我是爱敲代码的小黄,独角兽企业的Java开发工程师,CSDN博客专家,阿里云专家博主
- 📕系列专栏:Java设计模式、数据结构和算法、Kafka从入门到成神、Kafka从成神到升仙、Spring从成神到升仙系列
- 🔥如果感觉博主的文章还不错的话,请👍三连支持👍一下博主哦
- 🍂博主正在努力完成2023计划中:以梦为马,扬帆起航,2023追梦人
- 📝联系方式:hls1793929520,和大家一起学习,一起进步👀

文章目录
在 Spring 中,最重要的应该当属 IOC 和 AOP 了,IOC 的源码流程还比较简单,但 AOP 的流程就较为抽象了。
其中,AOP 中代理模式的重要性不言而喻,但对于没了解过代理模式的人来说,痛苦至极
于是,我就去看了动态代理的实现,发现网上大多数文章讲的都是不清不楚,甚至讲了和没讲似的,让我极其难受
本着咱们方向主打的就是源码,直接从从源码角度讲述一下 代理模式
兄弟们系好安全带,准备发车!
注意:本文篇幅较长,请留出较长时间来阅读
代理模式的定义:由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。
举个生活中常见的例子:客户想买房,房东有很多房,提供卖房服务,但房东不会带客户看房,于是客户通过中介买房。

这时候对于房东来说,不直接和客户沟通,而是交于中介进行代理
对于中介来说,她也会在原有的基础上收取一定的中介费
我们创建 Landlord 接口如下:
public interface Landlord {
// 出租房子
void apartmentToRent();
}
创建其实现类 HangZhouLandlord 代表杭州房东出租房子
public class HangZhouLandlord implements Landlord {
@Override
public void apartmentToRent() {
System.out.println("杭州房东出租房子");
}
}
创建代理类 LandlordProxy,代表中介服务
public class LandlordProxy {
public Landlord landlord;
public LandlordProxy(Landlord landlord) {
this.landlord = landlord;
}
public void apartmentToRent() {
apartmentToRentBefore();
landlord.apartmentToRent();
apartmentToRentAfter();
}
public void apartmentToRentBefore() {
System.out.println("出租房前,收取中介费");
}
public void apartmentToRentAfter() {
System.out.println("出租房后,签订合同");
}
}
创建最终测试:
public class JavaMain {
public static void main(String[] args) {
Landlord landlord = new HangZhouLandlord();
LandlordProxy proxy = new LandlordProxy(landlord);
// 从中介进行租房
proxy.apartmentToRent();
}
}
得出最终结果:
出租房前,收取中介费
杭州房东出租房子
出租房后,签订合同
通过上述 demo 我们大概了解代理模式是怎么一回事
动态代理利用了JDK API,动态地在内存中构建代理对象,从而实现对目标对象的代理功能,动态代理又被称为JDK代理或接口代理。
静态代理与动态代理的区别:
class 文件class 文件,而是在运行时动态生成类字节码,并加载到 JVM 中代码如下:
public class ProxyFactory {
// 目标方法
public Object target;
public ProxyFactory(Object target) {
this.target = target;
}
public Object getProxyInstance() {
return Proxy.newProxyInstance(
// 目标对象的类加载器
target.getClass().getClassLoader(),
// 目标对象的接口类型
target.getClass().getInterfaces(),
// 事件处理器
new InvocationHandler() {
/**
*
* @param proxy 代理对象
* @param method 代理对象调用的方法
* @param args 代理对象调用方法时实际的参数
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("我是前置增强");
method.invoke(target, args);
System.out.println("我是后置增强");
return null;
}
}
);
}
}
我们测试一下:
public class JavaMain {
public static void main(String[] args) {
Landlord landlord = new HangZhouLandlord();
System.out.println(landlord.getClass());
Landlord proxy = (Landlord) new ProxyFactory(landlord).getProxyInstance();
proxy.apartmentToRent();
System.out.println(proxy.getClass());
while (true){}
}
}
得出结果:
class com.company.proxy.HangZhouLandlord
我是前置增强
杭州房东出租房子
我是后置增强
class com.sun.proxy.$Proxy0
这里可能有小伙伴已经懵了,接着往后看
Java虚拟机类加载过程主要分为五个阶段:加载、验证、准备、解析、初始化。其中加载阶段需要完成以下3件事情:
java.lang.Class 对象,作为方法区这个类的各种数据访问入口由于虚拟机规范对这3点要求并不具体,所以实际的实现是非常灵活的,关于第1点,获取类的二进制字节流(class字节码)就有很多途径:

从本地获取
从网络中获取
运行时计算生成,这种场景使用最多的是动态代理技术,在 java.lang.reflect.Proxy 类中,就是用了 ProxyGenerator.generateProxyClass 来为特定接口生成形式为 *$Proxy 的代理类的二进制字节流

所以,动态代理就是想办法,根据接口或目标对象,计算出代理类的字节码,然后再加载到 JVM 中使用
所以,我们可以得出一个结论:我们上面的 $Proxy0 实际上是 JVM 在编译时期加载出来的类,由于这个类是编译时期加载的,所以我们没办法在 IDEA 里面看到。
可能一般的文章,到这里基本就结束了,让大家知道 $Proxy0是由 JVM 编译时期加载出来的类
但大家都知道,小黄的文章主打的就是一个硬核、源码级。所以,我们直接去看 $Proxy0 的源代码
首先,我们需要下载一个 arthas 的产品,网址:https://arthas.aliyun.com/doc/,跟随流程解压即可。
Arthas 是一款线上监控诊断产品,通过全局视角实时查看应用 load、内存、gc、线程的状态信息,并能在不修改应用代码的情况下,对业务问题进行诊断,包括查看方法调用的出入参、异常,监测方法执行耗时,类加载信息等,大大提升线上问题排查效率。
当我们一切准备完成后,启动我们上面动态代理的测试 JavaMain 类
启动完成后,进入我们的 arthas 页面,执行命令:java -jar arthas-boot.jar

我们可以看到,我们的目标类 com.company.proxy.JavaMain 就出现了,随后我们按下 4,进入到我们的监控页面。

随后使用 jad com.sun.proxy.$Proxy0 之后,可以看到我们已经解析出来 $Proxy0 的源码了

我们将其复制到下面,并删减一些不必要的信息。
public final class $Proxy0 extends Proxy implements Landlord {
private static Method m3;
// $Proxy0 类的构造方法
// 参数为 invocationHandler
public $Proxy0(InvocationHandler invocationHandler) {
super(invocationHandler);
}
static {
m3 = Class.forName("com.company.proxy.Landlord").getMethod("apartmentToRent", new Class[0]);
}
public final void apartmentToRent() {
this.h.invoke(this, m3, null);
return;
}
}
我们先看其有参构造方法,可以看到 $Proxy0 的构造方法入参为 InvocationHandler,有没有感觉似曾相识。
如果你这里忘掉了,不妨去看一下动态代理的 ProxyFactory 的代码,可以发现,我们 Proxy.newProxyInstance() 的第三个自定义的参数,也正是我们的 InvocationHandler。
我们猜测一下,如果这里的传的 InvocationHandler 是我们之前自定义的 InvocationHandler
那么,如果我调用 $Proxy0.apartmentToRent() 是不是就是执行下面的代码:
public final void apartmentToRent() {
this.h.invoke(this, m3, null);
return;
}
// 这里的h.invoke执行的是我们这里自定义的方法,然后进行的前后增强
public Object getProxyInstance() {
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("我是前置增强");
method.invoke(target, args);
System.out.println("我是后置增强");
return null;
}
}
);
如果说我们这个猜测是正确的话,那么会得出这样的几个结论:
Landlord 的接口,然后重写了 Landlord 接口中的 apartmentToRent 方法apartmentToRent() 方法时,实际上是调用的我们自定义的 new InvocationHandler() 类里面的 invoke 方法
还有我们的最后一步,也就是证明 $Proxy0 的构造入参 InvocationHandler 就是我们自定义的 InvocationHandler,废话不多说,直接来看代理的源码。
return Proxy.newProxyInstance(ClassLoader,Interfaces,new InvocationHandler() {});
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h){
// cl = class com.sun.proxy.$Proxy0
Class<?> cl = getProxyClass0(loader, intfs);
// cons = public com.sun.proxy.$Proxy0(java.lang.reflect.InvocationHandler)
final Constructor<?> cons = cl.getConstructor(constructorParams);
// 根据构造参数实例化对象
return cons.newInstance(new Object[]{h});
}
我们通过源码可以看到,一共分为三步(下面为反射的内容,如不熟悉可提前学习下反射):
$Proxy0 的 ClassClass 拿到其构造方法这就确定了我们上述的猜想是正确的。
cglib (Code Generation Library ) 是一个第三方代码生成类库,运行时在内存中动态生成一个子类对象从而实现对目标对象功能的扩展。cglib 为没有实现接口的类提供代理,为 JDK 的动态代理提供了很好的补充。

ASM 是操作字节码的工具cglib 基于 ASM 字节码工具操作字节码(即动态生成代理,对方法进行增强)SpringAOP 基于 cglib 进行封装,实现 cglib 方式的动态代理使用 cglib 需要引入 cglib 的jar包,如果你已经有 spring-core 的jar包,则无需引入,因为 spring 中包含了cglib 。
cglib 的Maven坐标
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.5</version>
</dependency>
还是同样的配方,我们要创建一个需要代理的类(UserServiceImpl),但不需要实现任何的接口,因为我们的 cglib 是根据类来进行创建的。
UserServiceImpl
public class UserServiceImpl {
// 查询功能
List<String> findUserList() {
return Collections.singletonList("小A");
}
}
实现 cglib 的工厂类:UserLogProxy
public class UserLogProxy implements MethodInterceptor {
/**
* 生成 CGLIB 动态代理类方法
*
* @param target
* @return
*/
public Object getLogProxy(Object target) {
// 增强器类,用来创建动态代理类
Enhancer enhancer = new Enhancer();
// 设置代理类的父类字节码对象
enhancer.setSuperclass(target.getClass());
// 设置回调
enhancer.setCallback(this);
// 创建动态代理对象并返回
return enhancer.create();
}
/**
* @param o 代理对象
* @param method 目标对象中的方法的Method实例
* @param objects 实际参数
* @param methodProxy 代理类对象中的方法的Method实例
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("前置输出");
Object result = methodProxy.invokeSuper(o, objects);
return result;
}
}
测试程序:JavaMainTest
public class JavaMainTest {
public static void main(String[] args) {
// 目标对象
UserServiceImpl userService = new UserServiceImpl();
System.out.println(userService.getClass());
// 代理对象
UserServiceImpl proxy = (UserServiceImpl) new UserLogProxy().getLogProxy(userService);
System.out.println(proxy.getClass());
List<String> list = proxy.findUserList();
System.out.println("用户信息:" + list);
while (true) {
}
}
}
结果:
class com.study.spring.proxy.UserServiceImpl
class com.study.spring.proxy.UserServiceImpl$$EnhancerByCGLIB$$cd9788d
前置输出
用户信息:[小A]
按照上述我们分析 $Proxy0 的方法,将 com.study.spring.proxy.UserServiceImpl$$EnhancerByCGLIB$$cd9788d 取出,得到如下:
public class UserServiceImpl$$EnhancerByCGLIB$$cd9788d extends UserServiceImpl implements Factory {
final List findUserList() {
// 是否设置了回调
MethodInterceptor methodInterceptor = this.CGLIB$CALLBACK_0;
if (methodInterceptor == null) {
UserServiceImpl$$EnhancerByCGLIB$$cd9788d.CGLIB$BIND_CALLBACKS(this);
methodInterceptor = this.CGLIB$CALLBACK_0;
}
// 设置回调,需要调用 intercept 方法
if (methodInterceptor != null) {
return (List) methodInterceptor.intercept(this, CGLIB$findUserList$0$Method, CGLIB$emptyArgs, CGLIB$findUserList$0$Proxy);
}
// 无回调,调用父类的 findUserList 即可
return super.findUserList();
}
final List CGLIB$findUserList$0() {
return super.findUserList();
}
}
博主先把整个流程图放到下面,然后结合流程图来进行讲解:

JVM 编译期间,我们的 Enhancer 会根据目标类的信息去动态的生成 动态代理类并设置 回调findUserList() 方法时,有两个执行选项
UserLogProxy 中的 intercept ,然后通过 FastClass 类调用动态代理类,执行CGLIB$findUserList$0 方法,调用父类的 findUserList() 方法findUserList() 方法jdk 代理和 CGLIB 代理
使用 CGLib 实现动态代理,CGLib 底层采用 ASM 字节码生成框架,使用字节码技术生成代理类,在JDK1.6 之前比使用 Java 反射效率要高。唯一需要注意的是,CGLib 不能对声明为 final 的类或者方法进行代理,因为 CGLib 原理是动态生成被代理类的子类。
在 JDK1.6、JDK1.7、JDK1.8 逐步对 JDK 动态代理优化之后,在调用次数较少的情况下,JDK 代理效率高于 CGLib 代理效率,只有当进行大量调用的时候,JDK1.6 和 JDK1.7 比 CGLib 代理效率低一点,但是到 JDK1.8 的时候,JDK 代理效率高于 CGLib 代理。所以如果有接口使用 JDK 动态代理,如果没有接口使用 CGLIB 代理。
动态代理和静态代理
优点:
缺点:
功能增强
远程(Remote)代理
防火墙(Firewall)代理
保护(Protect or Access)代理
终于写完了这篇文章,动态代理在我看 AOP 源码时,就感觉挺抽象的
我感觉最大的原因应该在于:代理类动态生成,无法查看,导致对其模糊,从而陷入不理解
但通过这篇文章,我相信,99% 的人应该都可以理解了动态代理模式的来龙去脉
当然,好刀要用在刀刃上,在面试中,若面试官提及 设计模式、动态代理、Spring、Dubbo 都可以引出动态代理,基本这篇文章无差别秒杀
如果你能看到这,那博主必须要给你一个大大的鼓励,谢谢你的支持!
喜欢的可以点个关注,后续会更新 Spring 源码系列文章
我是爱敲代码的小黄,独角兽企业的Java开发工程师,CSDN博客专家,Java领域新星创作者,喜欢后端架构和中间件源码。
我们下期再见。
我正在使用Ruby2.1.1和Rails4.1.0.rc1。当执行railsc时,它被锁定了。使用Ctrl-C停止,我得到以下错误日志:~/.rvm/gems/ruby-2.1.1/gems/spring-1.1.2/lib/spring/client/run.rb:47:in`gets':Interruptfrom~/.rvm/gems/ruby-2.1.1/gems/spring-1.1.2/lib/spring/client/run.rb:47:in`verify_server_version'from~/.rvm/gems/ruby-2.1.1/gems/spring-1.1.
华为OD机试题本篇题目:明明的随机数题目输入描述输出描述:示例1输入输出说明代码编写思路最近更新的博客华为od2023|什么是华为od,od薪资待遇,od机试题清单华为OD机试真题大全,用Python解华为机试题|机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为o
转自:spring.profiles.active和spring.profiles.include的使用及区别说明下文笔者讲述spring.profiles.active和spring.profiles.include的区别简介说明,如下所示我们都知道,在日常开发中,开发|测试|生产环境都拥有不同的配置信息如:jdbc地址、ip、端口等此时为了避免每次都修改全部信息,我们则可以采用以上的属性处理此类异常spring.profiles.active属性例:配置文件,可使用以下方式定义application-${profile}.properties开发环境配置文件:application-dev
有没有办法在Ruby中动态创建数组?例如,假设我想遍历用户输入的书籍数组:books=gets.chomp用户输入:"TheGreatGatsby,CrimeandPunishment,Dracula,Fahrenheit451,PrideandPrejudice,SenseandSensibility,Slaughterhouse-Five,TheAdventuresofHuckleberryFinn"我把它变成一个数组:books_array=books.split(",")现在,对于用户输入的每一本书,我想用Ruby创建一个数组。伪代码来做到这一点:x=0books_array.
我想在IRB中浏览文件系统并让提示更改以反射(reflect)当前工作目录,但我不知道如何在每个命令后进行提示更新。最终,我想在日常工作中更多地使用IRB,让bash溜走。我在我的.irbrc中试过这个:require'fileutils'includeFileUtilsIRB.conf[:PROMPT][:CUSTOM]={:PROMPT_N=>"\e[1m:\e[m",:PROMPT_I=>"\e[1m#{pwd}>\e[m",:PROMPT_S=>"FOO",:PROMPT_C=>"\e[1m#{pwd}>\e[m",:RETURN=>""}IRB.conf[:PROMPT_MO
我是Ruby的新手。我试过查看在线文档,但没有找到任何有效的方法。我想在以下HTTP请求botget_response()和get()中包含一个用户代理。有人可以指出我正确的方向吗?#PreliminarycheckthatProggitisupcheck=Net::HTTP.get_response(URI.parse(proggit_url))ifcheck.code!="200"puts"ErrorcontactingProggit"returnend#Attempttogetthejsonresponse=Net::HTTP.get(URI.parse(proggit_url)
有人知道如何将capybarapoltergeist的用户代理覆盖到移动用户代理以进行测试吗?我发现了一些有关为seleniumwebdriver配置它的信息:http://blog.plataformatec.com.br/2011/03/configuring-user-agents-with-capybara-selenium-webdriver/这在capybara闹鬼中怎么可能? 最佳答案 请参阅poltergeistgithub页面上的链接:https://github.com/teampoltergeist/polte
我正在使用Ruby/Mechanize编写一个“自动填写表格”应用程序。它几乎可以工作。我可以使用精彩CharlesWeb代理以查看服务器和我的Firefox浏览器之间的交换。现在我想使用Charles查看服务器和我的应用程序之间的交换。Charles在端口8888上代理。假设服务器位于https://my.host.com。.一件不起作用的事情是:@agent||=Mechanize.newdo|agent|agent.set_proxy("my.host.com",8888)end这会导致Net::HTTP::Persistent::Error:...lib/net/http/pe
首先,我使用的是rails3.1.3和来自master的carrierwavegithub仓库的分支。我使用after_init钩子(Hook)来确定基于属性的字段页面模型实例并为这些字段定义属性访问器将值存储在序列化哈希中(希望它清楚我是什么谈论)。这是我正在做的事情的精简版:classPage省略mount_uploader命令让我可以访问我想要的属性。但是当我安装uploader时出现错误消息说“nil类的未定义新方法”我在源代码中读到有方法read_uploader和扩展模块中的write_uploader。我如何必须覆盖这些来制作mount_uploader命令使用我的“虚拟
我希望访问我机器上的所有HTTP流量(我的Windows机器-不是服务器)。据我了解,拥有一个本地代理是所有流量路线的必经之路。我一直在谷歌搜索但未能找到任何资源(关于Ruby)来帮助我。非常感谢任何提示或链接。 最佳答案 WEBrick中有一个HTTP代理(Rubystdlib的一部分)和here's一个实现示例。如果你喜欢生活在边缘,还有em-proxy伊利亚·格里戈里克。这postIlya暗示它似乎确实需要一些调整来解决您的问题。 关于ruby-如何捕获所有HTTP流量(本地代理)