微前端"--将微服务扩展到前端开发(实践)
前言与大纲
本文分为理论和实战上下两篇。本篇为微前端的实战篇,共计约 5k 字,预计阅读时间 10 mins。
在技术雷达之「微前端」- 将微服务理念扩展到前端开发(上:理论篇)中,我们介绍了微前端在单体应用和微服务的架构演进中所产生的缘由,将微服务理念运用到前端开发就是为了解决臃肿前端的当前现状。与此同时,合理拆分微前端也给我们的应用开发带来显而易见的好处,在本篇当中我们将逐一介绍微前端的实践方案与可能遇到的问题和对应的优化建议。
文章大纲
微前端的可选实践方案(4 种 +)
创建更小的 Apps(而不是 Components)
如何组合微前端的 App 模块?
Option 1: 使用后端模板引擎插入 HTML
Option 1.1: 渐进式从后端进行加载
Option 2: 使用 IFrame 隔离运行时
Option 3: 客户端 JavaScript 异步加载
Option 4: WebComponents 整合所有功能模块
不同 App 模块之间如何交互?
More Options…
微前端的页面优化与实例
多模块页面加载问题与优化建议
微前端在 AEM(CMS)项目的应用
现成解决方案:Single-SPA “meta framework”
总结与思考:微前端的优缺点
微前端的优点
微前端的缺点
持续思考…
附:参考资料
微前端的可选实践方案(4 种+)
创建更小的 Apps(而不是 Components)
首先让我们来创建一个典型 Web 应用程序的基本组件(Header、ProductList、ShoppingCart),以 Header 组件为例:
# src/App.js
export default () =>
<header>
<h1>Logo</h1>
<nav>
<ul>
<li>About</li>
<li>Contact</li>
</ul>
</nav>
</header>;
然后需要注意的是我们会用到 Express 对刚刚创建的 React 组件进行服务器端渲染,使之成为一个 App 模块:
# server.js
fs.readFile(htmlPath, 'utf8', (err, html) => {
const rootElem = '<div id="root">';
const renderedApp = renderToString(React.createElement(App, null));
res.send(html.replace(rootElem, rootElem + renderedApp));
});
再依次创建其他 Apps 并独立部署:
https://microfrontends-header.herokuapp.com/
https://microfrontends-products-list.herokuapp.com/
https://microfrontends-cart.herokuapp.com/
如何组合微前端的 App 模块?
在每个独立团队创建好各自的 App 模块后,我们就可以将网站或 Web 应用程序视为由各种模块的功能组合。下文将介绍多种技术实践方案来重新组合这些模块(有时作为页面,有时作为组件),而前端(不管是不是 SPA)将只需要负责路由器(Router)如何选择和决定要导入哪些模块,从而为最终用户提供一致性的用户体验。
Option 1: 使用后端模板引擎插入 HTML
# server.js
Promise.all([
getContents('https://microfrontends-header.herokuapp.com/'),
getContents('https://microfrontends-products-list.herokuapp.com/'),
getContents('https://microfrontends-cart.herokuapp.com/')
]).then(responses =>
res.render('index', { header: responses[0], productsList: responses[1], cart: responses[2] })
).catch(error =>
res.send(error.message)
)
);
# views/index.ejs
<head>
<meta charset="utf-8">
<title>Microfrontends Homepage</title>
</head>
<body>
<%- header %>
<%- productsList %>
<%- cart %>
</body>
但是,这种方案也存在弊端,即某些 App 模块可能会需要相对较长的加载时间,而在前端整个页面的渲染却要取决于最慢的那个模块。
比如说,可能 Header 模块的加载速度要比其他部分快得多,而 ProductList 则因为需要获取更多 API 数据而需要更多时间。通常情况下我们希望尽快将网页显示给用户,而在这种情况下后台加载时间就会变得更长。
Option 1.1: 渐进式从后端进行加载
当然,我们也可以通过修改一些后端代码来渐进式地(Progressive)往前端发送 HTML,但与此同时却徒增了后端复杂度,并且又将前端的渲染控制权交回了后端服务器。而且我们的优化也取决于每个模块加载的速度,若是进行优化就必须按一定顺序进行加载。
Option 2: 使用 IFrame 隔离运行时
<body>
<iframe width="100%" height="200" src="https://microfrontends-header.herokuapp.com/"></iframe>
<iframe width="100%" height="200" src="https://microfrontends-products-list.herokuapp.com/"></iframe>
<iframe width="100%" height="200" src="https://microfrontends-cart.herokuapp.com/"></iframe>
</body>
我们也可以将每个子应用程序嵌入到各自的 <iframe>
中,这使得每个模块能够使用任何他们需要的框架,而无需与其他团队协调工具和依赖关系,依然可以借助于一些库或者 Window.postMessageAPI
来进行交互。
优点:
最强大的是隔离了组件和应用程序部分的运行时环境,因此每个模块都可以独立开发,并且可以与其他部分的技术无关
可以各自使用完全不同的前端框架,可以在 React 中开发一部分,在 Angular 中开发一部分,然后使用原生 JavaScript 开发其他部分或任何其他技术。
只要每个 iframe 来自同一个来源,消息传递也就相当直接和强大。参考文档 Window.postMessageAPI
缺点:
Bundle 的大小非常明显,因为可能最终会多次发送相同的库,并且由于应用程序是分开的,所以在构建时也不能提取公共依赖关系。
至于浏览器的支持,基本上不可能嵌套两层以上的 iframe(
parent - > iframe - > iframe
)。如果任何嵌套的框架需要能够滚动或具有 Form 表单域,那样的情况处理起来就会变得特别痛苦。
Option 3: 客户端 JavaScript 异步加载
function loadPage (element) {
[].forEach.call(element.querySelectorAll('script'), function (nonExecutableScript) {
var script = document.createElement("script");
script.setAttribute("src", nonExecutableScript.src);
script.setAttribute("type", "text/javascript");
element.appendChild(script);
});
}
document.querySelectorAll('.load-app').forEach(loadPage);
<div class="load-app" data-url="header"></div>
<div class="load-app" data-url="products-list"></div>
<div class="load-app" data-url="cart"></div>
简单来说,这种方式就是在客户端浏览器通过 Ajax 加载应用程序,然后将不同模块的内容插入到对应的 div
中,而且还必须手动克隆每个 script 的标记才能使其工作。
需要注意的是,为了避免 Javascript 和 CSS 加载顺序的问题,建议将其修改成类似于 Facebook bigpipe 的解决方案,返回一个 JSON 对象 { html: ..., css: [...], js: [...] }
再进行加载顺序的控制。
Option 4: WebComponents 整合所有功能模块
Web Components 是一个 Web 标准,所以像 Angular、React/Preact、Vue 或 Hyperapp 这样的主流 JavaScript 框架都支持它们。你可以将 Web Components 视为使用开放 Web 技术创建的可重用的用户界面小部件,也许会是 Web 组件化的未来。
Web Components 由以下四种技术组成(尽管每种技术都可以独立使用):
自定义元素(Custom Elements)对外提供组件的标签,实现自定义标签:可以创建自己的自定义 HTML 标签和元素。每个元素可以有自己的脚本和 CSS 样式。还包括生命周期回调,它们允许我们定义正在加载的组件特定行为。
HTML 模板(HTML )定义组件的 HTML 模板能力:一种用于保存客户端内容的机制,该内容在页面加载时不被渲染,但可以在运行时使用 JavaScript 进行实例化。可以将一个模板视为正在被存储以供随后在文档中使用的一个内容片段。
影子 DOM(Shadow DOM)封装组件的内部结构,并且保持其独立性:允许我们在 Web 组件中封装 JavaScript,CSS 和 HTML。在组件内部时,这些东西与主文档的 DOM 分离。
HTML 导入(HTML Imports)解决组件组合和依赖加载:在微前端的上下文中,可以是包含我们要使用的组件在服务器上的远程位置。
# src/index.js
class Header extends HTMLElement {
attachedCallback() {
ReactDOM.render(<App />, this.createShadowRoot());
}
}
document.registerElement('microfrontends-header', Header);
<body>
<microfrontends-header></microfrontends-header>
<microfrontends-products-list></microfrontends-products-list>
<microfrontends-cart></microfrontends-cart>
</body>
在微前端的实践当中:
每个团队使用各自的技术栈创建他们的组件,并把它包装到自定义元素(Custom Element)中(如
<microfrontends-header></microfrontends-header>
)。Web 组件就是应用程序中包含的组件的本地实现,如菜单,表单,日期选择器等。每个组件都是独立开发的,主应用程序项目利用它们组装成最终的应用程序。
特定元素(标签名称,属性和事件)的 DOM 规范还可以充当跨团队之间的契约或公共 API。
创建可被导入到 Web 应用程序中的可重用组件,它们就像可以导入任何网页的用户界面小部件。
<link rel="import" href="/components/microfrontends/header.html">
<link rel="import" href="/components/microfrontends/products-list.html">
<link rel="import" href="/components/microfrontends/cart.html">
优点
代码的可读性变得非常清晰,组件资源内部高内聚,组件资源由自身加载控制,作用域独立。
功能团队可以使用组件及其功能,而不必知道实现,他们只需要能够与 HTML DOM 进行交互。
使用 PubSub 机制,组件可以发布消息,其他组件可以订阅特定的主题。幸运的是浏览器内置了这个功能。比如购物车可以在
window
订阅此事件并在应该刷新其数据时得到通知。缺点
可惜的是,Web 组件规范跟服务器渲染无关。没有 JavaScript,就没有所谓的自定义元素。
浏览器和框架的支持不够,需要更多的 polyfills 从而影响到用户页面的加载体验。
我们需要在整个 Web 应用程序上做出改变,把它们全部转换成 Web Components。
社区不够活跃,Web Components 还没有真正流行起来,也许永远也不会。
不同 App 模块之间如何交互?
# angularComponent.ts
const event = new CustomEvent('addToCart', { detail: item });
window.dispatchEvent(event);
# reactComponent.js
componentDidMount() {
window.addEventListener('addToCart', (event) => {
this.setState({ products: [...this.state.products, event.detail] });
}, false);
}
得益于浏览器的原生 API,Custom Event 可以与其他任何技术和框架一起工作。比如,我们可以将消息从 Angular 组件发送到 React 组件。其实这也是现在 API 之间普遍使用 JSON 进行通信的原因,即使没有人使用 NodeJS 作为服务器端。
但是,新的问题又出现了。我们该如何测试这种跨模块之间的交互?需要编写类似于后端微服务之间的 Contract Testing 或 Integration Testing 吗?并没有答案。
More Options...
组件库 - 根据主 App 的技术栈,不同的组件和 App 模块拆分作为库的形式提供给主App,所以主 App 是由不同组件组成的。但是组件库的升级将成为一个大麻烦,比如对 Header 组件进行了更改,那么如果已经有 50 个页面使用了 Header 组件该怎么办?必须要求每一页都升级它的 Header,而且升级过程中用户还会在整个网站不同页面上看到不一致的标题。并且,在两边还必须都使用相同的技术,比如 Header 组件中使用了 ClojureScript,而 Content 组件中又用了 Elm,那么该怎么办?构建工具就必须在编译时处理不同的语言。
将 App 模块作为 React 黑盒组件分发给消费者模块 - 应用程序的状态完全包含在组件中,API 只是通过 props 暴露出来。这种方式其实增加了应用程序之间的耦合,因为它迫使每个人都使用 React,甚至会使用相同版本的 React,但是这似乎也是一个比较好的折衷。
Edge Side Includes(ESI)/Server Side Includes(SSI) - 通过特殊的文件后缀 (shtml,inc) 或简单的标记语言来对那些可以加速和不能加速的网页中的内容片断进行描述,将每个网页划分成不同的小部分分别赋予不同的缓存控制策略。SSI / ESI 方法的缺点是,最慢的片段决定了整个页面的响应时间。
微前端的页面优化与实例
多模块页面加载问题与优化建议
使用 skeleton screen 响应式布局:如上图 LinkedIn 所做的那样,首先展现给用户一个页面的空白版本,然后在这个页面中逐渐加载和填充相应的信息。否则中间的信息流部分的内容最初是空白的,然后在 JavaScript 被加载和执行过后,信息流就会因为需要占用更多的空间而推动整个页面的布局。虽然我们可以控制页面来固定中间部分的高度,但在响应式网站上,确定一个确切的高度往往很难,而且不同的屏幕尺寸可能会有所不同。但更重要的问题是,这种高度尺寸的约定会让不同团队之间产生紧密的联系,从而违背了微前端的初衷。
使用浏览器异步加载加快初始渲染:对于加载成本高且难以缓存的碎片,将其从初始渲染中排除是一个好主意。比如说 LinkedIn 首页的信息流就是一个很好的例子。
共享 UI 组件库保证视觉体验一致:在前端设计中,必须向用户呈现外观和感觉一致的用户界面。建议可以建立一个共享组件库(包含 CSS、字体和 JavaScript)。将这些资源托管在 CDN,每个微前端就可以在其 HTML 输出中引用它们的位置。每个组件库的版本都正确地对资源进行版本控制,而每个微前端都指定要使用的组件库的版本和显式更新依赖关系。
-
使用集中式服务(Router)来管理 URL:可以理解为前端的 Gateway,不同的 URL 对应不同应用程序所包含的内容。建议通过一个集中式的 URLs Router 来为应用程序提供一个 API 来注册他们自己的 URL,Router 将会位于 Web 应用程序的前面,根据不同的用户请求指向不同的 App 模块组合。
提取共同依赖作为 externals 加载:虽然说不同 App 模块之间不能直接共享相同的第三方模块,当我们依然可以将常用的依赖比如
lodash
、moment.js
等公共库,或者跨多个团队共同使用的react
和react-dom
。通过 Webpack 等构建工具就可以把打包的时候将这些共同模块排除掉,而只需要在 HTML<header>
中的<script>
中直接通过 CDN 加载 externals 依赖。
<script
src="https://cdnjs.cloudflare.com/ajax/libs/react/16.2.0/react.min.js"
crossorigin="anonymous"></script>
<script
src="https://cdnjs.cloudflare.com/ajax/libs/react/16.2.0/react-dom.min.js"
crossorigin="anonymous"></script>
微前端在 AEM(CMS)项目的应用
我们在「三靠谱」(<del>已和谐客户名称</del>)的 Marketplace 项目当中也曾经探索过 AEM + React 混合开发的解决方案,其中就涉及到如何在 AEM 当中嵌入 React 组件,甚至将 AEM 组件又强行转化为 React 组件进行嵌套。现在回过头来其实也算是微前端的一种实践:
AEM 仅仅包含网页内容,不包含 domain 相关的结构化数据。
React 组件被托管在 AEM 组件当中,再经由 AEM 传递给组件所需要的属性,比如 IDs 或 APIs 的 URL 等等
后端微服务则包含 domain 结构化数据,由对应的 React 组件通过 Ajax 进行数据查询。
<div id="cms-container-1">
<div id="react-input-container"></div>
<script>
ReactDOM.render(React.createElement(Input, { ...injectProps }), document.getElementById('react-input-container'));
</script>
</div>
<div id="cms-container-2">
<div id="react-button-container"></div>
<script>
ReactDOM.render(React.createElement(Button, {}), document.getElementById('react-button-container'));
</script>
</div>
现成解决方案:Single-SPA “meta framework”
开源的 single-spa 自称为「元框架」,可以实现在一个页面将多个不同的框架整合,甚至在切换的时候都不需要刷新页面(支持 React、Vue、Angular 1、Angular 2、Ember 等等):
Build micro frontends that coexist and can each be written with their own framework.
Use multiple frameworks on the same page without refreshing the page (React, AngularJS, Angular, Ember, or whatever you're using)
Write code using a new framework, without rewriting your existing app
Lazy load code for improved initial load time.
Hot reload entire chunks of your overall application (instead of individual files).
请看示例代码,所提供的 API 非常简单:
import * as singleSpa from 'single-spa';
const appName = 'app1';
const loadingFunction = () => import('./app1/app1.js');
const activityFunction = location => location.hash.startsWith('#/app1');
singleSpa.declareChildApplication(appName, loadingFunction, activityFunction);
singleSpa.start();
# single-spa-examples.js
declareChildApplication('navbar', () => import('./navbar/navbar.app.js'), () => true);
declareChildApplication('home', () => import('./home/home.app.js'), () => location.hash === "" || location.hash === "#");
declareChildApplication('angular1', () => import('./angular1/angular1.app.js'), hashPrefix('/angular1'));
declareChildApplication('react', () => import('./react/react.app.js'), hashPrefix('/react'));
declareChildApplication('angular2', () => import('./angular2/angular2.app.js'), hashPrefix('/angular2'));
declareChildApplication('vue', () => import('src/vue/vue.app.js'), hashPrefix('/vue'));
declareChildApplication('svelte', () => import('src/svelte/svelte.app.js'), hashPrefix('/svelte'));
declareChildApplication('preact', () => import('src/preact/preact.app.js'), hashPrefix('/preact'));
declareChildApplication('iframe-vanilla-js', () => import('src/vanillajs/vanilla.app.js'), hashPrefix('/vanilla'));
declareChildApplication('inferno', () => import('src/inferno/inferno.app.js'), hashPrefix('/inferno'));
declareChildApplication('ember', () => loadEmberApp("ember-app", '/build/ember-app/assets/ember-app.js', '/build/ember-app/assets/vendor.js'), hashPrefix('/ember'));
start();
值得一提的是,single-spa 已经进入到最新一期技术雷达的评估阶段。这意味着 single-spa 会是值得研究一番的技术,以确认它将对你产生何种影响,你应该投入一些精力来确定它是否会对你所在的组织产生影响。
摘自技术雷达:
SINGLE-SPA是一个 JavaScript 元框架,它允许我们使用不同的框架构建微前端,而这些框架可以共存于单个应用中。一般来说,我们不建议在单个应用中使用多个框架,但有时却不得不这么做。例如当你在开发遗留系统时,你希望使用现有框架的新版本或完全不同的框架来开发新功能,single-spa 就能派上用场了。鉴于很多 JavaScript框架 都昙花一现,我们需要一个解决方案来应对未来框架的变化,以及在不影响整个应用的前提下进行局部尝试。在这个方向上,single-spa 是一个不错的开始。
总结与思考:微前端的优缺点
微前端的优点
敏捷性 - 独立开发和更快的部署周期:
开发团队可以选择自己的技术并及时更新技术栈。
一旦完成其中一项就可以部署,而不必等待所有事情完毕。
降低错误和回归问题的风险,相互之间的依赖性急剧下降。
更简单快捷的测试,每一个小的变化不必再触碰整个应用程序。
更快交付客户价值,有助于持续集成、持续部署以及持续交付。
维护和 bugfix 非常简单,每个团队都熟悉所维护特定的区域。
微前端的缺点
开发与部署环境分离
本地需要一个更为复杂的开发环境。
每个 App 模块有一个孤立的部署周期。
最终应用程序需要在同一个孤立的环境中运行。
复杂的集成
需要考虑隔离 JS,避免 CSS 冲突,并考虑按需加载资源
处理数据获取并考虑用户的初始化加载状态
如何有效测试,微前端模块之间的 Contract Testing?
第三方模块重叠
依赖冗余增加了管理的复杂性
在团队之间共享公共资源的机制
影响最终用户的体验
初始 Loading 时间可能会增加
HTML 会需要服务器端的渲染
持续思考…
变幻莫测)前端的技术选型?
前端 JavaScript 框架工具穷出不穷,过几个月就要重写前端项目?比如最近又出来了声称要取代 Webpack(Parcel)和 Yarn(Turbo)的工具。伴随着前端框架的更新换代,如果整个项目一起升级/重构的话压力大、风险高,那不如拆分微前端直接支持多 framework,或者同一 framework 的不同版本?`</pre>
在 Mobile/Mobile Web 上的悖论
受限于 Mobile 尺寸大小,单一页面所能展现的内容本就有限。
既然已经分出了不同的子页面,那何不如直接 Route 即可?
合理划分的边界:DDD(领域驱动开发)
最大的挑战是搞清楚如何合理拆分应用程序。
糟糕的设计可能成为开发和维护的噩梦。
Don't use any of this if you don't need it
Do not use the ideas described here until it is needed, it will make things more complex.
If you are in a big company, those ideas could help you.
软件架构到底在解决什么问题?—— 跨团队沟通的问题
在正常情况下,每个团队拥有开发和维护其特性所需的一切,都应该有自己的能力来完成自己的特性,并最大限度地减少团队要求其他部门获得许可和/或帮助。
当引入 library 或 framework 时的好处是只需要少数人讨论,而不用涉及超过 100 人的决策和他们的各种需求。这样一场大讨论不仅会耗费时间和精力,而且会迫使我们采用最不起眼的方法来选择 library,而不是选择专门针对每个 team 的问题领域的方案。
所谓架构,其实是解决人的问题;所谓敏捷,其实是解决沟通的问题;
附:参考资料
本次技术雷达「微前端」主题的宣讲 Slides 可以在我的博客找到:[「技术雷达」之 Micro Frontends:微前端 - 将微服务理念扩展到前端开发 - 吕立青的博客]
在做 Slides 之前所整理的 XMind 思维导图:https://www.xmind.net/m/e3dv
本人所整理的所有关于「微前端」的资料:https://www.diigo.com/user/jimmylv?query=%23microfrontends
https://micro-frontends.org/
https://medium.com/@tomsoderlund/micro-frontends-a-microservice-approach-to-front-end-web-development-f325ebdadc16
https://medium.com/@rchaves/building-microfrontends-part-i-creating-small-apps-710d709b48b7
http://www.agilechamps.com/microservices-to-micro-frontends/
http://allegro.tech/2016/03/Managing-Frontend-in-the-microservices-architecture.html
https://technologyconversations.com/2015/08/09/developing-front-end-microservices-with-polymer-web-components-and-test-driven-development-part-55-using-microservices/
https://technologyconversations.com/2015/08/09/including-front-end-web-components-into-microservices/
推荐阅读
-
SSM三大框架基础面试题-一、Spring篇 什么是Spring框架? Spring是一种轻量级框架,提高开发人员的开发效率以及系统的可维护性。 我们一般说的Spring框架就是Spring Framework,它是很多模块的集合,使用这些模块可以很方便地协助我们进行开发。这些模块是核心容器、数据访问/集成、Web、AOP(面向切面编程)、工具、消息和测试模块。比如Core Container中的Core组件是Spring所有组件的核心,Beans组件和Context组件是实现IOC和DI的基础,AOP组件用来实现面向切面编程。 Spring的6个特征: 核心技术:依赖注入(DI),AOP,事件(Events),资源,i18n,验证,数据绑定,类型转换,SpEL。 测试:模拟对象,TestContext框架,Spring MVC测试,WebTestClient。 数据访问:事务,DAO支持,JDBC,ORM,编组XML。 Web支持:Spring MVC和Spring WebFlux Web框架。 集成:远程处理,JMS,JCA,JMX,电子邮件,任务,调度,缓存。 语言:Kotlin,Groovy,动态语言。 列举一些重要的Spring模块? Spring Core:核心,可以说Spring其他所有的功能都依赖于该类库。主要提供IOC和DI功能。 Spring Aspects:该模块为与AspectJ的集成提供支持。 Spring AOP:提供面向切面的编程实现。 Spring JDBC:Java数据库连接。 Spring JMS:Java消息服务。 Spring ORM:用于支持Hibernate等ORM工具。 Spring Web:为创建Web应用程序提供支持。 Spring Test:提供了对JUnit和TestNG测试的支持。 谈谈自己对于Spring IOC和AOP的理解 IOC(Inversion Of Controll,控制反转)是一种设计思想: 在程序中手动创建对象的控制权,交由给Spring框架来管理。IOC在其他语言中也有应用,并非Spring特有。IOC容器实际上就是一个Map(key, value),Map中存放的是各种对象。 将对象之间的相互依赖关系交给IOC容器来管理,并由IOC容器完成对象的注入。这样可以很大程度上简化应用的开发,把应用从复杂的依赖关系中解放出来。IOC容器就像是一个工厂一样,当我们需要创建一个对象的时候,只需要配置好配置文件/注解即可,完全不用考虑对象是如何被创建出来的。在实际项目中一个Service类可能由几百甚至上千个类作为它的底层,假如我们需要实例化这个Service,可能要每次都搞清楚这个Service所有底层类的构造函数,这可能会把人逼疯。如果利用IOC的话,你只需要配置好,然后在需要的地方引用就行了,大大增加了项目的可维护性且降低了开发难度。 Spring中的bean的作用域有哪些? 1.singleton:该bean实例为单例 2.prototype:每次请求都会创建一个新的bean实例(多例)。 3.request:每一次HTTP请求都会产生一个新的bean,该bean仅在当前HTTP request内有效。 4.session:每一次HTTP请求都会产生一个新的bean,该bean仅在当前HTTP session内有效。 5.global-session:全局session作用域,仅仅在基于Portlet的Web应用中才有意义,Spring5中已经没有了。Portlet是能够生成语义代码(例如HTML)片段的小型Java Web插件。它们基于Portlet容器,可以像Servlet一样处理HTTP请求。但是与Servlet不同,每个Portlet都有不同的会话。 Spring中的单例bean的线程安全问题了解吗? 概念用于理解:大部分时候我们并没有在系统中使用多线程,所以很少有人会关注这个问题。单例bean存在线程问题,主要是因为当多个线程操作同一个对象的时候,对这个对象的非静态成员变量的写操作会存在线程安全问题。 有两种常见的解决方案(用于回答的点): 1.在bean对象中尽量避免定义可变的成员变量(不太现实)。 2.在类中定义一个ThreadLocal成员变量,将需要的可变成员变量保存在ThreadLocal(线程本地化对象)中(推荐的一种方式)。 ThreadLocal解决多线程变量共享问题(参考博客):https://segmentfault.com/a/1190000009236777 Spring中Bean的生命周期: 1.Bean容器找到配置文件中Spring Bean的定义。 2.Bean容器利用Java Reflection API创建一个Bean的实例。 3.如果涉及到一些属性值,利用set方法设置一些属性值。 4.如果Bean实现了BeanNameAware接口,调用setBeanName方法,传入Bean的名字。 5.如果Bean实现了BeanClassLoaderAware接口,调用setBeanClassLoader方法,传入ClassLoader对象的实例。 6.如果Bean实现了BeanFactoryAware接口,调用setBeanClassFacotory方法,传入ClassLoader对象的实例。 7.与上面的类似,如果实现了其他*Aware接口,就调用相应的方法。 8.如果有和加载这个Bean的Spring容器相关的BeanPostProcessor对象,执postProcessBeforeInitialization方法。 9.如果Bean实现了InitializingBean接口,执行afeterPropertiesSet方法。 10.如果Bean在配置文件中的定义包含init-method属性,执行指定的方法。 11.如果有和加载这个Bean的Spring容器相关的BeanPostProcess对象,执行postProcessAfterInitialization方法。 12.当要销毁Bean的时候,如果Bean实现了DisposableBean接口,执行destroy方法。 13.当要销毁Bean的时候,如果Bean在配置文件中的定义包含destroy-method属性,执行指定的方法。 Spring框架中用到了哪些设计模式? 1.工厂设计模式:Spring使用工厂模式通过BeanFactory和ApplicationContext创建bean对象。 2.代理设计模式:Spring AOP功能的实现。 3.单例设计模式:Spring中的bean默认都是单例的。 4.模板方法模式:Spring中的jdbcTemplate、hibernateTemplate等以Template结尾的对数据库操作的类,它们就使用到了模板模式。 5.包装器设计模式:我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源。 6.观察者模式:Spring事件驱动模型就是观察者模式很经典的一个应用。 7.适配器模式:Spring AOP的增强或通知(Advice)使用到了适配器模式、Spring MVC中也是用到了适配器模式适配Controller。 还有很多。。。。。。。 @Component和@Bean的区别是什么 1.作用对象不同。@Component注解作用于类,而@Bean注解作用于方法。 2.@Component注解通常是通过类路径扫描来自动侦测以及自动装配到Spring容器中(我们可以使用@ComponentScan注解定义要扫描的路径)。@Bean注解通常是在标有该注解的方法中定义产生这个bean,告诉Spring这是某个类的实例,当我需要用它的时候还给我。 3.@Bean注解比@Component注解的自定义性更强,而且很多地方只能通过@Bean注解来注册bean。比如当引用第三方库的类需要装配到Spring容器的时候,就只能通过@Bean注解来实现。 @Configuration public class AppConfig { @Bean public TransferService transferService { return new TransferServiceImpl; } } <beans> <bean id="transferService" class="com.kk.TransferServiceImpl"/> </beans> @Bean public OneService getService(status) { case (status) { when 1: return new serviceImpl1; when 2: return new serviceImpl2; when 3: return new serviceImpl3; } } 将一个类声明为Spring的bean的注解有哪些? 声明bean的注解: @Component 组件,没有明确的角色 @Service 在业务逻辑层使用(service层) @Repository 在数据访问层使用(dao层) @Controller 在展现层使用,控制器的声明 注入bean的注解: @Autowired:由Spring提供 @Inject:由JSR-330提供 @Resource:由JSR-250提供 *扩:JSR 是 java 规范标准 Spring事务管理的方式有几种? 1.编程式事务:在代码中硬编码(不推荐使用)。 2.声明式事务:在配置文件中配置(推荐使用),分为基于XML的声明式事务和基于注解的声明式事务。 Spring事务中的隔离级别有哪几种? 在TransactionDefinition接口中定义了五个表示隔离级别的常量:ISOLATION_DEFAULT:使用后端数据库默认的隔离级别,Mysql默认采用的REPEATABLE_READ隔离级别;Oracle默认采用的READ_COMMITTED隔离级别。ISOLATION_READ_UNCOMMITTED:最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。ISOLATION_READ_COMMITTED:允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生ISOLATION_REPEATABLE_READ:对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。ISOLATION_SERIALIZABLE:最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。 Spring事务中有哪几种事务传播行为? 在TransactionDefinition接口中定义了八个表示事务传播行为的常量。 支持当前事务的情况:PROPAGATION_REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。PROPAGATION_SUPPORTS: 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。PROPAGATION_MANDATORY: 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。(mandatory:强制性)。 不支持当前事务的情况:PROPAGATION_REQUIRES_NEW: 创建一个新的事务,如果当前存在事务,则把当前事务挂起。PROPAGATION_NOT_SUPPORTED: 以非事务方式运行,如果当前存在事务,则把当前事务挂起。PROPAGATION_NEVER: 以非事务方式运行,如果当前存在事务,则抛出异常。 其他情况:PROPAGATION_NESTED: 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于PROPAGATION_REQUIRED。 二、SpringMVC篇 什么是Spring MVC ?简单介绍下你对springMVC的理解? Spring MVC是一个基于Java的实现了MVC设计模式的请求驱动类型的轻量级Web框架,通过把Model,View,Controller分离,将web层进行职责解耦,把复杂的web应用分成逻辑清晰的几部分,简化开发,减少出错,方便组内开发人员之间的配合。 Spring MVC的工作原理了解嘛? image.png Springmvc的优点: (1)可以支持各种视图技术,而不仅仅局限于JSP; (2)与Spring框架集成(如IoC容器、AOP等); (3)清晰的角色分配:前端控制器(dispatcherServlet) , 请求到处理器映射(handlerMapping), 处理器适配器(HandlerAdapter), 视图解析器(ViewResolver)。 (4) 支持各种请求资源的映射策略。 Spring MVC的主要组件? (1)前端控制器 DispatcherServlet(不需要程序员开发) 作用:接收请求、响应结果,相当于转发器,有了DispatcherServlet 就减少了其它组件之间的耦合度。 (2)处理器映射器HandlerMapping(不需要程序员开发) 作用:根据请求的URL来查找Handler (3)处理器适配器HandlerAdapter 注意:在编写Handler的时候要按照HandlerAdapter要求的规则去编写,这样适配器HandlerAdapter才可以正确的去执行Handler。 (4)处理器Handler(需要程序员开发) (5)视图解析器 ViewResolver(不需要程序员开发) 作用:进行视图的解析,根据视图逻辑名解析成真正的视图(view) (6)视图View(需要程序员开发jsp) View是一个接口, 它的实现类支持不同的视图类型(jsp,freemarker,pdf等等) springMVC和struts2的区别有哪些? (1)springmvc的入口是一个servlet即前端控制器(DispatchServlet),而struts2入口是一个filter过虑器(StrutsPrepareAndExecuteFilter)。 (2)springmvc是基于方法开发(一个url对应一个方法),请求参数传递到方法的形参,可以设计为单例或多例(建议单例),struts2是基于类开发,传递参数是通过类的属性,只能设计为多例。 (3)Struts采用值栈存储请求和响应的数据,通过OGNL存取数据,springmvc通过参数解析器是将request请求内容解析,并给方法形参赋值,将数据和视图封装成ModelAndView对象,最后又将ModelAndView中的模型数据通过reques域传输到页面。Jsp视图解析器默认使用jstl。 SpringMVC怎么样设定重定向和转发的? (1)转发:在返回值前面加"forward:",譬如"forward:user.do?name=method4" (2)重定向:在返回值前面加"redirect:",譬如"redirect:http://www.baidu.com" SpringMvc怎么和AJAX相互调用的? 通过Jackson框架就可以把Java里面的对象直接转化成Js可以识别的Json对象。具体步骤如下 : (1)加入Jackson.jar (2)在配置文件中配置json的映射 (3)在接受Ajax方法里面可以直接返回Object,List等,但方法前面要加上@ResponseBody注解。 如何解决POST请求中文乱码问题,GET的又如何处理呢? (1)解决post请求乱码问题: 在web.xml中配置一个CharacterEncodingFilter过滤器,设置成utf-8; <filter> <filter-name>CharacterEncodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>utf-8</param-value> </init-param> </filter> <filter-mapping> <filter-name>CharacterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> (2)get请求中文参数出现乱码解决方法有两个: ①修改tomcat配置文件添加编码与工程编码一致,如下: <ConnectorURIEncoding="utf-8" connectionTimeout="20000" port="8080" protocol="HTTP/1.1" redirectPort="8443"/> ②另外一种方法对参数进行重新编码: String userName = new String(request.getParamter("userName").getBytes("ISO8859-1"),"utf-8") ISO8859-1是tomcat默认编码,需要将tomcat编码后的内容按utf-8编码。 Spring MVC的异常处理 ? 统一异常处理: Spring MVC处理异常有3种方式: (1)使用Spring MVC提供的简单异常处理器SimpleMappingExceptionResolver; (2)实现Spring的异常处理接口HandlerExceptionResolver 自定义自己的异常处理器; (3)使用@ExceptionHandler注解实现异常处理; 统一异常处理的博客:https://blog.csdn.net/ctwy291314/article/details/81983103 SpringMVC的控制器是不是单例模式,如果是,有什么问题,怎么解决? 是单例模式,所以在多线程访问的时候有线程安全问题,不要用同步,会影响性能的,解决方案是在控制器里面不能写成员变量。(此题目类似于上面Spring 中 第5题 有两种解决方案) SpringMVC常用的注解有哪些? @RequestMapping:用于处理请求 url 映射的注解,可用于类或方法上。用于类上,则表示类中的所有响应请求的方法都是以该地址作为父路径。 @RequestBody:注解实现接收http请求的json数据,将json转换为java对象。 @ResponseBody:注解实现将conreoller方法返回对象转化为json对象响应给客户。 SpingMvc中的控制器的注解一般用那个,有没有别的注解可以替代? 一般用@Controller注解,也可以使用@RestController,@RestController注解相当于@ResponseBody + @Controller,表示是表现层,除此之外,一般不用别的注解代替。 如果在拦截请求中,我想拦截get方式提交的方法,怎么配置? 可以在@RequestMapping注解里面加上method=RequestMethod.GET。 怎样在方法里面得到Request,或者Session? 直接在方法的形参中声明request,SpringMVC就自动把request对象传入。 如果想在拦截的方法里面得到从前台传入的参数,怎么得到? 直接在形参里面声明这个参数就可以,但必须名字和传过来的参数一样。 如果前台有很多个参数传入,并且这些参数都是一个对象的,那么怎么样快速得到这个对象? 直接在方法中声明这个对象,SpringMVC就自动会把属性赋值到这个对象里面。 SpringMVC中函数的返回值是什么? 返回值可以有很多类型,有String, ModelAndView。ModelAndView类把视图和数据都合并的一起的。 SpringMVC用什么对象从后台向前台传递数据的? 通过ModelMap对象,可以在这个对象里面调用put方法,把对象加到里面,前台就可以拿到数据。 怎么样把ModelMap里面的数据放入Session里面? 可以在类上面加上@SessionAttributes注解,里面包含的字符串就是要放入session里面的key。 SpringMvc里面拦截器是怎么写的: 有两种写法,一种是实现HandlerInterceptor接口,另外一种是继承适配器类,接着在接口方法当中,实现处理逻辑;然后在SpringMvc的配置文件中配置拦截器即可: <!-- 配置SpringMvc的拦截器 --> <mvc:interceptors> <!-- 配置一个拦截器的Bean就可以了 默认是对所有请求都拦截 --> <bean id="myInterceptor" class="com.zwp.action.MyHandlerInterceptor"></bean> <!-- 只针对部分请求拦截 --> <mvc:interceptor> <mvc:mapping path="/modelMap.do" /> <bean class="com.zwp.action.MyHandlerInterceptorAdapter" /> </mvc:interceptor> </mvc:interceptors> 注解原理: 注解本质是一个继承了Annotation的特殊接口,其具体实现类是Java运行时生成的动态代理类。我们通过反射获取注解时,返回的是Java运行时生成的动态代理对象。通过代理对象调用自定义注解的方法,会最终调用AnnotationInvocationHandler的invoke方法。该方法会从memberValues这个Map中索引出对应的值。而memberValues的来源是Java常量池 三、Mybatis篇 什么是MyBatis? MyBatis是一个可以自定义SQL、存储过程和高级映射的持久层框架。 讲下MyBatis的缓存 MyBatis的缓存分为一级缓存和二级缓存,一级缓存放在session里面,默认就有, 二级缓存放在它的命名空间里,默认是不打开的,使用二级缓存属性类需要实现Serializable序列化接口, 可在它的映射文件中配置<cache/> Mybatis是如何进行分页的?分页插件的原理是什么? 1)Mybatis使用RowBounds对象进行分页,也可以直接编写sql实现分页,也可以使用Mybatis的分页插件。 2)分页插件的原理:实现Mybatis提供的接口,实现自定义插件,在插件的拦截方法内拦截待执行的sql,然后重写sql。 举例:select * from student,拦截sql后重写为:select t.* from (select * from student)t limit 0,10 简述Mybatis的插件运行原理,以及如何编写一个插件? 1)Mybatis仅可以编写针对ParameterHandler、ResultSetHandler、StatementHandler、 Executor这4种接口的插件,Mybatis通过动态代理, 为需要拦截的接口生成代理对象以实现接口方法拦截功能, 每当执行这4种接口对象的方法时,就会进入拦截方法, 具体就是InvocationHandler的invoke方法,当然, 只会拦截那些你指定需要拦截的方法。 2)实现Mybatis的Interceptor接口并复写intercept方法, 然后在给插件编写注解,指定要拦截哪一个接口的哪些方法即可, 记住,别忘了在配置文件中配置你编写的插件。 Mybatis动态sql是做什么的?都有哪些动态sql?能简述一下动态sql的执行原理不? 1)Mybatis动态sql可以让我们在Xml映射文件内, 以标签的形式编写动态sql,完成逻辑判断和动态拼接sql的功能。 2)Mybatis提供了9种动态sql标签:trim|where|set|foreach|if|choose|when|otherwise|bind。 3)其执行原理为,使用OGNL从sql参数对象中计算表达式的值, 根据表达式的值动态拼接sql,以此来完成动态sql的功能。 #{}和${}的区别是什么? 1)#{}是预编译处理,${}是字符串替换。 2)Mybatis在处理#{}时,会将sql中的#{}替换为?号,调用PreparedStatement的set方法来赋值(有效的防止SQL注入); 3)Mybatis在处理${}时,就是把${}替换成变量的值。 为什么说Mybatis是半自动ORM映射工具?它与全自动的区别在哪里? Hibernate属于全自动ORM映射工具, 使用Hibernate查询关联对象或者关联集合对象时, 可以根据对象关系模型直接获取,所以它是全自动的。 而Mybatis在查询关联对象或关联集合对象时, 需要手动编写sql来完成,所以,称之为半自动ORM映射工具。 Mybatis是否支持延迟加载?如果支持,它的实现原理是什么? 1)Mybatis仅支持association关联对象和collection关联集合对象的延迟加载, association指的就是一对一,collection指的就是一对多查询。 在Mybatis配置文件中, 可以配置是否启用延迟加载lazyLoadingEnabled=true|false。 2)它的原理是,使用CGLIB创建目标对象的代理对象, 当调用目标方法时,进入拦截器方法, 比如调用a.getB.getName, 拦截器invoke方法发现a.getB是null值, 那么就会单独发送事先保存好的查询关联B对象的sql, 把B查询上来,然后调用a.setB(b), 于是a的对象b属性就有值了, 接着完成a.getB.getName方法的调用。 这就是延迟加载的基本原理。 MyBatis与Hibernate有哪些不同? 1)Mybatis和hibernate不同,它不完全是一个ORM框架, 因为MyBatis需要程序员自己编写Sql语句, 不过mybatis可以通过XML或注解方式灵活配置要运行的sql语句, 并将java对象和sql语句映射生成最终执行的sql, 最后将sql执行的结果再映射生成java对象。 2)Mybatis学习门槛低,简单易学,程序员直接编写原生态sql, 可严格控制sql执行性能,灵活度高,非常适合对关系数据模型要求不高的软件开发, 例如互联网软件、企业运营类软件等,因为这类软件需求变化频繁, 一但需求变化要求成果输出迅速。但是灵活的前提是mybatis无法做到数据库无关性, 如果需要实现支持多种数据库的软件则需要自定义多套sql映射文件,工作量大。 3)Hibernate对象/关系映射能力强,数据库无关性好, 对于关系模型要求高的软件(例如需求固定的定制化软件) 如果用hibernate开发可以节省很多代码,提高效率。 但是Hibernate的缺点是学习门槛高,要精通门槛更高, 而且怎么设计O/R映射,在性能和对象模型之间如何权衡, 以及怎样用好Hibernate需要具有很强的经验和能力才行。 总之,按照用户的需求在有限的资源环境下只要能做出维护性、 扩展性良好的软件架构都是好架构,所以框架只有适合才是最好。 MyBatis的好处是什么? 1)MyBatis把sql语句从Java源程序中独立出来,放在单独的XML文件中编写, 给程序的维护带来了很大便利。 2)MyBatis封装了底层JDBC API的调用细节,并能自动将结果集转换成Java Bean对象, 大大简化了Java数据库编程的重复工作。 3)因为MyBatis需要程序员自己去编写sql语句, 程序员可以结合数据库自身的特点灵活控制sql语句, 因此能够实现比Hibernate等全自动orm框架更高的查询效率,能够完成复杂查询。 简述Mybatis的Xml映射文件和Mybatis内部数据结构之间的映射关系? Mybatis将所有Xml配置信息都封装到All-In-One重量级对象Configuration内部。 在Xml映射文件中,<parameterMap>标签会被解析为ParameterMap对象, 其每个子元素会被解析为ParameterMapping对象。 <resultMap>标签会被解析为ResultMap对象, 其每个子元素会被解析为ResultMapping对象。 每一个<select>、<insert>、<update>、<delete> 标签均会被解析为MappedStatement对象, 标签内的sql会被解析为BoundSql对象。 什么是MyBatis的接口绑定,有什么好处? 接口映射就是在MyBatis中任意定义接口,然后把接口里面的方法和SQL语句绑定, 我们直接调用接口方法就可以,这样比起原来了SqlSession提供的方法我们可以有更加灵活的选择和设置. 接口绑定有几种实现方式,分别是怎么实现的? 接口绑定有两种实现方式,一种是通过注解绑定,就是在接口的方法上面加 上@Select@Update等注解里面包含Sql语句来绑定, 另外一种就是通过xml里面写SQL来绑定,在这种情况下, 要指定xml映射文件里面的namespace必须为接口的全路径名. 什么情况下用注解绑定,什么情况下用xml绑定? 当Sql语句比较简单时候,用注解绑定;当SQL语句比较复杂时候,用xml绑定,一般用xml绑定的比较多 MyBatis实现一对一有几种方式?具体怎么操作的? 有联合查询和嵌套查询,联合查询是几个表联合查询,只查询一次, 通过在resultMap里面配置association节点配置一对一的类就可以完成; 嵌套查询是先查一个表,根据这个表里面的结果的外键id, 去再另外一个表里面查询数据,也是通过association配置, 但另外一个表的查询通过select属性配置。 Mybatis能执行一对一、一对多的关联查询吗?都有哪些实现方式,以及它们之间的区别? 能,Mybatis不仅可以执行一对一、一对多的关联查询, 还可以执行多对一,多对多的关联查询,多对一查询, 其实就是一对一查询,只需要把selectOne修改为selectList即可; 多对多查询,其实就是一对多查询,只需要把selectOne修改为selectList即可。 关联对象查询,有两种实现方式,一种是单独发送一个sql去查询关联对象, 赋给主对象,然后返回主对象。另一种是使用嵌套查询,嵌套查询的含义为使用join查询, 一部分列是A对象的属性值,另外一部分列是关联对象B的属性值, 好处是只发一个sql查询,就可以把主对象和其关联对象查出来。 MyBatis里面的动态Sql是怎么设定的?用什么语法? MyBatis里面的动态Sql一般是通过if节点来实现,通过OGNL语法来实现, 但是如果要写的完整,必须配合where,trim节点,where节点是判断包含节点有 内容就插入where,否则不插入,trim节点是用来判断如果动态语句是以and 或or 开始,那么会自动把这个and或者or取掉。 Mybatis是如何将sql执行结果封装为目标对象并返回的?都有哪些映射形式? 第一种是使用<resultMap>标签,逐一定义列名和对象属性名之间的映射关系。 第二种是使用sql列的别名功能,将列别名书写为对象属性名, 比如T_NAME AS NAME,对象属性名一般是name,小写, 但是列名不区分大小写,Mybatis会忽略列名大小写,
-
Node.js:开发人员必须掌握的 10 个库(下) - Lodash 库的功能和优势: 使用 Polyfills 实现跨浏览器兼容。 在处理对象数组时,提供过滤器、搜索和 flatMap 等内置解决方案。 帮助开发人员避免冗余代码并保持代码清晰。 阿克西奥斯 Axios 是一款基于 Promise 的 HTTP 客户端,适用于 Node.js 和浏览器。它还能根据需要管理浏览器或 Node.js 请求和响应数据的转换。Axios 是同构的,这意味着它可以在服务器和客户端使用相同的代码库。Axios 在服务器端使用本地 HTTP 模块,在客户端使用 XMLHttpRequest 进行 HTTP 通信。 Axios 库的特点和优势: 为 GET、PUT、POST 和 DELETE 等常见 HTTP 数据类型提供 API 方法。 在互联网上执行 HTTP 查询时,可防止跨站请求伪造(CSRF),从而提高安全性。 自动 JSON 数据转换,可轻松将响应数据转换为 JSON 格式。 Puppeteer Puppeteer 是一个 Node.js 框架,它通过 DevTools 协议提供了一个高级 API,允许你通过控制 Chrome/Chromium 实现浏览器自动化。它可用于自动化前端测试,如处理请求、识别和比较用户界面组件以及性能测试。在代码中导入 Puppeteer 软件包后,就能构建 Chromium 实例,然后与浏览器引擎通信,执行自动化测试。 Puppeteer 库的优势和功能:
-
微前端"--将微服务扩展到前端开发(实践)
-
微前端实践
-
智联招聘发布第三季度平均薪酬报告;价值13亿美元的Metaverse日活跃用户仅38人;统一充电接口或让苹果一年损失数百亿美元 | EA周报 - 热点大事件 微信推出刷掌付小程序,开启全新支付模式 据悉,微信已上线 "微信刷掌付 "小程序,可以为用户刷掌付增加更便捷的管理方式,但刷掌付功能需要在刷掌设备上开通。刷掌付是继密码支付、指纹支付、刷脸支付之后,微信的又一新型支付方式。据悉,目前微信支付已在深圳部分商户接入刷掌付设备进行测试,用户可通过刷掌纹支付订单。刷掌纹设备由微信支付提供,设备上设有显示屏和掌纹识别区,用户开通微信刷掌纹支付功能后,只需在掌纹识别区扫描,即可完成商品支付,相比传统的密码支付和指纹支付,更加便捷。(星球科技) 微软多项云服务落户中国新数据中心 2022年10月13日,微软年度技术大会Ignite 2022和Ignite China中国技术峰会同步开启在线直播。面对中国市场日益增长的客户需求,微软宣布,Azure、Dynamics Power Platform等多项服务已在北上广三地数据中心落地,提升在中国市场的服务能力;世纪互联运营的Office 365上的Teams服务和世纪互联运营的Microsoft 365服务将于2023年上半年正式上线,为中国市场带来更全面、更优质的本地化服务体验和技术保障。 IBM宣布将红帽存储并入存储业务部 根据IBM与红帽的协议,IBM将成为Ceph基金会的主要赞助商,该基金会的成员合作推动Ceph开源项目的创新、开发、营销和社区活动。红帽OpenStack客户仍可从红帽及其合作伙伴处购买红帽Ceph存储,而拥有现有订购服务的红帽OpenShift和红帽OpenStack客户将能够在不改变与红帽关系的情况下,根据需要维护和扩展其存储足迹。 扎克伯格谈新款1万美元VR头显:成本价,我们不会像苹果那样定高价 元CEO扎克伯格在接受采访时谈到了公司新发布的Quest Pro新款VR头显的价格,他表示1499.99美元的定价只是 "性价比",让更多人通过购买硬件来体验元宇宙。扎克伯格还借此机会挖苦了竞争对手苹果公司,称苹果公司对该设备的定价 "已经到了极限"。他说:"通常,人们制造硬件,然后想从中获利。例如,苹果公司就是这样做的,制造硬件,然后尽可能多地收费。他说,公司还计划推出 Quest 3,售价在 300 美元到 500 美元之间。 智联招聘发布招聘薪资报告,第三季度全国平均薪资为10168美元/月
-
智联招聘微前端落地实践 - 小程序
-
个人记账系统-微信小程序前端开发
-
网易七鱼微前端实践
-
乾坤:vue3+vite从开发到部署实现微前端
-
Vue3 + Vite + 乾坤微前端实践