Spring Bean 名称暗藏玄机,这样取名就不会被代理
一些使用小细节就是在不断的源码探索中逐步发现的,今天就来和小伙伴们聊一下通过 beanName 的设置,可以让一个 bean 拒绝被代理!
1. 代码实践
假设我有如下一个切面:
@Aspect @EnableAspectJAutoProxy @Component public class LogAspect { @Pointcut("execution(* org.javaboy.demo.service.*.*(..))") public void pc() { } @Before("pc()") public void before(JoinPoint jp) { String name = jp.getSignature().getName(); System.out.println(name + " 方法开始执行了..."); } }
这个切面要拦截的方法是 org.javaboy.demo.service
包下的所有类的所有方法,现在,这个包下有一个 BookService 类,内容如下:
@Service("org.javaboy.demo.service.BookService.ORIGINAL") public class BookService { public void hello() { System.out.println("hello bs"); } }
这个 BookService 的 beanName 我没有使用默认的 beanName,而是自己配置了一个 beanName,这个 beanName 的配置方式是 类名的完整路径+.ORIGINAL
。
当我们按照这样的规则给 bean 取名之后,那么即使当前 bean 已经包含在切点所定义的范围内,这个 bean 也不会被代理了。
这是 Spring5.1 开始的新玩法。
这种写法的原理是什么呢?
2. 原理分析
在 Spring 创建 Bean 的时候,小伙伴们都知道,bean 创建完成之后会去各种后置处理器(BeanPostProcessor)中走一圈,所以一般我们认为 BeanPostProcessor 是在 bean 实例创建完成之后执行。但是,BeanPostProcessor 中有一个特例 InstantiationAwareBeanPostProcessor,这个接口继承自 BeanPostProcessor,但是在 BeanPostProcessor 的基础之上,增加了额外的能力:
- 在 bean 实例化之前先做一些预处理,例如直接创建代理对象,代替后续的 bean 生成。
- 在 bean 实例化之后但是属性填充之前,可以自定义一些属性注入策略。
大致上就是这两方面的能力。
具体到代码上,就是在创建 bean 的 createBean 方法中:
@Override protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException { //省略。。。 try { // Give BeanPostProcessors a chance to return a proxy instead of the target bean instance. Object bean = resolveBeforeInstantiation(beanName, mbdToUse); if (bean != null) { return bean; } } catch (Throwable ex) { throw new BeanCreationException(mbdToUse.getResourceDescription(), beanName, "BeanPostProcessor before instantiation of bean failed", ex); } try { Object beanInstance = doCreateBean(beanName, mbdToUse, args); if (logger.isTraceEnabled()) { logger.trace("Finished creating instance of bean '" + beanName + "'"); } return beanInstance; } //省略。。。 }
小伙伴们看,这里的 resolveBeforeInstantiation 方法就是给 BeanPostProcessor 一个返回代理对象的机会,在这个方法中,最终就会触发到 InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation 方法,我们来看看这里涉及到的跟 AOP 相关的 AbstractAutoProxyCreator#postProcessBeforeInstantiation 方法:
@Override public Object postProcessBeforeInstantiation(Class<!--?--> beanClass, String beanName) { Object cacheKey = getCacheKey(beanClass, beanName); if (!StringUtils.hasLength(beanName) || !this.targetSourcedBeans.contains(beanName)) { if (this.advisedBeans.containsKey(cacheKey)) { return null; } if (isInfrastructureClass(beanClass) || shouldSkip(beanClass, beanName)) { this.advisedBeans.put(cacheKey, Boolean.FALSE); return null; } } // Create proxy here if we have a custom TargetSource. // Suppresses unnecessary default instantiation of the target bean: // The TargetSource will handle target instances in a custom fashion. TargetSource targetSource = getCustomTargetSource(beanClass, beanName); if (targetSource != null) { if (StringUtils.hasLength(beanName)) { this.targetSourcedBeans.add(beanName); } Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(beanClass, beanName, targetSource); Object proxy = createProxy(beanClass, beanName, specificInterceptors, targetSource); this.proxyTypes.put(cacheKey, proxy.getClass()); return proxy; } return null; }
这个方法实际上干了两件事:
- 检查当前 bean 是否需要代理,如果不需要代理,那么就存入到一个 map 集合 advisedBeans 中,key 是 bean 的名字,value 如果为 true 则表示这个 bean 是需要代理的,value 为 false,则表示这个 bean 是不需要代理的。
- 如果有我们有自定义的 TargetSource,则根据自定义的 TargetSource 去创建代理对象。
这里我要和大家说的是第一点。
在判断一个 bean 是否需要代理的时候,主要依据两个方法:
- isInfrastructureClass:这个方法主要是检查当前 bean 是否是 Advice/Advisor/Pointcut 等类型,或者这个类上是否有 @Aspect 注解,这个松哥在之前的文章中其实和大家介绍过了:听说 Spring Bean 的创建还有一条捷径?。
- shouldSkip:如果 isInfrastructureClass 方法返回 false,那么就要执行 shouldSkip 了,我们来仔细看下 shouldSkip 方法。
@Override protected boolean shouldSkip(Class<!--?--> beanClass, String beanName) { // TODO: Consider optimization by caching the list of the aspect names List<advisor> candidateAdvisors = findCandidateAdvisors(); for (Advisor advisor : candidateAdvisors) { if (advisor instanceof AspectJPointcutAdvisor pointcutAdvisor && pointcutAdvisor.getAspectName().equals(beanName)) { return true; } } return super.shouldSkip(beanClass, beanName); }
这里首先找到系统中所有的切面,找到之后挨个遍历,遍历的时候判断如果当前要创建的 bean 刚好就是切面,那切面肯定是不需要代理的,直接返回 true。否则就会去调用父类的 shouldSkip 方法,我们再来瞅一眼父类的 shouldSkip 方法:
protected boolean shouldSkip(Class<!--?--> beanClass, String beanName) { return AutoProxyUtils.isOriginalInstance(beanName, beanClass); } static boolean isOriginalInstance(String beanName, Class<!--?--> beanClass) { if (!StringUtils.hasLength(beanName) || beanName.length() != beanClass.getName().length() + AutowireCapableBeanFactory.ORIGINAL_INSTANCE_SUFFIX.length()) { return false; } return (beanName.startsWith(beanClass.getName()) && beanName.endsWith(AutowireCapableBeanFactory.ORIGINAL_INSTANCE_SUFFIX)); }
父类 shouldSkip 方法主要是调用了一个静态工具方法 isOriginalInstance 来判断当前 bean 是否是一个不需要代理的 bean,这个具体的判断逻辑就是检查这个 beanName 是否按照 类名的完整路径+.ORIGINAL
的方式命名的,如果是则返回 true。
当 shouldSkip 方法返回 true 的时候,就会进入到 postProcessBeforeInstantiation 方法的 if 分支中,该分支将当前 beanName 存入到 advisedBeans 集合中,存储的 key 就是 beanName,value 则是 false,然后将方法 return。
当 bean 创建完成之后,再进入到 AbstractAutoProxyCreator#postProcessAfterInitialization 方法中处理的时候,就会发现这个 bean 已经存入到 advisedBeans 集合中,并且 value 是 false,这就意味着这个 bean 不需要代理,那么就针对该 bean 就不会进行 AOP 处理了,直接 return 即可。
好啦,一个小小细节,加深大家对 Spring AOP 的理解,感兴趣的小伙伴可以去试试哦~</advisor>

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
用户案例 | 珍岛集团基于 Apache DolphinScheduler 打造智能营销云平台
珍岛集团致力于打造全球领先的智能营销云平台,在国内率先推出的Marketingforce(营销力)平台,专注于人工智能、大数据、云计算在数字营销及企业数字化智能化领域的创新与实践,面向全球企业提供营销力软件及服务,以一站式智能营销生态助力企业进行数字化转型。 之前,珍岛集团使用完全开源的Apache DolphinScheduler任务调度框架,随着业务的发展,以及数据集成平台和GMA,算法计算平台越来越多的业务需求,开源版本的Apache DolphinScheduler已经不能完全满足需求,迫切地需要对Apache DolphinScheduler做一些定制化的开发。以下是珍岛集团团队最近一年在开源版本的基础上进行的优化和改进。 业务需求 技术方面 1.期待简单易用,低代码的方式; 2.Plug-in足够多,能够符合各业务模块需求; 3.活跃的开源社区,优秀的人才; 4.技术栈能够和珍岛现有各业务模块高度吻合; 5.后期新建业务模块时,不需要过多的二次开发。 业务方面 对调度系统的稳定性要求高; 高并发情况下,任务能够正常执行。 拿一个简单的业务来举例,当用户通过配置设置好受众的特...
- 下一篇
如何避免JavaScript中的内存泄漏?
前言 过去,我们浏览静态网站时无须过多关注内存管理,因为加载新页面时,之前的页面信息会从内存中删除。 然而,随着单页Web应用(SPA)的兴起,应用程序消耗的内存越来越多,这不仅会降低浏览器性能,甚至会导致浏览器卡死。因此,在编码实践中,开发人员需要更加关注与内存相关的内容。因此,小编今天将为大家介绍JavaScript内存泄漏的编程模式,并提供一些内存管理的改进方法。 什么是内存泄漏以及如何发现它? 什么是内存泄漏? JavaScript对象被保存在浏览器内存的堆中,并通过引用方式访问。值得一提的是,JavaScript垃圾回收器则运行于后台,并通过识别无法访问的对象来释放并恢复底层存储空间,从而保证JavaScript引擎的良好运行状态。 当内存中的对象在垃圾回收周期中应该被清理时,若它们被另一个仍然存在于内存中的对象通过一个意外的引用所持有,就会引发内存泄漏问题。这种情况下,冗余对象会继续占据内存空间,导致应用程序消耗过多的内存资源,并可能导致性能下降和表现不佳的情况出现。因此,及时清理无用对象并释放内存资源是至关重要的,以确保应用程序的正常运行和良好的性能表现。 如何发现内存泄...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题
- Eclipse初始化配置,告别卡顿、闪退、编译时间过长
- SpringBoot2全家桶,快速入门学习开发网站教程
- MySQL8.0.19开启GTID主从同步CentOS8
- Docker安装Oracle12C,快速搭建Oracle学习环境
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- CentOS7安装Docker,走上虚拟化容器引擎之路
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- CentOS7编译安装Gcc9.2.0,解决mysql等软件编译问题
- CentOS6,7,8上安装Nginx,支持https2.0的开启