通过一个 Spring 的 HelloWorld 引入 Spring 要点

目录

一. 前言

二. 设计一个 Spring 的 HelloWorld

2.1. 创建 HelloWorld 项目

2.2. 核心要点一:控制反转(IOC)

2.3. 核心要点二:面向切面(AOP)

三. Spring 框架如何逐步简化开发

3.1. Java 配置方式改造

3.2. 注解配置方式改造

四. SpringBoot 托管配置


一. 前言

    本文主要通过一个示例,向你展示 Spring Framework 组件的典型应用场景和基于这个场景设计出的简单案例,并以此引出 Spring 的核心要点,比如 IOC 和 AOP 等;在此基础上还引入了不同的配置方式, 如 XML,Java 配置和注解方式的差异。

Spring 和 Spring Framework 的组件,对于开发者来说有几个问题:

  1. 首先,对于 Spring 进阶,直接去看 IOC 和 AOP,存在一个断层,所以需要整体上构建对Spring 框架认知上进一步深入,这样才能构建知识体系。
  2. 其次,很多开发者入门都是从 Spring Boot 开始的,他对 Spring 整体框架底层,以及发展历史不是很了解;特别是对于一些老旧项目维护和底层 Bug 分析没有全局观。
  3. 再者,Spring 代表的是一种框架设计理念,需要全局上理解 Spring Framework 组件是如何配合工作的,需要理解它设计的初衷和未来趋势。

如下是官方在解释 Spring 框架的常用场景的图:

引入这个图,最重要的原因是为后面设计一个案例帮助你构建认知。

二. 设计一个 Spring 的 HelloWorld

    结合上图的使用场景,设计一个查询用户的案例的两个需求,来看 Spring 框架帮我们简化了什么开发工作:

  1. 查询用户数据,来看 DAO + POJO -> Service 的初始化和装载。
  2. 给所有 Service 的查询方法记录日志。

2.1. 创建 HelloWorld 项目

创建一个 Maven 的 Java 项目:

引入 Spring 框架的 POM 依赖,以及查看这些依赖之间的关系:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.lm.it</groupId>
    <artifactId>lm-spring-framework-demo-helloworld-xml</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <spring.version>5.3.9</spring.version>
        <aspectjweaver.version>1.9.6</aspectjweaver.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>${aspectjweaver.version}</version>
        </dependency>
    </dependencies>
</project>

POJO - User:

package com.lm.it.springframework.entity;

/**
 * User
 */
public class User {
    /**
     * user's name.
     */
    private String name;

    /**
     * user's age.
     */
    private int age;

    /**
     * init.
     *
     * @param name name
     * @param age  age
     */
    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

DAO 获取 POJO, UserDaoServiceImpl(mock 数据):

package com.lm.it.springframework.dao;

import java.util.Collections;
import java.util.List;

import tech.pdai.springframework.entity.User;

/**
 * UserDao
 */
public class UserDaoImpl {
    /**
     * init.
     */
    public UserDaoImpl() {
    }

    /**
     * mocked to find user list.
     *
     * @return user list
     */
    public List<User> findUserList() {
        return Collections.singletonList(new User("流华追梦", 18));
    }
}

增加 daos.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="userDao" class="com.lm.it.springframework.dao.UserDaoImpl">
        <!-- additional collaborators and configuration for this bean go here -->
    </bean>

    <!-- more bean definitions for data access objects go here -->
</beans>

业务层 UserServiceImpl(调用DAO层):

package com.lm.it.springframework.service;

import java.util.List;

import com.lm.it.springframework.dao.UserDaoImpl;
import com.lm.it.springframework.entity.User;

/**
 * UserService
 */
public class UserServiceImpl {
    /**
     * user dao impl.
     */
    private UserDaoImpl userDao;

    /**
     * init.
     */
    public UserServiceImpl() {
    }

    /**
     * find user list.
     *
     * @return user list
     */
    public List<User> findUserList() {
        return this.userDao.findUserList();
    }

    /**
     * set dao.
     *
     * @param userDao user dao
     */
    public void setUserDao(UserDaoImpl userDao) {
        this.userDao = userDao;
    }
}

增加 services.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">

    <!-- services -->
    <bean id="userService" class="com.lm.it.springframework.service.UserServiceImpl">
        <property name="userDao" ref="userDao"/>
        <!-- additional collaborators and configuration for this bean go here -->
    </bean>

    <!-- more bean definitions for services go here -->
</beans>

拦截所有 service 中的方法,并输出记录:

package com.lm.it.springframework.aspect;

import java.lang.reflect.Method;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

/**
 * LogAspect
 */
@Aspect
public class LogAspect {
    /**
     * aspect for every methods under service package.
     */
    @Around("execution(* com.lm.it.springframework.service.*.*(..))")
    public Object businessService(ProceedingJoinPoint pjp) throws Throwable {
        // get attribute through annotation
        Method method = ((MethodSignature) pjp.getSignature()).getMethod();
        System.out.println("execute method: " + method.getName());

        // continue to process
        return pjp.proceed();
    }
}

增加 aspects.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:aop="http://www.springframework.org/schema/aop"
       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/aop
 http://www.springframework.org/schema/aop/spring-aop.xsd
 http://www.springframework.org/schema/context
 http://www.springframework.org/schema/context/spring-context.xsd
">

    <context:component-scan base-package="com.lm.it.springframework" />

    <aop:aspectj-autoproxy/>

    <bean id="logAspect" class="com.lm.it.springframework.aspect.LogAspect">
        <!-- configure properties of aspect here as normal -->
    </bean>

    <!-- more bean definitions for data access objects go here -->
</beans>

组装 App:

package com.lm.it.springframework;

import java.util.List;

import com.lm.it.springframework.entity.User;
import com.lm.it.springframework.service.UserServiceImpl;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * App
 */
public class App {
    /**
     * main interfaces.
     *
     * @param args args
     */
    public static void main(String[] args) {
        // create and configure beans
        ApplicationContext context =
                new ClassPathXmlApplicationContext("aspects.xml", "daos.xml", "services.xml");

        // retrieve configured instance
        UserServiceImpl service = context.getBean("userService", UserServiceImpl.class);

        // use configured instance
        List<User> userList = service.findUserList();

        // print info from beans
        userList.forEach(a -> System.out.println(a.getName() + "," + a.getAge()));
    }
}

整体结构和运行 App:

至此,整个 HelloWorld 示例完成。那么这个例子体现了 Spring 的哪些核心要点呢?请继续往下看。 

2.2. 核心要点一:控制反转(IOC)

    来看第一个需求:查询用户(service 通过调用 dao 查询 POJO),本质上如何创建User/Dao/Service 等。

1. 如果没有 Spring 框架,我们需要自己创建 User/Dao/Service 等,比如:

UserDaoImpl userDao = new UserDaoImpl();
UserSericeImpl userService = new UserServiceImpl();
userService.setUserDao(userDao);
List<User> userList = userService.findUserList();

2. 有了 Spring 框架,可以将原有 Bean 的创建工作转给框架,需要用时从 Bean 的容器中获取即可,这样便简化了开发工作,Bean 的创建和使用分离了:

// create and configure beans
ApplicationContext context =
        new ClassPathXmlApplicationContext("aspects.xml", "daos.xml", "services.xml");

// retrieve configured instance
UserServiceImpl service = context.getBean("userService", UserServiceImpl.class);

// use configured instance
List<User> userList = service.findUserList();

更进一步,你便能理解为何会有如下的知识点了:

  1. Spring 框架管理这些 Bean 的创建工作,即由用户管理 Bean 转变为框架管理 Bean,这个就叫控制反转 - Inversion of Control(IoC)
  2. Spring 框架托管创建的 Bean 放在哪里呢? 这便是 IoC Container。
  3. Spring 框架为了更好让用户配置 Bean,必然会引入不同方式来配置 Bean。这便是 xml 配置,Java 配置,注解配置等支持。
  4. Spring 框架既然接管了 Bean 的生成,必然需要管理整个 Bean 的生命周期等。
  5. 应用程序代码从 Ioc Container 中获取依赖的 Bean,注入到应用程序中,这个过程叫 依赖注入(Dependency Injection,DI),所以说控制反转是通过依赖注入实现的,其实它们是同一个概念的不同角度描述。通俗来说就是 IoC 是设计思想,DI 是实现方式。
  6. 在依赖注入时,有哪些方式呢?这就是构造器方式,@Autowired、@Resource、 @Qualifier... 同时 Bean 之间存在依赖(可能存在先后顺序问题,以及循环依赖问题等)。

2.3. 核心要点二:面向切面(AOP)

    来看第二个需求:给 Service 所有方法调用添加日志(调用方法时),本质上是解耦问题。

1. 如果没有 Spring 框架,我们需要在每个 service 的方法中都添加记录日志的方法,比如:

/**
* find user list.
*
* @return user list
*/
public List<User> findUserList() {
    System.out.println("execute method findUserList");
    return this.userDao.findUserList();
}

2. 有了Spring框架,通过 @Aspect 注解定义了切面,这个切面中定义了拦截所有 service 的方法,并记录日志;可以明显看到,框架将日志记录和业务需求的代码解耦了,不再是侵入式的了:

/**
* aspect for every methods under service package.
*/
@Around("execution(* com.lm.it.springframework.service.*.*(..))")
public Object businessService(ProceedingJoinPoint pjp) throws Throwable {
    // get attribute through annotation
    Method method = ((MethodSignature) pjp.getSignature()).getMethod();
    System.out.println("execute method: " + method.getName());

    // continue to process
    return pjp.proceed();
}

更进一步,你便能理解为何会有如下的知识点了:

  1. Spring 框架通过定义切面,通过拦截切点实现了不同业务模块的解耦,这个就叫面向切面编程 - Aspect Oriented Programming(AOP)
  2. 为什么 @Aspect 注解使用的是 aspectj 的 jar 包呢?这就引出了 Aspect4J 和 Spring AOP 的历史渊源,只有理解了 Aspect4J 和 Spring 的渊源才能理解有些注解上的兼容设计。
  3. 如何支持更多拦截方式来实现解耦,以满足更多场景需求呢? 这就是 @Around、 @Pointcut... 等的设计。
  4. 那么 Spring 框架又是如何实现 AOP 的呢?这就引入代理技术,静态代理动态代理,动态代理又包含 JDK 代理CGLIB 代理等。

三. Spring 框架如何逐步简化开发

    通过上述的框架介绍和例子,已经初步知道了 Spring 设计的两个大的要点:IOCAOP。从框架的设计角度而言,更为重要的是简化开发,比如提供更为便捷的配置 Bean 的方式,直至 0配置(即约定大于配置)。这里我将通过 Spring 历史版本的发展和 SpringBoot 的推出等,来帮你理解Spring 框架是如何逐步简化开发的。

3.1. Java 配置方式改造

    在前文的例子中, 是通过 xml 配置方式实现的,这种方式实际上比较麻烦,我们通过 Java 配置进行改造:

  1. User、UserDaoImpl、UserServiceImpl、LogAspect 不用改;
  2. 将原通过 xml 配置转换为 Java 配置。

增加 BeansConfig:

package com.lm.it.springframework.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import com.lm.it.springframework.aspect.LogAspect;
import com.lm.it.springframework.dao.UserDaoImpl;
import com.lm.it.springframework.service.UserServiceImpl;

/**
 * BeansConfig
 */
@EnableAspectJAutoProxy
@Configuration
public class BeansConfig {
    /**
     * @return user dao
     */
    @Bean("userDao")
    public UserDaoImpl userDao() {
        return new UserDaoImpl();
    }

    /**
     * @return user service
     */
    @Bean("userService")
    public UserServiceImpl userService() {
        UserServiceImpl userService = new UserServiceImpl();
        userService.setUserDao(userDao());
        return userService;
    }

    /**
     * @return log aspect
     */
    @Bean("logAspect")
    public LogAspect logAspect() {
        return new LogAspect();
    }
}

在 App 中加载 BeansConfig 的配置:

package com.lm.it.springframework;

import java.util.List;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import com.lm.it.springframework.config.BeansConfig;
import com.lm.it.springframework.entity.User;
import com.lm.it.springframework.service.UserServiceImpl;

/**
 * App
 */
public class App {
    /**
     * main interfaces.
     *
     * @param args args
     */
    public static void main(String[] args) {
        // create and configure beans
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(BeansConfig.class);

        // retrieve configured instance
        UserServiceImpl service = context.getBean("userService", UserServiceImpl.class);

        // use configured instance
        List<User> userList = service.findUserList();

        // print info from beans
        userList.forEach(a -> System.out.println(a.getName() + "," + a.getAge()));
    }
}

整体结构和运行 App:

3.2. 注解配置方式改造

    更进一步,Java 5 开始提供注解支持,Spring 2.5 开始完全支持基于注解的配置并且也支持JSR250 注解。在 Spring 后续的版本发展倾向于通过注解和 Java 配置结合使用。

BeanConfig 不再需要 Java 配置:

package com.lm.it.springframework.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScans;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

/**
 * BeansConfig
 */
@Configuration
@EnableAspectJAutoProxy
public class BeansConfig {

}

UserDaoImpl 增加了 @Repository 注解:

/**
 * UserDao
 */
@Repository
public class UserDaoImpl {
    /**
     * mocked to find user list.
     *
     * @return user list
     */
    public List<User> findUserList() {
        return Collections.singletonList(new User("流华追梦", 18));
    }
}

UserServiceImpl 增加了 @Service 注解,并通过 @Autowired 注入 userDao:

/**
 * UserService
 */
@Service
public class UserServiceImpl {
    /**
     * user dao impl.
     */
    @Autowired
    private UserDaoImpl userDao;

    /**
     * find user list.
     *
     * @return user list
     */
    public List<User> findUserList() {
        return userDao.findUserList();
    }
}

在 App 中扫描 com.lm.it.springframework 包:

package com.lm.it.springframework;

import java.util.List;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import com.lm.it.springframework.entity.User;
import com.lm.it.springframework.service.UserServiceImpl;

/**
 * App
 */
public class App {
    /**
     * main interfaces.
     *
     * @param args args
     */
    public static void main(String[] args) {
        // create and configure beans
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
                "com.lm.it.springframework");

        // retrieve configured instance
        UserServiceImpl service = context.getBean(UserServiceImpl.class);

        // use configured instance
        List<User> userList = service.findUserList();

        // print info from beans
        userList.forEach(a -> System.out.println(a.getName() + "," + a.getAge()));
    }
}

整体结构和运行 App:

四. SpringBoot 托管配置

    SpringBoot 实际上通过约定大于配置的方式,使用 xx-starter 统一的对 Bean 进行默认初始化,用户只需要很少的配置就可以进行开发了。很多开发者都是从 SpringBoot 开始着手开发的,所以这个比较好理解。我们需要的是将知识点都串联起来,构筑认知体系。

结合 Spring 历史版本和 SpringBoot 看发展:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/348968.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

接入技术以及互联网架构

1. 接入技术 1.1 两种物理基础设施&#xff1a;有线和无线基础设施 有线基础设施包括铜线和光纤电缆。铜线和光纤是用来传输数据的物理介质&#xff0c;其中光纤以其高速度和大容量而闻名&#xff0c;而铜线则是一种更传统的技术。 无线基础设施则包括高点&#xff08;如专门建…

时序分解 | MATLAB实现CEEMDAN+SE自适应经验模态分解+样本熵计算

时序分解 | MATLAB实现CEEMDANSE自适应经验模态分解样本熵计算 目录 时序分解 | MATLAB实现CEEMDANSE自适应经验模态分解样本熵计算效果一览基本介绍程序设计参考资料 效果一览 基本介绍 MATLAB实现CEEMDANSE自适应经验模态分解样本熵计算 包括频谱图 附赠案例数据 可直接运行 …

GPT微信机器人部署,集成gpt问答、dall e3绘画、midjourney以及新闻热搜、天气等丰富联网功能,免费入群体验!

GPT问答和midjourney作为AI届两大亮点&#xff0c;都各自有官方体验方式。 同时&#xff0c;也有很多大神搭建了各类软件、平台供用户体验使用。 但是如果同时将GPT问答和midjourney集合到日常最常使用的微信呢&#xff1f; 打造一个微信机器人&#xff0c;不仅自己可以随时…

vue3 21 数据大屏scale

数据类型 &#xff1f;&#xff1a;&#xff0c;有就指定 40.根据菜单动态生成路由&#xff0c;将路由放到store里 配置路由数据类型&#xff1a; 在template标签上做遍历的好处是&#xff0c;标签不会渲染到页面 菜单的递归&#xff08;Menu&#xff09;: 130数据大屏:

使用mapstruct实现对象拷贝

Mapstructs实现对象拷贝: 单个对象拷贝(默认只拷贝属性名和方法名都相同的值)&#xff0c;当属性名或者属性类型不同时可使用Mapping注解进行映射List拷贝List嵌套List拷贝 代码示例 import lombok.AllArgsConstructor; import lombok.Data; import org.mapstruct.Mapper; i…

Buffer Pool

Buffer Pool 概念free链表flush链表LRU链表chunk 概念 MySQL在启动时向操作系统申请的一片连续的内存&#xff0c;默认128M。然后将这块内存分为一个一个缓冲页(16KB&#xff0c;因为页就是16KB的)。再为每个缓冲页创建对应的控制块用于管理。比如第一次查询数据之后&#xff…

RockChip DRM Display Driver

资料来源: 《Rockchip_DRM_Display_Driver_Development_Guide_V1.0.pdf》 《Rockchip_Developer_Guide_DRM_Display_Driver_CN.pdf》 一:DRM概述 DRM(Direct Rendering Manager)直接渲染管理,buffer分配,帧缓冲。对应userspace库位libdrm,libdrm库提供了一系列友好的…

jmeter之接口测试实现参数化(利用函数助手),参数值为1-9(自增的数字)

1.前言 思考&#xff1a;为什么不用postman&#xff0c;用postman的话就得导入csv文件/json文件 如果不想导入文件&#xff0c;postman是实现不了&#xff0c;因为postman每次只会运行一次 2.jmeter函数助手实现参数化 &#xff08;1&#xff09;新建“线程组”--新建“http…

nginx安装ssl模块http_ssl_module

查看nginx安装的模块 /usr/local/nginx/sbin/nginx -V若出现“–with-http_ssl_module”说明已经安装过&#xff0c;否则继续执行下列步骤 进入nginx源文件目录 cd /usr/local/nginx/nginx-1.20.2重新编译nginx ./configure --with-http_ssl_module如果组件linux缺少&…

【投稿优惠|EI优质会议】2024年材料化学与清洁能源国际学术会议(IACMCCE 2024)

【投稿优惠|优质会议】2024年材料化学与清洁能源国际学术会议(IACMCCE 2024) 2024 International Conference Environmental Engineering and Mechatronics Integration(ICEEMI 2024) 一、【会议简介】 随着全球能源需求的不断增长&#xff0c;清洁能源的研究与应用成为了国际…

一文深度解读多模态大模型视频检索技术的实现与使用

当视频检索叠上大模型Buff。 万乐乐&#xff5c;技术作者 视频检索&#xff0c;俗称“找片儿”&#xff0c;即通过输入一段文本&#xff0c;找出最符合该文本描述的视频。 随着视频社会化趋势以及各类视频平台的快速兴起与发展&#xff0c;「视频检索」越来越成为用户和视频平…

Go语言指针变量

1. 指针变量 区别于C/C中的指针&#xff0c;Go语言中的指针不能进行偏移和运算&#xff0c;是安全指针。 Go语言中的指针3个概念&#xff1a;指针地址、指针类型和指针取值。 1.1. Go语言中的指针 Go语言中的函数传参都是值拷贝&#xff0c;当我们想要修改某个变量的时候&a…

Top Down - Fantasy Forest (HDRP)

适用于任何自上而下游戏的近180个风格化资产的强大集合!无论是RTS、MOBA、RPG还是Hacknslash,此包都可以让您制作完整而详细的自上而下关卡。 特点: 共计177个预制件和4个粒子效果 6 个可平铺的地形纹理 7 棵树模型 24个灌木丛、草地和植物 14个蘑菇 20个资源节点(金、铁、…

04-了解所有权

上一篇&#xff1a; 03-常用编程概念 所有权是 Rust 最独特的特性&#xff0c;对语言的其他部分有着深刻的影响。它使 Rust 可以在不需要垃圾回收器的情况下保证内存安全&#xff0c;因此了解所有权的工作原理非常重要。在本章中&#xff0c;我们将讨论所有权以及几个相关特性&…

Ngnix负载均衡

配置Ngnix环境 1.安装 创建Nginx的目录&#xff1a; mkdir /soft && mkdir /soft/nginx/ cd /home/centos/nginx下载Nginx安装包通过wget命令在线获取安装包&#xff1a; wget https://nginx.org/download/nginx-1.21.6.tar.gz解压Nginx压缩包&#xff1a; tar -x…

RT-Thread: 串口操作、增加串口、串口函数

说明&#xff1a;本文记录RT-Thread添加串口的步骤和串口的使用。 1.新增串口 官方链接&#xff1a;https://www.rt-thread.org/document/site/rtthread-studio/drivers/uart/v4.0.2/rtthread-studio-uart-v4.0.2/ 新增串口只需要在 board.h 文件中定义相关串口的宏定…

宋仕强论道之华强北的缺货潮(十六)

始于2019年缺货潮让华强北又生产一大批亿万富翁&#xff0c;缺货的原因主要是&#xff1a;首先&#xff0c;疫情封控导致大量白领在家远程办公&#xff0c;需要购买电脑、打印机等办公设备&#xff0c;同时孩子们也要在家上网课&#xff0c;进一步增加对电子智能终端产品的需求…

Java实现天沐瑜伽馆管理系统 JAVA+Vue+SpringBoot+MySQL

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 数据中心模块2.2 瑜伽课程模块2.3 课程预约模块2.4 系统公告模块2.5 课程评价模块2.6 瑜伽器械模块 三、系统设计3.1 实体类设计3.1.1 瑜伽课程3.1.2 瑜伽课程预约3.1.3 系统公告3.1.4 瑜伽课程评价 3.2 数据库设计3.2.…

口腔炎症和灼口综合征的区别有哪些?

随着社会发展的进步&#xff0c;当代人面临的压力越来越大&#xff0c;口腔疾病也随之而来。那么懂得如何分辨口腔疾病就至关重要。在我们绝大多数人眼里&#xff0c;口腔疾病都会统称为“上火”&#xff0c;但是不同的口腔疾病有不同症状和现象&#xff0c;如果没有早发现&…

C语言练习题110例(十)

91.杨辉三角 题目描述: KK知道什么叫杨辉三角之后对杨辉三角产生了浓厚的兴趣&#xff0c;他想知道杨辉三角的前n行&#xff0c;请编程帮他 解答。杨辉三角&#xff0c;本质上是二项式(ab)的n次方展开后各项的系数排成的三角形。其性质包括&#xff1a;每行的端点数为1&…