目录
📢📢📢📣📣📣
哈喽!大家好,我是「Leen」。刚工作几年,想和大家一同进步🤝🤝
一位上进心十足的Java博主!😜😜😜
喜欢尝试一些新鲜的东西,平时比较喜欢研究一些新鲜技术和一些自己没有掌握的技术领域。能用程序解决的坚决不手动解决😜😜😜目前已涉足Java、Python、数据库(MySQL、pgsql、MongoDB、Oracle...)、Linux、HTML、VUE、PHP、C(了解不多,主要是嵌入式编程方向做了一些)...(还在不断地学习,扩展自己的见识和技术领域中),希望可以和各位大佬们一起进步,共同学习🤝🤝
✨ 如果有对【Java】,或者喜欢看一些实操笔记感兴趣的【小可爱】,欢迎关注我
❤️❤️❤️感谢各位大可爱小可爱!❤️❤️❤️
————————————————如果觉得本文对你有帮助,欢迎点赞,欢迎关注我,如果有补充欢迎评论交流,我将努力创作更多更好的文章。
前言
一、解析子元素 lookup-method
了解了部分Spring源码的同学应该知道,子元素lookup-method似乎并不是很常用,但是在某些时候它的确是非常有用的属性,通常我们称它为获取器注人。引用《Spring in Action》中的一句话:获取器注人是一种特殊的方法注人,它是把一个方法声明为返回某种类型的bean,但实际要返回的bean是在配置文件里面配置的,此方法可用在设计有些可插拔的功能上,解除程序依赖。我们看看具体的应用。
(1) 首先我们创建一个父类
package com.www.test.test2024.test506.lookup.bean;
public class User {
public void showMe(){
System.out.println("I am User");
}
}
(2)创建其子类并覆盖showMe方法
package com.www.test.test2024.test506.lookup.bean;
public class It extends User {
public void showMe() {
System.out.println("I am It");
}
}
(3)创建调用方法
package com.www.test.test2024.test506.lookup.bean;
public abstract class GetBeanTest {
public void showMe(){
this.getBean().showMe();
}
public abstract User getBean();
}
(4)创建测试方法
package com.www.test.test2024.test506.lookup.bean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Main {
//解析子元素 lookup-method
public static void main(String[] args) {
ApplicationContext bf = new ClassPathXmlApplicationContext("classpath:lookup/lookupTest.xml");
GetBeanTest test = (GetBeanTest) bf.getBean("getBeanTest");
test.showMe();
}
}
到现在为止,除了配置文件外,整个测试方法就完成了,如果之前没有接触过获取器注人的同学们可能会有疑问:抽象方法还没有被实现,怎么可以直接调用呢?答案就在Spring为我们提供的获取器中,我们看看配置文件是怎么配置的。
(5)创建lookupTest.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="getBeanTest" class="com.www.test.test2024.test506.lookup.bean.GetBeanTest">
<lookup-method name="getBean" bean="it"/>
</bean>
<bean id="it" class="com.www.test.test2024.test506.lookup.bean.It"/>
</beans>
(6)启动测试类,看效果
在配置文件中,我们看到了源码解析中提到的lookup-method 子元素,这个配置完成的功能是动态地将 teacher 所代表的 bean 作为 getBean 的返回值,运行测试方法我们会看到控制台上的输出:
I am It
当我们的业务变更或者在其他情况下,teacher里面的业务逻辑已经不再符合我们的业务要求,需要进行替换怎么办呢?这是我们需要增加新的逻辑类:
package com.www.test.test2024.test506.lookup.bean;
public class Student extends User {
public void showMe() {
System.out.println("I am Student");
}
}
同时修改配置文件
<?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="getBeanTest" class="com.www.test.test2024.test506.lookup.bean.GetBeanTest">
<!-- <lookup-method name="getBean" bean="it"/>-->
<lookup-method name="getBean" bean="student"/>
</bean>
<bean id="it" class="com.www.test.test2024.test506.lookup.bean.It"/>
<bean id="student" class="com.www.test.test2024.test506.lookup.bean.Student"/>
</beans>
再次运行测试类,你会发现不一样的结果:
i am Student
至此,我们已经初步了解了 lookup-method 子元素所提供的大致功能,相信这时再次去看它的属性提取源码会觉得更有针对性。
/**
* Parse lookup-override sub-elements of the given bean element.
*/
public void parseLookupOverrideSubElements(Element beanEle, MethodOverrides overrides) {
NodeList nl = beanEle.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
//仅当在 Spring 默认 bean 的子元素下且为<lookup-method 时有效
if (isCandidateElement(node) && nodeNameEquals(node, LOOKUP_METHOD_ELEMENT)) {
Element ele = (Element) node;
//获取要修饰的方法
String methodName = ele.getAttribute(NAME_ATTRIBUTE);
//获取配置返回的bean
String beanRef = ele.getAttribute(BEAN_ELEMENT);
LookupOverride override = new LookupOverride(methodName, beanRef);
override.setSource(extractSource(ele));
overrides.addOverride(override);
}
}
}
上面的代码很眼熟,似乎与 parseMetaElements的代码大同小异,最大的区别就是在if判断中的节点名称在这里被修改为LOOKUPMETHODELEMENT。还有,在数据存储上面通过使用 LookupOverride 类型的实体类来进行数据承载并记录在AbstractBeanDefinition中的methodOverrides属性中。
二、解析子元素 replaced-method
这个方法主要是对 bean 中replaced-method 子元素的提取,在开始提取分析之前我们还是预先介绍下这个元素的用法。
方法替换:可以在运行时用新的方法替换现有的方法。与之前的look-up不同的是,replaced-method不但可以动态地替换返回实体 bean,而且还能动态地更改原有方法的逻辑。我们来看看使用示例。
(1)在changeMe 中完成某个业务逻辑。
package com.www.test.test2024.test506.replaced.bean;
public class TestChangeMethod {
public void changeMe() {
System.out.println("changeMe");
}
}
(2)在运营一段时间后需要改变原有的业务逻辑。
package com.www.test.test2024.test506.replaced.bean;
import org.springframework.beans.factory.support.MethodReplacer;
import java.lang.reflect.Method;
public class TestMethodReplacer implements MethodReplacer {
@Override
public Object reimplement(Object obj, Method method, Object[] args) throws Throwable {
System.out.println("我替换了原有的方法");
return null;
}
}
(3)使替换后的类生效。
<?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="testChangeMethod" class="com.www.test.test2024.test506.replaced.bean.TestChangeMethod">
<replaced-method name="changeMe" replacer="replacer"/>
</bean>
<bean id="replacer" class="com.www.test.test2024.test506.replaced.bean.TestMethodReplacer"></bean>
</beans>
(4)测试
package com.www.test.test2024.test506.replaced.bean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Main {
//解析子元素 replaced-method
public static void main(String[] args) {
ApplicationContext bf = new ClassPathXmlApplicationContext("classpath:replacerMethod/replacerMethodTest.xml");
TestChangeMethod test = (TestChangeMethod) bf.getBean("testChangeMethod");
test.changeMe();
}
}
好了,运行测试类就可以看到预期的结果了,控制台成功打印出“我替换了原有的方法”也就是说我们做到了动态替换原有方法,知道了这个元素的用法,我们再次来看元素的提取过程:
/**
* Parse replaced-method sub-elements of the given bean element.
*/
public void parseReplacedMethodSubElements(Element beanEle, MethodOverrides overrides) {
NodeList nl = beanEle.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
//仅当在 Spring 默认bean 的子元素下且为 <replaced-method 时有效
if (isCandidateElement(node) && nodeNameEquals(node, REPLACED_METHOD_ELEMENT)) {
Element replacedMethodEle = (Element) node;
//提取要替换的旧的方法
String name = replacedMethodEle.getAttribute(NAME_ATTRIBUTE);
//提取对应的新的替换方法
String callback = replacedMethodEle.getAttribute(REPLACER_ATTRIBUTE);
ReplaceOverride replaceOverride = new ReplaceOverride(name, callback);
// Look for arg-type match elements.
List<Element> argTypeEles = DomUtils.getChildElementsByTagName(replacedMethodEle, ARG_TYPE_ELEMENT);
for (Element argTypeEle : argTypeEles) {
String match = argTypeEle.getAttribute(ARG_TYPE_MATCH_ATTRIBUTE);
//记录参数
match = (StringUtils.hasText(match) ? match : DomUtils.getTextValue(argTypeEle));
if (StringUtils.hasText(match)) {
replaceOverride.addTypeIdentifier(match);
}
}
replaceOverride.setSource(extractSource(replacedMethodEle));
overrides.addOverride(replaceOverride);
}
}
}
我们可以看到无论是look-up还是replaced-method 都是构造了一个 MethodOverride,并最终记录在了 AbstractBeanDefinition中的 methodOverrides 属性中。而这个属性如何使用以完成它所提供的功能可以往后深度研究一下Spring的代码,后续我会整理一下出一期这方面的笔记。
欢迎大家在评论区讨论,今天的干货分享就到此结束了,如果觉得对您有帮助,麻烦给个三连!
以上内容为本人的经验总结和平时操作的笔记。若有错误和重复请联系作者删除!!感谢支持!!