最新要闻
- 最新资讯:微软或提高Win12升级门槛:SSD成为刚需
- 19.48万元起 国产豪华轿车红旗H6预售:双中置排气着实罕见
- 焦点速看:赵长江:腾势D9新增订单一天破500台!别克GL8危险了
- 【世界播资讯】只差价格了 Redmi新机爆料汇总 1999元起交个朋友?
- 环球动态:市场震荡分化 软件行业红利不断
- 全球快消息!2022年度十大科学辟谣榜出炉:O型血更招蚊子是谣言、变电站很危险?
- 男子爬树看邓紫棋演唱会致手臂骨折 省1千花1万:网友直呼追星也要注意安全
- 华海诚科网上发行最终中签率为0.0363%
- 速读:票房破3亿!新海城电影《铃芽之旅》拿下2023年引进片票房冠军
- 新一轮国内油价将于3月底调整:目前分析大概率下调
- 今日播报!头部车企打架尾部遭殃 恒驰汽车北京仅剩一家门店营业
- 【热闻】95后夫妻摆摊日入9千网友让查税慌了 本人回应:当日是偶然 但生意也不错
- 院士:抗流感特效药“达菲”原料为八角茴香、附加值提升1100多倍
- 口碑并入高德:这是要和美团、抖音拼了?
- 速递!我国首次实现固态氢能发电并网!密度提高20倍
- 讯息:镗削
手机
iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?
警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案
- iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?
- 警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案
- 男子被关545天申国赔:获赔18万多 驳回精神抚慰金
- 3天内26名本土感染者,辽宁确诊人数已超安徽
- 广西柳州一男子因纠纷杀害三人后自首
- 洱海坠机4名机组人员被批准为烈士 数千干部群众悼念
家电
Spring源码核心剖析
作者:京东科技 韩国凯
(资料图片)
前言
SpringAOP作为Spring最核心的能力之一,其重要性不言而喻。然后需要知道的是AOP并不只是Spring特有的功能,而是一种思想,一种通用的功能。而SpringAOP只是在AOP的基础上将能力集成到SpringIOC中,使其作为bean的一种,从而我们能够很方便的进行使用。
一、SpringAOP的使用方式
1.1 使用场景
当我们在日常业务开发中,例如有些功能模块是通用的(日志、权限等),或者我们需要在某些功能前后去做一些增强,例如在某些方法执行后发送一条mq消息等。
如果我们将这些通用模块代码与业务代码放在一块,那么每个业务代码都要写这些通用模块,维护成本与耦合情况都十分严重。
因此,我们可以将此模块抽象出来,就有了”切面“的概念。
1.2 常用方式
AOP的使用方式相对比较简单,首先我们需要完成业务代码
@Servicepublic class AopDemo implements AopInterface{ public Student start(String name) { System.out.println("执行业务逻辑代码....."); return new Student(name); }}
业务逻辑比较简单,接收一个name参数。
接下来我们需要创建其对应的切面
//将该切面加入spring容器@Service//声明该类为一个切面@Aspectclass AopAspect { //声明要进行代理的方法 @Pointcut("execution(* com.example.demo.aop.AopInterface.start(..))") public void startAspect() { } //在方法执行之前的逻辑 @Before(value = "startAspect()") public void beforeAspect() { System.out.println("业务逻辑前代码....."); } //在方法执行之后的逻辑 @After(value = "startAspect()") public void afterAspect() { System.out.println("业务逻辑后代码....."); } //围绕方法前后的逻辑 @Around("startAspect()") public Object aroundAspect(ProceedingJoinPoint point) throws Throwable { Object[] requestParams = point.getArgs(); String name = requestParams[0].toString(); System.out.println("传入参数:" + name); requestParams[0] = "bob"; return point.proceed(requestParams); }}
可以看到,首先需要我们指明要代理的对象及方法,然后根据需要选择不同的注解即可实现代理对象。
传入参数:tom业务逻辑前代码.....执行业务逻辑代码.....业务逻辑后代码.....
二、SpringAOP源码解析
2.1 被代理对象的开始initializeBean
根据上面的使用情况,我们知道只需要声明对应的注解即可,不需要其他额外的配置,然后我们获得的bean对象就已经是被代理的了,那么我们可以推断代理对象的过程一定是发生在bean创建的过程的。
我们回顾一下创建bean的流程
实例化bean
装配属性
初始化bean
只有第三步初始化bean的时候才会有机会进行代理。
找到对应的代码位置:
protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) { Object wrappedBean = bean; if (mbd == null || !mbd.isSynthetic()) { //前置处理器 wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName); } //... try { //对象的初始化方法 invokeInitMethods(beanName, wrappedBean, mbd); } if (mbd == null || !mbd.isSynthetic()) { //后置处理器,AOP开始的地方 wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName); } return wrappedBean;}
2.2 后置处理器applyBeanPostProcessorsAfterInitialization
后置处理器会执行那些实现了后置处理器接口的代码:
public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName) throws BeansException { Object result = existingBean; //获取所有的后置处理器 for (BeanPostProcessor processor : getBeanPostProcessors()) { //实现其要执行的方法 Object current = processor.postProcessAfterInitialization(result, beanName); if (current == null) { return result; } result = current; } return result;}
而AOP的后置处理器就是其中的一个:AbstractAutoProxyCreator
其对应的方法为(以下代码不为同一个类,而是对应的执行顺序):
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) { if (bean != null) { Object cacheKey = getCacheKey(bean.getClass(), beanName); if (this.earlyProxyReferences.remove(cacheKey) != bean) { //执行到下面方法 return wrapIfNecessary(bean, beanName, cacheKey); } } return bean;}protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {// Create proxy if we have advice.Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);if (specificInterceptors != DO_NOT_PROXY) {this.advisedBeans.put(cacheKey, Boolean.TRUE); //创建代理对象Object proxy = createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));this.proxyTypes.put(cacheKey, proxy.getClass());return proxy;}this.advisedBeans.put(cacheKey, Boolean.FALSE);return bean;}protected Object createProxy(Class beanClass, @Nullable String beanName,@Nullable Object[] specificInterceptors, TargetSource targetSource) {//获取advisorsAdvisor[] advisors = buildAdvisors(beanName, specificInterceptors);proxyFactory.addAdvisors(advisors);proxyFactory.setTargetSource(targetSource);customizeProxyFactory(proxyFactory);proxyFactory.setFrozen(this.freezeProxy);if (advisorsPreFiltered()) {proxyFactory.setPreFiltered(true);}// Use original ClassLoader if bean class not locally loaded in overriding class loaderClassLoader classLoader = getProxyClassLoader();if (classLoader instanceof SmartClassLoader && classLoader != beanClass.getClassLoader()) {classLoader = ((SmartClassLoader) classLoader).getOriginalClassLoader();} //通过代理工厂创建代理对象return proxyFactory.getProxy(classLoader);}public Object getProxy(@Nullable ClassLoader classLoader) { //首先获取对应的代理return createAopProxy().getProxy(classLoader);}//该方法根据要被代理的类选择使用jdk代理还是cglib代理public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {if (!NativeDetector.inNativeImage() &&(config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config))) {Class targetClass = config.getTargetClass(); //如果被代理的类是一个接口则使用jdk代理if (targetClass.isInterface() || Proxy.isProxyClass(targetClass) || ClassUtils.isLambdaClass(targetClass)) {return new JdkDynamicAopProxy(config);} //否则使用cglib代理return new ObjenesisCglibAopProxy(config);}else { //根据配置选择强制使用jdk代理return new JdkDynamicAopProxy(config);}}
我们知道,代理方式有jdk动态代理与cglib动态代理两种方式,而我们一个bean使用那种代理方式则由上述的方法决定。
至此,我们已经确定了使用那种代理方式获取代理对象。
2.3 获取代理对象
从上文中,我们已经确定了选用何种方式构建代理对象。接下来就是通过不同的方式是如何获取代理对象的。
看懂本章需要实现了解jdk动态代理或者cglib动态代理的方式。
2.3.1 JDK代理
首先在获取代理对象时选择JdkDynamicAopProxy
public Object getProxy(@Nullable ClassLoader classLoader) { if (logger.isTraceEnabled()) { logger.trace("Creating JDK dynamic proxy: " + this.advised.getTargetSource()); } //这里通过反射创建代理对象 return Proxy.newProxyInstance(classLoader, this.proxiedInterfaces, this);}
当被代理对象执行被代理的方法时,会进入到此方法。(jdk动态代理的概念)
JDK通过反射创建对象,效率上来说相对低一些。
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {try {// 获取被代理对象的所有切入点List chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);// 如果调用链路为空说明没有需要执行的切入点,直接执行对应的方法即可if (chain.isEmpty()) {// We can skip creating a MethodInvocation: just invoke the target directly// Note that the final invoker must be an InvokerInterceptor so we know it does// nothing but a reflective operation on the target, and no hot swapping or fancy proxying.Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);}else {// 如果有切入点的话则按照切入点顺序开始执行MethodInvocation invocation =new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);// Proceed to the joinpoint through the interceptor chain.retVal = invocation.proceed();}return retVal;}}
`invocation.proceed();`这个方法就是通过递归的方式执行所有的调用链路。
public Object proceed() throws Throwable { // We start with an index of -1 and increment early. if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) { return invokeJoinpoint(); } Object interceptorOrInterceptionAdvice = this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex); if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) { InterceptorAndDynamicMethodMatcher dm = (InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice; Class targetClass = (this.targetClass != null ? this.targetClass : this.method.getDeclaringClass()); if (dm.methodMatcher.matches(this.method, targetClass, this.arguments)) { return dm.interceptor.invoke(this); } else { // 继续执行 return proceed(); } } else { // 如果调用链路还持续的话,下一个方法仍会调用proceed() return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this); }}
2.3.2 cglib代理
public Object getProxy(@Nullable ClassLoader classLoader) { try { //配置CGLIB Enhancer... Enhancer enhancer = createEnhancer(); if (classLoader != null) { enhancer.setClassLoader(classLoader); if (classLoader instanceof SmartClassLoader && ((SmartClassLoader) classLoader).isClassReloadable(proxySuperClass)) { enhancer.setUseCache(false); } } enhancer.setSuperclass(proxySuperClass); enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised)); enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE); enhancer.setStrategy(new ClassLoaderAwareGeneratorStrategy(classLoader)); //1.获取回调函数,对于代理类上所有方法的调用,都会调用CallBack,而Callback则需要实现intercept()方法 Callback[] callbacks = getCallbacks(rootClass); Class[] types = new Class[callbacks.length]; for (int x = 0; x < types.length; x++) { types[x] = callbacks[x].getClass(); } // fixedInterceptorMap only populated at this point, after getCallbacks call above enhancer.setCallbackFilter(new ProxyCallbackFilter( this.advised.getConfigurationOnlyCopy(), this.fixedInterceptorMap, this.fixedInterceptorOffset)); enhancer.setCallbackTypes(types); //2.创建代理对象 return createProxyClassAndInstance(enhancer, callbacks); } catch (CodeGenerationException | IllegalArgumentException ex) { throw new AopConfigException("Could not generate CGLIB subclass of " + this.advised.getTargetClass() + ": Common causes of this problem include using a final class or a non-visible class", ex); } catch (Throwable ex) { // TargetSource.getTarget() failed throw new AopConfigException("Unexpected AOP exception", ex); }}
可以看到我们在创建代理对象前会先获取代理对象的所有回调函数:
![image-20230310174107202](https://s2.loli.net/2023/03/10/wDRQOpAJEtscZ5q.png)
首先可以看到我们一共有7个回调方法,其中第一个为AOP相关的方法,其他的为spring相关。
在第一个对调对象中持有的 `advised`对象中有 `advisors`属性,就是对应我们的代理类中四个切片,@Before等等。
然后我们看一下 `createProxyClassAndInstance()`都做了什么。
//CglibAopProxy类的创建代理对象方法protected Object createProxyClassAndInstance(Enhancer enhancer, Callback[] callbacks) { enhancer.setInterceptDuringConstruction(false); enhancer.setCallbacks(callbacks); return (this.constructorArgs != null && this.constructorArgTypes != null ? enhancer.create(this.constructorArgTypes, this.constructorArgs) : enhancer.create());}//ObjenesisCglibAopProxy继承了CglibAopProxy类,并覆写了其方法protected Object createProxyClassAndInstance(Enhancer enhancer, Callback[] callbacks) {Class proxyClass = enhancer.createClass();Object proxyInstance = null; //1.尝试使用objenesis创建对象if (objenesis.isWorthTrying()) {try {proxyInstance = objenesis.newInstance(proxyClass, enhancer.getUseCache());}catch (Throwable ex) {logger.debug("Unable to instantiate proxy using Objenesis, " +"falling back to regular proxy construction", ex);}} //2.根据commit的提交记录发现,objenesis有可能创建对象失败,如果失败的话则选用放射的方式创建对象if (proxyInstance == null) {// Regular instantiation via default constructor...try {Constructor ctor = (this.constructorArgs != null ?proxyClass.getDeclaredConstructor(this.constructorArgTypes) :proxyClass.getDeclaredConstructor());ReflectionUtils.makeAccessible(ctor);proxyInstance = (this.constructorArgs != null ?ctor.newInstance(this.constructorArgs) : ctor.newInstance());}catch (Throwable ex) {throw new AopConfigException("Unable to instantiate proxy using Objenesis, " +"and regular proxy instantiation via default constructor fails as well", ex);}} //((Factory) proxyInstance).setCallbacks(callbacks);return proxyInstance;}
2.3.3 cglib
此处有个遇到的问题,当我在debug的时候,发现怎么都进不去 `createProxyClassAndInstance()`,百思不得其解,然后看到IDEA旁边有一个向下的箭头,代表该方法可能其子类被覆写了。然后在其子类处打断点果然发现是其子类的实现。
此处在2.2中也可看到:
![image-20230310174806723](https://s2.loli.net/2023/03/10/g2i7SfVo1nvjJNI.png)
可以看到返回的是其子类的对象,而不是`CglibAopProxy`本身的对象。
关键词:
Spring源码核心剖析
焦点热门:计算机解决高中离子浓度计算
最新资讯:微软或提高Win12升级门槛:SSD成为刚需
19.48万元起 国产豪华轿车红旗H6预售:双中置排气着实罕见
焦点速看:赵长江:腾势D9新增订单一天破500台!别克GL8危险了
【世界播资讯】只差价格了 Redmi新机爆料汇总 1999元起交个朋友?
环球动态:市场震荡分化 软件行业红利不断
【全球独家】你不知道的ubuntu DIY发行版
全球快消息!2022年度十大科学辟谣榜出炉:O型血更招蚊子是谣言、变电站很危险?
男子爬树看邓紫棋演唱会致手臂骨折 省1千花1万:网友直呼追星也要注意安全
华海诚科网上发行最终中签率为0.0363%
专家解读消费基础设施纳入公募REITs试点:推动消费扩容提质 并非简单为房企提供资产处置渠道
天天最新:主板注册制新股开启申购 投资者打新须适应新规则
今日观点!国际金融市场早知道:3月27日
速读:票房破3亿!新海城电影《铃芽之旅》拿下2023年引进片票房冠军
新一轮国内油价将于3月底调整:目前分析大概率下调
今日播报!头部车企打架尾部遭殃 恒驰汽车北京仅剩一家门店营业
读Java性能权威指南(第2版)笔记29_线程和同步性能下
【热闻】95后夫妻摆摊日入9千网友让查税慌了 本人回应:当日是偶然 但生意也不错
【环球新视野】ctf反序列化练题
全球新资讯:win32com操作word 第十五 Find接口的使用
院士:抗流感特效药“达菲”原料为八角茴香、附加值提升1100多倍
口碑并入高德:这是要和美团、抖音拼了?
速递!我国首次实现固态氢能发电并网!密度提高20倍
讯息:镗削
当前短讯!转注是什么_转注是什么意思
【Visual Leak Detector】QT 中 VLD 输出解析(三)
快报:北大"韦神"出难题:没想到 初二学生给出标准答案!ChatGPT被难倒
环球关注:MM32 SPIN MCU 电机 FOC 驱动 风机无传感器弦波驱动篇应用笔记
前端设计模式——路由模式
【全球新视野】Mysql 查询指定节点的所有子节点
当前动态:NVIDIA显卡突然解锁视频编码限制:9年前老卡欢呼雀跃
苹果自动驾驶数据曝光:2个月16起车祸
视觉SLAM中的三角化
计算机专业规划
最资讯丨青春期教育怎么写_青春期教育资料
胖东来回应给员工设超5000元委屈奖:鼓励做正确的事情
环球快资讯:PTA OOP第一次总结性作业
快消息!IO多路复用形象举例
新一代Java高性能构建工具Maven-mvnd【实践可行版】
全球短讯!老人的屋子里怎么总有一股怪怪的味道?小心这四种
最资讯丨AMD锐龙7000平台终于要便宜了!砍掉又贵又没用的PCIe 5.0
看热讯:gomock优化diff展示
即时:Xcode的Search Paths配置
天天速读:漫威超级大反派翻车!《蚁人3》男演员乔纳森梅杰斯被捕:攻击妇女
世界新消息丨深圳一立体车库禁停比亚迪 车主:赤裸裸歧视
环球头条:NAS容量告急 但没空余硬盘位怎么办?群晖教你换上新硬盘
天天视讯!煜邦电力: 关于向不特定对象发行可转换公司债券的审核问询函回复及募集说明书等申请文件更新财务数据的提示性公告
热门:JS 做一个简单的 Parser
【全球新视野】SCO音频采集
骁龙7+处理性能加持:Redmi Note 12 Turbo拍照加速50%
李彦宏:百度文心一言和ChatGPT差距也就一两个月
每日消息!虚幻5再次炸场!1部iPhone搞定3A大作级动作捕捉 游戏行业要变天
【全球快播报】高校回应考生因航班延误错过复试:通知合规 可按流程重新参加
天天视点!中国火箭回收新进展:每年可节省十几亿元
快看点丨yolov5训练自己的数据集
环球关注:有监督学习——决策树、集成学习
每日播报!定时任务的路径问题
视焦点讯!支持Win7运行最后版本:U盘软件Rufus 3.22正式发布
女子家中开氛围灯外面看如同着火 引来消防员场面十分尴尬
信息:003至薄套壁 杰士邦避孕套0.66元/枚狂促
世界热讯:永安林业:副董事长辞职
今日快看!简单部署halo博客
微速讯:Qt源码阅读(二) moveToThread
AES之CryptoJS加密与C#解密
环球观速讯丨肝帝玩家!《暗黑破坏神4》公测玩家达成全职业满级
男子高速奇葩操作:头上竟然套了一个“车顶”
热点评!股票除权日在什么时候
【环球新视野】软件测试--详细判断电话号码
前端设计模式——计算属性模式
世界速递!PLG SaaS 案例:如何实践外链自动增长策略?
需求分析报告(软件项目)
每日播报!Android中常见的线程池
环球观焦点:阿什莉动捕演员庆祝《生化危机4:重制版》发售 身穿黑丝超吸睛
今日精选:网络爽文改编 《赘婿》动画定档4月23日B站开播
全球球精选!加拿大旅游团费_加拿大旅游费用
小米顶级自研技术!Redmi Note 12 Turbo用上小米影像大脑
世界观点:.NET6+Quartz实现定时任务
当前观察:logback的使用和原理
【新视野】GPT已长出眼睛耳朵 周鸿祎:人工智能将会产生意识
环球看热讯:状态观测器(一)
看点:苏州天使母基金合作子基金接连完成投资
曹德旺谈人工智能:能做饭吃吗?如果大家都去研究会饿死
当前看点!南方新一轮强降雨将上线!北方大部气温“狂飙” 最高飙到25度
今日观点!12款随便挑 太平鸟印花T恤清仓:到手69元
全球观热点:苹果WWDC 2023日期曝光!iOS 17首秀来了:或不再支持iPhone X/8
广东突降冰雹 多车被砸毁容!保险给赔吗?
事关出生证明、免税购物、高铁新路线!4月一大波新规来了
精选!AMD新一代APU曝光:大小核架构终于上了
动态:我国首次实现固态氢能发电并网,“绿电”与“绿氢”灵活转换
易烊千玺代言!九号电动两轮车国内出货突破150万辆
转型电动化 韩系车在中国还有救吗?
老人故意推倒摩托车案胜诉 老人儿子需赔1.6万!车主回应
全球关注:读Java性能权威指南(第2版)笔记28_线程和同步性能中
速讯:孤芳自赏是哪一期蒙面唱将
【全球热闻】“韩国宾利”能拿下中国人吗?
焦点观察:票房破40亿中国影史第10!《流浪地球2》4月14日上线咪咕视频
焦点快看:叙利亚外交部谴责美国对叙发动敌对行动
使用 Linux dd 命令测试磁盘读写性能
国际短信平台接口调用的方法步骤,简单5步快速教程