首页 文章 精选 留言 我的

精选列表

搜索[腾讯云双十一抄作业],共10000篇文章
优秀的个人博客,低调大师

敏捷史话(十一):敏捷宣言“间谍”——Steve Mellor

Steve Mellor 是敏捷宣言的签署人之一,他自称是作为“间谍”去参加雪鸟会议的。 起初收到会议邀请时,Steve 非常惊讶,因为他所做的工作一直都是关于建模方面的,很少将深受敏捷实践者喜爱的编码和测试作为重点。确实,我们很少会看到“敏捷”和“建模”同时出现,接下来我们就来了解 Steve Mellor 与它们的故事吧。 Steve Mellor 与“敏捷” 在收到会议邀请前,Steve 刚读过 Kent Beck 的《极限编程》,书中所说的:不重视前期思考、憎恶模型、反对文档……这些理论着实吓到了他,不过也激发了他的好奇心。 冬日里,闲在落基山脉无事可做,Steve 决定去参加雪鸟会议一探究竟。 会议上,Steve 坦言,他原本想邪恶地来阻挠雪鸟会议的计划,但是在会议的过程中,他却发现自己对大家所提出的绝大多数观点都十分赞同。比如,对“前期大规模设计”的过度强调是存在的。就这样,Steve 成为了一名敏捷的支持者,只不过他关注的仍然是建模的价值,尤其是自己十多年来专注于构建的可执行模型。 Steve Mellor 与“建模” 就在雪鸟会议之前,Steve几乎与所有的宣言签署者都有过一段对话,有时对话不只一次。对话如下所示: Steve 与其他人意见相左,是因为他们在“模型”这个词的含义上各持己见。一些签署人把模型视为草图,用完即扔;更让人愤怒的是把模型视作蓝图,画完后直接扔给隔壁言听计从的开发人员。这些做法 Steve 都不认可,他认为模型是可以运行的。 雪鸟会议中谈论到模型时,Steve 一直听到关于无法用统一建模语言写“Hello World”程序的说法。事实上,这是可以做到的,只是不容易做到。因为早在雪鸟会议之前,Steve 及其团队成员就已经在运用自己的动作语言运行模型了。此时他意识到,当下亟需解决的问题是,建模要被广泛认为是可执行的。 不相关的两个事物融合,往往会发生奇妙的化学反应。后来,Steve 这样描述“敏捷”和“建模”:“敏捷”和“建模”虽然很少出现在同一个句子中,但它们一点也不冲突。恰恰相反,建模者能从实施敏捷的人身上学到许多,例如尽早为模型构建测试;遵循敏捷过程的人,也能从提高生产率以及轻松地跟客户沟通过程中受益。无疑,所有人都能从中获益。 其实,Steve 对建模的执着追求,早在几十年前就有了苗头。 Steve Mellor 与他的追求 1974年,Steve 在埃塞克斯大学拿到了首批计算机科学学士学位,而后在世界上最大的粒子物理学实验室,也是万维网的发源地——CERN 总部,开始了他的职业生涯。在CERN 中,Steve 主要负责加速器控制系统,用以支持 CERN 出售在不同国家或地区的系统。 如果说这是一个计算机工作者梦寐以求的事业,那 Steve 还有第二个、第三个…… 1977年,Steve 加入了美国最杰出的国家实验室之一——伯克利实验室,为多个项目提供了系统支持软件。不到两年的时间,比团队中任何成员都年轻的他成为了一名出色的小组负责人,领导团队为多个项目开发控制系统。 此时大量的实操项目对 Steve 来说只是不断的重复,他认为建模在未来的可能性远远大于当下,而眼下,是要让更多人知道建模的价值。 1982年,Steve 全职加入由程序设计方法学的开拓者之一—— Edward Yourdon 创办的咨询公司 Yourdon Inc.。在那里,他与 Paul Ward 合作,重新开发IT课程。于是,Ward-Mellor 方法问世了,发表在他极具开创性的三部曲《实时系统的结构化开发》中。Steve向多家公司提供了咨询服务,这也令他重新找到了事业方向。 1985年,Steve 与 Sally Shlaer 共同创立了 Project Technology Inc.,目标是提供咨询服务。几年间,他们不断开设课程,以期将技术更快地传达给客户。也就是在这个时期,他们开发出了Shlaer-Mellor方法:最早的面向对象分析设计方法学。并于1998年出版了第一本有关该主题的书——《面向对象的分析:在数据中建模世界》,随后又相继出版了《对象生命周期:建模世界》以及有关模型驱动开发的特刊,还创建了第一个模型编译器。 从顾问委员会主席、澳大利亚国立大学兼职教授、首席科学家到程序主席……种种身份都是Steve 在建模领域走出的一步步踏实而坚定的脚步。 曾有人问过 Steve ,如何才能成为一个优秀的架构师,他笑而不语。但他早已归结出一个方法——“永远不要相信你最近创建的系统是唯一的,应设法寻找不同方法来解决相同类型的问题。” 架构师如此,程序员亦如此。

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

带你入门前端工程(十一):微前端

什么是微服务?先看看维基百科的定义: 微服务(英语:Microservices)是一种软件架构风格,它是以专注于单一责任与功能的小型功能区块 (Small Building Blocks) 为基础,利用模块化的方式组合出复杂的大型应用程序,各功能区块使用与语言无关 (Language-Independent/Language agnostic)的API集相互通信。 换句话说,就是将一个大型、复杂的应用分解成几个服务,每个服务就像是一个组件,组合起来一起构建成整个应用。 想象一下,一个上百个功能、数十万行代码的应用维护起来是个什么场景? 牵一发而动全身,仅仅修改一处代码,就需要重新部署整个应用。经常有“修改一分钟,编译半小时”的情况发生。 代码模块错综复杂,互相依赖。更改一处地方的代码,往往会影响到应用的其他功能。 如果使用微服务来重构整个应用有什么好处? 一个应用分解成多个服务,每个服务独自服务内部的功能。例如原来的应用有 abcd 四个页面,现在分解成两个服务,第一个服务有 ab 两个页面,第二个服务有 cd 两个页面,组合在一起就和原来的应用一样。 当应用其中一个服务出故障时,其他服务仍可以正常访问。例如第一个服务出故障了, ab 页面将无法访问,但 cd 页面仍能正常访问。 好处:不同的服务独立运行,服务与服务之间解耦。我们可以把服务理解成组件,就像本小书第 3 章《前端组件化》中所说的一样。每个服务可以独自管理,修改一个服务不影响整体应用的运行,只影响该服务提供的功能。 另外在开发时也可以快速的添加、删除功能。例如电商网站,在不同的节假日时推出的活动页面,活动过后马上就可以删掉。 难点:不容易确认服务的边界。当一个应用功能太多时,往往多个功能点之间的关联会比较深。因而就很难确定这一个功能应该归属于哪个服务。 PS:微前端就是微服务在前端的应用,也就是前端微服务。 微服务实践 现在我们将使用微前端框架 qiankun 来构建一个微前端应用。之所以选用 qiankun 框架,是因为它有以下几个优点: 技术栈无关,任何技术栈的应用都能接入。 样式隔离,子应用之间的样式互不干扰。 子应用的 JavaScript 作用域互相隔离。 资源预加载,在浏览器空闲时间预加载未打开的微应用资源,加速微应用打开速度。 样式隔离 样式隔离的原理是:每次切换子应用时,都会加载该子应用对应的 css 文件。同时会把原先的子应用样式文件移除掉,这样就达到了样式隔离的效果。 我们可以自己模拟一下这个效果: <!-- index.html --> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <link rel="stylesheet" href="index.css"> <body> <div>移除样式文件后将不会变色</div> </body> </html> /* index.css */ body { color: red; } 现在我们加一段 JavaScript 代码,在加载完样式文件后再将样式文件移除掉: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <link rel="stylesheet" href="index.css"> <body> <div>移除样式文件后将不会变色</div> <script> setTimeout(() => { const link = document.querySelector('link') link.parentNode.removeChild(link) }, 3000) </script> </body> </html> 这时再打开页面看一下,可以发现 3 秒后字体样式就没有了。 JavaScript 作用域隔离 主应用在切换子应用之前会记录当前的全局状态,然后在切出子应用之后恢复全局状态。假设当前的全局状态如下所示: const global = { a: 1 } 在进入子应用之后,无论全局状态如何变化,将来切出子应用时都会恢复到原先的全局状态: // global { a: 1 } 官方还提供了一张图来帮助我们理解这个机制: 好了,现在我们来创建一个微前端应用吧。这个微前端应用由三部分组成: main:主应用,使用 vue-cli 创建。 vue:子应用,使用 vue-cli 创建。 react: 子应用,使用的 react 16 版本。 对应的目录如下: -main -vue -react 创建主应用 我们使用 vue-cli 创建主应用(然后执行 npm i qiankun 安装 qiankun 框架): vue create main 如果主应用只是起到一个基座的作用,即只用于切换子应用。那可以不需要安装 vue-router 和 vuex。 改造 App.vue 文件 主应用必须提供一个能够安装子应用的元素,所以我们需要将 App.vue 文件改造一下: <template> <div class="mainapp"> <!-- 标题栏 --> <header class="mainapp-header"> <h1>QianKun</h1> </header> <div class="mainapp-main"> <!-- 侧边栏 --> <ul class="mainapp-sidemenu"> <li @click="push('/vue')">Vue</li> <li @click="push('/react')">React</li> </ul> <!-- 子应用 --> <main class="subapp-container"> <h4 v-if="loading" class="subapp-loading">Loading...</h4> <div id="subapp-viewport"></div> </main> </div> </div> </template> <script> export default { name: 'App', props: { loading: Boolean, }, methods: { push(subapp) { history.pushState(null, subapp, subapp) } } } </script> 可以看到我们用于安装子应用的元素为 #subapp-viewport,另外还有切换子应用的功能: <!-- 侧边栏 --> <ul class="mainapp-sidemenu"> <li @click="push('/vue')">Vue</li> <li @click="push('/react')">React</li> </ul> 改造 main.js 根据 qiankun 文档说明,需要使用 registerMicroApps() 和 start() 方法注册子应用及启动主应用: import { registerMicroApps, start } from 'qiankun'; registerMicroApps([ { name: 'react app', // app name registered entry: '//localhost:7100', container: '#yourContainer', activeRule: '/yourActiveRule', }, { name: 'vue app', entry: { scripts: ['//localhost:7100/main.js'] }, container: '#yourContainer2', activeRule: '/yourActiveRule2', }, ]); start(); 所以现在需要将 main.js 文件改造一下: import Vue from 'vue' import App from './App' import { registerMicroApps, runAfterFirstMounted, setDefaultMountApp, start, initGlobalState } from 'qiankun' let app = null function render({ loading }) { if (!app) { app = new Vue({ el: '#app', data() { return { loading, } }, render(h) { return h(App, { props: { loading: this.loading } }) } }); } else { app.loading = loading } } /** * Step1 初始化应用(可选) */ render({ loading: true }) const loader = (loading) => render({ loading }) /** * Step2 注册子应用 */ registerMicroApps( [ { name: 'vue', // 子应用名称 entry: '//localhost:8001', // 子应用入口地址 container: '#subapp-viewport', loader, activeRule: '/vue', // 子应用触发路由 }, { name: 'react', entry: '//localhost:8002', container: '#subapp-viewport', loader, activeRule: '/react', }, ], // 子应用生命周期事件 { beforeLoad: [ app => { console.log('[LifeCycle] before load %c%s', 'color: green', app.name) }, ], beforeMount: [ app => { console.log('[LifeCycle] before mount %c%s', 'color: green', app.name) }, ], afterUnmount: [ app => { console.log('[LifeCycle] after unmount %c%s', 'color: green', app.name) }, ], }, ) // 定义全局状态,可以在主应用、子应用中使用 const { onGlobalStateChange, setGlobalState } = initGlobalState({ user: 'qiankun', }) // 监听全局状态变化 onGlobalStateChange((value, prev) => console.log('[onGlobalStateChange - master]:', value, prev)) // 设置全局状态 setGlobalState({ ignore: 'master', user: { name: 'master', }, }) /** * Step3 设置默认进入的子应用 */ setDefaultMountApp('/vue') /** * Step4 启动应用 */ start() runAfterFirstMounted(() => { console.log('[MainApp] first app mounted') }) 这里有几个注意事项要注意一下: 子应用的名称 name 必须和子应用下的 package.json 文件中的 name 一样。 每个子应用都有一个 loader() 方法,这是为了应对用户直接从子应用路由进入页面的情况而设的。进入子页面时判断一下是否加载了主应用,没有则加载,有则跳过。 为了防止在切换子应用时显示空白页面,应该提供一个 loading 配置。 设置子应用的入口地址时,直接填入子应用的访问地址。 更改访问端口 vue-cli 的默认访问端口一般为 8080,为了和子应用保持一致,需要将主应用端口改为 8000(子应用分别为 8001、8002)。创建 vue.config.js 文件,将访问端口改为 8000: module.exports = { devServer: { port: 8000, } } 至此,主应用就已经改造完了。 创建子应用 子应用不需要引入 qiankun 依赖,只需要暴露出几个生命周期函数就可以: bootstrap,子应用首次启动时触发。 mount,子应用每次启动时都会触发。 unmount,子应用切换/卸载时触发。 现在将子应用的 main.js 文件改造一下: import Vue from 'vue' import VueRouter from 'vue-router' import App from './App.vue' import routes from './router' import store from './store' Vue.config.productionTip = false let router = null let instance = null function render(props = {}) { const { container } = props router = new VueRouter({ // hash 模式不需要下面两行 base: window.__POWERED_BY_QIANKUN__ ? '/vue' : '/', mode: 'history', routes, }) instance = new Vue({ router, store, render: h => h(App), }).$mount(container ? container.querySelector('#app') : '#app') } if (window.__POWERED_BY_QIANKUN__) { // eslint-disable-next-line no-undef __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__; } else { render() } function storeTest(props) { props.onGlobalStateChange && props.onGlobalStateChange( (value, prev) => console.log(`[onGlobalStateChange - ${props.name}]:`, value, prev), true, ) props.setGlobalState && props.setGlobalState({ ignore: props.name, user: { name: props.name, }, }) } export async function bootstrap() { console.log('[vue] vue app bootstraped') } export async function mount(props) { console.log('[vue] props from main framework', props) storeTest(props) render(props) } export async function unmount() { instance.$destroy() instance.$el.innerHTML = '' instance = null router = null } 可以看到在文件的最后暴露出了 bootstrap mount unmount 三个生命周期函数。另外在挂载子应用时还需要注意一下,子应用是在主应用下运行还是自己独立运行:container ? container.querySelector('#app') : '#app'。 配置打包项 根据 qiankun 文档提示,需要对子应用的打包配置项作如下更改: const packageName = require('./package.json').name; module.exports = { output: { library: `${packageName}-[name]`, libraryTarget: 'umd', jsonpFunction: `webpackJsonp_${packageName}`, }, }; 所以现在我们还需要在子应用目录下创建 vue.config.js 文件,输入以下代码: // vue.config.js const { name } = require('./package.json') module.exports = { configureWebpack: { output: { // 把子应用打包成 umd 库格式 library: `${name}-[name]`, libraryTarget: 'umd', jsonpFunction: `webpackJsonp_${name}` } }, devServer: { port: 8001, headers: { 'Access-Control-Allow-Origin': '*' } } } vue.config.js 文件有几个注意事项: 主应用、子应用运行在不同端口下,所以需要设置跨域头 'Access-Control-Allow-Origin': '*'。 由于在主应用配置了 vue 子应用需要运行在 8001 端口下,所以也需要在 devServer 里更改端口。 另外一个子应用 react 的改造方法和 vue 是一样的,所以在此不再赘述。 部署 我们将使用 express 来部署项目,除了需要在子应用设置跨域外,没什么需要特别注意的地方。 主应用服务器文件 main-server.js: const fs = require('fs') const express = require('express') const app = express() const port = 8000 app.use(express.static('main-static')) app.get('*', (req, res) => { fs.readFile('./main-static/index.html', 'utf-8', (err, html) => { res.send(html) }) }) app.listen(port, () => { console.log(`main app listening at http://localhost:${port}`) }) vue 子应用服务器文件 vue-server.js: const fs = require('fs') const express = require('express') const app = express() const cors = require('cors') const port = 8001 // 设置跨域 app.use(cors()) app.use(express.static('vue-static')) app.get('*', (req, res) => { fs.readFile('./vue-static/index.html', 'utf-8', (err, html) => { res.send(html) }) }) app.listen(port, () => { console.log(`vue app listening at http://localhost:${port}`) }) react 子应用服务器文件 react-server.js: const fs = require('fs') const express = require('express') const app = express() const cors = require('cors') const port = 8002 // 设置跨域 app.use(cors()) app.use(express.static('react-static')) app.get('*', (req, res) => { fs.readFile('./react-static/index.html', 'utf-8', (err, html) => { res.send(html) }) }) app.listen(port, () => { console.log(`react app listening at http://localhost:${port}`) }) 另外需要将这三个应用打包后的文件分别放到 main-static、vue-static、react-static 目录下。然后分别执行命令 node main-server.js、node vue-server.js、node react-server.js 即可查看部署后的页面。现在这个项目目录如下: -main -main-static // main 主应用静态文件目录 -react -react-static // react 子应用静态文件目录 -vue -vue-static // vue 子应用静态文件目录 -main-server.js // main 主应用服务器 -vue-server.js // vue 子应用服务器 -react-server.js // react 子应用服务器 我已经将这个微前端应用的代码上传到了 github,建议将项目克隆下来配合本章一起阅读,效果更好。下面放一下 DEMO 的运行效果图: 小结 对于大型应用的开发和维护,使用微前端能让我们变得更加轻松。不过如果是小应用,建议还是单独建一个项目开发。毕竟微前端也有额外的开发、维护成本。 参考资料 Microservices 可能是你见过最完善的微前端解决方案 qiankun 带你入门前端工程 全文目录: 技术选型:如何进行技术选型? 统一规范:如何制订规范并利用工具保证规范被严格执行? 前端组件化:什么是模块化、组件化? 测试:如何写单元测试和 E2E(端到端) 测试? 构建工具:构建工具有哪些?都有哪些功能和优势? 自动化部署:如何利用 Jenkins、Github Actions 自动化部署项目? 前端监控:讲解前端监控原理及如何利用 sentry 对项目实行监控。 性能优化(一):如何检测网站性能?有哪些实用的性能优化规则? 性能优化(二):如何检测网站性能?有哪些实用的性能优化规则? 重构:为什么做重构?重构有哪些手法? 微服务:微服务是什么?如何搭建微服务项目? Severless:Severless 是什么?如何使用 Severless? 本文同步分享在 博客“谭光志”(SegmentFault)。如有侵权,请联系 support@oschina.cn 删除。本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

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

python设计模式(二十一):状态模式

状态模式,当对象的内部状态改变的时候,允许对象执行不同的流程,看起来就像改写了一个对象,核心的方法是把复杂状态变化情况下的流程抽象出来,简化复杂情况状态的判断。 我们设计一个应用场景:当状态是CPU使用率,在不同状态下的自动化运维脚本执行不同的操作 示例code: classBase: defexecutor(self,value): self.run(value)classLow(Base): def__init__(self): self.name="较低占用率状态" defrun(self,value): print("当前:{}值:{}".format(self.name,value)) print("无应急情况执行")classLarge(Base): def__init__(self): self.name="较高占用率状态" defrun(self,value): print("当前:{}值:{}".format(self.name,value)) print("发送警报邮件")classStatu: def__init__(self): self.value=0.1 self.low=Low() self.large=Large() self.ststu=None defmonitor(self): ifself.value<0.5: self.ststu=self.low else: self.ststu=self.large self.ststu.executor(self.value)if__name__=='__main__': test=Statu() test.monitor() test.value=0.9 test.monitor() 当前:较低占用率状态值:0.1 无应急情况执行 当前:较高占用率状态值:0.9 发送警报邮件 优点: 1、封装了转换规则。 2、枚举可能的状态,在枚举状态之前需要确定状态种类。 3、将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为。 4、允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块。 5、可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数。 缺点: 1、状态模式的使用必然会增加系统类和对象的个数。 2、状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱。 3、状态模式对"开闭原则"的支持并不太好,对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态转换的源代码,否则无法切换到新增状态,而且修改某个状态类的行为也需修改对应类的源代码。 使用场景:1、行为随状态改变而改变的场景。 2、条件、分支语句的代替者。 注意事项:在行为受状态约束的时候使用状态模式,而且状态不超过 5 个。

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

SpringBoot实战(十一)之与JMS简单通信

什么是JMS? 引用百度百科上的说明: JMS即 Java消息服务(Java Message Service)应用程序接口,是一个 Java平台中关于面向 消息中间件(MOM)的 API,用于在两个应用程序之间,或 分布式系统中发送消息,进行异步通信。Java消息服务是一个与具体平台无关的API,绝大多数MOM提供商都对JMS提供支持。 JMS是一种与厂商无关的 API,用来访问消息收发系统消息,它类似于 JDBC(Java Database Connectivity)。这里,JDBC 是可以用来访问许多不同关系数据库的 API,而 JMS 则提供同样与厂商无关的访问方法,以访问消息收发服务。许多厂商都支持 JMS,包括 IBM 的 MQSeries、BEA的 Weblogic JMS service和 Progress 的 SonicMQ。 JMS 使您能够通过消息收发服务(有时称为消息中介程序或路由器)从一个 JMS 客户机向另一个 JMS客户机发送消息。消息是 JMS 中的一种类型对象,由两部分组成:报头和消息主体。报头由路由信息以及有关该消息的元数据组成。消息主体则携带着应用程序的数据或有效负载。根据有效负载的类型来划分,可以将消息分为几种类型,它们分别携带:简单文本(TextMessage)、可序列化的对象 (ObjectMessage)、属性集合 (MapMessage)、字节流 (BytesMessage)、原始值流 (StreamMessage),还有无有效负载的消息 (Message)。 下面进入SpringBoot简单使用JMS示例: 一、导入依赖 <?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>org.springframework</groupId> <artifactId>gs-messaging-jms</artifactId> <version>0.1.0</version> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.8.RELEASE</version> </parent> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-activemq</artifactId> </dependency> <dependency> <groupId>org.apache.activemq</groupId> <artifactId>activemq-broker</artifactId> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project> 二、编写消息接收器 package hello; public class Email { private String to; private String body; public Email() { } public Email(String to, String body) { this.to = to; this.body = body; } public String getTo() { return to; } public void setTo(String to) { this.to = to; } public String getBody() { return body; } public void setBody(String body) { this.body = body; } @Override public String toString() { return String.format("Email{to=%s, body=%s}", getTo(), getBody()); } } 三、定义消息接收者 package hello; import org.springframework.jms.annotation.JmsListener; import org.springframework.stereotype.Component; @Component public class Receiver { @JmsListener(destination = "mailbox", containerFactory = "myFactory") public void receiveMessage(Email email) { System.out.println("Received <" + email + ">"); } } Receiver也被称为消息驱动的POJO。正如您在上面的代码中所看到的,不需要实现任何特定的接口或方法具有任何特定的名称。此外,该方法可以具有非常灵活的签名。请特别注意,此类在JMS API上没有导入。 四、使用Spring发送和接收JMS消息 package hello; import javax.jms.ConnectionFactory; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.jms.DefaultJmsListenerContainerFactoryConfigurer; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.jms.annotation.EnableJms; import org.springframework.jms.config.DefaultJmsListenerContainerFactory; import org.springframework.jms.config.JmsListenerContainerFactory; import org.springframework.jms.core.JmsTemplate; import org.springframework.jms.support.converter.MappingJackson2MessageConverter; import org.springframework.jms.support.converter.MessageConverter; import org.springframework.jms.support.converter.MessageType; @SpringBootApplication @EnableJms public class Application { @Bean public JmsListenerContainerFactory<?> myFactory(ConnectionFactory connectionFactory, DefaultJmsListenerContainerFactoryConfigurer configurer) { DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory(); // This provides all boot's default to this factory, including the message converter configurer.configure(factory, connectionFactory); // You could still override some of Boot's default if necessary. return factory; } @Bean // Serialize message content to json using TextMessage public MessageConverter jacksonJmsMessageConverter() { MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter(); converter.setTargetType(MessageType.TEXT); converter.setTypeIdPropertyName("_type"); return converter; } public static void main(String[] args) { // Launch the application ConfigurableApplicationContext context = SpringApplication.run(Application.class, args); JmsTemplate jmsTemplate = context.getBean(JmsTemplate.class); // Send a message with a POJO - the template reuse the message converter System.out.println("Sending an email message."); jmsTemplate.convertAndSend("mailbox", new Email("info@example.com", "Hello")); } } @Bean注解,主要作用是控制反转(IOC),同 <bean id="connectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" > </bean> 举例说明: 例如以Message为例: @Bean public String message() { return new String("hello"); } 它等价于 <bean id="message" class="java.lang.String"> <constructor-arg index="0" value="hello"/> </bean> 通常情况下,有三种配置Bean的方式: 如图所示 关于Bean注解三种配置法,深入介绍可以参考该博文:https://blog.csdn.net/icarus_wang/article/details/51649635 @EnableJms 触发发现带注释的方法@JmsListener,在封面下创建消息监听器容器。 为清楚起见,我们还定义了一个在接收器注释中myFactory引用的beanJmsListener。因为我们使用DefaultJmsListenerContainerFactoryConfigurerSpring Boot提供的基础结构,所以JmsMessageListenerContainer它与默认情况下引导创建的基础结构相同。 默认MessageConverter是能够转换只有基本类型(例如String,Map,Serializable)我们Email是不是Serializable故意的。我们想要使用Jackson并以文本格式将内容序列化为json(即作为aTextMessage)。Spring Boot将检测a的存在,MessageConverter并将其与默认值JmsTemplate和任何JmsListenerContainerFactory创建者相关联DefaultJmsListenerContainerFactoryConfigurer。 JmsTemplate使消息发送到JMS目的地变得非常简单。在mainrunner方法中,启动后,您可以使用jmsTemplate发送EmailPOJO。因为我们的自定义MessageConverter已自动关联到它,所以只会生成一个json文档TextMessage。 你没看到的两个bean是JmsTemplate和ConnectionFactory。这些是由Spring Boot自动创建的。在这种情况下,ActiveMQ代理运行嵌入式。 注意: SpringJmsTemplate可以通过它的receive方法直接接收消息,但这只能同步工作,这意味着它会阻塞。这就是为什么我们建议您使用侦听器容器,例如DefaultMessageListenerContainer使用基于缓存的连接工厂,这样您就可以异步使用消息并以最大的连接效率。 最后运行结果如下图所示:

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

Hadoop运维记录系列(二十一)

Zeppelin启用https过程和Hack内核以满足客户需求的记录。 原因是这客户很有意思,该客户中国分公司的人为了验证内网安全性,从国外找了一个***测试小组对Zeppelin和其他产品进行***测试,结果发现Zeppelin主要俩问题,一个是在内网没用https,一个是zeppelin里面可以执行shell命令和python语句。其实这不算大问题,zeppelin本来就是干这个用的。但是***小组不了解zeppelin是做什么的,认为即使在内网里,执行shell命令能查看操作系统的一些文件是大问题,然后发生的事就不说了,不是我们的问题了。 不过既然他们要求整改,我们也只好配合,虽然大家都觉得内网域名加https属于脱了裤子放屁,然后不让zeppelin干他本来应该干的事就更过分了,但鉴于客户是甲方,也只好hack源码了。 于是某个周末用了4个小时完成所有工作。 先记录下zeppelin加https访问,我们有自己的域名证书,所以直接用即可。如果没有域名证书,需要自签发,那么可以看第二部分,双向认证步骤。 https第一部分,已有域名添加jks: opensslpkcs12-export-inxxx.com.crt-inkeyxxx.com.key-outxxx.com.pkcs12 keytool-importkeystore-srckeystorexxx.com.pkcs12-destkeystorexxx.com.jks-srcstoretypepkcs12 https第二部分,自签发证书双向认证添加jks #生成root私钥和证书文件。 opensslgenrsa-outroot.key(pem)2048#Generaterootkeyfile opensslreq-x509-new-keyroot.key(pem)-outroot.crt#Generaterootcertfile #创建客户端私钥和证书以及证书请求文件csr opensslgenrsa-outclient.key(pem)2048#Generateclientkeyfile opensslreq-new-keyclient.key(pem)-outclient.csr#Generateclientcertrequestfile opensslx509-req-inclient.csr-CAroot.crt-CAkeyroot.key(pem)-CAcreateserial-days3650-outclient.crt#Userootcerttogenerateclientcertfile #生成服务器端私钥,证书和证书请求文件csr opensslgenrsa-outserver.key(pem)2048#Generateserverkeyfile,useinZeppelin opensslreq-new-keyserver.key(pem)outserver.csr@Generateservercertrequestfile opensslx509-req-inserver.csr-CAroot.crt-CAkeyroot.key(pem)-CAcreateserial-days3650-outserver.crt#Userootcerttogenerateservercertfile #生成客户端端jks文件 opensslpkcs12-export-inclient.crt-inkeyclient.key(pem)-outclient.pkcs12#Packagetopkcs12format,mustinputapassword,youshouldrememberthepassword keytool-importkeystore-srckeystoreclient.pkcs12-destkeystoreclient.jks-srcstoretypepkcs12#Theclientpasswordyoujustinputatlaststep #生成服务器端jks文件 opensslpkcs12-export-inserver.crt-inkeyserver.key(pem)-outserver.pkcs12@Packagetopkcs12format,mustinputapassword,youshouldrememberthepassword keytool-importkeystore-srckeystoreserver.pkcs12-destkeystoreserver.jks-srcstoretypepkcs12#Theserverpasswordyoujustinputatlaststep 如果是不需要双向认证,只要单向自签发,不创建客户端的各种就可以了。 然后找个地把这些文件放过去,再修改zeppelin配置即可。 mkdir-p/etc/zeppelin/conf/ssl cpserver.crtserver.jks/etc/zeppelin/conf/ssl <property> <name>zeppelin.server.ssl.port</name> <value>8443</value> <description>Serversslport.(usedwhensslpropertyissettotrue)</description> </property> <property> <name>zeppelin.ssl</name> <value>true</value> <description>ShouldSSLbeusedbytheservers?</description> </property> <property> <name>zeppelin.ssl.client.auth</name> <value>false</value> <description>ShouldclientauthenticationbeusedforSSLconnections?</description> </property> <property> <name>zeppelin.ssl.keystore.path</name> <value>/etc/zeppelin/conf/ssl/xxx.com.jks</value> <description>PathtokeystorerelativetoZeppelinconfigurationdirectory</description> </property> <property> <name>zeppelin.ssl.keystore.type</name> <value>JKS</value> <description>Theformatofthegivenkeystore(e.g.JKSorPKCS12)</description> </property> <property> <name>zeppelin.ssl.keystore.password</name> <value>passwordwhichyouinputongeneratingserverjksstep</value> <description>Keystorepassword.CanbeobfuscatedbytheJettyPasswordtool</description> </property> 然后反代那里也加上443的ssl证书以及443转8443的upstream即可。 然后是hack zeppelin源码加入关键字限制,这个确实找了一小会zeppelin发送执行源码给interpreter的地方,zeppelin架构比较清晰,但是代码挺复杂的,用到了很多小花活儿。比如thrift,interpreter脚本里建立nc监听。然后各个解释器插件用socket跟interpreter脚本通信,前端angular,后端jetty,还用shiro做验证和授权。回头可以单开好几篇说说zeppelin安装,使用和详细配置,做这项目基本把zeppelin摸透了。 找到发送前端编写内容给interpreter的java代码,然后用很生硬的办法限制执行命令。具体那个.java文件的名字我就不说了,有悬念有惊喜。我不写java,只负责读源码找到代码位置,hack的java是同事写的。然后编译,替换jar包,完成。后面改了改配置,后续的***测试顺利通过。 staticHashSet<String[]>blockedCodeString=newHashSet<>(); static{ blockedCodeString.add(newString[]{"import","os"}); blockedCodeString.add(newString[]{"import","sys"}); blockedCodeString.add(newString[]{"import","subprocess"}); blockedCodeString.add(newString[]{"import","pty"}); blockedCodeString.add(newString[]{"import","socket"}); blockedCodeString.add(newString[]{"import","commands"}); blockedCodeString.add(newString[]{"import","paramiko"}); blockedCodeString.add(newString[]{"import","pexpect"}); blockedCodeString.add(newString[]{"import","BaseHTTPServer"}); blockedCodeString.add(newString[]{"import","ConfigParser"}); blockedCodeString.add(newString[]{"import","platform"}); blockedCodeString.add(newString[]{"import","popen2"}); blockedCodeString.add(newString[]{"import","copy"}); blockedCodeString.add(newString[]{"import","SocketServer"}); blockedCodeString.add(newString[]{"import","sysconfig"}); blockedCodeString.add(newString[]{"import","tty"}); blockedCodeString.add(newString[]{"import","xmlrpmlib"}); blockedCodeString.add(newString[]{"etc"}); blockedCodeString.add(newString[]{"boot"}); blockedCodeString.add(newString[]{"dev"}); blockedCodeString.add(newString[]{"lib"}); blockedCodeString.add(newString[]{"lib64"}); blockedCodeString.add(newString[]{"lost+found"}); blockedCodeString.add(newString[]{"mnt"}); blockedCodeString.add(newString[]{"proc"}); blockedCodeString.add(newString[]{"root"}); blockedCodeString.add(newString[]{"sbin"}); blockedCodeString.add(newString[]{"selinux"}); blockedCodeString.add(newString[]{"usr"}); blockedCodeString.add(newString[]{"passwd"}); blockedCodeString.add(newString[]{"useradd"}); blockedCodeString.add(newString[]{"userdel"}); blockedCodeString.add(newString[]{"rm"}); blockedCodeString.add(newString[]{"akka"}); blockedCodeString.add(newString[]{"groupadd"}); blockedCodeString.add(newString[]{"groupdel"}); blockedCodeString.add(newString[]{"mkdir"}); blockedCodeString.add(newString[]{"rmdir"}); blockedCodeString.add(newString[]{"ping"}); blockedCodeString.add(newString[]{"nc"}); blockedCodeString.add(newString[]{"telnet"}); blockedCodeString.add(newString[]{"ftp"}); blockedCodeString.add(newString[]{"scp"}); blockedCodeString.add(newString[]{"ssh"}); blockedCodeString.add(newString[]{"ps"}); blockedCodeString.add(newString[]{"hostname"}); blockedCodeString.add(newString[]{"uname"}); blockedCodeString.add(newString[]{"vim"}); blockedCodeString.add(newString[]{"nano"}); blockedCodeString.add(newString[]{"top"}); blockedCodeString.add(newString[]{"cat"}); blockedCodeString.add(newString[]{"more"}); blockedCodeString.add(newString[]{"less"}); blockedCodeString.add(newString[]{"chkconfig"}); blockedCodeString.add(newString[]{"service"}); blockedCodeString.add(newString[]{"netstat"}); blockedCodeString.add(newString[]{"iptables"}); blockedCodeString.add(newString[]{"ip"}); blockedCodeString.add(newString[]{"route"}); blockedCodeString.add(newString[]{"curl"}); blockedCodeString.add(newString[]{"wget"}); blockedCodeString.add(newString[]{"sysctl"}); blockedCodeString.add(newString[]{"touch"}); blockedCodeString.add(newString[]{"scala.sys.process"}); blockedCodeString.add(newString[]{"0.0.0.0"}); blockedCodeString.add(newString[]{"git"}); blockedCodeString.add(newString[]{"svn"}); blockedCodeString.add(newString[]{"hg"}); blockedCodeString.add(newString[]{"cvs"}); blockedCodeString.add(newString[]{"exec"}); blockedCodeString.add(newString[]{"ln"}); blockedCodeString.add(newString[]{"kill"}); blockedCodeString.add(newString[]{"rsync"}); blockedCodeString.add(newString[]{"lsof"}); blockedCodeString.add(newString[]{"crontab"}); blockedCodeString.add(newString[]{"libtool"}); blockedCodeString.add(newString[]{"automake"}); blockedCodeString.add(newString[]{"autoconf"}); blockedCodeString.add(newString[]{"make"}); blockedCodeString.add(newString[]{"gcc"}); blockedCodeString.add(newString[]{"cc"}); } staticbooleanallMatch(Stringaim,String[]checker){ if(checker==null||checker.length<1){ returnfalse; }else{ //bydefault,treatasmatch,everynotmatchchangeit for(Stringi:checker){ if(!aim.matches(".*\\b"+i+"\\b.*")){ returnfalse; } } returntrue; } } staticStringanyMatch(Stringaim,HashSet<String[]>all)throwsException{ if(aim.contains("FUCKP&G")){ thrownewException("Howdoyouknowthis????"); }else{ for(String[]one:all){ if(allMatch(aim,one)){ StringBuildersb=newStringBuilder(); for(Strings:one){ sb.append(s+""); } returnsb.toString(); } } thrownewException("Noonematch"); } } //......此处是个public类 try{ StringmatchesStrings=anyMatch(st,blockedCodeString); result=newInterpreterResult(Code.ERROR,"Containsdangerouscode:"+matchesStrings); }catch(Exceptionme){//nomatchany scheduler.submit(job); while(!job.isTerminated()){ synchronized(jobListener){ try{ jobListener.wait(1000); }catch(InterruptedExceptione){ logger.info("ExceptioninRemoteInterpreterServerwhileinterpret,jobListener.wait",e); } } } if(job.getStatus()==Status.ERROR){ result=newInterpreterResult(Code.ERROR,Job.getStack(job.getException())); }else{ result=(InterpreterResult)job.getReturn(); //incaseofjobabortinPENDINGstatus,resultcanbenull if(result==null){ result=newInterpreterResult(Code.KEEP_PREVIOUS_RESULT); } } } //......直到该public类结束 因为客户有deadline限制,所以快速定位源码位置的过程还是挺有意思的,比较紧张刺激,在这个以小时计算deadline压力下,什么intelliJ, Eclipse都不好使啊,就grep和vi最好用,从找到到改完,比客户定的deadline提前了好几个小时。

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

腾讯云软件源

腾讯云软件源

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

Rocky Linux

Rocky Linux

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

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

用户登录
用户注册