`
bianhao92115
  • 浏览: 2362 次
最近访客 更多访客>>
文章分类
社区版块
存档分类
最新评论

带你手写基于 Spring 的可插拔式 RPC 框架(四)代理类的注入与服务启动

阅读更多

上一章节我们已经实现了从客户端往服务端发送数据并且通过反射方法调用服务端的实现类最后返回给客户端的底层协议。
这一章节我们来实现客户端代理类的注入。

承接上一章,我们实现了多个底层协议,procotol 有 netty,http,和 socket 三个实现类,每个实现类都有启动服务端和客户端发送数据两个方法。

问题

  1. 如何实现底层协议的选择那?
    可以通过配置文件来选择协议。
  2. 单独的配置文件还是和 Spring 的配置文件结合起来那?
    我们选择与 Spring 结合的配置文件,自定义一些属性的标签,这样能够更好的利用 Spring 的特性。

自定义 Spring 标签

先看整体的结构:

  1. 写一个 xsd 文件来自定义我们的标签和属性,注意 schema 的 xmlns 和
    targetNamespace 属性, http://paul.com/schema。

    Copy
    <xsd:schema
             xmlns="http://paul.com/schema"
             xmlns:xsd="http://www.w3.org/2001/XMLSchema"
             targetNamespace="http://paul.com/schema">
         <xsd:complexType name="procotol-type">
             <xsd:attribute name="procotol" type="xsd:string">
                 <xsd:annotation>
                     <xsd:documentation><![CDATA[ The elementname1 name. ]]></xsd:documentation>
                 </xsd:annotation>
             </xsd:attribute>
             <xsd:attribute name="port" type="xsd:int">
                 <xsd:annotation>
                     <xsd:documentation><![CDATA[ The elementname1 age. ]]></xsd:documentation>
                 </xsd:annotation>
             </xsd:attribute>
             <xsd:attribute name="serialize" type="xsd:string">
             <xsd:annotation>
                 <xsd:documentation><![CDATA[ The elementname1 age. ]]></xsd:documentation>
             </xsd:annotation>
             </xsd:attribute>
             <xsd:attribute name="stragety" type="xsd:string">
                 <xsd:annotation>
                     <xsd:documentation><![CDATA[ The elementname1 age. ]]></xsd:documentation>
                 </xsd:annotation>
              </xsd:attribute>
             <xsd:attribute name="role" type="xsd:string">
                 <xsd:annotation>
                     <xsd:documentation><![CDATA[ The elementname1 age. ]]></xsd:documentation>
                 </xsd:annotation>
             </xsd:attribute>
             <xsd:attribute name="address" type="xsd:string">
                 <xsd:annotation>
                     <xsd:documentation><![CDATA[ The elementname1 age. ]]></xsd:documentation>
                 </xsd:annotation>
             </xsd:attribute>
         </xsd:complexType>
    
         <xsd:element name="procotol" type="procotol-type">
             <xsd:annotation>
                 <xsd:documentation><![CDATA[ elementname1的文档 ]]></xsd:documentation>
             </xsd:annotation>
         </xsd:element>
    
         <xsd:complexType name="application-type">
             <xsd:attribute name="name" type="xsd:string">
                 <xsd:annotation>
                     <xsd:documentation><![CDATA[ The elementname1 name. ]]></xsd:documentation>
                 </xsd:annotation>
             </xsd:attribute>
         </xsd:complexType>
    
         <xsd:element name="application" type="application-type">
             <xsd:annotation>
                 <xsd:documentation><![CDATA[ elementname1的文档 ]]></xsd:documentation>
             </xsd:annotation>
         </xsd:element>
    
         <xsd:complexType name="service-type">
             <xsd:attribute name="interfaces" type="xsd:string">
                 <xsd:annotation>
                     <xsd:documentation><![CDATA[ The elementname1 name. ]]></xsd:documentation>
                 </xsd:annotation>
             </xsd:attribute>
             <xsd:attribute name="ref" type="xsd:string">
                 <xsd:annotation>
                     <xsd:documentation><![CDATA[ The elementname1 name. ]]></xsd:documentation>
                 </xsd:annotation>
             </xsd:attribute>
             <xsd:attribute name="timeout" type="xsd:int">
                 <xsd:annotation>
                     <xsd:documentation><![CDATA[ The elementname1 age. ]]></xsd:documentation>
                 </xsd:annotation>
             </xsd:attribute>
         </xsd:complexType>
    
         <xsd:element name="service" type="service-type">
             <xsd:annotation>
                 <xsd:documentation><![CDATA[ elementname1的文档 ]]></xsd:documentation>
             </xsd:annotation>
         </xsd:element>
    
    
         <xsd:complexType name="provider-type">
             <xsd:attribute name="interf" type="xsd:string">
                 <xsd:annotation>
                     <xsd:documentation><![CDATA[ The elementname1 name. ]]></xsd:documentation>
                 </xsd:annotation>
             </xsd:attribute>
             <xsd:attribute name="impl" type="xsd:string">
                 <xsd:annotation>
                     <xsd:documentation><![CDATA[ The elementname1 name. ]]></xsd:documentation>
                 </xsd:annotation>
             </xsd:attribute>
         </xsd:complexType>
    
         <xsd:element name="provider" type="provider-type">
             <xsd:annotation>
                 <xsd:documentation><![CDATA[ elementname1的文档 ]]></xsd:documentation>
             </xsd:annotation>
         </xsd:element>
    
     </xsd:schema>
  2. 在自定义的 BeanDefinitionParser 来对我们自定义标签的属性进行解析。
    在 BeanDefinitionParser 里面我们可以使用 Spring 的一些组件,也可以只将我们自定的属性解析出来。parse 方法里面传入的两个参数,通过 element 可以获得 xml 中的属性信息,通过 parserContext 可以获取到 BeanDefinitionRegistry,熟悉 Spring 源码的同学应该知道这个类,我们可以通过这个类将我们的类注入到 Spring 容器中。
    构造方法中的 beanClass 我们可以传入自己定义的类,将解析出来的属性赋值到类的属性中。

    rpc:procotol 标签
    这个标签中包含了协议类型,端口,序列化协议,注册中心地址和角色(服务端还是客户端)。这个标签解析中我们将一些属性赋值到了 Configuration 配置类中,根据属性选择了协议类型,如果是客户端,提前初始化出 channel 保存到阻塞队列中,提高并发能力,如果是客户端则启动通信服务器。

    客户端 procotol 标签配置:

    Copy
    <rpc:procotol procotol="Dubbo" port="3230" serialize="ProtoStuff" address="47.107.56.23:2181"/>

    服务端 procotol 标签配置:

    Copy
    <rpc:procotol procotol="Dubbo" port="3230" serialize="ProtoStuff" role="provider" address="47.107.56.23:2181"/>

    对应的解析器。

    Copy
    public class ProcotolBeanDefinitionParser implements BeanDefinitionParser {
    
         private final Class<?> beanClass;
    
         public ProcotolBeanDefinitionParser(Class<?> beanClass) {
             this.beanClass = beanClass;
         }
    
    
         @Override
         public BeanDefinition parse(Element element, ParserContext parserContext) {
             System.out.println("1");
             String pro = element.getAttribute("procotol");
             int port = Integer.parseInt(element.getAttribute("port"));
             Configuration.getInstance().setProcotol(pro);
             Configuration.getInstance().setPort(port);
             Configuration.getInstance().setSerialize(element.getAttribute("serialize"));
             Configuration.getInstance().setStragety(element.getAttribute("stragety"));
             Configuration.getInstance().setRole(element.getAttribute("role"));
             Configuration.getInstance().setAddress(element.getAttribute("address"));
             if("provider".equals(element.getAttribute("role"))){
                 Procotol procotol = null;
                     if("Dubbo".equalsIgnoreCase(pro)){
                         procotol = new DubboProcotol();
                     }else if("Http".equalsIgnoreCase(pro)){
                         procotol = new HttpProcotol();
                     }else if("Socket".equalsIgnoreCase(pro)){
                         procotol = new SocketProcotol();
                     }else{
                         procotol = new DubboProcotol();
                     }
    
                     try {
                         InetAddress addr = InetAddress.getLocalHost();
                         String ip = addr.getHostAddress();
                         if(port == 0){
                             port = 32115;
                         }
                         URL url = new URL(ip,port);
                         procotol.start(url);
    
                     } catch (Exception e) {
                         e.printStackTrace();
                     }
             }else{
                 //获取服务注册中心
                 ZookeeperRegisterCenter registerCenter4Consumer = ZookeeperRegisterCenter.getInstance();
                 //初始化服务提供者列表到本地缓存
                 registerCenter4Consumer.initProviderMap();
                 //初始化Netty Channel
                 Map<String, List<ServiceProvider>> providerMap = registerCenter4Consumer.getServiceMetaDataMap4Consumer();
                 if (MapUtils.isEmpty(providerMap)) {
                     throw new RuntimeException("service provider list is empty.");
                 }
                 NettyChannelPoolFactory.getInstance().initNettyChannelPoolFactory(providerMap);
             }
             return null;
         }
     }
    

    rpc:provider 标签,这个是服务端服务发布标签。通过这个标签表明服务端想要将哪些服务发布出来。

    Copy
    <rpc:provider interf="com.paul.service.HelloService" impl="com.paul.service.HelloServiceImpl" />
    <rpc:provider interf="com.paul.service.UserService" impl="com.paul.service.UserServiceImpl" />

    对应的解析器:
    将需要暴露的服务注册中 zookeeper。

    Copy
    public class ProviderBeanDefinitionParser implements BeanDefinitionParser {
    
         private final Class<?> beanClass;
    
         public ProviderBeanDefinitionParser(Class<?> beanClass) {
             this.beanClass = beanClass;
         }
         @Override
         public BeanDefinition parse(Element element, ParserContext parserContext) {
             System.out.println("15");
             String interfaces = element.getAttribute("interf");
             String impl = element.getAttribute("impl");
    
             int port = Configuration.getInstance().getPort();
             InetAddress addr = null;
             try {
                 addr = InetAddress.getLocalHost();
                 String ip = addr.getHostAddress();
                 if(port == 0) {
                     port = 32115;
                 }
                 List<ServiceProvider> providerList = new ArrayList<>();
                 ServiceProvider providerService = new ServiceProvider();
                 providerService.setProvider(Class.forName(interfaces));
                 providerService.setServiceObject(impl);
                 providerService.setIp(ip);
                 providerService.setPort(port);
                 providerService.setTimeout(5000);
                 providerService.setServiceMethod(null);
                 providerService.setApplicationName("");
                 providerService.setGroupName("nettyrpc");
                 providerList.add(providerService);
    
                 //注册到zk,元数据注册中心
                 RegisterCenter4Provider registerCenter4Provider = ZookeeperRegisterCenter.getInstance();
                 registerCenter4Provider.registerProvider(providerList);
    
             } catch (UnknownHostException e) {
                 e.printStackTrace();
             } catch (ClassNotFoundException e) {
                 e.printStackTrace();
             }
             return null;
         }
     }

    rpc:service 标签,这个标签表明客户端需要调用哪些服务端的接口,将对应的代理类注入到 Spring 中,在成需中可以直接使用 @Autowired 注入这个代理类,就可以像调用本地服务一样调用远程服务了。

    Copy
    <rpc:service interfaces="com.paul.service.HelloService" ref="helloService" timeout="5000"/>

    对应的解析器:
    将接口的代理类注入到 Spring 中,并且将消费者也就是客户端注册到注册中心。

    Copy
    public class ServiceBeanDefinitionParser implements BeanDefinitionParser {
    
         private final Class<?> beanClass;
    
         public ServiceBeanDefinitionParser(Class<?> beanClass) {
             this.beanClass = beanClass;
         }
    
    
    
    
         @Override
         public BeanDefinition parse(Element element, ParserContext parserContext) {
    
    
    
             String interfaces = element.getAttribute("interfaces");
             String ref = element.getAttribute("ref");
             Class clazz = null;
             try {
                 clazz = Class.forName(interfaces);
             } catch (ClassNotFoundException e) {
                 e.printStackTrace();
             }
             BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(clazz);
             GenericBeanDefinition definition = (GenericBeanDefinition) builder.getRawBeanDefinition();
    
             definition.getConstructorArgumentValues().addGenericArgumentValue(clazz);
    
             definition.setBeanClass(ProxyFactory.class);
             definition.setAutowireMode(GenericBeanDefinition.AUTOWIRE_BY_TYPE);
    
             BeanDefinitionRegistry beanDefinitionRegistry = parserContext.getRegistry();
             beanDefinitionRegistry.registerBeanDefinition(ref,definition);
    
             //获取服务注册中心
             ZookeeperRegisterCenter registerCenter4Consumer = ZookeeperRegisterCenter.getInstance();
    
             //将消费者信息注册到注册中心
             ServiceConsumer invoker = new ServiceConsumer();
             List<ServiceConsumer> consumers = new ArrayList<>();
             consumers.add(invoker);
             invoker.setConsumer(clazz);
             invoker.setServiceObject(interfaces);
             invoker.setGroupName("");
             registerCenter4Consumer.registerConsumer(consumers);
    
             return definition;
         }
     }
  3. 定义一个 NamespaceHandler 来注册对应的标签和 BeanDefinitionParser。

    Copy
    public class RpcNamespaceHandler extends NamespaceHandlerSupport {
         @Override
         public void init() {
             registerBeanDefinitionParser("procotol", new ProcotolBeanDefinitionParser(Configuration.class));
     //        registerBeanDefinitionParser("register", new RegisterBeanDefinitionParser(Configuration.class));
             registerBeanDefinitionParser("application", new ApplicationBeanDefinitionParser(Configuration.class));
             registerBeanDefinitionParser("provider", new ProviderBeanDefinitionParser(Configuration.class));
      //       registerBeanDefinitionParser("role", new ServerBeanDefinitionParser(Configuration.class));
             registerBeanDefinitionParser("service", new ServiceBeanDefinitionParser(Configuration.class));
         }
     }
  4. 在 Spring 中注册上面的 schema 和 handler。
    spring.handlers, 这里要将 schema 和我们自定义的 handler 类 mapping 起来。

    Copy
    http\://paul.com/schema=com.paul.spring.RpcNamespaceHandler

    spring.schema,表明 xsd 文件的位置。

    Copy
    http\://paul.com/schema/rpc.xsd=META-INF/rpc.xsd

    通过上面的配置我们实现了根据配置来做通信协议,序列化协议的选择以及客户端代理类注入到 Spring 中方便我们以后调用,还实现了服务端的启动,以及对应注册到注册中心的功能。

获取接口代理类的实现
我们使用的是 JDK 动态代理。
```java
public class ProxyFactory implements FactoryBean {
private Class interfaceClass;

Copy
    private ApplicationContext ctx;


    public ProxyFactory(Class<T> interfaceClass) {
        this.interfaceClass = interfaceClass;
    }



    @Override
    public T getObject() throws Exception {
        return (T)Proxy.newProxyInstance(interfaceClass.getClassLoader(), new Class[]{interfaceClass}, new Handler(interfaceClass));
    }

    @Override
    public Class<?> getObjectType() {
        return interfaceClass;
    }

}
```

Invocation 的实现类 handler, 也就是动态代理类的 invoke 方法的调用,通过 invoke 方法调用对应协议的 send 方法去发送数据。在发送数据前,通过负载均衡策略选择对应的服务端地址,拼装 RpcRequest 调用 proctol 接口实现类的 send 方法发送数据。
```java
public class Handler implements InvocationHandler{

Copy
private Class<T> interfaceClass;

public Handler(Class<T> interfaceClass) {
    this.interfaceClass = interfaceClass;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    Configuration configuration = Configuration.getInstance();

    Procotol procotol;

    if("Dubbo".equalsIgnoreCase(configuration.getProcotol())){
        procotol = new DubboProcotol();
    }else if("Http".equalsIgnoreCase(configuration.getProcotol())){
        procotol = new HttpProcotol();
    }else if("Socket".equalsIgnoreCase(configuration.getProcotol())){
        procotol = new SocketProcotol();
    }else{
        procotol = new DubboProcotol();
    }

    //服务接口名称
    String serviceKey = interfaceClass.getName();
    //获取某个接口的服务提供者列表
    RegisterCenter4Consumer registerCenter4Consumer = ZookeeperRegisterCenter.getInstance();
    List<ServiceProvider> providerServices = registerCenter4Consumer.getServiceMetaDataMap4Consumer().get(serviceKey);
    //根据软负载策略,从服务提供者列表选取本次调用的服务提供者
    String stragety = configuration.getStragety();
    if(null == stragety || stragety == ""){
        stragety = "random";
    }
    System.out.println("paul:"+ providerServices.get(0).toString());
    LoadStrategy loadStrategyService = LoadBalanceEngine.queryLoadStrategy(stragety);
    ServiceProvider serviceProvider = loadStrategyService.select(providerServices);
    URL url = new URL(serviceProvider.getIp(),serviceProvider.getPort());
    String impl = serviceProvider.getServiceObject().toString();
    int timeout = 20000;
    RpcRequest invocation = new RpcRequest(UUID.randomUUID().toString(),interfaceClass.getName(),method.getName(),args, method.getParameterTypes(),impl,timeout);
    Object res = procotol.send(url, invocation);
    return res;
}

}

```

这样我们完成 rpc-spring 模块的代码。

分享到:
评论

相关推荐

    基于SpringBoot+Vue的“智慧食堂”设计与实现(Java毕业设计,包括源码、数据库、教程).zip

    Java 项目, Java 毕业设计,Java 课程设计,基于 SpringBoot 开发的,含有代码注释,新手也可看懂。毕业设计、期末大作业、课程设计、高分必看,下载下来,简单部署,就可以使用。 包含:项目源码、数据库脚本、软件工具等,前后端代码都在里面。 该系统功能完善、界面美观、操作简单、功能齐全、管理便捷,具有很高的实际应用价值。 项目都经过严格调试,确保可以运行! 1. 技术组成 前端:html、javascript、Vue 后台框架:SpringBoot 开发环境:idea 数据库:MySql(建议用 5.7 版本,8.0 有时候会有坑) 数据库工具:navicat 部署环境:Tomcat(建议用 7.x 或者 8.x 版本), maven 2. 部署 如果部署有疑问的话,可以找我咨询 Java工具包下载地址: https://pan.quark.cn/s/eb24351ebac4 后台路径地址:localhost:8080/项目名称/admin/dist/index.html 前台路径地址:localhost:8080/项目名称/front/index.html (无前台不需要输入)

    历届奥运会奖牌数据(1896-2024年).xlsx

    本次分享的数据为1896年-2024年(从雅典到巴黎)间奥运会奖牌数据,包括年份、届次、国家地区、名次、金牌、银牌、铜牌等数据,含免费下载链接 ## 一、数据介绍 数据名称:历届奥运会奖牌数据 数据范围:世界各国 样本数量:1877条 数据年份:1896年-2024年 数据说明:包括届次、国家、名次等数据

    基于SpringBoot+Vue的实习管理系统(Java毕业设计,包括源码、数据库、教程).zip

    Java 项目, Java 毕业设计,Java 课程设计,基于 SpringBoot 开发的,含有代码注释,新手也可看懂。毕业设计、期末大作业、课程设计、高分必看,下载下来,简单部署,就可以使用。 包含:项目源码、数据库脚本、软件工具等,前后端代码都在里面。 该系统功能完善、界面美观、操作简单、功能齐全、管理便捷,具有很高的实际应用价值。 项目都经过严格调试,确保可以运行! 1. 技术组成 前端:html、javascript、Vue 后台框架:SpringBoot 开发环境:idea 数据库:MySql(建议用 5.7 版本,8.0 有时候会有坑) 数据库工具:navicat 部署环境:Tomcat(建议用 7.x 或者 8.x 版本), maven 2. 部署 如果部署有疑问的话,可以找我咨询 Java工具包下载地址: https://pan.quark.cn/s/eb24351ebac4 后台路径地址:localhost:8080/项目名称/admin/dist/index.html 前台路径地址:localhost:8080/项目名称/front/index.html (无前台不需要输入)

    【人机交互】MATLAB直车道线检测.zip

    【人机交互】MATLAB直车道线检测

    基于SSM+JSP的KTV点歌系统+数据库(Java毕业设计,包括源码,教程).zip

    Java 项目, Java 毕业设计,Java 课程设计,基于 SpringBoot 开发的,含有代码注释,新手也可看懂。毕业设计、期末大作业、课程设计、高分必看,下载下来,简单部署,就可以使用。 包含:项目源码、数据库脚本、软件工具等,前后端代码都在里面。 该系统功能完善、界面美观、操作简单、功能齐全、管理便捷,具有很高的实际应用价值。 项目都经过严格调试,确保可以运行! 1. 技术组成 前端:jsp 后台框架:SSM 开发环境:idea 数据库:MySql(建议用 5.7 版本,8.0 有时候会有坑) 数据库工具:navicat 部署环境:Tomcat(建议用 7.x 或者 8.x 版本), maven 2. 部署 如果部署有疑问的话,可以找我咨询 Java工具包下载地址: https://pan.quark.cn/s/eb24351ebac4

    基于SSM+JSP的课程在线教育资源管理系统(1)+数据库(Java毕业设计,包括源码,教程).zip

    Java 项目, Java 毕业设计,Java 课程设计,基于 SpringBoot 开发的,含有代码注释,新手也可看懂。毕业设计、期末大作业、课程设计、高分必看,下载下来,简单部署,就可以使用。 包含:项目源码、数据库脚本、软件工具等,前后端代码都在里面。 该系统功能完善、界面美观、操作简单、功能齐全、管理便捷,具有很高的实际应用价值。 项目都经过严格调试,确保可以运行! 1. 技术组成 前端:jsp 后台框架:SSM 开发环境:idea 数据库:MySql(建议用 5.7 版本,8.0 有时候会有坑) 数据库工具:navicat 部署环境:Tomcat(建议用 7.x 或者 8.x 版本), maven 2. 部署 如果部署有疑问的话,可以找我咨询 Java工具包下载地址: https://pan.quark.cn/s/eb24351ebac4

    2023年计算机硬件的组装实验报告.pdf

    2023年计算机硬件的组装实验报告.pdf

    springboot大学生租房系统 LW PPT.zip

    ava项目springboot基于springboot的课程设计,包含源码+数据库+毕业论文

    Flutter分析:带有质量平衡部分机翼的MATLAB计算(含Elastic轴与中心对齐)

    内容概要:本文档主要针对含有质量平衡段(即弹性轴和重心重合点xa=0)的硬翼Flutter问题提供了MATLAB解决方案。文档通过迭代的方式对一系列参数(如频率比(fr)、弹性轴(E)和半径(r)等)进行操作,并利用贝塞尔函数(Kn)来评估flutter速度(UFhat),从而预测了不同质比(mu)下flutter的缩减速度变化情况。同时,文档包含了绘图命令以视觉展示减小颤振速度随质量比变化的趋势以及相应的MATLAB代码。 适合人群:航空工程、飞行器动力学领域的科研工作者,工程师及研究生。尤其是那些从事飞行安全性和稳定性研究的专业人士。 使用场景及目标:主要用于解决飞行器设计过程中遇到的具体颤振问题,能够为设计新型飞机或其他有翼飞行物体提供科学依据和技术支持。它还能够辅助教育,帮助相关专业的学生理解flutter现象及其预防措施。 其他说明:此文件是以数值方法探讨带质量平衡的翅膀颤振特性的实例,在工程上有着重要意义。对于希望深入学习此类问题的人来说,这是一个极好的参考资料和实验平台。然而,实际应用还需要进一步考虑真实条件下的复杂因素,因此需要更多的专业知识和背景资料的支持。

    基于JAVA的机场航班起降与协调管理系统&毕业设计&毕业论文&数据库&演示视频&源代码

    本次项目是设计一个基于JAVA的机场航班起降与协调管理系统。 (1)在经济可行性上来分析的话,该软件是机场内部使用的一个指挥协调软件,属于航空安全投资,本软件开发成本并不高,软件和服务器数据库可以用机场原有的数据库进行开发,比起空难给航空公司造成的损失来说九牛一毛。 (2)在技术可行性上来分析的话,该软件主要运用了Java技术、jQuery-easyui和Mysql数据库技术。Java是到目前来说最稳定的、最可靠的软件开发工具;jQuery-easyui虽然是比较新的前台开发技术,但是他的界面新颖整洁,适合于功能性软件的开发;Mysql数据库也是许多大公司都采用的软件项目开发数据库,不仅稳定而且性能可靠,可以用作本次软件的开发。 (3)在法律可行性上来分析的话,该软件使用的技术都为开源的软件开发工具和语言,虽然Java等开发技术都存在Sun公司的版权问题,但是Java技术是可以免费使用的,没有涉及到法律上的侵权。 (4)在方案可行性上来分析的话,此次软件开发的很大一部分精力都放在了软件的需求分析和设计方面,设计出来的软件可以很好地去实现我们所要完成的软件预先设计的功能。

    2023年计算机与通信网络实验报告.pdf

    2023年计算机与通信网络实验报告.pdf

    2023年四川省德阳市统招专升本计算机自考真题(含答案).pdf

    2023年四川省德阳市统招专升本计算机自考真题(含答案).pdf

    基于SSM+JSP的农产品供销服务系统+数据库(Java毕业设计,包括源码,教程).zip

    Java 项目, Java 毕业设计,Java 课程设计,基于 SpringBoot 开发的,含有代码注释,新手也可看懂。毕业设计、期末大作业、课程设计、高分必看,下载下来,简单部署,就可以使用。 包含:项目源码、数据库脚本、软件工具等,前后端代码都在里面。 该系统功能完善、界面美观、操作简单、功能齐全、管理便捷,具有很高的实际应用价值。 项目都经过严格调试,确保可以运行! 1. 技术组成 前端:jsp 后台框架:SSM 开发环境:idea 数据库:MySql(建议用 5.7 版本,8.0 有时候会有坑) 数据库工具:navicat 部署环境:Tomcat(建议用 7.x 或者 8.x 版本), maven 2. 部署 如果部署有疑问的话,可以找我咨询 Java工具包下载地址: https://pan.quark.cn/s/eb24351ebac4

    篮球论坛系统--论文.zip

    Java项目基于springboot的课程设计,包含源码+数据库+毕业论文

    基于SpringBoot+Vue的致远汽车租赁系统(Java毕业设计,包括源码、数据库、教程).zip

    Java 项目,仅供学习参考。 Java 毕业设计,Java 课程设计,基于 SpringBoot 开发的,含有代码注释,新手也可看懂。毕业设计、期末大作业、课程设计、高分必看,下载下来,简单部署,就可以使用。 包含:项目源码、数据库脚本、软件工具等,前后端代码都在里面。 该系统功能完善、界面美观、操作简单、功能齐全、管理便捷,具有很高的实际应用价值。 项目都经过严格调试,确保可以运行! 1. 技术组成 前端:html、javascript、Vue 后台框架:SpringBoot 开发环境:idea 数据库:MySql(建议用 5.7 版本,8.0 有时候会有坑) 数据库工具:navicat 部署环境:Tomcat(建议用 7.x 或者 8.x 版本), maven 2. 部署 如果部署有疑问的话,可以找我咨询 Java工具包下载地址: https://pan.quark.cn/s/eb24351ebac4 后台路径地址:localhost:8080/项目名称/admin/dist/index.html 前台路径地址:localhost:8080/项目名称/front/index.html (无前台不需要输入)

    GUI面板MATLAB答题纸试卷自动识别.zip

    GUI面板MATLAB答题纸试卷自动识别

    Java毕业设计-SpringBoot+Vue的班级综合测评管理系统(附源码、数据库、教程).zip

    Java 项目, Java 毕业设计,Java 课程设计,基于 SpringBoot 开发的,含有代码注释,新手也可看懂。毕业设计、期末大作业、课程设计、高分必看,下载下来,简单部署,就可以使用。 包含:项目源码、数据库脚本、软件工具等,前后端代码都在里面。 该系统功能完善、界面美观、操作简单、功能齐全、管理便捷,具有很高的实际应用价值。 项目都经过严格调试,确保可以运行! 1. 技术组成 前端:html、javascript、Vue 后台框架:SpringBoot 开发环境:idea 数据库:MySql(建议用 5.7 版本,8.0 有时候会有坑) 数据库工具:navicat 部署环境:Tomcat(建议用 7.x 或者 8.x 版本), maven 2. 部署 如果部署有疑问的话,可以找我咨询 Java工具包下载地址: https://pan.quark.cn/s/eb24351ebac4 后台路径地址:localhost:8080/项目名称/admin/dist/index.html 前台路径地址:localhost:8080/项目名称/front/index.html (无前台不需要输入)

    springboot大学生体质测试管理系统--论文.zip

    ava项目springboot基于springboot的课程设计,包含源码+数据库+毕业论文

    美国机器人渗透度(工具变量).dta

    美国工业机器人渗透度是衡量自动化技术在制造业和其他工业领域应用程度的重要指标,可帮助研究制造业自动化趋势、劳动力市场变化,以及智能制造对经济的影响,同时,本分享数据也可用作工业机器人工具变量 ## 一、美国工业机器人渗透度数据的介绍 数据年份:2011-2019年 数据范围:美国各行业 数据格式:excel、dta

    编织复合材料模型,已经画好周期性网格,可直接运行easy-pbc插件计算均质化属性,可修改材料属性

    编织复合材料模型,已经画好周期性网格,可直接运行easy_pbc插件计算均质化属性,可修改材料属性,关于周期性网格画法可讨论

Global site tag (gtag.js) - Google Analytics