手写模拟SpringBoot核心流程

news2024/10/6 4:08:59

通过手写模拟实现一个Spring Boot,让大家能以非常简单的方式就能知道Spring Boot大概是如何工作的。
依赖
建一个工程,两个Module:

1.springboot模块,表示springboot框架的源码实现
2.user包,表示用户业务系统,用来写业务代码来测试我们所模拟出来的SpringBoot

首先,SpringBoot是基于的Spring,所以我们要依赖Spring,然后我希望我们模拟出来的SpringBoot也支持Spring MVC的那一套功能,所以也要依赖Spring MVC,包括Tomcat等,所以在SpringBoot模块中要添加以下依赖:

<dependencies>
    <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.3.18</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>5.3.18</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.3.18</version>
        </dependency>

        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>4.0.1</version>
        </dependency>

        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-core</artifactId>
            <version>9.0.60</version>
        </dependency>
</dependencies>

在User模块下我们进行正常的开发就行了,比如先添加SpringBoot依赖:

<dependencies>
    <dependency>
        <groupId>org.example</groupId>
        <artifactId>springboot</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
</dependencies>

然后定义相关的Controller和Service:

@RestController
public class UserController {

    @Autowired
    private UserService userService;

    @GetMapping("test")
    public String test(){
        return userService.test();
    }
}

因为我们模拟实现的是SpringBoot,而不是SpringMVC,所以我直接在user包下定义了UserController和UserService,最终我希望能运行MyApplication中的main方法,就直接启动了项目,并能在浏览器中正常的访问到UserController中的某个方法。
核心注解和核心类
我们在真正使用SpringBoot时,核心会用到SpringBoot一个类和注解:
1.@SpringBootApplication,这个注解是加在应用启动类上的,也就是main方法所在的类
2.SpringApplication,这个类中有个run()方法,用来启动SpringBoot应用的
所以我们也来模拟实现他们。

一个@ZhouyuSpringBootApplication注解:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Configuration
@ComponentScan
public @interface ZhouyuSpringBootApplication {
}

一个用来实现启动逻辑的ZhouyuSpringApplication类。

public class ZhouyuSpringApplication {

    public static void run(Class clazz){

    }

}

注意run方法需要接收一个Class类型的参数,这个class是用来干嘛的,等会就知道了。

有了以上两者,我们就可以在MyApplication中来使用了,比如:

@ZhouyuSpringBootApplication
public class MyApplication {

    public static void main(String[] args) {
        ZhouyuSpringApplication.run(MyApplication.class);
    }
}

现在用来是有模有样了,但中看不中用,所以我们要来好好实现以下run方法中的逻辑了。

run方法
run方法中需要实现什么具体的逻辑呢?

首先,我们希望run方法一旦执行完,我们就能在浏览器中访问到UserController,那势必在run方法中要启动Tomcat,通过Tomcat就能接收到请求了。

大家如果学过Spring MVC的底层原理就会知道,在SpringMVC中有一个Servlet非常核心,那就是DispatcherServlet,这个DispatcherServlet需要绑定一个Spring容器,因为DispatcherServlet接收到请求后,就会从所绑定的Spring容器中找到所匹配的Controller,并执行所匹配的方法。

所以,在run方法中,我们要实现的逻辑如下:
1.创建一个Spring容器
2.创建Tomcat对象
3.生成DispatcherServlet对象,并且和前面创建出来的Spring容器进行绑定
4.将DispatcherServlet添加到Tomcat中
5.启动Tomcat

创建Spring容器
这个步骤比较简单,代码如下:

public class ZhouyuSpringApplication {

    public static void run(Class clazz){
        AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext();
        applicationContext.register(clazz);
        applicationContext.refresh();
    }
}

我们创建的是一个AnnotationConfigWebApplicationContext容器,并且把run方法传入进来的class作为容器的配置类,比如在MyApplication的run方法中,我们就是把MyApplication.class传入到了run方法中,最终MyApplication就是所创建出来的Spring容器的配置类,并且由于MyApplication类上有@ZhouyuSpringBootApplication注解,而@ZhouyuSpringBootApplication注解的定义上又存在@ComponentScan注解,所以AnnotationConfigWebApplicationContext容器在执行refresh时,就会解析MyApplication这个配置类,从而发现定义了@ComponentScan注解,也就知道了要进行扫描,只不过扫描路径为空,而AnnotationConfigWebApplicationContext容器会处理这种情况,如果扫描路径会空,则会将MyApplication所在的包路径做为扫描路径,从而就会扫描到UserService和UserController。

所以Spring容器创建完之后,容器内部就拥有了UserService和UserController这两个Bean。

启动Tomcat

我们用的是Embed-Tomcat,也就是内嵌的Tomcat,真正的SpringBoot中也用的是内嵌的Tomcat,而对于启动内嵌的Tomcat,也并不麻烦,代码如下:

public static void startTomcat(WebApplicationContext applicationContext){
    
    Tomcat tomcat = new Tomcat();
    
    Server server = tomcat.getServer();
    Service service = server.findService("Tomcat");
    
    Connector connector = new Connector();
    connector.setPort(8081);
    
    Engine engine = new StandardEngine();
    engine.setDefaultHost("localhost");
    
    Host host = new StandardHost();
    host.setName("localhost");
    
    String contextPath = "";
    Context context = new StandardContext();
    context.setPath(contextPath);
    context.addLifecycleListener(new Tomcat.FixContextListener());
    
    host.addChild(context);
    engine.addChild(host);
    
    service.setContainer(engine);
    service.addConnector(connector);
    
    tomcat.addServlet(contextPath, "dispatcher", new DispatcherServlet(applicationContext));
    context.addServletMappingDecoded("/*", "dispatcher");
    
    try {
        tomcat.start();
    } catch (LifecycleException e) {
        e.printStackTrace();
    }
    
}

代码虽然看上去比较多,但是逻辑并不复杂,比如配置了Tomcat绑定的端口为8081,后面向当前Tomcat中添加了DispatcherServlet,并设置了一个Mapping关系,最后启动,其他代码则不用太过关心。

而且在构造DispatcherServlet对象时,传入了一个ApplicationContext对象,也就是一个Spring容器,就是我们前文说的,DispatcherServlet对象和一个Spring容器进行绑定。

接下来,我们只需要在run方法中,调用startTomcat即可:

public static void run(Class clazz){
    AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext();
    applicationContext.register(clazz);
    applicationContext.refresh();
    
    startTomcat(applicationContext);
    
}

实际上代码写到这,一个极度精简版的SpringBoot就写出来了,比如现在运行MyApplication,就能正常的启动项目,并能接收请求。

启动能看到Tomcat的启动日志:
在这里插入图片描述
然后在浏览器上访问:http://localhost:8081/test

也能正常的看到结果:
在这里插入图片描述

此时,你可以继续去写其他的Controller和Service了,照样能正常访问到,而我们的业务代码中仍然只用到了ZhouyuSpringApplication类和@ZhouyuSpringBootApplication注解。

实现Tomcat和Jetty的切换

虽然我们前面已经实现了一个比较简单的SpringBoot,不过我们可以继续来扩充它的功能,比如现在我有这么一个需求,这个需求就是我现在不想使用Tomcat了,而是想要用Jetty,那该怎么办?

我们前面代码中默认启动的是Tomcat,那我现在想改成这样子:
1.如果项目中有Tomcat的依赖,那就启动Tomcat
2.如果项目中有Jetty的依赖就启动Jetty
3.如果两者都没有则报错
4.如果两者都有也报错

这个逻辑希望SpringBoot自动帮我实现,对于程序员用户而言,只要在Pom文件中添加相关依赖就可以了,想用Tomcat就加Tomcat依赖,想用Jetty就加Jetty依赖。

那SpringBoot该如何实现呢?

我们知道,不管是Tomcat还是Jetty,它们都是应用服务器,或者是Servlet容器,所以我们可以定义接口来表示它们,这个接口叫做WebServer(别问我为什么叫这个,因为真正的SpringBoot源码中也叫这个)。

并且在这个接口中定义一个start方法:

public interface WebServer {
    
    public void start();
    
}

有了WebServer接口之后,就针对Tomcat和Jetty提供两个实现类:

public class TomcatWebServer implements WebServer{

    @Override
    public void start() {
        System.out.println("启动Jetty");
    }
}

public class JettyWebServer implements WebServer{

    @Override
    public void start() {
       System.out.println("启动Tomcat");
    }
}

而在ZhouyuSpringApplication中的run方法中,我们就要去获取对应的WebServer,然后启动对应的webServer,代码为:

public static void run(Class clazz){
    AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext();
    applicationContext.register(clazz);
    applicationContext.refresh();
    
    WebServer webServer = getWebServer(applicationContext);
    webServer.start();
    
}

public static WebServer getWebServer(ApplicationContext applicationContext){
    return null;
}

这样,我们就只需要在getWebServer方法中去判断到底该返回TomcatWebServer还是JettyWebServer。

前面提到过,我们希望根据项目中的依赖情况,来决定到底用哪个WebServer,我就直接用SpringBoot中的源码实现方式来模拟了。

模拟实现条件注解
首先我们得实现一个条件注解@ZhouyuConditionalOnClass,对应代码如下:

@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Conditional(ZhouyuOnClassCondition.class)
public @interface ZhouyuConditionalOnClass {
    String value() default "";
}

注意核心为@Conditional(ZhouyuOnClassCondition.class)中的ZhouyuOnClassCondition,因为它才是真正得条件逻辑:

public class ZhouyuOnClassCondition implements Condition {

    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        Map<String, Object> annotationAttributes = 
            metadata.getAnnotationAttributes(ZhouyuConditionalOnClass.class.getName());

        String className = (String) annotationAttributes.get("value");

        try {
            context.getClassLoader().loadClass(className);
            return true;
        } catch (ClassNotFoundException e) {
            return false;
        }
    }
}

具体逻辑为,拿到@ZhouyuConditionalOnClass中的value属性,然后用类加载器进行加载,如果加载到了所指定的这个类,那就表示符合条件,如果加载不到,则表示不符合条件。

模拟实现自动配置类
有了条件注解,我们就可以来使用它了,那如何实现呢?

这里就要用到自动配置类的概念,我们先看代码:

@Configuration
public class WebServiceAutoConfiguration {

    @Bean
    @ZhouyuConditionalOnClass("org.apache.catalina.startup.Tomcat")
    public TomcatWebServer tomcatWebServer(){
        return new TomcatWebServer();
    }

    @Bean
    @ZhouyuConditionalOnClass("org.eclipse.jetty.server.Server")
    public JettyWebServer jettyWebServer(){
        return new JettyWebServer();
    }
}

这个代码还是比较简单的,通过一个WebServiceAutoConfiguration的Spring配置类,在里面定义了两个Bean,一个TomcatWebServer,一个JettyWebServer,不过这两个要生效的前提是符合当前所指定的条件,比如:
1.只有存在"org.apache.catalina.startup.Tomcat"类,那么才有TomcatWebServer这个Bean
2.只有存在"org.eclipse.jetty.server.Server"类,那么才有TomcatWebServer这个Bean

并且我们只需要在ZhouyuSpringApplication中getWebServer方法,如此实现:

public static WebServer getWebServer(ApplicationContext applicationContext){
    // key为beanName, value为Bean对象
    Map<String, WebServer> webServers = applicationContext.getBeansOfType(WebServer.class);
    
    if (webServers.isEmpty()) {
        throw new NullPointerException();
    }
    if (webServers.size() > 1) {
        throw new IllegalStateException();
    }
    
    // 返回唯一的一个
    return webServers.values().stream().findFirst().get();
}

这样整体SpringBoot启动逻辑就是这样的:
1.创建一个AnnotationConfigWebApplicationContext容器
2.解析MyApplication类,然后进行扫描
3.通过getWebServer方法从Spring容器中获取WebServer类型的Bean
4.调用WebServer对象的start方法

有了以上步骤,我们还差了一个关键步骤,就是Spring要能解析到WebServiceAutoConfiguration这个自动配置类,因为不管这个类里写了什么代码,Spring不去解析它,那都是没用的,此时我们需要SpringBoot在run方法中,能找到WebServiceAutoConfiguration这个配置类并添加到Spring容器中。

MyApplication是Spring的一个配置类,但是MyApplication是我们传递给SpringBoot,从而添加到Spring容器中去的,而WebServiceAutoConfiguration就需要SpringBoot去自动发现,而不需要程序员做任何配置才能把它添加到Spring容器中去,而且要注意的是,Spring容器扫描也是扫描不到WebServiceAutoConfiguration这个类的,因为我们的扫描路径是"com.zhouyu.user",而WebServiceAutoConfiguration所在的包路径为"com.zhouyu.springboot"。

那SpringBoot中是如何实现的呢?通过SPI,当然SpringBoot中自己实现了一套SPI机制,也就是我们熟知的spring.factories文件,那么我们模拟就不搞复杂了,就直接用JDK自带的SPI机制。

发现自动配置类
为了实现这个功能,以及为了最后的效果演示,我们需要把springboot源码和业务代码源码拆分两个maven模块,也就相当于两个项目,最后的源码结构为:
在这里插入图片描述

现在我们只需要在springboot项目中的resources目录下添加如下目录(META-INF/services)和文件:
在这里插入图片描述

SPI的配置就完成了,相当于通过com.zhouyu.springboot.AutoConfiguration文件配置了springboot中所提供的配置类。

并且提供一个接口:

public interface AutoConfiguration {
}

并且WebServiceAutoConfiguration实现该接口:

@Configuration
public class WebServiceAutoConfiguration implements AutoConfiguration {

    @Bean
    @ZhouyuConditionalOnClass("org.apache.catalina.startup.Tomcat")
    public TomcatWebServer tomcatWebServer(){
        return new TomcatWebServer();
    }

    @Bean
    @ZhouyuConditionalOnClass("org.eclipse.jetty.server.jetty")
    public JettyWebServer jettyWebServer(){
        return new JettyWebServer();
    }
}

然后我们再利用spring中的@Import技术来导入这些配置类,我们在@ZhouyuSpringBootApplication的定义上增加如下代码:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Configuration
@ComponentScan
@Import(ZhouyuImportSelect.class)
public @interface ZhouyuSpringBootApplication {
}

ZhouyuImportSelect类为:

public class ZhouyuImportSelect implements DeferredImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        ServiceLoader<AutoConfiguration> serviceLoader = ServiceLoader.load(AutoConfiguration.class);

        List<String> list = new ArrayList<>();
        for (AutoConfiguration autoConfiguration : serviceLoader) {
            list.add(autoConfiguration.getClass().getName());
        }

        return list.toArray(new String[0]);
    }
}

这就完成了从com.zhouyu.springboot.AutoConfiguration文件中获取自动配置类的名字,并导入到Spring容器中,从而Spring容器就知道了这些配置类的存在,而对于user项目而言,是不需要修改代码的。

此时运行MyApplication,就能看到启动了Tomcat:

因为SpringBoot默认在依赖中添加了Tomcat依赖,而如果在User模块中再添加jetty的依赖:

<dependencies>
    <dependency>
        <groupId>org.example</groupId>
        <artifactId>springboot</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>

    <dependency>
        <groupId>org.eclipse.jetty</groupId>
        <artifactId>jetty-server</artifactId>
        <version>9.4.43.v20210629</version>
    </dependency>
</dependencies>

那么启动MyApplication就会报错:
在这里插入图片描述

只有先排除到Tomcat的依赖,再添加Jetty的依赖才能启动Jetty:
在这里插入图片描述

注意:由于没有了Tomcat的依赖,记得把最开始写的startTomcat方法给注释掉,并删除掉相关依赖。

总结
到此,我们实现了一个简单版本的SpringBoot,因为SpringBoot首先是基于Spring的,而且提供的功能也更加强大,随着后续内容的展开,相信大家会对本文中的各个功能会有更加深刻的理解,也希望大家都自己去实现一边

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

iview表格 异步修改列数据卡顿 滚动条失效

使用表格row-key属性 将row-key属性设置为true <Table ref"table" border :row-key"true" :columns"tableColumns" :loading"loading":data"tableData"></Table>

web基础及http协议

web基础 全称 world wide web 全球广域网也就是万维网 web1.0 只能看 web2.0 页面交互&#xff1a;静态页面和动态页面 静态页面url&#xff1a;文本文件&#xff0c;可以修改&#xff0c;一般以html .htm保存的文本文件。网站的基础。静态页面和后台数据库没有任何交互不包含…

接口自动化测试方案模版。希望可以帮到你

XXX接口自动化测试方案 1、引言 1.1 文档版本 版本 作者 审批 备注 V1.0 XXXX 创建测试方案文档 1.2 项目情况 项目名称 XXX 项目版本 V1.0 项目经理 XX 测试人员 XXXXX&#xff0c;XXX 所属部门 XX 备注 1.3 文档目的 本文档主要用于指导XXX-Y…

匠心传承,长期主义 | 竹云董事长董宁受邀出席大湾区品牌新消费论坛

10月8日&#xff0c;大湾区品牌新消费论坛在深圳落下帷幕&#xff0c;此次论坛以“酿造美好生活”为主题&#xff0c;由广东省粤港澳大湾区产业协同发展联合会、张支云酒业集团主办&#xff0c;香港大湾区工商业联合会、深圳市老字号协会协办。 深圳市人大常委会教科文卫工委主…

红队专题-REVERSE二进制逆向反编译

红队专题 招募六边形战士队员IDA pro安装python2加入环境变量py2安装pip安装IDA 7.0 proIDAPython: importing "site" failed. 招募六边形战士队员 一起学习 代码审计、安全开发、web攻防、逆向等。。。 私信联系 IDA pro 安装python2 python-2.7.3.msi 加入环…

点云曲面重建

曲面重建技术在逆向工程、数据可视化、机器视觉、虚拟现实、医疗技术等领域中得到了广泛的应用 。 例如&#xff0c;在汽车、航空等工业领域中&#xff0c;复杂外形产品的设计仍需要根据手工模型&#xff0c;采用逆向工程的手段建立产品的数字化模型&#xff0c;根据测量数据建…

时代风口中的Web3.0基建平台,重新定义Web3.0!

近年来&#xff0c;Web3.0概念的广泛兴起&#xff0c;给加密行业带来了崭新的叙事方式&#xff0c;同时也为加密行业提供了更加具有想象力的应用场景与商业空间&#xff0c;并让越来越多的行业从业者们意识到只有更大众化的市场共性需求才能推动加密市场的持续繁荣。当前围绕这…

软件测试/测试开发丨App自动化—高级控件交互方法

点此获取更多相关资料 本文为霍格沃兹测试开发学社学员学习笔记分享 原文链接&#xff1a;https://ceshiren.com/t/topic/27687 一、Actions Actions执行一系列或多个键盘和指针&#xff08;触摸、鼠标、触控笔&#xff09;操作链w3c 二、用法 定义 ActionChains 实例定义输…

种草文案怎么写吸引人?纯干货

种草文案的最终目的是激发用户的购买欲望&#xff0c;使他们产生购买行为。为了实现这一目标&#xff0c;种草文案应当抓住用户的痛点&#xff0c;突出产品的优势&#xff0c;让用户感受到产品的实用价值。此外&#xff0c;种草文案还应注重情感诉求&#xff0c;通过生动的故事…

Bean的作用域和生命周期(Bean是线程安全的吗?Spring如何在并发情况下获取不完整的Bean...)

Bean 注解是 Spring 框架中的一个注解&#xff0c;用于告诉 Spring 容器需要将被注解修饰的方法的返回值注册为一个 Bean。通常情况下&#xff0c;Spring 容器会自动扫描并创建带有 Component 或其他类似注解的类&#xff0c;并将这些类的实例注册为 Bean。但对于某些特殊情况&…

iPhone手机上使用的定时提醒APP是哪个

在日常喧闹的生活和工作中&#xff0c;琐碎的任务会像喷泉一样突涌而至&#xff0c;如不及时规划&#xff0c;我们将陷入手足无措的境地。而想要让各项工作任务按时完成&#xff0c;我们可以借助一些比较好用的时间提醒软件来督促各项任务。 就拿常用的iPhone手机来讲&#xf…

Tomcat隔离web原理和热加载热部署

Tomcat 如何打破双亲委派机制 Tomcat 的自定义类加载器 WebAppClassLoader 打破了双亲委派机制&#xff0c;它首先自己尝试去加载某个类&#xff0c;如果找不到再代理给父类加载器&#xff0c;其目的是优先加载 Web 应用自己定义的类。具体实现就是重写 ClassLoader 的两个方法…

九小场所“一店一码”消防安全监管如何制作

将九小场所信息在凡尔码平台生成消防安全码落地粘贴场所&#xff0c;微信扫码了解场所以往消防安全检查情况、整改情况&#xff1b;同时也可以了解学习消防安全知识&#xff1b;“一店一码”实现场所消防安全动态管理。 监管部门检查微信扫码即可填写检查记录&#xff0c;有隐…

【智能家居项目】裸机版本——网卡设备接入输入子系统 | 业务子系统 | 整体效果展示

&#x1f431;作者&#xff1a;一只大喵咪1201 &#x1f431;专栏&#xff1a;《智能家居项目》 &#x1f525;格言&#xff1a;你只管努力&#xff0c;剩下的交给时间&#xff01; 目录 &#x1f95e;网卡设备接入输入子系统&#x1f354;测试 &#x1f95e;业务子系统&#…

Astronomaly:利用 CNN 和主动学习识别 400 万张星系图像中的异常

星系中的异常现象是我们了解宇宙的关键。然而&#xff0c;随着天文观测技术的发展&#xff0c;天文数据正以指数级别增长&#xff0c;超出了天文工作者的分析能力。 尽管志愿者可以在线上参与对天文数据的处理&#xff0c;但他们只能进行一些简单的分类&#xff0c;还可能会遗漏…

数字孪生与GIS数据为何高度互补?二者融合后能达到什么样的效果?

山海鲸可视化作为一款数字孪生软件&#xff0c;在GIS的融合方面处于业内领先水平&#xff0c;那么为什么一款数字孪生软件要花费巨大的精力&#xff0c;去实现GIS的融合&#xff0c;实现后又能达到什么样的效果呢&#xff1f;下面就让我们来一探究竟。 一、为什么数字孪生需要…

OpenCV级联分类器识别车辆实践笔记

1. OpenCV 级联分类器的基本原理 基于Haar特征的级联分类器的目标检测是Paul Viola和Michael Jones在2001年的论文中提出的一种有效的目标检测方法。这是一种基于机器学习的方法&#xff0c;从大量的正面和负面图像中训练级联函数。然后用它来检测其他图像中的物体。 Haar特征…

终于找到了!多种类型的电子期刊模板在这里!

经过我不懈的努力和搜寻&#xff0c;终于找到了一个提供多种类型电子期刊模板的网站。这个网站拥有丰富多样的模板&#xff0c;可以满足各种不同的需求&#xff0c;无论是学术研究、商业报告还是个人兴趣爱好&#xff0c;都能在这里找到心仪的模板。 一、网站介绍 这个网站叫做…

弧形进度条,弧形百分比

要帮助同事写一个弧度的进度条&#xff0c;进度条顶部有一个小圆&#xff0c;具体如下 需要指出的是&#xff0c;我们canvas的绘制是需要弧度&#xff0c;所以我们代码中使用角度&#xff0c;等待绘制的时候再砖话为弧度值 <!DOCTYPE html> <html lang"en"…

【MATLAB源码-第45期】基于matlab的16APSK调制解调仿真,使用卷积编码软判决。

操作环境&#xff1a; MATLAB 2022a 1、算法描述 1. 16APSK调制解调 16APSK (16-ary Amplitude Phase Shift Keying) 是一种相位调制技术&#xff0c;其基本思想是在恒定幅度的条件下&#xff0c;改变信号的相位&#xff0c;从而传送信息。 - 调制&#xff1a;在16APSK中&am…