作者:小傅哥

博客:https://bugstack.cn

沉淀、分享、成长,让自己和他人都能有所收获!

一、前言

忒复杂,没等搞明白大促都过去了!

你经历过618双11吗?你加入过大促时候那么多复杂的营销活动赚几毛钱吗?你开发过连读明白玩法都需要一周但只使用3天的大促需求吗?有时候对于有些产品的需求真的是太复杂了,复杂到开发、测试都需要在整个过程中不断的学习最后才可能读懂产品为啥这样的玩,要是一个长期的活动可能也就算了,培养用户心智吗!但这一整套拉新、助力、激活、下单、投保、领券、消费、开红包等等一连串的骚操作下来,如果在线上只用3天呢,或者是只用1天,那TM连参与的用户都没弄明白呢,活动就结束了,最后能打来什么样好的数据呢?对于这样流程复杂,估计连羊毛当都看不上!!!

以上只是举个例子,大部分时候并不会搞的这么恶心,评审也是过不去的!而同样的道理用在程序设计开发和使用中也是一样的,如果你把你的代码逻辑实现的过于分散,让外部调用方在使用的时候,需要调用你的接口多个和多次,还没有消息触达,只能定时自己轮训你的接口查看订单状态,每次还只能查10条,查多了你说不行,等等反人类的设计,都会给调用方带来要干你的体会。

所以,如果我们能在完成目的的情况下,都是希望尽可能流程简单、模式清晰、自动服务。那这在Spring的框架中也是有所体现的,这个框架的普及使用程度和它所能带来的方便性是分不开的,而我们如果能做到如此的方便,那肯定是一种好的设计和实现。

二、目标

其实到本章节我们已经把关于 IOC 和 AOP 全部核心内容都已经实现完成了,只不过在使用上还有点像早期的 Spring 版本,需要一个一个在 spring.xml 中进行配置。这与实际的目前使用的 Spring 框架还是有蛮大的差别,而这种差别其实都是在核心功能逻辑之上建设的在更少的配置下,做到更简化的使用。

这其中就包括:包的扫描注册、注解配置的使用、占位符属性的填充等等,而我们的目标就是在目前的核心逻辑上填充一些自动化的功能,让大家可以学习到这部分的设计和实现,从中体会到一些关于代码逻辑的实现过程,总结一些编码经验。

三、方案

首先我们要考虑,为了可以简化 Bean 对象的配置,让整个 Bean 对象的注册都是自动扫描的,那么基本需要的元素包括:扫描路径入口、XML解析扫描信息、给需要扫描的Bean对象做注解标记、扫描Class对象摘取Bean注册的基本信息,组装注册信息、注册成Bean对象。那么在这些条件元素的支撑下,就可以实现出通过自定义注解和配置扫描路径的情况下,完成 Bean 对象的注册。除此之外再顺带解决一个配置中占位符属性的知识点,比如可以通过 ${token} 给 Bean 对象注入进去属性信息,那么这个操作需要用到 BeanFactoryPostProcessor,因为它可以处理 在所有的 BeanDefinition 加载完成后,实例化 Bean 对象之前,提供修改 BeanDefinition 属性的机制 而实现这部分内容是为了后续把此类内容结合到自动化配置处理中。整体设计结构如下图:

结合bean的生命周期,包扫描只不过是扫描特定注解的类,提取类的相关信息组装成BeanDefinition注册到容器中。

在XmlBeanDefinitionReader中解析<context:component-scan />标签,扫描类组装BeanDefinition然后注册到容器中的操作在ClassPathBeanDefinitionScanner#doScan中实现。

  • 自动扫描注册主要是扫描添加了自定义注解的类,在xml加载过程中提取类的信息,组装 BeanDefinition 注册到 Spring 容器中。
  • 所以我们会用到 <context:component-scan /> 配置包路径并在 XmlBeanDefinitionReader 解析并做相应的处理。这里的处理会包括对类的扫描、获取注解信息等
  • 最后还包括了一部分关于 BeanFactoryPostProcessor 的使用,因为我们需要完成对占位符配置信息的加载,所以需要使用到 BeanFactoryPostProcessor 在所有的 BeanDefinition 加载完成后,实例化 Bean 对象之前,修改 BeanDefinition 的属性信息。这一部分的实现也为后续处理关于占位符配置到注解上做准备

四、实现

1. 工程结构

small-spring-step-13
└── src
├── main
│ └── java
│ └── cn.bugstack.springframework
│ ├── aop
│ │ ├── aspectj
│ │ │ └── AspectJExpressionPointcut.java
│ │ │ └── AspectJExpressionPointcutAdvisor.java
│ │ ├── framework
│ │ │ ├── adapter
│ │ │ │ └── MethodBeforeAdviceInterceptor.java
│ │ │ ├── autoproxy
│ │ │ │ └── MethodBeforeAdviceInterceptor.java
│ │ │ ├── AopProxy.java
│ │ │ ├── Cglib2AopProxy.java
│ │ │ ├── JdkDynamicAopProxy.java
│ │ │ ├── ProxyFactory.java
│ │ │ └── ReflectiveMethodInvocation.java
│ │ ├── AdvisedSupport.java
│ │ ├── Advisor.java
│ │ ├── BeforeAdvice.java
│ │ ├── ClassFilter.java
│ │ ├── MethodBeforeAdvice.java
│ │ ├── MethodMatcher.java
│ │ ├── Pointcut.java
│ │ ├── PointcutAdvisor.java
│ │ └── TargetSource.java
│ ├── beans
│ │ ├── factory
│ │ │ ├── config
│ │ │ │ ├── AutowireCapableBeanFactory.java
│ │ │ │ ├── BeanDefinition.java
│ │ │ │ ├── BeanFactoryPostProcessor.java
│ │ │ │ ├── BeanPostProcessor.java
│ │ │ │ ├── BeanReference.java
│ │ │ │ ├── ConfigurableBeanFactory.java
│ │ │ │ ├── InstantiationAwareBeanPostProcessor.java
│ │ │ │ └── SingletonBeanRegistry.java
│ │ │ ├── support
│ │ │ │ ├── AbstractAutowireCapableBeanFactory.java
│ │ │ │ ├── AbstractBeanDefinitionReader.java
│ │ │ │ ├── AbstractBeanFactory.java
│ │ │ │ ├── BeanDefinitionReader.java
│ │ │ │ ├── BeanDefinitionRegistry.java
│ │ │ │ ├── CglibSubclassingInstantiationStrategy.java
│ │ │ │ ├── DefaultListableBeanFactory.java
│ │ │ │ ├── DefaultSingletonBeanRegistry.java
│ │ │ │ ├── DisposableBeanAdapter.java
│ │ │ │ ├── FactoryBeanRegistrySupport.java
│ │ │ │ ├── InstantiationStrategy.java
│ │ │ │ └── SimpleInstantiationStrategy.java
│ │ │ ├── support
│ │ │ │ └── XmlBeanDefinitionReader.java
│ │ │ ├── Aware.java
│ │ │ ├── BeanClassLoaderAware.java
│ │ │ ├── BeanFactory.java
│ │ │ ├── BeanFactoryAware.java
│ │ │ ├── BeanNameAware.java
│ │ │ ├── ConfigurableListableBeanFactory.java
│ │ │ ├── DisposableBean.java
│ │ │ ├── FactoryBean.java
│ │ │ ├── HierarchicalBeanFactory.java
│ │ │ ├── InitializingBean.java
│ │ │ ├── ListableBeanFactory.java
│ │ │ └── PropertyPlaceholderConfigurer.java
│ │ ├── BeansException.java
│ │ ├── PropertyValue.java
│ │ └── PropertyValues.java
│ ├── context
│ │ ├── annotation
│ │ │ ├── ClassPathBeanDefinitionScanner.java
│ │ │ ├── ClassPathScanningCandidateComponentProvider.java
│ │ │ └── Scope.java
│ │ ├── event
│ │ │ ├── AbstractApplicationEventMulticaster.java
│ │ │ ├── ApplicationContextEvent.java
│ │ │ ├── ApplicationEventMulticaster.java
│ │ │ ├── ContextClosedEvent.java
│ │ │ ├── ContextRefreshedEvent.java
│ │ │ └── SimpleApplicationEventMulticaster.java
│ │ ├── support
│ │ │ ├── AbstractApplicationContext.java
│ │ │ ├── AbstractRefreshableApplicationContext.java
│ │ │ ├── AbstractXmlApplicationContext.java
│ │ │ ├── ApplicationContextAwareProcessor.java
│ │ │ └── ClassPathXmlApplicationContext.java
│ │ ├── ApplicationContext.java
│ │ ├── ApplicationContextAware.java
│ │ ├── ApplicationEvent.java
│ │ ├── ApplicationEventPublisher.java
│ │ ├── ApplicationListener.java
│ │ └── ConfigurableApplicationContext.java
│ ├── core.io
│ │ ├── ClassPathResource.java
│ │ ├── DefaultResourceLoader.java
│ │ ├── FileSystemResource.java
│ │ ├── Resource.java
│ │ ├── ResourceLoader.java
│ │ └── UrlResource.java
│ ├── stereotype
│ │ └── Component.java
│ └── utils
│ └── ClassUtils.java
└── test
└── java
└── cn.bugstack.springframework.test
├── bean
│ ├── IUserService.java
│ └── UserService.java
└── ApiTest.java

工程源码公众号「bugstack虫洞栈」,回复:Spring 专栏,获取完整源码

在Bean的生命周期中自动加载包扫描注册Bean对象和设置占位符属性的类关系,如图 14-2

  • 整个类的关系结构来看,其实涉及的内容并不多,主要包括的就是 xml 解析类 XmlBeanDefinitionReader 对 ClassPathBeanDefinitionScanner#doScan 的使用。
  • 在 doScan 方法中处理所有指定路径下添加了注解的类,拆解出类的信息:名称、作用范围等,进行创建 BeanDefinition 好用于 Bean 对象的注册操作。
  • PropertyPlaceholderConfigurer 目前看上去像一块单独的内容,后续会把这块的内容与自动加载 Bean 对象进行整合,也就是可以在注解上使用占位符配置一些在配置文件里的属性信息。

2. 处理占位符配置

cn.bugstack.springframework.beans.factory.PropertyPlaceholderConfigurer

public class PropertyPlaceholderConfigurer implements BeanFactoryPostProcessor {

    /**
* Default placeholder prefix: {@value}
*/
public static final String DEFAULT_PLACEHOLDER_PREFIX = "${"; /**
* Default placeholder suffix: {@value}
*/
public static final String DEFAULT_PLACEHOLDER_SUFFIX = "}"; private String location; @Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
// 加载属性文件
try {
DefaultResourceLoader resourceLoader = new DefaultResourceLoader();
Resource resource = resourceLoader.getResource(location);
Properties properties = new Properties();
properties.load(resource.getInputStream()); String[] beanDefinitionNames = beanFactory.getBeanDefinitionNames();
for (String beanName : beanDefinitionNames) {
BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName); PropertyValues propertyValues = beanDefinition.getPropertyValues();
for (PropertyValue propertyValue : propertyValues.getPropertyValues()) {
Object value = propertyValue.getValue();
if (!(value instanceof String)) continue;
String strVal = (String) value;
StringBuilder buffer = new StringBuilder(strVal);
int startIdx = strVal.indexOf(DEFAULT_PLACEHOLDER_PREFIX);
int stopIdx = strVal.indexOf(DEFAULT_PLACEHOLDER_SUFFIX);
if (startIdx != -1 && stopIdx != -1 && startIdx < stopIdx) {
String propKey = strVal.substring(startIdx + 2, stopIdx);
String propVal = properties.getProperty(propKey);
buffer.replace(startIdx, stopIdx + 1, propVal);
propertyValues.addPropertyValue(new PropertyValue(propertyValue.getName(), buffer.toString()));
}
}
}
} catch (IOException e) {
throw new BeansException("Could not load properties", e);
}
} public void setLocation(String location) {
this.location = location;
} }
  • 依赖于 BeanFactoryPostProcessor 在 Bean 生命周期的属性,可以在 Bean 对象实例化之前,改变属性信息。所以这里通过实现 BeanFactoryPostProcessor 接口,完成对配置文件的加载以及摘取占位符中的在属性文件里的配置。
  • 这样就可以把提取到的配置信息放置到属性配置中了,buffer.replace(startIdx, stopIdx + 1, propVal); propertyValues.addPropertyValue

3. 定义拦截注解

cn.bugstack.springframework.context.annotation.Scope

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Scope { String value() default "singleton"; }
  • 用于配置作用域的自定义注解,方便通过配置Bean对象注解的时候,拿到Bean对象的作用域。不过一般都使用默认的 singleton

cn.bugstack.springframework.stereotype.Component

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Component { String value() default ""; }
  • Component 自定义注解大家都非常熟悉了,用于配置到 Class 类上的。除此之外还有 Service、Controller,不过所有的处理方式基本一致,这里就只展示一个 Component 即可。

4. 处理对象扫描装配

cn.bugstack.springframework.context.annotation.ClassPathScanningCandidateComponentProvider

public class ClassPathScanningCandidateComponentProvider {

    public Set<BeanDefinition> findCandidateComponents(String basePackage) {
Set<BeanDefinition> candidates = new LinkedHashSet<>();
Set<Class<?>> classes = ClassUtil.scanPackageByAnnotation(basePackage, Component.class);
for (Class<?> clazz : classes) {
candidates.add(new BeanDefinition(clazz));
}
return candidates;
} }
  • 这里先要提供一个可以通过配置路径 basePackage=cn.bugstack.springframework.test.bean,解析出 classes 信息的工具方法 findCandidateComponents,通过这个方法就可以扫描到所有 @Component 注解的 Bean 对象了。

cn.bugstack.springframework.context.annotation.ClassPathBeanDefinitionScanner

public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateComponentProvider {

    private BeanDefinitionRegistry registry;

    public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry) {
this.registry = registry;
} public void doScan(String... basePackages) {
for (String basePackage : basePackages) {
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
for (BeanDefinition beanDefinition : candidates) {
// 解析 Bean 的作用域 singleton、prototype
String beanScope = resolveBeanScope(beanDefinition);
if (StrUtil.isNotEmpty(beanScope)) {
beanDefinition.setScope(beanScope);
}
registry.registerBeanDefinition(determineBeanName(beanDefinition), beanDefinition);
}
}
} private String resolveBeanScope(BeanDefinition beanDefinition) {
Class<?> beanClass = beanDefinition.getBeanClass();
Scope scope = beanClass.getAnnotation(Scope.class);
if (null != scope) return scope.value();
return StrUtil.EMPTY;
} private String determineBeanName(BeanDefinition beanDefinition) {
Class<?> beanClass = beanDefinition.getBeanClass();
Component component = beanClass.getAnnotation(Component.class);
String value = component.value();
if (StrUtil.isEmpty(value)) {
value = StrUtil.lowerFirst(beanClass.getSimpleName());
}
return value;
} }
  • ClassPathBeanDefinitionScanner 是继承自 ClassPathScanningCandidateComponentProvider 的具体扫描包处理的类,在 doScan 中除了获取到扫描的类信息以后,还需要获取 Bean 的作用域和类名,如果不配置类名基本都是把首字母缩写。

5. 解析xml中调用扫描

cn.bugstack.springframework.beans.factory.xml.XmlBeanDefinitionReader

public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {

    protected void doLoadBeanDefinitions(InputStream inputStream) throws ClassNotFoundException, DocumentException {
SAXReader reader = new SAXReader();
Document document = reader.read(inputStream);
Element root = document.getRootElement(); // 解析 context:component-scan 标签,扫描包中的类并提取相关信息,用于组装 BeanDefinition
Element componentScan = root.element("component-scan");
if (null != componentScan) {
String scanPath = componentScan.attributeValue("base-package");
if (StrUtil.isEmpty(scanPath)) {
throw new BeansException("The value of base-package attribute can not be empty or null");
}
scanPackage(scanPath);
} // ... 省略其他 // 注册 BeanDefinition
getRegistry().registerBeanDefinition(beanName, beanDefinition);
} private void scanPackage(String scanPath) {
String[] basePackages = StrUtil.splitToArray(scanPath, ',');
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(getRegistry());
scanner.doScan(basePackages);
} }
  • 关于 XmlBeanDefinitionReader 中主要是在加载配置文件后,处理新增的自定义配置属性 component-scan,解析后调用 scanPackage 方法,其实也就是我们在 ClassPathBeanDefinitionScanner#doScan 功能。
  • 另外这里需要注意,为了可以方便的加载和解析xml,XmlBeanDefinitionReader 已经全部替换为 dom4j 的方式进行解析处理。

五、测试

1. 事先准备

@Component("userService")
public class UserService implements IUserService { private String token; public String queryUserInfo() {
try {
Thread.sleep(new Random(1).nextInt(100));
} catch (InterruptedException e) {
e.printStackTrace();
}
return "小傅哥,100001,深圳";
} public String register(String userName) {
try {
Thread.sleep(new Random(1).nextInt(100));
} catch (InterruptedException e) {
e.printStackTrace();
}
return "注册用户:" + userName + " success!";
} @Override
public String toString() {
return "UserService#token = { " + token + " }";
} public String getToken() {
return token;
} public void setToken(String token) {
this.token = token;
}
}
  • 给 UserService 类添加一个自定义注解 @Component("userService") 和一个属性信息 String token。这是为了分别测试包扫描和占位符属性。

2. 属性配置文件

token=RejDlI78hu223Opo983Ds
  • 这里配置一个 token 的属性信息,用于通过占位符的方式进行获取

3. spring.xml 配置对象

spring-property.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"
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"> <bean class="cn.bugstack.springframework.beans.factory.PropertyPlaceholderConfigurer">
<property name="location" value="classpath:token.properties"/>
</bean> <bean id="userService" class="cn.bugstack.springframework.test.bean.UserService">
<property name="token" value="${token}"/>
</bean> </beans>
  • 加载 classpath:token.properties 设置占位符属性值 ${token}

spring-scan.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"
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"> <context:component-scan base-package="cn.bugstack.springframework.test.bean"/> </beans>
  • 添加 component-scan 属性,设置包扫描根路径

4. 单元测试(占位符)

@Test
public void test_property() {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:spring-property.xml");
IUserService userService = applicationContext.getBean("userService", IUserService.class);
System.out.println("测试结果:" + userService);
}

测试结果

测试结果:UserService#token = { RejDlI78hu223Opo983Ds }

Process finished with exit code 0
  • 通过测试结果可以看到 UserService 中的 token 属性已经通过占位符的方式设置进去配置文件里的 token.properties 的属性值了。

5. 单元测试(包扫描)

@Test
public void test_scan() {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:spring-scan.xml");
IUserService userService = applicationContext.getBean("userService", IUserService.class);
System.out.println("测试结果:" + userService.queryUserInfo());
}

测试结果

测试结果:小傅哥,100001,深圳

Process finished with exit code 0
  • 通过这个测试结果可以看出来,现在使用注解的方式就可以让 Class 注册完成 Bean 对象了。

六、总结

  • 通过整篇的内容实现可以看出来,目前的功能添加其实已经不复杂了,都是在 IOC 和 AOP 核心的基础上来补全功能。这些补全的功能也是在完善 Bean 的生命周期,让整个功能使用也越来越容易。
  • 在你不断的实现着 Spring 的各项功能时,也可以把自己在平常使用 Spring 的一些功能想法融入进来,比如像 Spring 是如何动态切换数据源的,线程池是怎么提供配置的,这些内容虽然不是最基础的核心范围,但也非常重要。
  • 可能有些时候这些类实现的内容对新人来说比较多,可以一点点动手实现逐步理解,在把一些稍微较有难度的内容实现后,其实后面也就没有那么难理解了。

七、系列推荐

就想搞明白,component-scan 是怎么把Bean都注册到Spring容器的!的更多相关文章

  1. vue-router 路由元信息 终于搞明白了路由元信息是个啥了

    vue-router 路由元信息:https://blog.csdn.net/wenyun_kang/article/details/70987840 终于搞明白了路由元信息是个啥了:https:// ...

  2. 相机拍的图,电脑上画的图,word里的文字,电脑屏幕,手机屏幕,相机屏幕显示大小一切的一切都搞明白了!

    相机拍的图,电脑上画的图,word里的文字,电脑屏幕,手机屏幕,相机屏幕显示大小一切的一切都搞明白了! 先说图片X×dpi=点数dotX是图片实际尺寸,简单点,我们只算图片的高吧,比如说拍了张图片14 ...

  3. 彻底搞明白find命令的-mtime参数的含义【转载】

    转自: 彻底搞明白find命令的-mtime参数的含义-goolen-ITPUB博客http://blog.itpub.net/23249684/viewspace-1156932/ 以前一直没有弄明 ...

  4. LIN、CAN、FlexRay、MOST,三分钟搞明白四大汽车总线

    LIN.CAN.FlexRay.MOST,三分钟搞明白四大汽车总线 2016-09-21 13:09 汽车中的电子部件越来越多,光是ECU就有几十个,这么多的电子单元都要进行信息交互.传统的点对点通信 ...

  5. [Spring Boot] Use Component Scan to scan for Bean

    Component Scan is important concept when we want to create Bean. Currently we know what, for the cla ...

  6. 真正搞明白Python中Django和Flask框架的区别

    在谈Python中Django框架和Flask框架的区别之前,我们需要先探讨如下几个问题. 一.为什么要使用框架? 为了更好地阐述这个问题,我们把开发一个应用的过程进行类比,往往开发一个应用(web应 ...

  7. 常用注解解析(因为不太明白@component和@configuration写了)

    1.@controller 控制器(注入服务) 用于标注控制层,相当于struts中的action层 2.@service 服务(注入dao) 用于标注服务层,主要用来进行业务的逻辑处理 3.@rep ...

  8. [转帖]Nginx为什么高效?一文搞明白Nginx核心原理

    Nginx为什么高效?一文搞明白Nginx核心原理 咔咔侃技术 2019-09-06 15:37:00 https://www.toutiao.com/a6733057587622707724/ Ng ...

  9. 终于搞明白Unicode,ASCII,UTF8,UCS2编码是啥了

    [本文版权归微信公众号"代码艺术"(ID:onblog)所有,若是转载请务必保留本段原创声明,违者必究.若是文章有不足之处,欢迎关注微信公众号私信与我进行交流!] 前言 本文起因于 ...

随机推荐

  1. Java面试必知必会(扩展)——Java基础

    float f=3.4;是否正确? 不正确 3.4是双精度,将双精度赋值给浮点型属于向下转型,会造成精度损失: 因此需要强制类型转换: 方式一:float f=(float)3.4 方式二:float ...

  2. 四、SSL虚拟证书

    沿用练习三,配置基于加密网站的虚拟主机,实现以下目标: 域名为www.c.com 该站点通过https访问 通过私钥.证书对该站点所有数据加密 4.2 方案 源码安装Nginx时必须使用--with- ...

  3. ipconfig提示不是内部或外部命令

    昨天因为公司断网,重新连上之后ip地址变了,于是就想看看现在的ip是什么 输入ipconfig,回车 提示不是外部和内部命令,是因为系统在本路径下未找到ipconfig.exe系统,所以无法识别ipc ...

  4. MQTT 协议是个啥?这篇文章告诉你!

    文章首发于我的公众号「程序员cxuan」,欢迎大家关注呀- 说到做到! 之前有位读者给我留言说想要了解一下什么是 MQTT 协议,顺便还把我夸了一把,有点不好意思啦. 那么读者的要求必须要满足啊,所以 ...

  5. 6.17考试总结(NOIP模拟8)[星际旅行·砍树·超级树·求和]

    6.17考试总结(NOIP模拟8) 背景 考得不咋样,有一个非常遗憾的地方:最后一题少取膜了,\(100pts->40pts\),改了这么多年的错还是头一回看见以下的情景... T1星际旅行 前 ...

  6. 使用 vite 构建一个表情选择插件

    初始化 Vite 基于原生 ES 模块提供了丰富的内建功能,开箱即用.同时,插件足够简单,它不需要任何运行时依赖,只需要安装 vite (用于开发与构建)和 sass (用于开发环境编译 .scss ...

  7. HTTP头部POST表单详解

    2 POST /hello/checkUser.html?opt=xxx HTTP/1.1 方法的声明,Get,Post,Delete等 3 Accept: */* 4 Referer: http:/ ...

  8. 复习Spring第三课--数据源配置的多种方式

    spring数据源配置可以说分为:spring容器自带连接池.项目中创建连接池.服务器创建连接池三种 一.spring容器自带连接池   Spring本身也提供了一个简单的数据源实现类DriverMa ...

  9. 文字识别OCR开源框架的对比--Tesseract vs EasyOCR

    ​ 前言: OCR文字识别在目前有着比较好的应用,也出现了很多的文字识别软件,但软件是面向用户的.对于我们技术人员来说,有时难免需要在计算机视觉任务中加入文字识别,如车牌号识别,票据识别等,因此软件对 ...

  10. 带你掌握C++中三种类成员初始化方式

    摘要:在C++11之后,声明时初始化->初始化列表->构造函数初始化. 本文分享自华为云社区<如何编写高效.优雅.可信代码系列(3)--类成员初始化的三种方式>,原文作者:我是 ...