setupfactory,JAVA架构之线程池是怎样工作的?
提示

SimpleThreadPool
程序中我们创建了固定大小为五个工作线程的线程池。然后分配给线程池十个工作,因为线程池大小为五,它将启动五个工作线程先处理五个工作,其他的工作则处于等待状态,一旦有工作完成,空闲下来工作线程就会捡取等待队列里的其他工作进行执行。这里是以上程序的输出。输出表明线程池中至始至终只有五个名为 "pool-1-thread-1" 到 "pool-1-thread-5" 的五个线程,这五个线程不随着工作的完成而消亡,会一直存在,并负责执行分配给线程池的任务,直到线程池消亡。Executors 类提供了使用了 ThreadPoolExecutor 的简单的 ExecutorService 实现,但是 ThreadPoolExecutor 提供的功能远不止于此。我们可以在创建 ThreadPoolExecutor 实例时指定活动线程的数量,我们也可以限制线程池的大小并且创建我们自己的 RejectedExecutionHandler 实现来处理不能适应工作队列的工作。这里是我们自定义的 RejectedExecutionHandler 接口的实现。RejectedExecutionHandlerImpl.javaThreadPoolExecutor 提供了一些方法,我们可以使用这些方法来查询 executor 的当前状态,线程池大小,活动线程数量以及任务数量。因此我是用来一个监控线程在特定的时间间隔内打印 executor 信息。MyMonitorThread.java这里是使用 ThreadPoolExecutor 的线程池实现例子。WorkerPool.java注意在初始化 ThreadPoolExecutor 时,我们保持初始池大小为 2,最大池大小为 4 而工作队列大小为 2。因此如果已经有四个正在执行的任务而此时分配来更多任务的话,工作队列将仅仅保留他们(新任务)中的两个,其他的将会被RejectedExecutionHandlerImpl 处理。上面程序的输出可以证实以上观点。注意 executor 的活动任务、完成任务以及所有完成任务,这些数量上的变化。我们可以调用 shutdown() 方法来结束所有提交的任务并终止线程池。ThreadPoolExecutor使用详解其实java线程池的实现原理很简单,说白了就是一个线程集合workerSet和一个阻塞队列workQueue。当用户向线程池提交一个任务(也就是线程)时,线程池会先将任务放入workQueue中。workerSet中的线程会不断的从workQueue中获取线程然后执行。当workQueue中没有任务的时候,worker就会阻塞,直到队列中有任务了就取出来继续执行。Execute原理当一个任务提交至线程池之后:线程池首先当前运行的线程数量是否少于corePoolSize。如果是,则创建一个新的工作线程来执行任务。如果都在执行任务,则进入2.判断BlockingQueue是否已经满了,倘若还没有满,则将线程放入BlockingQueue。否则进入3.如果创建一个新的工作线程将使当前运行的线程数量超过maximumPoolSize,则交给RejectedExecutionHandler来处理任务。当ThreadPoolExecutor创建新线程时,通过CAS来更新线程池的状态ctl.参数corePoolSize 线程池中的核心线程数,当提交一个任务时,线程池创建一个新线程执行任务,直到当前线程数等于corePoolSize, 即使有其他空闲线程能够执行新来的任务, 也会继续创建线程;如果当前线程数为corePoolSize,继续提交的任务被保存到阻塞队列中,等待被执行;如果执行了线程池的prestartAllCoreThreads()方法,线程池会提前创建并启动所有核心线程。workQueue 用来保存等待被执行的任务的阻塞队列. 在JDK中提供了如下阻塞队列: 具体可以参考JUC 集合: BlockQueue详解ArrayBlockingQueue: 基于数组结构的有界阻塞队列,按FIFO排序任务;LinkedBlockingQueue: 基于链表结构的阻塞队列,按FIFO排序任务,吞吐量通常要高于ArrayBlockingQueue;SynchronousQueue: 一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue;PriorityBlockingQueue: 具有优先级的无界阻塞队列;LinkedBlockingQueue比ArrayBlockingQueue在插入删除节点性能方面更优,但是二者在put(), take()任务的时均需要加锁,SynchronousQueue使用无锁算法,根据节点的状态判断执行,而不需要用到锁,其核心是Transfer.transfer().maximumPoolSize 线程池中允许的最大线程数。如果当前阻塞队列满了,且继续提交任务,则创建新的线程执行任务,前提是当前线程数小于maximumPoolSize;当阻塞队列是无界队列, 则maximumPoolSize则不起作用, 因为无法提交至核心线程池的线程会一直持续地放入workQueue.keepAliveTime 线程空闲时的存活时间,即当线程没有任务执行时,该线程继续存活的时间;默认情况下,该参数只在线程数大于corePoolSize时才有用, 超过这个时间的空闲线程将被终止;unit keepAliveTime的单位threadFactory 创建线程的工厂,通过自定义的线程工厂可以给每个新建的线程设置一个具有识别度的线程名。默认为DefaultThreadFactoryhandler 线程池的饱和策略,当阻塞队列满了,且没有空闲的工作线程,如果继续提交任务,必须采取一种策略处理该任务,线程池提供了4种策略:AbortPolicy: 直接抛出异常,默认策略;CallerRunsPolicy: 用调用者所在的线程来执行任务;DiscardOldestPolicy: 丢弃阻塞队列中靠最前的任务,并执行当前任务;DiscardPolicy: 直接丢弃任务;当然也可以根据应用场景实现RejectedExecutionHandler接口,自定义饱和策略,如记录日志或持久化存储不能处理的任务。三种类型newFixedThreadPool线程池的线程数量达corePoolSize后,即使线程池没有可执行任务时,也不会释放线程。FixedThreadPool的工作队列为无界队列LinkedBlockingQueue(队列容量为Integer.MAX_VALUE), 这会导致以下问题:线程池里的线程数量不超过corePoolSize,这导致了maximumPoolSize和keepAliveTime将会是个无用参数由于使用了无界队列, 所以FixedThreadPool永远不会拒绝, 即饱和策略失效newSingleThreadExecutor初始化的线程池中只有一个线程,如果该线程异常结束,会重新创建一个新的线程继续执行任务,唯一的线程可以保证所提交任务的顺序执行.由于使用了无界队列, 所以SingleThreadPool永远不会拒绝, 即饱和策略失效newCachedThreadPool线程池的线程数可达到Integer.MAX_VALUE,即2147483647,内部使用SynchronousQueue作为阻塞队列; 和newFixedThreadPool创建的线程池不同,newCachedThreadPool在没有任务执行时,当线程的空闲时间超过keepAliveTime,会自动释放线程资源,当提交新任务时,如果没有空闲线程,则创建新线程执行任务,会导致一定的系统开销; 执行过程与前两种稍微不同:主线程调用SynchronousQueue的offer()方法放入task, 倘若此时线程池中有空闲的线程尝试读取 SynchronousQueue的task, 即调用了SynchronousQueue的poll(), 那么主线程将该task交给空闲线程. 否则执行(2)当线程池为空或者没有空闲的线程, 则创建新的线程执行任务.执行完任务的线程倘若在60s内仍空闲, 则会被终止. 因此长时间空闲的CachedThreadPool不会持有任何线程资源.关闭线程池遍历线程池中的所有线程,然后逐个调用线程的interrupt方法来中断线程.关闭方式 - shutdown将线程池里的线程状态设置成SHUTDOWN状态, 然后中断所有没有正在执行任务的线程.关闭方式 - shutdownNow将线程池里的线程状态设置成STOP状态, 然后停止所有正在执行或暂停任务的线程. 只要调用这两个关闭方法中的任意一个, isShutDown() 返回true. 当所有任务都成功关闭了, isTerminated()返回true.ThreadPoolExecutor源码详解几个关键属性内部状态其中AtomicInteger变量ctl的功能非常强大: 利用低29位表示线程池中线程数,通过高3位表示线程池的运行状态:RUNNING: -1 << COUNT_BITS,即高3位为111,该状态的线程池会接收新任务,并处理阻塞队列中的任务;SHUTDOWN: 0 << COUNT_BITS,即高3位为000,该状态的线程池不会接收新任务,但会处理阻塞队列中的任务;STOP : 1 << COUNT_BITS,即高3位为001,该状态的线程不会接收新任务,也不会处理阻塞队列中的任务,而且会中断正在运行的任务;TIDYING : 2 << COUNT_BITS,即高3位为010, 所有的任务都已经终止;TERMINATED: 3 << COUNT_BITS,即高3位为011, terminated()方法已经执行完成任务的执行execute –> addWorker –>runworker (getTask)线程池的工作线程通过Woker类实现,在ReentrantLock锁的保证下,把Woker实例插入到HashSet后,并启动Woker中的线程。 从Woker类的构造方法实现可以发现: 线程工厂在创建线程thread时,将Woker实例本身this作为参数传入,当执行start方法启动线程thread时,本质是执行了Worker的runWorker方法。 firstTask执行完成之后,通过getTask方法从阻塞队列中获取等待的任务,如果队列中没有任务,getTask方法会被阻塞并挂起,不会占用cpu资源;execute()方法ThreadPoolExecutor.execute(task)实现了Executor.execute(task)为什么需要double check线程池的状态?在多线程环境下,线程池的状态时刻在变化,而ctl.get()是非原子操作,很有可能刚获取了线程池状态后线程池状态就改变了。判断是否将command加入workque是线程池之前的状态。倘若没有double check,万一线程池处于非running状态(在多线程环境下很有可能发生),那么command永远不会执行。addWorker方法从方法execute的实现可以看出: addWorker主要负责创建新的线程并执行任务 线程池创建新线程执行任务时,需要 获取全局锁:Worker类的runworker方法继承了AQS类,可以方便的实现工作线程的中止操作;实现了Runnable接口,可以将自身作为一个任务在工作线程中执行;当前提交的任务firstTask作为参数传入Worker的构造方法;一些属性还有构造方法:runWorker方法是线程池的核心:线程启动之后,通过unlock方法释放锁,设置AQS的state为0,表示运行可中断;Worker执行firstTask或从workQueue中获取任务:进行加锁操作,保证thread不被其他线程中断(除非线程池被中断)检查线程池状态,倘若线程池处于中断状态,当前线程将中断。执行beforeExecute执行任务的run方法执行afterExecute方法解锁操作通过getTask方法从阻塞队列中获取等待的任务,如果队列中没有任务,getTask方法会被阻塞并挂起,不会占用cpu资源;getTask方法下面来看一下getTask()方法,这里面涉及到keepAliveTime的使用,从这个方法我们可以看出线程池是怎么让超过corePoolSize的那部分worker销毁的。注意这里一段代码是keepAliveTime起作用的关键:
allowCoreThreadTimeOut为false,线程即使空闲也不会被销毁;倘若为ture,在keepAliveTime内仍空闲则会被销毁。如果线程允许空闲等待而不被销毁timed == false,workQueue.take任务: 如果阻塞队列为空,当前线程会被挂起等待;当队列中有任务加入时,线程被唤醒,take方法返回任务,并执行;如果线程不允许无休止空闲timed == true, workQueue.poll任务: 如果在keepAliveTime时间内,阻塞队列还是没有任务,则返回null;任务的提交submit任务,等待线程池execute执行FutureTask类的get方法时,会把主线程封装成WaitNode节点并保存在waiters链表中, 并阻塞等待运行结果;FutureTask任务执行完成后,通过UNSAFE设置waiters相应的waitNode为null,并通过LockSupport类unpark方法唤醒主线程;在实际业务场景中,Future和Callable基本是成对出现的,Callable负责产生结果,Future负责获取结果。Callable接口类似于Runnable,只是Runnable没有返回值。Callable任务除了返回正常结果之外,如果发生异常,该异常也会被返回,即Future可以拿到异步执行任务各种结果;Future.get方法会导致主线程阻塞,直到Callable任务执行完成;submit方法AbstractExecutorService.submit()实现了ExecutorService.submit() 可以获取执行完的返回值, 而ThreadPoolExecutor 是AbstractExecutorService.submit()的子类,所以submit方法也是ThreadPoolExecutor`的方法。通过submit方法提交的Callable任务会被封装成了一个FutureTask对象。通过Executor.execute方法提交FutureTask到线程池中等待被执行,最终执行的是FutureTask的run方法;FutureTask对象public class FutureTask<V> implements RunnableFuture<V> 可以将FutureTask提交至线程池中等待被执行(通过FutureTask的run方法来执行)内部状态内部状态的修改通过sun.misc.Unsafe修改get方法内部通过awaitDone方法对主线程进行阻塞,具体实现如下:
如果主线程被中断,则抛出中断异常;判断FutureTask当前的state,如果大于COMPLETING,说明任务已经执行完成,则直接返回;如果当前state等于COMPLETING,说明任务已经执行完,这时主线程只需通过yield方法让出cpu资源,等待state变成NORMAL;通过WaitNode类封装当前线程,并通过UNSAFE添加到waiters链表;最终通过LockSupport的park或parkNanos挂起线程;run方法FutureTask.run方法是在线程池中被执行的,而非主线程通过执行Callable任务的call方法;如果call执行成功,则通过set方法保存结果;如果call执行有异常,则通过setException保存异常;任务的关闭shutdown方法会将线程池的状态设置为SHUTDOWN,线程池进入这个状态后,就拒绝再接受任务,然后会将剩余的任务全部执行完shutdownNow做的比较绝,它先将线程池状态设置为STOP,然后拒绝所有提交的任务。最后中断左右正在运行中的worker,然后清空任务队列。
更深入理解为什么线程池不允许使用Executors去创建? 推荐方式是什么?线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。 说明:Executors各个方法的弊端:newFixedThreadPool和newSingleThreadExecutor: 主要问题是堆积的请求处理队列可能会耗费非常大的内存,甚至OOM。newCachedThreadPool和newScheduledThreadPool: 主要问题是线程数最大数是Integer.MAX_VALUE,可能会创建数量非常多的线程,甚至OOM。推荐方式 1首先引入:commons-lang3包推荐方式 2首先引入:com.google.guava包推荐方式 3spring配置线程池方式:自定义线程工厂bean需要实现ThreadFactory,可参考该接口的其它默认实现类,使用方式直接注入bean调用execute(Runnable task)方法即可配置线程池需要考虑因素从任务的优先级,任务的执行时间长短,任务的性质(CPU密集/ IO密集),任务的依赖关系这四个角度来分析。并且近可能地使用有界的工作队列。性质不同的任务可用使用不同规模的线程池分开处理:CPU密集型: 尽可能少的线程,Ncpu+1IO密集型: 尽可能多的线程, Ncpu*2,比如数据库连接池混合型: CPU密集型的任务与IO密集型任务的执行时间差别较小,拆分为两个线程池;否则没有必要拆分。监控线程池的状态可以使用ThreadPoolExecutor以下方法:getTaskCount() Returns the approximate total number of tasks that have ever been scheduled for execution.getCompletedTaskCount() Returns the approximate total number of tasks that have completed execution. 返回结果少于getTaskCount()。getLargestPoolSize() Returns the largest number of threads that have ever simultaneously been in the pool. 返回结果小于等于maximumPoolSizegetPoolSize() Returns the current number of threads in the pool.getActiveCount() Returns the approximate number of threads that are actively executing tasks.参考文章《Java并发编程艺术》https://www.jianshu.com/p/87bff5cc8d8chttps://blog.csdn.net/programmer_at/article/details/79799267https://blog.csdn.net/u013332124/article/details/79587436https://www.journaldev.com/1069/threadpoolexecutor-java-thread-pool-example-executorservice由于问答代码块插入受限,部分代码未完全展示,若有需要可阅读原文:戳我阅读原文
大众导航错误代码001怎么办?
大众车辆的导航系统出现错误代码001通常指的是导航地图故障或数据过期。以下是解决该问题的步骤:
1. 确认地图数据库版本:首先需要确认导航系统的地图数据库版本是否过期或是有问题。如不确定,可以查看车辆的使用手册或在官方网站上查询相关信息。
2. 更新导航地图:如果确认导航地图数据库版本有问题或过期,需要更新导航地图。可以在大众官方网站上下载并安装最新版本的导航地图,或者联系当地的认可维修机构购买。
3. 重启导航系统:如果更新导航地图后问题还是存在,可以尝试重启导航系统。按下导航系统的“Menu”或“Setup”按钮,进入设置菜单,然后选择“System Reset”或“Factory Reset”,进行重启设置。这有时可以帮助解决导航错误代码001的问题。
4. 联系大众认可维修机构:如果以上步骤都无法解决问题,建议联系大众认可维修机构进行进一步诊断和维修。
需要注意的是,在操作导航系统时,应注意安全,以免因分神驾驶而发生意外。如果您不确定如何操作,建议与车辆使用手册或相关的认可维修机构进行联系以获取帮助。
国内低代码平台有哪些?
随着低代码概念的火热,相关的技术及产品也是层出不穷,不管是老牌行业软件厂商还是开放平台厂商,不论是互联网行业企业SAAS软件新动向还是新兴的低代码创新产品服务,都在第一时间打出了低代码这张牌。各个平台虽然各有优势,但大多又是自成体系,真正在企业方面进行选择时却一时难以抉择。对于低代码平台的功能评价,以及各平台组件间的互联互通则成为了市场上迫切需求。
在刚刚过去的2022年,在平台互联互通的方面,阿里在第二季度推出开源引擎 “LowCodeEngin”,国家队信通院也应市场需求在第四季度推出了《低代码开发平台通用技术要求》,针对低代码相关概念以及功能点新型了进一步的规范和梳理。在全面开放的大背景下,CodeBee团队,推出了基于开源LGPL协议 低代码引擎(LowCodeEngine)。二,产品组成低代码引擎,由界面设计器、OneCode通码框架以及,DSM领域建模工具 三部分支撑体系相互支撑的部分来组成,通过开放标准的组件协议完成相互继承支持。(1) 视图设计器引擎设计器,采用的是拖拽引擎+插件的构造模型,用户可以通过开放的低代码协议编写插件。支持JS和JAVA两种扩展语言。样式构建提供了标准CSS3编辑器,支持事件动作以及函数动态扩展。支持自定义函数库扩展,支持阿里字体图片等资源库。(2)OneCode通码编辑器:OneCode,是一款为低代码语言定制的统一语法体系,采用Java语言作为原生语言,运行在JVM环境中,用户可以通过Java语言与低代码应用进行交互,也可以通过Java语言完成引擎插件,调用代码引擎完成编译部署应用。(3)DSMEngine领域建模:DSMEngine 是独立于设计器的OneCode建模工具,平台采用领域建模模型,支持仓储管理、聚合应用,以及CodeFactory输出为OneCode代码编译输出。DSM模型支持三种建模模式:(1)CodeFrist 代码优先模式通过Java语言 OneCode 模式原生撰写。(2)ViewFrist 视图优先通过视图引擎拖拽完成前期的交互模型,反向完成DSM模型。(3)ModuleFrist 模型优先通过数据库,微服务接口等模式,构建基础模型。DSM逆向转换通过不同方式完成的DSM模型,可以通过OneCode 在视图、Code 、以及Module 三种方式之间自由切换,利用相关工具完成仿真调试以及部署运行。DSM第三方语言转换DSM出码模块采用了独立的模板架构,除了可以以OneCode形式存在,还可以支持独立的出码模块定制独立的第三方语言模型输出。三,设计器引擎介绍(1)功能概览(2)设计器布局设计器引擎是低代码引擎前端的SDK,面向开发人员,他本身不是一套可以适应所有人的低代码平台,而是技术开发人员可以通过扩展插件,周边生态,完成自身业务的定制,实现协同办公,CRM客户管理、物联网平台等通过低代码能力赋能业务系统(3)物料库"物料":低代码引擎的核心目的之一是建设跨行业的低代码框架,而每个行业由于其应用的领域不同,使用的人员以及方法方式不同,在一些底层组件方面会有会有加大差距。比如:政府业务中会大量使用的非规则表单元素,企业应用中各个行业自有的图标体系,物联网行业大量的设备图标图片以及实时联网图。(4)组件库组件定义:可以用于低代码平台的组件,包含了搭建体验增强配置,可以在设计器中 进行拖拽、配置等操作。有两种分类方式:按照场景可以分为基础组件、业务组件、图 表组件、布局组件和复合组件等。通常用户可以自主完成相关设定,并根据业务特点在视图引擎中进行自行扩展(后续章节中会演示实际注册示例)组件通常是一组完成特定功能的可交互组件,根据不同的引擎模式,在引擎中完成加载渲染配置。配置示例Code转换组件调试导入(5)支撑管理公共资源导入(6)样式体系DOM树透视样式盒DOM树透视添加图片注释,不超过 140 字(可选)配图示例代码{ "alias":"BuildTreeTreeView", "key":"xui.UI.TreeView", "host":this, "properties":{ "name":"BuildTreeTreeGrid", "items":[ { "borderType":"none", "caption":"JAVA树", "dynDestory":false, "hidden":false, "id":"getBuildTree", "imageClass":"bpmfont bpmgongzuoliuxitongpeizhi", "tagVar":{ } } ], "iniFold":false, "dynDestory":true }, "CS":{ "KEY":{ "color":"#000000", "font-weight":"lighter", "border-radius":"0px 2px 0px 0px" }, "BAR":{ "font-family":"tahoma,geneva,sans-serif" } } }
动态样式盒添加图片注释,不超过 140 字(可选)代码配置示例{ "alias":"xui_ui_cssbox1", "key":"xui.UI.CSSBox", "host":this, "properties":{ "className":"xui-css-ame", "normalStatus":{ "color":"#eeeeee", "border-radius":"6px", "box-shadow":"inset 0px 1px 0px #87C1DD", "text-shadow":"0 1px 0 #297192", "$gradient":{ "stops":[ { "pos":"0%", "clr":"#4BA3CC" }, { "pos":"70%", "clr":"#3289B2" } ], "type":"linear", "orient":"T" }, "cursor":"pointer", "border-top":"solid #3899C6 1px", "border-right":"solid #3899C6 1px", "border-bottom":"solid #3899C6 1px", "border-left":"solid #3899C6 1px" }, "hoverStatus":{ "border-radius":"0px 3px 0px 0px" } } }
(7)事件框架添加图片注释,不超过 140 字(可选)配置代码示例:{ "alias":"BuildTreeTreeView", "key":"xui.UI.TreeView", "host":this, "properties":{ "name":"BuildTreeTreeGrid", "items":[ { "borderType":"none", "caption":"JAVA树", "dynDestory":false, "hidden":false, "id":"getBuildTree", "imageClass":"bpmfont bpmgongzuoliuxitongpeizhi", "tagVar":{ } } ], "iniFold":false, "dynDestory":true }, "events":{ //获取数据 "onGetContent":{ "actions":[ { "args":[ "{page.ReloadChild.setQueryData()}", null, null, "{args[1].tagVar}", "" ], "desc":"设置扩展参数", "method":"setQueryData", "redirection":"other:callback:call", "target":"ReloadChild", "type":"control" } ] }, //数据项选择 "onItemSelected":{ "actions":[ { "args":[ "{args[1].id}" ], "conditions":[ { "symbol":"non-empty", "right":"", "conditionId":"_nonempty_{args[1].className}", "left":"{args[1].className}" } ], "desc":"删除存在页", "method":"removeItems", "target":"BuildTreeTab", "type":"control" } ] } } }
(8)动作调用功能概览添加图片注释,不超过 140 字(可选)添加图片注释,不超过 140 字(可选)配置实例代码:{ "args":[ "{page.ReloadChild.setQueryData()}", null, null, "{args[1].tagVar}", "" ], "desc":"设置扩展参数", "method":"setQueryData", "redirection":"other:callback:call", "target":"ReloadChild", "type":"control" }, { "args":[ "{page.ReloadChild.invoke()}", "temp", null, "{args[2]}" ], "desc":"子节点装载", "method":"invoke", "redirection":"other:callback:call", "return":false, "target":"ReloadChild", "type":"control" } ] }
(9)插件体系插件是嵌入到设计器的内置管理功能,不同于业务组件,插件更多的是系统极的扩展功能。在实际应用中也比较常见,如系统运行期我们要根据用户不同显示不同内容数据,这就需要权限插件来完成,而业务用户在使用过程也会涉及到大量的业务和数据的流转功能而这些功能则需要动态的来管理页面的属性,甚至动态生成注入页面。这就需要流程插件来辅助完成。 在实际开发过程中特别是真实项目的工程开发时,我们往往要针对工程方面的进行很多的宏操作比如批量的修改特定组件样式,按特定条件检索复制组件特性,自动添加动作等等。这些都需要类似的宏插件来完成,OneCode 在整合后端运行以及服务部署方面也是按插件体系来规范的。分别针对,DSM建模提供了DSM插件,发布管理及运行提供了OPS插件, API整合方面提供了代理服务器插件,系统插件部分采用全开源方式共有需要的用户自行修改方便用户后期可以参照插件体系来修改自身的插件体系。添加图片注释,不超过 140 字(可选)添加图片注释,不超过 140 字(可选)四,OneCode中后台OneCode 本身基于JAVA语言体系,是在Java Spring 注解基础上的一套扩展子集,可以在普通额Java程序中通过添加注解,来实现前后台与低代码引擎的交互处理。(1)渲染原理(2)示例展示添加图片注释,不超过 140 字(可选)(3)完整模块OneCode添加图片注释,不超过 140 字(可选)添加图片注释,不超过 140 字(可选)@Controller @RequestMapping("/admin/org/person/") @MethodChinaName(cname = "人员管理", imageClass = "spafont spa-icon-login") @Aggregation(sourceClass = PersonService.class) public class PersonAPI { @RequestMapping(method = RequestMethod.POST, value = "Persons") @GridViewAnnotation() @ModuleAnnotation( caption = "人员列表") @APIEventAnnotation(autoRun = true, bindMenu = {CustomMenuItem.reload}) @ResponseBody public ListResultModel<List<PersonGridView>> getPersons(String orgId) { ListResultModel<List<PersonGridView>> resultModel = new ListResultModel<List<PersonGridView>>(); List<Person> personList = new ArrayList<>(); try { personList = getService().getPersons(orgId); resultModel = PageUtil.getDefaultPageList(personList, PersonGridView.class); } catch (Exception e) { e.printStackTrace(); } return resultModel; } @MethodChinaName(cname = "人员信息") @RequestMapping(method = RequestMethod.POST, value = "PersonInfo") @NavGroupViewAnnotation() @APIEventAnnotation(callback = {CustomCallBack.ReloadParent, CustomCallBack.Close}, bindMenu = {CustomMenuItem.editor}) @DialogAnnotation @ModuleAnnotation(caption = "编辑人员信息", width = "800", height = "550") @ResponseBody public ResultModel<PersonNav> getPersonInfo(String personId) { ResultModel<PersonNav> resultModel = new ResultModel<PersonNav>(); return resultModel; } @MethodChinaName(cname = "添加人员") @RequestMapping(method = RequestMethod.POST, value = "AddPersonView") @FormViewAnnotation @APIEventAnnotation(bindMenu = {CustomMenuItem.add}, autoRun = true) @Disabled @ModuleAnnotation( caption = "添加人员信息", width = "370", height = "260") @ResponseBody public ResultModel<AddPerson> AddPerson(String orgId) { ResultModel<AddPerson> resultModel = new ResultModel<AddPerson>(); CtPerson person = new CtPerson(); person.setOrgId(orgId); resultModel.setData(new AddPerson(person)); return resultModel; } @MethodChinaName(cname = "保存成员信息") @RequestMapping(value = {"savePerson"}, method = {RequestMethod.GET, RequestMethod.POST}) @APIEventAnnotation(callback = {CustomCallBack.ReloadParent, CustomCallBack.Close}, bindMenu = CustomMenuItem.save) public @ResponseBody ResultModel<Boolean> savePerson(@RequestBody CtPerson person) { ResultModel<Boolean> userStatusInfo = new ResultModel<Boolean>(); getService().savePerson(person); return userStatusInfo; } @MethodChinaName(cname = "删除人员") @RequestMapping(value = {"delPerson"}, method = {RequestMethod.GET, RequestMethod.POST}) @APIEventAnnotation(callback = {CustomCallBack.Reload, CustomCallBack.ReloadParent}, bindMenu = CustomMenuItem.delete) public @ResponseBody ResultModel<Boolean> delPerson(String iD) { ResultModel<Boolean> userStatusInfo = new ResultModel<Boolean>(); getService().delPerson(iD); return userStatusInfo; } PersonService getService() { return EsbUtil.parExpression(PersonService.class); } } @PageBar // @GridAnnotation(event = CustomGridEvent.editor, customService = PersonService.class, customMenu = {GridMenu.Add, GridMenu.Delete, GridMenu.Reload}) public class PersonGridView { @CustomAnnotation(pid = true, hidden = true) String orgId; @CustomAnnotation(pid = true, hidden = true) String roleId; @CustomAnnotation(uid = true, hidden = true) String iD; @CustomAnnotation(caption = "用户名称", required = true) String name; @CustomAnnotation(caption = "账户信息", required = true) String account; @CustomAnnotation(caption = "邮箱") String email; @InputAnnotation(inputType = InputType.password) @CustomAnnotation(caption = "密码", required = true) String password; @CustomAnnotation(caption = "手机") String mobile; @CustomAnnotation(caption = "部门名称") String orgName; public PersonGridView(Person person) { this.iD = person.getID(); this.orgId = person.getOrgId(); this.name = person.getName(); this.account = person.getAccount(); this.password = person.getPassword(); this.mobile = person.getMobile(); this.email = person.getEmail(); Org org = null; try { org = OrgManagerFactory.getOrgManager().getOrgByID(person.getOrgId()); this.orgName = org.getName(); } catch (OrgNotFoundException e) { e.printStackTrace(); } } } @BottomBarMenu @FormAnnotation(bottombarMenu = {CustomFormMenu.Save, CustomFormMenu.Close}, customService = PersonService.class, col = 1) public class AddPerson { @CustomAnnotation(uid = true, hidden = true) String iD; @CustomAnnotation(caption = "用户名称", required = true) String name; @CustomAnnotation(pid = true, hidden = true) String orgId; @CustomAnnotation(pid = true, hidden = true) String roleId; @CustomAnnotation(caption = "账户信息", required = true) String account; @CustomAnnotation(caption = "邮箱") String email; @InputAnnotation(inputType= InputType.password) @CustomAnnotation(caption = "密码", required = true) String password; @CustomAnnotation(caption = "手机") String mobile; public AddPerson(Person person) { this.iD = person.getID(); this.orgId = person.getOrgId(); this.name = person.getName(); this.account = person.getAccount(); this.password = person.getPassword(); this.mobile = person.getMobile(); this.email = person.getEmail(); } }
(五)DSM建模工具DSM建模,百度百科是这样定义的:特定域建模(Domain-specific modeling,DSM),是一种设计和开发系统(如电脑软件)的软件工程方法学。它系统使用图形化特定域语言(DSL),表现系统的各个方面。DSM的语言倾向于支持比通用建模语言更高级别的抽象,因此需要较少的努力和更少的底层细节来描述特定系统。低代码技术应用可以通过提供更强的工具,提升程序员的代码效率。但其本质上也是一种特定场景下的软件描述方法,这个层面上低码技术和DSM思想是有其相通相同之处的,产品在设计之初就将DSM建模语言的构建以及工具支撑作为了底层支撑设计,将应用中积累的建模应用采用DSM的思想进行重构整合在底层打通。我们将现有的资源类的工具,统一到仓库应用中,包括统一的物料库导入,统一的数据源(数据库,外部存储)管理。并通代码工厂的辅助构建统一到Contenxt(OneCode)的当前环境技术模型中。在各个特定的业务模型中,完成独立的聚合实体整合,以及相应的服务管理,并为相关的方法模型透视管理服务。在OneCode 的基础之上,摆脱传统的 代码模板与生成机制。实现代码向模型的逆向能力。将DSM设计能贯穿整个项目开发实施管理过程,打造结余真实的代码之上的建模语言。(1)仓储建模(2)聚合应用(3)视图工厂(4)支撑域添加图片注释,不超过 140 字(可选)(5)流程建模添加图片注释,不超过 140 字(可选)六,应用集成(1)内核最小集合集成(JS离线应用)设计器内核完全采用JS脚本完成,是独立的前端框架。内核版本包括:RAD 页面设计编辑器,xui运行运行脚本两部分。添加图片注释,不超过 140 字(可选)最小集合仅包含,页面设计及器以及插件框架。设计界面集成:下载开源包后,运行debug.html 即可打开编辑器。添加图片注释,不超过 140 字(可选)集成到自由应用时只需要,引入两个关键的js lib包即可集成到自有的应用。<script type="text/javascript" src="/RAD/xui.js"></script> <script type="text/javascript" src="/RAD/index.js"></script> <script type="text/javascript"> var lang = (function () { var dft = 'zh-cn', map = { 'en-us': 'en', 'zh-cn': 'cn' }, n = navigator, l = (n.language || n.browserLanguage || '').toLowerCase(); return map[l] || map[dft]; })(); xui.include("xui.Locale." + lang + '.doc', "/RAD/Locale/" + lang + ".js", function () { xui.Module.load('RAD', function () { SPA = this; }, lang); });
运行支撑环境集成:打开:设计完成的文件再应用环境中引入如下代码即可<script type="text/javascript" src="/xui/js/xui-all.js"></script> <script type="text/javascript" src="./xuiconf.js"></script> <script type="text/javascript" src="/xui/Locale/cn.js"></script>
(2)团队协作版团队协作版是独立的服务器部署版,在官网上申请账号后,可以在独立的服务器上运行。启动后通过浏览器访问 http://demoserver:83 用管理员 sysadmin 登录:首次登录 会进入默认的工程配置界面配置工程关联API设定团队管理员刷机前如何双WIPE操作教程?
因为安卓手机的recovery (恢复)界面一般是英文的,wipe是清理的意思。wipe data 和wipe cache 清理数据和清理缓存称为双清,为方便也说双wipe,一般关机状态下同时按音量键减和开机键就能进入recovery如不能请查找相关资料,选择factory set 确定就执行清理数据了,清理缓存是wipe cache就不说了一样的操作,双清是为了防止数据冲突引发系统问题,双清为了保险省事,不会在刷机前在设置里恢复出厂设置效果差不多
先科户外音响怎么恢复出厂设置?
要恢复先科户外音响的出厂设置,可以按照以下步骤进行操作:
首先,确保音响处于开启状态。
然后,在音响上找到"Settings"或"Set up"按钮,并按下进入设置菜单。
接着,在菜单中找到"Factory Reset"或"Reset to Factory Defaults"选项,并选择确认。
此时,系统将会提示确认操作,确认后音响将开始恢复为出厂设置。
完成恢复后,音响将重启并重新初始化设置,返回最初的默认设置。请注意,这个操作将清除所有用户自定义的设置和存储的数据,因此请确保在执行之前进行备份。


还没有评论,来说两句吧...