spring boot运行过程中动态加载Controller

news2024/11/21 2:33:47

1.被加载的jar代码

package com.dl;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class App {
    public static void main(String[] args) {
        SpringApplication.run(App.class, args);
    }
}
package com.dl;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class JarController {


    @RequestMapping("/jar")
    String jar() {
        return "i  am  a  jar";
    }


}

<?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.dl</groupId>
    <artifactId>jar</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>Spring Boot Blank Project (from https://github.com/making/spring-boot-blank)</name>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.12</version>
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <start-class>com.dl.App</start-class>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <excludes>
                        <!-- 去除指定的类-->
                        <exclude>**/App.java</exclude>
                    </excludes>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <version>2.6</version>
                <configuration>
                    <archive>
                        <addMavenDescriptor>false</addMavenDescriptor>
                    </archive>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

工程结构
在这里插入图片描述

2.实现代码

package com.dl;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class App {
    public static void main(String[] args) {
        SpringApplication.run(App.class, args);
    }
}
package com.dl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

/**
 * 测试
 */
@RestController
public class HelloController {

    private final TestDynamicLoad testDynamicLoad;

    @Autowired
    public HelloController(TestDynamicLoad testDynamicLoad) {
        this.testDynamicLoad = testDynamicLoad;
    }

    @RequestMapping("/")
    String hello() {
        return "Hello";
    }

    /**
     *
     * @param path  jar文件的路径
     * @param fileName  jar文件的名称
     * @return  加载结果
     */
    @RequestMapping("/load")
    String load(@RequestParam String path, @RequestParam String fileName) {
        try {
            testDynamicLoad.loadJar(path,fileName);
        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
            return "失败:"+e.getMessage();
        }
        return "加载成功";
    }


    /**
     *
     * @param name 卸载jar的名称
     * @return
     */
    @RequestMapping("/unload")
    String unload(@RequestParam String name) {
        try {
            testDynamicLoad.unloadJar(name);
        } catch (IllegalAccessException | NoSuchFieldException e) {
            e.printStackTrace();
            return "失败:"+e.getMessage();
        }
        return "卸载成功";
    }

}

package com.dl;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 自定义类加载器
 */
public class TestClassLoader  extends URLClassLoader{
    private Map<String, Class<?>> loadedClasses = new ConcurrentHashMap<>();

    public Map<String, Class<?>> getLoadedClasses() {
        return loadedClasses;
    }

    public TestClassLoader(URL[] urls, ClassLoader parent) {
        super(urls, parent);
    }

    //加载
    @Override
    protected Class<?> findClass(String name) {
        // 从已加载的类集合中获取指定名称的类
        Class<?> clazz = loadedClasses.get(name);
        if (clazz != null) {
            return clazz;
        }
        try {
            // 调用父类的findClass方法加载指定名称的类
            clazz = super.findClass(name);
            // 将加载的类添加到已加载的类集合中
            loadedClasses.put(name, clazz);
            return clazz;
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
            return null;
        }
    }

    //卸载
    public void unload() {
        try {
            for (Map.Entry<String, Class<?>> entry : loadedClasses.entrySet()) {
                // 从已加载的类集合中移除该类
                String className = entry.getKey();
                loadedClasses.remove(className);
                try{
                    // 调用该类的destory方法,回收资源
                    Class<?> clazz = entry.getValue();
                    Method destory = clazz.getDeclaredMethod("destory");
                    destory.invoke(clazz);
                } catch (Exception e ) {
                    // 表明该类没有destory方法
                }
            }
            // 从其父类加载器的加载器层次结构中移除该类加载器
            close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

package com.dl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

@Component
public class TestDynamicLoad {

    @Autowired
    private ApplicationContext applicationContext;

    private Map<String, TestClassLoader> myClassLoaderCenter = new ConcurrentHashMap<>();



    /**
     * 动态加载指定路径下指定jar包
     * @param path
     * @param fileName
     */
    public void loadJar(String path, String fileName) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        //获取jar文件
        File file = new File(path +"/" + fileName);
        // 获取beanFactory
        DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) applicationContext.getAutowireCapableBeanFactory();

        try {
            //创建URLConnection
            URL url = new URL("jar:file:" + file.getAbsolutePath() + "!/");
            URLConnection urlConnection = url.openConnection();
            JarURLConnection jarURLConnection = (JarURLConnection)urlConnection;
            // 获取jar文件
            JarFile jarFile = jarURLConnection.getJarFile();
            Enumeration<JarEntry> entries = jarFile.entries();
            // 创建自定义类加载器,并加到map中方便管理
            TestClassLoader myClassloader = new TestClassLoader(new URL[] { url }, ClassLoader.getSystemClassLoader());
            myClassLoaderCenter.put(fileName, myClassloader);
            // 遍历文件
            while (entries.hasMoreElements()) {
                JarEntry jarEntry = entries.nextElement();
                if (jarEntry.getName().endsWith(".class")) {
                    // 1. 加载类到jvm中
                    // 获取类的全路径名
                    String className = jarEntry.getName().replace('/', '.').substring(0, jarEntry.getName().length() - 6);
                    // 1.1进行反射获取
                    myClassloader.loadClass(className);
                }
            }
            Map<String, Class<?>> loadedClasses = myClassloader.getLoadedClasses();
            for(Map.Entry<String, Class<?>> entry : loadedClasses.entrySet()){
                String className = entry.getKey();
                Class<?> clazz = entry.getValue();
                // 此处beanName使用全路径名是为了防止beanName重复
                String packageName = className.substring(0, className.lastIndexOf(".") + 1);
                String beanName = className.substring(className.lastIndexOf(".") + 1);
                beanName = packageName + beanName.substring(0, 1).toLowerCase() + beanName.substring(1);
                // 2. 将有@spring注解的类交给spring管理
                // 2.1 判断类的类型
                String flag = hasSpringAnnotation(clazz);
                if(!flag.equals("pass")){
                    // 2.2交给spring管理
                    BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(clazz);
                    AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();
                    // 2.3注册到spring的beanFactory中
                    beanFactory.registerBeanDefinition(beanName, beanDefinition);
                    // 2.4允许注入和反向注入
                    beanFactory.autowireBean(clazz);
                    beanFactory.initializeBean(clazz, beanName);
                    // 2.5手动构建实例,并注入base service 防止卸载之后不再生成
                    Object obj = clazz.newInstance();
                    beanFactory.registerSingleton(beanName, obj);
                    //3.特殊处理
                    //3.1不同的spring核心类不同的处理,实例中只是写了contrller
                    handle(flag,beanName);
                }

            }
        } catch (IOException e) {
            e.printStackTrace();
            throw new RuntimeException("读取jar文件异常: " + fileName);
        }
    }


    /**
     * 判断一个类是具体类型,如果是spring核心类需要交给spring管理
     * @param clazz 要检查的类
     * @return string 如果该类上添加了相应的 Spring 注解返回对应标识;否则返回 pass
     */
    private  String hasSpringAnnotation(Class<?> clazz) {
        if (clazz == null) {
            return "pass";
        }
        //是否是接口
        if (clazz.isInterface()) {
            return "pass";
        }
        //是否是抽象类
        if (Modifier.isAbstract(clazz.getModifiers())) {
            return "pass";
        }
        //常规注解效验和处理
        try {
            if (clazz.getAnnotation(Component.class) != null ) {
                return "Component";
            }
            if (clazz.getAnnotation(Repository.class) != null) {
                return "Repository";
            }
            if (clazz.getAnnotation(Service.class) != null ) {
                return "Service";
            }
            if (clazz.getAnnotation(Configuration.class) != null ) {
                return "Configuration";
            }
            if (clazz.getAnnotation(Controller.class) != null || clazz.getAnnotation(RestController.class) != null) {
                return "Controller";
            }

        }catch (Exception e){
            e.printStackTrace();
        }
        return "pass";
    }
    /**
     * 处理类
     * @param type 类型标识
     * @param name bean名称
     */
    private  void handle(String type ,String name){
        //这里只做了contrller类型标识的处理
        if(type.equals("Controller")){
            RequestMappingHandlerMapping handlerMapping = applicationContext.getBean(RequestMappingHandlerMapping.class);
            // 注册Controller
            Method method = null;
            try {
                method = handlerMapping.getClass().getSuperclass().getSuperclass().
                        getDeclaredMethod("detectHandlerMethods", Object.class);
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            }
            // 将private改为可使用
            assert method != null;
            method.setAccessible(true);
            try {
                method.invoke(handlerMapping, name);
            } catch (IllegalAccessException | InvocationTargetException e) {
                e.printStackTrace();
            }
        }

    }

    /**
     * 动态卸载
     * @param name  卸载jar的名称
     */
    public void unloadJar(String name) throws IllegalAccessException, NoSuchFieldException {
        // 获取加载当前jar的类加载器
        TestClassLoader myClassLoader = myClassLoaderCenter.get(name);
        // 获取beanFactory,准备从spring中卸载
        DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) applicationContext.getAutowireCapableBeanFactory();
        Map<String, Class<?>> loadedClasses = myClassLoader.getLoadedClasses();
        Set<String> beanNames = new HashSet<>();
        for (Map.Entry<String, Class<?>> entry: loadedClasses.entrySet()) {
            // 截取beanName
            String key = entry.getKey();
            String packageName = key.substring(0, key.lastIndexOf(".") + 1);
            String beanName = key.substring(key.lastIndexOf(".") + 1);
            beanName = packageName + beanName.substring(0, 1).toLowerCase() + beanName.substring(1);

            // 获取bean,如果获取失败,表名这个类没有加到spring容器中,则跳出本次循环
            Object bean = null;
            try{
                bean = applicationContext.getBean(beanName);
            }catch (Exception e){
                // 异常说明spring中没有这个bean
                continue;
            }
            // 从spring中移除,这里的移除是仅仅移除的bean,并未移除bean定义
            beanNames.add(beanName);
            beanFactory.destroyBean(beanName, bean);
        }
        // 移除bean定义
        Field mergedBeanDefinitions = beanFactory.getClass()
                .getSuperclass()
                .getSuperclass().getDeclaredField("mergedBeanDefinitions");
        mergedBeanDefinitions.setAccessible(true);
        Map<String, RootBeanDefinition> rootBeanDefinitionMap = ((Map<String, RootBeanDefinition>) mergedBeanDefinitions.get(beanFactory));
        for (String beanName : beanNames) {
            beanFactory.removeBeanDefinition(beanName);
            // 父类bean定义去除
            rootBeanDefinitionMap.remove(beanName);
        }
        // 从类加载中移除
        try {
            // 从类加载器底层的classes中移除连接
            Field field = ClassLoader.class.getDeclaredField("classes");
            field.setAccessible(true);
            Vector<Class<?>> classes = (Vector<Class<?>>) field.get(myClassLoader);
            classes.removeAllElements();
            // 移除类加载器的引用
            myClassLoaderCenter.remove(name);
            // 卸载类加载器
            myClassLoader.unload();
        } catch (NoSuchFieldException | IllegalAccessException e) {
            e.printStackTrace();
        }
    }

}
<?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.dl</groupId>
    <artifactId>li</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>Spring Boot Blank Project (from https://github.com/making/spring-boot-blank)</name>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.12</version>
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <start-class>com.dl.App</start-class>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.6.0</version>
            </plugin>
        </plugins>
    </build>

</project>

3.测试

①启动项目
在这里插入图片描述

②测试url
在这里插入图片描述

③没有加载jar前
在这里插入图片描述

④加载jar
在这里插入图片描述

⑤加载后验证
在这里插入图片描述

⑥卸载jar
在这里插入图片描述

⑦验证
在这里插入图片描述

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

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

相关文章

自学Vue3 Day2

一、组合式Api组件通信 1.父与子之间 父传子&#xff1a;父导入子组件&#xff0c;定义好数据&#xff0c;子组件用props接收&#xff0c;这里defineProps底层本质还是props. 注意模板渲染过程不需要写props 子传 父&#xff1a; 2.模版引用&#xff08;ref&#xff09;和组…

学习VUE2第6天

一.请求拦截器 可以节流&#xff0c;防止多次点击请求 toast是单例 二.前置路由守卫 在Vue.js中&#xff0c;前置路由守卫是指在路由转换实际发生之前执行的钩子函数。这是Vue Router&#xff08;Vue.js官方的路由管理器&#xff09;提供的一种功能&#xff0c;允许开发者在用…

Django后台项目开发实战四

用户可以浏览工作列表以及工作详情 第四阶段 在 jobs 文件夹下创建 templates 文件夹&#xff0c;在里面创建 base.html 网页&#xff0c;内容如下 <!-- base.html --> <div style"text-align:center;"><h1 style "margin:auto; width:50%;&…

DS高阶:图论基础知识

一、图的基本概念及相关名词解释 1.1 图的基本概念 图是比线性表和树更为复杂且抽象的结&#xff0c;和以往所学结构不同的是图是一种表示型的结构&#xff0c;也就是说他更关注的是元素与元素之间的关系。下面进入正题。 图是由顶点集合及顶点间的关系组成的一种数据结构&…

基于网络爬虫技术的网络新闻分析参考论文(论文 + 源码)

【免费】基于网络爬虫技术的网络新闻分析系统.zip资源-CSDN文库https://download.csdn.net/download/JW_559/89248815 基于网络爬虫技术的网络新闻分析 摘 要 自从大数据的概念被提出后&#xff0c;互联网数据成为了越来越多的科研单位进行数据挖掘的对象。网络新闻数据占据了…

EasyRecovery2024汉化版电脑数据恢复软件下载

EasyRecovery是一款功能强大的数据恢复软件&#xff0c;其主要功能包括但不限于以下几点&#xff1a; 硬盘数据恢复&#xff1a;能够扫描本地计算机中的所有卷&#xff0c;建立丢失和被删除文件的目录树&#xff0c;实现硬盘格式化、重新分区、误删数据、重建RAID等硬盘数据恢…

RTSP,RTP,RTCP

机器学习 Machine Learning&#xff08;ML&#xff09; 深度学习&#xff08;DL&#xff0c;Deep Learning&#xff09; CV计算机视觉&#xff08;computer vision&#xff09; FFMPEG&#xff0c;MPEG2-TS,H.264,H.265,AAC rstp,rtp,rtmp,webrtc onvif,gb28181 最详细的音…

数智新重庆 | 推进信号升格 打造算力山城

2024年&#xff0c;是实现“十四五”规划目标任务的关键一年&#xff0c;高质量的5G网络、强大的AI能力作为新质生产力的重要组成部分&#xff0c;将有效赋能包括制造业在内的千行万业数字化化、智能化、绿色化转型升级&#xff0c;推动融合应用新业态、新模式蓬勃兴起&#xf…

上传jar到github仓库,作为maven依赖存储库

记录上传maven依赖包到github仓库问题 利用GitHubPackages作为依赖的存储库踩坑1 仓库地址问题踩坑2 Personal access tokens正确姿势一、创建一个普通仓库&#xff0c;比如我这里是fork的腾讯Shadow到本地。地址是&#xff1a;https://github.com/dhs964057117/Shadow二、生成…

Postgresql 从小白到高手 十一 :数据迁移ETL方案

文章目录 Postgresql 数据迁移ETL方案1、Pg 同类型数据库2 、Pg 和 不同数据库 Postgresql 数据迁移ETL方案 1、Pg 同类型数据库 备份 : pg_dump -U username -d dbname -f backup.sql插入数据&#xff1a; psql -U username -d dbname -f backup.sqlpg_restore -U username…

31.Gateway网关-跨域问题

跨域 1.域名不同&#xff1a;www.baidu.com和www.taobao.com,www.taobao.org 2.域名相同&#xff0c;端口不同。localhost:8080和localhost:8081 跨域问题 浏览器禁止请求的发起者与服务端发生跨域ajax请求&#xff0c;请求被浏览器拦截的问题。 解决方案 CORS 浏览器询…

实操——使用uploadify插件(php版和Java版) 与 Dropzone.js插件分别实现附件上传

实操——使用uploadify插件&#xff08;php版和Java版&#xff09;与 Dropzone.js插件分别实现附件上传 1. 使用uploadify插件上传1.1 简介1.1.1 简介1.1.2 参考GitHub 1.2 后端PHP版本的uploadify1.2.1 下载项目的目录结构1.2.2 测试看界面效果1.2.3 附页面代码 和 PHP代码 1.…

操作DOM:性能优化之道

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

【webrtc】MessageHandler 3: 基于线程的消息处理:以sctp测试为例

消息处理可以用于模拟发包处理G:\CDN\rtcCli\m98\src\net\dcsctp\socket\dcsctp_socket_network_test.cc 这个实现中,onMessage还是仅对了一种消息进行处理,就是接收则模式下,打印带宽。当然,可能程序有多个消息,分别在不同的onmessage中执行?SctpActor:以一个恒定的速率…

u盘量产工具拥有分区功能,它把一个U盘分成数个移动盘,更改U盘介质类型(Fixed 和 Removabe),供大家学习研究参考~

非常受欢迎的u盘量产工具。最新版拥有分区功能&#xff0c;它把一个U盘分成数个移动盘&#xff0c;更改U盘介质类型(Fixed 和 Removabel)。数码之家量产工具官方版不是数据恢复&#xff0c;是对U盘底层硬件信息的恢复(非硬件损坏)&#xff0c;使因为底层硬件信息受损电脑无法识…

正点原子[第二期]Linux之ARM(MX6U)裸机篇学习笔记-8.2-链接脚本

前言&#xff1a; 本文是根据哔哩哔哩网站上“正点原子[第二期]Linux之ARM&#xff08;MX6U&#xff09;裸机篇”视频的学习笔记&#xff0c;在这里会记录下正点原子 I.MX6ULL 开发板的配套视频教程所作的实验和学习笔记内容。本文大量引用了正点原子教学视频和链接中的内容。…

如何利用仪表构造InfiniBand流量在数据中心测试中的应用

一、什么是Infiniband&#xff1f; 在当今数据爆炸的时代&#xff0c;数据中心作为信息处理的中心枢纽&#xff0c;面临着前所未有的挑战。传统的通信方式已经难以满足日益增长的数据传输需求&#xff0c;而InfiniBand技术的出现&#xff0c;为数据中心带来了全新的通信解决方…

在AndroidStudio创建Flutter项目并运行到模拟器

1.Flutter简介 Flutter是Google开源的构建用户界面&#xff08;UI&#xff09;工具包&#xff0c;帮助开发者通过一套代码库高效构建多平台精美应用&#xff0c;支持移动、Web、桌面和嵌入式平台。Flutter 开源、免费&#xff0c;拥有宽松的开源协议&#xff0c;适合商…

如何提升制造设备文件汇集的可靠性和安全性?

制造设备文件汇集通常指的是将与制造设备相关的各种文档和资料进行整理和归档的过程。这些文件可能包括但不限于&#xff1a; 生产数据&#xff1a;包括生产计划、订单信息、生产进度等。 设计文件&#xff1a;如CAD图纸、设计蓝图、产品模型等。 工艺参数&#xff1a;用于指…

Django后台项目开发实战五

完成两个功能&#xff1a; HR 可以维护候选人信息面试官可以录入面试反馈 第五阶段 创建 interview 应用&#xff0c;实现候选人面试评估表的增删改功能&#xff0c;并且按照页面分组来展示不同的内容&#xff0c;如候选人基础信息&#xff0c;一面&#xff0c;二面的面试结…