在我们的实际开发过程中,几乎所有的业务场景所产生的数据都需要一个唯一ID作为核心标识,用来流程化管理。比如常见的:
那么如何生成的唯一标识呢?网上大部分博客都会介绍UUID,SnowFlake算法、以及类雪花算法MongoDB ObjectId的生成算法。传送门
笔者的看法是,在考虑高效、全局唯一、安全等技术层面的同时,还应在不同使用场景中使用不同的唯一标识生成算法。
这种常被用在业务属性较强的场景,如订单编号order_no、支付流水号trade_no等。当然,此时生成无规律的唯一标识作为订单号或者流水号不是不可以,只是有规律的唯一标识可能会更好。
如下是一种生成订单编号算法的一个举例
DD前缀+8位用户标识+8位商品标识+2位购买次数 (位数不足要补足)
可以看出,这种生成方式生成的字串是有规律,拿到一个订单编号,就可知道多少位是用户标识,多少位是商品标识,不用查询订单相信信息即可获取两种关键对象标识,便于查问题和调试,同时后两位也标识了当前是指定用户多少次购买了。
同时该种设计,可以在数据库索引设计中使用前缀索引或者后缀索引,节省数据库索引空间和搜索效率。
这种常用于技术属性较强但需要限制长度的场景,如短信模板编号template_no、短链code等等,这种就是要求尽量短。
常用的方式
redis key inc --> 转换62进制编码
为什么是62进制?0-9,a-z,A-Z 刚好62位。其长度受限于位数限制,拿一个6位字符来说,其上限为62的5次方减一。
这种常用于技术属性较强场景,如版本号等。
相关的算法有上述说的 UUID,SnowFlake算法、以及类雪花算法MongoDB ObjectId的生成算法,此处不详述。
]]>不得不说ProcessOn是一个很好用的作图工具,但是免费的文件数仅9个,完全不够用,官方有两种方式对此进行升级
其中免费增加文件数量详情如下图所示:
下面来详细说说通过互助点赞的方式来增加文件数。
日期 | 描述 |
---|---|
2021-03-03 | 需手动发布公开,仅发布公开的文件才可得到点赞数 |
2021-02-03 | 引入公平机制,「可得点赞数」 约等于 「发起点赞数」,初始值 20 次 |
2021-02-02 | 取消暗黑模式、新增好友互赞模式 |
2021-01-31 | 创建分享文件夹和文件等步骤自动化、暗黑模式改为手动开启 |
安装步骤:
检测 Processon 登录状态
用户昵称 | 总文件数 | 已用文件数 | 可得点赞数 | 被点赞数 |
---|---|---|---|---|
步骤:
以上步骤已改为 全自动 创建文件,自动创建文件数20封顶
✰✰✰✰✰✰✰✰✰需要手动将文件「发布公开」,待审核通过后可获得点赞数✰✰✰✰✰✰✰✰✰
完成上诉步骤,将浏览器提留在该页面,文件数会自动增加
完成上诉步骤,将浏览器提留在该页面,文件数会自动增加
完成上诉步骤,将浏览器提留在该页面,文件数会自动增加
]]>最近做了一个订单15分钟过期变更状态机的功能,中间想过以定时任务来实现,考虑到定时任务存在一定的时间误差,于是在系统中引入了时间轮算法。
时间轮是一种环形的数据结构,类似于时钟,秒针、分针、时针分别为一层,每层分成多个格子,每个格子中存放任务集合,一个单独的线程推进时间一格一格的移动,并执行格子中的任务。它常用于延时任务,在Netty、akka、Quartz、Zookeeper等高性能组件中都存在时间轮定时器的踪影。
在Kafka中应用了大量的延迟操作,但它并没用使用JDK自带的Timer或是DelayQueue用于延迟操作,而是使用自己开发的DelayedOperationPurgatory组件用于管理延迟操作,
Kafka这类分布式框架有大量延迟操作并且对性能要求及高,而java.util.Timer与java.util.concurrent.DelayQueue的插入和删除复杂度都为对数阶O(log n)并不能满足Kafka性能要求,
所以Kafka实现了基于时间轮的定时任务组件,该时间轮定时任务实现的插入与删除(开始定时器与暂停定时器)的时间复杂度都为常数阶O(1)。
Timer是kafka中的定时器类,定义了共客户端调用的方法。SystemTimer是对Timer的具体实现。
关键代码:
1 | //添加任务 |
关键代码:
1 | //添加或获取上级时间轮 |
继承java Runnable接口
]]>最近身边的朋友讨论「布隆过滤器」的频次有点多,刚好自己也不太了解,于是乎研究一下。学习一个知识,首先了解该知识能带来的价值是什么,那么撇开算法本身,先来了解下这个东西的实际应用场景,大概的场景有以下几种:
本质上布隆过滤器是一种数据结构,比较巧妙的概率型数据结构,它的特点是高效地插入和查询,而根据查询结果可以判断某个对象 一定不存在或者可能存在。
相比于传统的List、Set、Map 等数据结构,它更高效、占用空间更少,但是缺点是得到结果是概率性的,而不是确切的,同时布隆过滤器还有一个缺陷就是 数据只能插入不能删除。
布隆过滤器是由一个很长的bit数组
和一系列哈希函数
组成的。数组的每个元素都只占1bit空间,并且每个元素只能为0或1。布隆过滤器还拥有k个哈希函数,当一个元素加入布隆过滤器时,会使用k个哈希函数对其进行k次计算,得到k个哈希值,并且根据得到的哈希值,在bit数组中把对应下标的值置位1。
判断某个数是否在布隆过滤器中,就对该元素进行k次哈希计算,得到的值在bit数组中判断对应位置的所有元素是否都为1,如素都为1,就说明这个值在布隆过滤器中。
当插入的元素越来越多时,当一个不在布隆过滤器中的元素,经过同样规则的哈希计算之后,得到的值在bit数组中查询,有可能这些位置因为其他的元素提前被置1了。因此布隆过滤器存在误判的情况,但是如果布隆过滤器判断某个元素不在布隆过滤器中,那么这个值就一定不在。
背景
现在有个100亿个黑名单网页数据,每个网页的URL占用64字节。现在想要实现一种网页过滤系统,可以根据网页的URL判断该网站是否在黑名单上,请设计该系统。
需求
可以允许有0.01%以下的判断失误率,并且使用的总空间不要超过200G。
分析一下,这里一共有4个常量:
100亿条黑名单数据
,每条数据占64个字节
,万分之一的失误率
,总空间不要超过200G
。
如果不考虑布隆过滤器,那么这里存储100亿条数据就需要 $ 100亿 \times 64字节 = 596G $ 显然超过300G。
如果使用布隆过滤器,在满足有100亿条数据 并且允许万分之一的失误率的布隆过滤器需要多大的bit数组呢?
下面来分析下使用布隆过滤器的情况(设bit数组的大小为m,样本数量为n,失误率为p):
由题可知 $ n=100亿,n=0.01% $,根据布隆过滤器大小m的公式:
求得 m = 19.19n,向上取整为 20n,2000亿字节,约为186G,满足条件。
算完m,我们顺便来算下m,n已知,这时满足最小误差的k是几个。根据哈希函数个数k的公式:
求得 k = 14,即需要14个哈希函数。
通过通过 m = 20n, k = 14我们再来算下真实的失误率。根据布隆过滤器真实失误率p公式:
求得 p = 0.006%,即布隆过滤器的真实失误率为0.006%。
通过布隆过滤器公式也可以看出:
单个数据的大小不影响布隆过滤器大小,因为样本会通过哈希函数得到输出值
就好比上面的每个网页的URL占用64字节
这个数据大小跟布隆过滤器大小没啥关系。
这三个公式就是有关布隆过滤器已经推倒出的公式,下面我们来看看这个公式是如何推导出来的。
讲公式,应该先知道几个关键的常量:误判率p
、布隆过滤器长度m
、元素个数n
、哈希函数个数k
前提条件
:假设每个元素哈希得到的值分布到m数组上的每一个数组节点的概率是相等的。
假设布隆过滤器长度为m,元素个数n为1,哈希函数个数k也为1。那么在插入时某一数组节点没有被置为1的概率。
如果上面其它不变,而哈希函数个数变成k个,那么在插入时某一数组节点没有被置为1的概率。
如果元素个数变成n个,而哈希函数个数变成k个,那么在插入时某一数组节点没有被置为1的概率。
从上面推导出的是: 当布隆过滤器长度为m,元素个数变成n个,哈希函数个数变成k个的时候,某一节点被置为1的概率为
这个还需要考虑到,一个元素通过hash会生成多个k,放入m数组中,所以需要这k个值都为1才会认为该该元素已经存在,转换如下。
=>
思考
为什么上面这个公式的值就是最终的误差率?
因为当一个布隆过滤器中不存在的元素进来的是的时候,首先通过hash算法产生k个哈希值,分布在m数组上都为1的的概率不就是上面推导出的这个公式吗,那不就是误差吗?明明是不存在的值,却有这个概率表明已经存在。
思考
给定的m和n,思考k值为多少误差会最小。
为什么k值的大小不合理会影响误差呢?
我们来思考下,一个元素最终生成k个hash值,那么会在数组m上的k个位置标记为1。
假设k为1,那么每次进来只在m上的某一个位置标记为1,这样的话如果一个新元素进来刚好hash值也在这里,而不用其它位置来判断是否为1,这个误差就会比较大。
假设k为m,那么第一个元素进来,在m上所有位置上都表为1了 ,以后只要进来一个元素就会标记为已存在。这个误差也太大了。
上面只是举了两个极端的例子,但也说明k值太大、太小都不好,它的最优值一定跟m、n存在某种关系。
它们之间的关系只要记住下面这个公式就可以了。
最近开始在做算法题,不全是为了找工作,为了面试,旨在不断思考,锻炼思维,同时查阅别人的解题方法,开拓自己的思路。在遇到实际问题时,可以有料参考,有迹可循。
这个问题是以弗拉维奥·约瑟夫命名的,他是1世纪的一名犹太历史学家。他在自己的日记中写道,他和他的40个战友被罗马军队包围在洞中。
他们讨论是自杀还是被俘,最终决定自杀,并以抽签的方式决定谁杀掉谁。约瑟夫斯和另外一个人是最后两个留下的人。约瑟夫斯说服了那个人,他们将向罗马军队投降,不再自杀。
约瑟夫斯把他的存活归因于运气或天意,他不知道是哪一个。 —— 【约瑟夫问题】维基百科
解题秘诀:只关心最终活着那个人的序号变化
我们也给每个人一个编号(索引值),单个人用字母代替,下面这个例子是N=8 m=3的例子
定义F(n,m)表示最后剩下那个人的索引号,因此只关心最后剩下来这个人的索引号的变化情况即可
从8个人开始,每次杀掉一个人,去掉被杀的人,然后把杀掉那个人之后的第一个人作为开头重新编号
从N = 7 到N = 8 的过程
如何才能将N = 7 的排列变回到N = 8 呢?
我们先把被杀掉的C补充回来,然后右移m个人,发现溢出了,再把溢出的补充在最前面
神奇了 经过这个操作就恢复了N = 8 的排列了!
因此我们可以推出递推公式 $ f(8,3) = [f(7, 3) + 3] \% 8 $
进行推广泛化,即 $ f(n,m) = [f(n-1, m) + m] \% n $
再把n=1这个最初的情况加上,就得到递推公式
照着递推公式写即可,也可写成递归的形式
1 | class Solution { |
可以的,Android手机在常用的Chrome、UC、360、QQ等浏览器上都可以很方便的使用本站。推荐使用Chrome浏览器获得最佳下载体验。
因Safari及微信内置浏览器均不支持下载文件,ios用户建议使用小程序”今天去水印”。也可以在App Store下载免费的Documents 6,在Documents的内置浏览器中使用本站,Documents支持将下载的视频移到手机相册。
电脑上部分浏览器不支持直接下载,请点击”复制视频链接”按钮,在浏览器中打开复制的链接,在视频画面上点击右键,选择”视频另存为”或”存储为”来下载视频。推荐使用谷歌Chrome浏览器、360浏览器、QQ浏览器等主流浏览器。
暂无。
]]>之前介绍了git多账号ssh-key管理 中涉及到 user.name 和 user.email 按照项目来管理
1 | $ git config --local user.name "你的名字" |
1 |
|
同理可以写出如下脚本:
1 |
|
日常使用 git 作为仓库使用时,会遇到以下情况:
新安装 git 跳过。
若之前对 git 设置过全局的 user.name 和 user.email。
类似(用git config —global —list 进行查看你是否设置)
1 | $ git config --global user.name "你的名字" |
必须删除该设置
1 | $ git config --global --unset user.name "你的名字" |
1 | $ git config --local user.name "你的名字" |
(1)#GitHub的钥匙
1 | ssh-keygen -t rsa -C "xxx@xx.com" |
1 | ssh-keygen -t rsa -C "xxx@xx.com" |
(3)完成后会在~/.ssh/目录下生成以下文件:
1 | github_id_rsa |
默认只读取 id_rsa,为了让 SSH 识别新的私钥,需要将新的私钥加入到 SSH agent 中
1 | $ ssh-agent bash |
若无 config 文件,则需创建 config 文件
1 | $ touch ~/.ssh/config # 创建config文件 |
1 | #Default gitHub user Self |
1 | # 测试github |
1 |
|
本 maven settings.xml相关配置是在做smnpo微服务相关组件时进行配置,包含nexus3 maven 私有仓库的搭建以及账号密码权限相关配置,具体可参考xml配置的详细信息。
日期 | 更新内容 |
---|---|
20190507 | 新增pom.xml中添加repository不生效的解决方案 |
20190509 | 将私有maven库改用到阿里云效并更新完整settings.xml |
今天抽空看了一下centos的cpu和mem使用情况,发现自建maven仓库造成内存消耗严重,于是将私有仓库切换到阿里云效
1 | <repositories> |
项目pom.xml中的repository配置不生效的问题解析
1 | <mirror> |
1 | * = everything |
完整settings.xml如下:
1 |
|
ORM框架的本质就是简化编程中操作数据库的编码,发展到现在基本上就剩两家了,一个是宣称可以不用写一句SQL的hibernate,一个是可以灵活调试动态sql的mybatis,两者各有特点,在企业级系统开发中可以根据需求灵活使用。发现一个有趣的现象:传统企业大都喜欢使用hibernate,互联网行业通常使用mybatis。
Hibernate特点就是所有的sql都用Java代码来生成,不用跳出程序去写(看)sql,有着编程的完整性,发展到最顶端就是spring data jpa这种模式了,基本上根据方法名就可以生成对应的sql了。
Mybatis初期使用比较麻烦,需要各种配置文件、实体类、dao层映射关联、还有一大堆其他配置。当然mybatis也发现了这种弊端,初期开发了generator可以根据表结果自动生成实体类、配置文件和dao层代码,可以减轻一部分开发量;后期也进行了大量的优化可以使用注解,自动管理dao层和配置文件等,发展到最顶端就是今天讲的这种模式,mybatis-spring-boot-starter就是springboot+mybatis可以完全注解不用配置文件,也可以简单配置轻松上手。
官方说明:MyBatis Spring-Boot-Starter will help you use MyBatis with Spring Boot
其实就是MyBatis看spring boot这么火热也开发出一套解决方案来凑凑热闹,但这一凑却是解决了很多问题,使用起来也却是顺畅了很多。mybatis-spring-boot-starter主要有两种解决方案,一种是使用注解解决一切问题,一种是简化后的老传统。
当然,任何模式都需要在pom文件中首先引入mybatis-spring-boot-starter的依赖。
1 | <dependency> |
就是一切使用注解搞定。
1 | <dependencies> |
1 | mybatis.type-aliases-package=com.example.springbootmybatis.entity |
spring boot 会自动加载spring.datasource.*相关配置,数据源就会自动注入到sqlSessionFactory中,sqlSessionFactory会自动注入到Mapper中,对了你一切都不用管了,直接拿起来用就可以了。
在启动类中添加对Mapper包扫描@MapperScan
1 |
|
@Mapper
,建议使用上面这种,不然每个Mapper都加个注解也挺麻烦的。第三步是关键的一步,所有的sql生成都在这里
1 | public interface UserMapper { |
- @Select 是查询类的注解,所有的查询均使用这个;
- @Result 修饰返回的结果集,关联实体类属性和数据库字段一一对应,如果实体类属性和数据库属性名保持一致,就不需要这个属性来修饰;
- @Insert 插入数据库使用,直接传入实体类会自动解析属性到对应的值;
- @Update 负责修改,也可以直接传入对象
- @Delete 负责删除
了解更多属性参考这里注意,使用#符号和$符号的不同
1
2
3
4
5
6
7 // This example creates a prepared statement, something like select * from teacher where name = ?;
"Select * from teacher where name = #{name}") (
Teacher selectTeachForGivenName(@Param("name") String name);
// This example creates n inlined statement, something like select * from teacher where name = 'someName';
"Select * from teacher where name = '${name}'") (
Teacher selectTeachForGivenName(@Param("name") String name);
上面三步就基本完成了相关的dao层开发,使用的时候当作普通的类注入就可以了
1 | .class) (SpringRunner |
极简xml版本保持映射文件的老传统,优点主要体现在不需要实现dao的是实现层,系统会自动根据方法名在映射文件中找到对应的sql。
pom文件和上面第一种方式一致,只是application.properties新增以下配置
1 | mybatis.config-location=classpath:mybatis/mybatis-config.xml |
mybatis-config.xml配置
1 |
|
1 |
|
其实就是把上个版本中Mapper的sql搬到了这里的xml中了
1 | public interface UserMapper { |
对比上一种方式,这里只剩接口方法
使用方式和第一种方式没什么区别,可以参考第一种代码实现。
两种模式各有特点,注解版适合简单快速的模式,其实像现在流行的微服务模式,一个微服务就会对应一个自己的数据库,多表连接查询的需求会大大的降低,会越来越适合这种模式了。
老传统模式即xml配置文件的方式,更适合大型项目,可以灵活的动态生成SQL,方便调整SQL,也有痛痛快快,洋洋洒洒的写SQL的感觉。
]]>JPA(Java Persistence API) 是Sun官方提出的Java持久化规范。它为Java开发人员提供了一种对象/关联映射工具来管理Java应用中的关系数据。它的出现主要是为了简化现有的持久化开发工作和整合ORM技术,结束现在Hibernate、TopLink、JDO等ORM框架各自为营的局面。值得注意的是,JPA是在充分吸收了现有Hibernate、TopLink、JDO等ORM框架的长处发展而来,具有易于使用,伸缩性强等特点。从目前的开发社区的反应上看,JPA受到了极大的支持和赞扬,其中就包括了Spring和EJB3.0的开发团队。
JPA是一套规范,不是一套产品,而Hibernate、TopLink、JDO等是一套产品。
如果说这些产品实现了JPA规范,那么我们就可以叫它们为JPA的实现产品。
Spring Data JPA 是 Spring 基于ORM框架、JPA规范的基础封装的一套JPA应用框架,可使开发者用极简的代码即可实现对数据的访问和操作。它提供了包括增删改查等在内的常用功能,且易于扩展!学习并使用Spring Data JPA可以极大提高开发效率!
Spring Data JPA让我们解脱了DAO层的操作,基本上所有的CRUD都可以依赖于它来实现。
基本查询也分两种,一种是spring data jpa默认实现,一种是根据查询的方法名来自动解析成SQL。
spring data jpa 默认预先生成了一些基本的CURD的方法,例如:增、删、改等等
继承JpaRepository
1 | public class UserRepository extends JpaRepository<User, Long> { |
使用默认方法, 顾名思义
1 |
|
自定义的简单查询就是根据方法名来自动生成SQL,主要的语法是findXXBy
,readXXBy
,queryXXBy
,countXXBy
,getXXBy
后面跟属性名称。
1 | User findByUserName(String userName); |
And
、Or
1 | User findByUserNameOrEmail(String userName, String email); |
1 | void deleteById(Long id); |
Like
、IgnoreCase
、OrderBy
1 | List<User> findByEmailLike(String email); |
Keyword | Sample | JPQL snippet |
---|---|---|
And | findByLastNameAndFirstName | … where x.lastname = ?1 and x.firstname = ?2 |
Or | findByLastNameOrFirstName | … where x.lastname = ?1 or x.firstname = ?2 |
Is Equals | findByFirstNameIs findByFirstNameEquals | … where x.firstname = ?1 |
Between | findByStartDateBetween | … where x.startdate between ?1 and ?2 |
LessThan | findByAgeLessThan | … where x.age < ?1 |
LessThanEqual | findByAgeLessThanEqual | … where x.age <= ?1 |
GreaterThan | findByAgeGreaterThan | … where x.age > ?1 |
GreaterThanEqual | findByAgeGreaterThanEqual | … where x.age >= ?1 |
After | findByStartDateAfter | … where x.startdate > ?1 |
Before | findByStartDateBefore | … where x.startdate < ?1 |
IsNull | findByAgeIsNull | … where x.age is null |
IsNotNull NotNull | findByAge(Is)NotNull | … where x.age not null |
Like | findByFirstNameLike | … where x.firstname like ?1 |
NotLike | findByFirstNameNotLike | … where x.firstName not like ?1 |
StartingWith | findByFirstNameStartingWith | … where x.firstname like ?1(parameter bound with appended %) |
EndingWith | findByFirstNameEndingWith | … where x.firstname like ?1(parameter bound with prepend %) |
Containing | findByFirstNameContaining | … where x.firstname like ?1(parameter bound wrapped in %) |
OrderBy | findByAgeOrderByLastNameDesc | … where x.age = ?1 order by x.lastname desc |
Not | findByLastNameNot | … where x.lastname <> ?1 |
In | findAgeIn(Collection ages) | … where x.age in ?1 |
NotIn | findAgeNotIn(Collection ages) | … where x.age not in ?1 |
True | findByActiveTrue | … where x.active = true |
False | findByActiveFalse | … where x.active = false |
IgnoreCase | findByFirstNameIgnoreCase | … where UPPER(x.firstname) = UPPER(?1) |
在实际开发中我们需要用到分页、删选、连表等查询的时候就需要特殊的方法或者自定义SQL
分页查询在实际开发中已经非常普遍了,spring data jpa 已经帮我们实现了分页的功能,在查询方法中,需要传入参数Pageable
,当查询中有多个参数时,Pageable
建议做为最后一个参数传入
1 | Page<User> findAll(Pageable pageable); |
Pageable
是spring封装的分页实现类,使用的使用需要传入页码,每页条数和排序规则1 |
|
1 | User findFirstByOrderByLastnameAsc(); |
其实spring data jpa绝大部分的SQL都可以根据方法名定义的方式来实现,但是由于某些原因我们想使用自定义的SQL来查询,spring data jpa也是完美支持的;在SQL的查询方法上面使用@Query
注解,如涉及到删除和修改则需要加上@Modifying
,也可以根据需要添加 @Transactional
对事务的支持,查询超时的设置等
1 |
|
多表查询在spring data jpa中有两种实现方式,第一种是利用hibernate的级联查询来实现,第二种是创建一个结果集接口来接收查询后的结果,这里主要是第二种方式。
首先需要定义一个结果集的接口类。
1 | public interface HotelSummary { |
1 | "select h.city as city, h.name as name, avg(r.rating) as averageRating " ( |
1 | Page<HotemlSummary> hotels = hotelRepository.findByCity(new PageRequest(0, 10, Direction.ASC, "name")); |
在运行中Spring会给接口(HotelSummary)自动生成一个代理类来接收返回结果,代码汇总使用
getXX()
的形式来获取
日常项目中因为使用的是分布式开发模式,不同的服务有不同的数据源,常常需要在一个项目中使用多个数据源,因此需要配置spring data jpa 以适合对多数据源的使用,一般分以下三步:
比如我们的项目中,即需要对mysql的支持,也需要对mongodb的支持等。
实体类声明@Entity
关系型数据库支持类型、声明@Document
为mongodb支持类型,不同的数据源使用不同的实体就可以了
1 | interface PersonRepository extends JpaRepository<Person, Long> { |
1 | interface JpaPersonRepository extends JpaRepository<Person, Long> { |
1 | "com.example.repositories.jpa") (basePackages = |
使用枚举的时候,我们希望数据库中存储的是枚举对应的String类型,而不是枚举的索引值,需要在属性上面添加@Enumerated(EnumType.STRING)
注解
1 | (EnumType.STRING) |
正常情况下,我们在实体上加上注解@Entity
,就会让实体类的属性和表的列相关联,如果其中某个属性不需要和数据库来进行关联,而只是展示的时候做计算,只需要加上@Transient
属性即可。
1 |
|
Thymeleaf是新一代的模板引擎,在Spring 4.0中推荐使用Thymeleaf来做前端模板引擎。
之前在springBoot实战(二)Web中简单介绍了下thymeleaf,本篇将更加全面详细的介绍thymeleaf的使用。
简单说,Thymeleaf是一个跟Velocity、FreeMarker类似的模板引擎,它可以完全替代JSP。相比较其他模板引擎,它有如下三个极其吸引人的特点:
它们分为四类:
变量表达式即OGNL表达式或Spring EL表达式(在Spring 术语中也叫做model attributes)。如下所示:
1 | ${session.user.name} |
1 | <li th:each="book : ${books}"> |
选择表达式很像变量表达式,不过它们用一个预先选择的对象来代替上下文变量容器(map)来执行,如下:
1 | *{customer.name} |
th:object
属性定义:1 | <div th:object="${book}"> |
文字国际化表达式允许我们从一个外部文件获取区域文字信息(.properties),用Key索引Value,还可以提供一组参数(可选)。
1 | #{main.title} |
1 | <table> |
URL表达式值指的是把一个有用的上下文或会话信息添加到URL,这个过程经常被叫做URL重写。
1 | @{/order/list} |
1 | @{/order/details(id=${orderId})} |
1 | @{../documents/report} |
1 | <form th:action="@{/createOrder}">...</form> |
如果不考虑上下文的情况下,两者没有区别;星号语法在选定对象上表达,而不是整个上下文。
什么是选定对象?就是父标签的值,如下:
1 | <div th:object="${session.user}"> |
1 | <div> |
1 | <div th:object="${session.user}"> |
'one text', 'Another one!',…
0, 34, 3.0, 12.3,…
true, false
null
one, sometext, main,…
+
|The name is ${name}|
+, -, *, /, %
-
and, or
!, not
>, <, >=, <= (gt, lt, ge, le)
==, != (eq, ne)
(if) ? (then)
(if) ? (then) : (else)
(value) ?: (defaultvalue)
所有这些特征可以被组合并嵌套:
1 | 'User is of type ' + (${user.isAdmin()} ? 'Administrator' : (${user.type} ?: 'Unknown')) |
关键字 | 功能介绍 | 案例 |
---|---|---|
th:id | 替换id | <input th:id="'xxx' + ${collect.id}"/> |
th:text | 文本替换 | <p th:text="${collect.description}">description</p> |
th:utext | 支持html的文本替换 | <p th:utext="${htmlcontent}">conten</p> |
th:object | 替换对象 | <div th:object="${session.user}"> |
th:value | 属性赋值 | <input th:value="${user.name}" /> |
th:with | 变量赋值运算 | <div th:with="isEven=${prodStat.count}%2==0"></div> |
th:style | 设置样式 | th:style="'display:' + @{(${sitrue} ? 'none' : 'inline-block')} + ''" |
th:onclick | 点击事件 | th:onclick="'getCollect()'" |
th:each | 属性赋值 | <tr th:each="user,userStat:${users}"> |
th:if | 判断条件 | <a th:if="${userId == collect.userId}" > |
th:unless | 和th:if判断相反 | <a th:href="@{/login}" th:unless=${session.user != null}>Login</a> |
th:href | 链接地址 | <a th:href="@{/login}" th:unless=${session.user != null}>Login</a> /> |
th:switch | 多路选择 配合th:case 使用 | <div th:switch="${user.role}"> |
th:case | th:switch的一个分支 | <p th:case="'admin'">User is an administrator</p> |
th:fragment | 布局标签,定义一个代码片段,方便其它地方引用 | <div th:fragment="alert"> |
th:include | 布局标签,替换内容到引入的文件 | <head th:include="layout :: htmlhead" th:with="title='xx'"></head> /> |
th:replace | 布局标签,替换整个标签到引入的文件 | <div th:replace="fragments/header :: title"></div> |
th:selected | selected选择框 选中 | th:selected="(${xxx.id} == ${configObj.dd})" |
th:src | 图片类地址引入 | <img class="img-responsive" alt="App Logo" th:src="@{/img/logo.png}" /> |
th:inline | 定义js脚本可以使用变量 | <script type="text/javascript" th:inline="javascript"> |
th:action | 表单提交的地址 | <form action="subscribe.html" th:action="@{/subscribe}"> |
th:remove | 删除某个属性 | <tr th:remove="all"> 1.all:删除包含标签和所有的孩子。2.body:不包含标记删除,但删除其所有的孩子。3.tag:包含标记的删除,但不删除它的孩子。4.all-but-first:删除所有包含标签的孩子,除了第一个。5.none:什么也不做。这个值是有用的动态评估。 |
th:attr | 设置标签属性,多个属性可以用逗号分隔 | 比如 th:attr="src=@{/image/aa.jpg},title=#{logo}" ,此标签不太优雅,一般用的比较少。 |
还有非常多的标签,这里只列出最常用的几个,由于一个标签内可以包含多个th:x属性,其生效的优先级顺序为:
1 | include,each,if/unless/switch/case,with,attr/attrprepend/attrappend,value/href,src ,etc,text/utext,fragment,remove |
1 | <p th:text="${collect.description}">description</p> |
字符串拼接还有另外一种简洁的写法
1 | <span th:text="|Welcome to our application, ${user.name}!|"> |
Thymeleaf中使用th:if
和th:unless
属性进行条件判断,下面的例子中,<a>
标签只有在th:if
中条件成立时才显示:
1 | <a th:if="${myself=='yes'}" >test</a> |
th:unless
与th:if
恰好相反,只有表达式中的条件不成立,才会显示其内容。(if) ? (then) : (else)
这种语法来判断显示的内容1 | <tr th:each="collect,iterStat : ${collects}"> |
iterStat称作状态变量,属性有:
URL在Web应用模板中占据着十分重要的地位,需要特别注意的是Thymeleaf对于URL的处理是通过语法@{…}来处理的。
如果需要Thymeleaf对URL进行渲染,那么务必使用th:href
,th:src
等属性,下面是一个例子
1 | <!-- Will produce 'http://localhost:8080/standard/unread' (plus rewriting) --> |
1 | <div th:style="'background:url(' + @{/<path-to-image>} + ');'"></div> |
1 | <div class="media-object resource-card-image" |
(orderId=${o.id})
表示将括号内的内容作为URL参数处理,该语法避免使用字符串拼接,大大提高了可读性@{...}
表达式中可以通过{orderId}
访问Context中的orderId变量@{/order}
是Context相关的相对路径,在渲染时会自动添加上当前Web应用的Context名字,假设context名字为app,那么结果应该是/app/order内联文本:[[...]]
内联文本的表示方式,使用时,必须先用th:inline="text/javascript/none"
激活,th:inline
可以在父级标签内使用,甚至作为body的标签。内联文本尽管比th:text
的代码少,不利于原型显示。
1 | <script th:inline="javascript"> |
1 | /*[+ |
1 | /*[- */ |
为了模板更加易用,Thymeleaf还提供了一系列Utility对象(内置于Context中),可以通过#直接访问:
dates
1 | /* |
strings
1 | /* |
使用thymeleaf布局非常的方便
定义代码片段
1 | <footer th:fragment="copy"> |
1 | <body> |
th:include
和 th:replace
区别,include只是加载,replace是替换返回的HTML如下:
1 | <body> |
1 | <body class="layout-fixed"> |
1 | <html xmlns:th="http://www.thymeleaf.org" layout:decorator="layout"> |
1 | <head th:include="layout :: htmlhead" th:with="title='Hello'"></head> |
fileName/layout:htmlhead
htmlhead 是指定义的代码片段 如 th:fragment="copy"
Spring Boot 对常用的数据库支持外,对nosql数据库也进行了封装自动化,
Redis是目前业界使用最广泛的内存数据存储。相比Memcached,Redis支持更丰富的数据结构,例如hashes,lists,sets等,同时支持数据的持久化。除此之外,Redis还提供一些类数据库的特性,比如事务,HA,主从库等。可以说Redis兼具了缓存系统和数据库的一些特性,因此有着丰富的应用场景。本文介绍Redis在Spring Boot中两个典型的应用场景。
引入spring-boot-starter-data-redis
1 | <dependency> |
添加redis配置
1 | #Redis数据库索引(默认为0) |
添加cache的配置类
1 |
|
好了,接下来我们就可以直接使用了
1 | .class) (SpringJUnit4ClassRunner |
以上都是手动使用的方式,如何在查找数据库的时候自动使用缓存呢,看下面:
自动根据方法生成缓存
1 |
|
其中value的值就是就是缓存到redis中的key。
spring boot 整合 spring-session-data-redis
分布式系统中,session共享有很多的解决方案,其中托管到缓存中应该是最常用的方案之一。
Spring Session provides an API and implementations for managing a user’s session information.
引入依赖
1 | <!--当@EnableRedisHttpSession注解找不到时引入--> |
Session配置
1 |
|
maxInactiveIntervalInSeconds: 设置Session失效时间。
使用Redis Session之后,原Boot的server.session.timeout属性不再生效
好了,这样就配置完成了,让我们来测试下
1 | "/uid") ( |
keys '*session*'
进行查询1 | 127.0.0.1:6379> keys '*session*' |
21099c0b-ae12-4ebc-ac8f-7d2f9baf5cdf
为sessionId,登录http://localhost:8080/uid 发现会一致,就说明session 已经在redis里面进行有效的管理了。其实就是按照上面的步骤在另一个项目中再次配置一次,启动后就自动进行了session共享。
]]>Spring Boot Web开发非常的简单,其中包括常用的json输出、filters、property、log等。
在以前的spring开发的时候需要我们提供json接口的时候需要做以下配置:
就这样我们会经常由于配置问题,导致406错误等等,那么spring boot是如何做的呢,只需要给Controller类添加@RestController
注解即可,那么默认类中的方法都会以json的格式返回。
1 |
|
@Controller
,下面会结合模板来说明。我们常常在项目中会使用filters用于记录调用日志、排除有XSS威胁的字符、执行权限验证等等。Spring Boot自动添加了OrderedCharacterEncodingFilter和HiddenHttpMethodFilter,并且我们可以自定义Filter。
两个步骤:
@Configuration
注解,将自定义的Filter加入过滤链;ok,直接上代码
WebConfiguration.java
1 |
|
MyFilter.java
1 | public class MyFilter implements Filter { |
在Web开发中,我们经常需要自定义一些配置文件,如何使用呢?
配置在application.properties中
1 | com.example.title=SnailDev's Blog |
自定义配置类
1 |
|
log配置
配置输出的地址和输出的级别
1 | logging.path=/user/local/log |
在这里我们重点来看一下mysql、spring data jpa的使用,其中mysql就不用说了,大家应该都很熟悉,jpa是利用Hibernate生成各种自动化的sql,如果只是简单的增删改查,基本上不用手写了,spring 内部已经帮我们封装实现了。
下面简单介绍一下如何在spring boot中简单使用
1 | <dependency> |
1 | spring.datasource.url=jdbc:mysql://localhost:3306/test |
其实这个hibernate.hbm2ddl.auto参数的作用主要用于:自动创建|更新|验证数据库表结构,有四个值:
dialect
主要是指定生成表名的存储引擎为InneoDBshow-sql
是否打印出自动生成的SQL,方便调试的时候查看
1 |
|
dao只要继承JpaRepository类就可以,几乎可以不用写方法,还有一个特别的功能非常赞,就是根据方法名来自动生成SQL,比如findByUserName
,会自动产生一个以userName
为参数的查询方法,再比如findAll
就会查询表里面的所有数据,再比如自动分页等等。
Entity中不映射成列的字段得加@Transient注解,不加注解就会映射成列
1 | public interface UserRepository extends JpaRepository<User,Long> { |
1 | .class) (SpringJUnit4ClassRunner |
当然spring data jpa还有很多功能,比如封装好的分页,可以自定义SQL,主从分离等等,等到以后再做细讲。
Spring Boot推荐使用thymeleaf模板来代替jpa,那么thymeleaf模板到底好在哪里呢,让Spring官方来推荐,下面我们来看看
Thymeleaf是一款用于渲染XML/XHTML/HTML5内容的模板引擎。类似JSP,Velocity,FreeMarker等,它也可以轻易的与Spring MVC等Web框架进行集成作为Web应用的模板引擎。与其它模板引擎相比,Thymeleaf最大的特点是能够直接在浏览器中打开并正确显示模板页面,而不需要启动整个Web应用。
好了,你们可能会觉得已经习惯使用了velocity,freemarker,beetle之类的模板,那么thymeleaf到底好在哪呢?让我们来做个对比:
Thymeleaf是与众不同的,因为它使用了自然的模板技术。这意味着Thymeleaf的模板语法并不会破坏文档的结构,模板依旧是有效的XML文档。模板还可以用作工作原型,Thymeleaf会在运行期替换掉静态值。Velocity与FreeMarker则是连续的文本处理器。
下面的代码示例分别使用Velocity、FreeMarker与Thymeleaf打印出一条消息:
1 | Velocity: <p>$message</p> |
URL
URL在Web应用模板中占据着十分重要的地位,需要特别注意 的是Thymeleaf对于URL的处理是通过语法@{…}来处理的。Thymeleaf支持绝对路径URL:
1 | <a th:href="@{https://www.baidu.com}">百度</a> |
条件求值
1 | <a th:href="@{/login}" th:unless="${session.user != null}">登录</a> |
for循环
1 | <tr th:each="prod : ${prods}"> |
在Web开发过程中一个绕不开的话题就是前端工程师与后端工程师的协作,在传统Java Web开发过程中,前端工程师和后端工程师一样,也需要安装一套完整的开发环境,然后各类Java IDE中修改模板、静态资源文件,启动/重启/重新加载应用服务器,刷新页面查看最终效果。
但实际上前端工程师的职责更多应该关注于页面本身而非后端,使用JSP,Velocity等传统的Java模板引擎很难做到这一点,因为它们必须在应用服务器中渲染完成后才能在浏览器中看到结果,而Thymeleaf从根本上颠覆了这一过程,通过属性进行模板渲染不会引入任何新的浏览器不能识别的标签,例如JSP中的,不会在Tag内部写表达式。整个页面直接作为HTML文件用浏览器打开,几乎就可以看到最终的效果,这大大解放了前端工程师的生产力,它们的最终交付物就是纯的HTML/CSS/JavaScript文件。
WebJars是一个很神奇的东西,可以让我们以jar包的形式来使用前端的各种框架和组件。
什么是WebJars?WebJars是将客户端(浏览器)资源(JavaScript,Css等)打成jar包文件,以对资源进行统一依赖管理。WebJars的jar包部署在Maven中央仓库上。
我们在开发Java web项目的时候会使用像Maven,Gradle等构建工具以实现对jar包版本依赖管理,以及项目的自动化管理,但是对于JavaScript,Css等前端资源包,我们只能采用拷贝到webapp下的方式,这样做就无法对这些资源进行依赖管理。那么WebJars就提供给我们这些前端资源的jar包形势,我们就可以进行依赖管理。
1 | <dependency> |
1 | <link th:href="@{/webjars/bootstrap/3.3.6/dist/css/bootstrap.css}" rel="stylesheet"></link> |
就可以正常使用了。
]]>Spring Boot 是由 Pivotal 团队提供的全新框架,其设计目的是用来简化新 Spring 应用的初始搭建以及开发过程,该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置。Spring Boot 默认配置了很多框架的使用方式,就像 Maven 整合了所有的 Jar 包,Spring Boot 整合了所有的框架。它的核心设计思想是:约定优于配置,Spring Boot 所有开发细节都是依据此思想进行实现的。
Spring Boot 是一套全新的框架,它来自于 Spring 大家族,因此 Spring 所有具备的功能它都有并且更容易使用;同时还简化了基于 Spring 的应用开发,通过少量的代码就能创建一个独立的、产品级别的 Spring 应用。
下图展示出了 Spring Boot 在 Spring 生态中的位置:
该项目主要的目的是:
Spring Boot 特性
Spring Boot 本身并不提供 Spring 框架的核心特性以及扩展功能,只是用于快速、敏捷地开发新一代基于 Spring 框架的应用程序。同时它集成了大量常用的第三方库配置(如 Redis、MongoDB、JPA、RabbitMQ、Quartz 等),Spring Boot 应用中这些第三方库几乎可以零配置进行开箱即用,大部分的 Spring Boot 应用都只需要非常少量的配置代码,开发者能够更加专注于业务逻辑。
使用 Spring Boot 开发项目,有以下几方面优势:
从软件发展的角度来讲,越简单的开发模式越流行,简单的开发模式解放出更多生产力,让开发人员可以避免将精力耗费在各种配置、语法所设置的门槛上,从而更专注于业务。这点上,Spring Boot 已尽可能地简化了应用开发的门槛。
Spring Boot 所集成的技术栈,涵盖了各大互联网公司的主流技术,跟着 Spring Boot 的路线去学习,基本可以了解国内外互联网公司的技术特点。
说了那么多,手痒痒的很,马上来一发试试。
如上图所示,Spring Boot的基础结构共三个文件:
另外,Spring Boot建议的目录结构如下:
root package结构:com.example.myproject
1 | com |
采用默认配置可以省去很多配置,当然也可以根据自己的喜欢来进行更改
最后,启动Application main方法,至此一个java项目搭建好了。
1 | <dependency> |
pom.xml 文件中默认有两个模块:
编写controller内容
1 |
|
@RestController的作用就是controller里面的方法都以json格式输出,不用再写jackson配置,其本质实际上就是@Controller + @ResponseBody。
启动主程序,打开浏览器访问http://localhost:8080/hello,就可以看到效果了,有木有很简单!
打开scr/test/java 下的测试入口,编写简单的http请求来测试:使用mockmvc实现,并利用MockMcvResultHandlers.print()打印出执行结果。
1 | .class) (SpringRunner |
热启动在正常开发项目中已经很常见了,虽然平时开发web项目过程中,改动项目后重启总是报错,但是Spring Boot对调试支持很好,修改之后可以实时生效,需要添加以下配置:
1 | <dependencies> |
热启动模块在完整的打包环境下运行的时候会被禁用。如果你使用java -jar启动应用或者用一个特定的classloader启动,它会认为这是一个“生产环境”。
使用spring boot可以非常方便、快速搭建项目,使我们不用关心框架之间的兼容性,适用版本等各种问题,我们想使用任何东西,仅仅添加一个配置就可以,所以使用sping boot非常适合构建微服务。
]]>最近偶然读了一篇文章一位女运维的自述:3年为公司节省10亿元!,大意是腾讯运维如何运用图像影音压缩技术为公司和用户节省了大量流量,节约了成本并提高了用户体验。其中关键技术 H.265 吸引了我的注意,于是百谷歌度了一番,做了一个大致的了解。
4K is the next big thing in TVs, 和 4K videos are starting to pop up everywhere. 但由于4K视频占用了大量的空间,很难以最佳质量进行下载和流式传输,而值得庆幸的是,一种被称为高效视频编码技术(High Efficiency Video Coding (HEVC), or H.265)正在改变这种情况。
这项新技术还需要很长一段时间才能得到大范围的应用,目前它正出现在—4K UHD Blu-rays 使用 HEVC, VLC 3.0使得你PC机上的HEVC 和 4K 视频更加清晰,iPhone 设备甚至可以用HEVC技术记录视频以节约存储空间。但它是如何工作的,为什么对4K视频如此重要?
当你观看蓝光光盘,YouTube视频或者来自ITunes的电影时,它与来自编辑室的原始视频不是完全相同的。为了是该电影或者视频更适合蓝光光盘——或者使其足够小以便在网上轻松下载——必须对电影压缩。
高级视频编码也被称为AVC或H.264,是广泛使用的视频压缩的最佳标准,并且有几种不同的方法可用于减小视频文件的大小。
例如,在任何给定的帧中,它可以查找大多数颜色相同的区域。在我和我儿子的这幅禁止帧中——大部分的天空都是相同的蓝色,因此压缩算法可以将图像分割成块——称之为“macroblocks”——并表明“我们仅仅假设沿着顶部的所有这些块都是相同的蓝色以替代记录每个像素的颜色”。这比存储每个单独像素的颜色效率更高,降低最终图像的文件大小。在视频中, 该技术被称之为帧内压缩——压缩单个帧的数据。
AVC还使用帧间压缩,它可以查看多个帧并记录帧的哪些部分正在改变 - 哪些不是。从“美国队长:内战”中获得这一镜头。 背景并没有太大的变化 - 帧之间的差异大部分来自钢铁侠的脸部和身体。 所以,压缩算法可以将帧分成相同的宏块,并表明“你知道什么? 这些块不会改变100帧,所以让我们再次显示它们,而不是将整个图像存储100次。“这可以显著减小文件大小。
这些只是AVC / H.264使用方法的两个过度简化的例子,但您清楚了其中的原理。 这完全是为了在不影响质量的情况下提高视频文件的效率。 (当然,如果压缩太多,任何视频都会失去质量,但这些技术越聪明,在您进入该点之前就可以压缩越多。)
高效视频编码技术也称为HEVC或H.265,是视频编码技术演进的下一步。 它的构建使用了AVC / H.264中的许多技术,使视频压缩效率更高。
举个例子,当AVC查看多个帧变更时(例如上面的美国队长例子),这些宏块“块”可以是几个不同的形状和大小,最多可达16个像素乘16个像素。 而使用HEVC,这些块可以达到64×64的尺寸 - 远远大于16×16,这意味着该算法可以记忆更少的块,从而减小整体视频的尺寸。
您可以在HandyAndy Tech Tips的这个精彩视频中看到对这项技术的更多技术性解释.
当然,HEVC还有其他一些技术正在运用,但这是最大的改进之一 - 当所有事情说到做到之后,HEVC可以在相同的质量水平下将视频压缩两倍于AVC。 对于用AVC技术编码而占用大量空间的4K视频而尤其重要。 HEVC使4K视频更容易流式传输,下载或翻录到硬盘。
自2013年以来,HEVC一直是获批准的标准,那么为什么我们不能将它用于所有视频?
相关: 如何通过启用硬件加速使VLC使用更少的电量
这些压缩算法非常复杂 - 在视频播放之前,需要花费大量的数学计算才能实现解码。 计算机有两种主要的方式可以解码在这种视频:一软件解码,它会使用你的计算机的CPU来完成这个数学运算,或者二硬件解码 ,在这个解码过程中,它将负载交给你的图形卡(或者你的集成图形芯片中央处理器)。 只要显卡支持所尝试播放的视频的编解码器,则效率更高。
因此,尽管许多PC和程序都可以尝试播放HEVC视频,但是如果没有硬件解码,它可能会卡顿或者非常慢。 所以,除非你有一个支持HEVC硬件解码的图形卡和视频播放器,否则HEVC对你来说并不是很好。
这对于独立播放设备来说不是问题 - 包括Xbox One在内的4K蓝光播放机都是以HEVC为基础构建的。 但是当谈到在PC上播放HEVC视频时,事情变得更加困难。 您的计算机将需要以下硬件之一才能硬解码HEVC视频:
您可能还需要使用不仅能支持HEVC视频,而且还支持HEVC硬件解码的操作系统和视频播放器 - 目前这有点多余。 许多玩家仍在增加对HEVC硬件解码的支持,并且在某些情况下,它可能仅适用于上面列表中的某些芯片。 在撰写本文时, VLC 3.0, Kodi 17和Plex Media Server 1.10都支持某种形式的HEVC硬件解码,至少对于某些卡而言。 不过,您可能必须在选择的播放器中启用硬件加速才能正常工作。
随着时间的推移,越来越多的计算机将能够处理这种视频,而更多的播放器将会更广泛地支持它 - 就像现在使用AVC / H.264一样。 它可能需要一段时间才会变得无处不在,在此之前,您必须以巨大的文件大小(或压缩它并丢失图像质量)将您的4K视频存储在AVC / H.264中。 但是,HEVC / H.265得到广泛支持越多,视频就越好。
图片来源: alphaspirit /Shutterstock.com
本文翻译自:What Is HEVC H.265 Video, and Why Is It So Important for 4K Movies?
]]>对于面向对象的特点,想必大家应该都可以倒背如流:封装,继承,多态。但很多人对这些特点的理解仅仅停留在表面,认为封装就是变量的私有化,然后对外开放接口,获取和设置值,而不知道为什么要这样做。
封装,简单来说就是将变量私有化,在java里的用的就是private修饰符修饰,这样在外部产生的对象就不能直接访问这个变量。想要外部对象对变量进行访问或操作,就需要在类里面提供外部访问的接口,也就是我们熟知的get和set方法。
以上就是大部分人对于封装的理解。知道有封装这回事,知道怎么用,却不知道为什么要用,甚至觉得多此一举。因为明明person.name就是访问到变量,为什么非要person.getName()呢?
让我们先来看一下不使用封装的情况:
设计了3个类,人、男人、女人
1 | public class Person{ |
到这里一切正常,看起来也还不错。
但是这这个时候来了一个小偷,这个小偷呢,除了偷别人的钱和老婆啥都不干。
1 | public class Thief extends Man{ |
这时,你觉得是时候改变一下了!!!
封装觉得你有点惨,于是过来帮助你一下:
1 | public class PackagePerson{ |
上面的代码看起来除了长了点,没什么其他问题。这时候小偷已经不能偷我们的钱和老婆了,钱和老婆都被保护了起来,以至于我们自己想设置和更换都不行了,这明显不太科学…
如何解决上面的问题呢?私有化外部访问不到,自己也没法改数据,提供了set方法又会让所有人都能改,和不私有设计没什么区别,好纠结。
Wait,这里说的“所有人”真的是所有人吗?
让我们来看看:
1 | public void setMoney(PackageMan man, double money){ |
这样就只有自己可以修改了,别人不可以。
但是你老婆不满意了,凭什么只有你自己可以改?我也想改!
这种需求还是应该满足一下的,怎么做呢?
1 | public void setMoney(Object obj, double money){ |
以上就是对面向对象中的封装的理解,封装不仅仅只是 private + getter and setter。使用封装可以对setter进行更深层次的定制,我们可以对可以执行的setter方法的对象做规定,也可以对数据操作要求,还可以做类型转换等一系列可以想到的。
使用封装不仅仅是安全,更可以简化操作。不要觉得用了封装多了好多代码,看起来乱糟糟的。如果你写一个大系统,一开始你可能这样定义属性的
1 | public int age; |
1 | p.age = 10; |
1 | public String age; |
但是如果用了封装,只需要这样:
1 | public void setAge(int age){ |
这里只是举个栗子,实际开发中也不会出现改变数据类型这么操蛋的事…
封装还有一个好处就是模块化。当你参与一个很多人实现的大型系统中,不可能知道所有的类是怎样实现的。只需要知道这个类提供了哪些方法,需要传入什么数据,能得到什么样的结果。至于怎么得到的,关我X事?
所以说,如果你写的代码还没用封装,改过来吧。不仅仅因为大家都在用,而是这确实可以给我们提供很大的便利。
封装的有以下四大好处:
面向对象的思想面向过程的思想有着本质的区别的,对于面向过程的思维来说,我们分析解决问题时会将问题拆分成n个步骤,第一步先做什么,第二步再做什么;而对于面向对象的思维来说,会首先分析问题里面涉及到哪些类和对象,然后再分析这些类和对象应该具有哪些属性和方法,最后分析类与类之间的关系。
面向对象有一个非常重要的设计思维:合适的方法应该出现在合适的类里面。
面对问题域:
不能
应该
面向对象的基本思想是,从现实世界中可观存在的事物出发来构造软件系统,并在系统的构造中尽可能运用人类的自然思维方式。
面向对象更加强调运用人类在日常生活的逻辑思维中经常采用的思想方法与原则,如抽象、分类、继承、聚合、多态等。
人在思考的时候,首先眼睛里看到的是一个个对象。
对象是用于计算机语言对问题域中事物的描述,对象通过“属性(attribute)”和“方法(method)”来分别对应事物所具有的静态属性和动态属性。
类是用于描述同一类对象的一个抽象的概念,类中定义了这一类对象所具有的静态属性和动态属性。
类可以看成一类对象的模板,对象可以看成该类的一个具体的实例。
举个栗子:
人的定义:能制造工具并能熟练使用工具进行劳动的高等动物。
给人下定义的过程,其实就是把人的相关特性抽象出来,如:
人在这里叫做这一类事物的抽象,在你的脑子里有人的概念,可人的概念在脑子里到底是怎样的? 首先人是高等动物,那么高等动物这个特征就是人的静态属性,其次,这个高等动物可以 制造工具并熟练使用工具,制造工具和熟练使用工具就是人的动态属性。反应到java的类上,就是一个是成员变量(静态属性),一个是方法(动态属性)。脑子里的人的概念其实是一类事物的抽象,这种抽象的东西我们就叫做类,椅子是类,桌子是类,交通工具也是类,而这一类事物的具体实例就是对象了。
上文已经说明面向对象的设计思想就是对问题域特征进行抽象,抽象出一些类,那么该如何去做呢?首先需要明确的是,类具有静态属性和动态属性,那么反应到java类中的体现就是,成员变量和方法。
让我们来看一下职员的抽象:
首先分析下,职员有哪些属性?有姓名,年龄,目前薪资等属性;有哪些方法?显示姓名,显示年龄,修改姓名,领取工资。当然,所有的方法其实都可以让别人来做,但面向对象的设计思想就是方法应该出现在最合适的类里面,这里显然职员这个类自己最合适。
对于每个对象来说,它都有一些属性(成员变量),只不过对象间的取值不同而已。上图所描述的职员这个类实例化出两个职员,职员A和职员B,他们都有姓名、年龄和目前薪资这些属性,但具体数值却不一样,正因为这些变化,对象和对象之间才能区分开来。
如何区分类和对象?类是一类具有共同特征事物的抽象,而对象就是这个类下面的具体实例了。
类和类之间存在关系的,如学生和老师这两个类,老师可以教学生,学生可以向老师学习,这是他们之间的关系,而关系和关系之间也是不同,你和你老婆的关系和你和你其他女朋友的关系是不能混为一谈的。关系中最弱的一种关系就是关联关系了。关联关系反应到程序上往往是一个类的方法里面的参数是另一个类的具体对象,比如教授教学生,教哪个学生,教学是教授这个类的一个方法,某个研究生是研究生这个类里面的一个具体对象,这两个类或者说对象之间有关系,但是不是很紧密的关系。
对应:一般和特殊
继承关系的前提:XX是一种XX。满足继承的前提就可以称之为继承关系,如游泳运动员是一种运动员,这点是满足的,那么游泳运动员就是从运动员继承过来的,游泳运动员和运动员之间就是一种继承关系。学生是一个人,学生从人继承,老师也是一个人,老师也从人继承,学生是一种老师,这点说不过去,所以学生和老师之间就不存在继承关系,因此做设计的时候分清继承关系很简单,那就是使得“XX是一种XX”说的过去。图中,运动员派生出了不同种类的运动员,包括游泳的,球类的,射击的。球类运动员再派生出足球的,篮球的,排球的,这就是一棵继承树,不过这棵树是比较理想的情况,只有一个根节点。但是实际中,继承关系不一定只从一个类继承,可能从多个类继承,比如说,孩子即继承父亲这个类,也继承母亲这个类。C++正是想封装这种关系,所以它存在多继承。
对应:整体和部分
什么是聚合?聚合就是一个整体和部分的关系。我们是“XX是XX的一部分”,只要说的通,他就是聚合关系,队长是球队的一部分,队员也是球队的一部分,因此队长和球队,队员和球队都是聚合关系。脑袋是人的一部分,身体和胳膊也是人的一部分,因此脑袋、身体和胳膊与人都是聚合关系。聚合关系再分细一点就是聚集关系和组合关系,比如球队、队长、队员,这三者是聚集关系,假如这个队长既是足球队的队长,也是篮球队的队长,一个人分属两个不同的球队,这是可以的,球队和队长之间没有不可分割的关系,这就是聚集关系。而另一种组合关系,就是一种不可分割的关系,相对于一个人的脑袋不可能既属于你又属于别人,身体也一样不可能同时属于多个人。
对于父类(抽象类)或接口来说,我觉得应该具有这样一个方法,但是不知道具体怎么实现,谁去实现,我的子类实现,这就是实现关系。而根据实现不同,具有不同的表现形态,这就是多态。
对象和类是分不开的,必须首先定义类才能有对象,首先定义方法才能调用。对象是Java里面的核心,做任何东西你首先造出一个对象才能做。静态属性简称属性,也叫成员变量,以后说属性或者说成员变量他们指的都是同一回事。
整个类可以看作是静态的属性还有方法之间的一个综合。怎么抽象出一个类的概念,从两方面入手,一个是静态属性,即具有哪些成员变量,另一个就是动态属性,具有哪些方法。
下面是一段示例代码,可以仔细体会下:
1 | public class Person { |
面向对象编程就是使用一组对象互相配合通过沟通一起完成特定的功能.做软件苦苦追求的一种境界是可重用性(reusable),可扩展性。如果是面向过程,一般情况下属性和方法是分开的,没有关系的,这样复用起来很麻烦,而且局限在方法这个层次上,而面向对象则不同,它将属性和方法包装,可以综合起来复用,就是整个对象的复用。所以面向对象和面向过程相比,前者明显更容易让我们达到可重用性。
]]>Consul是google开源的一个使用go语言开发的服务发现、配置管理中心服务。内置了服务注册与发现框架、分布一致性协议实现、健康检查、Key/Value存储、多数据中心方案,不再需要依赖其他工具(如ZooKeeper等)。服务部署简单,只有一个可运行的二进制的包。每个节点都需要运行agent,他有两种运行模式server和client。每个数据中心官方建议需要3或5个server节点以保证数据安全,同时保证server-leader的选举能够正确的进行。
client
client表示Consul的client模式,就是客户端模式。是Consul节点的一种模式,这种模式下,所有注册到当前节点的服务会被转发到server,本身是不持久化这些信息。
server
server表示Consul的server模式,表明这个Consul是个server,这种模式下,功能和client都一样,唯一不同的是,它会把所有的信息持久化的本地,这样遇到故障,信息是可以被保留的。
server-leader
中间那个server下面有leader的字眼,表明这个server是它们的老大,它和其它server不一样的一点是,它需要负责同步注册的信息给其它的server,同时也要负责各个节点的健康监测。
raft
server节点之间的数据一致性保证,一致性协议使用的是raft,而zookeeper用的paxos,etcd采用的也是taft。
服务发现协议
Consul采用http和dns协议,etcd只支持http
服务注册
Consul支持两种方式实现服务注册,一种是通过Consul的服务注册http API,由服务自己调用API实现注册,另一种方式是通过json个是的配置文件实现注册,将需要注册的服务以json格式的配置文件给出。Consul官方建议使用第二种方式。
服务发现
Consul支持两种方式实现服务发现,一种是通过http API来查询有哪些服务,另外一种是通过Consul agent 自带的DNS(8600端口),域名是以NAME.service.consul的形式给出,NAME即在定义的服务配置文件中,服务的名称。DNS方式可以通过check的方式检查服务。
服务间的通信协议
Consul使用gossip协议管理成员关系、广播消息到整个集群,他有两个gossip pool(LAN pool和WAN pool),LAN pool是同一个数据中心内部通信的,WAN pool是多个数据中心通信的,LAN pool有多个,WAN pool只有一个。
首先去官网现在合适的consul包:https://www.consul.io/downloads.html
安装直接下载zip包,解压后只有一个可执行的文件consul,将consul添加到系统的环境变量里面。
1 | #unzip consul_1.0.2_linux_amd64.zip |
出现上面的内容证明安装成功。
consul必须启动agent才能使用,有两种启动模式server和client,还有一个官方自带的ui。server用与持久化服务信息,集群官方建议3或5个节点。client只用与于server交互。ui可以查看集群情况的。
Server
cn1:
1 | #consul agent -bootstrap-expect 2 -server -data-dir /data/consul0 -node=cn1 -bind=192.168.1.202 -config-dir /etc/consul.d -enable-script-checks=true -datacenter=dc1 |
cn2:
1 | #consul agent -server -data-dir /data/consul0 -node=cn2 -bind=192.168.1.201 -config-dir /etc/consul.d -enable-script-checks=true -datacenter=dc1 -join 192.168.1.202 |
cn3:
1 | #consul agent -server -data-dir /data/consul0 -node=cn3 -bind=192.168.1.200 -config-dir /etc/consul.d -enable-script-checks=true -datacenter=dc1 -join 192.168.1.202 |
参数解释:
- -bootstrap-expect: 集群期望的节点数,只有节点数量达到这个值才会选举leader。
- -server: 运行在server模式
- -data-dir:指定数据目录,其他的节点对于这个目录必须有读的权限
- -node:指定节点的名称
- -bind:为该节点绑定一个地址
- -config-dir:指定配置文件,定义服务的,默认所有一.json结尾的文件都会读
- -enable-script-checks=true:设置检查服务为可用
- -datacenter: 数据中心没名称,
- -join:加入到已有的集群中
Client
1 | #consul agent -data-dir /data/consul0 -node=cn4 -bind=192.168.1.199 -config-dir /etc/consul.d -enable-script-checks=true -datacenter=dc1 -join 192.168.1.202 |
UI
1 | #consul agent -ui -data-dir /data/consul0 -node=cn4 -bind=192.168.1.198 -client 192.168.1.198 -config-dir /etc/consul.d -enable-script-checks=true -datacenter=dc1 -join 192.168.1.202 |
- -ui:使用自带的ui,
- -ui-dir:指定ui的目录,使用自己定义的ui
- -client:指定web ui、的监听地址,默认127.0.0.1只能本机访问。
集群创建完成后,可以使用一些常用的命令检查集群的状态:
1 | #consul info |
1 | #consul members |
新加入一个节点有几种方式:
这种方式,重启后不会自动加入集群
1 | #consul join 192.168.1.202 |
在启动的时候使用-join指定一个集群
1 | #consul agent -ui -data-dir /data/consul0 -node=cn4 -bind=192.168.1.198 -config-dir /etc/consul.d -enable-script-checks=true -datacenter=dc1 -join 192.168.1.202 |
使用-startjoin或-rejoin
1 | #consul agent -ui -data-dir /data/consul0 -node=cn4 -bind=192.168.1.198 -config-dir /etc/consul.d -enable-script-checks=true -datacenter=dc1 -rejoin |
访问ui:
http://192.168.1.198:8500/ui
端口:
8300:consul agent服务relplaction、rpc(client-server)
8301:lan gossip
8302:wan gossip
8500:http api端口
8600:DNS服务端口
采用的是配置文件的方式,(官方推荐)首先创建一个目录用于存放定义服务的配置文件
1 | #mkdir /etc/consul.d/ |
下面给出一个服务定义:
1 | #cat web.json |
1 | # dig @127.0.0.1 -p 8600 web.service.consul SRV |
check使用来做服务的健康检查的,可以拥有多个,也可以不使用支持多种方式检查。check必须是script或者TTL类型的,如果是script类型则script和interval变量必须被提供,如果是TTL类型则ttl变量必须被提供。script是consul主动去检查服务的健康状况,ttl是服务主动向consul报告自己的状况。
script check
1 | { |
http check
1 | { |
tcp check
1 | { |
ttl check
1 | { |
全文完
]]>有几年开发经验的工程师,往往都会有自己的一套工具库,称为utils、helpers等等,这套库一方面是自己的技术积累,另一方面也是对某项技术的扩展,领先于技术规范的制订和实现。
Lodash就是这样的一套工具库,它内部封装了诸多对字符串、数组、对象等常见数据类型的处理函数,其中部分是目前ECMAScript尚未制订的规范,但同时被业界所认可的辅助函数。而且每天使用npm安装Lodash的数量在百万级以上,这在一定程度上证明了其代码的健壮性,值得我们在项目中一试。
在 Filip Zawada的文章《How to Speed Up Lo-Dash ×100? Introducing Lazy Evaluation》中提到了Lodash提高执行速度的思路,主要有三点: Lazy Evaluation、Pipelining和Deferred Execution。下面两张图来自Filip的博客:
假设有如上图所示的问题: 从若干个求中取出三个面值小于10的球。第一步是从所有的求中取出所有面值小于10的球,第二部是从上一步的结果中去三个球。
上图是另一个解决方案,如果一个球能够通过第一步,那么就继续执行第二步,直至结束然后测试下一个球。。。当我们取到三个球之后就中断整个循环。Filip称这是Lazy Evaluation Algorithm, 就个人理解这并不全面,他后续提到的Pipelining(管道计算),再加上一个中断循环执行的算法应该更符合这里的图示。
此外,使用Lodash的链式调用时,只有现实或隐式调用 .value 方法才会对链式调用的整个操作进行取值,这种不在声明时立即求值,而在使用时进行求职的方式,是Lazy Evaluation最大的特点。
收益于Lodash的普及程度,使用它可以提高很多人开发时于都代码的效率,减少彼此之间的误解(Loss of Consciousness)。在《Lodash: 10 Javascript Utility Functions That You Should Probably Stop Rewriting》一文中,作者列举了多个常用的Lodash函数,实例演示了使用Lodash的技巧。
1 | // 1. Basic for loop. |
for 语句是执行虚幻的不二选择,Array.apply也可以模拟循环,但在上面代码的使用场景下,_.tiems()的解决方法更加简洁和易于理解。
深层查找属性值
1 | // Fetch the name of the first pet from each owner |
_.map 方法是对原生 map 方法的改进,其中使用 pets[0].name 字符串对嵌套数据取值的方式简化了很多冗余的代码,非常类似使用jQuery选择DOM节点 ul>li>a , 对于前端开发者来说有种久违的亲切感。
个性化数组
1 | // Array's map method. |
在上面的代码中,我们要创建一个初始值不同、长度为6的数组,其中 .uniqueId 方法用于生成独一无二的标示符(递增的数字,在程序运行期间保持独一无二), .partial 方法是对 bind 的封装。
深拷贝
1 | var objA = { |
JavaScript 没有直接提供深拷贝的函数,但是我们可以用其他杉树来模拟,比如 JSON.parse(JSON.stringify(objectToClone)), 但这种方法要求对象中的属性值不能是函数。Lodash 中的 _.cloneDeep 函数封装了深拷贝的逻辑,用起来更加简洁。
随机数
1 | // Native utility method |
Lodash 的随机数生成函数更贴近实际开发,ECMAScript 的随机数生成函数式底层必备的接口,两者都不可获取。此外,使用 _.random(15, 20, true) 还可以在15到20之间生成随机的浮点数。
对象扩展
1 | // Adding extend function to Object.prototype |
_.assign 是浅拷贝, 和ES6新增的 Object.assign 函数功能一致(建议优先使用Object.assign)。
筛选属性
1 | // Native method: Remove an array of keys from object |
大多数情况下,Lodash所提供的辅助函数都会比原声的函数更贴近开发需求。在上面的代码中,开发者可以使用数组、字符串以及函数的方式筛选对象的属性,并且最终会返回一个新的对象,中间执行筛选时不会对旧对象产生影响。
1 | // Native method: Returning a new object with selected properties |
.pick 是 .omit 的相反操作,用于从其他对象中挑选属性生成新的对象。
随机元素
1 | var luckDraw = ["Colin", "John", "James", "Lily", "Mary"]; |
_.sample 支持随机挑选多个元素并返回新的数组。
针对 JSON.parse 的错误处理
1 | // Using try-catch to handle the JSON.parse error |
如果你在使用 JSON.parse 时没有预置错误处理,那么它很有可能会成为一个定时炸弹,我们不应该默认接收的JSON对象都是有效的。 try-catch 是常见的错误处理方式,如果项目中使用Lodash,那么可以使用 _.attmpt 替代 try-catch 的方式,当解析JSON出错时,该方法会返回一个 Error 对象。
随着ES6的普及,Lodash的功能或多或少会被原生功能所替代,所以使用时还需要进一步甄别,建议优先使用原生函数,有关ES6替代Lodash的部分,请参考文章《10 个可用 ES6 替代的 Lodash 特性》。
其中有两处分别值得一看:
1 | // 使用箭头函数创建可复用的路径 |
1 | const func = p => v; |
1 | const func = a => b => c => a + b + c; |