首页 文章 精选 留言 我的

精选列表

搜索[过拟合],共10002篇文章
优秀的个人博客,低调大师

每日一博 | 写 Java 这么久,JDK 源码编译过没?编译 JDK 源码踩坑纪实

好奇害死羊 很多小伙伴们做Java开发,天天写Java代码,肯定离不开Java基础环境:JDK,毕竟我们写好的Java代码也是跑在JVM虚拟机上。 一般来说,我们学Java之前,第一步就是安装JDK环境。这个简单啊,我们一般直接把JDK从官网下载下来,安装完成,配个环境变量就可以愉快地使用了。 不过话说回来,对于这个天天使用的东西,我们难道不好奇这玩意儿它到底是怎么由源码编译出来的吗? 带着这个原始的疑问,今天准备大干一场,自己动动呆萌的小手,来编译一个属于自己的JDK吧! 对了,本文在开源项目:https://github.com/hansonwang99/JavaCollection中已收录,包含自学编程路线、面试题集合/面经、及系列技术文章等,资源持续更新中... 还有个待填的坑 记得之前不是出过一期关于《JDK源码阅读环境搭建》相关的视频以及文章嘛,细心的小伙伴,可能会发现一个很实际的问题: 我们将src.zip包里的JDK源码解压出来,关联到这份源码之后,调试时是可以进,但是我们在加注释的时候却只能在行尾添加,并不能改变原代码的行结构。换句话说,如果在源码中加了跨行的多行注释,则debug调试的时候就会出现当前行的运行错位问题,这个有点尴尬了。 当然那个视频的评论区,的确也有几个小伙伴提了这个问题: 原因也很简单,因为实际支撑调试运行的代码,并不是我们解压出来的那份JDK源码,那个仅仅是做关联用,实际运行用到的JDK,还是之前系统安装好的那个JDK环境。 要想解决这个问题,那就只能使用自己修改过的代码来自行编译生成自己的JDK,然后用到项目中去! 所以什么都憋说了,肝就完了! 环境准备 首选说在前面的是,编译前的软件版本关系极其重要,自己在踩坑时,所出现的各种奇奇怪怪的问题几乎都和这个有关,后来版本匹配之后,就非常顺利了。 我们来盘点和梳理一下编译一个JDK需要哪些环境和工具: 1、boot JDK 我们要想编译JDK,首先自己本机必须提前已经安装有一个JDK,官方称之为bootstrap JDK(或者称为boot JDK)。 比如想编译JDK 8,那本机必须最起码得有一个JDK 7或者更新一点的版本;你想编译JDK 11,那就要求本机必须装有JDK 10或者11。 所以鸡生蛋、蛋生鸡又来了... 2、Unix环境 编译JDK需要Unix环境的支持! 这一点在Linux操作系统和macOS操作系统上已经天然的保证了,而对于Windows兄弟来说稍微麻烦一点,需要通过使用Cygwin或者MinGW/MSYS这种软件来模拟。 就像官方所说:在Linux平台编译JDK一般问题最少,容易成功;macOS次之;Windows上则需要稍微多花点精力,问题可能也多一些。 究其本质原因,还是因为Windows毕竟不是一个Unix-Like内核的系统,毕竟很多软件的原始编译都离不开Unix Toolkit,所以相对肯定要麻烦一些。 3、编译器/编译工具链 JDK底层源码(尤其JVM虚拟机部分)很多都是C++/C写的,所以相关编译器也跑不掉。 一图胜千言,各平台上的编译器支持如下表所示,按平台选择即可: 4、其他工具 典型的比如: Autoconf:软件源码包的自动配置工具 Make:编译构建工具 freetype:一个免费的渲染库,JDK图形化部分的代码可能会用它 好,环境盘点就到这里,接下来具体列一下我在编译JDK 8和JDK 11时分别用到的软件详细版本信息: 编译JDK 8时: 操作系统:macOS 10.11.6 boot JDK:JDK 1.8.0 (build 1.8.0_201-b09) Xcode版本:8.2 编译器:Version 8.0.0 (at /usr/bin/clang) 编译JDK 11时: 操作系统:macOS 10.15.4 boot JDK:JDK 11.0.7 (build 11.0.7+8-LTS) Xcode版本:11.5 编译器:Version 11.0.3 (at /usr/bin/clang) 大家在编译时如果过程中有很多问题,大概率少软件没装,或者软件版本不匹配,不要轻易放弃,需要耐心自查一下。 下载JDK源码 下载JDK源码其实有两种方式。 方式一:通过Mercurial工具下载 Mercurial可以理解为和Git一样,是另外一种代码管理工具,安装好之后就有一个hg命令可用。 而OpenJDK的源码已经提前托管到http://hg.openjdk.java.net/。 因此,比如下载JDK 8,可直接hg clone一下就行,和git clone一样: hgclonehttp://hg.openjdk.java.net/jdk8/jdk8 同理,下载JDK 11: hgclonehttp://hg.openjdk.java.net/jdk/jdk11 但是这种方式下载速度不是很快。 方式二:直接下载打包好的源码包 下载地址:https://jdk.java.net/ 选择你想要的版本下载即可。 编译前的自动配置 源码包下载好,放到本地某个目录(建议路径纯英文,避免不必要的麻烦),解压之,然后进入源码根目录,执行: shconfigure 当然这里运行的是默认配置项。 这一步会进行一系列的自动配置工作,时间一般很快,最终如果能出现一下提示,那么很幸运,编译前的配置工作就完成了! 这里我给出我自己分别在配置JDK 11和JDK 8时候完成时的样子: 配置JDK 8完成: 配置JDK 11完成: 注:如果这一步出错,大概率是某个软件环境未装,或者即使装了,但版本不匹配,控制台打印日志里一般是会提醒的。 比如我在配置JDK 8的时候,就遇到了一个errof:GCC compiler is required的问题: 明明系统里已经有编译器,但还是报这个错误。通过后来修改jdk源码根目录/common/autoconf/generated-configure.sh文件,将相关的两行代码注释后就配置通过了 配置完成,接下来开始执行真正的编译动作了! 真正的编译动作 我们这里进行的是全量编译,直接在我们下载的JDK源码根目录下执行如下命令即可: makeall 这一步编译需要一点时间,耐心等待一下即可。编译过程如果有错误,会终止编译,如果能看到如下两个画面,那么则恭喜你,自己编译JDK源码就已经通过了,可以搞一杯咖啡庆祝一下了。 JDK 8编译完成: JDK 11编译完成: 从两张图的对比可以看出,编译JDK 8和JDK 11完成时在输出上还是有区别的。时间上的区别很大程度上来源于JDK 11的编译机配置要高不少。 验证成果 JDK源码编译完成之后肯定会产生和输出很多产物,这也是我们所迫不及待想看到的。 由于JDK 8和JDK 11的源码包组织结构并不一样,所以输出东西的内容和位置也有区别。我们一一来盘点一下。 1、JDK 8的编译输出 编译完成,build目录下会生成一个macosx-x86_64-normal-server-release目录,所有的编译成果均位于其中。 首先,编译出来的Java可执行程序可以在如下目录里找到: jdk源码根目录/build/macosx-x86_64-normal-server-release/jdk/bin 进入该目录后,可以输入./java -version命令验证: 其次,编译生成的成品JDK套装,可以在目录 jdk源码根目录/build/macosx-x86_64-normal-server-release/images 下找到,如图所示: 其中: j2sdk-image:编译生成的JDK j2re-image:编译生成的JRE 进入j2sdk-image目录会发现,里面的内容和我们平时从网络上下载的成品JDK内容一致。 2、JDK 11的编译输出 JDK 11的源码目录组织方式和JDK 8本身就有区别,编译生成的产物和上面编译JDK 8的输出有一定区别,但也不大。 JDK 11编译完成,同样在build目录下会生成一个macosx-x86_64-normal-server-release目录,所有的编译成果均位于其中。 同样编译出来的Java可执行程序可以在目录 JDK源码根目录/build/macosx-x86_64-normal-server-release/jdk/bin 下看到,进入该目录后,也可以输入./java -version命令验证: 其次,编译生成的成品JDK 11套装,可以在目录 JDK源码根目录/build/macosx-x86_64-normal-server-release/images 下找到,如图所示: 其中jdk目录就是编译生成的成品JDK 11套装。 使用自己编译的JDK 既然我们已经动手编译出了JDK成品,接下来我们得用上哇。 新建一个最最基本的Java工程,比如命名为JdkTest,目的是把我们自己编译出的JDK给用上。 我们点开Project Structure,选到SDKs选项,新添加上自己刚刚编译生成的JDK,并选为项目的JDK,看看是否能正常工作 点击确定之后,我们运行之: 可以看到我们自己编译出的JDK已经用上了。 关联JDK源码并修改 我们继续在上一步JdkTest项目的Project Structure→SDKs里将JDK源码关联到自行下载的JDK源码路径上: 这样方便我们对自己下载的JDK源码进行阅读、调试、修改、以及在源码里随意做笔记和加注释。 举个最简单的例子,比如我们打开System.out.println()这个函数的底层源码: 我们随便给它修改一下,加两行简单的标记,像这样: 为了使我们新加的代码行生效,我们必须要重新去JDK源码的根目录中再次执行make images重新编译生成JDK方可生效: 因为之前已经全量编译过了,所以再次make的时候增量编译一般很快。 重新编译之后,我们再次运行JdkTest项目,就可以看到改动的效果了: 多行注释的问题 记得之前搭建《JDK源码阅读环境》时,大家可能发现了一个问题:阅读源码嘛,给源代码做点注释或笔记很常见!但那时候有个问题就是做注释时不可改变代码的行结构(只能行尾注释,不能跨行注释),否则debug调试时会出现行号错位的问题。 原因很简单,因为我们虽然做了源代码目录的映射,但是实际支撑运行的JDK还是预先安装好的那个JDK环境,并不是根据我们修改后的源码来重新编译构建的,所以看到这里,解决这个问题就很简单,就像上面一样自行编译一下JDK即可。 实际在实验时,还有一个很典型的问题是,当添加了多行的中文注释后,再编译居然会报错! 比如,还是以上面例子中最简单的System.out.println()源码为例,我们添加几行中文注释: 这时候我们去JDK源码目录下编译会发现满屏类似这样的报错: 错误: 编码 ascii 的不可映射字符 顿时有点懵,毕竟仅仅是加了几行注释。对于我们来说,源码里写点多行的中文注释基本是刚需,然而编译竟会报错,这还能不能让人愉快的玩耍了... 当时后背有点发凉。 实不相瞒,就这个问题排查了一段时间,熬到了很晚。最终折腾了一番,通过如下这种方式解决了,顺便分享给小伙伴们,大家如果遇到了这个问题,可以参考着解决一下。 因为从控制台的报错可以很明显的看出,肯定是字符编码相关的问题导致的,而且都指向了ascii这种编码方式。 于是将JDK的源码从根目录导入了Vs Code,然后全目录查找encoding ascii相关的内容,看看有没有什么端倪,结果发现 jdk源码根目录/make/common/SetupJavaCompilers.gmk文件中有两处指定了ascii相关的编码方式: 于是尝试将这两处-encoding ascii的均替换成-encoding utf-8: 然后再次执行make images编译,编译顺利通过! 至此大功告成! 这样后面不管是阅读、调试还是定制JDK源码都非常方便了。 后记:这篇文章在开源项目:https://github.com/hansonwang99/JavaCollection中也已经收录了,包含自学编程路线、面试题集合/面经、及系列技术文章等,资源持续更新中... 每天进步一点点 慢一点才能更快

优秀的个人博客,低调大师

我自己开发的工具,打印出百度贴吧某用户发表过的所有帖子

<html> <meta charset="UTF-8"/> <style> a { color: green; font-family: arial; font-weight: bold } </style> <body> <div id="container"></div> </body> <script src="jquery1.7.1.js"> /* Jerry 2017-02-06 14:58PM update should use C:\MyApp\Chrome\Application\chrome.exe --user-data-dir="C:/yaas" --disable-web-security and then FIRST LOG ON BAIDU successfully!!!! */ </script> <script> /* Jerry 2017-02-05 5:54PM 这个警告的意思是说:请求的资源可能会被(扩展/或其他什么机制)屏蔽掉。 之所以会出现这个警告,是因为去获取该资源的请求其实并(还)没有真的发生,所以 Header 里显示的是伪信息,直到服务器真的有响应返回,这里的 Header 信息才会被更新为真实的。不过这一切也可能不会发生,因为该请求可能会被屏蔽。比如说 AdBlock 什么的,当然了不全是浏览器扩展,具体情况具体分析了。 对了,别忘了用 chrome://net-internals 来帮助你查找被屏蔽的请求以及可能的原因。 */ var PREFIX = "http://tieba.baidu.com"; var START = "http://tieba.baidu.com/i/i/my_tie"; //var START = "http://www.baidu.com"; var POST = {}; var TOTAL = 0; var SORTED = []; function getTotalCount(collection){ var count = 0; for( bar in collection){ if( !collection.hasOwnProperty(bar)) continue; var postList = collection[bar]; count += postList.length; } return count; } function shouldEnd(previousCount) { TOTAL = getTotalCount(POST); console.log("pre: " + previousCount + " total: " + TOTAL); return ( previousCount == TOTAL ); } function main() { var html = getPostByAJAX(START); handleLiChildren(html); var page = 2; while(1){ var prevCount = getTotalCount(POST); var task = START + "?&pn=" + page; var html = getPostByAJAX(task); handleLiChildren(html); page++; /* if( page >=2 ) break;*/ if( shouldEnd(prevCount) ) break; } sort(); generate(); } function handleLiChildren(resultString){ var htmlDom = $(resultString); var liChildren = $("li", htmlDom); $.each( liChildren, function(i, value) { // if( value.className.indexOf("nav_item") != -1 ) if( value.className) return true; if( value.innerText == "我回复的" || value.innerText == "我的精品") return true; var detail = parseDetail(value); insertPost(detail); }); } /* <ul> <li> <cite>2016</cite> <a href="/f?kw=%E5%A4%A7%E9%82%91" >尿素氮</a> </li> <li> <cite>2015</cite> <a href="/f?kw=%E5%A4%A7%E9%82%91" >尿素氮2</a> </li> </ul> */ function getpostSource(post) { var source = "<li><cite>"; source += post.date + "/<cite>"; source += '<a href="' + post.url + '">' + post.postTitle + "</a></li>"; return source; } function getBarPostsSource(barName, posts) { var source = '<h1>' + barName + ': ' + posts.length + '个</h1>'; source += "<ul>"; for( var i = 0; i < posts.length; i++){ var post = posts[i]; source += getpostSource(post); } source += "</ul>"; return source; } function sortNumber(a,b){ return b.size - a.size; } function sort() { for( barName in POST) { if( !POST.hasOwnProperty(barName)) continue; var post = { name: barName, size: POST[barName].length }; SORTED.push(post); } SORTED.sort(sortNumber); } function generate(){ var div = document.getElementById("container"); var source = "总共帖子: " + TOTAL + "个"; for( var i = 0; i < SORTED.length; i++){ var posts = POST[SORTED[i].name]; source += getBarPostsSource(SORTED[i].name, posts); } div.innerHTML = source; } $(function(){ main(); }); function getPostByAJAX(requestURL){ var html = $.ajax({ url: requestURL, xhrFields: { // The 'xhrFields' property sets additional fields on the XMLHttpRequest. // This can be used to set the 'withCredentials' property. // Set the value to 'true' if you'd like to pass cookies to the server. // If this is enabled, your server must respond with the header // 'Access-Control-Allow-Credentials: true'. withCredentials: true }, async: false}).responseText; debugger; return html; } /* function getPostByAJAX(requestURL){ var settings = { type: "GET", crossOrigin: true, url:requestURL, error: function(XHR,textStatus,errorThrown) { alert ("XHR="+XHR+"\ntextStatus="+textStatus+"\nerrorThrown=" + errorThrown); }, success: function(data,textStatus) { debugger; }, headers: { "Access-Control-Allow-Origin":"http://tieba.baidu.com", "Access-Control-Allow-Headers":"X-Requested-With" } }; $.ajax(settings); } */ /* function getPostByAJAX(requestURL){ var html = $.ajax({ url: requestURL, dataType:"jsonp", xhrFields: { // The 'xhrFields' property sets additional fields on the XMLHttpRequest. // This can be used to set the 'withCredentials' property. // Set the value to 'true' if you'd like to pass cookies to the server. // If this is enabled, your server must respond with the header // 'Access-Control-Allow-Credentials: true'. withCredentials: true }, async: false}).responseText; return html; } */ function insertPost(postDetail){ if( !POST[postDetail.barName]){ POST[postDetail.barName] = []; } POST[postDetail.barName].push(postDetail); } function parseDetail(liNode) { var cite = $("cite", liNode); var date = cite[0].innerHTML; // value1 var tds = $("td", liNode); var a1 = $("a", tds[0]); var barName = a1[0].innerHTML; // value2 var a2 = $("a", tds[1]); var postTitle = a2[0].innerHTML; // value3 var url = PREFIX + a2.attr("href"); return { date: date, barName: barName, postTitle: postTitle, url: url } } function getTestData(){ return '<!DOCTYPE html><html><body><div class="wrap1"><div class="wrap2"><div ' + ' id="main_wrapper" class="main_wrapper"><div id="main_back_img"><div ' + ' id="main_back_bottom"><div id="container" class="ibody clearfix"><div><div ' + ' id="content"><div class="simple_block_container"><ul><li><cite>2-16</cite>' + '<div class="wrap_container"><table><tr><td class="nowrap">在<a style="" ' + ' href="/f?kw=%E5%A4%A7%E9%82%91" target="_blank">ANDROID吧</a> 发贴</td><td class="wrap">' + '<a href="/p/4356641476?pid=84106363194&amp;cid=0#841063631" class="thread_title" target="_blank">硬盘</a></td>' + '</tr></table></div><div class="clear"></div></li>' + '<li></li><li></li></ul></div></div></div></div></div></div></div></div></body></html>'; } </script> </html> 本文来自云栖社区合作伙伴“汪子熙”,了解相关信息可以关注微信公众号"汪子熙"。

优秀的个人博客,低调大师

Java程序员金九银十跳槽面试,微服务架构是你必须过的坎

近几年,微服务架构迅速在整个技术社区窜红,被认为是 IT 软件架构的未来方向。一线互联网公司由于具有大量的业务体量和业务场景,比如阿里、百度、网易,很早就开始入坑微服务架构。 但说起微服务,不少人还是有这样的困惑:“作为一个开发,微服务架构是不是和我关系不大?那不都是架构师的事吗?”关于这个问题,我来谈谈自己的看法。微服务是当下最火热的后端架构之一。不管你是一个什么级别的程序员,也不论你在一个什么体量的公司,服务化都是你迟早会遇到的难题。实践微服务的过程本身也是一个升级打怪的过程,这中间你会遇到基本上所有后端架构的问题。解决了这些问题,你自然也就理解了那些高深的概念,也就成为了一名架构师,成长和能力提升都是这个过程的附属品。并且,你了解微服务架构之后,能知道领导为什么让你这么做,也更容易站在系统角度思考公司技术的进程,这对于你的大局观构建来说非常有帮助。再者,微服务这技术在面试的时候总有人提,尤其对于头部互联网企业,微服务架构更是面试考核必备,所以“进大厂必须掌握的50个微服务面试问题”等一些文章备受欢迎。今天专门分享一份微服务架构的技术路线给大家 如果下面这些微服务面试题总分是100分,看看你能答多少分呢?1.什么是 Spring Cloud?2.使用 Spring Cloud 有什么优势?3.服务注册和发现是什么意思?Spring Cloud 如何实现?4.负载平衡的意义什么?5.什么是 Hystrix?它如何实现容错?6.什么是 Hystrix 断路器?我们需要它吗?7.什么是 Netflix Feign?它的优点是什么?8.什么是 Spring Cloud Bus?我们需要它吗?9.什么是 Spring Boot?10.Spring Boot 有哪些优点?11.什么是 JavaConfig?12.如何重新加载 Spring Boot 上的更改,而无需重新启动服务器?13.Spring Boot 中的监视器是什么?14.如何在 Spring Boot 中禁用 Actuator 端点安全性?15.什么是 YAML?16.如何实现 Spring Boot 应用程序的安全性?17.如何使用 Spring Boot 实现分页和排序?18.什么是 Swagger?你用 Spring Boot 实现了它吗?19.什么是 Spring Batch?20.如何使用 Spring Boot 实现异常处理? 欢迎大家一起交流,喜欢文章记得点个赞,感谢支持!

优秀的个人博客,低调大师

Spring常见的十大错误,78%的老程序员都踩过这些坑!

首先我们来看一下,Spring常见错误有那些1.太过关注底2.内部结构 “泄露”3.缺乏关注点分离4.缺乏异常处理或处理不当5.多线程处理不当6.不使用基于注解的验证7.(依旧)使用基于xml的配置8.忽略 profile9.无法接受依赖项注入10.缺乏测试,或测试不当 接下来就一一介绍这些常见的错误1. 错误一:太过关注底层我们正在解决这个常见错误,是因为 “非我所创” 综合症在软件开发领域很是常见。症状包括经常重写一些常见的代码,很多开发人员都有这种症状。虽然理解特定库的内部结构及其实现,在很大程度上是好的并且很有必要的(也可以是一个很好的学习过程),但作为软件工程师,不断地处理相同的底层实现细节对个人的开发生涯是有害的。像 Spring 这种抽象框架的存在是有原因的,它将你从重复地手工劳作中解放出来,并允许你专注于更高层次的细节 —— 领域对象和业务逻辑。因此,接受抽象。下次面对特定问题时,首先进行快速搜索,确定解决该问题的库是否已被集成到 Spring 中;现在,你可能找到一个合适的现成解决方案。比如,一个很有用的库,在本文的其他部分,我将在示例中使用 Project Lombok 注解。Lombok 被用作样板代码生成器,希望懒惰的开发人员在熟悉这个库时不会遇到问题。举个例子,看看使用 Lombok 的 “标准 Java Bean” 是什么样子的:如你所想,上述代码被编译为:但是,请注意,如果你打算在 IDE 中使用 Lombok,很可能需要安装一个插件,可在 此处 找到 Intellij IDEA 版本的插件。 2. 错误二:内部结构 “泄露”公开你的内部结构,从来都不是一个好主意,因为它在服务设计中造成了不灵活性,从而促进了不好的编码实践。“泄露” 的内部机制表现为使数据库结构可以从某些 API 端点访问。例如,下面的 POJO(“Plain Old Java Object”)类表示数据库中的一个表: @Entity @NoArgsConstructor @Getter public class TopTalentEntity { @Id @GeneratedValue private Integer id; @Column private String name; public TopTalentEntity(String name) { this.name = name; } } 假设,存在一个端点,他需要访问 TopTalentEntity 数据。返回 TopTalentEntity 实例可能很诱人,但更灵活的解决方案是创建一个新的类来表示 API 端点上的 TopTalentEntity 数据。 @AllArgsConstructor @NoArgsConstructor @Getter public class TopTalentData { private String name; } 这样,对数据库后端进行更改将不需要在服务层进行任何额外的更改。考虑下,在TopTalentEntity 中添加一个 “password” 字段来存储数据库中用户密码的 Hash 值 —— 如果没有 TopTalentData 之类的连接器,忘记更改服务前端,将会意外地暴露一些不必要的秘密信息。 3. 错误三:缺乏关注点分离随着程序规模的增长,逐渐地,代码组织成为一个越来越重要的问题。讽刺的是,大多数好的软件工程原则开始在规模上崩溃 —— 特别是在没有太多考虑程序体系结构设计的情况下。开发人员最常犯的一个错误就是混淆代码关注点,这很容易做到!通常,打破 关注点分离 的是将新功能简单地 “倒” 在现有类中。当然,这是一个很好的短期解决方案(对于初学者来说,它需要更少的输入),但它也不可避免地会在将来成为一个问题,无论是在测试期间、维护期间还是介于两者之间。考虑下下面的控制器,它将从数据库返回 TopTalentData。 @RestController public class TopTalentController { private final TopTalentRepository topTalentRepository; @RequestMapping("/toptal/get") public List<TopTalentData> getTopTalent() { return topTalentRepository.findAll() .stream() .map(this::entityToData) .collect(Collectors.toList()); } private TopTalentData entityToData(TopTalentEntity topTalentEntity) { return new TopTalentData(topTalentEntity.getName()); } } 起初,这段代码似乎没什么特别的问题;它提供了一个从 TopTalentEntity 实例检索出来的TopTalentData 的 List。然而,仔细观察下,我们可以看到 TopTalentController 实际上在此做了些事情;也就是说,它将请求映射到特定端点,从数据库检索数据,并将从 TopTalentRepository 接收的实体转换为另一种格式。一个“更干净” 的解决方案是将这些关注点分离到他们自己的类中。看起来可能是这个样子的:**@RestController@RequestMapping("/toptal")@AllArgsConstructorpublic class TopTalentController { private final TopTalentService topTalentService; @RequestMapping("/get") public List<TopTalentData> getTopTalent() { return topTalentService.getTopTalent(); } }@AllArgsConstructor@Servicepublic class TopTalentService { private final TopTalentRepository topTalentRepository; private final TopTalentEntityConverter topTalentEntityConverter; public List<TopTalentData> getTopTalent() { return topTalentRepository.findAll() .stream() .map(topTalentEntityConverter::toResponse) .collect(Collectors.toList()); } }@Componentpublic class TopTalentEntityConverter { public TopTalentData toResponse(TopTalentEntity topTalentEntity) { return new TopTalentData(topTalentEntity.getName()); } }** 这种层次结构的另一个优点是,它允许我们通过检查类名来确定将功能驻留在何处。此外,在测试期间,如果需要,我们可以很容易地用模拟实现来替换任何类。 4. 错误四:缺乏异常处理或处理不当一致性的主题并非是 Spring(或 Java)所独有的,但仍然是处理 Spring 项目时需要考虑的一个重要方面。虽然编码风格可能存在争议(通常团队或整个公司内部已达成一致),但拥有一个共同的标准最终会极大地提高生产力。对多人团队尤为如此;一致性允许交流发生,而不需要花费很多资源在手把手交接上,也不需要就不同类的职责提供冗长的解释。考虑一个包含各种配置文件、服务和控制器的 Spring 项目。在命名时保持语义上的一致性,可以创建一个易于搜索的结构,任何新的开发人员都可以按照自己的方式管理代码;例如,将 Config 后缀添加到配置类,服务层以 Service 结尾,以及控制器用 Controller 结尾。与一致性主题密切相关,服务器端的错误处理值得特别强调。如果你曾经不得不处理编写很差的 API 的异常响应,那你可能知道原因 —— 正确解析异常会是一件痛苦的事情,而确定这些异常最初发生的原因则更为痛苦。作为一名 API 开发者,理想情况下你希望覆盖所有面向用户的端点,并将他们转换为常见的错误格式。这通常意味着有一个通用的错误代码和描述,而不是逃避解决问题:a) 返回一个 “500 Internal Server Error”信息。b) 直接返回异常的堆栈信息给用户。(实际上,这些都应该不惜一切代价地去避免,因为除了客户端难以处理以外,它还暴露了你的内部信息)。例如,常见错误响应格式可能长这样: @Value public class ErrorResponse { private Integer errorCode; private String errorMessage; } 与此类似的事情在大多数流行的 API 中也经常遇到,由于可以容易且系统地记录,效果往往很不错。将异常转换为这种格式可以通过向方法提供 @ExceptionHandler 注解来完成(注解案例可见于第六章)。 5. 错误五:多线程处理不当不管是桌面应用还是 Web 应用,无论是 Spring 还是 No Spring,多线程都是很难破解的。由并行执行程序所引起的问题是令人毛骨悚然且难以捉摸的,而且常常难以调试 —— 实际上,由于问题的本质,一旦你意识到你正在处理一个并行执行问题,你可能就不得不完全放弃调试器了,并 “手动” 检查代码,直到找到根本上的错误原因。不幸的是,这类问题并没有千篇一律的解决方案;根据具体场景来评估情况,然后从你认为最好的角度来解决问题。当然,理想情况下,你也希望完全避免多线程错误。同样,不存在那种一刀切的方法,但这有一些调试和防止多线程错误的实际考虑因素:5.1. 避免全局状态首先,牢记 “全局状态” 问题。如果你正创建一个多线程应用,那么应该密切关注任何可能全局修改的内容,如果可能的话,将他们全部删掉。如果某个全局变量有必须保持可修改的原因,请仔细使用 synchronization,并对程序性能进行跟踪,以确定没有因为新引入的等待时间而导致系统性能降低。5.2. 避免可变性这点直接来自于 函数式编程,并且适用于 OOP,声明应该避免类和状态的改变。简而言之,这意味着放弃 setter 方法,并在所有模型类上拥有私有的 final 字段。它们的值唯一发生变化的时间是在构造期间。这样,你可以确定不会出现争用问题,且访问对象属性将始终提供正确的值。5.3. 记录关键数据评估你的程序可能会在何处发生异常,并预先记录所有关键数据。如果发生错误,你将很高兴可以得到信息说明收到了哪些请求,并可更好地了解你的应用程序为什么会出现错误。需要再次注意的是,日志记录引入了额外的文件 I/O,可能会严重影响应用的性能,因此请不要滥用日志。5.4. 复用现存实现每当你需要创建自己的线程时(例如:向不同的服务发出异步请求),复用现有的安全实现来代替创建自己的解决方案。这在很大程度上意味着要使用 ExecutorServices 和 Java 8 简洁的函数式 CompletableFutures 来创建线程。Spring 还允许通过 DeferredResult 类来进行异步请求处理。 6. 错误六:不使用基于注解的验证假设我们之前的 TopTalent 服务需要一个端点来添加新的 TopTalent。此外,假设基于某些原因,每个新名词都需要为 10 个字符长度。执行此操作的一种方法可能如下: @RequestMapping("/put") public void addTopTalent(@RequestBody TopTalentData topTalentData) { boolean nameNonExistentOrHasInvalidLength = Optional.ofNullable(topTalentData) .map(TopTalentData::getName) .map(name -> name.length() == 10) .orElse(true); if (nameNonExistentOrInvalidLength) { // throw some exception } topTalentService.addTopTalent(topTalentData); } 然而,上面的方法(除了构造很差以外)并不是一个真正 “干净” 的解决办法。我们正检查不止一种类型的有效性(即 TopTalentData 不得为空,TopTalentData.name 不得为空,且 TopTalentData.name 为 10 个字符长度),以及在数据无效时抛出异常。通过在 Spring 中集成 Hibernate validator,数据校验可以更干净地进行。让我们首先重构 addTopTalent 方法来支持验证: @RequestMapping("/put") public void addTopTalent(@Valid @NotNull @RequestBody TopTalentData topTalentData) { topTalentService.addTopTalent(topTalentData); } @ExceptionHandler @ResponseStatus(HttpStatus.BAD_REQUEST) public ErrorResponse handleInvalidTopTalentDataException(MethodArgumentNotValidException methodArgumentNotValidException) { // handle validation exception } // 此外,我们还必须指出我们想要在 TopTalentData 类中验证什么属性: public class TopTalentData { @Length(min = 10, max = 10) @NotNull private String name; } 现在,Spring 将在调用方法之前拦截其请求并对参数进行验证 —— 无需使用额外的手工测试。另一种实现相同功能的方法是创建我们自己的注解。虽然你通常只在需要超出 Hibernate的内置约束集 时才使用自定义注解,本例中,我们假设 @Length 不存在。你可以创建两个额外的类来验证字符串长度,一个用于验证,一个用于对属性进行注解: @Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) @Documented @Constraint(validatedBy = { MyAnnotationValidator.class }) public @interface MyAnnotation { String message() default "String length does not match expected"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; int value(); } @Component public class MyAnnotationValidator implements ConstraintValidator<MyAnnotation, String> { private int expectedLength; @Override public void initialize(MyAnnotation myAnnotation) { this.expectedLength = myAnnotation.value(); } @Override public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) { return s == null || s.length() == this.expectedLength; } } 请注意,这些情况下,关注点分离的最佳实践要求在属性为 null 时,将其标记为有效(isValid 方法中的 s == null),如果这是属性的附加要求,则使用 @NotNull 注解。 public class TopTalentData { @MyAnnotation(value = 10) @NotNull private String name; } 7. 错误七:(依旧)使用基于xml的配置虽然之前版本的 Spring 需要 XML,但如今大部分配置均可通过 Java 代码或注解来完成;XML 配置只是作为附加的不必要的样板代码。本文(及其附带的 GitHub 仓库)均使用注解来配置 Spring,Spring 知道应该连接哪些 Bean,因为待扫描的顶级包目录已在 @SpringBootApplication 复合注解中做了声明,如下所示: @SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } } 复合注解(可通过 Spring 文档 了解更多信息)只是向 Spring 提示应该扫描哪些包来检索 Bean。在我们的案例中,这意味着这个顶级包 (co.kukurin)将用于检索: @Component (TopTalentConverter, MyAnnotationValidator) @RestController (TopTalentController) @Repository (TopTalentRepository) @Service (TopTalentService) 类 如果我们有任何额外的 @Configuration 注解类,它们也会检查基于 Java 的配置。 8. 错误八:忽略 profile在服务端开发中,经常遇到的一个问题是区分不同的配置类型,通常是生产配置和开发配置。在每次从测试切换到部署应用程序时,不要手动替换各种配置项,更有效的方法是使用 profile。推荐阅读:Spring Boot Profile不同环境配置。关注Java技术栈微信公众号,在后台回复关键字:boot,可以获取一份栈长整理的 Spring Boot 最新技术干货。考虑这么一种情况:你正在使用内存数据库进行本地开发,而在生产环境中使用 MySQL 数据库。本质上,这意味着你需要使用不同的 URL 和 (希望如此) 不同的凭证来访问这两者。让我们看看可以如何做到这两个不同的配置文件:8.1. APPLICATION.YAML 文件 # set default profile to 'dev' spring.profiles.active: dev # production database details spring.datasource.url: 'jdbc:mysql://localhost:3306/toptal' spring.datasource.username: root spring.datasource.password: 8.2. APPLICATION-DEV.YAML 文件 spring.datasource.url: 'jdbc:h2:mem:' spring.datasource.platform: h2 假设你不希望在修改代码时意外地对生产数据库进行任何操作,因此将默认配置文件设为 dev 是很有意义的。然后,在服务器上,你可以通过提供 -Dspring.profiles.active=prod 参数给 JVM 来手动覆盖配置文件。另外,还可将操作系统的环境变量设置为所需的默认 profile。 9. 错误九:无法接受依赖项注入正确使用 Spring 的依赖注入意味着允许其通过扫描所有必须的配置类来将所有对象连接在一起;这对于解耦关系非常有用,也使测试变得更为容易,而不是通过类之间的紧耦合来做这样的事情: public class TopTalentController { private final TopTalentService topTalentService; public TopTalentController() { this.topTalentService = new TopTalentService(); } } 我们让 Spring 为我们做连接: public class TopTalentController { private final TopTalentService topTalentService; public TopTalentController(TopTalentService topTalentService) { this.topTalentService = topTalentService; } } Misko Hevery 的 Google talk 深入解释了依赖注入的 “为什么”,所以,让我们看看它在实践中是如何使用的。在关注点分离(常见错误 #3)一节中,我们创建了一个服务和控制器类。假设我们想在 TopTalentService 行为正确的前提下测试控制器。我们可以通过提供一个单独的配置类来插入一个模拟对象来代替实际的服务实现: @Configuration public class SampleUnitTestConfig { @Bean public TopTalentService topTalentService() { TopTalentService topTalentService = Mockito.mock(TopTalentService.class); Mockito.when(topTalentService.getTopTalent()).thenReturn( Stream.of("Mary", "Joel").map(TopTalentData::new).collect(Collectors.toList())); return topTalentService; } } 然后,我们可以通过告诉 Spring 使用 SampleUnitTestConfig 作为它的配置类来注入模拟对象: @ContextConfiguration(classes = { SampleUnitTestConfig.class }) 之后,我们就可以使用上下文配置将 Bean 注入到单元测试中。 10. 错误十:缺乏测试,或测试不当尽管单元测试的概念已经存在很长时间了,但很多开发人员似乎要么 “忘记” 做这件事(特别是如果它不是 “必需” 的时候),要么只是在事后把它添加进来。这显然是不可取的,因为测试不仅应该验证代码的正确性,还应该作为程序在不同场景下应如何表现的文档。在测试 Web 服务时,很少只进行 “纯” 单元测试,因为通过 HTTP 进行通信通常需要调用 Spring 的 DispatcherServlet,并查看当收到一个实际的 HttpServletRequest 时会发生什么(使它成为一个 “集成” 测试,处理验证、序列化等)。REST Assured,一个用于简化测试REST服务的 Java DSL,在 MockMVC 之上,已经被证明提供了一个非常优雅的解决方案。考虑以下带有依赖项注入的代码片段: @RunWith(SpringJUnit4Cla***unner.class) @ContextConfiguration(classes = { Application.class, SampleUnitTestConfig.class }) public class RestAssuredTestDemonstration { @Autowired private TopTalentController topTalentController; @Test public void shouldGetMaryAndJoel() throws Exception { // given MockMvcRequestSpecification givenRestAssuredSpecification = RestAssuredMockMvc.given() .standaloneSetup(topTalentController); // when MockMvcResponse response = givenRestAssuredSpecification.when().get("/toptal/get"); // then response.then().statusCode(200); response.then().body("name", hasItems("Mary", "Joel")); } } SampleUnitTestConfig 类将 TopTalentService 的模拟实现连接到 TopTalentController 中,而所有的其他类都是通过扫描应用类所在包的下级包目录来推断出的标准配置。RestAssuredMockMvc 只是用来设置一个轻量级环境,并向 /toptal/get 端点发送一个 GET请求。 欢迎大家一起交流,喜欢文章记得点个赞哟,感谢支持!

优秀的个人博客,低调大师

【南京Meetup】苏宁Elastic平台化实践中踩过哪些坑,又是如何解决的?

在南京 Elastic Meetup 南京交流会专场中,苏宁大数据平台搜索平台组的韩宝君为我们带来如何在大量的数据中发现数据的价值。从大数据平台的架构出发,详细解读了平台的概况和服务化平台的模块等方面的知识。最后,具体举出了在实践中出现的一些问题及对应的处理方案。阿里云Elasticsearch1核2G首月免费试用,开始云上实践吧直播视频回顾 [PPT下载请点击]https://yq.aliyun.com/download/2885) 以下为精彩视频内容整理: 苏宁大数据平台总体架构 大数据平台职责是提供苏宁集团各个业务所需要的大数据存储和计算能力,保证平台的稳定、高效运行,高平台易用性。本文将从ES平台总体介绍、ES平台化之路、实战经验这三个方面来为大家详细解读。大数据平台分为服务层、计算层、存储层三个部分。服务层包括大数据管理平

优秀的个人博客,低调大师

python易错盲点排查之+=与+的区别分析以及一些赋值运算踩过的坑

问题1. int和list是不一样的 >>> a=1 >>> b=a >>> a+=1 >>> a,b (2, 1) >>> a=[1,2,3,4] >>> b=a >>> a+=[5] >>> a,b ([1, 2, 3, 4, 5], [1, 2, 3, 4, 5]) 通俗地讲,类型为int时,a和b是“不一样的”;类型为list时,a和b是“一样的”。术语叫做immutable和mutable,具体原理在这个节点不必深究。问题1.1. 我们通常运行b=a这一语句时,会直觉地认为,b和a已经不一样了。 >>> a=[[1],[2],[3],[4]] >>> b+=a[0:2] >>> b [1, 2, 3, 4, [1], [2]] >>> a=[[1],[2],[3],[4]] >>> b=[] >>> b+=a[0:2] >>> a,b ([[1], [2], [3], [4]], [[1], [2]]) >>> b[0] [1] >>> b[0][0]='changed!' >>> # You don't expect a to change >>> # However >>> a, b ([['changed!'], [2], [3], [4]], [['changed!'], [2]]) 可以看到,a[0]的[1]和b[0]的[1]是“一样的”,因为改变b[0]就会改变a[0](注意不是改变b,是改变b[0]。改变b不会对a有任何影响)问题2. list的情况下,a+=b和a=a+b是不一样的: >>> a=[1,2,3,4] >>> b=a >>> a+=[5] >>> a,b ([1, 2, 3, 4, 5], [1, 2, 3, 4, 5]) >>> a=[1,2,3,4] >>> b=a >>> a=a+[5] >>> a,b ([1, 2, 3, 4, 5], [1, 2, 3, 4]) 同样通俗地讲,在+=的情况下,a还是原来的a,和b“一样”;在+的情况下,a已经不是原来的a了,和b“不一样”。问题3. 如果要让+=和+行为一致,应该怎么做? >>> import copy >>> a=[1,2,3,4] >>> b=copy.deepcopy(a) >>> a+=[5] >>> a,b ([1, 2, 3, 4, 5], [1, 2, 3, 4]) 这与问题2中a=a+b的情况结果一致了。当对list进行b=a时,实际上进行的是“引用”操作;只有使用b=copy.deepcopy(a)才是进行我们通常期望的“拷贝”操作。问题4. 回到问题中的代码,当k=1时,以下代码: subset += (elements[0:size]) 根据问题1.1,subset与elements是“一样的”,因此未来改变subset的元素的操作有可能改变elements的元素。 到这行代码时,注意set就是递归传递过来的subset: #set[j] += (elements[i]) #Why Elements change here? set[j] = set[j] + (elements[i]) 根据问题2,+=中set[j]依然是原来的set[j],也就可能是elements的元素。因此 set[j] += elements[i] 可能会等价于 elements[*] += elements[i] 一旦改变了elements的元素,结果自然就不对了。怎么解决这个问题?根据问题3,只要保证set与elements是“不一样的”,就符合程序的逻辑。因此将 subset += (elements[0:size]) 改为(记得import copy) subset += copy.deepcopy(elements[0:size]) 就能在+=的情况下正常运行了。总结:在python中,list类型的赋值b=a进行的引用操作,而非拷贝操作,在需要拷贝操作时,需要加上b=copy.deepcopy(a)。(copy.copy和copy.deepcopy的区别超出问题范畴,有兴趣可以google) 您可以考虑给博主来个小小的打赏以资鼓励,您的肯定将是我最大的动力。thx. 微信打赏 支付宝打赏 作 者: Angel_Kitty 出 处:http://www.cnblogs.com/ECJTUACM-873284962/ 关于作者:潜心机器学习以及信息安全的综合研究。如有问题或建议,请多多赐教! 版权声明:本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。 特此声明:所有评论和私信都会在第一时间回复。也欢迎园子的大大们指正错误,共同进步。或者直接私信我 声援博主:如果您觉得文章对您有帮助,可以点击右下角【推荐】推荐一下该博文。您的鼓励是作者坚持原创和持续写作的最大动力!

优秀的个人博客,低调大师

云计算市场价格战猛过双十一:巨头大战 小厂商或被洗牌

在刚刚过去的双十一,不仅是电商在降价促销,吸引网购者不顾“剁手”风险去“买、买、买”,还有云计算服务提供商,也在这段时间进行了新一轮大幅打折促销。领衔这场云计算服务降价促销的正是阿里云与腾讯云,最高降幅达到50%,不可谓不狠。 素来暗流涌动的云计算市场,因价格战而变得更加激烈火爆。在业内人士看来,云计算市场的价格战应该会持续一段时间,而且这只是云计算服务提供商竞争的一方面,他们更要比拼的是自己的产业链实力、生态圈力量,小的厂商很难避免被洗牌的命运。 互联网企业掀起云计算价格战 今年的云计算市场十分热闹,多家大企业纷纷向云技术领域发力。上半年,马化腾亲自为腾讯云发布会站台吆喝,下半年,华为、网易隆重向市场推出了各自的云计算服务,让原本暗潮涌动的云计算市场愈发不平静。作为行业内起步较早的云计算服务商,阿里云、盛大云则在今年内多次调价,希望巩固自己在市场上的原有地位。 据了解,双十一期间阿里云推出了特别促销价,最高折扣达到50%。尽管目前价格已恢复正常,但与去年同期相比,降幅依然不低。阿里云服务中心一位销售人员告诉《IT时报》记者,对于按年租赁阿里云服务器的用户,他们目前推出的是“一年八五折、二年七折、三年五折”的优惠,与之前买满十个月送两个月的优惠政策相比,已经是变相降价。据了解,仅过去一年,阿里云对云计算大数据产品和服务进行了17次降价调整。在今年7月,阿里云宣布华南深圳节点计算类产品价格下调50%,主要针对按量付费使用的云服务器ECS。这位销售人员坦承,如此频繁的价格调动,目的是为抢占更多市场。 同样是老牌云计算服务提供商,盛大云的核心产品云主机今年内也进行了两次调价,降价幅度超过60%。以购买1核2GB规格的云主机为例,包月价格从原来的149元降至60元,包年的价格更是从1788元直降为600元。 “云计算的降价一部分原因与硬件成本下降有关,同时这也是为了应对后来者发起低价攻势而做出的应对。”一位来自云计算业内人士表示,腾讯云、网易云等等来势汹汹,价格上都要比阿里云低出不少。其中,腾讯云计算按量计费,同时分三个阶梯价,用户使用时长越长,费用折扣比就越低。比如,腾讯推出的云数据库产品MySQL,用户使用96小时以上,每小时可获得25%价格优惠,使用360小时以上,每小时价格将再优惠25%,相当于直接优惠50%。 而网易云的价格在三家中相对偏低,记者从网易云销售团队处了解到,网易云的基础计算产品蜂巢采取的是按时计费的模式,用户根据所需带宽、大数据库、服务器配置等计算每小时成本。按照两台2核2G的计算服务器,5M带宽的配置计算,一个月的成本在300到400元。在双十一期间,网易云并未推出大幅促销,但目前针对新加入会员企业推出1000元新人礼金。 从价格战到综合实力对抗 未来云计算的竞争一定不会完全依靠价格,而更多的是比拼生态。在此前阿里巴巴举行的云栖大会上,阿里云总裁胡晓明就曾表示,“在云计算领域没有一家愿意去做亏本生意,要不断通过技术提升寻求更多机会。” 因此,未来云计算的核心竞争力是解决方案、生态圈。腾讯体系中除了游戏、微信、QQ之外,最新的人工智能领域的AI实验室、物联网等新业务体系都已经与腾讯云形成了一条生态链。网易云尽管是今年才正式上线,但在其内部早已形成了生态架构,包括云音乐、云课堂以及网易旗下业务核心暴雪游戏等同样是网易云的服务对象。 “月消费在几百万元或以上的客户和云厂商之间的合作一般不会只局限于云计算的合作,更多考虑的是品牌间的价值交换。”一位业内人士看来,随着生态链的不断拓展,云计算领域的价格战将不再是用户选择的主要因素,取而代之的是社交关系链、广告资源、游戏分发平台等等,这些都将是云计算平台竞争力的体现。 “今后,云计算会越来越接近基础建设,强调自身性能和稳定性之外,也会被融入更多产业链中,因此,尽管现在阿里云市场庞大,但后来者并不是没有赶超的机会。”上述业内人士表示。从最新发布的腾讯第三季度财报来看,来自云服务及支付服务业务的收入已经同比增长348%。 云技术的竞争,将很难避免地演变成综合实力的较量,小型的云计算厂商将面临新的洗牌。随着互联网巨头纷纷转战云计算市场,行业的竞争将不仅仅局限于云计算业务本身能否盈利,市场份额的多少将直接决定背后所能掌握的大数据量,这才是互联网企业未来竞争的关键, “未来发展的重要趋势是人工智能,而这将在很大程度上依托对海量数据的掌握以及有效开发上。”因此,谁能在云计算平台上争取更多用户,谁就更容易在通往未来的竞争之路跑得更快一些。 本文转自d1net(转载)

资源下载

更多资源
腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

用户登录
用户注册