肥仔John

肥仔John 查看完整档案

佛山编辑华南师范大学  |  计算机科学与技术 编辑美的集团  |  高级前端工程师 编辑 Http://fsjohnhuang.cnblogs.com/ 编辑
编辑

偏前端的临栈工程师@Midea/QMS,熟悉JavaScript(ES2015/2016),CSS2-3,HTML,ClojureScript,Polymer,React,Electron,Java,DevOps

个人动态

肥仔John 发布了文章 · 1月16日

SpringBoot魔法堂:@MatrixVariable参数注解使用详解

前言

RFC3986定义URI的路径(Path)中可包含name-value片段,扩充了以往仅能通过查询字符串(Query String)设置可选参数的囧境。
假如现在需要设计一个用于“搜索某部门某些员工可选信息中的部分信息”的API,我们分别使用查询字符串和路径name-value方式来设计对比,看看具体效果:

  1. 查询字符串方式:/api/v1/users/optional-info?dept=321&name=joh*&fields=hometown,birth
    问题:其中的dept和name理应属于users路径,而fields则属于optional-info路径,但现在全部都要挤在查询字符串中。
  2. 路径name-value方式:/api/v1/users/depts=321;name=joh*/optional-fields/fields=hometown,birth

可以看出路径name-value的方式逻辑上更在理些。

@MatrixVariable注解属性说明

在正式开始前我们先死记硬背一下注解的属性吧。

  1. value 和属性pathVar的别名;
  2. pathVar 用于指定name-value参数所在的路径片段名称
  3. name 用于指定name-value参数的参数名
  4. required 是否为必填值,默认为false
  5. defaultValue 设置默认值

其中pathVarname到底是什么呢?请继续看后面的示例吧,准能秒懂!

启用@MatrixVariable

虽然从Spring 3.2就已经支持@MatrixVariable特性,但直至现在其依然为默认禁用的状态。我们需要手工配置开启才能使用。

@Configuration                                                                                                                                                                                                                                                      
public class SpringBootConfig implements WebMvcConfigurer {                                                                                                                                                                                                         
   @Override                                                                                                                                                                                                                                                       
   public void configurePathMatch(PathMatchConfigurer configurer) {                                                                                                                                                                                                
      UrlPathHelper urlPathHelper = new UrlPathHelper();                                                                                                                                                                                                          
      urlPathHelper.setRemoveSemicolonContent(false);                                                                                                                                                                                                             
      configurer.setUrlPathHelper(urlPathHelper);                                                                                                                                                                                                                 
   }                                                                                                                                                                                                                                                               
}    

参数仅有一个值的玩法

注意:多个name-value间以分号分隔,如name=joh*;dept=321

/*                                                                                                                                                                                                                                                                  
 1. 获取单个路径片段中的参数                                                                                                                                                                                                                                        
 请求URI为 /Demo2/66;color=red;year=2020                                                                                                                                                                                                                            
*/                                                                                                                                                                                                                                                                  
@RequestMapping(path="/Demo1/{id}", method=RequestMethod.GET)                                                                                                                                                                                                       
public String test1(@PathVariable String id, @MatrixVariable String color, @MatrixVariable String year){}                                                                                                                                                           
/*                                                                                                                                                                                                                                                                  
 2. 获取单个路径片段中的参数                                                                                                                                                                                                                                        
 请求URI为 /Demo2/color=red;year=2020                                                                                                                                                                                                                               
*/                                                                                                                                                                                                                                                                  
@RequestMapping(path="/Demo1/{id}", method=RequestMethod.GET)                                                                                                                                                                                                       
public String test2(@MatrixVariable String color, @MatrixVariable String year){}                                                                                                                                                                                    
                                                                                                                                                                                                                                                                    
                                                                                                                                                                                                                                                                    
/*                                                                                                                                                                                                                                                                  
 3. 获取不同路径片段中的参数                                                                                                                                                                                                                                        
 请求URI为 /Demo2/66;color=red;year=2020/pets/77;color=blue;year=2019                                                                                                                                                                                               
*/                                                                                                                                                                                                                                                                  
@RequestMapping(path="/Demo2/{id1}/pets/{id2}", method=RequestMethod.GET)                                                                                                                                                                                           
public String test3(@PathVariable String id1, @PathVariable String id2,                                                                                                                                                                                             
  @MatrixVariable(name="color", pathVar="id1") String color1, @MatrixVariable(name="year", pathVar="id1") String year1,                                                                                                                                             
  @MatrixVariable(name="color", pathVar="id2") String color2, @MatrixVariable(name="year", pathVar="id2") String year2){}                                                                                                                                           
/*                                                                                                                                                                                                                                                                  
 4. 获取不同路径片段中的参数                                                                                                                                                                                                                                        
 请求URI为 /Demo2/color=red;year=2020/pets/77;color=blue;year=2019                                                                                                                                                                                                  
*/                                                                                                                                                                                                                                                                  
@RequestMapping(path="/Demo2/{id1}/pets/{id2}", method=RequestMethod.GET)                                                                                                                                                                                           
public String test4(@PathVariable String id2,                                                                                                                                                                                                                       
  @MatrixVariable(name="color", pathVar="id1") String color1, @MatrixVariable(name="year", pathVar="id1") String year1,                                                                                                                                             
  @MatrixVariable(name="color", pathVar="id2") String color2, @MatrixVariable(name="year", pathVar="id2") String year2){}                                                                                                                                                                                                                                                                                                                                                                                                           
                                                                                                                                                                                                                                                                    
/*                                                                                                                                                                                                                                                                  
 5. 通过Map获取所有或指定路径下的所有参数                                                                                                                                                                                                                           
*/                                                                                                                                                                                                                                                                  
@RequestMapping(path="/Demo3/{id1}/pets/{id2}", method=RequestMethod.GET)                                                                                                                                                                                           
public String test5(@MatrixVariable Map<String, Object> all, @MatrixVariable(pathVar="id1") Map<String, Object> mapId1) {}    

参数有多个值的玩法

若参数值不是单个,那么可以通过两种方式传递:

  1. 值之间通过逗号分隔,如dept=321,123
  2. 重名name-value对,如dept=321;dept=123
/*                                                                                                                                                                                                                                                                  
 请求为/Demo1/color=123,321                                                                                                                                                                                                                                         
 那么color值为123,321                                                                                                                                                                                                                                               
*/                                                                                                                                                                                                                                                                  
@RequestMapping(path="/Demo1/{id}", method=RequestMethod.GET)                                                                                                                                                                                                       
public String test1(@MatrixVariable Integer[] color){}                                                                                                                                                                                                              
/*                                                                                                                                                                                                                                                                  
 请求为/Demo1/color=123;color=321                                                                                                                                                                                                                                   
 那么color值为123,321                                                                                                                                                                                                                                               
*/                                                                                                                                                                                                                                                                  
@RequestMapping(path="/Demo1/{id}", method=RequestMethod.GET)                                                                                                                                                                                                       
public String test1(@MatrixVariable Integer[] color){}                                                                                                                                                                                                              

那些要注意的坑

在参数多值的情况下还有如下3个坑,请各位多加注意:

  1. String参数类型可以接受通过逗号和通过重名name-value传递的所有值,而其它类型只能获取第一个值。
/*                                                                                                                                                                                                                                                                  
 请求为/Demo1/color=123,321                                                                                                                                                                                                                                         
 那么color值为123,321                                                                                                                                                                                                                                               
*/                                                                                                                                                                                                                                                                  
@RequestMapping(path="/Demo1/{id}", method=RequestMethod.GET)                                                                                                                                                                                                       
public String test1(@MatrixVariable String color){}                                                                                                                                                                                                                 
/*                                                                                                                                                                                                                                                                  
 请求为/Demo1/color=123;color=321                                                                                                                                                                                                                                   
 那么color值为123,321                                                                                                                                                                                                                                               
*/                                                                                                                                                                                                                                                                  
@RequestMapping(path="/Demo1/{id}", method=RequestMethod.GET)                                                                                                                                                                                                       
public String test1(@MatrixVariable String color){}   
/*                                                                                                                                                                                                                                                                  
 请求为/Demo1/color=123;color=321                                                                                                                                                                                                                                   
 那么color值为123                                                                                                                                                                                                                                                   
*/                                                                                                                                                                                                                                                                  
@RequestMapping(path="/Demo1/{id}", method=RequestMethod.GET)                                                                                                                                                                                                       
public String test1(@MatrixVariable Integer color){}   
  1. Map<String, Object[]>只能获取参数中的第一个值而已。
/*                                                                                                                                                                                                                                                                  
 请求为/Demo1/color=123,321                                                                                                                                                                                                                                         
 那么color值为123                                                                                                                                                                                                                                                   
*/                                                                                                                                                                                                                                                                  
@RequestMapping(path="/Demo1/{id}", method=RequestMethod.GET)                                                                                                                                                                                                       
public String test1(@MatrixVariable Map<String, Integer[]> color){}       
  1. 不同路径片段中出现名称相同的参数,那么必须通过pathVar标识所有相同参数所属路径,否则URI匹配失败。
// 以下handler仅标识第二个参数的pathVar,而没有标识第一个,那么也是会匹配失败的。                                                                                                                                                                                   
@RequestMapping(path="/Demo2/{id1}/pets/{id2}", method=RequestMethod.GET)                                                                                                                                                                                        
public String test2(@MatrixVariable String color, @MatrixVariable(name="color", pathVar="id2") String color2){}       

总结

今天就写到这里吧,后续会有更多Spring Boot的分享,请大家多关注我哦!
转载请注明来自: https://www.cnblogs.com/fsjoh... —— 肥仔John

查看原文

赞 4 收藏 3 评论 0

肥仔John 发布了文章 · 2020-12-16

前端魔法堂:可能是你见过最详细的WebWorker实用指南

前言

JavaScript从使用开初就一直基于事件循环的单线程运行模型,即使是成功进军后端开发的Nodejs也没有改变这一模型。那么对于计算密集型的应用,我们必须创建新进程来执行运算,然后执行进程间通信实现传参和获取运算结果。否则会造成UI界面卡顿,甚至导致浏览器无响应。
从功能实现来看,我们可以通过新增iframe加载同域页面来创建JSVM进程执行运算从而避免造成界面卡顿的问题。但存在如下问题:

  1. 这里涉及到HTML页面、JavaScript、iframe同源策略、iframe间消息通信的综合应用,其中实际的运算逻辑都以JavaScript描述,而HTML页面和iframe同源策略属于底层基础设施,而且这些基础设施没办法封装为一个类库对外提供服务,这就增大应用开发和运维的难度;
  2. 进程的创建和销毁成本绝对比线程的创建和销毁多得多。

幸运的是HTML5为JavaScript引入多线程运行模型,这也是本文将和大家一起探讨的———Web Worker。

困在笼子里的Web Worker

在使用Web Worker前我们要了解它的能力边界,让我们避免无谓的撞壁:

  1. 同源限制

1.1. 以http(s)://协议加载给WebWorker线程运行的脚本时,其URL必须和UI线程所属页面的URL同源;
1.2. 不能加载客户端本地脚本给WebWorker线程运行(即采用file://协议),即使UI线程所属页面也是本地页面;

  1. DOM和BOM限制

1.1. 无法访问UI线程所属页面的任何DOM元素;
1.2. 可访问如下BOM元素
1.2.1. XMLHttpRequest/fetch
1.2.2. setTimeout/clearTimeout
1.2.3. setInterval/clearInterval
1.2.4. location,注意该location指向的是WebWorker创建时以UI线程所属页面的当前Location为基础创建的WorkerLocation对象,即使此后页面被多次重定向,该location的信息依然保持不变。
1.2.5. navigator,注意该navigator指向的是WebWorker创建时以UI线程所属页面的当前Navigator为基础创建的WorkerNavigator对象,即使此后页面被多次重定向,该navigator的信息依然保持不变。

  1. 通信限制,UI线程和WebWorker线程间必须通过消息机制进行通信。

Dedicated Web Worker详解

Web Worker分为Dedicated Web Worker和Shared Web Worker两类,它们的特性如下:

  1. Dedicated Web Worker仅为创建它的JSVM进程服务,当其所属的JSVM进程结束该Dedicated Web Worker线程也将结束;
  2. Shared Web Worker为创建它的JSVM进程所属页面的域名服务,当该域名下的所有JSVM进程均结束时该Shared Web Worker线程才会结束。

基本使用

  1. UI线程
const worker = new Worker('work.js') // 若下载失败如404,则会默默地失败不会抛异常,即无法通过try/catch捕获。                                                                                                                      
const workerWithName = new Worker('work.js', {name: 'worker2'}) // 为Worker线程命名,那么在Worker线程内的代码可通过 self.name 获取该名称。                                                                                        
                                                                                                                                                                                                                                              
worker.postMessage('Send message to worker.') // 发送文本消息                                                                                                                                                                     
worker.postMessage({type: 'message', payload: ['hi']}) // 发送JavaScript对象,会先执行序列化为JSON文本消息再发送,然后在接收端自动反序列化为JavaScript对象。                                                                      
const uInt8Array = new Uint8Array(new ArrayBuffer(10))                                                                                                                                                                            
for (let i = 0; i < uint8array.length; ++i) {                                                                                                                                                                                     
  uInt8Array[i] = i * 2                                                                                                                                                                                                         
}                                                                                                                                                                                                                                 
worker.postMessage(uInt8Array) // 以先序列化后反序列化的方式发送二进制数据,发送后主线程仍然能访问uInt8Array变量的数据,但会造成性能问题。                                                                                        
worker.postMessage(uInt8Array, [uInt8Array]) // 以Transferable Objets的方式发送二进制数据,发送后主线程无法访问uInt8Array变量的数据,但不会造成性能问题,适合用于影像、声音和3D等大文件运算。                                     
                                                                                                                                                                                                                                              
// 接收worker线程向主线程发送的消息                                                                                                                                                                                               
worker.onmessage = event => {                                                                                                                                                                                                     
  console.log(event.data)                                                                                                                                                                                                       
}                                                                                                                                                                                                                                 
worker.addEventListener('message', event => {                                                                                                                                                                                     
  console.log(event.data)                                                                                                                                                                                                       
})                                                                                                                                                                                                                                
                                                                                                                                                                                                                                              
// 当发送的消息序列化失败时触发该事件。                                                                                                                                                                                           
worker.onmessageerror = error => console.error(error)                                                                                                                                                                             
                                                                                                                                                                                                                                              
// 捕获Worker线程发生的异常                                                                                                                                                                                                       
worker.onerror = error => {                                                                                                                                                                                                       
  console.error(error)                                                                                                                                                                                                          
}                                                                                                                                                                                                                                 
                                                                                                                                                                                                                                              
// 关闭worker线程                                                                                                                                                                                                                 
worker.terminate()                                                                                                                                                                                                                
  1. Worker线程
// Worker线程的全局对象为WorkerGlobalScrip,通过self或this引用。调用全局对象的属性和方法时可以省略全局对象。                                                                                                                      
                                                                                                                                                                                                                                              
// 接收主线程向worker线程发送的消息                                                                                                                                                                                               
self.addEventListener('message', event => {                                                                                                                                                                                       
  console.log(event.data)                                                                                                                                                                                                       
})                                                                                                                                                                                                                                
addEventListener('message', event => {                                                                                                                                                                                            
  console.log(event.data)                                                                                                                                                                                                       
})                                                                                                                                                                                                                                
this.onmessage = event => {                                                                                                                                                                                                       
  console.log(event.data)                                                                                                                                                                                                       
}                                                                                                                                                                                                                                 
// 当发送的消息序列化失败时触发该事件。                                                                                                                                                                                           
self.onmessageerror = error => console.error(error)                                                                                                                                                                               
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      // 向主线程发送消息                                                                                                                                                                                                               
self.postMessage('send text to main worker')                                                                                                                                                                                      
                                                                                                                                                                                                                                              
// 结束自身所在的Worker线程                                                                                                                                                                                                       
self.close()                                                                                                                                                                                                                      
                                                                                                                                                                                                                                              
// 导入其他脚本到当前的Worker线程,不要求所引用的脚本必须同域。                                                                                                                                                                   
self.importScripts('script1.js', 'script2.js')                                                                                                                                                                                    

通过WebWorker运行本页脚本

方式1——BlobURL.createObjectURL

限制:UI线程所属页面不是本地页面,即必须为http(s)://协议。

const script = `addEventListener('message', event => {                                                                                                                                                                                    
  console.log(event.data)                                                                                                                                                                                                               
  postMessage('echo')                                                                                                                                                                                                                   
}`                                                                                                                                                                                                                                        
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            
const blob = new Blob([script])                                                                                                                                                                                                           
const url = URL.createObjectURL(blob)                                                                                                                                                                                                     
const worker = new Worker(url)                                                                                                                                                                                                            
worker.onmessage = event => console.log(event.data)                                                                                                                                                                                       
worker.postMessage('main thread')                                                                                                                                                                                                         
setTimeout(()=>{                                                                                                                                                                                                                          
  worker.terminate()                                                                                                                                                                                                                    
  URL.revokeObjectURL(url) // 必须手动释放资源,否则需要刷新Browser Context时才会被释放。                                                                                                                                               
}, 1000)  

方式2——Data URL

限制:无法利用JavaScript的ASI机制少写分号。
优点:即使UI线程所属页面是本地页面也可以执行。

// 由于Data URL的内容为必须压缩为一行,因此JavaScript无法利用换行符达到分号的效果。                                                                                                                                                       
const script = `addEventListener('message', event => {                                                                                                                                                                                    
  console.log(event.data);                                                                                                                                                                                                              
  postMessage('echo');                                                                                                                                                                                                                  
}`                                                                                                                                                                                                                                        
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       
const worker = new Worker(`data:,${script}`)                                                                                                                                                                                              
// 或 const worker = new Worker(`data:application/javascript,${script}`)                                                                                                                                                                  
worker.onmessage = event => console.log(event.data)                                                                                                                                                                                       
worker.postMessage('main thread')  

Shared Web Worker详解

共享线程可以和多个同域页面间通信,当所有相关页面都关闭时共享线程才会被释放。
这里的多个同域页面包括:

  1. iframe之间
  2. 浏览器标签页之间

简单示例

  1. UI主线程
const worker = new SharedWorker('./worker.js')                                                                                                                                                                                             
worker.port.addEventListener('message', e => {                                                                                                                                                                                             
  console.log(e.data)                                                                                                                                                                                                                      
}, false)                                                                                                                                                                                                                                  
worker.port.start()  // 连接worker线程                                                                                                                                                                                                     
worker.port.postMessage('hi')                                                                                                                                                                                                              
                                                                                                                                                                                                                                              
setTimeout(()=>{                                                                                                                                                                                                                           
  worker.port.close() // 关闭连接                                                                                                                                                                                                          
}, 10000)                                                                                                                                                                                                                                  
  1. Shared Web Worker线程
let conns = 0                                                                                                                                                                                                                              
                                                                                                                                                                                                                                              
// 当UI线程执行worker.port.start()时触发建立连接                                                                                                                                                                                           
self.addEventListener('connect', e => {                                                                                                                                                                                                    
  const port = e.ports[0]                                                                                                                                                                                                                  
  conns+=1                                                                                                                                                                                                                                 
                                                                                                                                                                                                                                              
  port.addEventListener('message', e => {                                                                                                                                                                                                  
    console.log(e.data)  // 注意console对象指向第一个创建Worker线程的UI线程的console对象。即如果A先创建Worker线程,那么后续B、C等UI线程执行worker.port.postMessage时回显信心依然会发送给A页面。                                            
  })                                                                                                                                                                                                                                       
                                                                                                                                                                                                                                              
  // 建立双向连接,可相互通信                                                                                                                                                                                                              
  port.start()                                                                                                                                                                                                                             
  port.postMessage('hey')                                                                                                                                                                                                                  
})                                                                                                                                                                                                                                         

示例——广播

  1. UI主线程
   const worker = new SharedWorker('./worker.js')                                                                                                                                                                                             
   worker.port.addEventListener('message', e => {                                                                                                                                                                                             
     console.log('SUM:', e.data)                                                                                                                                                                                                              
   }, false)                                                                                                                                                                                                                                  
   worker.port.start()  // 连接worker线程                                                                                                                                                                                                     
                                                                                                                                                                                                                                              
   const button = document.createElement('button')                                                                                                                                                                                            
   button.textContent = 'Increment'                                                                                                                                                                                                           
   button.onclick = () => worker.port.postMessage(1)                                                                                                                                                                                          
   document.body.appendChild(button)                                                                                                                                                                                                          
  1. Shared Web Worker线程
   let sum = 0                                                                                                                                                                                                                                
   const conns = []                                                                                                                                                                                                                           
   self.addEventListener('connect', e => {                                                                                                                                                                                                    
     const port = e.ports[0]                                                                                                                                                                                                                  
     conns.push(port)                                                                                                                                                                                                                         
                                                                                                                                                                                                                                              
     port.addEventListener('message', e => {                                                                                                                                                                                                  
       sum += e.data                                                                                                                                                                                                                          
       conns.forEach(conn => conn.postMessage(sum))                                                                                                                                                                                           
     })                                                                                                                                                                                                                                       
                                                                                                                                                                                                                                              
     port.start()                                                                                                                                                                                                                             
   })                                                                                                                                                                                                                                         

即使是Web Worker也阻止不了你卡死浏览器的决心

通过WebWorker执行计算密集型任务是否就可以肆无忌惮地编写代码,并保证用户界面操作流畅呢?当然不是啦,工具永远只能让你更好地完成工作,但无法禁止你用错。
只要在频繁持续执行的代码中加入console对象方法的调用,加上一不小心打开Devtools工具,卡死浏览器简直不能再就简单了。这是为什么呢?
因为UI线程在创建WebWorker线程时会将自身的console对象绑定给WebWorker线程的console属性上,那么WebWorker线程是以同步阻塞方式调用console将参数传递给UI线程的console对象,自然会占用UI线程的处理时间。

工程化——通过Webpack的worker-loader打包代码

上面说了这么多那实际项目中应该怎么使用呢?或者说如何更好的集成到工程自动化工具——Webpack呢?
worker-loader和shared-worker-loader就是我们想要的。
通过worker-loader将代码转换为Blob类型,并通过URL.createObjectURL创建url分配给WebWorker线程执行。

  1. 安装loader
npm install worker-loader -D
  1. 配置Webpack.config.js
// 处理worker代码的loader必须位于js和ts之前                                                                                                                                                                                              
{                                                                                                                                                                                                                                        
 test: /\.worker\.ts$/,                                                                                                                                                                                                                 
 use: {                                                                                                                                                                                                                                 
 loader: 'worker-loader',                                                                                                                                                                                                             
 options: {                                                                                                                                                                                                                           
   name: '[name]:[hash:8].js', // 打包后的chunk的名称                                                                                                                                                                                 
   inline: true // 开启内联模式,将chunk的内容转换为Blob对象内嵌到代码中。                                                                                                                                                            
   }                                                                                                                                                                                                                                    
 }                                                                                                                                                                                                                                      
},                                                                                                                                                                                                                                      
{                                                                                                                                                                                                                                        
 test: /\.js$/,                                                                                                                                                                                                                         
 use: {                                                                                                                                                                                                                                 
  loader: 'babel-loader'                                                                                                                                                                                                               
 },                                                                                                                                                                                                                                     
 exclude: [path.resolve(__dirname, 'node_modules')]                                                                                                                                                                                     
},                                                                                                                                                                                                                                       
{                                                                                                                                                                                                                                        
 test: /\.ts(x?)$/,                                                                                                                                                                                                                     
 use: [                                                                                                                                                                                                                                 
   { loader: 'babel-loader' },                                                                                                                                                                                                          
   { loader: 'ts-loader' } // loader顺序从后往前执行                                                                                                                                                                                    
 ],                                                                                                                                                                                                                                     
 exclude: [path.resolve(__dirname, 'node_modules')]                                                                                                                                                                                     
}                                                                                                                                                                                                                                                
  1. UI线程代码
import MyWorker from './my.worker'                                                                                                                                                                                                       
                                                                                                                                                                                                                                              
const worker = new MyWorker('');                                                                                                                                                                                                         
worker.postMessage('hi')                                                                                                                                                                                                                 
worker.addEventListener('message', event => console.log(event.data))   
  1. Worker线程代码
cosnt worker: Worker = self as any                                                                                                                                                                                                       
worker.addEventListener('message', event => console.log(event.data))                                                                                                                                                                     
                                                                                                                                                                                                                                              
export default null as any // 标识当前为TS模块,避免报xxx.ts is not a module的异常   

工程化——RPC类库Comlink

一般场景下我们会这样使用WebWorker,

  1. UI线程传递参数并调用运算函数;
  2. 在不影响用户界面响应的前提下等待函数返回值;
  3. 获取函数返回值继续后续代码。

翻译为代码就是

let arg1 = getArg1()
let arg2 = getArg2()
const result = await performCalcuation(arg1, arg2)
doSomething(result)

而UI线程和WebWorker线程的消息机制通信机制显然会加大代码复杂度,而Comlink类库恰好能抚平这道伤疤。

  1. UI线程
import * as Comlink from 'comlink'                                                                                                                                                                                                       
                                                                                                                                                                                                                                              
async function init() {                                                                                                                                                                                                                  
 const cl = Comlink.wrap(new Worker('worker.js'))                                                                                                                                                                                     
 console.log(`Counter: ${await cl.counter}`)                                                                                                                                                                                          
 await cl.inc()                                                                                                                                                                                                                       
 console.log(`Counter: ${await cl.counter}`)                                                                                                                                                                                          
}                                                                                                                                                                                                                                        
  1. Worker线程
import * as Comlink from 'comlink'                                                                                                                                                                                                       
                                                                                                                                                                                                                                              
const obj = {                                                                                                                                                                                                                            
  counter: 0,                                                                                                                                                                                                                            
  inc() {                                                                                                                                                                                                                                
    this.counter+=1                                                                                                                                                                                                                      
  }                                                                                                                                                                                                                                      
}                                                                                                                                                                                                                                        
Comlink.expose(obj)                                                                                                                                                                                                                      

Electron中使用WebWorker

Electron中使用Web Worker的同源限制中开了个口——UI线程所属页面URL为本地文件时,所分配给Web Worker的脚本可为本地脚本。
其实Electron打包后读取的HTML页面、脚本等都是本地文件,如果不能分配本地脚本给Web Worker执行,那就进入死胡同了。

const path = window.require('path')                                                                                                                                                                                                                                     
                                                                                                                                                                                                                                                                          
const worker = new Worker(path.resolve(__dirname, 'worker.js'))                                                                                                                                                                                                         

上述代码仅表示Electron可以分配本地脚本给WebWorker线程执行,但实际开发阶段一般是通过 ~http(s)://~ 协议加载页面资源,而发布时才会打包为本地资源。
所以这里还要分为开发阶段用和发布用代码,还涉及资源的路径问题,所以还不如直接转换为Blob数据内嵌到UI线程的代码中更便捷。

总结

随着边缘计算的兴起,客户端承担部分计算任务提高运算时效性和降低服务端压力必将成为趋势。WebWorker这一秘技你Get到了吗?:)
转载请注明来自: https://www.cnblogs.com/fsjoh... —— 肥仔John

查看原文

赞 14 收藏 12 评论 0

肥仔John 发布了文章 · 2020-12-15

SpringBoot魔法堂:应用热部署实践与原理浅析

前言

后端开发的同学想必每天都在重复经历着修改代码、执行代码编译,等待……重启Tomcat服务,等待……最后测试发现还是有bug,然后上述流程再来一遍(我听不见):(
能不能像前端开发的同学那样,修改代码保存文件后自动编译、重新加载应用呢?Spring Boot给了我们一个大大的Yes!
本文我们就一起来探索Spring Boot的热部署功能提升开发效率吧!

长话短说

热部署作为开发阶段的特性,由spring-boot-devtools模块提供,用于在修改类、配置文件和页面等静态资源后,自动编译Spring Boot应用和加载应用和页面静态资源,从而提高开发流程自动化程度提升开发效率。
那么第一步当然是在pom.xml中添加配置:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-devtools</artifactId>
  <option>true</option>
</dependency>

<build>
  <plugins>
    <plugin>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-maven-plugin</artifactId>
      <configuration>
        <fork>true</fork> <!-- 默认值为false,必须设置为true才能启用热部署功能(具体原因请见下文) -->
      </configuration>
    </plugin>
  </plugins>
</build>

静态资源热部署

对于HTML页面、图片、CSS样式文件这些显然不需要编译的静态资源,Spring Boot Devtools模块通过内置的livereload服务端和浏览器的LiveReload插件共同实现热部署。

  1. 服务端配置
spring:
  devtools:
    livereload:
      enabled: true # 启用LiveReload服务端
      port: 35729 # LiveReload服务端口

默认仅触发LiveReload事件的默认路径如下: /META-INF/maven,/META-INF/resources,/resources,/static,/public/templates

  1. 浏览器配置

无论时FireFox还是Chrome都有相应的LiveReload插件,按步骤安装就可以了。

Java类资源热部署

Spring Boot Devtools模块是通过监听Java类资源变化触发应用热部署,请注意这里监听的是Java类资源而不是Java源代码文件,那么什么是Java类资源**呢?其实就是.class文件。
这样从保存Java源代码文件到Spring Boot Devtools监听到Java类资源变化之间,就有一道不可逾越的鸿沟了。我们必须通过额外手段填平:

  1. 手动方式:修改Java源代码文件后,执行mvn compile
  2. 自动方式:配置IDEA监听Java源代码文件变化,触发重新编译

2.1. 右键点击SpringBootApplication入口类文件,并点击Create XXXX.main(),创建Application类型的Configuration;
2.2. 勾选File/Settings/Compiler/Build Project automatically
2.3. 按ctrl+shift+alt+/,然后选择Registry并勾选Compiler autoMake allow when app running
2.4. 通过IDEA左上角绿色的运行按钮启动Spring Boot应用,然后修改Java源代码文件后IDEA会自动重新编译项目,从而触发Spring Boot Devtools热部署。

更多配置配置项

spring:
  devtools:
  restart:
    enabled: true # 启用热部署
    exclude: main/static/** # 除默认路径外,添加文件变化不触发热部署的路径列表,多个路径之间通过逗号分隔。
    additional: assets/** # 添加文件变化会触发热部署的路径列表,多个路径之间通过逗号分隔。
    additional-exclude: assets/public/** # 设置additional属性指定的路径下某些路径的文件变化,不触发热部署,多个路径之间通过逗号分隔。

默认不触发热部署的路径有:/META-INF/maven,/META-INF/resources,/resources,/static,/public/templates

除了通过yml文件配置是否启用热部署功能外,还可以通过环境变量设置。在SpringBootApplication入口方法中加入

System.setProperty("spring.devtools.restart.enabled", "false");
SpringApplication.run(MyApp.class, args);

疑难解答

  1. 在IDEA中修改文件后报 Maven Resource Compiler: Maven project configuration required for module 'lkm-api' isn't available. Compilation of Maven projects is supported only if build is started from an IDE.
    答:请使用IDEA那个绿色的运行按钮启动Spring Boot应用。
  2. 在IDEA中修改文件后没有反应
    答:请稍等数秒自然会触发重新编译和热部署的。

为什么是热部署而不是热替换呢?

开发过React或Vue的同学对热替换应该不陌生吧,可以粗线条地理解为将应用以比文件更细粒度的模块或函数来组织,当源代码发生变化时仅仅替换发生变化的模块或函数以及依赖它们的模块或函数,通过最小化变更达到快速更新应用状态。
而Spring Boot Devtools并没有做成像React和Vue的开发工具那么细粒度的更新,而是采取通过基类加载器重启类加载器两个类加载器来实现热部署:

  1. 基类加载器,用于加载第三方依赖等开发阶段不经常发生变化的Java类资源。
  2. 重启类加载器,用于加载当前项目的Java类资源。若当前项目的Java类资源发生变化时,正在运行的重启类加载器会被丢弃,并另外创建一个重启类加载器并加载最新的Java类资源。

为什么pom.xml文件中的spring-boot-maven-plugin要设置为独立JVM进程运行呢(<fork>true</fork>)?

默认情况下<fork>false</fork>表示Maven采用运行自身的JVM虚拟机运行插件,而通过<fork>true</fork>则告知Maven启动一个新的JVM虚拟机进程运行插件。
那么为什么要耗费资源启动新JVM虚拟机执行插件呢?直接运行不香吗?

场景1——使用不同的JDK运行插件

执行mvn -v会显示当前Maven运行的JDK版本信息,假设为JDK1.8且编码方式为UTF-8。
由于Maven 3.8.1必须运行在JDK1.8以上,而项目只能在JDK1.6上编译运行,因此需要通过如下方式执行插件:

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-compiler-plugin</artifactId>
  <version>3.8.1</version>
  <configuration>
    <fork>true</fork>
    <!-- 指定JDK家目录,默认为环境变量PATH中的路径 -->
    <executable>/path/to/jdk1.6</executable>
    <compilerVersion>1.3</compilerVersion>
  </configuration>
</plugin>

场景2——采用不同的JVM配置运行插件

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-compiler-plugin</artifactId>
  <version>3.8.1</version>
  <configuration>
      <fork>true</fork>
      <meminitial>128m</meminitial>
      <maxmem>1024m</maxmem>
      <compilerArgs>
        <arg>-XX:MaxPermSize=256m</arg>
      </compilerArgs>
  </configuration>
</plugin>

场景3——插件需要特定的JVM配置来运行

像spring-boot-maven-plugin那样在启用spring-boot-devtools模块时需要特定JVM配置来运行,并且运行途中还会对重启类加载器惨下杀手的,自然也要创建新的JVM虚拟机进程来运行才可以了。

总结

Spring Boot不单单通过约定由于配置的原则简化了过去Spring MVC那些繁琐的配置文件,还提供各种显著提升开发效率的自动化工具,而spring-boot-devtools就是其中一个。
倘若你所在的团队还没用上Spring Boot那么是不是就无法享受这份便捷呢?我想JRebel IDEA插件应该是你需要的:)

转载请注明来自:https://www.cnblogs.com/fsjoh... —— 肥仔John

查看原文

赞 4 收藏 3 评论 0

肥仔John 发布了文章 · 2020-12-11

前端魔法堂:手写缓存模块

前言

之前系统接入大数据PV统计平台,最近因PV统计平台侧服务器资源紧张,要求各接入方必须缓存API调用验证用的Token,从而减少无效请求和服务端缓存中间件的存储压力。
虽然系统部分业务模块都有缓存数据的需求,但由于没有提供统一的前端缓存模块,这导致各业务模块都自行实现一套刚好能用的缓存机制,甚至还会导致内存泄漏。
以兄弟部门这张整改工单作为契机,是时候开发一个系统级的前端缓存模块,逐步偿还技术负债了。

1分钟上手指南

  1. 直接使用CacheManager
// 提供3种级别的缓存提供器
// 1. 当前Browser Context级缓存MemoryCacheProvider
// 2. 基于SessionStorage的SessionCacheProvider
// 3. 基于LocalStorage的LocalCacheProvider
const cache = new CacheManager(MemoryCacheProvider.default())

console.log(cache.get('token') === CacheManager.MISSED) // 回显true,缓存击穿时返回CacheManager.MISSED
cache.set('token1', (Math.random()*1000000).toFixed(0), 5000) // 缓存同步求值表达式结果5000毫秒

// 缓存异步求值表达式求值成功,缓存结果5000毫秒
cache.set('token2', new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('hi there!')
    }, 6000)
}, 5000)
cache.get('token2').then(console.log, console.error) // 6000毫秒后回显 'hi there!'
setTimeout(() => {
    // 回显true,对于最终状态为fulfilled的异步求值操作,缓存有效创建时间为从pending转换为fulfilled的那一刻。
    // 因此token2缓存的有效时长为6000+5000毫秒。
    console.log(cache.get('token2') === CacheManager.MISSED)
}, 12000)

// 缓存异步求值表达式求值失败,中止缓存操作
cache.set('token3', new Promise((resolve, reject) => {
    setTimeout(() => {
        reject('hi there!')
    }, 6000)
}, 5000)
cache.get('token3').then(console.log, console.error) // 6000毫秒后回显 'hi there!'
setTimeout(() => {
    console.log(cache.get('token3') === CacheManager.MISSED) // 7000毫秒后回显true
}, 7000)
  1. 高阶函数——memorize
function getToken(deptId, sysId){
    console.log(deptId, sysId)
    return new Promise(resolve => setTimeout(() => resolve('Bye!'), 5000))
}

// 缓存函数返回值10000毫秒
// 第三个参数默认值为MemoryCacheProvider.default()
const getCachableToken = memorize(getToken, 10000, MemoryCacheProvider.default())
getCachableToken(1,1).then(console.log) // 立即回显 '1 1',5000毫秒后回显 'Bye!'
getCachableToken(1,1).then(console.log) // 不再调用getToken方法,因此没有立即回显 '1 1',5000毫秒后回显 'Bye!'
getCachableToken(1,2).then(console.log) // 立即回显 '1 2',5000毫秒后回显 'Bye!'

Coding

CacheItem.js

class CacheItem {                                                                                                                                                                                             
    constructor(timeout, value, status) {                                                                                                                                                                     
        this.timeout = timeout                                                                                                                                                                                
        this.value = value                                                                                                                                                                                    
        this.created = (+new Date())                                                                                                                                                                          
        this.status = status || CacheItem.STATUS.SYNC                                                                                                                                                         
    }                                                                                                                                                                                                         
    isExpired() {                                                                                                                                                                                             
        return (this.timeout + this.created < (+new Date())) && (this.status !== CacheItem.STATUS.PENDING)                                                                                                    
    }                                                                                                                                                                                                         
    isSync() {                                                                                                                                                                                                
        return this.status === CacheItem.STATUS.SYNC                                                                                                                                                          
    }                                                                                                                                                                                                         
    isPending() {                                                                                                                                                                                             
        return this.status === CacheItem.STATUS.PENDING                                                                                                                                                       
    }                                                                                                                                                                                                         
    isFulfilled() {                                                                                                                                                                                           
        return this.status === CacheItem.STATUS.FULFILLED                                                                                                                                                     
    }                                                                                                                                                                                                         
    isRejected() {                                                                                                                                                                                            
        return this.status === CacheItem.STATUS.REJECTED                                                                                                                                                      
    }                                                                                                                                                                                                         
    expire() {                                                                                                                                                                                                
        this.timeout = 0                                                                                                                                                                                      
        return this                                                                                                                                                                                           
    }                                                                                                                                                                                                         
    pending() {                                                                                                                                                                                               
        this.status = CacheItem.STATUS.PENDING                                                                                                                                                                
        return this                                                                                                                                                                                           
    }                                                                                                                                                                                                         
    fulfill(value) {                                                                                                                                                                                          
        this.value = value                                                                                                                                                                                    
        this.status = CacheItem.STATUS.FULFILLED                                                                                                                                                              
        this.created = (+new Date())                                                                                                                                                                          
        return this                                                                                                                                                                                           
    }                                                                                                                                                                                                         
    reject(error) {                                                                                                                                                                                           
        this.value = error                                                                                                                                                                                    
        this.status = CacheItem.STATUS.REJECTED                                                                                                                                                               
        this.expire()                                                                                                                                                                                         
    }                                                                                                                                                                                                         
    toString() {                                                                                                                                                                                              
        return JSON.stringify(this)                                                                                                                                                                           
    }                                                                                                                                                                                                         
}
CacheItem.STATUS = {                                                                                                                                                                                          
    SYNC: 0,                                                                                                                                                                                                  
    PENDING: 1,                                                                                                                                                                                               
    FULFILLED: 2,                                                                                                                                                                                             
    REJECTED: 3                                                                                                                                                                                               
}                                                                                                                                                                                                             
CacheItem.of = (timeout, value, status) => {                                                                                                                                                                  
    if (typeof timeout === 'string' && value === undefined && status === undefined) {                                                                                                                         
        // Parse cache item serialized presentation to CacheItem instance.                                                                                                                                    
        const proto = JSON.parse(timeout)                                                                                                                                                                     
        const cacheItem = new CacheItem(proto.timeout, proto.value, proto.status)                                                                                                                             
        cacheItem.created = proto.created                                                                                                                                                                     
        return cacheItem                                                                                                                                                                                      
    }                                                                                                                                                                                                         
    else {                                                                                                                                                                                                    
        return new CacheItem(timeout, value, status)                                                                                                                                                          
    }                                                                                                                                                                                                         
}   

CacheManager.js

class CacheManager {                                                                                                                                                                                          
    constructor(cacheProvider, id) {                                                                                                                                                                          
        this.provider = cacheProvider                                                                                                                                                                         
        this.id = id                                                                                                                                                                                          
    }                                                                                                                                                                                                         
    key(name) {                                                                                                                                                                                               
        return (this.id != null ? this.id + '-' : '') + String(name)                                                                                                                                          
    }                                                                                                                                                                                                         
    set(name, value, timeout) {                                                                                                                                                                               
        const key = this.key(name)                                                                                                                                                                            
                                                                                                                                                                                                              
        if (value && value.then) {                                                                                                                                                                            
            // Cache thenable object                                                                                                                                                                          
            this.provider.set(key, CacheItem.of(timeout).pending())                                                                                                                                           
            value.then(value => this.provider.get(key).fulfill(value)                                                                                                                                         
                       , error => this.provider.get(key).reject(error))                                                                                                                                       
        }                                                                                                                                                                                                     
        else {                                                                                                                                                                                                
            this.provider.set(key, CacheItem.of(timeout, value))                                                                                                                                              
        }                                                                                                                                                                                                     
    }                                                                                                                                                                                                         
    get(name) {                                                                                                                                                                                               
        const key = this.key(name)                                                                                                                                                                            
        const cacheItem = this.provider.get(key)                                                                                                                                                              
        if (null === cacheItem) return CacheManager.MISSED                                                                                                                                                    
                                                                                                                                                                                                              
        if (cacheItem.isExpired()) {                                                                                                                                                                          
            this.provider.remove(key)                                                                                                                                                                         
            return CacheManager.MISSED                                                                                                                                                                        
        }                                                                                                                                                                                                     
        else if (cacheItem.isSync()) {                                                                                                                                                                        
            return cacheItem.value                                                                                                                                                                            
        }                                                                                                                                                                                                     
        else if (cacheItem.isFulfilled()) {                                                                                                                                                                   
            return Promise.resolve(cacheItem.value)                                                                                                                                                           
        }                                                                                                                                                                                                     
        else if (cacheItem.isPending()) {                                                                                                                                                                     
            return new Promise((resolve, reject) => {                                                                                                                                                         
                let hInterval = setInterval(() => {                                                                                                                                                           
                    let item = this.provider.get(key)                                                                                                                                                         
                    if (item.isFulfilled()) {                                                                                                                                                                 
                        clearInterval(hInterval)                                                                                                                                                              
                        resolve(item.value)                                                                                                                                                                   
                    }                                                                                                                                                                                         
                    else if (item.isRejected()) {                                                                                                                                                             
                        clearInterval(hInterval)                                                                                                                                                              
                        reject(item.value)                                                                                                                                                                    
                    }                                                                                                                                                                                         
                                                                                                                                                                                                              
                }, CacheManager.PENDING_BREAK)                                                                                                                                                                
            })                                                                                                                                                                                                
        }                                                                                                                                                                                                     
        throw Error('Bug flies ~~')                                                                                                                                                                           
    }                                                                                                                                                                                                         
}                                                                                                                                                                                                             
CacheManager.MISSED = new Object()                                                                                                                                                                            
CacheManager.PENDING_BREAK = 250                                                                                                                                                                                                                     

SessionCacheProvider.js

class SessionCacheProvider {                                                                                                                                                                                  
    constructor() {                                                                                                                                                                                           
        if (SessionCacheProvider.__default !== null) {                                                                                                                                                        
            throw Error('New operation is forbidden!')                                                                                                                                                        
        }                                                                                                                                                                                                     
    }                                                                                                                                                                                                         
                                                                                                                                                                                                              
    get(key) {                                                                                                                                                                                                
        let item = sessionStorage.getItem(key)                                                                                                                                                                
        if (item !== null) {                                                                                                                                                                                  
            item = CacheItem.of(item)                                                                                                                                                                         
        }                                                                                                                                                                                                     
        return item                                                                                                                                                                                           
    }                                                                                                                                                                                                         
    set(key, cacheItem) {                                                                                                                                                                                     
        sessionStorage.setItem(key, cacheItem)                                                                                                                                                                
        return this                                                                                                                                                                                           
    }                                                                                                                                                                                                         
    remove(key) {                                                                                                                                                                                             
        sessionStorage.removeItem(key)                                                                                                                                                                        
        return this                                                                                                                                                                                           
    }                                                                                                                                                                                                         
}                                                                                                                                                                                                             
SessionCacheProvider.__default = null                                                                                                                                                                         
SessionCacheProvider.default = () => {                                                                                                                                                                        
    if (SessionCacheProvider.__default === null) {                                                                                                                                                            
        SessionCacheProvider.__default = new SessionCacheProvider()                                                                                                                                           
    }                                                                                                                                                                                                         
    return SessionCacheProvider.__default                                                                                                                                                                     
}

LocalCacheProvider.js

class LocalCacheProvider {                                                                                                                                                                                    
    constructor() {                                                                                                                                                                                           
        if (LocalCacheProvider.__default !== null) {                                                                                                                                                          
            throw Error('New operation is forbidden!')                                                                                                                                                        
        }                                                                                                                                                                                                     
    }                                                                                                                                                                                                         
                                                                                                                                                                                                              
    get(key) {                                                                                                                                                                                                
        let item = localStorage.getItem(key)                                                                                                                                                                  
        if (item !== null) {                                                                                                                                                                                  
            item = CacheItem.of(item)                                                                                                                                                                         
        }                                                                                                                                                                                                     
        return item                                                                                                                                                                                           
    }                                                                                                                                                                                                         
    set(key, cacheItem) {                                                                                                                                                                                     
        localStorage.setItem(key, cacheItem)                                                                                                                                                                  
        return this                                                                                                                                                                                           
    }                                                                                                                                                                                                         
    remove(key) {                                                                                                                                                                                             
        localStorage.removeItem(key)                                                                                                                                                                          
        return this                                                                                                                                                                                           
    }                                                                                                                                                                                                         
}                                                                                                                                                                                                             
LocalCacheProvider.__default = null                                                                                                                                                                           
LocalCacheProvider.default = () => {                                                                                                                                                                          
    if (LocalCacheProvider.__default === null) {                                                                                                                                                              
        LocalCacheProvider.__default = new LocalCacheProvider()                                                                                                                                               
    }                                                                                                                                                                                                         
    return LocalCacheProvider.__default                                                                                                                                                                       
}  

MemoryCacheProvider.js

class MemoryCacheProvider {                                                                                                                                                                                   
    constructor() {                                                                                                                                                                                           
        this.cache = {}                                                                                                                                                                                       
    }                                                                                                                                                                                                         
                                                                                                                                                                                                              
    get(key) {                                                                                                                                                                                                
        let item = this.cache[key]                                                                                                                                                                            
        if (item == null) return null                                                                                                                                                                         
        else return item                                                                                                                                                                                      
    }                                                                                                                                                                                                         
    set(key, cacheItem) {                                                                                                                                                                                     
        this.cache[key] = cacheItem                                                                                                                                                                           
        return this                                                                                                                                                                                           
    }                                                                                                                                                                                                         
    remove(key) {                                                                                                                                                                                             
        delete this.cache[key]                                                                                                                                                                                
        return this                                                                                                                                                                                           
    }                                                                                                                                                                                                         
}                                                                                                                                                                                                             
MemoryCacheProvider.__default = null                                                                                                                                                                          
MemoryCacheProvider.default = () => {                                                                                                                                                                         
    if (MemoryCacheProvider.__default === null) {                                                                                                                                                             
        MemoryCacheProvider.__default = new MemoryCacheProvider()                                                                                                                                             
    }                                                                                                                                                                                                         
    return MemoryCacheProvider.__default                                                                                                                                                                      
}  

helper.js

function memorize(f, timeout, cacheProvider) {                                                                                                                                                                
    var cacheManager = new CacheManager(cacheProvider || MemoryCacheProvider.default(), f.name || String(+new Date()))                                                                                        
    return function() {                                                                                                                                                                                       
        var args = Array.prototype.slice.call(arguments)                                                                                                                                                      
        var argsId = JSON.stringify(args)                                                                                                                                                                     
        var cachedResult = cacheManager.get(argsId)                                                                                                                                                           
        if (cachedResult !== CacheManager.MISSED) return cachedResult                                                                                                                                         
                                                                                                                                                                                                              
        var result = f.apply(null, args)                                                                                                                                                                      
        cacheManager.set(argsId, result, timeout)                                                                                                                                                             
        return result                                                                                                                                                                                         
    }                                                                                                                                                                                                         
}  

总结

后续还要加入失效缓存定时清理、缓存记录大小限制、总体缓存大小限制和缓存清理策略等功能,毕竟作为生产系统,用户不刷新页面持续操作8个小时是常态,若是无效缓存导致内存溢出就得不偿失了。
当然后面重构各业务模块的缓存代码也是不少的工作量,共勉。

转载请注明来自:https://www.cnblogs.com/fsjoh... —— ^_^肥仔John

查看原文

赞 4 收藏 3 评论 0

肥仔John 发布了文章 · 2020-11-11

SpringBoot魔法堂:说说带智能提示的spring-boot-starter

前言

前几个月和隔壁组的老王闲聊,他说项目的供应商离职率居高不下,最近还有开发刚接手ESB订阅发布接口才两周就提出离职,而他能做的就只有苦笑和默默地接过这个烂摊子了。
而然幸福的家庭总是相似的,而不幸的我却因业务变革走上了和老王一样的道路。单单是接口的开发居然能迫使一位开发毅然决然地离职,我既不相信是人性的扭曲,更不信是道德的沦丧。
抛开这个富有色彩的故事而言,我发现原来的项目存在如下问题:

  1. 没有使用任何现代依赖管理和构建工具(如Maven, Gradle),直接把所依赖的Jar包存放在项目目录下的lib目录中,日积月累导致lib目录下存放大量无用Jar包;
  2. 没有使用代码版本管理工具管理代码;
  3. 技术文档欠缺,全靠师傅带徒弟的方式传授框架使用方式和开发流程;
  4. 机械性配置项多,而后来的开发人员大多只能依葫芦画瓢添加配置,既容易出错同时又增加问题排查的难度。

针对前两个问题,我们只需梳理出必须的依赖项并加入Maven或Gradle管理,然后托管到Git即可。
而后两者则可以通过spring-boot-starter将必选依赖项和配置统一管理,并附上相关技术文档;然后通过模板模式和注解简化开发流程,提供Demo降低入门难度。
最后就可以把具体的业务功能开发交给供应商处理,我们专心做好过程管理和验收即可。

本文将着重分享spring-boot-starter开发的事项,请坐好扶稳!

命名规范

在自定义starter前我们总要思考如何命名我们的starter,而官方提供如下的命名规范:

  1. 官方的starter以spring-boot-starter作为前缀命名项目
    如:spring-boot-starter-web
  2. 非官方的则以spring-boot-starter作为后缀命名项目
    如:mybatis-spring-boot-starter

项目结构

通过Spring Initializr或Spring Boot CLI创建项目结构后,将pom.xml的相关项目修改为如下内容

<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifacId>
  <version>2.3.1.RELEASE</version>
  <relativePath/>
</parent>

<dependencies>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
  </dependency>

  <!-- 下面为自定义Starter的依赖项 -->
</dependencies>

<build>
  <plugins>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-compiler-plugin</artifactId>
      <configuration>
    <source>1.8</source>
    <target>1.8</target>
      </configuration>
    </plugin>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-source-plugin</artifactId>
      <version>2.4</version>
      <executions>
    <execution>
      <goals>
        <goal>jar</goal>
      </goals>
    </execution>
      </executions>
    </plugin>
  </plugins>
</build>

在starter中我们会定义SpringBean的注册配置和属性配置,如ESB订阅服务的配置项目为

@Configuration
@EnableConfigurationProperties({EsbServerProperties.class})
public class EsbServerConfiguration {
    @Bean
    public SpringBus springBus(){
        return new SpringBus();
    }

    @Bean
    public LoggingFeature loggingFeature(){
        return new LoggingFeature();
    }

    @Bean
    public List<JMSConfigFeature> jmsConfigFeatures(EsbServerProperties props) throws JMSException {
        List<JMSConfigFeature> features = new ArrayList<>();
          
        /** 
         * 这里会使用EsbServerProperties的属性构建Bean实例
         */

        return features;
    }
}

属性配置项

// 从application.yml等配置文件中读取并绑定esb.server.destination等属性值
@Data
@ConfigurationProperties("esb.server")
public class EsbServerProperties {
    String destination;
    int currConsumers = 1;
    String channel;
    int ccsid = 1205;
    int transportType = 1;
    List<String> connectionNameLists;
    boolean replyError = false;
    String replySuccessText = "Success";
    String replyErrorText = "Failure";
}

到这里我们已经完成一个基本的starter的功能

  1. 通过@ConfigurationProperties定义该starter注册bean时需要的属性集合
  2. 通过@Configuration定义该starter注册的bean

但引用该starter的项目要如何启用配置呢?其实有两种方式,分别为手动和自动,其中我们会着重讲解自动启用配置。

手动启用配置

所谓手动启用配置其实就是在SpringBoot入口类上添加启用配置用的自定义注解,针对上面的EsbServerConfiguration我们可以自定义EnableESBSrv注解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({EsbServerConfiguration.class})
public @interface EnableEsbSrv {
}

然后入口类的@SpringBootApplication注解前后添加@EnableEsbSrv即可。

让人省心省力的自动启用配置

自动启用配置即只需在pom.xml中引入所依赖的starter,然后启用应用即可自动启用该starter的@Configuration所注解的类从而注册Bean和读取属性配置。
而这一切都是由AutoConfigurationImportSelector来操刀,而我们可以通过@EnableAutoConfiguration@SpringBootApplication等实例化AutoConfigurationImportSelector类,配合菜谱resources/META-INF/spring.factories实现自动化配置的功能。
具体手法就是:将EsbServerConfiguration的全限类名称写在resources/META-INF/spring.factories的org.springframework.boot.autoconfigure.EnableAutoConfiguration下, 若存在多个则用逗号分隔。

org.springframework.boot.autoconfigure.EnableAutoConfiguration = \
com.john.starter.EsbServerConfiguration,\
com.john.starter.OtherConfiguration

好与更好——集成IDE智能提示

应用启动时会将application.yml中对应的配置项绑定到@ConfigurationProperties标注的类实例上,那么对于应用开发人员而言日常工作就是修改application.yml的配置项。但IDE又缺少配置项的智能提示,那就很低效了。幸亏Spring Boot早就为我们提供好解决方案,分为手工和自动两种。为了效率当然是可以自动就不用手动的了。

Starter项目的工作

  1. 引入spring-boot-configuration-processor依赖项;
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-configuration-processor</artifactId>
  <optional>true</optional>
</dependency>
  1. 若src/resources/META-INF/spring-configuration-metadata.json不存在,那么执行mvn compile时会生成target/classes/META-INF/spring-configuration-metadata.json;
  2. 复制target/classes/META-INF/spring-configuration-metadata.json到src/resources/META-INF/spring-configuration-metadata.json即可。

业务系统项目的工作

  1. 引入spring-boot-configuration-processor依赖项;
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-configuration-processor</artifactId>
  <optional>true</optional>
</dependency>
  1. IDEA安装Spring Assistant插件,并启用Enable annotation processing(勾选 Settings/Build, Execution & Deployment/Compiles/Annotation Processors/Enable annotation processing)。

总结

spring-boot-starter非常适合用于团队的技术积累和沉淀,不过想恰到好处地应用起来,不仅要需要深入Spring内部原理还要梳理清楚业务逻辑。后续我们再深入探讨Spring内核的事情吧!

转载请注明来自:https://www.cnblogs.com/fsjoh... —— ^_^肥仔John

查看原文

赞 0 收藏 0 评论 0

肥仔John 发布了文章 · 2020-11-06

Maven魔法堂:安装Oracle JDBC Driver依赖的那些坑

前言

由于Oracle并没有向公开Maven仓库提供任何Oracle JDBC Driver的Jar包,因此我们无法像MySQL、SQLite等那么轻松直接通过Maven加载依赖。
而手动下载Oracle JDBC Driver Jar包,然后安装到本地仓库(.m2目录),再通过Maven加载依赖则是常用手段。但此外我们还能通过<scope>system</scope>的方式引入,但其中的坑下面将细细道来。

手动安装到本地仓库

  1. 将ojdbc7-12.1.0.2.jar放置到项目根目录的lib目录下,随项目进行版本管理;
  2. 在POM.xml文件中加入依赖定义;
<dependency>
    <groupId>com.oracle</groupId>
    <artifactId>ojdbc7</artifactId>
    <version>12.1.0.2</version>
</dependency>
  1. 安装JAR包到本地仓库。
# 在项目根目录下执行
mvn install:install-file -Dfile=./lib/ojdbc7-12.1.0.2.jar -DgroupId=com.oracle -DartifactId=ojdbc7 -Dversion=12.1.0.2 -Dpackaging=jar

scope为system在SpringBoot中的坑

除上述方式外,我们可以在POM.xml将依赖定义为

<dependency>
    <groupId>com.oracle</groupId>
    <artifactId>ojdbc7</artifactId>
    <version>12.1.0.2</version>
    <scope>system</scope>
    <systemPath>${project.basedir}/lib/ojdbc7-12.1.0.2.jar</systemPath>
</dependency>

那么即使本地仓库(.m2目录)下没有Oracle JDBC Driver依赖,执行mvn compilemvn spring-boot:run依然成功执行。
请注意,执行mvn package打包出来的SpringBoot UberJar包中没有包含Oracle JDBC Driver依赖,那么直接部署到服务器上则报如下错误:

application failed to start

Description:
..................................
Reason: Failed to load driver class oracle.jdbc.driver.OracleDriver in either of HikariConfig class loader or Thread context classloader.

Action:
Update your application's configuration

解决方法:

  1. 对于外置Web容器的SpringBoot应用,则将Oracle JDBC Driver Jar包放置在容器的lib目录下;
  2. 对于内置Web容器的SpringBoot应用,则修改spring-boot-maven-plugin插件配置即可,具体请见下文。

说说Maven依赖定义中的scope属性

作用:用于限制依赖在Maven项目各生命周期的作用范围。

  • compile,默认值,依赖将参与编译阶段,并会被打包到最终发布包(如Jar、War、UberJar)内的Lib目录下。具有传递性,即该依赖项对于依赖当前项目的其它项目同样生效;
  • provided,依赖将参与编译阶段但不会被打包到最终的发布包,运行阶段由容器或JDK提供。不具备传递性。(如Servlet API,JSP API,Lombok等);
  • runtime,依赖不参与编译阶段(即不加入到classpath),但会打包到最终发布包,从而参与运行和测试阶段。通常用于根据配置文件动态加载或接口反射加载的依赖(如JDBC驱动);
  • test,依赖参加编译阶段,但不打包到最终发布包,依赖仅参与测试阶段;
  • system,表示该依赖项的路径为基于文件系统的Jar包路径,并且必须通过systemPath指定本地文件路径。依赖参与编译阶段,默认不会被打包到最终发布包。

    • 对于Spring Boot项目,若要将scope为system的Jar包打包到发布包,则需要配置spring-boot-maven-plugin

      <build>
          <plugins>
              <plugin>
                  <groupId>org.springframework.boot</groupId>
                  <artifactId>spring-boot-maven-plugin</artifactId>
                  <configuration>
                      <includeSystemScope>true</includeSystemScope>
                  </configuration>
              </plugin>
          </plugins>
      </build>
  • import,Maven2.0.9新增的scope值。仅能在<dependencyManagement>中使用,用于引用其它项目的依赖项。

    • 示例

      <!-- 引入项目io.fsjohnhuang.deps的依赖项 -->
      <dependencyManagement>
          <dependency>
              <groupId>io.fsjohnhuang</groupId>
              <artifactId>deps</artifactId>
              <type>pom</type>
              <scope>import</scope>
          </dependency>
      </dependencyManagement>
    • 关于type pom
      若项目拥有大量依赖项,那么会导致当前项目的POM.xml文件臃肿,即使采用父POM依然无法很好地解决该问题。而通过type为pom的项目则可以将依赖项分打包为独立项目,然后其它业务系统项目则可以通过引入这些项目导入相关依赖项,从而精简POM文件。
    • 关于type属性
      默认的type属性值为jar,即Maven将项目编译打包为Jar包,当设置为pom时则表示该项目为一堆相关依赖项的打包定义而已,当设置为apk或ejb等时则表示Maven在编译打包时采用andriod或ejb相关的插件执行任务。

总结

好记性不如烂笔头,Maven功能强大的背后自然也蕴藏着大量的知识点,记下来以便日后查阅!

转载请注明来自:https://www.cnblogs.com/fsjoh... —— ^_^肥仔John

查看原文

赞 0 收藏 0 评论 2

肥仔John 发布了文章 · 2020-11-02

TypeScript魔法堂:函数类型声明其实很复杂

前言

江湖有传“动态类型一时爽,代码重构火葬场”,由于动态类型语言在开发时不受数据类型的约束,因此非常适合在项目原型阶段和初期进行快速迭代开发使用,这意味着项目未来将通过重写而非重构的方式进入成熟阶段。而在企业级应用开发中,每个系统特性其实都是需求分析人员与用户进行多次调研后明确下来的,后期需要重写的可能性微乎其微,更多的是修修改改,在单元测试不足常态化的环境下静态类型的优势就尤为突出。而TypeScript的类型系统和编译时类型检查机制则非常适合用于构建企业级或不以重写实现迭代升级的应用系通。
本系列将重点分享TypeScript类型声明相关实践

  1. 函数类型声明其实很复杂
  2. 玩转交叉类型和联合类型
  3. class,inteface和type到底选哪个?
  4. 从lib.d.ts学习外部类型声明的最佳实践
  5. 类型声明综合实战

本文为该系列的首发,那么我们现在就开始吧!

定义即声明

当我们通过TypeScript定义函数时,实际上已经声明了函数签名和定义了函数体。

function foo(message: string, count?: number, displayLog = true): never {
    console[displayByLog ? 'log' : 'warn'](`message: ${message}; count: ${count}`)
    throw new Error('Just a error.')
}

上述函数定义附带声明了function foo(x: boolean, y: string, z: undefined | number): never函数签名,这里我特意替换参数名称以便大家将关注点放在函数参数列表类型和返回值类型上。
后续通过如下代码调用foo函数

foo('hi') // 回显 message: hi; count: undefined
foo('hi', 'yes') // 编译报错

函数重载

JavaScript中我们会通过函数重载来整合处理入参数据结构存在差异,但处理意图和处理结果相同的行为。具体实现方式有

function querySelector(x, parent) {
    var arg1 = typeof x === 'string' ? 0 : 1
    var arg2 = parent instanceof HTMLElement ? 0 : 1
    return (querySelector.overloads[arg1][arg2]).call(null, x, parent)
}
function q00 (x /*: string*/, p /*: HTMLElement*/) {
  return p.querySelector(x)
}
function q01 (x /*: string*/, p /*: JQuery*/) {
  return p.find(x)[0]
}
function q10 (x /*: JQuery*/, p /*: HTMLElement*/) {
  return $(p).find(x)[0]
}
function q11 (x /*: JQuery*/, p /*: JQuery*/) {
  return p.find(x)[0]
}

querySelector.overloads = [[q00,q01],[q10,q11]]

而TypeScript中的函数重载并没有让我们定义得更轻松,可以理解为在原JavaScript实现的基础上添加类型声明信息,这样反而让定义变得复杂,但为了能更安全地调用却是值得的。
写法1:

function querySelector(x: string, p: HTMLElement): HTMLElement
function querySelector(x: string, p: JQuery): HTMLElement
function querySelector(x: JQuery, p: HTMLElement): HTMLElement
function querySelector(x: JQuery, p: JQuery): HTMLElement
// 和JavaScript一样需要定义一个Dispatch函数,用于实现调用重载函数的具体规则
function querySelector(x, y) {
    var arg1 = typeof x === 'string' ? 0 : 1
    var arg2 = parent instanceof HTMLElement ? 0 : 1
    if (arg1 === 0 && arg2 === 0) {
        return p.querySelector(x)
    }
    else if (arg1 === 0 && arg2 === 1) {
        return p.find(x)[0]
    }
    else if (arg1 === 1 && arg2 === 0) {
        return $(p).find(x)[0]
    }
    else {
        return p.find(x)[0]
    }
}

写法2:

interface QuerySelector{
    (x: string, p: HTMLElement): HTMLElement
    (x: string, p: number): HTMLElement
    (x: number, p: HTMLElement): HTMLElement
    (x: number, p: number): HTMLElement
    overloads: Function[][]
}
// 和JavaScript一样需要定义一个Dispatch函数,用于实现调用重载函数的具体规则
let querySelector: QuerySelector= <QuerySelector>function (x: string | number, p: HTMLElement | number): HTMLElement { 
    let arg1 = typeof x === 'string' ? 0 : 1
    let arg2 = parent instanceof HTMLElement ? 0 : 1
    return (querySelector.overloads[arg1][arg2]).call(null, x, parent)
}
function q00 (x: string, p: HTMLElement):HTMLElement {
  return p.querySelector(x)
}
function q01 (x: string, p: JQuery):HTMLElement {
  return p.find(x)[0]
}
function q10 (x: JQuery, p: HTMLElement):HTMLElement {
  return p.find(x)[0]
}
function q11 (x: JQuery, p: JQuery):HTMLElement {
  return p.find(x)[0]
}
querySelector.overloads = [[q00, q01],[q10, q11]]

写法2注意事项:

  1. Dispatch函数必须采用<T>作为类型断言而不能使用as进行类型转;
  2. Dispatch函数必须通过function方式定义,而不能使用箭头函数方式定义。

如果想以箭头函数的方式定义Dispatch函数,那么写法就会更复杂了。

interface QuerySelector{
    (x: string, p: HTMLElement): HTMLElement
    (x: string, p: number): HTMLElement
    (x: number, p: HTMLElement): HTMLElement
    (x: number, p: number): HTMLElement
}
interface Overload {
    overloads: Function[][]
}
let querySelector: <QuerySelector & Overload>
let querySelectorDispatch:<QuerySelector> = (x: string | number, p: HTMLElement | number): HTMLElement => { 
    let arg1 = typeof x === 'string' ? 0 : 1
    let arg2 = parent instanceof HTMLElement ? 0 : 1
    return (querySelector.overloads[arg1][arg2]).call(null, x, parent)
}

function q00 (x: string, p: HTMLElement):HTMLElement {
  return p.querySelector(x)
}
function q01 (x: string, p: JQuery):HTMLElement {
  return p.find(x)[0]
}
function q10 (x: JQuery, p: HTMLElement):HTMLElement {
  return p.find(x)[0]
}
function q11 (x: JQuery, p: JQuery):HTMLElement {
  return p.find(x)[0]
}
querySelector = querySelectorDispatch as QuerySelector & Overload
querySelector.overloads = [[q00, q01],[q10, q11]]

累死人了。。。。。。。

高阶函数的类型声明

高阶函数作为JavaScript最为人称道的特性,在TypeScript中怎能缺席呢?

// 1
let foo1: (message: string, count?: number, displayLog?: boolean) => never

// 2
interface FooDecl {
  (message: string, count?: number, displayLog?: boolean): never
}
let foo2: FooDecl 

// 3
let foo3: {(message: string, count?: number, displayLog?: boolean): never}

// 4
type FooType = (message: string, count?: number, displayLog?: boolean) => never

上述为4种声明高阶函数类型的写法,其中第3种是第2种的简写形式。
1、2和3方式声明了变量的值类型,而2中的interface FooDecl和4中则声明类型本身。
foo1,foo2,foo3作为变量(value)可作为传递给函数的实参,和函数的返回值。因此针对它们的值类型声明是无法被重用的,也无法用于函数声明和其它类型声明中;
FooDecl,FooType作为类型声明,及可以被反复重用在各函数声明和其它类型声明中。

函数类型兼容

函数类型兼容的条件:

  1. 形参列表个数小于等于目标函数类型的形参列表个数;
  2. 形参列表中形参类型的顺序和目标函数类型的形参列表一致,或形参类型为目标函数类型相应位置的参数类型的子类型;
  3. 函数返回值必须为目标函数类型返回值的子类型。
const add: (x: number, y: number) => number = (x, y) => x + y
const increment(x: number) => number = x => x+1

add = increment // 类型兼容
increment = add // 类型不兼容

const handleEvent: (e: Event) => void;
const handleMouseEvent: (e: MouseEvent) => void;
   
handleEvent = handleMouseEvent // 类型兼容
handleMouseEvent = handleEvent // 类型不兼容

总结

函数类型声明难点在于函数重载这一块,而作为库开发者函数重载往往能帮助我们开发出更容易记忆使用和优雅的接口,既然逃不过那不如好好努力克服困难吧!

转载请注明来自:https://www.cnblogs.com/fsjoh... —— \^\_\^肥仔John

查看原文

赞 3 收藏 3 评论 0

肥仔John 发布了文章 · 2020-10-29

TypeScript魔法堂:枚举的超实用手册

前言

也许前端的同学会问JavaScript从诞生至今都没有枚举类型,我们不是都活得挺好的吗?为什么TypeScript需要引入枚举类型呢?
也许被迫写前端的后端同学会问,TypeScript的枚举类型是和Java/.NET的一样吗?
下面我们来一起探讨和尝试解答吧!

前端一直都需要枚举

我敢保证,前端的同学都会万分肯定地告诉大家:我们从来没有写过枚举。那是因为虽然ECMAScript将enum作为保留字,但至ES2020为止还没有提出枚举的实现规范。语言没有提供规范和语言实现,不代表思想活跃勇于造轮子的程序员们不会自己撸一个。
如果语言没有提供,还有那么毅然决然要自己造一个,那枚举到底能解决我们什么问题呢?

枚举真的有点用

首先,枚举字面上的意思就遍历一个存在若干个的值有穷集合的所有成员。核心有两点:

  1. 有穷集合;
  2. 遍历。

也就是说,只要我们需要表示某个变量的值必须为某个有穷集合的成员时,我们是怎么也绕不开枚举的。

写个JavaScript版本的枚举

下面是刚好满足大部分业务需求的枚举实现:

class Color {
    // tricky:自增枚举成员值
    static counter = null
    
    // 枚举成员
    static Red = new Color('Red')
    static Green = new Color('Green')
    
    // 反向映射
    static valueOf(value) {
        for (var name in Color) {
            if (!(name in Color.prototype) && Color[name].value === value) {
                return Color[name]
            }
        }
    }
        
    constructor(name, value){
                if ('counter' in Color);else return

        this.name = name
        if (value == null) {
            if (Color.counter === null) {
                this.value = Color.counter = 0
            }
            else {
                this.value = ++Color.counter
            }
        }
        else {
            this.value = Color.counter = value
        }
    }
    
    toString() {
        return `Color.${this.name}`
    }
}
delete Color.counter
Object.freeze(Color) // tricky:禁止在定义之外的位置修改枚举成员

其实我们只想表达某些变量将以含有Red、Green两个成员的Color有穷集合作为值域而已,却要写这么多语义无关的代码(严格遵循“能写hi绝对不写hello”原则)。而且在一般规模的项目当中,往往不止一个枚举类型,复制粘贴确实可以解决问题,但真心不优雅。
而TypeScript内置枚举的语言实现恰恰能解决这个问题。

TypeScript的枚举和后端的真不一样

后端的同学对枚举绝对是不会陌生的(除非是Pyton/Nodejs后端的同学啦),虽然TypeScript是JavaScript的超集,但最终需要编译为JavaScript代码,并且要兼容现有JavaScript库,所以确实无法和后端的枚举类型一模一样。
所以我还是建议大家运用空杯心理,重头理解TypeScript的枚举类型,将过去的知识作为助燃剂,而不是围栏更适宜。

数字枚举类型和字符串枚举类型

TypeScript官网教程已经对枚举类型进行了详细的讲解说明,我认为最核心是理解清楚其分为两大类:

  1. 数字枚举类型
enum Response {
    No = 0, // 手动设置初始化器
    Yes = // 附加默认的支持自动增长的初始化器,因此Yes的值为1
}

特性为:
1.1. 枚举成员附带默认的初始化器;
1.2. 初始化器支持自动增长;
1.3. 支持反向映射。(注意:这里是反向映射,而不是通过值转换为枚举成员)

  1. 字符串枚举类型
enum Color {
  Red = 'Red',
  Green = 'Green',
}

特性为:
1.1. 必须为枚举成员设置初始化器;
1.2. 初始化器不支持自动增长;
1.3. 不支持反向映射。

而计算和常量成员其实就是上述两种枚举类型中初始化器的细分特性罢了。

enum让数字枚举类型反向映射成为可能

上一节介绍到数字枚举类型支持反向映射,但前提是通过enum定义的数字枚举类型才支持。那是因为enum Respose {No,Yes,}最终会被编译为如下JavaScript代码:

var Response;
(function (Response) {
    Response[Response["No"] = 0] = "No";
    Response[Response["Yes"] = 1] = "Yes";
})(Response || (Response = {}));

那么我们就可以通过~Response[0]~反向映射得到~"No"~。
但对于字符串枚举类型就没有这种好事了,看看enum Color {Red='Red',Green='Green',}编译出来的代码吧

var Color;
(function (Color) {
    Color["Red"] = "Red";
    Color["Green"] = "Green";
})(Color || (Color = {}));

只能说非常朴实无华,就这样没啥好说的,大家在使用时注意差异就好了。

const enum高效的编译时内联

官方文档明确写出“大多数情况下,枚举是十分有效的方案。 然而在某些情况下需求很严格。 为了避免在额外生成的代码上的开销和额外的非直接的对枚举成员的访问,我们可以使用 const枚举”,那是为什么呢?
那是因为通过const enum定义的编译时枚举类型,效果和通过C/C++的#define定义常量没实质区别。说白了就是假如仅仅通过通过const enum定义了枚举类型而没有其它地方调用,这段代码将在编译时被直接抹掉。
当其它地方调用该枚举类型时,将直接把枚举类型成员的值内联到使用处,如下:

const enum Response {
    No,
    Yes,
}

console.log(Response.NO, Response.Yes)

编译后成为console.log(0, 1),运行效果自然只能比enum定义的好。

什么时候用enum?又在什么场景下用const enum呢?

先说说结论:

  1. 使用enum的场景:

1.1. 需要使用反向映射时;
1.2. 需要编译后的JavaScript代码保留对象.属性对象[属性]形式时。

  1. 使用const enum的场景:能不用enum时就用const enum(哈哈!)

使用enum的场景中的第一条还很好理解,但第二条是啥回事呢?我这里有个真实发生的示例,可以让大家更好的理解:
背景:为Photoshop的ExtendScript编写类型声明。
需求:DialogModes.NO在ExtendScript中返回值为DialogModes.No本身,编译后的JavaScript中必须保留DialogModes.NO的代码形式。

那么又为何鼓励大家能用const enum时就用const enum呢?
这是TypeScript为大家特意准备的编译时优化方式,好东西为啥不用呢?编译时优化难道不香吗?

外部枚举declare enum的作用?

所谓外部枚举,即使我们为了在TypeScript开发环境下,更好地使用某些已采用JavaScript编写的库,而被迫为其编写的枚举类型声明。
如ExtendScript标准库存在枚举DialogModes.NODialogModes.YESDialogModes.ALL。于是在.d.ts文件中编写如下外部枚举类型声明

declare enum DialogModes {
    NO,
    YES,
    ALL,
}

总结

对于日常开发中我们绕不过枚举类型,TypeScript为我们提供语言实现和编译时优化,除了保护了我们为如何优化实现枚举类型而日思夜想导致日渐稀疏的头发外,还大大降低了因复制粘贴带来的代码库体积徒增的风险。
写更少的代码,做正确的事,早点下班岂不更快哉^_^

转载请注明来自:https://www.cnblogs.com/fsjoh... —— ^_^肥仔John

查看原文

赞 4 收藏 3 评论 0

肥仔John 赞了文章 · 2018-10-13

Gin 框架的路由结构浅析

Gingo 语言的一款轻量级框架,风格简单朴素,支持中间件,动态路由等功能。gin项目github地址

路由是web框架的核心功能。在没有读过 gin 的代码之前,在我眼里的路由实现是这样的:根据路由里的 / 把路由切分成多个字符串数组,然后按照相同的前子数组把路由构造成树的结构;寻址时,先把请求的 url 按照 / 切分,然后遍历树进行寻址。

比如:定义了两个路由 /user/get/user/delete,则会构造出拥有三个节点的路由树,根节点是 user,两个子节点分别是 getdelete

上述是一种实现路由树的方式,且比较直观,容易理解。对 url 进行切分、比较,时间复杂度是 O(2n)

Gin的路由实现使用了类似前缀树的数据结构,只需遍历一遍字符串即可,时间复杂度为O(n)

当然,对于一次 http 请求来说,这点路由寻址优化可以忽略不计。

Engine

GinEngine 结构体内嵌了 RouterGroup 结构体,定义了 GETPOST 等路由注册方法。

Engine 中的 trees 字段定义了路由逻辑。treesmethodTrees 类型(其实就是 []methodTree),trees 是一个数组,不同请求方法的路由在不同的树(methodTree)中。

最后,methodTree 中的 root 字段(*node类型)是路由树的根节点。树的构造与寻址都是在 *node的方法中完成的。

UML 结构图
engine结构图

trees 是个数组,数组里会有不同请求方法的路由树。

tree结构

node

node 结构体定义如下

type node struct {
    path      string           // 当前节点相对路径(与祖先节点的 path 拼接可得到完整路径)
    indices   string           // 所以孩子节点的path[0]组成的字符串
    children  []*node          // 孩子节点
    handlers  HandlersChain    // 当前节点的处理函数(包括中间件)
    priority  uint32           // 当前节点及子孙节点的实际路由数量
    nType     nodeType         // 节点类型
    maxParams uint8            // 子孙节点的最大参数数量
    wildChild bool             // 孩子节点是否有通配符(wildcard)
}

path 和 indices

关于 pathindices,其实是使用了前缀树的逻辑。

举个栗子:
如果我们有两个路由,分别是 /index/inter,则根节点为 {path: "/in", indices: "dt"...},两个子节点为{path: "dex", indices: ""},{path: "ter", indices: ""}

handlers

handlers里存储了该节点对应路由下的所有处理函数,处理业务逻辑时是这样的:

func (c *Context) Next() {
    c.index++
    for s := int8(len(c.handlers)); c.index < s; c.index++ {
        c.handlers[c.index](c)
    }
}

一般来说,除了最后一个函数,前面的函数被称为中间件

如果某个节点的 handlers为空,则说明该节点对应的路由不存在。比如上面定义的根节点对应的路由 /in 是不存在的,它的 handlers就是[]

nType

Gin 中定义了四种节点类型:

const (
    static nodeType = iota // 普通节点,默认
    root       // 根节点
    param      // 参数路由,比如 /user/:id
    catchAll   // 匹配所有内容的路由,比如 /article/*key
)

paramcatchAll 使用的区别就是 :* 的区别。* 会把路由后面的所有内容赋值给参数 key;但 : 可以多次使用。
比如:/user/:id/:no 是合法的,但 /user/*id/:no 是非法的,因为 * 后面所有内容会赋值给参数 id

wildChild

如果孩子节点是通配符(*或者:),则该字段为 true

一个路由树的例子

定义路由如下:

r.GET("/", func(context *gin.Context) {})
r.GET("/index", func(context *gin.Context) {})
r.GET("/inter", func(context *gin.Context) {})
r.GET("/go", func(context *gin.Context) {})
r.GET("/game/:id/:k", func(context *gin.Context) {})

得到的路由树结构图为:
路有树

附一篇前缀树的文章,前缀树和后缀树


图片描述

查看原文

赞 18 收藏 14 评论 0

肥仔John 赞了文章 · 2018-10-13

Git 学习笔记

最近公司的代码管理工具要从SVN转到Git上,因此虽然之前用过Git,但是都是一些简单的推送提交,因此还是有必要进行一些系统的学习,这里做一下笔记,以备后询,且不定期更新。

关于SVN和Git的比较已经有很多文章说过了,就不再赘述,本文的重点是如何使用常用的Git命令进行操作,冷门的就不说了,且比较零散,系统的学习推介廖雪峰的Git教程

声明

  1. 下面用户名都为SHERlocked93,请自行修改成自己的用户名

1. 概览

  • 工作区 Workspace
  • 暂存区 Stage / Index
  • 本地仓库 Repository
  • 远程仓库 Remote

2. 修改

2.1 暂存修改

操作一览

操作bash
创建stashgit stash
查看git stash list
应用git stash apply stash@{<num>}
删除git stash drop stash@{<num>}
还原上一个暂存并删除暂存(如无conflict)git stash pop

如果在工作的时候出现了临时需要解决的问题,而你又不希望提交,那么有个stash功能

git stash

在暂存后工作区会回退到最近的一个commit的状态,以便开建新分支;比如我们修复bug时,我们会通过创建新的bug分支进行修复,然后合并,最后删除;

当手头工作没有完成时,先把工作现场git stash一下,然后去修复bug,修复后,再git stash pop,回到工作现场。

2.2 撤销修改

还未提交到暂存区

当修改还没有被add的时候,可以使用

git checkout -- filename.txt

来丢弃工作区某文件的修改,当然也可以把后面的文件改成*来撤销所有文件的修改。这是用仓库的文件覆盖工作区的文件。

注意这里用的是--,如果没有这个--的话就变成切换分支了。

还未提交到仓库

如果你的修改已经被add到了暂存区,但是还没有被commit,那么可以使用

git reset HEAD filename.txt
git checkout -- filename.txt

首先用reset来把修改撤回到工作区,再使用上面的checkout命令撤回工作区的修改。这里的reset相当于add的反操作。

已经提交到仓库

则可以版本回退

git reset --hard 15zdx2s

这里的--hard表示强制回退,丢弃本地的修改。这个回退比较野蛮,该版本号之后的提交都将不可见。

撤销之前某一个提交

git revert撤销一个提交的同时会创建一个新的提交,这是一个安全的方法,因为它不会重写提交历史。但实现上和reset是完全不同的。它撤销这个提交引入的更改,然后在最后加上一个撤销了更改的新提交,而不是从项目历史中移除这个提交。

git revert 46af7z6

相较于resetrevert不会改变项目历史,对那些已经发布到共享仓库的提交来说这是一个安全的操作。其次git revert 可以将提交历史中的任何一个提交撤销、而reset会把历史上某个提交及之后所有的提交都移除掉,这太野蛮了。

相比 reset,它不会改变现在的提交历史。因此,revert 可以用在公共分支上,reset 应该用在私有分支上。

合并commit

如果已经commit了怎么办,如果要撤回目前的commit,可以把它合并到上一个commit

git rebase -i HEAD~~

在出现的两个提交信息的pick改为fixup

3. 分支操作

3.1 创建/查看/合并分支

操作一览

操作bash
查看分支git branch
查看本地和远程分支git branch -a
在target分支上创建分支,没有则从当前分支git branch <branch-name><target-branch>
创建并切换分支git checkout -b <branch-name>
合并某分支到当前分支git merge <branch-name>
删除分支,只能删参与了合并的git branch -d <branch-name>
强行删除git branch -D <branch-name>
删除远程分支git push origin :<remote-branch-name>

创建分支

# 创建新分支
git branch bug-fix
# 查看分支,-a查看本地和远程的分支,-r查看远程分支,-l或没有只查看本地
git branch -a
# 切换到刚刚创建的分支
git checkout bug-fix

上面两个步骤可以合并为

# 创建并切换到分支
git checkout -b bug-fix

如果修改一下本地文件之后在这个分支继续培育一个版本之后,怎么去合并到主分支呢

git add *
git commit -m "some change"
# 切换到主分支
git checkout master
# 合并分支
git merge bug-fix
# 删除分支 (可选)
git branch -d bug-fix

如果master分支和新的分支都各自培育了版本,那么自动合并通常会失败,发生冲突conflict,此时需要打开文件解决冲突之后commit一个版本以完成合并

git add *
git commit -m "branch merge"

这里提一下,merge的时候有几个主要模式,--no-fffast-forward,其中fast-forward是默认的

  1. fast-forward:在master开始的新分支前进了几个版本之后如果需要merge回来,此时master并没有前进,那么这个模式就是把HEAD与master指针指向新分支上,完成合并。这种情况如果删除分支,则会丢失分支信息,因为在这个过程中并没有创建commit。
  2. --no-ff:关闭默认的fast-forward模式,也就是在merge的时候生成一个新的commit,这样在分支历史上就可以看出分支信息。

3.2 远程仓库操作

操作一览

操作bash
克隆git clone <url>
添加远程仓库git remote add <name><url>
删除远程仓库git remote rm <name>
拉取git pull <remote-branch-name><local-branch-name>
推送本地所有分支到远程git push --all origin
推送到远程同名分支git push origin <local-branch-name>
推送本地分支到远程mastergit push origin <local-branch-name>:master
把当前本地分支推送并创建到远程git push origin
检出远程分支git checkout -b <new-local-branch-name> origin/<remote-branch-name>

关于各个分支,哪些需要推送呢

  1. master分支是主分支,因此要时刻与远程同步;
  2. dev分支是开发分支,团队所有成员都需要在上面工作,所以也需要与远程同步;
  3. bug分支只用于在本地修复bug,就没必要推到远程了,除非老板要看看你每周到底修复了几个bug;
  4. feature分支是否推到远程,取决于你是否和你的小伙伴合作在上面开发。

直接clone

在github上创建一个新的项目之后,比如叫learn-git,那么可以直接clone下来,注意创建的时候不要选择 Initialize this repository with a README,我们要的是一个空的仓库

git clone https://github.com/SHERlocked93/learn-git.git

这样在本地就直接创建了一个空的文件夹learn-git,当然里面有.git文件夹。
也可以使用SSH地址来clone,速度会快一些,也不用每次推送都输入口令,推介使用这种

git clone git@github.com:SHERlocked93/learn-git.git

添加一个文件filename.txt之后

git add filename.txt
git commit -m "add filename.txt"
git push -u origin master

这样就把本地新建的文件push到了远程仓库

本地与远程建立关联

如果已经有了本地工程文件夹,如何分享到github远程仓库呢,当然此时我们已经在github上创建了一个新的空白项目,还是叫learn-git,在本地文件夹中

git init
# 关联远程库
git remote add origin git@github.com:SHERlocked93/learn-git.git
git push -u origin master

就可以了,如果你的远程仓库已经有了提交,那么在push之前需要

# 允许不想干库合并
git pull origin master --allow-unrelated-histories
git push -u origin master

先拉取远程分支,注意这里--allow-unrelated-histories允许两个不想干的分支强行合并,再push;这样在github的网站上还能看到commit记录。

也可以强硬一点直接强行推送

# -f 强行推送
git push -u origin master -f

这样本地仓库就直接把远程仓库覆盖了,且github上也看不到历史commit了,如果不想被同事枪击的话,还是推介上一种做法。

同步远程仓库

那么已经clone的仓库如果希望同步原仓库新的提交怎么办

# 从远程分支拉取代码到本地
git pull upstream master
# push到自己的库里
git push  origin master

3.3 多人协作

多人协作的工作模式通常是这样:

  1. 首先,可以试图用git push origin <branch-name>推送自己的修改;
  2. 如果推送失败,则因为远程分支比你的本地更新,需要先用git pull试图合并;
  3. 如果合并有冲突,则解决冲突,并在本地提交;
  4. 没有冲突或者解决掉冲突后,再用git push origin <branch-name>推送就能成功

从远程抓取分支,使用git pull,如果有冲突,要先处理冲突,add->commit->push。如果git pull提示no tracking information,则说明本地分支和远程分支的链接关系没有创建,用命令git branch --set-upstream-to <branch-name> origin/<branch-name>

4. 标签操作

操作一览

操作bash
查看所有标签git tag
新建标签git tag <tagname>
新建并制定说明git tag <tagname> -m <message><bash>
查看标签说明git show <tagname>
删除标签git tag -d <tagname>
推送某个标签到远程git push origin <tagname>
推送所有未推送到远程的本地标签git push origin --tags
合并远程仓库的标签到本地git pull origin --tags
删除远程标签git push origin :refs/tags/<tagname>

如果要删除远程分支,需要

# 首先删除本地tag,假如tag是v0.9
git tag -d v0.9
# 再从远程删除
git push origin :refs/tags/v0.9

5. 提交格式

type:

  • feat: 新特性,添加功能
  • fix: 修改bug
  • refactor: 代码重构
  • docs: 文档修改
  • style: 代码格式修改, 注意不是 css 修改
  • test: 测试用例修改
  • chore: 其他修改, 比如构建流程, 依赖管理.

网上的帖子大多深浅不一,甚至有些前后矛盾,在下的文章都是学习过程中的总结,如果发现错误,欢迎留言指出~

推介阅读:

  1. 廖雪峰 - Git教程
  2. github实现本地仓库与远程仓库同步
  3. 图解 Git 命令
  4. git基本操作,一篇文章就够了!
  5. 团队协作中的 Github flow 工作流程
  6. git 命令大全

附件

  1. Git常用命令速查表:

PS:欢迎大家关注我的公众号【前端下午茶】,一起加油吧~

另外可以加入「前端下午茶交流群」微信群,长按识别下面二维码即可加我好友,备注加群,我拉你入群~

查看原文

赞 34 收藏 29 评论 0

认证与成就

  • 获得 480 次点赞
  • 获得 8 枚徽章 获得 0 枚金徽章, 获得 2 枚银徽章, 获得 6 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

  • iPromise

    iPromise is a standalone async library which implements the Promises/A+. Async codes are confusing because you will dump into the callback hell easily. iPromise improves the readability of async codes by saving us from the hell.

注册于 2015-02-20
个人主页被 4.1k 人浏览