Spring的轻量级实现竟如此的轻若鸿毛,微乎其微?,java线程面试常问
一个人可以走的很快,但一群人才能走的更远。如果你从事以下工作或对以下感兴趣,欢迎戳这里加入程序员的圈子,让我们一起学习成长!AI人工智能、Android移动开发、AIGC大模型、C C#、Go语言、Java、Linux运维、云计算、MySQL、PMP、网络安全、Python爬虫、UE5、UI设计、Unity3D、Web前端开发、产品经理、车载开发、大数据、鸿蒙、计算机网络、嵌入式物联网、软件测试、
抽象SingletonBeanRegistry这个接口,专门用于注册和获取单例对象,
public interface SingletonBeanRegistry {
void registerSingleton(String beanName, Object singletonObject);
Object getSingleton(String beanName);
}
DefaultSingletonBeanRegistry实现这个接口,实现对单例对象的注册
public class DefaultSingletonBeanRegistry implements SingletonBeanRegistry {
// TODO 考虑线程安全的容器
private final Map<String, Object> singletonObjects = new HashMap<>();
@Override
public void registerSingleton(String beanName, Object singletonObject) {
// 注册单例Bean
…
}
@Override
public Object getSingleton(String beanName) {
// 获取单例Bean
return this.singletonObjects.get(beanName);
}
}
DefaultBeanFactory继承
DefaultSingletonBeanRegistry这个类,就有了获取单例Bean和注册单例Bean的能力。
实现细节参考代码见:vstep6-scope
整合并抽象出ApplicationContext#
=========================
我们使用Spring的时候,一般是这样做的:
ApplicationContext ctx = new ClassPathXmlApplicationContext(“mycontainer.xml”);
UserService userService = (UserService) ctx.getBean(“userService”);
或
ApplicationContext ctx = new FileSystemApplicationContext(“src\test\resources\bean-v1.xml”);
UserService userService = (UserService) ctx.getBean(“userService”);
现在,我们需要抽象出ApplicationContext这个接口来实现如上的功能,其中有如下两个类去实现这个接口。
ClassPathXmlApplicationContext
从classpath中读取配置文件
FileSystemApplicationContext
从文件中读取配置文件
这两个子类都需要持有DefaultBeanFactory才能有getBean的能力,
ClassPathXmlApplicationContext代码如下:
public class ClassPathXmlApplicationContext implements ApplicationContext {
private final DefaultBeanFactory factory;
public ClassPathXmlApplicationContext(String configPath) {
factory = new DefaultBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
reader.loadBeanDefinitions(new ClassPathResource(configPath));
}
@Override
public Object getBean(String beanId) {
return factory.getBean(beanId);
}
}
FileSystemApplicationContext代码如下:
public class FileSystemApplicationContext implements ApplicationContext {
private final DefaultBeanFactory factory;
public FileSystemApplicationContext(String configPath) {
factory = new DefaultBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
reader.loadBeanDefinitions(new FileSystemResource(configPath));
}
@Override
public Object getBean(String beanId) {
return factory.getBean(beanId);
}
}
实现细节参考代码见:
vstep7-applicationcontext-v1
通过观察发现,
ClassPathXmlApplicationContext和
FileSystemApplicationContext大部分代码都是相同的,只有在获取Resource的时候,方法不一样,所以,我们通过模板方法这个设计模式,设计一个抽象类
AbstractApplicationContext,代码如下:
public abstract class AbstractApplicationContext implements ApplicationContext {
private DefaultBeanFactory factory;
public AbstractApplicationContext(String configPath) {
factory = new DefaultBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
reader.loadBeanDefinitions(getResourceByPath(configPath));
}
@Override
public Object getBean(String beanId) {
return factory.getBean(beanId);
}
protected abstract Resource getResourceByPath(String path);
}
这个抽象类实现除了获取Resource以外的所有逻辑,
ClassPathXmlApplicationContext和
FileSystemApplicationContext都继承这个抽象类,完成Resource的获取逻辑的编写即可。以
FileSystemApplicationContext为例,示例代码如下:
public class FileSystemApplicationContext extends AbstractApplicationContext {
public FileSystemApplicationContext(String configPath) {
super(configPath);
}
@Override
protected Resource getResourceByPath(String path) {
return new FileSystemResource(path);
}
}
实现细节参考代码见:
vstep7-applicationcontext-v2
注入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中,我们需要解决如下两个问题:
第一个问题是:把字符串转成各种各样的Value,比如把String转换成Integer或者转换成Boolean。jdk中java.bean包中的PropertyEditorSupport这个类来完成的,我们新建了CustomBooleanEditor和CustomNumberEditor两个类,这两个类都继承于PropertyEditorSupport,分别实现了String类型转换成Boolean类型和String类型转换成Integer类型的功能。其他的类型转换也可以通过类似的方法来实现。然后抽象出了TypeConvert这个接口,并把这些转换器加入一个特定的Map中,Map的key就是要转换的目标的类型,Value就是对应的转换器的实现类,即可实现类型转换。
public interface TypeConverter {
// TODO 抽象出:TypeMismatchException
T convertIfNecessary(Object value, Class requiredType);
}
第二个问题是:我们调用Bean的setXXX方法把这些Value值set到目标Bean中,做法是抽象出PropertyValue
public class PropertyValue {
private final String name;
private final Object value;
// 省略构造方法和get/set方法
}
BeanDefiniton需要增加方法获取PropertyValue的逻辑,BeanDefiniton的所有子类,例如:GenericBeanDefinition中需要增加
private List propertyValues = new ArrayList<>();
在解析XML文件的时候,就需要把List识别出来并加入BeanDefinition中(RuntimeBeanReference,TypedStringValue),使用
BeanDefinitionValueResolver把对应的PropertyValue给初始化好,如下代码:
public class BeanDefinitionValueResolver {
…
public Object resolveValueIfNecessary(Object value) {
if (value instanceof RuntimeBeanReference) {
…
} else if (value instanceof TypedStringValue) {
return ((TypedStringValue) value).getValue();
} else {
//TODO
throw new RuntimeException(“the value " + value + " has not implemented”);
}
}
…
}
而setXXX的背后实现利用的是jdk原生java.beans.Introspector来实现,见DefaultBeanFactory的populateBean方法
private void populateBean(BeanDefinition bd, Object bean) {
…
try {
for (PropertyValue pv : pvs) {
String propertyName = pv.getName();
Object originalValue = pv.getValue();
Object resolvedValue = valueResolver.resolveValueIfNecessary(originalValue);
BeanInfo beanInfo = Introspector.getBeanInfo(bean.getClass());
PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();
for (PropertyDescriptor pd : pds) {
if (pd.getName().equals(propertyName)) {
Object convertedValue = converter.convertIfNecessary(resolvedValue, pd.getPropertyType());
pd.getWriteMethod().invoke(bean, convertedValue);
break;
}
}
}
} catch (Exception ex) {
// TODO 封装Exception
throw new RuntimeException(“Failed to obtain BeanInfo for class [” + bd.getBeanClassName() + “]”, ex);
}
}
其中
pd.getWriteMethod().invoke(bean, convertedValue);
就是对bean的属性进行赋值操作(即:setXXX方法)
实现细节参考代码见:vstep8-inject
实现构造器注入#
========
处理形如以下的配置:
和上例中注入Bean和字符串常量一样,我们抽象出ConstructorArgument用于表示一个构造函数信息,每个BeanDefinition中持有这个对象,
public class ConstructorArgument {
private final List argumentValues = new LinkedList<>();
public ConstructorArgument() {}
public void addArgumentValue(ValueHolder valueHolder) {
this.argumentValues.add(valueHolder);
}
public List getArgumentValues() {
return Collections.unmodifiableList(this.argumentValues);
}
public int getArgumentCount() {
return this.argumentValues.size();
}
public boolean isEmpty() {
return this.argumentValues.isEmpty();
}
/**
- Clear this holder, removing all argument values.
*/
public void clear() {
this.argumentValues.clear();
}
public static class ValueHolder {
private Object value;
private String type;
private String name;
// 省略get/set和构造方法
}
}
在解析XML的时候,XmlBeanDefinitionReader需要负责解析出ConstuctorArgument,DefaultBeanFactory通过指定构造函数来生成Bean对象并通过ConstructorResolver注入Bean实例到构造方法中。
public class ConstructorResolver {
…
public Object autowireConstructor(final BeanDefinition bd) {
// …通过bd找到一个合适的构造函数
try {
// 找到了一个合适的构造函数,则用这个构造函数初始化Bean对象初始化Bean对象
return constructorToUse.newInstance(argsToUse);
} catch (Exception e) {
// TODO throw new BeanCreationException(bd.getID(), "can’t find a create instance using " + constructorToUse); }
throw new RuntimeException(bd.getID() + "can’t find a create instance using " + constructorToUse);
}
}
…
}
注:这里指定的构造函数的查找逻辑为:解析出XML的构造函数的参数列表,和通过反射拿到对应的构造函数的参数列表进行对比(每个参数的类型和个数必须一样)
Constructor<?>[] candidates = beanClass.getConstructors();
BeanDefinitionValueResolver valueResolver = new BeanDefinitionValueResolver(this.beanFactory);
ConstructorArgument cargs = bd.getConstructorArgument();
TypeConverter typeConverter = new SimpleTypeConverter();
for (int i = 0; i < candidates.length; i++) {
// 匹配参数类型和个数,要完全对应上才可以
Class<?>[] parameterTypes = candidates[i].getParameterTypes();
if (parameterTypes.length != cargs.getArgumentCount()) {
continue;
}
argsToUse = new Object[parameterTypes.length];
boolean result = this.valuesMatchTypes(parameterTypes,
cargs.getArgumentValues(),
argsToUse,
valueResolver,
typeConverter);
if (result) {
constructorToUse = candidates[i];
break;
}
}
实现细节参考代码见:vstep9-constructor
实现注解#
=====
实现两个注解:@Component @Autowired(只针对属性注入,暂时不考虑方法注入)
且需要实现如下的XML的解析,即实现某个包下的Bean扫描。
<?xml version="1.0" encoding="UTF-8"?><beans xmlns=“http://www.springframework.org/schema/beans”
xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance”
xmlns:context=“http://www.springframework.org/schema/context”
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package=“org.spring.service.v4,org.spring.dao.v4”></context:component-scan>
我们首先需要定义注解Component ,Autowired,代码如下:
@Target({ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
boolean required() default true;
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Component {
String value() default “”;
}
其次,我们需要实现一个功能,即:给一个包名,扫描获取到这个包以及子包下面的所有Class,示例代码如下:
public Resource[] getResources(String basePackage) throws IOException {
Assert.notNull(basePackage, “basePackage must not be null”);
// 把包名中的.转成/, 即可获取包的路径
String location = ClassUtils.convertClassNameToResourcePath(basePackage);
// TODO ClassLoader cl = getClassLoader();
URL url = Thread.currentThread().getContextClassLoader().getResource(location);
File rootDir = new File(url.getFile());
Set matchingFiles = retrieveMatchingFiles(rootDir);
Resource[] result = new Resource[matchingFiles.size()];
int i = 0;
for (File file : matchingFiles) {
result[i++] = new FileSystemResource(file);
}
return result;
}
主要思路是将包名转换成文件路径,然后递归获取路径下的Class文件。
protected Set retrieveMatchingFiles(File rootDir) throws IOException {
if (!rootDir.exists()) {
// Silently skip non-existing directories.
/*if (logger.isDebugEnabled()) {
logger.debug(“Skipping [” + rootDir.getAbsolutePath() + “] because it does not exist”);
}*/
return Collections.emptySet();
}
if (!rootDir.isDirectory()) {
// Complain louder if it exists but is no directory.
/* if (logger.isWarnEnabled()) {
logger.warn(“Skipping [” + rootDir.getAbsolutePath() + “] because it does not denote a directory”);
}*/
return Collections.emptySet();
}
if (!rootDir.canRead()) {
/*if (logger.isWarnEnabled()) {
logger.warn(“Cannot search for matching files underneath directory [” + rootDir.getAbsolutePath() +
“] because the application is not allowed to read the directory”);
}*/
return Collections.emptySet();
}
/*String fullPattern = StringUtils.replace(rootDir.getAbsolutePath(), File.separator, “/”);
if (!pattern.startsWith(“/”)) {
fullPattern += “/”;
}
fullPattern = fullPattern + StringUtils.replace(pattern, File.separator, “/”);
*/
Set result = new LinkedHashSet<>(8);
doRetrieveMatchingFiles(rootDir, result);
return result;
}
protected void doRetrieveMatchingFiles(File dir, Set result) throws IOException {
File[] dirContents = dir.listFiles();
if (dirContents == null) {
/* if (logger.isWarnEnabled()) {
logger.warn(“Could not retrieve contents of directory [” + dir.getAbsolutePath() + “]”);
}*/
return;
}
for (File content : dirContents) {
if (content.isDirectory()) {
if (!content.canRead()) {
/* if (logger.isDebugEnabled()) {
logger.debug(“Skipping subdirectory [” + dir.getAbsolutePath() +
“] because the application is not allowed to read the directory”);
}*/
} else {
doRetrieveMatchingFiles(content, result);
}
} else {
result.add(content);
}
}
}
由于注解的Bean不像之前的xml定义的Bean那样,会对Bean配置一个id,所以,这里解析出来的Bean定义需要自动生成一个BeanId(默认先取注解中的value的配置,否则就就是类名第一个字母小写,抽象BeanNameGenerator来专门对Bean定义ID),同时,Spring中单独新建了一个AnnotatedBeanDefinition接口来定义包含注解的BeanDefinition。
我们得到了对应的Class文件,我们需要通过某种方式去解析这个Class文件,拿到这个Class中的所有信息,特别是注解信息。可以使用ASM这个来解析Class的信息,用ASM的原生方式解析不太方便,解析ClassMetaData和Annotation都需要定义一个Visitor,所以Spring抽象了一个接口MetadataReader来封装ASM的实现
public interface MetadataReader {
/**
- Read basic class metadata for the underlying class.
*/
ClassMetadata getClassMetadata();
/**
-
Read full annotation metadata for the underlying class,
-
including metadata for annotated methods.
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Java)
最后
如果觉得本文对你有帮助的话,不妨给我点个赞,关注一下吧!
一个人可以走的很快,但一群人才能走的更远。如果你从事以下工作或对以下感兴趣,欢迎戳这里加入程序员的圈子,让我们一起学习成长!
AI人工智能、Android移动开发、AIGC大模型、C C#、Go语言、Java、Linux运维、云计算、MySQL、PMP、网络安全、Python爬虫、UE5、UI设计、Unity3D、Web前端开发、产品经理、车载开发、大数据、鸿蒙、计算机网络、嵌入式物联网、软件测试、数据结构与算法、音视频开发、Flutter、IOS开发、PHP开发、.NET、安卓逆向、云计算
不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!**
因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-tVY69MSh-1712189780814)]
[外链图片转存中…(img-V9ROceJd-1712189780815)]
[外链图片转存中…(img-EZMuuCeV-1712189780815)]
[外链图片转存中…(img-QL7Iv44v-1712189780816)]
[外链图片转存中…(img-CBCo4vdv-1712189780816)]
[外链图片转存中…(img-njIgzQO7-1712189780816)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Java)
[外链图片转存中…(img-x0KaZhFu-1712189780817)]
最后
如果觉得本文对你有帮助的话,不妨给我点个赞,关注一下吧!
[外链图片转存中…(img-yUc03BIL-1712189780817)]
[外链图片转存中…(img-bEYbRFtJ-1712189780817)]
一个人可以走的很快,但一群人才能走的更远。如果你从事以下工作或对以下感兴趣,欢迎戳这里加入程序员的圈子,让我们一起学习成长!
AI人工智能、Android移动开发、AIGC大模型、C C#、Go语言、Java、Linux运维、云计算、MySQL、PMP、网络安全、Python爬虫、UE5、UI设计、Unity3D、Web前端开发、产品经理、车载开发、大数据、鸿蒙、计算机网络、嵌入式物联网、软件测试、数据结构与算法、音视频开发、Flutter、IOS开发、PHP开发、.NET、安卓逆向、云计算

GitCode 天启AI是一款由 GitCode 团队打造的智能助手,基于先进的LLM(大语言模型)与多智能体 Agent 技术构建,致力于为用户提供高效、智能、多模态的创作与开发支持。它不仅支持自然语言对话,还具备处理文件、生成 PPT、撰写分析报告、开发 Web 应用等多项能力,真正做到“一句话,让 Al帮你完成复杂任务”。
更多推荐
所有评论(0)