Spring 源码分析衍生篇三 : lookup-method 和 replaced-method
文章目录
- 一、前言
- 二、基本使用
- 1. 作用
- 三、原理实现
- 1. 预处理
- 1.1 AbstractBeanDefinition#prepareMethodOverrides
- 1.2 AutowiredAnnotationBeanPostProcessor#determineCandidateConstructors
- 2. 真正处理
一、前言
本文是 Spring源码分析:单例bean的获取 - createBean 的衍生文章。主要是因为本人菜鸡,在分析源码的过程中还有一些其他的内容不理解,故开设衍生篇来完善内容以学习。
二、基本使用
1. 作用
lookup-method
:用于注入方法返回结果,也就是说能通过配置方式替换方法返回结果。(在方法或者抽象方法上使用@Lookup注解,将会根据该方法的返回值,自动在BeanFactory中调用getBean()来注入该Bean)replaced-method
:可以实现方法主体或返回结果的替换
通俗来讲 : lookup-method 可以注入属性bean, replaced-method 替换方法实现。
下面跟着一个Demo来理解
// 基类接口
public interface DemoBase {String hello();
}...
public class DemoA implements DemoBase {public DemoBase getDemoBase() {return new DemoB();}@Overridepublic String hello() {return "demoA hello";}
}
...
public class DemoB implements DemoBase {@Overridepublic String hello() {return "DemoB hello";}
}
...
public class DemoC implements DemoBase{@Overridepublic String hello() {return "DemoC hello";}
}
....public class DemoMethodReplacer implements MethodReplacer {@Overridepublic Object reimplement(Object obj, Method method, Object[] args) throws Throwable {System.out.println("method = " + method);return "reimplement";}
}
我们来配置一下bean.xml 如下
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"><bean id="demoA" name="demoA" class="com.kingfish.springbootdemo.property.DemoA">// 指定 DemoA.getDemoBase 方法,返回值是 容器中的demoC<lookup-method name="getDemoBase" bean="demoC"></lookup-method>// 指定 DemoA.hello方法,其方法经过 demoMethodReplacer.reimplement 代理<replaced-method name="hello" replacer="demoMethodReplacer"></replaced-method></bean><bean id="demoB" name="demoB" class="com.kingfish.springbootdemo.property.DemoB"></bean><bean id="demoC" name="demoC" class="com.kingfish.springbootdemo.property.DemoC"></bean><bean id="demoMethodReplacer" name="demoMethodReplacer" class="com.kingfish.springbootdemo.property.DemoMethodReplacer"></bean>
</beans>
@SpringBootApplication
@ImportResource("bean.xml")
public class SpringbootDemoApplication implements ApplicationRunner {public static void main(String[] args) {ConfigurableApplicationContext run = SpringApplication.run(SpringbootDemoApplication.class, args);DemoA demoA = run.getBean(DemoA.class);System.out.println(demoA.hello());System.out.println(demoA.getDemoBase());}
运行后输出结果如下
根据上面的demo。我们可以看出
lookup-method
: name 指定方法名,bean 指定方法返回的bean。这里注意上面的demo中getDemoBase
方法没有必要实现,可以直接写一个抽象方法。即当调用demoA.getDemoBase
方法时返回的是 Spring 容器中name为demoC 的bean。这里可以通过将demoC 设置成原型模式来实现单例模式下原型bean的获取。replaced-mthod
: name 指定方法名,replacer 指定代理类。即当调用demoA中的hello方法时,会经过demoMethodReplacer
的代理。demoMethodReplacer
需要实现MethodReplacer
接口,调用方法时会经过MethodReplacer.reimplement
方法。
三、原理实现
首先需要知道,lookup-method 和 replaced-method 注解标注的方法都会被保存在BeanDefinition.methodOverrides 集合中。这两个功能的实现原理其实也是在bean实例化的时候如果检测到 methodOverrides 属性存在,则会动态的为当前bean生成代理并使用对应的拦截器为bean做增强处理。
Spring 对 这两个属性的处理 在两个部分
1. 预处理
1.1 AbstractBeanDefinition#prepareMethodOverrides
这个阶段的时候bean还没开始创建,先做了一个预先的工作,减少后面的工作量。
这个方法做了一些预处理的工作
详细代码如下:
public void prepareMethodOverrides() throws BeanDefinitionValidationException {// Check that lookup methods exist and determine their overloaded status.// 判断是否有方法需要重写if (hasMethodOverrides()) {getMethodOverrides().getOverrides().forEach(this::prepareMethodOverride);}}protected void prepareMethodOverride(MethodOverride mo) throws BeanDefinitionValidationException {// 获取对应的类中的对应方法名的个数int count = ClassUtils.getMethodCountForName(getBeanClass(), mo.getMethodName());// 等于0抛出异常if (count == 0) {throw new BeanDefinitionValidationException("Invalid method override: no method with name '" + mo.getMethodName() +"' on class [" + getBeanClassName() + "]");}else if (count == 1) {// Mark override as not overloaded, to avoid the overhead of arg type checking.// 标记 MethodOverride 暂未被覆盖,避免参数类型检查的开销。mo.setOverloaded(false);}}
解释一下上面的逻辑:
- 首先会判断是否有方法需要重写,这里的是根据
RootBeanDefinition
中的methodOverrides
属性来进行判断,为空则表示没有。 - 若上述判断有方法需要覆盖,则会调用
prepareMethodOverride(MethodOverride mo)
方法。而在prepareMethodOverride(MethodOverride mo)
方法中会根据 需要覆盖的方法名称 来获取加载类中关于该方法的实现。如果获取不到count == 0
,则直接抛出异常,如果获取到只有一个count == 1
,则记录该方法并未被重载(因为Spring在方法匹配时,如果一个类中存在若干个重载方法,则在函数调用及增强的时候需要根据参数类型进行匹配,来最终确定调用的方法是哪一个,这里直接设置了该方法并未被重载,在后续方法匹配的时候就不需要进行参数匹配验证,直接调用即可)。 - 打个比方,比如指定覆盖A类中的 a方法,但是A类中可能存在多个a方法或者不存在a方法,若count == 0不 存在a方法,则谈何覆盖,直接抛出异常,若count ==1 则a方法的实现只有一个,标记该方法并未被重载后续可跳过参数验证的步骤。
1.2 AutowiredAnnotationBeanPostProcessor#determineCandidateConstructors
在 这里对 @Lookup 注解 进行了一个简单处理,将被 @Lookup 注解修饰的方法添加到RootBeanDefinition
中的 methodOverrides
属性中
public Constructor<?>[] determineCandidateConstructors(Class<?> beanClass, final String beanName)throws BeanCreationException {// Let's check for lookup methods here...if (!this.lookupMethodsChecked.contains(beanName)) {if (AnnotationUtils.isCandidateClass(beanClass, Lookup.class)) {try {Class<?> targetClass = beanClass;do {ReflectionUtils.doWithLocalMethods(targetClass, method -> {Lookup lookup = method.getAnnotation(Lookup.class);if (lookup != null) {Assert.state(this.beanFactory != null, "No BeanFactory available");LookupOverride override = new LookupOverride(method, lookup.value());try {RootBeanDefinition mbd = (RootBeanDefinition)this.beanFactory.getMergedBeanDefinition(beanName);mbd.getMethodOverrides().addOverride(override);}catch (NoSuchBeanDefinitionException ex) {throw new BeanCreationException(beanName,"Cannot apply @Lookup to beans without corresponding bean definition");}}});targetClass = targetClass.getSuperclass();}while (targetClass != null && targetClass != Object.class);}catch (IllegalStateException ex) {throw new BeanCreationException(beanName, "Lookup method resolution failed", ex);}}this.lookupMethodsChecked.add(beanName);}...}
2. 真正处理
这个阶段bean处于正在创建的过程中,这里是对其他配置的处理,lookup-method,replaced-method就是其中之一。
SimpleInstantiationStrategy#instantiate(RootBeanDefinition, String, BeanFactory)
public Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner) {// Don't override the class with CGLIB if no overrides.// 判断是否有使用lookup-method,replaced-method 来标注方法。如果没有直接反射即可。if (!bd.hasMethodOverrides()) {Constructor<?> constructorToUse;synchronized (bd.constructorArgumentLock) {constructorToUse = (Constructor<?>) bd.resolvedConstructorOrFactoryMethod;if (constructorToUse == null) {final Class<?> clazz = bd.getBeanClass();if (clazz.isInterface()) {throw new BeanInstantiationException(clazz, "Specified class is an interface");}try {if (System.getSecurityManager() != null) {constructorToUse = AccessController.doPrivileged((PrivilegedExceptionAction<Constructor<?>>) clazz::getDeclaredConstructor);}else {constructorToUse = clazz.getDeclaredConstructor();}bd.resolvedConstructorOrFactoryMethod = constructorToUse;}catch (Throwable ex) {throw new BeanInstantiationException(clazz, "No default constructor found", ex);}}}return BeanUtils.instantiateClass(constructorToUse);}else {// Must generate CGLIB subclass.return instantiateWithMethodInjection(bd, beanName, owner);}}
这里简单将一下逻辑,不再详细分析代码。
首先判断当前bean是否有使用 lookup-method,replaced-method 属性,若没有使用,则直接使用反射即可。若使用,则不能简单的使用反射,需要将这两个配置提供的功能切入进去,所以就必须使用动态代理的方式将两个特性所对应的逻辑的拦截增强器设置进去。这样就可以保证在调用方法的时候会被相应的拦截器增强,返回值为包含拦截器的代理实例。
以上:内容部分参考
《Spring源码深度解析》
https://blog.csdn.net/lyc_liyanchao/article/details/82901216
如有侵扰,联系删除。 内容仅用于自我记录学习使用。如有错误,欢迎指正
Spring 源码分析衍生篇三 : lookup-method 和 replaced-method相关推荐
- Spring 源码分析衍生篇十 :Last-Modified 缓存机制
文章目录 一.前言 二.Last-Modify 三.实现方案 1. 实现 org.springframework.web.servlet.mvc.LastModified接口 1.1. 简单演示 1. ...
- Spring 源码分析衍生篇十三 :事务扩展机制 TransactionSynchronization
文章目录 一.前言 二.TransactionSynchronization 1. TransactionSynchronization 1.1 TransactionSynchronization ...
- Spring源码分析前篇
穷举法:把生活所见所闻全部归纳到我们所学的知识体系中来,加以思考总结变成自己的东西.(举例子) 类比法:用自己熟悉的方法(利用自己已有的知识体系),去对比学习新的知识. 学习最好的方法:就是重复 Sp ...
- spring源码分析之spring-core总结篇
1.spring-core概览 spring-core是spring框架的基石,它为spring框架提供了基础的支持. spring-core从源码上看,分为6个package,分别是asm,cgli ...
- Spring 源码分析(三) —— AOP(五)创建代理
2019独角兽企业重金招聘Python工程师标准>>> 创建代理 代理的定义其实非常简单,就是改变原来目标对象方法调用的运行轨迹.这种改变,首先会对这些方法进行拦截,从而为这些方法提 ...
- Spring源码分析系列——bean创建过程分析(三)——工厂方法创建bean
前言 spring创建bean的方式 测试代码准备 createBeanInstance()方法分析 instantiateUsingFactoryMethod()方法分析 总结 spring创建be ...
- Spring源码分析(三)
Spring源码分析 第三章 手写Ioc和Aop 文章目录 Spring源码分析 前言 一.模拟业务场景 (一) 功能介绍 (二) 关键功能代码 (三) 问题分析 二.使用ioc和aop重构 (一) ...
- spring源码分析之BeanDefinition相关
目录 前言: BeanDefinition的家族系列 1.BeanDefintion的UML类图 2.BeanDefintion家族类详解 2.1.通用接口 2.2.BeanDefintion接口 2 ...
- Spring 源码分析 (一)——迈向 Spring 之路
一切都是从 Bean 开始的 在 1996 年,Java 还只是一个新兴的.初出茅庐的编程语言.人们之所以关注她仅仅是因为,可以使用 Java 的 Applet 来开发 Web 应用.但这些开发者很快 ...
最新文章
- 神经网络中,设计loss function有哪些技巧?
- 网络带宽由什么决定_加工中心价格分析,加工中心价格是由什么决定的呢?
- python笔记总结_python学习笔记总结(1)
- 日本钢铁业排名(2014/11/5)
- 算法学习之路|打印排名
- php util.js,javascript中一些util方法汇总_javascript技巧
- slot多作用域 vue_vue插槽(slot)详解
- Paddle实现NLP-文本分类
- 怎么估算空间利用率?新研发传感器分分钟搞定!
- 关于oracle的物理dg,单机上创建物理DG(Oracle 10g单实例)
- 三菱PLC-信捷人机通信(编程)
- 【到世界的尽头win7主题】
- 椭圆曲线数字签名算法
- React-Native强制关闭软键盘
- 罗尔定理、拉格朗日中值定理和柯西中值定理和用他们证明不等式、
- Segmentation fault (core dumped) 和double free or corruption (out)Aborted (core dumped)
- 清醒看自己,糊涂看别人
- js关闭当前页面 并 刷新主页面
- for each in 和 for in 和for of
- going deeper with convolutions (Googlenet网络解读)