
公号「二哥聊RPA」。关注我,一起洞察 RPA ◎ RPA创业者 ◎ 现明堂红木商业顾问,为十月结晶、可啦啦、鑫蜂维等多家企业提供 RPA 咨询服务 ◎ 希望帮助大家在 RPA 上做一些力所能及的事情
一、RPA 是什么?难吗?RPA 对大家来说,可能挺陌生的,其实它很简单。Robotic Process Automation(简称 RPA )机器人流程自动化,是一种技术,是以软件机器人 + AI / OCR 等科技能力结合,在软件上的某些业务操作场景中,达到机器替人,实现重复的工作自动化。RPA 通过模拟人在电脑、平板和手机上的操作方式,包括 Web 自动化、桌面软件自动化、手机 APP 自动化、鼠标键盘自动化等等,实现不需要人工,替人将操作流程自动化。RPA 的应用场景是在线上的。类比线下机器人的场景。如图,在车间里的一位工程师,正在排查机械手臂的问题。线下车间上的非主观决策工作,比如特斯拉造车,利用物理机器人(机械手臂)释放蓝领的生产力。那线上呢?软件上的非主观决策工作,可以利用软件机器人(通过 RPA 实现)释放白领的生产力。软件上的非主观决策工作,有很多常用的场景,比如:运营场景:运营的数据日报统计、运营的批量操作等等客服场景:售后客服批量发消息、批量评价回复等等办公场景:人事批量招聘、考勤统计、财务批量通知工资单等等RPA 的应用场景有以下特征:非主观判断高频或单次工作量大工作流程繁琐,机械,重复并且容易出错任何行业、任何部门都有大量的场景,涉及重复、有规则逻辑的工作,都可以用 RPA 开发一个软件机器人帮助完成。二、RPA 自动化获客实战同样,关于 RPA 这个技术可以在很多部门场景中实战,这里分享下在帮助朋友或者企业中,关于「获客」相关的实战案例。希望能给圈友们带来一点点启发,帮助圈友们利用 RPA 这个工具能够降低成本 & 提高发财的效率。2.1 实战前准备就像电脑上设计个海报,你用 PS 还是 Sketch 还是创可贴工具。同样设计一个 RPA 自动化机器人,跟之前大家玩过爬虫工具或按键精灵类似。RPA 工具推荐如下:影刀 RPA:情景化教学课程非常适合小白。winrobot360.comuiBot:uibot.com.cn阿里云 RPA:www.aliyun.com/product/codestore还有国外的 uiPath 等等怕大家不知道链接,我已经给大家准备好了链接,方便你取用任何东西就像学 PS 一样,初级玩家简单学一下,改个图片尺寸,P 个小图一样是很容易上手出结果的。RPA 工具上手也是这样的,简单学一下上手还是很快的:像程序员背景或者经历的圈友,估计研究几天,完全轻松就能上手像纯技术小白的圈友,推荐大家学习并使用影刀。我这边安排了一批 CS 的大学生,大概花了 2 周多一点的时间,每天花费 2 个小时左右,就能考出影刀 RPA 高级使用证书。并轻轻松松写一些 RPA 机器人。以小白 & 影刀 RPA 为例,快速入门参考资料如下:软件介绍&下载:https://www.winrobot360.com/软件使用教程帮助中心:https://www.winrobot360.com/yddoc/课程学院:https://college.winrobot360.com/学好了大致的基本操作就基本够了,因为基本核心操作就可以让你搭建很多 RPA 机器人了,下面可以开始实战分享2.2 自动化选品 - 电商选品大家知道蓝海词选品,就是选蓝海词的流量,就是获客。举淘宝生意参谋来说,一般选品的流程:在生意参谋上输入某个关键词:比如「女王的世界乳胶」筛选时间、支付转化率、商城点击占比等筛选项然后查看对应列表的数据如图,生意参谋的「商城点击占比」只要低于 70% 左右的品,基本上都是蓝海品。然后看看支付转化率还可以,基本上确定这个品就可以做。那如何用 RPA 实现自动化选品?分为两个步骤:利用 影刀 RPA 等工具,搭建一个「生意参谋选品工具」数字化机器人开启这个程序,导入对应输入的词根列表(比如:乳胶、手套、温岭等),并预设「商城点击占比」最小值,「支付转化率」最小值。运行 RPA 数字机器人,就会输出对应的「蓝海品表格」蓝海品表格,包括了乳胶相关的蓝海品表格、手套相关的蓝海品表格、温岭相关的蓝海品表格等等下一步就是人工去主观判断这个蓝海品适不适合做:包括有一定真实的流量,蓝海品稳定且足够蓝海,这样投资回报率指数足够高同理可得,可以搭建更多的选品机器人:「亚马逊选品机器人」「抖音-考古加选品机器人」「抖音-抖查查选品机器人」等等如图,就是词根「欧莱雅」,输出的抖音蓝海品「欧莱雅蓝海品表格」:2.3 自动化获客 - 百度地图获客一键轻松获取百度商家信息:1、自动输入关键词2、自动选择地区3、自动选择数量如图,运行「百度地图获客 RPA 机器人」,自动选择「杭州」城市,自动输入「五金」,最后自动输出对应的客户手机号。RPA 流程其实就是个输入和输出,你只要批量的输入城市列表、关键词列表,然后运行完后,输出的就是你想要的分好类的客户号码列表。那怎么处理手机号呢?RPA 机器人也可以自动处理:自动化清洗手机:过滤非手机号的类似 0571-xxx自动补全客户信息:RPA 通过手机号用支付宝转账或者钉钉加好友,获取客户关键信息,并补全「客户名称」最后输出到表格,然后配合「RPA 主动加人机器人」通过企业微信或者微信,自动化主动加人到微信,沉淀到私域做成交。同理可得:比如你想要「建材」公司老板的联系方式:通过「建材」行业的关键词,上企查查网站 RPA 自动化获客比如你想要「服装」公司老板的联系方式:通过「服装」行业的关键词,上1688网站 RPA 自动化获客2.4 获客思路拓展根据每个商业模式不同,所需要的客群不同,想象一下客户画像,客户在哪?你去就哪里获客,先手工操作获客,后面让 RPA 助你一臂之力。比如生财有术缺的是创业者伙伴(虽然不缺哈),那就可以去「企查查」平台,自动找到新注册的公司,并且实现自动化获客比如卖电商软件的缺少的是电商客户,那就可以去「1688」等平台自动化获客比如卖制造企业生产过程执行管理软件,那就可以去「企查查」,通过关键词搜索对应的公司,实现自动化获客等等为了帮助大家拓展这块的思路,我思考了这个问题:用户是否可以拓展?比如在「企查查」通过词搜索获取电话号码?将三元素拆开:「企查查」 -> 「微博主页」、「小红书主页」、「公众号主页」、「知乎个人首页」、「抖音主页」等等「词搜索」 -> 「相关推荐」、「搜索词关联推荐」、「竞品粉丝列表」等等「电话号码」 -> 「微信号」、「QQ 号」、「微信二维码」、「群二维码」等等仅列出来的,就有很多种组合。 抽几个比较牛的组合看看:通过相关推荐进入「小红书主页」,批量获取主页博主微信号通过「公众号主页」,进入文章页面,批量获取号主微信二维码等等三、小结还有很多大量的场景,涉及重复、有规则逻辑的工作,都可以用 RPA 开发一个数字化机器人帮助完成。比如:店群运营的,需要 RPA 批量商品上下架机器人图文内容运营的,需要 RPA 图文群发机器人,一键群发到各个图文平台:今日头条、知乎等短视频内容运营的,需要 RPA 短视频群发机器人,一键群发到各个短视频平台:抖音、视频号、快手等私域运营的,需要 RPA 微信群发&主动加人机器人人事的,需要 Boss 直聘机器人:自动发送面试要求,自动发送问候语,自动交换微信等等RPA 的目标:让人在机械的工作时间中解放出来,把时间留给做更有意义的事情。时间就是价值,那些机械的软件工作太多,通过招人解决,招人还有管人 & 发社保多累。那就用 RPA 数字化人帮你解决,希望这次的分享能给圈友们在生财路上提高生财的速度,让团队的时间放在最重要的事情上。
在之前的文章 《Nacos 本地单机版部署步骤和使用》 中,大家应该了解了 Nacos 是什么?其中 Nacos 提供了动态配置服务功能一、Nacos 动态配置服务是什么?官方是这么说的:Nacos 动态配置服务是什么?动态配置服务可以以中心化、外部化和动态化的方式管理所有环境的应用配置和服务配置。动态配置消除了配置变更时重新部署应用和服务的需要,让配置管理变得更加高效和敏捷。配置中心化管理让实现无状态服务变得更简单,让服务按需弹性扩展变得更容易。Nacos 控制台的功能Nacos 提供了一个简洁易用的 UI (控制台样例 Demo) 用来管理所有的服务和应用的配置。Nacos 还提供包括配置版本跟踪、金丝雀发布、一键回滚配置以及客户端配置更新状态跟踪在内的一系列开箱即用的配置管理特性,更安全地在生产环境中管理配置变更和降低配置变更带来的风险。二、实战:Nacos 实现服务配置中心下面通过两个大模块实现:在 Nacos 中新建或修改配置在 Spring Cloud 应用中加载 Nacos 配置2.1 在 Nacos 新建配置根据上篇文章,部署运行 Nacos ,然后打开配置管理 - 配置列表页面。地址: http://localhost:8848/nacos/index.html#/configurationManagement点击右上角创建按钮,进入新建配置页面,新建配置如图所示:配置详解:Data ID :配置为 config-service.yml 。Data ID 是指定配置且保证全局唯一性。Group :默认配置为 DEFAULT_GROUP,不需要修改。配置格式 : 选择 YAML 配置文件格式配置内容 : 具体配置的内容。这里简单配置了个键值对,其实实际应用场景,会配置包括存储配置、端口配置和各种中间件配置等Nacos Data ID 标准格式如下:${prefix}-${spring.profiles.active}.${file-extension}其中:prefix :默认为 spring.application.name 的值spring.profiles.active:该案例为空,一般指定 dev test 等环境配置file-extension:配置内容格式2.2 创建 Spring Cloud 应用1、创建应用新建工程,工程名为:springcloud-nacos-config-sample工程项目地址在:Github:https://github.com/JeffLi1993/springcloud-learning-example/tree/master/springcloud-nacos-config-sampleGitee:https://gitee.com/jeff1993/springcloud-learning-example/tree/master/springcloud-nacos-config-sample2、配置 pom 依赖pom.xml 代码如下:<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>springcloud</groupId> <artifactId>springcloud-nacos-config-sample</artifactId> <version>0.0.1-SNAPSHOT</version> <name>springcloud-nacos-config-sample :: Nacos 服务配置中心案例</name> <!-- Spring Boot 启动父依赖 --> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.3.2.RELEASE</version> </parent> <dependencies> <!-- Nacos Config 依赖 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> </dependency> <!-- Spring Boot Web 依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- Spring Boot Test 依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.10</version> <optional>true</optional> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> <version>0.2.2.RELEASE</version> </dependency> <!-- Spring Cloud Hoxton.SR12 版本依赖 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Hoxton.SR7</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <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> </plugins> </build> </project>其中依赖了:Spring Cloud Alibaba Nacos Config 依赖Spring Cloud Hoxton.SR12 版本依赖3、创建配置文件在应用工程的 resources 目录下,创建 application.yml 文件,填入如下信息:server: port: 8083 # 服务端口 spring: application: name: config-service # 服务名称其中:server.port 指定了服务端口 8083spring.application.name 指定了服务名称 config-service ,要跟 Nacos 后台新建配置的 Data ID 值保持一致。为啥呢?因为看一下 Nacos Config 源码 org.springframework.cloud.alibaba.nacos.client.NacosPropertySourceLocator#locate 。源码如下:if (StringUtils.isEmpty(dataIdPrefix)) { dataIdPrefix = env.getProperty("spring.application.name"); }如果 Data ID 没有配置,则读取 spring.application.name 服务名称配置。继续创建 bootstrap.yml 文件,填入以下信息:spring: cloud: nacos: config: server-addr: 127.0.0.1:8848 # Nacos 配置中心地址 file-extension: yml # 配置文件格式其中:nacos.config.server-addr 指定了 Nacos 地址和端口nacos.config.file-extension 指定了配置文件格式为 yml4、创建测试类和启动类新建 Spring Cloud 应用启动类 ConfigApplication,代码如下:/** * Spring Boot 服务启动类 * * Created by bysocket.com on 21/12/06. */ @SpringBootApplication // Spring Boot 应用标识 public class ProviderApplication { public static void main(String[] args) { // 程序启动入口 // 启动嵌入式的 Tomcat 并初始化 Spring 环境及其各 Spring 组件 SpringApplication.run(ProviderApplication.class,args); } }然后新建测试控制类 ConfigController,代码如下:/** * Config 案例 * <p> * Created by bysocket.com on 21/12/07. */ @RestController @Slf4j @RefreshScope @Data public class ConfigController { @Value("${blog.name}") private String blogName; @GetMapping("/get") public String get() { return "ConfigController#get blog name = " + getBlogName(); } }代码详解如下:@Value 注解 :@Value 对 Bean 的字段或者方法参数进行标注,职责是基于表达式给字段或方法参数设置默认属性值。通常格式是注解 + SpEL 表达式,如 @Value("SpEL 表达式")。@RefreshScope 注解 :允许在运行时动态刷新 Bean 的 Scope 实现。如果 Bean 被刷新,则在下次访问 Bean 即执行方法时,会创建一个新实例。这说明在应用运行时,在 Nacos 控制台修改了对应配置的值后,会同时修改和生效该 Bean 这个值,达到动态配置的效果。5、运行测试启动上面的应用,会在控制台看到如下信息:2021-12-09 20:11:43.399 INFO 13909 --- [-127.0.0.1_8848] o.s.c.a.n.c.NacosPropertySourceBuilder : Loading nacos data, dataId: 'config-service.yml', group: 'DEFAULT_GROUP' 2021-12-09 20:11:43.400 INFO 13909 --- [-127.0.0.1_8848] b.c.PropertySourceBootstrapConfiguration : Located property source: [BootstrapPropertySource {name='bootstrapProperties-config-service.yml'}]这里可以看出,已经加载了 Nacos 配置信息 dataId: 'config-service.yml' 和 group: 'DEFAULT_GROUP' 。最后在浏览器打开地址 http://localhost:8083/get ,响应如图所示:动态配置测试然后去 Nacos 控制台,配置列表点击修改 config-service.yml 配置。将 www.bysocekt.com 改成 bysocket.com,然后确认发布。如图所示:可以从控制台看到如下日志:2021-12-09 20:31:30.747 INFO 13909 --- [-127.0.0.1_8848] o.s.c.e.event.RefreshEventListener : Refresh keys changed: [blog.name]重新访问下浏览器该地址 http://localhost:8083/get ,响应如图所示:说明动态刷新配置成功。三、Nacos 实现分布式配置小结本文详细介绍了Spring Cloud 整合 Nacos 实现服务分布配置。关键两点:如何在 Nacos 设置对应的配置如何在工程中通过依赖和注解关联上对应的外化配置参考资料官方案例:https://github.com/nacos-group/nacos-examples/tree/master/nacos-spring-cloud-example官方文档:https://nacos.io/zh-cn/docs/quick-start-spring-cloud.htmlhttps://blog.didispace.com/spring-cloud-alibaba-nacos-config-2/代码示例地址本文案例,可以查看开源项目 springcloud-learning-example 中的 springcloud-nacos-config-sample 模块:Github:https://github.com/JeffLi1993/springcloud-learning-exampleGitee:https://gitee.com/jeff1993/springcloud-learning-example以下系列教程推荐《Spring Cloud 系列教程》《Spring Boot 2.x 系列教程》《Elasticsearch 入门系列教程》(本文完)
本系列是 Spring Cloud 微服务实战系列教程。之前在 《Spring Cloud Eureka 入门 (一)服务注册中心详解》 聊过 Spring Cloud Eureka。那今天聊聊阿里开源的 Nacos ~一、Nacos 是什么?先了解下 Spring Cloud Eureka 是基于 Netflix Eureka (Netflix 是 Java 实现的开源软件)。服务治理(Eureka)包括服务注册、服务发现和服务检测监控等。那 Nacos 致力于发现、配置和管理微服务。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据及流量管理。简而言之,Nacos 包含了微服务的配置管理 + 服务的注册、发现等监控。微服务也包括了 Spring Cloud 的微服务实现。Nacos 其特性重点包含了以下:服务发现和服务健康监测动态配置服务动态 DNS 服务服务及其元数据管理等等官方图如下:二、如何本地部署和使用 Nacos可以在 github 上 Nacos Release 版本列表中下载最新的稳定版本。地址:https://github.com/alibaba/nacos/releases当前稳定版本:2.0.32.1 环境准备Nacos 依赖 Java 环境,所以如果从源码构建并运行 Nacos 需要配置如下:JDK 1.8+Maven 3.2+64 bit OS 支持 Linux/Unix/Mac/Windows,推荐选用 Linux/Unix/Mac如果本机部署的话,不需要从源码构建,则只需要下载编译后的压缩包和 JDK 1.8+ 环境即可2.2 下载编译后的压缩包在 https://github.com/alibaba/nacos/releases 地址中,下载 nacos-server-2.0.3.zip 压缩包。然后执行一下解压命令:unzip nacos-server-2.0.3.zip这样目录中会出现 Nacos 的项目目录2.3 启动前配置目录结构如下:nacos % ls -ltotal 48-rw-r--r--@ 1 qq staff 16583 3 18 2021 LICENSE-rw-r--r--@ 1 qq staff 1305 5 14 2020 NOTICEdrwxr-xr-x@ 8 qq staff 256 12 3 14:25 bindrwxr-xr-x@ 9 qq staff 288 7 27 14:18 confdrwxr-xr-x 6 qq staff 192 12 3 14:43 datadrwxr-xr-x 35 qq staff 1120 12 3 14:25 logsdrwxr-xr-x@ 3 qq staff 96 7 28 19:28 target进入 conf 目录,目录结构如下:conf % ls -ltotal 176-rw-r--r--@ 1 qq staff 1224 6 18 10:39 1.4.0-ipv6_support-update.sql-rw-r--r--@ 1 qq staff 9752 12 3 14:41 application.properties-rw-r--r--@ 1 qq staff 9506 7 27 14:18 application.properties.example-rw-r--r--@ 1 qq staff 670 3 18 2021 cluster.conf.example-rw-r--r--@ 1 qq staff 31156 7 15 19:19 nacos-logback.xml-rw-r--r--@ 1 qq staff 10660 6 18 10:39 nacos-mysql.sql-rw-r--r--@ 1 qq staff 8795 6 18 10:39 schema.sql这次部署,目录中其中两个文件需要知晓:application.properties 配置文件nacos-mysql.sql 数据库 MySQL 库表信息文件1/ 创建数据库 nacos 并执行 nacos-mysql.sql本次测试用 MySQL 作为数据存储,则需要在 MySQL 中新建数据库 nacos,命令如下:CREATE DATABASE nacos然后再数据库中执行建表和插入默认数据操作,具体看 nacos-mysql.sql 文件。2/ 修改 application.properties 配置文件然后打开 application.properties 配置文件,对应的地方改成如下:#*************** Config Module Related Configurations ***************#### If use MySQL as datasource:spring.datasource.platform=mysql### Count of DB:db.num=1### Connect URL of DB:db.url.0=jdbc:mysql://127.0.0.1:3306/nacos?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTCdb.user.0=admindb.password.0=1234562.4 运行并使用 Nacos以 Mac 本机为例,启动下面命令:cd nacos/binsh startup.sh -m standalonestandalone 代表着单机模式运行,非集群模式控制台可以看到以下信息:nacos is starting with standalonenacos is starting,you can check the /Users/qq/project/nacos/logs/start.out那如何看 Nacos 日志,从上面可见日志输出在 nacos/logs 目录,查看 start.out 日志文件执行如下命令:tail -200f /Users/qq/project/nacos/logs/start.out控制台可以看到以下信息: ,--. ,--.'| ,--,: : | Nacos 2.0.3,`--.'`| ' : ,---. Running in stand alone mode, All function modules| : : | | ' ,'\ .--.--. Port: 8848: | \ | : ,--.--. ,---. / / | / / ' Pid: 2452| : ' '; | / \ / \. ; ,. :| : /`./ Console: http://xxxx:8848/nacos/index.html' ' ;. ;.--. .-. | / / '' | |: :| : ;_| | | \ | \__\/: . .. ' / ' | .; : \ \ `. https://nacos.io' : | ; .' ," .--.; |' ; :__| : | `----. \| | '`--' / / ,. |' | '.'|\ \ / / /`--' /' : | ; : .' \ : : `----' '--'. /; |.' | , .-./\ \ / `--'---''---' `--`---' `----'...2021-12-04 15:25:18,201 INFO Tomcat started on port(s): 8848 (http) with context path '/nacos'这说明部署成功,电脑运行 Nacos 很简单,直接打开浏览器,输入地址:http://localhost:8848/nacos/index.html账号密码默认都为 nacos,如图如何关闭呢?关闭命令:sh shutdown.sh控制台会响应:The nacosServer(4317) is running...Send shutdown request to nacosServer(4317) OK三、Nacos 小结本文主要描述 Nacos 和 Nacos 的使用场景,然后本地部署 Nacos,达到可用状态。如果需要服务器部署,切记不能单机模式。Nacos 主要是微服务的配置中心和服务注册中心。后续系列文章,会结合 Spring Cloud 微服务实践 Nacos 细节。
这是泥瓦匠的第103篇原创 《程序兵法:Java String 源码的排序算法(一)》 文章工程: JDK 1.8 工程名:algorithm-core-learning # StringComparisonDemo 工程地址:https://github.com/JeffLi1993/algorithm-core-learning 一、前言 Q:什么是选择问题?选择问题,是假设一组 N 个数,要确定其中第 K 个最大值者。比如 A 与 B 对象需要哪个更大?又比如:要考虑从一些数组中找出最大项? 解决选择问题,需要对象有个能力,即比较任意两个对象,并确定哪个大,哪个小或者相等。找出最大项问题的解决方法,只要依次用对象的比较(Comparable)能力,循环对象列表,一次就能解决。 那么 JDK 源码如何实现比较(Comparable)能力的呢? 二、java.lang.Comparable 接口 Comparable 接口,从 JDK 1.2 版本就有了,历史算悠久。Comparable 接口强制了实现类对象列表的排序。其排序称为自然顺序,其 compareTo 方法,称为自然比较法。 该接口只有一个方法 public int compareTo(T o); ,可以看出 入参 T o :实现该接口类,传入对应的要被比较的对象 返回值 int:正数、负数和 0 ,代表大于、小于和等于 对象的集合列表(Collection List)或者数组(arrays) ,也有对应的工具类可以方便的使用: java.util.Collections#sort(List) 列表排序 java.util.Arrays#sort(Object[]) 数组排序 那 String 对象如何被比较的? 三、String 源码中的算法 String 源码中可以看到 String JDK 1.0 就有了。那么应该是 JDK 1.2 的时候,String 类实现了 Comparable 接口,并且传入需要被比较的对象是 String。对象如图: String 是一个 final 类,无法从 String 扩展新的类。从 114 行,可以看出字符串的存储结构是字符(Char)数组。先可以看看一个字符串比较案例,代码如下: /** * 字符串比较案例 * * Created by bysocket on 19/5/10. */ public class StringComparisonDemo { public static void main(String[] args) { String foo = "ABC"; // 前面和后面每个字符完全一样,返回 0 String bar01 = "ABC"; System.out.println(foo.compareTo(bar01)); // 前面每个字符完全一样,返回:后面就是字符串长度差 String bar02 = "ABCD"; String bar03 = "ABCDE"; System.out.println(foo.compareTo(bar02)); // -1 (前面相等,foo 长度小 1) System.out.println(foo.compareTo(bar03)); // -2 (前面相等,foo 长度小 2) // 前面每个字符不完全一样,返回:出现不一样的字符 ASCII 差 String bar04 = "ABD"; String bar05 = "aABCD"; System.out.println(foo.compareTo(bar04)); // -1 (foo 的 'C' 字符 ASCII 码值为 67,bar04 的 'D' 字符 ASCII 码值为 68。返回 67 - 68 = -1) System.out.println(foo.compareTo(bar05)); // -32 (foo 的 'A' 字符 ASCII 码值为 65,bar04 的 'a' 字符 ASCII 码值为 97。返回 65 - 97 = -32) String bysocket01 = "泥瓦匠"; String bysocket02 = "瓦匠"; System.out.println(bysocket01.compareTo(bysocket02));// -2049 (泥 和 瓦的 Unicode 差值) } } 运行结果如下: 0 -1 -2 -1 -32 -2049 可以看出, compareTo 方法是按字典顺序比较两个字符串。具体比较规则可以看代码注释。比较规则如下: 字符串的每个字符完全一样,返回 0 字符串前面部分的每个字符完全一样,返回:后面就是两个字符串长度差 字符串前面部分的每个字符存在不一样,返回:出现不一样的字符 ASCII 码的差值 中文比较返回对应的 Unicode 编码值(Unicode 包含 ASCII) foo 的 'C' 字符 ASCII 码值为 67 bar04 的 'D' 字符 ASCII 码值为 68。 foo.compareTo(bar04),返回 67 - 68 = -1 常见字符 ASCII 码,如图所示 再看看 String 的 compareTo 方法如何实现字典顺序的。源码如图: 源码解析如下: 第 1156 行:获取当前字符串和另一个字符串,长度较小的长度值 lim 第 1161 行:如果 lim 大于 0 (较小的字符串非空),则开始比较 第 1164 行:当前字符串和另一个字符串,依次字符比较。如果不相等,则返回两字符的 Unicode 编码值的差值 第 1169 行:当前字符串和另一个字符串,依次字符比较。如果均相等,则返回两个字符串长度的差值 所以要排序,肯定先有比较能力,即实现 Comparable 接口。然后实现此接口的对象列表(和数组)可以通过 Collections.sort(和 Arrays.sort)进行排序。 还有 TreeSet 使用树结构实现(红黑树),集合中的元素进行排序。其中排序就是实现 Comparable 此接口 另外,如果没有实现 Comparable 接口,使用排序时,会抛出 java.lang.ClassCastException 异常。详细看《Java 集合:三、HashSet,TreeSet 和 LinkedHashSet比较》https://www.bysocket.com/archives/195 四、小结 上面也说到,这种比较其实有一定的弊端: 默认 compareTo 不忽略字符大小写。如果需要忽略,则重新自定义 compareTo 方法 无法进行二维的比较决策。比如判断 2 1 矩形和 3 3 矩形,哪个更大? 比如有些类无法实现该接口。一个 final 类,也无法扩展新的类。其也有解决方案:函数对象(Function Object) 方法参数:定义一个没有数据只有方法的类,并传递该类的实例。一个函数通过将其放在一个对象内部而被传递。这种对象通常叫做函数对象(Funtion Object) 在接口方法设计中, T execute(Callback callback) 参数中使用 callback 类似。比如在 Spring 源码中,可以看出很多设计是:聚合优先于继承或者实现。这样可以减少很多继承或者实现。类似 SpringJdbcTemplate 场景设计,可以考虑到这种 Callback 设计实现。 代码示例 本文示例读者可以通过查看下面仓库的中: StringComparisonDemo 字符串比较案例案例: Github:https://github.com/JeffLi1993/algorithm-core-learning Gitee:https://gitee.com/jeff1993/algorithm-core-learning 如果您对这些感兴趣,欢迎 star、follow、收藏、转发给予支持! 参考资料 《数据结构与算法分析:Java语言描述(原书第3版)》 https://en.wikipedia.org/wiki/Unicode https://www.cnblogs.com/vamei/tag/%E7%AE%97%E6%B3%95/ https://www.bysocket.com/archives/2314/algorithm
摘要: 原创出处 https://www.bysocket.com 「公众号:泥瓦匠BYSocket 」欢迎关注和转载,保留摘要,谢谢! 这是泥瓦匠的第102篇原创 03:WebFlux Web CRUD 实践 文章工程: JDK 1.8 Maven 3.5.2 Spring Boot 2.1.3.RELEASE 工程名:springboot-webflux-2-restful 工程地址:见文末 一、前言 上一篇基于功能性端点去创建一个简单服务,实现了 Hello 。这一篇用 Spring Boot WebFlux 的注解控制层技术创建一个 CRUD WebFlux 应用,让开发更方便。这里我们不对数据库储存进行访问,因为后续会讲到,而且这里主要是讲一个完整的 WebFlux CRUD。 二、结构 这个工程会对城市(City)进行管理实现 CRUD 操作。该工程创建编写后,得到下面的结构,其目录结构如下: ├── pom.xml ├── src │ └── main │ ├── java │ │ └── org │ │ └── spring │ │ └── springboot │ │ ├── Application.java │ │ ├── dao │ │ │ └── CityRepository.java │ │ ├── domain │ │ │ └── City.java │ │ ├── handler │ │ │ └── CityHandler.java │ │ └── webflux │ │ └── controller │ │ └── CityWebFluxController.java │ └── resources │ └── application.properties └── target 如目录结构,我们需要编写的内容按顺序有: 对象 数据访问层类 Repository 处理器类 Handler 控制器类 Controller 三、对象 新建包 org.spring.springboot.domain ,作为编写城市实体对象类。新建城市(City)对象 City,代码如下: /** * 城市实体类 * */ public class City { /** * 城市编号 */ private Long id; /** * 省份编号 */ private Long provinceId; /** * 城市名称 */ private String cityName; /** * 描述 */ private String description; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public Long getProvinceId() { return provinceId; } public void setProvinceId(Long provinceId) { this.provinceId = provinceId; } public String getCityName() { return cityName; } public void setCityName(String cityName) { this.cityName = cityName; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } } 城市包含了城市编号、省份编号、城市名称和描述。具体开发中,会使用 Lombok 工具来消除冗长的 Java 代码,尤其是 POJO 的 getter / setter 方法。具体查看 Lombok 官网地址:projectlombok.org。 四、数据访问层 CityRepository 新建包 org.spring.springboot.dao ,作为编写城市数据访问层类 Repository。新建 CityRepository,代码如下: import org.spring.springboot.domain.City; import org.springframework.stereotype.Repository; import java.util.Collection; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicLong; @Repository public class CityRepository { private ConcurrentMap<Long, City> repository = new ConcurrentHashMap<>(); private static final AtomicLong idGenerator = new AtomicLong(0); public Long save(City city) { Long id = idGenerator.incrementAndGet(); city.setId(id); repository.put(id, city); return id; } public Collection<City> findAll() { return repository.values(); } public City findCityById(Long id) { return repository.get(id); } public Long updateCity(City city) { repository.put(city.getId(), city); return city.getId(); } public Long deleteCity(Long id) { repository.remove(id); return id; } } @Repository 用于标注数据访问组件,即 DAO 组件。实现代码中使用名为 repository 的 Map 对象作为内存数据存储,并对对象具体实现了具体业务逻辑。CityRepository 负责将 Book 持久层(数据操作)相关的封装组织,完成新增、查询、删除等操作。 这里不会涉及到数据存储这块,具体数据存储会在后续介绍。 五、处理器类 Handler 新建包 org.spring.springboot.handler ,作为编写城市处理器类 CityHandler。新建 CityHandler,代码如下: import org.spring.springboot.dao.CityRepository; import org.spring.springboot.domain.City; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @Component public class CityHandler { private final CityRepository cityRepository; @Autowired public CityHandler(CityRepository cityRepository) { this.cityRepository = cityRepository; } public Mono<Long> save(City city) { return Mono.create(cityMonoSink -> cityMonoSink.success(cityRepository.save(city))); } public Mono<City> findCityById(Long id) { return Mono.justOrEmpty(cityRepository.findCityById(id)); } public Flux<City> findAllCity() { return Flux.fromIterable(cityRepository.findAll()); } public Mono<Long> modifyCity(City city) { return Mono.create(cityMonoSink -> cityMonoSink.success(cityRepository.updateCity(city))); } public Mono<Long> deleteCity(Long id) { return Mono.create(cityMonoSink -> cityMonoSink.success(cityRepository.deleteCity(id))); } } @Component 泛指组件,当组件不好归类的时候,使用该注解进行标注。然后用 final 和 @Autowired 标注在构造器注入 CityRepository Bean,代码如下: private final CityRepository cityRepository; @Autowired public CityHandler(CityRepository cityRepository) { this.cityRepository = cityRepository; } 从返回值可以看出,Mono 和 Flux 适用于两个场景,即: Mono:实现发布者,并返回 0 或 1 个元素,即单对象 Flux:实现发布者,并返回 N 个元素,即 List 列表对象 有人会问,这为啥不直接返回对象,比如返回 City/Long/List。原因是,直接使用 Flux 和 Mono 是非阻塞写法,相当于回调方式。利用函数式可以减少了回调,因此会看不到相关接口。反应了是 WebFlux 的好处:集合了非阻塞 + 异步。 5.1 Mono Mono 是什么? 官方描述如下:A Reactive Streams Publisher with basic rx operators that completes successfully by emitting an element, or with an error. Mono 是响应流 Publisher ,即要么成功发布元素,要么错误。如图所示: Mono 常用的方法有: Mono.create():使用 MonoSink 来创建 Mono Mono.justOrEmpty():从一个 Optional 对象或 null 对象中创建 Mono。 Mono.error():创建一个只包含错误消息的 Mono Mono.never():创建一个不包含任何消息通知的 Mono Mono.delay():在指定的延迟时间之后,创建一个 Mono,产生数字 0 作为唯一值 5.2 Flux Flux 是什么? 官方描述如下:A Reactive Streams Publisher with rx operators that emits 0 to N elements, and then completes (successfully or with an error). Flux 是响应流 Publisher ,即要么成功发布 0 到 N 个元素,要么错误。Flux 其实是 Mono 的一个补充。如图所示: 所以要注意:如果知道 Publisher 是 0 或 1 个,则用 Mono。 Flux 最值得一提的是 fromIterable 方法。fromIterable(Iterable<? extends T> it) 可以发布 Iterable 类型的元素。当然,Flux 也包含了基础的操作:map、merge、concat、flatMap、take,这里就不展开介绍了。 六、控制器类 Controller Spring Boot WebFlux 也可以使用自动配置加注解驱动的模式来进行开发。 新建包目录 org.spring.springboot.webflux.controller ,并在目录中创建名为 CityWebFluxController 来处理不同的 HTTP Restful 业务请求。代码如下: import org.spring.springboot.domain.City; import org.spring.springboot.handler.CityHandler; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @RestController @RequestMapping(value = "/city") public class CityWebFluxController { @Autowired private CityHandler cityHandler; @GetMapping(value = "/{id}") public Mono<City> findCityById(@PathVariable("id") Long id) { return cityHandler.findCityById(id); } @GetMapping() public Flux<City> findAllCity() { return cityHandler.findAllCity(); } @PostMapping() public Mono<Long> saveCity(@RequestBody City city) { return cityHandler.save(city); } @PutMapping() public Mono<Long> modifyCity(@RequestBody City city) { return cityHandler.modifyCity(city); } @DeleteMapping(value = "/{id}") public Mono<Long> deleteCity(@PathVariable("id") Long id) { return cityHandler.deleteCity(id); } } 这里按照 REST 风格实现接口。那具体什么是 REST? REST 是属于 WEB 自身的一种架构风格,是在 HTTP 1.1 规范下实现的。Representational State Transfer 全称翻译为表现层状态转化。Resource:资源。比如 newsfeed;Representational:表现形式,比如用JSON,富文本等;State Transfer:状态变化。通过HTTP 动作实现。 理解 REST ,要明白五个关键要素: 资源(Resource) 资源的表述(Representation) 状态转移(State Transfer) 统一接口(Uniform Interface) 超文本驱动(Hypertext Driven) 6 个主要特性: 面向资源(Resource Oriented) 可寻址(Addressability) 连通性(Connectedness) 无状态(Statelessness) 统一接口(Uniform Interface) 超文本驱动(Hypertext Driven) 具体这里就不一一展开,详见 http://www.infoq.com/cn/articles/understanding-restful-style。 请求入参、Filters、重定向、Conversion、formatting 等知识会和以前 MVC 的知识一样,详情见文档:https://docs.spring.io/spring/docs/current/spring-framework-reference/web-reactive.html 七、运行工程 一个 CRUD 的 Spring Boot Webflux 工程就开发完毕了,下面运行工程验证下。使用 IDEA 右侧工具栏,点击 Maven Project Tab ,点击使用下 Maven 插件的 install 命令。或者使用命令行的形式,在工程根目录下,执行 Maven 清理和安装工程的指令: cd springboot-webflux-2-restful mvn clean install 在控制台中看到成功的输出: ... 省略 [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 01:30 min [INFO] Finished at: 2017-10-15T10:00:54+08:00 [INFO] Final Memory: 31M/174M [INFO] ------------------------------------------------------------------------ 在 IDEA 中执行 Application 类启动,任意正常模式或者 Debug 模式。可以在控制台看到成功运行的输出: ... 省略 2018-04-10 08:43:39.932 INFO 2052 --- [ctor-http-nio-1] r.ipc.netty.tcp.BlockingNettyContext : Started HttpServer on /0:0:0:0:0:0:0:0:8080 2018-04-10 08:43:39.935 INFO 2052 --- [ main] o.s.b.web.embedded.netty.NettyWebServer : Netty started on port(s): 8080 2018-04-10 08:43:39.960 INFO 2052 --- [ main] org.spring.springboot.Application : Started Application in 6.547 seconds (JVM running for 9.851) 打开 POST MAN 工具,开发必备。进行下面操作: 新增城市信息 POST http://127.0.0.1:8080/city 获取城市信息列表 GET http://127.0.0.1:8080/city 其他接口就不演示了。 八、总结 这里,探讨了 Spring WebFlux 的一些功能,构建没有底层数据库的基本 CRUD 工程。为了更好的展示了如何创建 Flux 流,以及如何对其进行操作。下面会讲到如何操作数据存储。 系列教程目录 《01:WebFlux 系列教程大纲》 《02:WebFlux 快速入门实践》 《03:WebFlux Web CRUD 实践》 《04:WebFlux 整合 Mongodb》 《05:WebFlux 整合 Thymeleaf》 《06:WebFlux 中 Thymeleaf 和 Mongodb 实践》 《07:WebFlux 整合 Redis》 《08:WebFlux 中 Redis 实现缓存》 《09:WebFlux 中 WebSocket 实现通信》 《10:WebFlux 集成测试及部署》 《11:WebFlux 实战图书管理系统》 代码示例 本文示例读者可以通过查看下面仓库的中的模块工程名: 2-x-spring-boot-webflux-handling-errors: Github:https://github.com/JeffLi1993/springboot-learning-example Gitee:https://gitee.com/jeff1993/springboot-learning-example 如果您对这些感兴趣,欢迎 star、follow、收藏、转发给予支持! 参考资料 Spring Boot 2.x WebFlux 系列:https://www.bysocket.com/archives/2290 spring.io 官方文档 以下专题教程也许您会有兴趣 《Spring Boot 2.x 系列教程》 https://www.bysocket.com/springboot 《Java 核心系列教程》 https://www.bysocket.com/archives/2100
摘要: 原创出处 https://www.bysocket.com 欢迎关注和转载,保留摘要,谢谢! 02:WebFlux 快速入门实践 文章工程: JDK 1.8 Maven 3.5.2 Spring Boot 2.1.3.RELEASE 工程名:springboot-webflux-1-quickstart 工程地址:见文末 一、Spring Boot 2.0 spring.io 官网有句醒目的话是: BUILD ANYTHING WITH SPRING BOOT Spring Boot (Boot 顾名思义,是引导的意思)框架是用于简化 Spring 应用从搭建到开发的过程。应用开箱即用,只要通过一个指令,包括命令行 java -jar 、SpringApplication 应用启动类 、 Spring Boot Maven 插件等,就可以启动应用了。另外,Spring Boot 强调只需要很少的配置文件,所以在开发生产级 Spring 应用中,让开发变得更加高效和简易。目前,Spring Boot 版本是 2.x 版本。Spring Boot 包括 WebFlux。 二、Spring Boot 2.0 WebFlux 了解 WebFlux ,首先了解下什么是 Reactive Streams。Reactive Streams 是 JVM 中面向流的库标准和规范: 处理可能无限数量的元素 按顺序处理 组件之间异步传递 强制性非阻塞背压(Backpressure) 2.1 Backpressure(背压) 背压是一种常用策略,使得发布者拥有无限制的缓冲区存储元素,用于确保发布者发布元素太快时,不会去压制订阅者。 2.2 Reactive Streams(响应式流) 一般由以下组成: 发布者:发布元素到订阅者 订阅者:消费元素 订阅:在发布者中,订阅被创建时,将与订阅者共享 处理器:发布者与订阅者之间处理数据 2.3 响应式编程 有了 Reactive Streams 这种标准和规范,利用规范可以进行响应式编程。那再了解下什么是 Reactive programming 响应式编程。响应式编程是基于异步和事件驱动的非阻塞程序,只是垂直通过在 JVM 内启动少量线程扩展,而不是水平通过集群扩展。这就是一个编程范例,具体项目中如何体现呢? 响应式项目编程实战中,通过基于 Reactive Streams 规范实现的框架 Reactor 去实战。Reactor 一般提供两种响应式 API : Mono:实现发布者,并返回 0 或 1 个元素 Flux:实现发布者,并返回 N 个元素 2.4 Spring Webflux Spring Boot Webflux 就是基于 Reactor 实现的。Spring Boot 2.0 包括一个新的 spring-webflux 模块。该模块包含对响应式 HTTP 和 WebSocket 客户端的支持,以及对 REST,HTML 和 WebSocket 交互等程序的支持。一般来说,Spring MVC 用于同步处理,Spring Webflux 用于异步处理。 Spring Boot Webflux 有两种编程模型实现,一种类似 Spring MVC 注解方式,另一种是使用其功能性端点方式。注解的会在第二篇文章讲到,下面快速入门用 Spring Webflux 功能性方式实现。 三、Spring Boot 2.0 WebFlux 特性 常用的 Spring Boot 2.0 WebFlux 生产的特性如下: 响应式 API 编程模型 适用性 内嵌容器 Starter 组件 还有对日志、Web、消息、测试及扩展等支持。 3.1 响应式 API Reactor 框架是 Spring Boot Webflux 响应库依赖,通过 Reactive Streams 并与其他响应库交互。提供了 两种响应式 API : Mono 和 Flux。一般是将 Publisher 作为输入,在框架内部转换成 Reactor 类型并处理逻辑,然后返回 Flux 或 Mono 作为输出。 3.2 适用性 一图就很明确了,WebFlux 和 MVC 有交集,方便大家迁移。但是注意: MVC 能满足场景的,就不需要更改为 WebFlux。 要注意容器的支持,可以看看下面内嵌容器的支持。 微服务体系结构,WebFlux 和 MVC 可以混合使用。尤其开发 IO 密集型服务的时候,选择 WebFlux 去实现。 3.3 编程模型 Spring 5 web 模块包含了 Spring WebFlux 的 HTTP 抽象。类似 Servlet API , WebFlux 提供了 WebHandler API 去定义非阻塞 API 抽象接口。可以选择以下两种编程模型实现: 注解控制层。和 MVC 保持一致,WebFlux 也支持响应性 @RequestBody 注解。 功能性端点。基于 lambda 轻量级编程模型,用来路由和处理请求的小工具。和上面最大的区别就是,这种模型,全程控制了请求 – 响应的生命流程 3.4 内嵌容器 跟 Spring Boot 大框架一样启动应用,但 WebFlux 默认是通过 Netty 启动,并且自动设置了默认端口为 8080。另外还提供了对 Jetty、Undertow 等容器的支持。开发者自行在添加对应的容器 Starter 组件依赖,即可配置并使用对应内嵌容器实例。 但是要注意,必须是 Servlet 3.1+ 容器,如 Tomcat、Jetty;或者非 Servlet 容器,如 Netty 和 Undertow。 3.5 Starter 组件 跟 Spring Boot 大框架一样,Spring Boot Webflux 提供了很多 “开箱即用” 的 Starter 组件。Starter 组件是可被加载在应用中的 Maven 依赖项。只需要在 Maven 配置中添加对应的依赖配置,即可使用对应的 Starter 组件。例如,添加 spring-boot-starter-webflux 依赖,就可用于构建响应式 API 服务,其包含了 Web Flux 和 Tomcat 内嵌容器等。 开发中,很多功能是通过添加 Starter 组件的方式来进行实现。那么,Spring Boot 2.x 常用的 Starter 组件有哪些呢? 四、Spring Boot 2.0 WebFlux 组件 Spring Boot WebFlux 官方提供了很多 Starter 组件,每个模块会有多种技术实现选型支持,来实现各种复杂的业务需求: Web:Spring WebFlux 模板引擎:Thymeleaf 存储:Redis、MongoDB、Cassandra。不支持 MySQL 内嵌容器:Tomcat、Jetty、Undertow 五、快速入门 5.1 Spring Initializr 快速构建项目骨架 Spring Boot Maven 工程,就是普通的 Maven 工程,加入了对应的 Spring Boot 依赖即可。Spring Initializr 则是像代码生成器一样,自动就给你出来了一个 Spring Boot Maven 工程。Spring Initializr 有两种方式可以得到 Spring Boot Maven 骨架工程: 5.1.1 start.spring.io 在线生成 Spring 官方提供了名为 Spring Initializr 的网站,去引导你快速生成 Spring Boot 应用。网站地址为:https://start.spring.io,操作步骤如下: 第一步,选择 Maven 或者 Gradle 构建工具,开发语言 Java 、Kotlin 或者 Groovy,最后确定 Spring Boot 版本号。这里默认选择 Maven 构建工具、Java 开发语言和 Spring Boot 2.0.1。 第二步,输入 Maven 工程信息,即项目组 groupId 和名字 artifactId。这里对应 Maven 信息为: groupId:springboot artifactId:sspringboot-webflux-1-quickstart 这里默认版本号 version 为 0.0.1-SNAPSHOT 。三个属性在 Maven 依赖仓库是唯一标识的。 第三步,选择工程需要的 Starter 组件和其他依赖。最后点击生成按钮,即可获得骨架工程压缩包。这里快速入门,只要选择 Reactive Web 即可。如图 1-8 所示。 5.2 配置 POM 依赖 检查工程 POM 文件中,是否配置了 spring-boot-starter-webflux 依赖。如果是上面自动生成的,配置如下: <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>io.projectreactor</groupId> <artifactId>reactor-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> spring-boot-starter-webflux 依赖,是我们核心需要学习 webflux 的包,里面默认包含了 spring-boot-starter-reactor-netty 、spring 5 webflux 包。也就是说默认是通过 netty 启动的。 reactor-test、spring-boot-starter-test 两个依赖搭配是用于单元测试。 spring-boot-maven-plugin 是 Spring Boot Maven 插件,可以运行、编译等调用。 5.3 编写处理器类 Handler 新建包 org.spring.springboot.handler ,作为编写功能处理类。新建城市(City)例子的处理类 CityHandler,代码如下: import org.springframework.http.MediaType; import org.springframework.stereotype.Component; import org.springframework.web.reactive.function.BodyInserters; import org.springframework.web.reactive.function.server.ServerRequest; import org.springframework.web.reactive.function.server.ServerResponse; import reactor.core.publisher.Mono; @Component public class CityHandler { public Mono<ServerResponse> helloCity(ServerRequest request) { return ServerResponse.ok().contentType(MediaType.TEXT_PLAIN) .body(BodyInserters.fromObject("Hello, City!")); } } ServerResponse 是对响应的封装,可以设置响应状态,响应头,响应正文。比如 ok 代表的是 200 响应码、MediaType 枚举是代表这文本内容类型、返回的是 String 的对象。 这里用 Mono 作为返回对象,是因为返回包含了一个 ServerResponse 对象,而不是多个元素。 5.4 编写路由器类 Router 新建 org.spring.springboot.router 包,作为编写路由器类。新建城市(City)例子的路由类 CityRouter,代码如下: import org.spring.springboot.handler.CityHandler; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.MediaType; import org.springframework.web.reactive.function.server.RequestPredicates; import org.springframework.web.reactive.function.server.RouterFunction; import org.springframework.web.reactive.function.server.RouterFunctions; import org.springframework.web.reactive.function.server.ServerResponse; @Configuration public class CityRouter { @Bean public RouterFunction<ServerResponse> routeCity(CityHandler cityHandler) { return RouterFunctions .route(RequestPredicates.GET("/hello") .and(RequestPredicates.accept(MediaType.TEXT_PLAIN)), cityHandler::helloCity); } } RouterFunctions 对请求路由处理类,即将请求路由到处理器。这里将一个 GET 请求 /hello 路由到处理器 cityHandler 的 helloCity 方法上。跟 Spring MVC 模式下的 HandleMapping 的作用类似。 RouterFunctions.route(RequestPredicate, HandlerFunction) 方法,对应的入参是请求参数和处理函数,如果请求匹配,就调用对应的处理器函数。 到这里一个简单的服务就写好了,下面怎么运行该服务。 5.5 启动运行项目 一个简单的 Spring Boot Webflux 工程就开发完毕了,下面运行工程验证下。使用 IDEA 右侧工具栏,点击 Maven Project Tab ,点击使用下 Maven 插件的 install 命令。或者使用命令行的形式,在工程根目录下,执行 Maven 清理和安装工程的指令: cd springboot-webflux-1-quickstart mvn clean install 在控制台中看到成功的输出: ... 省略 [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 01:30 min [INFO] Finished at: 2017-10-15T10:00:54+08:00 [INFO] Final Memory: 31M/174M [INFO] ------------------------------------------------------------------------ 5.5.1 运行工程 在 IDEA 中执行 Application 类启动,任意正常模式或者 Debug 模式。可以在控制台看到成功运行的输出: ... 省略 2018-04-10 08:43:39.932 INFO 2052 --- [ctor-http-nio-1] r.ipc.netty.tcp.BlockingNettyContext : Started HttpServer on /0:0:0:0:0:0:0:0:8080 2018-04-10 08:43:39.935 INFO 2052 --- [ main] o.s.b.web.embedded.netty.NettyWebServer : Netty started on port(s): 8080 2018-04-10 08:43:39.960 INFO 2052 --- [ main] org.spring.springboot.Application : Started Application in 6.547 seconds (JVM running for 9.851) 一看,确实是 Netty 启动的。 打开浏览器,访问 /hello 地址,会看到如图所示的返回结果: 六、总结 本文主要讲了 Spring Boot 2.0 WebFlux 背景和快速入门使用。用的是基于功能性端点去创建一个服务,但这个有点代码偏多。下一章一个 CRUD 我们使用注解控制层,让开发更方便。 系列教程目录 《01:WebFlux 系列教程大纲》 《02:WebFlux 快速入门实践》 《03:WebFlux Web CRUD 实践》 《04:WebFlux 整合 Mongodb》 《05:WebFlux 整合 Thymeleaf》 《06:WebFlux 中 Thymeleaf 和 Mongodb 实践》 《07:WebFlux 整合 Redis》 《08:WebFlux 中 Redis 实现缓存》 《09:WebFlux 中 WebSocket 实现通信》 《10:WebFlux 集成测试及部署》 《11:WebFlux 实战图书管理系统》 代码示例 本文示例读者可以通过查看下面仓库的中的模块工程名: 2-x-spring-boot-webflux-handling-errors: Github:https://github.com/JeffLi1993/springboot-learning-example Gitee:https://gitee.com/jeff1993/springboot-learning-example 如果您对这些感兴趣,欢迎 star、follow、收藏、转发给予支持! 参考资料 Spring Boot 2.x WebFlux 系列:https://www.bysocket.com/archives/2290 spring.io 官方文档 以下专题教程也许您会有兴趣 《Spring Boot 2.x 系列教程》 https://www.bysocket.com/springboot 《Java 核心系列教程》 https://www.bysocket.com/archives/2100
摘要: 原创出处 https://www.bysocket.com 本文内容 为什么要全局异常处理? WebFlux REST 全局异常处理实战 小结 摘录:只有不断培养好习惯,同时不断打破坏习惯,我们的行为举止才能够自始至终都是正确的。 一、为什么要全局异常处理? 前后端分离开发,一般提供 REST API,正常返回会有响应体,异常情况下会有对应的错误码响应。 挺多人咨询的,Spring Boot MVC 异常处理用切面 @RestControllerAdvice 注解去实现去全局异常处理。那 WebFlux 如何处理异常?如何实现统一错误码异常处理? 全局异常处理的好处: 异常错误码等统一维护 避免一些重复代码 二、WebFlux REST 全局异常处理实战 下面介绍如何统一拦截异常,进行响应处理。 2.1 工程信息 运行环境:JDK 7 或 8,Maven 3.0+ 技术栈:SpringBoot 2.1.3 代码地址:https://github.com/JeffLi1993/springboot-learning-example 模块工程名: 2-x-spring-boot-webflux-handling-errors 工程结构: ├── pom.xml └── src └── main ├── java │ └── org │ └── spring │ └── springboot │ ├── Application.java │ ├── error │ │ ├── GlobalErrorAttributes.java │ │ ├── GlobalErrorWebExceptionHandler.java │ │ └── GlobalException.java │ ├── handler │ │ └── CityHandler.java │ └── router │ └── CityRouter.java └── resources └── application.properties application.properties 无须配置,默认即可 Application Spring Boot 应用启动类,是可以用来启动 Spring Boot 应用。其包含了 @SpringBootApplication 注解和 SpringApplication 类,并调用 SpringApplication 类的 run() 方法,就可以启动该应用。 具体实现类的关系图如下: 2.2 CityRouter 路由器类 城市路由器代码如下: @Configuration public class CityRouter { @Bean public RouterFunction<ServerResponse> routeCity(CityHandler cityHandler) { return RouterFunctions.route(RequestPredicates.GET("/hello").and(RequestPredicates.accept(MediaType.TEXT_PLAIN)), cityHandler::helloCity); } } RouterFunctions 对请求路由处理类,即将请求路由到处理器,这将一个 GET 请求 /hello 路由到处理器 cityHandler 的 helloCity 方法上。跟 Spring MVC 模式下的 HandleMapping 类似。 RouterFunctions.route(RequestPredicate, HandlerFunction) 方法,对应的 参是请求参数和处理函数,如果请求匹配,就调 对应的处理器函数。 2.3 CityHandler 服务处理类 城市服务器处理类,代码如下: @Component public class CityHandler { public Mono<ServerResponse> helloCity(ServerRequest request) { return ServerResponse.ok().body(sayHelloCity(request), String.class); } private Mono<String> sayHelloCity(ServerRequest request) { Optional<String> cityParamOptional = request.queryParam("city"); if (!cityParamOptional.isPresent()) { throw new GlobalException(HttpStatus.INTERNAL_SERVER_ERROR, "request param city is ERROR"); } return Mono.just("Hello," + cityParamOptional.get()); } } Mono:实现发布者,并返回 0 或 1 个元素,即单对象。Mono 是响应流 Publisher 具有基础 rx 操作符。可以成功发布元素或者错误。用 Mono 作为返回对象,是因为返回包含了一个 ServerResponse 对象,而不是多个元素。 ServerResponse 是对响应的封装,可以设置响应状态,响应头,响应正文。比如 ok 代表的是 200 响应码、MediaType 枚举是代表这文本内容类型、返回的是 String 的对象。 ServerRequest 是对请求的封装。从请求中拿出 city 的值,如果没有的话则抛出对应的异常。GlobalException 是封装的全局异常。 Mono.justOrEmpty():从一个 Optional 对象或 null 对象中创建 Mono。 2.4 GlobalError 处理类 如图: GlobalException 全局异常类,代码如下: public class GlobalException extends ResponseStatusException { public GlobalException(HttpStatus status, String message) { super(status, message); } public GlobalException(HttpStatus status, String message, Throwable e) { super(status, message, e); } } GlobalErrorAttributes 全局异常属性值类,代码如下: @Component public class GlobalErrorAttributes extends DefaultErrorAttributes { @Override public Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace) { Map<String, Object> map = super.getErrorAttributes(request, includeStackTrace); if (getError(request) instanceof GlobalException) { GlobalException ex = (GlobalException) getError(request); map.put("exception", ex.getClass().getSimpleName()); map.put("message", ex.getMessage()); map.put("status", ex.getStatus().value()); map.put("error", ex.getStatus().getReasonPhrase()); return map; } map.put("exception", "SystemException"); map.put("message", "System Error , Check logs!"); map.put("status", "500"); map.put("error", " System Error "); return map; } } 重写了父类 DefaultErrorAttributes 默认错误属性类的 getErrorAttributes 获取错误属性方法,从服务请求封装 ServerRequest 中获取对应的异常。 然后判断是否是 GlobalException,如果是 CityHandler 服务处理类抛出的 GlobalException,则返回对应的异常的信息。 GlobalErrorWebExceptionHandler 全局异常处理类,代码如下: @Component @Order(-2) public class GlobalErrorWebExceptionHandler extends AbstractErrorWebExceptionHandler { public GlobalErrorWebExceptionHandler(GlobalErrorAttributes g, ApplicationContext applicationContext, ServerCodecConfigurer serverCodecConfigurer) { super(g, new ResourceProperties(), applicationContext); super.setMessageWriters(serverCodecConfigurer.getWriters()); super.setMessageReaders(serverCodecConfigurer.getReaders()); } @Override protected RouterFunction<ServerResponse> getRoutingFunction(final ErrorAttributes errorAttributes) { return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse); } private Mono<ServerResponse> renderErrorResponse(final ServerRequest request) { final Map<String, Object> errorPropertiesMap = getErrorAttributes(request, false); return ServerResponse.status(HttpStatus.BAD_REQUEST) .contentType(MediaType.APPLICATION_JSON_UTF8) .body(BodyInserters.fromObject(errorPropertiesMap)); } } 代码解析如下: AbstractErrorWebExceptionHandler 抽象类是用来处理全局错误时进行扩展和实现 @Order 注解标记 AspectJ 的切面排序,值越小拥有越高的优先级,这里设置优先级偏高。 构造函数将 GlobalErrorAttributes 全局异常属性值类设置到 AbstractErrorWebExceptionHandler 抽象类的局部变量中。 重写 getRoutingFunction 方法,设置对应的 RequestPredicates 和 Mono 服务响应对象 将 GlobalErrorAttributes 的全局异常属性值 map,设置到新的 ServerResponse 即可。 到此基本结束。Spring Boot MVC 错误码如何实战,参考地址:https://www.bysocket.com/archives/1692 2.5 运行验证 在 IDEA 中执行 Application 类启动,任意正常模式或者 Debug 模式。然后打开浏览器访问: http://localhost:8080/hello 异常界面如下: 可见,这是在 CityHandler 城市服务处理类逻辑中抛出的全局异常信息。那么正常情况会是如何? 改下 URL ,访问如下: http://localhost:8080/hello?city=WenLing 正常界面如下: 三、小结 在 Spring 框架中没有代表错误响应的类,只是返回响应对象,一个 Map。如果需要定义业务的错误码返回体,参考错误码如何实战,参考地址:https://www.bysocket.com/archives/1692。 本文重点还是有别于 Spring Boot 传统 MVC 模式统一异常处理,实战了 WebFlux 全局异常处理机制。实战中这块扩展需要考虑: 异常分层,从基类中扩展出来 错误码设计分层,易扩展,比如在错误码中新增调用量字段… 代码示例 本文示例读者可以通过查看下面仓库的中的模块工程名: 2-x-spring-boot-webflux-handling-errors: Github:https://github.com/JeffLi1993/springboot-learning-example Gitee:https://gitee.com/jeff1993/springboot-learning-example 如果您对这些感兴趣,欢迎 star、follow、收藏、转发给予支持! 参考资料 WebFlux REST API 全局异常处理:https://www.bysocket.com/archives/2100 https://dzone.com/articles/exception-handling-in-spring-boot-webflux-reactive 以下专题教程也许您会有兴趣 《Spring Boot 2.x 系列教程》 https://www.bysocket.com/springboot 《Java 核心系列教程》 https://www.bysocket.com/archives/2100 文章导航
目录 为啥要解决数据重复插入? 解决方案实战 可落地小总结 一、为啥要解决数据重复插入? 问题起源,微信小程序抽风 wx.request() 重复请求服务器提交数据。后端服务也很简单,伪代码如下: class SignLogService { public void saveSignLog(SignLogDO log) { // 简单插入做记录 SignLogDAO.insert(log); } } 发现数据库会存在重复数据行,提交时间一模一样。但业务需求是不能有多余的 log 出现,这明显是个问题。 问题是,重复请求导致的数据重复插入。这问题造成的后果很明显: 数据冗余,可能不单单多一条 有些业务需求不能有多余数据,造成服务问题 问题如图所示: 解决方式:如何将 同请求 A,不执行插入,而是读取前一个请求插入的数据并返回。解决后流程应该如下: 二、解决方案实战 1.单库单表解决方案 唯一索引 + 唯一字段 幂等 上面说的那种业务场景:sign_log 表会有 user_id、sign_id、sign_time 等。那么每次签到,每个人每天只有一条签到记录。 数据库层采取唯一索引的形式,保证数据记录唯一性。即 UNIQUE 约束,UNIQUE 约束唯一标识数据库表中的每条记录。另外,user_id,sign_id,sign_time 三个组合适唯一字段。创表的伪代码如下: CREATE TABLE sign_log ( id int NOT NULL, user_id int NOT NULL, sign_id int, sign_time int, CONSTRAINT unique_sign_log UNIQUE (user_id,sign_id,sign_time) ) 重点是 CONSTRAINT unique_sign_log UNIQUE (user_id,sign_id,sign_time)。有个小问题,数据量大的时候,每条记录都会有对应的唯一索引,比较耗资源。那么这样就行了吗? 答案是不行,服务不够健壮。第一个请求插入成功,第二个请求直接报错,Java 服务会抛出 DuplicateKeyException 。 简单的幂等写法操作即可,伪代码如下: class SignLogService { public SingLogDO saveSignLog(SignLogDO log) { // 幂等处理 SignLogDO insertLog = null; try { insertLog = signLogDAO.insert(log); } catch (DuplicateKeyException e) { insertLog = selectByUniqueKeys(userId,signId,signTime); } return insertLog; } } 的确,流量不是很大,也不算很高并发。重复写问题,这样处理即可。那大流量、高并发场景咋搞 2.分库分表解决方案 流量大了后,单库单表会演变成分库分表。那么基于单表的唯一索引形式,在碰到分表就无法保证呢,插入的地方可能是两个分表 A1 和 A2。 解决思路:将数据的唯一性条件放到其他存储,并进行锁控制 还是上面的例子,每天,每次签到,每个人只有一条签到记录。那么使用分布式锁 Redis 的解决方案。大致伪代码如下: a.加锁 // 加锁 jedis.set(lockKey, requestId, "NX", "PX", expireTime); lockKey 最简单的是 user_id + sign_id + sign_time expireTime 设置为一天 b.解锁 // 解锁 jedis.eval(script, lockKey,requestId); c.幂等代码加强 class SignLogService { public SingLogDO saveSignLog(SignLogDO log) { // 幂等校验 SignLogDO existLog = selectByUniqueKeys(userId,signId,signTime); if(Objects.nonNull(existLog)) { return existLog; } // 加锁 jedis.set SignLogDO insertLog = signLogDAO.insert(log); // 解锁 jedis.eval return insertLog; } } 这个方案还是不是很成熟,大家参考下即可。 三、可落地小总结 解决方案实战中,了解具体术。归纳如下: 幂等:保证多次同意请求后结果一致 并发控制:单表唯一索引、分布式多表分布式锁 降级兜底方案:分布式锁锁失效 – 考虑乐观锁兜底 参考资料 重复插入方案: http://www.bysocket.com/archives/2266 《阿里巴巴 Java 开发手册》 摘要: 原创出处 https://www.bysocket.com 「公众号:泥瓦匠BYSocket 」欢迎关注和转载,保留摘要,谢谢!
摘要: 原创出处:www.bysocket.com 泥瓦匠BYSocket 希望转载,保留摘要,谢谢! Thymeleaf 是一种模板语言。那模板语言或模板引擎是什么?常见的模板语言都包含以下几个概念:数据(Data)、模板(Template)、模板引擎(Template Engine)和结果文档(Result Documents)。 数据数据是信息的表现形式和载体,可以是符号、文字、数字、语音、图像、视频等。数据和信息是不可分离的,数据是信息的表达,信息是数据的内涵。数据本身没有意义,数据只有对实体行为产生影响时才成为信息。 模板模板,是一个蓝图,即一个与类型无关的类。编译器在使用模板时,会根据模板实参对模板进行实例化,得到一个与类型相关的类。 模板引擎模板引擎(这里特指用于Web开发的模板引擎)是为了使用户界面与业务数据(内容)分离而产生的,它可以生成特定格式的文档,用于网站的模板引擎就会生成一个标准的HTML文档。 结果文档一种特定格式的文档,比如用于网站的模板引擎就会生成一个标准的HTML文档。 模板语言用途广泛,常见的用途如下: 页面渲染 文档生成 代码生成 所有 “数据+模板=文本” 的应用场景 这里案例用途自然是 页面渲染,下面在 Spring Boot 中整合 Thymeleaf 实现完整 Web 案例。 一、运行 chapter-2-spring-boot-quick-start chapter-2-spring-boot-quick-start 工程用的是内存式数据库,不需要配置数据源。下载运行即可。 1. 下载工程 git clone 下载工程 springboot-learning-example ,项目地址见 GitHub:https://github.com/JeffLi1993/springboot-learning-example,即: git clone https://github.com/JeffLi1993/springboot-learning-example.git 2. 工程结构 用 IDEA 打开工程,可以看到子工程 chapter-2-spring-boot-quick-start ,其目录如下: ├── pom.xml └── src ├── main │ ├── java │ │ └── spring │ │ └── boot │ │ └── core │ │ ├── QuickStartApplication.java │ │ ├── domain │ │ │ ├── User.java │ │ │ └── UserRepository.java │ │ ├── service │ │ │ ├── UserService.java │ │ │ └── impl │ │ │ └── UserServiceImpl.java │ │ └── web │ │ └── UserController.java │ └── resources │ ├── application.properties │ ├── static │ │ ├── css │ │ │ └── default.css │ │ └── images │ │ └── favicon.ico │ └── templates │ ├── userForm.html │ └── userList.html └── test └── java └── spring └── boot └── core ├── QuickStartApplicationTests.java └── domain └── UserRepositoryTests.java 对应目录: org.spring.springboot.controller - Controller 层 org.spring.springboot.dao - 数据操作层 DAO org.spring.springboot.domain - 实体类 org.spring.springboot.service - 业务逻辑层 Application - 应用启动类 application.properties - 应用配置文件 模板是会用到下面两个目录 static 目录是存放 CSS、JS 等资源文件 templates 目录是存放视图 3. 编译运行工程 在该工程根目录,运行 maven 指令进行编译: cd chapter-2-spring-boot-quick-start mvn clean install 编译工程成功后,右键运行名为 QuickStartApplication.java 应用启动类的 main 函数,然后浏览器访问 localhost:8080/users 即可:用户列表页面:用户编辑页面: 二、详解 chapter-2-spring-boot-quick-start 工程代码: 1. pom.xml Thymeleaf 依赖 使用模板引擎,就在 pom.xml 加入 Thymeleaf 组件依赖: <!-- 模板引擎 Thymeleaf 依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> Thymeleaf 是什么?Thymeleaf is a modern server-side Java template engine for both web and standalone environments. Thymeleaf's main goal is to bring elegant natural templates to your development workflow — HTML that can be correctly displayed in browsers and also work as static prototypes, allowing for stronger collaboration in development teams. Thymeleaf 是新一代 Java 模板引擎,在 Spring 4 后推荐使用。 整体个 pom.xml 配置如下: <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>spring.boot.core</groupId> <artifactId>chapter-2-spring-boot-quick-start</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>chapter-2-spring-boot-quick-start</name> <description>第二章快速入门案例</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.7.RELEASE</version> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <!-- Web 依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- 单元测试依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!-- Spring Data JPA 依赖 :: 数据持久层框架 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <!-- h2 数据源连接驱动 --> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope> </dependency> <!-- 模板引擎 Thymeleaf 依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> </dependencies> <build> <plugins> <!-- Spring Boot Maven 插件 --> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project> 2. Thymeleaf 依赖配置 在 Spring Boot 项目中加入 Thymeleaf 依赖,即可启动其默认配置。如果想要自定义配置,可以在 application.properties 配置如下: spring.thymeleaf.cache=true # Enable template caching. spring.thymeleaf.check-template=true # Check that the template exists before rendering it. spring.thymeleaf.check-template-location=true # Check that the templates location exists. spring.thymeleaf.enabled=true # Enable Thymeleaf view resolution for Web frameworks. spring.thymeleaf.encoding=UTF-8 # Template files encoding. spring.thymeleaf.excluded-view-names= # Comma-separated list of view names that should be excluded from resolution. spring.thymeleaf.mode=HTML5 # Template mode to be applied to templates. See also StandardTemplateModeHandlers. spring.thymeleaf.prefix=classpath:/templates/ # Prefix that gets prepended to view names when building a URL. spring.thymeleaf.reactive.max-chunk-size= # Maximum size of data buffers used for writing to the response, in bytes. spring.thymeleaf.reactive.media-types= # Media types supported by the view technology. spring.thymeleaf.servlet.content-type=text/html # Content-Type value written to HTTP responses. spring.thymeleaf.suffix=.html # Suffix that gets appended to view names when building a URL. spring.thymeleaf.template-resolver-order= # Order of the template resolver in the chain. spring.thymeleaf.view-names= # Comma-separated list of view names that can be resolved. 3. Thymeleaf 使用 Controller 如何将 View 指向 Thymeleaf 用户控制层代码如下: @Controller @RequestMapping(value = "/users") // 通过这里配置使下面的映射都在 /users public class UserController { @Autowired UserService userService; // 用户服务层 /** * 获取用户列表 * 处理 "/users" 的 GET 请求,用来获取用户列表 * 通过 @RequestParam 传递参数,进一步实现条件查询或者分页查询 */ @RequestMapping(method = RequestMethod.GET) public String getUserList(ModelMap map) { map.addAttribute("userList", userService.findAll()); return "userList"; } /** * 显示创建用户表单 * */ @RequestMapping(value = "/create", method = RequestMethod.GET) public String createUserForm(ModelMap map) { map.addAttribute("user", new User()); map.addAttribute("action", "create"); return "userForm"; } /** * 创建用户 * 处理 "/users" 的 POST 请求,用来获取用户列表 * 通过 @ModelAttribute 绑定参数,也通过 @RequestParam 从页面中传递参数 */ @RequestMapping(value = "/create", method = RequestMethod.POST) public String postUser(@ModelAttribute User user) { userService.insertByUser(user); return "redirect:/users/"; } /** * 显示需要更新用户表单 * 处理 "/users/{id}" 的 GET 请求,通过 URL 中的 id 值获取 User 信息 * URL 中的 id ,通过 @PathVariable 绑定参数 */ @RequestMapping(value = "/update/{id}", method = RequestMethod.GET) public String getUser(@PathVariable Long id, ModelMap map) { map.addAttribute("user", userService.findById(id)); map.addAttribute("action", "update"); return "userForm"; } /** * 处理 "/users/{id}" 的 PUT 请求,用来更新 User 信息 * */ @RequestMapping(value = "/update", method = RequestMethod.POST) public String putUser(@ModelAttribute User user) { userService.update(user); return "redirect:/users/"; } /** * 处理 "/users/{id}" 的 GET 请求,用来删除 User 信息 */ @RequestMapping(value = "/delete/{id}", method = RequestMethod.GET) public String deleteUser(@PathVariable Long id) { userService.delete(id); return "redirect:/users/"; } } ModelMap 对象来进行数据绑定到视图。return 字符串,该字符串对应的目录在 resources/templates 下的模板名字。@ModelAttribute 注解是用来获取页面 Form 表单提交的数据,并绑定到 User 数据对象。 Form 表单页面 核心代码: <form th:action="@{/users/{action}(action=${action})}" method="post" class="form-horizontal"> <input type="hidden" name="id" th:value="${user.id}"/> <div class="form-group"> <label for="user_name" class="col-sm-2 control-label">名称</label> <div class="col-xs-4"> <input type="text" class="form-control" id="user_name" name="name" th:value="${user.name}" /> </div> </div> <div class="form-group"> <label for="user_age" class="col-sm-2 control-label">年龄:</label> <div class="col-xs-4"> <input type="text" class="form-control" id="user_age" name="age" th:value="${user.age}"/> </div> </div> <div class="form-group"> <label for="user_birthday" class="col-sm-2 control-label">出生日期:</label> <div class="col-xs-4"> <input type="date" class="form-control" id="user_birthday" name="birthday" th:value="${user.birthday}"/> </div> </div> <div class="form-group"> <div class="col-sm-offset-2 col-sm-10"> <input class="btn btn-primary" type="submit" value="提交"/> <input class="btn" type="button" value="返回" onclick="history.back()"/> </div> </div> </form> 这里定义了一个 Form 表单用于新增或者更新用户。 列表页面 代码如下: <table class="table table-hover table-condensed"> <legend> <strong>用户列表</strong> </legend> <thead> <tr> <th>用户编号</th> <th>名称</th> <th>年龄</th> <th>出生时间</th> <th>管理</th> </tr> </thead> <tbody> <tr th:each="user : ${userList}"> <th scope="row" th:text="${user.id}"></th> <td><a th:href="@{/users/update/{userId}(userId=${user.id})}" th:text="${user.name}"></a></td> <td th:text="${user.age}"></td> <td th:text="${user.birthday}"></td> <td><a class="btn btn-danger" th:href="@{/users/delete/{userId}(userId=${user.id})}">删除</a></td> </tr> </tbody> </table> 这里循环了用户列表。 Tymeleaf 的语法糖 我这边也就不详细展开了,大家看看人家写的 http://www.cnblogs.com/nuoyiamy/p/5591559.html或者看看官方文档 http://www.thymeleaf.org/documentation.html 三、本文小结 该文,利用 Thymeleaf 做了个 Web 的 CRUD 案例。大家多指教~ 如以上文章或链接对你有帮助的话,别忘了在文章结尾处评论哈~ 你也可以点击页面右边“分享”悬浮按钮哦,让更多的人阅读这篇文章。
本文提纲 一、前言 二、运行 chapter-5-spring-boot-paging-sorting 工程 三、chapter-5-spring-boot-paging-sorting 工程配置详解 四、小结 运行环境: Mac OS 10.12.x JDK 8 + Spring Boot 2.0.0.M4 一、前言 Spring 2.x 更新了一个小小的功能即: Spring Data Web configuration Spring Boot exposes a new spring.data.web configuration namespace that allows to easily configure paging and sorting. 就是说,可以在 application.properties 中自定义分页和排序相关的默认值和参数名。 具体见地址:https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.0.0-M2-Release-Notes 二、运行工程 git clone 下载工程 spring-boot-core-book-demo ,项目地址见 GitHub - https://github.com/JeffLi1993/spring-boot-core-book-demo。 1. 工程结构 项目结构如下图所示: org.spring.springboot.controller - Controller 层 org.spring.springboot.domain - 实体类及数据操作层 DAO org.spring.springboot.service - 业务逻辑层 PagingSortingApplication - 应用启动类 application.properties - 应用配置文件,应用启动会自动读取配置 具体详细结构如下: ├── pom.xml └── src ├── main │ ├── java │ │ └── spring │ │ └── boot │ │ └── core │ │ ├── PagingSortingApplication.java │ │ ├── domain │ │ │ ├── User.java │ │ │ └── UserRepository.java │ │ ├── service │ │ │ ├── UserService.java │ │ │ └── impl │ │ │ └── UserServiceImpl.java │ │ └── web │ │ └── UserController.java │ └── resources │ ├── application.properties │ └── static └── test 2.编译工程 在项目根目录 spring-boot-core-book-demo,运行 maven 指令去编译工程: mvn clean install 3.运行工程 在 chapter-5-spring-boot-paging-sorting 工程中,右键运行 PagingSortingApplication 应用启动类的 main 函数。待控制台日志中看到启动成功后。 在 PostMan 工具中,新增用户几个: POST http://localhost:8080/users/create Content-Type: application/json { "name":"javaer", "age":22, "birthday":"2019-09-19" } 如图: 重复上面步骤,新增用户 13 个。 然后,调用分页查询用户列表接口: GET http://localhost:8080/users?pageNumber=1&pageSize=3&orderBy=id,desc 如图: 可见,查询出第 2 页的用户数据,并且按 id 倒序。还有可见,返回了分页相关的数据:每页大小(这里是 3 个)、排序、总个数和总页数等。 从应用日志中也可以看出对应的 HQL : 2017-09-20 14:46:16.630 INFO 14593 --- [nio-8080-exec-4] s.b.core.service.impl.UserServiceImpl : 分页查询用户: PageNumber = 1 PageSize = 3 2017-09-20 14:46:16.703 INFO 14593 --- [nio-8080-exec-4] o.h.h.i.QueryTranslatorFactoryInitiator : HHH000397: Using ASTQueryTranslatorFactory Hibernate: select user0_.id as id1_0_, user0_.age as age2_0_, user0_.birthday as birthday3_0_, user0_.name as name4_0_ from user user0_ order by user0_.id desc limit ? offset ? Hibernate: select count(user0_.id) as col_0_0_ from user user0_ 三、工程配置详解 1.pom.xml <?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>spring.boot.core</groupId><artifactId>chapter-5-spring-boot-paging-sorting</artifactId><version>0.0.1-SNAPSHOT</version><packaging>jar</packaging><name>chapter-5-spring-boot-paging-sorting</name><description>第五章数据分页排序案例</description><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.0.0.M4</version><relativePath/> <!-- lookup parent from repository --> </parent><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding><java.version>1.8</java.version></properties><dependencies><!-- Web 依赖 --> <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- 单元测试依赖 --> <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><!-- Spring Data JPA 依赖 :: 数据持久层框架 --> <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId></dependency><!-- h2 数据源连接驱动 --> <dependency><groupId>com.h2database</groupId><artifactId>h2</artifactId><scope>runtime</scope></dependency></dependencies><build><plugins><!-- Spring Boot Maven 插件 --> <plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><version>1.5.1.RELEASE</version></plugin></plugins></build><repositories><repository><id>spring-milestones</id><name>Spring Milestones</name><url>https://repo.spring.io/libs-milestone</url><snapshots><enabled>false</enabled></snapshots></repository></repositories></project> 简单依赖了 Web 依赖、Spring Data JPA 依赖 :: 数据持久层框架,并且使用 h2 内存式数据源。 2.在 application.properties 应用配置文件,增加相关分页排序参数 ## 是否显示 SQL 语句spring.jpa.show-sql=true## DATA WEB 相关配置 {@link SpringDataWebProperties}## 分页大小 默认为 20spring.data.web.pageable.default-page-size=3## 当前页参数名 默认为 pagespring.data.web.pageable.page-parameter=pageNumber## 当前页参数名 默认为 sizespring.data.web.pageable.size-parameter=pageSize## 字段排序参数名 默认为 sortspring.data.web.sort.sort-parameter=orderBy 关于 Data Web 分页和排序相关的配置: 设置 spring.data.web.pageable.default-page-size 可修改分页大小,默认分页大小为 20 设置 spring.data.web.pageable.page-parameter 可修改当前页参数名,默认参数名为 page 设置 pring.data.web.pageable.size-parameter 可修改当前页参数名,默认参数名为 size 设置 spring.data.web.sort.sort-parameter 可修改字段排序参数名,默认参数名为 sort 这里我们修改了各个参数名。如果什么都不设置的话,分页排序查询接口地址如下: GET http://localhost:8080/users?page=1&size=3&sort=id,desc 这里就是,Spring 2.x 更新了一个小小的功能即: 就是说,可以在 application.properties 中自定义分页和排序相关的默认值和参数名。 3.用户持久层操作接口 UserRepository /** * 用户持久层操作接口 * * Created by bysocket on 18/09/2017. */public interface UserRepository extends PagingAndSortingRepository<User, Long> { } 接口只要继承 PagingAndSortingRepository 类即可。默认会提供很多实现,比如 CRUD 相关的实现。支持的默认方法有: count(), findAll(), findOne(ID), delete(ID), deleteAll(), exists(ID), save(DomainObject), save(Iterable<DomainObject>)。 最重要的是, PagingAndSortingRepository 提供了两个接口 Iterable<T> findAll(Sort sort); Page<T> findAll(Pageable pageable); 用来支持 分页 和 排序 的获取数据接口。 4.用户业务层实现类 UserServiceImpl /** * User 业务层实现 * * Created by bysocket on 18/09/2017. */@Servicepublic class UserServiceImpl implements UserService {private static final Logger LOGGER = LoggerFactory.getLogger(UserServiceImpl.class);@Autowired UserRepository userRepository;@Override public Page<User> findByPage(Pageable pageable) {LOGGER.info(" \n 分页查询用户:" + " PageNumber = " + pageable.getPageNumber() + " PageSize = " + pageable.getPageSize());return userRepository.findAll(pageable); }@Override public User insertByUser(User user) {LOGGER.info("新增用户:" + user.toString());return userRepository.save(user); } } 这边没有具体的业务操作,就打印了对应业务层分页相关的参数。 5.用户控制层 UserController /** * 用户控制层 * * Created by bysocket on 18/09/2017. */@RestController@RequestMapping(value = "/users") // 通过这里配置使下面的映射都在 /userspublic class UserController {@Autowired UserService userService; // 用户服务层 /** * 获取用户分页列表 * 处理 "/users" 的 GET 请求,用来获取用户分页列表 * 通过 @RequestParam 传递参数,进一步实现条件查询或者分页查询 * * Pageable 支持的分页参数如下 * page - 当前页 从 0 开始 * size - 每页大小 默认值在 application.properties 配置 */ @RequestMapping(method = RequestMethod.GET)public Page<User> getUserPage(Pageable pageable) {return userService.findByPage(pageable); }/** * 创建用户 * 处理 "/users" 的 POST 请求,用来获取用户列表 * 通过 @RequestBody 绑定实体类参数 */ @RequestMapping(value = "/create", method = RequestMethod.POST)public User postUser(@RequestBody User user) {return userService.insertByUser(user); } } 这里实现了两个 HTTP 服务接口。这次主要在实现 getUserPage 方法,利用分页参数来进行。 page - 当前页 从 0 开始 size - 每页大小 默认值在 application.properties 配置 其他不明白的,可以git clone 下载工程 spring-boot-core-book-demo,工程代码注解很详细,项目地址见 GitHub - https://github.com/JeffLi1993/spring-boot-core-book-demo。 四、小结 还是温故知新,加上一些 Spring 2.x 小新功能 - Spring Data Web configuration 推荐:《泥瓦匠 5 年 Java 的成长感悟(上)》 上一篇:《「北京站」ArchData 技术峰会-文末社区送福利》 最好的赞赏 就是你的关注
“优秀不是过去是一种心态” 「Spring Cloud Eureka 入门系列」Spring Cloud Eureka 入门 (一)服务注册中心详解Spring Cloud Eureka 入门 (二)服务提供者详解Spring Cloud Eureka 入门 (三)服务消费者详解本文提纲1. springcloud-eureka-sample 工程结构2. 运行 springcloud-eureka-client-provider 服务提供者工程3. 详解 springcloud-eureka-client-provider 服务提供者工程 一、springcloud-eureka-sample 工程结构接着上一小节《Spring Cloud Eureka 入门 (一)服务注册中心详解》,我们成功运行了 Spring Cloud Eureka Server 工程作为服务注册中心工程。这小节,我们写一个作为客户端的服务提供者工程,服务提供者向服务中心注册或者下线服务实例。即图中的右侧 1 ,2 流程: springcloud-eureka-sample 工程结构├── springcloud-eureka-client-customer ├── springcloud-eureka-client-provider └── springcloud-eureka-server 上面依次是 服务消费者工程、服务提供者工程和服务注册中心工程。 二、运行 springcloud-eureka-client-provider 服务提供者工程运行环境:JDK 7 或 8,Maven 3.0+技术栈:Spring Cloud Dalston.SR1、 spring-cloud-netflix 1.3.1、Spring Boot 1.5.4自然,我们先得去上一小节《Spring Cloud Eureka 入门 (一)服务注册中心详解》 ,把注册中心工程启动完毕。1. git clone 下载工程 springcloud-learning-example项目地址见 GitHub - https://github.com/JeffLi1993/ ... ample:git clone https://github.com/JeffLi1993/ ... e.git 2. Maven 编译安装这个工程:cd springcloud-learning-example mvn clean install 3. 运行 Eureka 工程 springcloud-eureka-client-provider启动 springcloud-eureka-client-provider 工程启动类 ProviderApplication,启动服务注册中心工程。EurekaServerApplication 类路径:/springcloud-learning-example/springcloud-eureka-sample/springcloud-eureka-client-provider/src/main/java/org/spring/springcloud/ProviderApplication.java控制台 Console 看到这类信息,代表启动成功:2017-07-10 16:03:15.075 INFO 11020 --- [ main] o.s.c.n.e.s.EurekaServiceRegistry : Registering application provider-service with eureka with status UP 2017-07-10 16:03:15.075 INFO 11020 --- [ main] com.netflix.discovery.DiscoveryClient : Saw local status change event StatusChangeEvent [timestamp=1499673795075, current=UP, previous=STARTING] 2017-07-10 16:03:15.079 INFO 11020 --- [nfoReplicator-0] com.netflix.discovery.DiscoveryClient : DiscoveryClient_PROVIDER-SERVICE/10.18.29.31:provider-service:8080: registering service... 2017-07-10 16:03:15.126 INFO 11020 --- [nfoReplicator-0] com.netflix.discovery.DiscoveryClient : DiscoveryClient_PROVIDER-SERVICE/10.18.29.31:provider-service:8080 - registration status: 204 2017-07-10 16:03:15.183 INFO 11020 --- [ main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (http) 2017-07-10 16:03:15.185 INFO 11020 --- [ main] .s.c.n.e.s.EurekaAutoServiceRegistration : Updating port to 8080 2017-07-10 16:03:15.191 INFO 11020 --- [ main] o.s.springcloud.ProviderApplication : Started ProviderApplication in 9.809 seconds (JVM running for 10.981) 可以看出,注册了应用名为 provider-service 的应用,该服务提供者的工程端口为 80804. 访问 Eureka 注册中心可视化界面打开浏览器,访问 http://localhost:8888/ ,如图所示: 可以看到,服务提供者向服务注册中心注册自己的实例,展示了应用名和端口信息等。三、详解 springcloud-eureka-client-provider 服务提供者工程1.springcloud-eureka-server 工程目录结构├── pom.xml └── src └── main ├── java │ └── org │ └── spring │ ├── springcloud │ │ └── ProviderApplication.java │ └── web │ └── ProviderController.java └── resources └── application.yml ProviderApplication.java Eureka Client 启动类,启动服务提供者工程ProviderApplication.java Provider HelloWorld 案例application.yml 配置文件2. pom.xml 配置<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/ma ... gt%3B <modelVersion>4.0.0</modelVersion> <groupId>springcloud</groupId> <artifactId>springcloud-eureka-client-provider</artifactId> <version>0.0.1-SNAPSHOT</version> <name>springcloud-eureka-client-provider :: 服务提供者</name> <!-- Spring Boot 启动父依赖 --> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.4.RELEASE</version> </parent> <dependencies> <!-- Spring Cloud Netflix Eureka Client 依赖 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> </dependency> <!-- Spring Boot Test 依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <dependencyManagement> <dependencies> <!-- Spring Cloud Netflix 依赖 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-netflix</artifactId> <version>1.3.1.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <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> </plugins> </build> </project> 使用的依赖是- spring-cloud-netflix 1.3.1 是 Spring Cloud Dalston.SR1 版本。- spring-cloud-starter-eureka Eureka Client 模块依赖,包含了客户端 client 的依赖,还有 Ribbon 的依赖,如:org.springframework.cloud:spring-cloud-netflix-eureka-client:1.3.1.RELEASEorg.springframework.cloud:spring-cloud-starter-ribbon:1.3.1.RELEASE 3. application.yml 配置server: port: 8080 # 服务端口 eureka: client: service-url: defaultZone: http://localhost:8888/eureka/ # 服务注册中心地址 spring: application: name: provider-service # 服务名称 - server.port 设置工程服务端口- eureka.client.service-url.defaultZone 设置服务注册中心地址 4.注册中心应用启动类/** * Spring Boot Eureka Server 应用启动类 * * Created by bysocket on 21/06/17. */ @EnableEurekaClient // Eureka Client 标识 @SpringBootApplication // Spring Boot 应用标识 public class ProviderApplication { public static void main(String args) { // 程序启动入口 // 启动嵌入式的 Tomcat 并初始化 Spring 环境及其各 Spring 组件 SpringApplication.run(ProviderApplication.class,args); } } @EnableEurekaClient 标志该应用作为 Eureka Client ,并会自动化读取 Eureka 相关配置。6.服务提供者 Hello World 案例/** * Provider HelloWorld 案例 * <p> * Created by bysocket on 06/22/17. */ @RestController public class ProviderController { private static final Logger LOGGER = LoggerFactory.getLogger(ProviderController.class); @Autowired private Registration registration; // 服务注册 @Autowired private DiscoveryClient discoveryClient; // 服务发现客户端 @RequestMapping("/provider") public String provider() { ServiceInstance instance = serviceInstance(); LOGGER.info("provider service, host = " + instance.getHost() + ", service_id = " + instance.getServiceId()); return "Hello,Provider!"; } /** * 获取当前服务的服务实例 * * @return ServiceInstance */ public ServiceInstance serviceInstance() { List<ServiceInstance> list = discoveryClient.getInstances(registration.getServiceId()); if (list != null && list.size() > 0) { return list.get(0); } return null; } } 可以看到注入了 Registration 和 DiscoveryClient 两个对象:- Registration 服务注册接口,包含了获取服务 ID 的方法。- DiscoveryClient 服务发现客户端,具有以下方法: - String description(); 获取描述 - ServiceInstance getLocalServiceInstance(); @Deprecated 方法被删除,推荐不要使用。获取本地服务实例 - List<ServiceInstance> getInstances(String serviceId); 通过服务 ID,获取当前服务的服务实例 - List<String> getServices(); 获取所有服务 ID 列表四、小结此小章节介绍了如何 Eureka 作为服务提供者,并向服务注册中心注册自己实例。 下一小结讲下 服务消费者详解 具体是如何向服务注册中心注册自己,发现其他服务,并调用其他服务的。系列目录如下:Spring Cloud Eureka 入门 (一)服务注册中心详解Spring Cloud Eureka 入门 (二)服务提供者详解Spring Cloud Eureka 入门 (三)服务消费者详解资料:1.《Spring Cloud微服务实战》 2. 官方文档http://cloud.spring.io/spring- ... EASE/ by 泥瓦匠博客 — http://www.bysocket.com/ — https://github.com/JeffLi1993
『 热烈的爱情到订婚早已是定点,婚一结一切了结。现在订了婚,彼此间还留着情感发展的余地,这是桩好事。- 《我们仨》 』 「系列文章」 深入浅出 spring-data-elasticsearch - ElasticSearch 架构初探(一) 深入浅出 spring-data-elasticsearch - 概述(二) 深入浅出 spring-data-elasticsearch - 基本案例详解(三) 深入浅出 spring-data-elasticsearch - 实战案例详解(四) 深入浅出 spring-data-elasticsearch - 架构原理以及源码浅析(五)(拼命编写ing) 运行环境:JDK 7 或 8,Maven 3.0+技术栈:SpringBoot 1.5+, Spring Data Elasticsearch 1.5+ ,ElasticSearch 2.3.2本文提纲一、搜索实战场景需求二、运行 spring-data-elasticsearch-query 工程三、spring-data-elasticsearch-query 工程代码详解 一、搜索实战场景需求搜索的场景会很多,常用的搜索场景,需要搜索的字段很多,但每个字段匹配到后所占的权重又不同。比如电商网站的搜索,搜到商品名称和商品描述,自然商品名称的权重远远大于商品描述。而且单词匹配肯定不如短语匹配。这样就出现了新的需求,如何确定这些短语,即自然分词。那就利用分词器,即可得到所需要的短语,然后进行搜索。下面介绍短语如何进行按权重分匹配搜索。 二、运行 spring-data-elasticsearch-query 工程1. 后台起守护线程启动 Elasticsearchcd elasticsearch-2.3.2/ ./bin/elasticsearch -d git clone 下载工程 springboot-elasticsearch ,项目地址见 GitHub - https://github.com/JeffLi1993/ ... ample。下面开始运行工程步骤(Quick Start):2. 项目结构介绍org.spring.springboot.controller - Controller 层 org.spring.springboot.repository - ES 数据操作层 org.spring.springboot.domain - 实体类 org.spring.springboot.service - ES 业务逻辑层 Application - 应用启动类 application.properties - 应用配置文件,应用启动会自动读取配置 本地启动的 ES ,就不需要改配置文件了。如果连测试 ES 服务地址,需要修改相应配置 3.编译工程在项目根目录 spring-data-elasticsearch-query,运行 maven 指令:mvn clean install 4.运行工程右键运行 Application 应用启动类(位置:org/spring/springboot/Application.java)的 main 函数,这样就成功启动了 spring-data-elasticsearch-query 案例。用 Postman 工具新增两个城市 a. 新增城市信息POST http://127.0.0.1:8080/api/city { "id”:"1", "score":"5", "name":"上海", "description":"上海是个热城市" } POST http://127.0.0.1:8080/api/city { "id":"2", "score”:"4", "name”:”温岭", "description":”温岭是个沿海城市" } 下面是实战搜索语句的接口:GET http://localhost:8080/api/city ... nt%3D城市获取返回结果:返回 JSON 如下:[ { "id": 2, "name": "温岭", "description": "温岭是个沿海城市", "score": 4 }, { "id": 1, "name": "上海", "description": "上海是个好城市", "score": 3 } ] 应用的控制台中,日志打印出查询语句的 DSL : DSL = { "function_score" : { "functions" : [ { "filter" : { "match" : { "name" : { "query" : "城市", "type" : "phrase" } } }, "weight" : 1000.0 }, { "filter" : { "match" : { "description" : { "query" : "城市", "type" : "phrase" } } }, "weight" : 500.0 } ], "score_mode" : "sum", "min_score" : 10.0 } } 三、spring-data-elasticsearch-query 工程代码详解具体代码见 GitHub - https://github.com/JeffLi1993/springboot-learning-example 1.pom.xml 依赖<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/ma ... gt%3B <modelVersion>4.0.0</modelVersion> <groupId>springboot</groupId> <artifactId>spring-data-elasticsearch-crud</artifactId> <version>0.0.1-SNAPSHOT</version> <name>spring-data-elasticsearch-crud :: spring-data-elasticsearch - 基本案例 </name> <!-- Spring Boot 启动父依赖 --> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.1.RELEASE</version> </parent> <dependencies> <!-- Spring Boot Elasticsearch 依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-elasticsearch</artifactId> </dependency> <!-- Spring Boot Web 依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- Junit --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> </dependencies> </project> 这里依赖的 spring-boot-starter-data-elasticsearch 版本是 1.5.1.RELEASE,对应的 spring-data-elasticsearch 版本是 2.1.0.RELEASE。对应官方文档:http://docs.spring.io/spring-d ... html/。后面数据操作层都是通过该 spring-data-elasticsearch 提供的接口实现。2. application.properties 配置 ES 地址# ES spring.data.elasticsearch.repositories.enabled = true spring.data.elasticsearch.cluster-nodes = 127.0.0.1:9300 默认 9300 是 Java 客户端的端口。9200 是支持 Restful HTTP 的接口。更多配置: spring.data.elasticsearch.cluster-name Elasticsearch 集群名。(默认值: elasticsearch) spring.data.elasticsearch.cluster-nodes 集群节点地址列表,用逗号分隔。如果没有指定,就启动一个客户端节点。 spring.data.elasticsearch.propertie 用来配置客户端的额外属性。 spring.data.elasticsearch.repositories.enabled 开启 Elasticsearch 仓库。(默认值:true。) 3. ES 数据操作层/** * ES 操作类 * <p> * Created by bysocket on 17/05/2017. */ public interface CityRepository extends ElasticsearchRepository<City, Long> { } 接口只要继承 ElasticsearchRepository 接口类即可,具体使用的是该接口的方法: Iterable<T> search(QueryBuilder query); Page<T> search(QueryBuilder query, Pageable pageable); Page<T> search(SearchQuery searchQuery); Page<T> searchSimilar(T entity, String[] fields, Pageable pageable); 4. 实体类/** * 城市实体类 * <p> * Created by bysocket on 03/05/2017. */ @Document(indexName = "province", type = "city") public class City implements Serializable { private static final long serialVersionUID = -1L; /** * 城市编号 */ private Long id; /** * 城市名称 */ private String name; /** * 描述 */ private String description; /** * 城市评分 */ private Integer score; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public Integer getScore() { return score; } public void setScore(Integer score) { this.score = score; } } 注意a. City 属性名不支持驼峰式。b. indexName 配置必须是全部小写,不然会出异常。org.elasticsearch.indices.InvalidIndexNameException: Invalid index name [provinceIndex], must be lowercase 5. 城市 ES 业务逻辑实现类代码如下:/** * 城市 ES 业务逻辑实现类 * <p> * Created by bysocket on 20/06/2017. */ @Service public class CityESServiceImpl implements CityService { private static final Logger LOGGER = LoggerFactory.getLogger(CityESServiceImpl.class); /* 分页参数 */ Integer PAGE_SIZE = 12; // 每页数量 Integer DEFAULT_PAGE_NUMBER = 0; // 默认当前页码 /* 搜索模式 */ String SCORE_MODE_SUM = "sum"; // 权重分求和模式 Float MIN_SCORE = 10.0F; // 由于无相关性的分值默认为 1 ,设置权重分最小值为 10 @Autowired CityRepository cityRepository; // ES 操作类 public Long saveCity(City city) { City cityResult = cityRepository.save(city); return cityResult.getId(); } @Override public List<City> searchCity(Integer pageNumber, Integer pageSize, String searchContent) { // 校验分页参数 if (pageSize == null || pageSize <= 0) { pageSize = PAGE_SIZE; } if (pageNumber == null || pageNumber < DEFAULT_PAGE_NUMBER) { pageNumber = DEFAULT_PAGE_NUMBER; } LOGGER.info("\n searchCity: searchContent [" + searchContent + "] \n "); // 构建搜索查询 SearchQuery searchQuery = getCitySearchQuery(pageNumber,pageSize,searchContent); LOGGER.info("\n searchCity: searchContent [" + searchContent + "] \n DSL = \n " + searchQuery.getQuery().toString()); Page<City> cityPage = cityRepository.search(searchQuery); return cityPage.getContent(); } /** * 根据搜索词构造搜索查询语句 * * 代码流程: * - 权重分查询 * - 短语匹配 * - 设置权重分最小值 * - 设置分页参数 * * @param pageNumber 当前页码 * @param pageSize 每页大小 * @param searchContent 搜索内容 * @return */ private SearchQuery getCitySearchQuery(Integer pageNumber, Integer pageSize,String searchContent) { // 短语匹配到的搜索词,求和模式累加权重分 // 权重分查询 https://www.elastic.co/guide/c ... .html // - 短语匹配 https://www.elastic.co/guide/c ... .html // - 字段对应权重分设置,可以优化成 enum // - 由于无相关性的分值默认为 1 ,设置权重分最小值为 10 FunctionScoreQueryBuilder functionScoreQueryBuilder = QueryBuilders.functionScoreQuery() .add(QueryBuilders.matchPhraseQuery("name", searchContent), ScoreFunctionBuilders.weightFactorFunction(1000)) .add(QueryBuilders.matchPhraseQuery("description", searchContent), ScoreFunctionBuilders.weightFactorFunction(500)) .scoreMode(SCORE_MODE_SUM).setMinScore(MIN_SCORE); // 分页参数 Pageable pageable = new PageRequest(pageNumber, pageSize); return new NativeSearchQueryBuilder() .withPageable(pageable) .withQuery(functionScoreQueryBuilder).build(); } } 可以看到该过程实现了,短语精准匹配以及匹配到根据字段权重分求和,从而实现按权重搜索查询。代码流程如下:- 权重分查询- 短语匹配- 设置权重分最小值- 设置分页参数注意:- 字段对应权重分设置,可以优化成 enum- 由于无相关性的分值默认为 1 ,设置权重分最小值为 10 权重分查询文档:https://www.elastic.co/guide/c ... .html。短语匹配文档: https://www.elastic.co/guide/c ... .html。 四、小结Elasticsearch 还提供很多高级的搜索功能。这里提供下需要经常逛的相关网站:Elasticsearch 中文社区 https://elasticsearch.cn/topic/elasticsearchElasticsearch: 权威指南-在线版 https://www.elastic.co/guide/cn/elasticsearch/guide/current/index.html 摘要: 原创出处 www.bysocket.com 「泥瓦匠BYSocket 」欢迎转载,保留摘要,谢谢!
『 风云说:能分享自己职位的知识的领导是个好领导。 』运行环境:JDK 7 或 8,Maven 3.0+技术栈:SpringBoot 1.5+, Spring Data Elasticsearch 1.5+ ,ElasticSearch 2.3.2本文提纲一、spring-data-elasticsearch-crud 的工程介绍二、运行 spring-data-elasticsearch-crud 工程三、spring-data-elasticsearch-crud 工程代码详解一、spring-data-elasticsearch-crud 的工程介绍spring-data-elasticsearch-crud 的工程,介绍 Spring Data Elasticsearch 简单的 ES 操作。Spring Data Elasticsearch 可以跟 JPA 进行类比。其使用方法也很简单。二、运行 spring-data-elasticsearch-crud 工程注意的是这里使用的是 ElasticSearch 2.3.2。是因为版本对应关系 https://github.com/spring-projects/spring-data-elasticsearch/wiki/Spring-Data-Elasticsearch---Spring-Boot---version-matrix; Spring Boot Version (x) Spring Data Elasticsearch Version (y) Elasticsearch Version (z)x <= 1.3.5 y <= 1.3.4 z <= 1.7.2*x >= 1.4.x 2.0.0 <=y < 5.0.0** 2.0.0 <= z < 5.0.0*** - 只需要你修改下对应的 pom 文件版本号 ** - 下一个 ES 的版本会有重大的更新 1. 后台起守护线程启动 Elasticsearchcd elasticsearch-2.3.2/ ./bin/elasticsearch -d git clone 下载工程 springboot-elasticsearch ,项目地址见 GitHub - https://github.com/JeffLi1993/ ... ample。下面开始运行工程步骤(Quick Start): 2. 项目结构介绍org.spring.springboot.controller - Controller 层 org.spring.springboot.repository - ES 数据操作层 org.spring.springboot.domain - 实体类 org.spring.springboot.service - ES 业务逻辑层 Application - 应用启动类 application.properties - 应用配置文件,应用启动会自动读取配置 本地启动的 ES ,就不需要改配置文件了。如果连测试 ES 服务地址,需要修改相应配置 3.编译工程在项目根目录 spring-data-elasticsearch-crud,运行 maven 指令:mvn clean install 4.运行工程右键运行 Application 应用启动类(位置:/springboot-learning-example/springboot-elasticsearch/src/main/java/org/spring/springboot/Application.java)的 main 函数,这样就成功启动了 springboot-elasticsearch 案例。用 Postman 工具新增两个城市 a. 新增城市信息POST http://127.0.0.1:8080/api/city { "id”:"1", "score":"5", "name":"上海", "description":"上海是个热城市" } POST http://127.0.0.1:8080/api/city { "id":"2", "score”:"4", "name”:”温岭", "description":”温岭是个沿海城市" } 可以打开 ES 可视化工具 head 插件:http://localhost:9200/_plugin/head/:(如果不知道怎么安装,请查阅 《Elasticsearch 和插件 elasticsearch-head 安装详解》 http://www.bysocket.com/?p=1744 。)在「数据浏览」tab,可以查阅到 ES 中数据是否被插入,插入后的数据格式如下:{ "_index": "cityindex", "_type": "city", "_id": "1", "_version": 1, "_score": 1, "_source": { "id":"2", "score”:"4", "name”:”温岭", "description":”温岭是个沿海城市" } } 下面是基本查询语句的接口:a. 普通查询,查询城市描述GET http://localhost:8080/api/city ... on%3D温岭 返回 JSON 如下:[ { "id": 2, "name": "温岭", "description": "温岭是个沿海城市", "score": 4 } ] b. AND 语句查询GET http://localhost:8080/api/city ... on%3D温岭&score=4 返回 JSON 如下:[ { "id": 2, "name": "温岭", "description": "温岭是个沿海城市", "score": 4 } ] 如果换成 score=5 ,就没有结果了。c. OR 语句查询GET http://localhost:8080/api/city ... on%3D上海&score=4 返回 JSON 如下:[ { "id": 2, "name": "温岭", "description": "温岭是个沿海城市", "score": 4 }, { "id": 1, "name": "上海", "description": "上海是个好城市", "score": 3 } ] d. NOT 语句查询GET http://localhost:8080/api/city ... on%3D温州 返回 JSON 如下:[ { "id": 2, "name": "温岭", "description": "温岭是个沿海城市", "score": 4 }, { "id": 1, "name": "上海", "description": "上海是个好城市", "score": 3 } ] e. LIKE 语句查询GET http://localhost:8080/api/city ... on%3D城市 返回 JSON 如下:[ { "id": 2, "name": "温岭", "description": "温岭是个沿海城市", "score": 4 }, { "id": 1, "name": "上海", "description": "上海是个好城市", "score": 3 } ] 三、spring-data-elasticsearch-crud 工程代码详解具体代码见 GitHub - https://github.com/JeffLi1993/springboot-learning-example 1.pom.xml 依赖<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/ma ... gt%3B <modelVersion>4.0.0</modelVersion> <groupId>springboot</groupId> <artifactId>spring-data-elasticsearch-crud</artifactId> <version>0.0.1-SNAPSHOT</version> <name>spring-data-elasticsearch-crud :: spring-data-elasticsearch - 基本案例 </name> <!-- Spring Boot 启动父依赖 --> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.1.RELEASE</version> </parent> <dependencies> <!-- Spring Boot Elasticsearch 依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-elasticsearch</artifactId> </dependency> <!-- Spring Boot Web 依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- Junit --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> </dependencies> </project> 这里依赖的 spring-boot-starter-data-elasticsearch 版本是 1.5.1.RELEASE,对应的 spring-data-elasticsearch 版本是 2.1.0.RELEASE。对应官方文档:http://docs.spring.io/spring-d ... html/。后面数据操作层都是通过该 spring-data-elasticsearch 提供的接口实现。2. application.properties 配置 ES 地址# ES spring.data.elasticsearch.repositories.enabled = true spring.data.elasticsearch.cluster-nodes = 127.0.0.1:9300 默认 9300 是 Java 客户端的端口。9200 是支持 Restful HTTP 的接口。 更多配置: spring.data.elasticsearch.cluster-name Elasticsearch 集群名。(默认值: elasticsearch) spring.data.elasticsearch.cluster-nodes 集群节点地址列表,用逗号分隔。如果没有指定,就启动一个客户端节点。 spring.data.elasticsearch.propertie 用来配置客户端的额外属性。 spring.data.elasticsearch.repositories.enabled 开启 Elasticsearch 仓库。(默认值:true。)3. ES 数据操作层/** * ES 操作类 * <p> * Created by bysocket on 17/05/2017. */ public interface CityRepository extends ElasticsearchRepository<City, Long> { /** * AND 语句查询 * * @param description * @param score * @return */ List<City> findByDescriptionAndScore(String description, Integer score); /** * OR 语句查询 * * @param description * @param score * @return */ List<City> findByDescriptionOrScore(String description, Integer score); /** * 查询城市描述 * * 等同于下面代码 * @Query("{\"bool\" : {\"must\" : {\"term\" : {\"description\" : \"?0\"}}}}") * Page<City> findByDescription(String description, Pageable pageable); * * @param description * @param page * @return */ Page<City> findByDescription(String description, Pageable page); /** * NOT 语句查询 * * @param description * @param page * @return */ Page<City> findByDescriptionNot(String description, Pageable page); /** * LIKE 语句查询 * * @param description * @param page * @return */ Page<City> findByDescriptionLike(String description, Pageable page); } 接口只要继承 ElasticsearchRepository 类即可。默认会提供很多实现,比如 CRUD 和搜索相关的实现。类似于 JPA 读取数据,是使用 CrudRepository 进行操作 ES 数据。支持的默认方法有: count(), findAll(), findOne(ID), delete(ID), deleteAll(), exists(ID), save(DomainObject), save(Iterable<DomainObject>)。另外可以看出,接口的命名是遵循规范的。常用命名规则如下:关键字 方法命名And findByNameAndPwdOr findByNameOrSexIs findByIdBetween findByIdBetweenLike findByNameLikeNotLike findByNameNotLikeOrderBy findByIdOrderByXDescNot findByNameNot4. 实体类/** * 城市实体类 * <p> * Created by bysocket on 03/05/2017. */ @Document(indexName = "province", type = "city") public class City implements Serializable { private static final long serialVersionUID = -1L; /** * 城市编号 */ private Long id; /** * 城市名称 */ private String name; /** * 描述 */ private String description; /** * 城市评分 */ private Integer score; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public Integer getScore() { return score; } public void setScore(Integer score) { this.score = score; } } 注意a. City 属性名不支持驼峰式。b. indexName 配置必须是全部小写,不然会出异常。org.elasticsearch.indices.InvalidIndexNameException: Invalid index name [provinceIndex], must be lowercase四、小结预告下下一篇《深入浅出 spring-data-elasticsearch - 实战案例详解》,会带来实战项目中涉及到的权重分 & 短语精准匹配的讲解。 摘要: 原创出处 www.bysocket.com 「泥瓦匠BYSocket 」欢迎转载,保留摘要,谢谢!
本文目录一、Elasticsearch 基本术语1.1 文档(Document)、索引(Index)、类型(Type)文档三要素1.2 集群(Cluster)、节点(Node)、分片(Shard)分布式三要素二、Elasticsearch 工作原理2.1 文档存储的路由2.2 如何健康检查2.3 如何水平扩容三、小结 推荐:Spring For All 社区 http://spring4all.com 一、Elasticsearch 基本术语1.1 文档(Document)、索引(Index)、类型(Type)文档三要素文档(Document)文档,在面向对象观念就是一个对象。在 ES 里面,是一个大 JSON 对象,是指定了唯一 ID 的最底层或者根对象。文档的位置由 _index、_type 和 _id 唯一标识。索引(Index)索引,用于区分文档成组,即分到一组的文档集合。索引,用于存储文档和使文档可被搜索。比如项目存索引 project 里面,交易存索引 sales 等。类型(Type)类型,用于区分索引中的文档,即在索引中对数据逻辑分区。比如索引 project 的项目数据,根据项目类型 ui 项目、插画项目等进行区分。和关系型数据库 MySQL 做个类比:Document 类似于 RecordType 类似于 TableIndex 类似于 Database1.2 集群(Cluster)、节点(Node)、分片(Shard)分布式三要素集群(Cluster)服务器集群大家都知道,这里 ES 也是类似的。多个 ElasticSearch 运行实例(节点)组合的组合体是 ElasticSearch 集群。ElasticSearch 是天然的分布式,通过水平扩容为集群添加更多节点。集群是去中心化的,有一个主节点(Master)。主节点是动态选举,因此不会出现单点故障。那分片和节点的配置呢?节点(Node)一个 ElasticSearch 运行实例就是节点。顺着集群来,任何节点都可以被选举成为主节点。主节点负责集群内所以变更,比如索引的增加、删除等。所以集群不会因为主节点流量的增大成为瓶颈。因为任何节点都会成为主节点。下面有 3 个节点,第 1 个节点有:2 个主分片和 1 个副分片。如图: 那么,只有一个节点的 ElasticSearch 服务会存在瓶颈。如图: 分片(Shard)分片,是 ES 节点中最小的工作单元。分片仅仅保存全部数据的一部分,分片的集合是 ES 的索引。分片包括主分片和副分片,主分片是副分片的拷贝。主分片和副分片地工作基本没有大的区别。在索引中全文搜索,然后会查询到每个分片,将每个分配的结果进行全局地收集处理,并返回。二、Elasticsearch 工作原理2.1 文档存储的路由当索引到一个文档(如:报价系统),具体的文档数据(如:报价数据)会存储到一个分片。具体文档数据会被切分,并分别存储在分片 1 或者 分片 2 … 那么如何确定存在哪个分片呢?存储路由过程由下面地公式决定:shard = hash(routing) % number_of_primary_shards routing 是可变值,支持自定义,默认文档 _id。hash 函数生成数字,经过取余算法得到余数,那么这个余数就是分片的位置。这是不是有点负载均衡的类似。 2.2 如何健康检查集群名,集群的健康状态GET http://127.0.0.1:9200/_cluster/stats { "cluster_name": "elasticsearch", "status": "green", "timed_out": false, "number_of_nodes": 1, "number_of_data_nodes": 1, "active_primary_shards": 0, "active_shards": 0, "relocating_shards": 0, "initializing_shards": 0, "unassigned_shards": 0 } status 字段是需要我们关心的。状态可能是下列三个值之一:green 所有的主分片和副本分片都已分配。你的集群是 100% 可用的。 yellow 所有的主分片已经分片了,但至少还有一个副本是缺失的。不会有数据丢失,所以搜索结果依然是完整的。高可用会弱化把 yellow 想象成一个需要及时调查的警告。 red 至少一个主分片(以及它的全部副本)都在缺失中。这意味着你在缺少数据:搜索只能返回部分数据,而分配到这个分片上的写入请求会返回一个异常。 active_primary_shards 集群中的主分片数量active_shards 所有分片的汇总值relocating_shards 显示当前正在从一个节点迁往其他节点的分片的数量。通常来说应该是 0,不过在 Elasticsearch 发现集群不太均衡时,该值会上涨。比如说:添加了一个新节点,或者下线了一个节点。initializing_shards 刚刚创建的分片的个数。unassigned_shards 已经在集群状态中存在的分片。 2.3 如何水平扩容主分片在索引创建已经确定。读操作可以同时被主分片和副分片处理。因此,更多的分片,会拥有更高的吞吐量。自然,需要增加更多的硬件资源支持吞吐量。说明,这里无法提高性能,因为每个分片获得的资源会变少。动态调整副本分片数,按需伸缩集群,比如把副本数默认值为 1 增加到 2:PUT /blogs/_settings { "number_of_replicas" : 2 } 三、小结简单初探了下 ElasticSearch 的相关内容。后面会主要落地到实战,关于 spring-data-elasticsearch 这块的实战。最后,《 深入浅出 spring-data-elasticsearch 》小连载目录如下:深入浅出 spring-data-elasticsearch - ElasticSearch 架构初探(一)深入浅出 spring-data-elasticsearch - 概述(二)深入浅出 spring-data-elasticsearch - 基本案例详解(三)深入浅出 spring-data-elasticsearch - 复杂案例详解(四)深入浅出 spring-data-elasticsearch - 架构原理以及源码浅析(五) 资料:官方《Elasticsearch: 权威指南》https://www.elastic.co/guide/c ... .html 本文作者: 泥瓦匠 原文链接: http://www.bysocket.com 版权归作者所有,转载请注明出处
摘要: 原创出处 www.bysocket.com 「泥瓦匠BYSocket 」欢迎转载,保留摘要,谢谢! 『 预见未来最好的方式就是亲手创造未来 – 《史蒂夫·乔布斯传》 』 运行环境:JDK 7 或 8,Maven 3.0+技术栈:SpringBoot 1.5+,ElasticSearch 2.3.2 本文提纲 一、ES 的使用场景 二、运行 springboot-elasticsearch 工程 三、springboot-elasticsearch 工程代码详解 一、ES 的使用场景 简单说,ElasticSearch(简称 ES)是搜索引擎,是结构化数据的分布式搜索引擎。在《Elasticsearch 和插件 elasticsearch-head 安装详解》 和 《Elasticsearch 默认配置 IK 及 Java AnalyzeRequestBuilder 使用》 我详细的介绍了如何安装,初步使用了 IK 分词器。这里,我主要讲下 SpringBoot 工程中如何使用 ElasticSearch。 ES 的使用场景大致分为两块 1. 全文检索。加上分词(IK 是其中一个)、拼音插件等可以成为强大的全文搜索引擎。 2. 日志统计分析。可以实时动态分析海量日志数据。 二、运行 springboot-elasticsearch 工程 注意的是这里使用的是 ElasticSearch 2.3.2。是因为版本对应关系 : 1 2 3 4 Spring Boot Version (x) Spring Data Elasticsearch Version (y) Elasticsearch Version (z) x <= 1.3.5 y <= 1.3.4 z <= 1.7.2* x >= 1.4.x 2.0.0 <=y < 5.0.0** 2.0.0 <= z < 5.0.0** * - 只需要你修改下对应的 pom 文件版本号 ** - 下一个 ES 的版本会有重大的更新 git clone 下载工程 springboot-elasticsearch ,项目地址见 GitHub – https://github.com/JeffLi1993/springboot-learning-example。 1. 后台起守护线程启动 Elasticsearch 1 2 cd elasticsearch-2.3.2/ ./bin/elasticsearch -d 下面开始运行工程步骤(Quick Start): 2. 项目结构介绍 1 2 3 4 5 6 org.spring.springboot.controller - Controller 层 org.spring.springboot.repository - ES 数据操作层 org.spring.springboot.domain - 实体类 org.spring.springboot.service - ES 业务逻辑层 Application - 应用启动类 application.properties - 应用配置文件,应用启动会自动读取配置 本地启动的 ES ,就不需要改配置文件了。如果连测试 ES 服务地址,需要修改相应配置 3.编译工程 在项目根目录 springboot-elasticsearch,运行 maven 指令: 1 mvn clean install 4.运行工程 右键运行 Application 应用启动类(位置:/springboot-learning-example/springboot-elasticsearch/src/main/java/org/spring/springboot/Application.java)的 main 函数,这样就成功启动了 springboot-elasticsearch 案例。 用 Postman 工具新增两个城市 新增城市信息 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 POST http://127.0.0.1:8080/api/city { "id":"1", "provinceid":"1", "cityname":"温岭", "description":"温岭是个好城市" } POST http://127.0.0.1:8080/api/city { "id":"2", "provinceid":"2", "cityname":"温州", "description":"温州是个热城市" } 可以打开 ES 可视化工具 head 插件:http://localhost:9200/_plugin/head/: (如果不知道怎么安装,请查阅 《Elasticsearch 和插件 elasticsearch-head 安装详解》 。) 在「数据浏览」tab,可以查阅到 ES 中数据是否被插入,插入后的数据格式如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 { "_index": "cityindex", "_type": "city", "_id": "1", "_version": 1, "_score": 1, "_source": { "id": 1, "provinceid": 1, "cityname": "温岭", "description": "温岭是个好城市" } } 下面验证下权重分查询搜索接口的实现: GET http://localhost:8080/api/city/search?pageNumber=0&pageSize=10&searchContent=温岭 数据是会出现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 [ { "id": 1, "provinceid": 1, "cityname": "温岭", "description": "温岭是个好城市" }, { "id": 2, "provinceid": 2, "cityname": "温州", "description": "温州是个热城市" } ] 从启动后台 Console 可以看出,打印出来对应的 DSL 语句: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 { "function_score" : { "functions" : [ { "filter" : { "bool" : { "should" : { "match" : { "cityname" : { "query" : "温岭", "type" : "boolean" } } } } }, "weight" : 1000.0 }, { "filter" : { "bool" : { "should" : { "match" : { "description" : { "query" : "温岭", "type" : "boolean" } } } } }, "weight" : 100.0 } ] } } 为什么会出现 温州 城市呢?因为 function score query 权重分查询,无相关的数据默认分值为 1。如果想除去,设置一个 setMinScore 分值即可。 三、springboot-elasticsearch 工程代码详解 具体代码见 GitHub – https://github.com/JeffLi1993/springboot-learning-example 1.pom.xml 依赖 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>springboot</groupId> <artifactId>springboot-elasticsearch</artifactId> <version>0.0.1-SNAPSHOT</version> <name>springboot-elasticsearch :: 整合 Elasticsearch </name> <!-- Spring Boot 启动父依赖 --> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.1.RELEASE</version> </parent> <dependencies> <!-- Spring Boot Elasticsearch 依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-elasticsearch</artifactId> </dependency> <!-- Spring Boot Web 依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- Junit --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> </dependencies> </project> 这里依赖的 spring-boot-starter-data-elasticsearch 版本是 1.5.1.RELEASE,对应的 spring-data-elasticsearch 版本是 2.1.0.RELEASE。后面数据操作层都是通过该 spring-data-elasticsearch 提供的接口实现。 操作对应官方文档:http://docs.spring.io/spring-data/elasticsearch/docs/2.1.0.RELEASE/reference/html/。 2. application.properties 配置 ES 地址 1 2 3 # ES spring.data.elasticsearch.repositories.enabled = true spring.data.elasticsearch.cluster-nodes = 127.0.0.1:9300 默认 9300 是 Java 客户端的端口。9200 是支持 Restful HTTP 的接口。 更多配置: 1 2 3 4 spring.data.elasticsearch.cluster-name Elasticsearch 集群名。(默认值: elasticsearch) spring.data.elasticsearch.cluster-nodes 集群节点地址列表,用逗号分隔。如果没有指定,就启动一个客户端节点。 spring.data.elasticsearch.propertie 用来配置客户端的额外属性。 spring.data.elasticsearch.repositories.enabled 开启 Elasticsearch 仓库。(默认值:true。) 3. ES 数据操作层 1 2 3 4 5 @Repository public interface CityRepository extends ElasticsearchRepository<City,Long> { } 接口只要继承 ElasticsearchRepository 类即可。默认会提供很多实现,比如 CRUD 和搜索相关的实现。 4. 实体类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 @Document(indexName = "cityindex", type = "city") public class City implements Serializable{ private static final long serialVersionUID = -1L; /** * 城市编号 */ private Long id; /** * 省份编号 */ private Long provinceid; /** * 城市名称 */ private String cityname; /** * 描述 */ private String description; } 注意 index 配置必须是全部小写,不然会暴异常。 org.elasticsearch.indices.InvalidIndexNameException: Invalid index name [cityIndex], must be lowercase 5. ES 业务逻辑层 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 /** * 城市 ES 业务逻辑实现类 * * Created by bysocket on 07/02/2017. */ @Service public class CityESServiceImpl implements CityService { private static final Logger LOGGER = LoggerFactory.getLogger(CityESServiceImpl.class); @Autowired CityRepository cityRepository; @Override public Long saveCity(City city) { City cityResult = cityRepository.save(city); return cityResult.getId(); } @Override public List<City> searchCity(Integer pageNumber, Integer pageSize, String searchContent) { // 分页参数 Pageable pageable = new PageRequest(pageNumber, pageSize); // Function Score Query FunctionScoreQueryBuilder functionScoreQueryBuilder = QueryBuilders.functionScoreQuery() .add(QueryBuilders.boolQuery().should(QueryBuilders.matchQuery("cityname", searchContent)), ScoreFunctionBuilders.weightFactorFunction(1000)) .add(QueryBuilders.boolQuery().should(QueryBuilders.matchQuery("description", searchContent)), ScoreFunctionBuilders.weightFactorFunction(100)); // 创建搜索 DSL 查询 SearchQuery searchQuery = new NativeSearchQueryBuilder() .withPageable(pageable) .withQuery(functionScoreQueryBuilder).build(); LOGGER.info("\n searchCity(): searchContent [" + searchContent + "] \n DSL = \n " + searchQuery.getQuery().toString()); Page<City> searchPageResults = cityRepository.search(searchQuery); return searchPageResults.getContent(); } } 保存逻辑很简单。 分页 function score query 搜索逻辑如下: 先创建分页参数,然后用 FunctionScoreQueryBuilder 定义 Function Score Query,并设置对应字段的权重分值。城市名称 1000 分,description 100 分。 然后创建该搜索的 DSL 查询,并打印出来。 四、小结 实际场景还会很复杂。这里只是点睛之笔,后续大家优化或者更改下 DSL 语句就可以完成自己想要的搜索规则。 推荐:《Spring Boot 整合 Dubbo/ZooKeeper 详解 SOA 案例》 上一篇:《Spring Boot 整合 Mybatis Annotation 注解案例》 欢迎扫一扫我的公众号关注 — 及时得到博客订阅哦! — http://www.bysocket.com/ — — https://github.com/JeffLi1993 —
摘要: 原创出处 www.bysocket.com 「泥瓦匠BYSocket 」欢迎转载,保留摘要,谢谢! 『 公司需要人、产品、业务和方向,方向又要人、产品、业务和方向,方向… 循环』 本文提纲 一、前言 二、运行 springboot-mybatis-annotation 工程 三、springboot-mybatis-annotation 工程配置详解 四、小结 运行环境:JDK 7 或 8、Maven 3.0+技术栈:SpringBoot 1.5+、SpringBoot Mybatis Starter 1.2+ 、MyBatis 3.4+ 前言 距离第一篇 Spring Boot 系列的博文 3 个月了。《Springboot 整合 Mybatis 的完整 Web 案例》第一篇出来是 XML 配置 SQL 的形式。虽然 XML 形式是我比较推荐的,但是注解形式也是方便的。尤其一些小系统,快速的 CRUD 轻量级的系统。 这里感谢晓春 http://xchunzhao.tk/ 的 Pull Request,提供了 springboot-mybatis-annotation 的实现。 一、运行 springboot-mybatis-annotation 工程 由于这篇文章和 《Springboot 整合 Mybatis 的完整 Web 案例》 类似,所以运行这块环境配置大家参考另外一篇兄弟文章。 然后Application 应用启动类的 main 函数,然后在浏览器访问: 1 http://localhost:8080/api/city?cityName=温岭市 可以看到返回的 JSON 结果: 1 2 3 4 5 6 { "id": 1, "provinceId": 1, "cityName": "温岭市", "description": "我的家在温岭。" } 三、springboot-mybatis-annotation 工程配置详解 1.pom 添加 Mybatis 依赖 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>springboot</groupId> <artifactId>springboot-mybatis-annotation</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>springboot-mybatis-annotation</name> <description>Springboot-mybatis :: 整合Mybatis Annotation Demo</description> <!-- Spring Boot 启动父依赖 --> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.1.RELEASE</version> </parent> <properties> <mybatis-spring-boot>1.2.0</mybatis-spring-boot> <mysql-connector>5.1.39</mysql-connector> </properties> <dependencies> <!-- Spring Boot Web 依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- Spring Boot Test 依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!-- Spring Boot Mybatis 依赖 --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>${mybatis-spring-boot}</version> </dependency> <!-- MySQL 连接驱动依赖 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>${mysql-connector}</version> </dependency> <!-- Junit --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> </dependencies> </project> 2.在 CityDao 城市数据操作层接口类添加注解 @Mapper、@Select 和 @Results 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 /** * 城市 DAO 接口类 * * Created by xchunzhao on 02/05/2017. */ @Mapper // 标志为 Mybatis 的 Mapper public interface CityDao { /** * 根据城市名称,查询城市信息 * * @param cityName 城市名 */ @Select("SELECT * FROM city") // 返回 Map 结果集 @Results({ @Result(property = "id", column = "id"), @Result(property = "provinceId", column = "province_id"), @Result(property = "cityName", column = "city_name"), @Result(property = "description", column = "description"), }) City findByName(@Param("cityName") String cityName); } @Mapper 标志接口为 MyBatis Mapper 接口 @Select 是 Select 操作语句 @Results 标志结果集,以及与库表字段的映射关系 其他的注解可以看 org.apache.ibatis.annotations 包提供的,如图: 可以 git clone 下载工程 springboot-learning-example ,springboot-mybatis-annotation 工程代码注解很详细。 https://github.com/JeffLi1993/springboot-learning-example。 四、小结 注解不涉及到配置,更近贴近 0 配置。再次感谢晓春 http://xchunzhao.tk/ 的 Pull Request~ 欢迎扫一扫我的公众号关注 — 及时得到博客订阅哦! — http://www.bysocket.com/ — — https://github.com/JeffLi1993 —
摘要: 原创出处 www.bysocket.com 「泥瓦匠BYSocket 」欢迎转载,保留摘要,谢谢! 『 与其纠结,不如行动学习。Innovate ,And out execute ! 』 本文提纲 一、前言 二、applications.properties 配置清单 三、@Service 服务提供者常用配置 四、@Reference 服务消费者常用配置 五、小结 运行环境:JDK 7 或 8、Maven 3.0+ 技术栈:SpringBoot 1.5+、、Dubbo 2.5+ 一、前言 在泥瓦匠出的Springboot 整合 Dubbo/ZooKeeper 详解 SOA 案例 Spring Boot 中如何使用 Dubbo Activate 扩展点 两篇文章后,很多人跟我聊 Spring Boot 整合 Dubbo 的细节问题。当然最多的是配置问题,比如 Q:如果一个程序既提供服务又是消费者怎么配置 scan package? A(群友周波): 就是 com.xxx.provider 生产者,com.xxx.consumer 消费者,那么 scan package 就设置到 com.xxx Q:如何设置消费者调用生产者的超时时间? A:目前不能通过 application.properties 定义。@Reference timeout Q:consumer 怎么配置接入多个 provider? A:@Reference 可以指定不同的 register。register (注册中心 like provider container)里面可以对应多个 provider Q: @Service(version = “1.0.0”) 这个 1.0.0 可以从 application.properties 配置文件中读取吗?可以区分不同的环境,可以统一升级管理 A:占时还没有解决… 但是应用环境,如:dev/test/run 可以使用下面的配置,在 application.properties 定义 spring.dubbo.application.environment Spring Boot 整合 Dubbo 的项目依赖了 spring-boot-starter-dubbo 工程,该项目地址是 https://github.com/teaey/spring-boot-starter-dubbo。 感谢作者~ 二、applications.properties 配置清单 根据 starter 工程源码,可以看出 application.properties 对应的 Dubbo 配置类 DubboProperties 。 1 2 3 4 5 6 7 8 9 10 11 @ConfigurationProperties(prefix = "spring.dubbo") public class DubboProperties { private String scan; private ApplicationConfig application; private RegistryConfig registry; private ProtocolConfig protocol; } 包括了扫描路径、应用配置类、注册中心配置类和服务协议类 所以具体常用配置下 扫描包路径:指的是 Dubbo 服务注解的服务包路径 1 2 3 ## Dubbo 配置 # 扫描包路径 spring.dubbo.scan=org.spring.springboot.dubbo 应用配置类:关于 Dubbo 应用级别的配置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 ## Dubbo 应用配置 # 应用名称 spring.dubbo.application.name=xxx # 模块版本 spring.dubbo.application.version=xxx # 应用负责人 spring.dubbo.application.owner=xxx # 组织名(BU或部门) spring.dubbo.application.organization=xxx # 分层 spring.dubbo.application.architecture=xxx # 环境,如:dev/test/run spring.dubbo.application.environment=xxx # Java代码编译器 spring.dubbo.application.compiler=xxx # 日志输出方式 spring.dubbo.application.logger=xxx # 注册中心 0 spring.dubbo.application.registries[0].address=zookeeper:#127.0.0.1:2181=xxx # 注册中心 1 spring.dubbo.application.registries[1].address=zookeeper:#127.0.0.1:2181=xxx # 服务监控 spring.dubbo.application.monitor.address=xxx 这里注意多个注册中心的配置方式。下面介绍单个注册中心的配置方式。 注册中心配置类:常用 ZooKeeper 作为注册中心进行服务注册。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 ## Dubbo 注册中心配置类 # 注册中心地址 spring.dubbo.application.registries.address=xxx # 注册中心登录用户名 spring.dubbo.application.registries.username=xxx # 注册中心登录密码 spring.dubbo.application.registries.password=xxx # 注册中心缺省端口 spring.dubbo.application.registries.port=xxx # 注册中心协议 spring.dubbo.application.registries.protocol=xxx # 客户端实现 spring.dubbo.application.registries.transporter=xxx spring.dubbo.application.registries.server=xxx spring.dubbo.application.registries.client=xxx spring.dubbo.application.registries.cluster=xxx spring.dubbo.application.registries.group=xxx spring.dubbo.application.registries.version=xxx # 注册中心请求超时时间(毫秒) spring.dubbo.application.registries.timeout=xxx # 注册中心会话超时时间(毫秒) spring.dubbo.application.registries.session=xxx # 动态注册中心列表存储文件 spring.dubbo.application.registries.file=xxx # 停止时等候完成通知时间 spring.dubbo.application.registries.wait=xxx # 启动时检查注册中心是否存在 spring.dubbo.application.registries.check=xxx # 在该注册中心上注册是动态的还是静态的服务 spring.dubbo.application.registries.dynamic=xxx # 在该注册中心上服务是否暴露 spring.dubbo.application.registries.register=xxx # 在该注册中心上服务是否引用 spring.dubbo.application.registries.subscribe=xxx 服务协议配置类: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 ## Dubbo 服务协议配置 # 服务协议 spring.dubbo.application.protocol.name=xxx # 服务IP地址(多网卡时使用) spring.dubbo.application.protocol.host=xxx # 服务端口 spring.dubbo.application.protocol.port=xxx # 上下文路径 spring.dubbo.application.protocol.contextpath=xxx # 线程池类型 spring.dubbo.application.protocol.threadpool=xxx # 线程池大小(固定大小) spring.dubbo.application.protocol.threads=xxx # IO线程池大小(固定大小) spring.dubbo.application.protocol.iothreads=xxx # 线程池队列大小 spring.dubbo.application.protocol.queues=xxx # 最大接收连接数 spring.dubbo.application.protocol.accepts=xxx # 协议编码 spring.dubbo.application.protocol.codec=xxx # 序列化方式 spring.dubbo.application.protocol.serialization=xxx # 字符集 spring.dubbo.application.protocol.charset=xxx # 最大请求数据长度 spring.dubbo.application.protocol.payload=xxx # 缓存区大小 spring.dubbo.application.protocol.buffer=xxx # 心跳间隔 spring.dubbo.application.protocol.heartbeat=xxx # 访问日志 spring.dubbo.application.protocol.accesslog=xxx # 网络传输方式 spring.dubbo.application.protocol.transporter=xxx # 信息交换方式 spring.dubbo.application.protocol.exchanger=xxx # 信息线程模型派发方式 spring.dubbo.application.protocol.dispatcher=xxx # 对称网络组网方式 spring.dubbo.application.protocol.networker=xxx # 服务器端实现 spring.dubbo.application.protocol.server=xxx # 客户端实现 spring.dubbo.application.protocol.client=xxx # 支持的telnet命令,多个命令用逗号分隔 spring.dubbo.application.protocol.telnet=xxx # 命令行提示符 spring.dubbo.application.protocol.prompt=xxx # status检查 spring.dubbo.application.protocol.status=xxx # 是否注册 spring.dubbo.application.protocol.status=xxx 三、@Service 服务提供者常用配置 常用 @Service 配置的如下 1 2 3 4 5 6 7 version 版本 group 分组 provider 提供者 protocol 服务协议 monitor 服务监控 registry 服务注册 … 四、@Reference 服务消费者常用配置 常用 @Reference 配置的如下 1 2 3 4 5 6 version 版本 group 分组 timeout 消费者调用提供者的超时时间 consumer 服务消费者 monitor 服务监控 registry 服务注册 五、小结 主要介绍了 Spring Boot Dubbo 整合中的细节问题大集合。 推荐:《Springboot 整合 Dubbo/ZooKeeper 详解 SOA 案例》 欢迎扫一扫我的公众号关注 — 及时得到博客订阅哦! — http://www.bysocket.com/ — — https://github.com/JeffLi1993 —
摘要: 原创出处 www.bysocket.com 「泥瓦匠BYSocket 」欢迎转载,保留摘要,谢谢! 『 春夏秋冬失去了你,我怎么过一年四季- 民谣歌词 』 本文提纲 一、什么是 Elasticsearch-analysis-ik 二、默认配置 IK 三、使用 AnalyzeRequestBuilder 获取分词结果 四、小结 运行环境:JDK 7 或 8、Maven 3.0+、ElasticSearch 2.3.2、Elasticsearch-analysis-ik 1.9.2 技术栈:SpringBoot 1.5+、Spring-data-elasticsearch 2.1.0 前言 在 Elasticsearch 和插件 elasticsearch-head 安装详解 http://www.bysocket.com/?p=1744 文章中,我使用的是 Elasticsearch 5.3.x。这里我改成了 ElasticSearch 2.3.2。是因为版本对应关系 https://github.com/spring-projects/spring-data-elasticsearch/wiki/Spring-Data-Elasticsearch---Spring-Boot---version-matrix: Spring Boot Version (x) Spring Data Elasticsearch Version (y) Elasticsearch Version (z) x <= 1.3.5 y <= 1.3.4 z <= 1.7.2* x >= 1.4.x 2.0.0 <=y < 5.0.0** 2.0.0 <= z < 5.0.0** * - 只需要你修改下对应的 pom 文件版本号 ** - 下一个 ES 的版本会有重大的更新 这里可以看出,5.3.x 不在第二行范围内。因此这里我讲下,如何在 ElasticSearch 2.3.2 中默认配置 IK。 一、什么是 Elasticsearch-analysis-ik 了解什么是 Elasticsearch-analysis-ik,首先了解什么是 IK Analyzer。 IK Analyzer 是基于 lucene 实现的分词开源框架。官方地址:https://code.google.com/p/ik-analyzer/ 。 Elasticsearch-analysis-ik 则是将 IK Analyzer 集成 Elasticsearch 的插件,并支持自定义词典。GitHub 地址:https://github.com/medcl/elasticsearch-analysis-ik。特性支持: 分析器 Analyzer: ik_smart 或 ik_max_word 分词器 Tokenizer: ik_smart 或 ik_max_word 二、默认配置 IK 在 Elasticsearch-analysis-ik 官网中可以看到,其中版本需要对应: IK版 ES版本 主 5.x -> master 5.3.2 5.3.2 5.2.2 5.2.2 5.1.2 5.1.2 1.10.1 2.4.1 1.9.5 2.3.5 1.8.1 2.2.1 1.7.0 2.1.1 1.5.0 2.0.0 1.2.6 1.0.0 1.2.5 0.90.x 1.1.3 0.20.x 1.0.0 0.16.2 -> 0.19.0 这里使用的是 Elasticsearch-analysis-ik 1.9.2,支持 ElasticSearch 2.3.2。下载地址:https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v1.9.2/elasticsearch-analysis-ik-1.9.2.zip,下载成功后进行安装。 解压 zip 文件,复制里面的内容到 elasticsearch-2.3.2/plugins/ik。 cd elasticsearch-2.3.2/plugins mkdir ik cp ... 在 elasticsearch-2.3.2/config/elasticsearch.yml 增加配置: index.analysis.analyzer.default.tokenizer : "ik_max_word" index.analysis.analyzer.default.type: "ik" 配置默认分词器为 ik,并指定分词器为 ik_max_word。 然后重启 ES 即可。验证 IK 是否成功安装,访问下 localhost:9200/_analyze?analyzer=ik&pretty=true&text=泥瓦匠的博客是bysocket.com 可以得到下面的结果集: { "tokens": [ { "token": "泥瓦匠", "start_offset": 0, "end_offset": 3, "type": "CN_WORD", "position": 0 }, { "token": "泥", "start_offset": 0, "end_offset": 1, "type": "CN_WORD", "position": 1 }, { "token": "瓦匠", "start_offset": 1, "end_offset": 3, "type": "CN_WORD", "position": 2 }, { "token": "匠", "start_offset": 2, "end_offset": 3, "type": "CN_WORD", "position": 3 }, { "token": "博客", "start_offset": 4, "end_offset": 6, "type": "CN_WORD", "position": 4 }, { "token": "bysocket.com", "start_offset": 8, "end_offset": 20, "type": "LETTER", "position": 5 }, { "token": "bysocket", "start_offset": 8, "end_offset": 16, "type": "ENGLISH", "position": 6 }, { "token": "com", "start_offset": 17, "end_offset": 20, "type": "ENGLISH", "position": 7 } ] } 记得在Docker 容器安装时,需要对应的端口开发。 三、使用 AnalyzeRequestBuilder 获取分词结果 ES 中默认配置 IK 后,通过 Rest HTTP 的方式我们可以进行得到分词结果。那么在 Spring Boot 和提供的客户端依赖 spring-data-elasticsearch 中如何获取到分词结果。 加入依赖 pom.xml <!-- Spring Boot Elasticsearch 依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-elasticsearch</artifactId> </dependency> 在 application.properties 配置 ES 的地址: # ES spring.data.elasticsearch.repositories.enabled = true spring.data.elasticsearch.cluster-nodes = 127.0.0.1:9300 然后创建一个方法,入参是搜索词,返回的是分词结果列表。 @Autowired private ElasticsearchTemplate elasticsearchTemplate; /** * 调用 ES 获取 IK 分词后结果 * * @param searchContent * @return */ private List<String> getIkAnalyzeSearchTerms(String searchContent) { // 调用 IK 分词分词 AnalyzeRequestBuilder ikRequest = new AnalyzeRequestBuilder(elasticsearchTemplate.getClient(), AnalyzeAction.INSTANCE,"indexName",searchContent); ikRequest.setTokenizer("ik"); List<AnalyzeResponse.AnalyzeToken> ikTokenList = ikRequest.execute().actionGet().getTokens(); // 循环赋值 List<String> searchTermList = new ArrayList<>(); ikTokenList.forEach(ikToken -> { searchTermList.add(ikToken.getTerm()); }); return searchTermList; } indexName 这里是指在 ES 设置的索引名称。 从容器注入的 ElasticsearchTemplate Bean 中获取 Client ,再通过 AnalyzeRequestBuilder 分析请求类型中进行分词并获取分词结果 AnalyzeResponse.AnalyzeToken 列表。 四、小结 默认配置了 IK 分词器,则 DSL 去 ES 查询时会自动调用 IK 分词。 如果想要自定义词库,比如比较偏的领域性。可以参考 Elasticsearch-analysis-ik GiHub 地址去具体查阅。 推荐开源项目:《springboot-learning-example》 spring boot 实践学习案例,是 spring boot 初学者及核心技术巩固的最佳实践 欢迎扫一扫我的公众号关注 — 及时得到博客订阅哦! — http://www.bysocket.com/ — — https://github.com/JeffLi1993 —
摘要: 原创出处 www.bysocket.com 「泥瓦匠BYSocket 」欢迎转载,保留摘要,谢谢! 『 仓廪实而知礼节,衣食足而知荣辱 - 管仲 』 本文提纲 一、自动配置 二、自定义属性 三、random.* 属性 四、多环境配置 运行环境:JDK 7 或 8,Maven 3.0+ 技术栈:SpringBoot 1.5+ 一、自动配置 Spring Boot 提供了对应用进行自动化配置。相比以前 XML 配置方式,很多显式方式申明是不需要的。二者,大多数默认的配置足够实现开发功能,从而更快速开发。 什么是自动配置? Spring Boot 提供了默认的配置,如默认的 Bean ,去运行 Spring 应用。它是非侵入式的,只提供一个默认实现。 大多数情况下,自动配置的 Bean 满足了现有的业务场景,不需要去覆盖。但如果自动配置做的不够好,需要覆盖配置。比如通过命令行动态指定某个 jar ,按不同环境启动(这个例子在第 4 小节介绍)。那怎么办?这里先要考虑到配置的优先级。 Spring Boot 不单单从 application.properties 获取配置,所以我们可以在程序中多种设置配置属性。按照以下列表的优先级排列: 1.命令行参数 2.java:comp/env 里的 JNDI 属性 3.JVM 系统属性 4.操作系统环境变量 5.RandomValuePropertySource 属性类生成的 random.* 属性 6.应用以外的 application.properties(或 yml)文件 7.打包在应用内的 application.properties(或 yml)文件 8.在应用 @Configuration 配置类中,用 @PropertySource 注解声明的属性文件 9.SpringApplication.setDefaultProperties 声明的默认属性 可见,命令好参数优先级最高。这个可以根据这个优先级,可以在测试或生产环境中快速地修改配置参数值,而不需要重新打包和部署应用。 还有第 6 点,根据这个在多 moudle 的项目中,比如常见的项目分 api 、service、dao 等 moudles,往往会加一个 deploy moudle 去打包该业务各个子 moudle,应用以外的配置优先。 二、自定义属性 泥瓦匠喜欢按着代码工程来讲解知识。git clone 下载工程 springboot-learning-example ,项目地址见 GitHub - https://github.com/JeffLi1993/springboot-learning-example。 a. 编译工程 在项目根目录 springboot-learning-example,运行 maven 指令: cd springboot-learning-example mvn clean install b. 运行工程 test 方法 运行 springboot-properties 工程 org.spring.springboot.property.PropertiesTest 测试类的 getHomeProperties 方法。可以在控制台看到输出,这是通过自定义属性获取的值: HomeProperties{province='ZheJiang', city='WenLing', desc='dev: I'm living in ZheJiang WenLing.'} 怎么定义自定义属性呢? 首先项目结构如下: ├── pom.xml └── src ├── main │ ├── java │ │ └── org │ │ └── spring │ │ └── springboot │ │ ├── Application.java │ │ └── property │ │ ├── HomeProperties.java │ │ └── UserProperties.java │ └── resources │ ├── application-dev.properties │ ├── application-prod.properties │ └── application.properties └── test ├── java │ └── org │ └── spring │ └── springboot │ └── property │ ├── HomeProperties1.java │ └── PropertiesTest.java └── resouorces └── application.yml 在 application.properties 中对应 HomeProperties 对象字段编写属性的 KV 值: ## 家乡属性 Dev home.province=ZheJiang home.city=WenLing home.desc=dev: I'm living in ${home.province} ${home.city}. 这里也可以通过占位符,进行属性之间的引用。 然后,编写对应的 HomeProperties Java 对象: /** * 家乡属性 * * Created by bysocket on 17/04/2017. */ @Component @ConfigurationProperties(prefix = "home") public class HomeProperties { /** * 省份 */ private String province; /** * 城市 */ private String city; /** * 描述 */ private String desc; public String getProvince() { return province; } public void setProvince(String province) { this.province = province; } public String getCity() { return city; } public void setCity(String city) { this.city = city; } public String getDesc() { return desc; } public void setDesc(String desc) { this.desc = desc; } @Override public String toString() { return "HomeProperties{" + "province='" + province + '\'' + ", city='" + city + '\'' + ", desc='" + desc + '\'' + '}'; } } 通过 @ConfigurationProperties(prefix = "home”) 注解,将配置文件中以 home 前缀的属性值自动绑定到对应的字段中。同是用 @Component 作为 Bean 注入到 Spring 容器中。 如果不是用 application.properties 文件,而是用 application.yml 的文件,对应配置如下: ## 家乡属性 home: province: 浙江省 city: 温岭松门 desc: 我家住在${home.province}的${home.city} 键值对冒号后面,必须空一格。 注意这里,就有一个坑了: application.properties 配置中文值的时候,读取出来的属性值会出现乱码问题。但是 application.yml 不会出现乱码问题。原因是,Spring Boot 是以 iso-8859 的编码方式读取 application.properties 配置文件。 注意这里,还有一个坑: 如果定义一个键值对 user.name=xxx ,这里会读取不到对应写的属性值。为什么呢?Spring Boot 的默认 StandardEnvironment 首先将会加载 “systemEnvironment" 作为首个PropertySource. 而 source 即为System.getProperties().当 getProperty时,按照读取顺序,返回 “systemEnvironment" 的值.即 System.getProperty("user.name") (Mac 机子会读自己的登录账号,这里感谢我的死党 http://rapharino.com/) 三、random.* 属性 Spring Boot 通过 RandomValuePropertySource 提供了很多关于随机数的工具类。概括可以生成随机字符串、随机 int 、随机 long、某范围的随机数。 运行 springboot-properties 工程 org.spring.springboot.property.PropertiesTest 测试类的 randomTestUser 方法。多次运行,可以发现每次输出不同 User 属性值: UserProperties{id=-3135706105861091890, age=41, desc='泥瓦匠叫做3cf8fb2507f64e361f62700bcbd17770', uuid='582bcc01-bb7f-41db-94d5-c22aae186cb4'} application.yml 方式的配置如下( application.properties 形式这里不写了): ## 随机属性 user: id: ${random.long} age: ${random.int[1,200]} desc: 泥瓦匠叫做${random.value} uuid: ${random.uuid} 四、多环境配置 很多场景的配置,比如数据库配置、Redis 配置、注册中心和日志配置等。在不同的环境,我们需要不同的包去运行项目。所以看项目结构,有两个环境的配置: application-dev.properties:开发环境 application-prod.properties:生产环境 Spring Boot 是通过 application.properties 文件中,设置 spring.profiles.active 属性,比如 ,配置了 dev ,则加载的是 application-dev.properties : # Spring Profiles Active spring.profiles.active=dev 那运行 springboot-properties 工程中 Application 应用启动类,从控制台中可以看出,是加载了 application-dev.properties 的属性输出: HomeProperties{province='ZheJiang', city='WenLing', desc='dev: I'm living in ZheJiang WenLing.'} 将 spring.profiles.active 设置成 prod,重新运行,可得到 application-prod.properties的属性输出: HomeProperties{province='ZheJiang', city='WenLing', desc='prod: I'm living in ZheJiang WenLing.'} 根据优先级,顺便介绍下 jar 运行的方式,通过设置 -Dspring.profiles.active=prod 去指定相应的配置: mvn package java -jar -Dspring.profiles.active=prod springboot-properties-0.0.1-SNAPSHOT.jar 五、小结 常用的样板配置在 Spring Boot 官方文档给出,我们常在 application.properties(或 yml)去配置各种常用配置: http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html 感谢资料: http://blog.didispace.com/springbootproperties/ https://docs.spring.io/spring-boot/docs 欢迎扫一扫我的公众号关注 — 及时得到博客订阅哦! — http://www.bysocket.com/ — — https://github.com/JeffLi1993 —
摘要: 原创出处 www.bysocket.com 「泥瓦匠BYSocket 」欢迎转载,保留摘要,谢谢! 『 公司的核心竞争力在于创新 – 《启示录》 』 继续上一篇:《 Springboot 整合 Dubbo/ZooKeeper 》,在 Spring Boot 使用 Dubbo Activate 扩展点。这是一个群友问的,我总结下,分享给更多人。 本文提纲 一、什么是 Dubbo Activate 注解 二、使用 Dubbo Activate 三、小结 运行环境:JDK 7 或 8,Maven 3.0+技术栈:SpringBoot 1.5+、Dubbo 2.5+、ZooKeeper 3.3+ 一、什么是 Dubbo Activate 注解 @Activate 是一个 Duboo 框架提供的注解。在 Dubbo 官方文档上有记载: 对于集合类扩展点,比如:Filter, InvokerListener, ExportListener, TelnetHandler, StatusChecker等, 可以同时加载多个实现,此时,可以用自动激活来简化配置。 用 @Activate 来实现一些 Filter ,可以具体如下: 1. 无条件自动激活 直接使用默认的注解即可 1 2 3 4 5 6 7 import com.alibaba.dubbo.common.extension.Activate; import com.alibaba.dubbo.rpc.Filter; @Activate // 无条件自动激活 public class XxxFilter implements Filter { // ... } 2. 配置 xxx 参数,并且参数为有效值时激活,比如配了cache=”lru”,自动激活 CacheFilter 1 2 3 4 5 6 7 import com.alibaba.dubbo.common.extension.Activate; import com.alibaba.dubbo.rpc.Filter; @Activate("xxx") // 当配置了xxx参数,并且参数为有效值时激活,比如配了cache="lru",自动激活CacheFilter。 public class XxxFilter implements Filter { // ... } 3. 只对提供方激活,group 可选 provider 或 consumer 1 2 3 4 5 6 7 8 import com.alibaba.dubbo.common.extension.Activate; import com.alibaba.dubbo.rpc.Filter; @Activate(group = "provider", value = "xxx") // 只对提供方激活,group可选"provider"或"consumer" public class XxxFilter implements Filter { // ... } 二、使用 Dubbo Activate 注解 基于以前的 springboot-dubbo-server 和 springboot-dubbo-client 工程,GitHub 地址:https://github.com/JeffLi1993/springboot-learning-example 。 这里我们在消费端,既 springboot-dubbo-client 工程上添加一个 Filter。代码如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package com.xxx; import com.alibaba.dubbo.rpc.Filter; import com.alibaba.dubbo.rpc.Invoker; import com.alibaba.dubbo.rpc.Invocation; import com.alibaba.dubbo.rpc.Result; import com.alibaba.dubbo.rpc.RpcException; public class XxxFilter implements Filter { public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException { // before filter ... Result result = invoker.invoke(invocation); // after filter ... return result; } } 启动 client 工程发现,Console 报错,出现: 1 Caused by: java.lang.IllegalStateException: No such extension dubboConsumerFilter for filter/com.alibaba.dubbo.rpc.Filter 发现这个 Filter 初始化时,报错了。证明没有配置成功。 原来根据官方文档中描述,我们需要配置扩展点配置文件。 在 META-INF 中配置: 1 xxx=com.xxx.XxxFilter Maven 项目目录结构 1 2 3 4 5 6 7 8 9 10 src |-main |-java |-com |-xxx |-XxxFilter.java (实现Filter接口) |-resources |-META-INF |-dubbo |-com.alibaba.dubbo.rpc.Filter (纯文本文件,内容为:xxx=com.xxx.XxxFilter) 三、小结 调用拦截扩展的应用场景很多,比如黑白名单,比如 IP 等。 欢迎扫一扫我的公众号关注 — 及时得到博客订阅哦! — http://www.bysocket.com/ — — https://github.com/JeffLi1993 —
摘要: 原创出处 www.bysocket.com 「泥瓦匠BYSocket 」欢迎转载,保留摘要,谢谢! 『 产品没有价值,开发团队再优秀也无济于事 – 《启示录》 』 本文提纲 一、缓存的应用场景 二、更新缓存的策略 三、运行 springboot-mybatis-redis 工程案例 四、springboot-mybatis-redis 工程代码配置详解 运行环境: Mac OS 10.12.x JDK 8 + Redis 3.2.8 Spring Boot 1.5.1.RELEASE 一、缓存的应用场景 什么是缓存? 在互联网场景下,尤其 2C 端大流量场景下,需要将一些经常展现和不会频繁变更的数据,存放在存取速率更快的地方。缓存就是一个存储器,在技术选型中,常用 Redis 作为缓存数据库。缓存主要是在获取资源方便性能优化的关键方面。 Redis 是一个高性能的 key-value 数据库。GitHub 地址:https://github.com/antirez/redis 。Github 是这么描述的: Redis is an in-memory database that persists on disk. The data model is key-value, but many different kind of values are supported: Strings, Lists, Sets, Sorted Sets, Hashes, HyperLogLogs, Bitmaps. 缓存的应用场景有哪些呢? 比如常见的电商场景,根据商品 ID 获取商品信息时,店铺信息和商品详情信息就可以缓存在 Redis,直接从 Redis 获取。减少了去数据库查询的次数。但会出现新的问题,就是如何对缓存进行更新?这就是下面要讲的。 二、更新缓存的策略 参考《缓存更新的套路》http://coolshell.cn/articles/17416.html,缓存更新的模式有四种:Cache aside, Read through, Write through, Write behind caching。 这里我们使用的是 Cache Aside 策略,从三个维度:(摘自 耗子叔叔博客) 失效:应用程序先从cache取数据,没有得到,则从数据库中取数据,成功后,放到缓存中。 命中:应用程序从cache中取数据,取到后返回。 更新:先把数据存到数据库中,成功后,再让缓存失效。 大致流程如下: 获取商品详情举例 a. 从商品 Cache 中获取商品详情,如果存在,则返回获取 Cache 数据返回。 b. 如果不存在,则从商品 DB 中获取。获取成功后,将数据存到 Cache 中。则下次获取商品详情,就可以从 Cache 就可以得到商品详情数据。 c. 从商品 DB 中更新或者删除商品详情成功后,则从缓存中删除对应商品的详情缓存 三、运行 springboot-mybatis-redis 工程案例 git clone 下载工程 springboot-learning-example ,项目地址见 GitHub – https://github.com/JeffLi1993/springboot-learning-example。 下面开始运行工程步骤(Quick Start): 1.数据库和 Redis 准备 a.创建数据库 springbootdb: 1 CREATE DATABASE springbootdb; b.创建表 city :(因为我喜欢徒步) 1 2 3 4 5 6 7 8 DROP TABLE IF EXISTS `city`; CREATE TABLE `city` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '城市编号', `province_id` int(10) unsigned NOT NULL COMMENT '省份编号', `city_name` varchar(25) DEFAULT NULL COMMENT '城市名称', `description` varchar(25) DEFAULT NULL COMMENT '描述', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; c.插入数据 1 INSERT city VALUES (1 ,1,'温岭市','BYSocket 的家在温岭。'); d.本地安装 Redis 详见写过的文章《 Redis 安装 》http://www.bysocket.com/?p=917 2. springboot-mybatis-redis 工程项目结构介绍 1 2 3 4 5 6 7 springboot-mybatis-redis 工程项目结构如下图所示: org.spring.springboot.controller - Controller 层 org.spring.springboot.dao - 数据操作层 DAO org.spring.springboot.domain - 实体类 org.spring.springboot.service - 业务逻辑层 Application - 应用启动类 application.properties - 应用配置文件,应用启动会自动读取配置 3.改数据库配置 打开 application.properties 文件, 修改相应的数据源配置,比如数据源地址、账号、密码等。 (如果不是用 MySQL,自行添加连接驱动 pom,然后修改驱动名配置。) 4.编译工程 在项目根目录 springboot-learning-example,运行 maven 指令: 1 mvn clean install 5.运行工程 右键运行 springboot-mybatis-redis 工程 Application 应用启动类的 main 函数。 项目运行成功后,这是个 HTTP OVER JSON 服务项目。所以用 postman 工具可以如下操作 根据 ID,获取城市信息 GET http://127.0.0.1:8080/api/city/1 再请求一次,获取城市信息会发现数据获取的耗时快了很多。服务端 Console 输出的日志: 1 2 2017-04-13 18:29:00.273 INFO 13038 --- [nio-8080-exec-1] o.s.s.service.impl.CityServiceImpl : CityServiceImpl.findCityById() : 城市插入缓存 >> City{id=12, provinceId=3, cityName='三亚', description='水好,天蓝'} 2017-04-13 18:29:03.145 INFO 13038 --- [nio-8080-exec-2] o.s.s.service.impl.CityServiceImpl : CityServiceImpl.findCityById() : 从缓存中获取了城市 >> City{id=12, provinceId=3, cityName='三亚', description='水好,天蓝'} 可见,第一次是从数据库 DB 获取数据,并插入缓存,第二次直接从缓存中取。 更新城市信息 PUT http://127.0.0.1:8080/api/city 删除城市信息 DELETE http://127.0.0.1:8080/api/city/2 这两种操作中,如果缓存有对应的数据,则删除缓存。服务端 Console 输出的日志: 1 2017-04-13 18:29:52.248 INFO 13038 --- [nio-8080-exec-9] o.s.s.service.impl.CityServiceImpl : CityServiceImpl.deleteCity() : 从缓存中删除城市 ID >> 12 四、springboot-mybatis-redis 工程代码配置详解 这里,我强烈推荐 注解 的方式实现对象的缓存。但是这里为了更好说明缓存更新策略。下面讲讲工程代码的实现。 pom.xml 依赖配置: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>springboot</groupId> <artifactId>springboot-mybatis-redis</artifactId> <version>0.0.1-SNAPSHOT</version> <name>springboot-mybatis-redis :: 整合 Mybatis 并使用 Redis 作为缓存</name> <!-- Spring Boot 启动父依赖 --> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.1.RELEASE</version> </parent> <properties> <mybatis-spring-boot>1.2.0</mybatis-spring-boot> <mysql-connector>5.1.39</mysql-connector> <spring-boot-starter-redis-version>1.3.2.RELEASE</spring-boot-starter-redis-version> </properties> <dependencies> <!-- Spring Boot Reids 依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-redis</artifactId> <version>${spring-boot-starter-redis-version}</version> </dependency> <!-- Spring Boot Web 依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- Spring Boot Test 依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!-- Spring Boot Mybatis 依赖 --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>${mybatis-spring-boot}</version> </dependency> <!-- MySQL 连接驱动依赖 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>${mysql-connector}</version> </dependency> <!-- Junit --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> </dependencies> </project> 包括了 Spring Boot Reids 依赖、 MySQL 依赖和 Mybatis 依赖。 在 application.properties 应用配置文件,增加 Redis 相关配置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 ## 数据源配置 spring.datasource.url=jdbc:mysql://localhost:3306/springbootdb?useUnicode=true&characterEncoding=utf8 spring.datasource.username=root spring.datasource.password=123456 spring.datasource.driver-class-name=com.mysql.jdbc.Driver ## Mybatis 配置 mybatis.typeAliasesPackage=org.spring.springboot.domain mybatis.mapperLocations=classpath:mapper/*.xml ## Redis 配置 ## Redis数据库索引(默认为0) spring.redis.database=0 ## Redis服务器地址 spring.redis.host=127.0.0.1 ## Redis服务器连接端口 spring.redis.port=6379 ## Redis服务器连接密码(默认为空) spring.redis.password= ## 连接池最大连接数(使用负值表示没有限制) spring.redis.pool.max-active=8 ## 连接池最大阻塞等待时间(使用负值表示没有限制) spring.redis.pool.max-wait=-1 ## 连接池中的最大空闲连接 spring.redis.pool.max-idle=8 ## 连接池中的最小空闲连接 spring.redis.pool.min-idle=0 ## 连接超时时间(毫秒) spring.redis.timeout=0 详细解释可以参考注释。对应的配置类:org.springframework.boot.autoconfigure.data.redis.RedisProperties CityRestController 控制层依旧是 Restful 风格的,详情可以参考《Springboot 实现 Restful 服务,基于 HTTP / JSON 传输》。 http://www.bysocket.com/?p=1627 domain 对象 City 必须实现序列化,因为需要将对象序列化后存储到 Redis。如果没实现 Serializable ,控制台会爆出以下异常: 1 2 Serializable java.lang.IllegalArgumentException: DefaultSerializer requires a Serializable payload but received an object of type City.java 城市对象: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 package org.spring.springboot.domain; import java.io.Serializable; /** * 城市实体类 * * Created by bysocket on 07/02/2017. */ public class City implements Serializable { private static final long serialVersionUID = -1L; /** * 城市编号 */ private Long id; /** * 省份编号 */ private Long provinceId; /** * 城市名称 */ private String cityName; /** * 描述 */ private String description; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public Long getProvinceId() { return provinceId; } public void setProvinceId(Long provinceId) { this.provinceId = provinceId; } public String getCityName() { return cityName; } public void setCityName(String cityName) { this.cityName = cityName; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } @Override public String toString() { return "City{" + "id=" + id + ", provinceId=" + provinceId + ", cityName='" + cityName + '\'' + ", description='" + description + '\'' + '}'; } } 如果需要自定义序列化实现,只要实现 RedisSerializer 接口去实现即可,然后在使用 RedisTemplate.setValueSerializer 方法去设置你实现的序列化实现。 主要还是城市业务逻辑实现类 CityServiceImpl.java: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 package org.spring.springboot.service.impl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.spring.springboot.dao.CityDao; import org.spring.springboot.domain.City; import org.spring.springboot.service.CityService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.core.ValueOperations; import org.springframework.stereotype.Service; import java.util.List; import java.util.concurrent.TimeUnit; /** * 城市业务逻辑实现类 * <p> * Created by bysocket on 07/02/2017. */ @Service public class CityServiceImpl implements CityService { private static final Logger LOGGER = LoggerFactory.getLogger(CityServiceImpl.class); @Autowired private CityDao cityDao; @Autowired private RedisTemplate redisTemplate; /** * 获取城市逻辑: * 如果缓存存在,从缓存中获取城市信息 * 如果缓存不存在,从 DB 中获取城市信息,然后插入缓存 */ public City findCityById(Long id) { // 从缓存中获取城市信息 String key = "city_" + id; ValueOperations<String, City> operations = redisTemplate.opsForValue(); // 缓存存在 boolean hasKey = redisTemplate.hasKey(key); if (hasKey) { City city = operations.get(key); LOGGER.info("CityServiceImpl.findCityById() : 从缓存中获取了城市 >> " + city.toString()); return city; } // 从 DB 中获取城市信息 City city = cityDao.findById(id); // 插入缓存 operations.set(key, city, 10, TimeUnit.SECONDS); LOGGER.info("CityServiceImpl.findCityById() : 城市插入缓存 >> " + city.toString()); return city; } @Override public Long saveCity(City city) { return cityDao.saveCity(city); } /** * 更新城市逻辑: * 如果缓存存在,删除 * 如果缓存不存在,不操作 */ @Override public Long updateCity(City city) { Long ret = cityDao.updateCity(city); // 缓存存在,删除缓存 String key = "city_" + city.getId(); boolean hasKey = redisTemplate.hasKey(key); if (hasKey) { redisTemplate.delete(key); LOGGER.info("CityServiceImpl.updateCity() : 从缓存中删除城市 >> " + city.toString()); } return ret; } @Override public Long deleteCity(Long id) { Long ret = cityDao.deleteCity(id); // 缓存存在,删除缓存 String key = "city_" + id; boolean hasKey = redisTemplate.hasKey(key); if (hasKey) { redisTemplate.delete(key); LOGGER.info("CityServiceImpl.deleteCity() : 从缓存中删除城市 ID >> " + id); } return ret; } } 首先这里注入了 RedisTemplate 对象。联想到 Spring 的 JdbcTemplate ,RedisTemplate 封装了 RedisConnection,具有连接管理,序列化和 Redis 操作等功能。还有针对 String 的支持对象 StringRedisTemplate。 Redis 操作视图接口类用的是 ValueOperations,对应的是 Redis String/Value 操作。还有其他的操作视图,ListOperations、SetOperations、ZSetOperations 和 HashOperations 。ValueOperations 插入缓存是可以设置失效时间,这里设置的失效时间是 10 s。 回到更新缓存的逻辑 a. findCityById 获取城市逻辑: 如果缓存存在,从缓存中获取城市信息 如果缓存不存在,从 DB 中获取城市信息,然后插入缓存 b. deleteCity 删除 / updateCity 更新城市逻辑: 如果缓存存在,删除 如果缓存不存在,不操作 其他不明白的,可以 git clone 下载工程 springboot-learning-example ,工程代码注解很详细。 https://github.com/JeffLi1993/springboot-learning-example。 五、小结 本文涉及到 Spring Boot 在使用 Redis 缓存时,一个是缓存对象需要序列化,二个是缓存更新策略是如何的。 欢迎扫一扫我的公众号关注 — 及时得到博客订阅哦! — http://www.bysocket.com/ — — https://github.com/JeffLi1993 —
摘要: 原创出处:www.bysocket.com 泥瓦匠BYSocket 希望转载,保留摘要,谢谢! “年轻人不要怕表现,要敢于出来表现,但还是那句话,要有正确的度,你的表现是分析问题和解决问题的能力。” – 《你凭什么做好互联网》 本文提纲 一、异常统一处理的使用场景 二、运行 springboot-validation-over-json 工程 三、springboot-validation-over-json 工程代码详解 一、异常统一处理的使用场景 在前后端分离开发中,经常用 HTTP over JSON 作为服务进行前后端联调对接。这里简单介绍下为啥前后端分离开发?我想到如下: 1.低耦合,责权分离,模块化。前后端之间利用轻量级协议对接耦合。 2.便于敏捷开发:后端给出 api 文档 -> 前端根据文档,mock出数据开发 ;同时,后端实现业务逻辑。 3.微服务尤其适用 这时候 HTTP over JSON 形式中很多涉及到返回码,错误码相关的处理。比如xxx参数不完整,权限不足,用户不存在等。 怎么统一处理认为是异常的场景呢? 利用的是 Spring 4.x 提供的 RestControllerAdvice。这里做下说明,也可以根据 ControllerAdvice 去实现。这里案例是 HTTP over JSON 模式,所以直接利用 RestControllerAdvice ,控制层通知器,这里用于统一拦截异常,进行响应处理。工作模式,如图: 二、运行 springboot-validation-over-json 工程 运行环境:JDK 7 或 8,Maven 3.0+ 技术栈:SpringBoot 1.5+(内涵 Spring 4.x) 1.git clone 下载工程 springboot-learning-example 项目地址见 GitHub – https://github.com/JeffLi1993/springboot-learning-example: 1 git clone git@github.com:JeffLi1993/springboot-learning-example.git 然后,Maven 编译安装这个工程: 1 2 cd springboot-learning-example mvn clean install 2.运行 springboot-validation-over-json 工程 右键运行 springboot-validation-over-json 工程 Application 应用启动类的 main 函数。默认端口 8080 3.访问案例 a. 参数不完整案例: 访问浏览器打开下面链接,可得到以下 JSON 返回 http://localhost:8080/api/city?cityName= 1 2 3 4 5 { "code": "000001", "message": "params no complete", "result": null } b. 成功案例: 访问浏览器打开下面链接,可得到以下 JSON 返回 http://localhost:8080/api/city?cityName=%E6%B8%A9%E5%B2%AD%E5%B8%82 1 2 3 4 5 6 7 8 9 10 { "code": "0", "message": "success", "result": { "id": 1, "provinceId": 2, "cityName": "温岭", "description": "是我的故乡" } } 三、springboot-validation-over-json 工程代码详解 代码详解提纲: a.控制层通知器 b.响应码设计 同样,代码共享在我的 GitHub 上:https://github.com/JeffLi1993/springboot-learning-example/tree/master/springboot-validation-over-json 首先,工程代码目录如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 ├── pom.xml └── src └── main └── java └── org └── spring └── springboot ├── Application.java ├── constant │ └── CityErrorInfoEnum.java ├── result │ ├── ErrorInfoInterface.java │ ├── GlobalErrorInfoEnum.java │ ├── GlobalErrorInfoException.java │ ├── GlobalErrorInfoHandler.java │ └── ResultBody.java └── web ├── City.java └── ErrorJsonController.java a.控制层通知器 GlobalErrorInfoHandler.java 代码如下: 1 2 3 4 5 6 7 8 9 10 11 @RestControllerAdvice public class GlobalErrorInfoHandler { @ExceptionHandler(value = GlobalErrorInfoException.class) public ResultBody errorHandlerOverJson(HttpServletRequest request, GlobalErrorInfoException exception) { ErrorInfoInterface errorInfo = exception.getErrorInfo(); ResultBody result = new ResultBody(errorInfo); return result; } } @ExceptionHandler 注解,标记了使用 errorHandlerOverJson() 方法来处理 GlobalErrorInfoException 异常。@RestControllerAdvice 是 @ControllerAdvice 和 @ResponseBody 的语义结合。是控制器增强,直接返回对象。这里用于统一拦截异常,然后返回错误码对象体。@ResponseBody 作用: 该注解用于将 Controller 的方法返回的对象,通过适当的 HttpMessageConverter 转换为指定格式后,写入到 Response 对象的 body 数据区。 b.响应码设计 简单讲讲,这里定义了一个错误码接口,全局错误码枚举和各个业务错误码枚举去实现接口,并用枚举值枚举出错误码及错误码消息列表。如图: 四、小结 如果实战中,大家遇到什么,或者建议《Spring boot 那些事》还需要一起交流的。请点击留言。 推荐书《腾讯传》,其中几章写的很不错。 欢迎扫一扫我的公众号关注 — 及时得到博客订阅哦! — http://www.bysocket.com/ — — https://github.com/JeffLi1993 —
摘要: 原创出处:www.bysocket.com 泥瓦匠BYSocket 希望转载,保留摘要,谢谢! “看看星空,会觉得自己很渺小,可能我们在宇宙中从来就是一个偶然。所以,无论什么事情,仔细想一想,都没有什么大不了的。这能帮助自己在遇到挫折时稳定心态,想得更开。” – 《腾讯传》 本文提纲 一、为啥整合 Dubbo 实现 SOA 二、运行 springboot-dubbo-server 和 springboot-dubbo-client 工程 三、springboot-dubbo-server 和 springboot-dubbo-client 工程配置详解 一、为啥整合 Dubbo 实现 SOA Dubbo 不单单只是高性能的 RPC 调用框架,更是 SOA 服务治理的一种方案。 核心: 1. 远程通信,向本地调用一样调用远程方法。 2. 集群容错 3. 服务自动发现和注册,可平滑添加或者删除服务提供者。 我们常常使用 Springboot 暴露 HTTP 服务,并走 JSON 模式。但慢慢量大了,一种 SOA 的治理方案。这样可以暴露出 Dubbo 服务接口,提供给 Dubbo 消费者进行 RPC 调用。下面我们详解下如何集成 Dubbo。 二、运行 springboot-dubbo-server 和 springboot-dubbo-client 工程 运行环境:JDK 7 或 8,Maven 3.0+ 技术栈:SpringBoot 1.5+、Dubbo 2.5+、ZooKeeper 3.3+ 1.ZooKeeper 服务注册中心 ZooKeeper 是一个分布式的,开放源码的分布式应用程序协调服务。它是一个为分布式应用提供一致性服务的软件,提供的功能包括:配置维护、域名服务、分布式同步、组服务等。 下载 ZooKeeper ,地址 http://www.apache.org/dyn/closer.cgi/zookeeper 解压 ZooKeeper 1 tar zxvf zookeeper-3.4.8.tar.gz 在 conf 目录新建 zoo.cfg ,照着该目录的 zoo_sample.cfg 配置如下。 1 2 cd zookeeper-3.3.6/conf vim zoo.cfg zoo.cfg 代码如下(自己指定 log 文件目录): 1 2 3 4 tickTime=2000 dataDir=/javaee/zookeeper/data dataLogDir=/javaee/zookeeper/log clientPort=2181 在 bin 目录下,启动 ZooKeeper: 1 2 cd zookeeper-3.3.6/bin ./zkServer.sh start 2. git clone 下载工程 springboot-learning-example 项目地址见 GitHub – https://github.com/JeffLi1993/springboot-learning-example: git clone git@github.com:JeffLi1993/springboot-learning-example.git 然后,Maven 编译安装这个工程: 1 2 cd springboot-learning-example mvn clean install 3.运行 springboot-dubbo-server Dubbo 服务提供者工程 右键运行 springboot-dubbo-server 工程 ServerApplication 应用启动类的 main 函数。Console 中出现如下表示项目启动成功: 这里表示 Dubbo 服务已经启动成功,并注册到 ZK (ZooKeeper)中。 4.运行 springboot-dubbo-client Dubbo 服务消费者工程 右键运行 springboot-dubbo-client 工程 ClientApplication 应用启动类的 main 函数。Console 中出现如下: 1 2 3 4 5 ... 2017-03-01 16:31:38.473 INFO 9896 --- [ main] o.s.j.e.a.AnnotationMBeanExporter : Registering beans for JMX exposure on startup 2017-03-01 16:31:38.538 INFO 9896 --- [ main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8081 (http) 2017-03-01 16:31:38.547 INFO 9896 --- [ main] org.spring.springboot.ClientApplication : Started ClientApplication in 6.055 seconds (JVM running for 7.026) City{id=1, provinceId=2, cityName='温岭', description='是我的故乡'} 最后打印的城市信息,就是通过 Dubbo 服务接口调用获取的。顺利运行成功,下面详解下各个代码及配置。 三、springboot-dubbo-server 和 springboot-dubbo-client 工程配置详解 代码都在 GitHub 上, https://github.com/JeffLi1993/springboot-learning-example。 1.详解 springboot-dubbo-server Dubbo 服务提供者工程 springboot-dubbo-server 工程目录结构 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 ├── pom.xml └── src └── main ├── java │ └── org │ └── spring │ └── springboot │ ├── ServerApplication.java │ ├── domain │ │ └── City.java │ └── dubbo │ ├── CityDubboService.java │ └── impl │ └── CityDubboServiceImpl.java └── resources └── application.properties a.pom.xml 配置 pom.xml 中依赖了 spring-boot-starter-dubbo 工程,该项目地址是 https://github.com/teaey/spring-boot-starter-dubbo。pom.xml 配置如下 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>springboot</groupId> <artifactId>springboot-dubbo-server</artifactId> <version>0.0.1-SNAPSHOT</version> <name>springboot-dubbo 服务端:: 整合 Dubbo/ZooKeeper 详解 SOA 案例</name> <!-- Spring Boot 启动父依赖 --> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.1.RELEASE</version> </parent> <properties> <dubbo-spring-boot>1.0.0</dubbo-spring-boot> </properties> <dependencies> <!-- Spring Boot Dubbo 依赖 --> <dependency> <groupId>io.dubbo.springboot</groupId> <artifactId>spring-boot-starter-dubbo</artifactId> <version>${dubbo-spring-boot}</version> </dependency> <!-- Spring Boot Web 依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- Spring Boot Test 依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!-- Junit --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> </dependencies> </project> b.application.properties 配置 1 2 3 4 5 6 ## Dubbo 服务提供者配置 spring.dubbo.application.name=provider spring.dubbo.registry.address=zookeeper://127.0.0.1:2181 spring.dubbo.protocol.name=dubbo spring.dubbo.protocol.port=20880 spring.dubbo.scan=org.spring.springboot.dubbo 这里 ZK 配置的地址和端口,就是上面本机搭建的 ZK 。如果有自己的 ZK 可以修改下面的配置。配置解释如下: spring.dubbo.application.name 应用名称 spring.dubbo.registry.address 注册中心地址 spring.dubbo.protocol.name 协议名称 spring.dubbo.protocol.port 协议端口 spring.dubbo.scan dubbo 服务类包目录 c.CityDubboServiceImpl.java 城市业务 Dubbo 服务层实现层类 1 2 3 4 5 6 7 8 // 注册为 Dubbo 服务 @Service(version = "1.0.0") public class CityDubboServiceImpl implements CityDubboService { public City findCityByName(String cityName) { return new City(1L,2L,"温岭","是我的故乡"); } } @Service 注解标识为 Dubbo 服务,并通过 version 指定了版本号。 d.City.java 城市实体类 实体类通过 Dubbo 服务之间 RPC 调用,则需要实现序列化接口。最好指定下 serialVersionUID 值。 2.详解 springboot-dubbo-client Dubbo 服务消费者工程 springboot-dubbo-client 工程目录结构 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ├── pom.xml └── src └── main ├── java │ └── org │ └── spring │ └── springboot │ ├── ClientApplication.java │ ├── domain │ │ └── City.java │ └── dubbo │ ├── CityDubboConsumerService.java │ └── CityDubboService.java └── resources └── application.properties pom.xml 、 CityDubboService.java、City.java 没有改动。Dubbo 消费者通过引入接口实现 Dubbo 接口的调用。 a.application.properties 配置 1 2 3 4 5 6 7 ## 避免和 server 工程端口冲突 server.port=8081 ## Dubbo 服务消费者配置 spring.dubbo.application.name=consumer spring.dubbo.registry.address=zookeeper://127.0.0.1:2181 spring.dubbo.scan=org.spring.springboot.dubbo 因为 springboot-dubbo-server 工程启动占用了 8080 端口,所以这边设置端口为 8081。 b.CityDubboConsumerService.java 城市 Dubbo 服务消费者 1 2 3 4 5 6 7 8 9 10 11 12 @Component public class CityDubboConsumerService { @Reference(version = "1.0.0") CityDubboService cityDubboService; public void printCity() { String cityName="温岭"; City city = cityDubboService.findCityByName(cityName); System.out.println(city.toString()); } } @Reference(version = “1.0.0”) 通过该注解,订阅该接口版本为 1.0.0 的 Dubbo 服务。 这里将 CityDubboConsumerService 注入 Spring 容器,是为了更方便的获取该 Bean,然后验证这个 Dubbo 调用是否成功。 c.ClientApplication.java 客户端启动类 1 2 3 4 5 6 7 8 9 10 11 @SpringBootApplication public class ClientApplication { public static void main(String[] args) { // 程序启动入口 // 启动嵌入式的 Tomcat 并初始化 Spring 环境及其各 Spring 组件 ConfigurableApplicationContext run = SpringApplication.run(ClientApplication.class, args); CityDubboConsumerService cityService = run.getBean(CityDubboConsumerService.class); cityService.printCity(); } } 解释下这段逻辑,就是启动后从 Bean 容器中获取城市 Dubbo 服务消费者 Bean。然后调用该 Bean 方法去验证 Dubbo 调用是否成功。 四、小结 还有涉及到服务的监控,治理。这本质上和 SpringBoot 无关,所以这边不做一一介绍。感谢阿里 teaey 提供的 starter-dubbo 项目。 推荐《》 欢迎扫一扫我的公众号关注 — 及时得到博客订阅哦! — http://www.bysocket.com/ — — https://github.com/JeffLi1993 —
摘要: 原创出处:www.bysocket.com 泥瓦匠BYSocket 希望转载,保留摘要,谢谢! “年轻就不应该让自己过得太舒服” – From yong 一、Springboot 那些事 SpringBoot 很方便的集成 FreeMarker ,DAO 数据库操作层依旧用的是 Mybatis,本文将会一步一步到来如何集成 FreeMarker 以及配置的详解: Springboot 那些事: 系类文章: 《Spring Boot 之 RESRful API 权限控制》 《Spring Boot 之 HelloWorld详解》 《Springboot 整合 Mybatis 的完整 Web 案例》 《Springboot 实现 Restful 服务,基于 HTTP / JSON 传输》 《Springboot 集成 FreeMarker》 二、运行 springboot-freemarker 工程 git clone 下载工程 springboot-learning-example ,项目地址见 GitHub – https://github.com/JeffLi1993/springboot-learning-example。下面开始运行工程步骤(Quick Start): 1.数据库准备 a.创建数据库 springbootdb: 1 CREATE DATABASE springbootdb; b.创建表 city :(因为我喜欢徒步) 1 2 3 4 5 6 7 8 DROP TABLE IF EXISTS `city`; CREATE TABLE `city` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '城市编号', `province_id` int(10) unsigned NOT NULL COMMENT '省份编号', `city_name` varchar(25) DEFAULT NULL COMMENT '城市名称', `description` varchar(25) DEFAULT NULL COMMENT '描述', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; c.插入数据 1 INSERT city VALUES (1 ,1,'温岭市','BYSocket 的家在温岭。'); 2. 项目结构介绍 项目结构如下图所示: org.spring.springboot.controller – Controller 层 org.spring.springboot.dao – 数据操作层 DAO org.spring.springboot.domain – 实体类 org.spring.springboot.service – 业务逻辑层 Application – 应用启动类 resources/application.properties – 应用配置文件,应用启动会自动读取配置 resources/web – *.ftl文件,是 FreeMarker 文件配置路径。在 application.properties 配置 resources/mapper – DAO Maper XML 文件 3.改数据库配置 打开 application.properties 文件, 修改相应的数据源配置,比如数据源地址、账号、密码等。(如果不是用 MySQL,pom 自行添加连接驱动依赖,然后修改驱动名配置。) 4.编译工程 在项目根目录 springboot-learning-example,运行 maven 指令: mvn clean install 5.运行工程 右键运行 springboot-freemarker 工程 Application 应用启动类的 main 函数,然后在浏览器访问: 获取 ID 编号为 1 的城市信息页面: 1 localhost:8080/api/city/1 获取城市列表页面: 1 localhost:8080/api/city 6.补充 运行环境:JDK 7 或 8,Maven 3.0+ 技术栈:SpringBoot、Mybatis、FreeMarker 三、 springboot-freemarker 工程配置详解 具体代码见 GitHub – https://github.com/JeffLi1993/springboot-learning-example 1.pom.xml 依赖 pom.xml 代码如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>springboot</groupId> <artifactId>springboot-freemarker</artifactId> <version>0.0.1-SNAPSHOT</version> <name>springboot-freemarker :: Spring Boot 集成 FreeMarker 案例</name> <!-- Spring Boot 启动父依赖 --> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.1.RELEASE</version> </parent> <properties> <mybatis-spring-boot>1.2.0</mybatis-spring-boot> <mysql-connector>5.1.39</mysql-connector> </properties> <dependencies> <!-- Spring Boot Freemarker 依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-freemarker</artifactId> </dependency> <!-- Spring Boot Web 依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- Spring Boot Test 依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!-- Spring Boot Mybatis 依赖 --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>${mybatis-spring-boot}</version> </dependency> <!-- MySQL 连接驱动依赖 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>${mysql-connector}</version> </dependency> <!-- Junit --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> </dependencies> </project> 在 pom.xml 依赖中增加 Spring Boot FreeMarker 依赖。 2.配置 FreeMarker 然后在 application.properties 中加入 FreeMarker 相关的配置: 1 2 3 4 5 6 7 8 9 10 11 ## Freemarker 配置 ## 文件配置路径 spring.freemarker.template-loader-path=classpath:/web/ spring.freemarker.cache=false spring.freemarker.charset=UTF-8 spring.freemarker.check-template-location=true spring.freemarker.content-type=text/html spring.freemarker.expose-request-attributes=true spring.freemarker.expose-session-attributes=true spring.freemarker.request-context-attribute=request spring.freemarker.suffix=.ftl 这是我这块的配置,如果需要更多的 FreeMarker 配置,可以查看下面的详解: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 spring.freemarker.allow-request-override=false # Set whether HttpServletRequest attributes are allowed to override (hide) controller generated model attributes of the same name. spring.freemarker.allow-session-override=false # Set whether HttpSession attributes are allowed to override (hide) controller generated model attributes of the same name. spring.freemarker.cache=false # Enable template caching. spring.freemarker.charset=UTF-8 # Template encoding. spring.freemarker.check-template-location=true # Check that the templates location exists. spring.freemarker.content-type=text/html # Content-Type value. spring.freemarker.enabled=true # Enable MVC view resolution for this technology. spring.freemarker.expose-request-attributes=false # Set whether all request attributes should be added to the model prior to merging with the template. spring.freemarker.expose-session-attributes=false # Set whether all HttpSession attributes should be added to the model prior to merging with the template. spring.freemarker.expose-spring-macro-helpers=true # Set whether to expose a RequestContext for use by Spring's macro library, under the name "springMacroRequestContext". spring.freemarker.prefer-file-system-access=true # Prefer file system access for template loading. File system access enables hot detection of template changes. spring.freemarker.prefix= # Prefix that gets prepended to view names when building a URL. spring.freemarker.request-context-attribute= # Name of the RequestContext attribute for all views. spring.freemarker.settings.*= # Well-known FreeMarker keys which will be passed to FreeMarker's Configuration. spring.freemarker.suffix= # Suffix that gets appended to view names when building a URL. spring.freemarker.template-loader-path=classpath:/templates/ # Comma-separated list of template paths. spring.freemarker.view-names= # White list of view names that can be resolved. 3.展示层 Controller 详解 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 /** * 城市 Controller 实现 Restful HTTP 服务 * <p> * Created by bysocket on 07/02/2017. */ @Controller public class CityController { @Autowired private CityService cityService; @RequestMapping(value = "/api/city/{id}", method = RequestMethod.GET) public String findOneCity(Model model, @PathVariable("id") Long id) { model.addAttribute("city", cityService.findCityById(id)); return "city"; } @RequestMapping(value = "/api/city", method = RequestMethod.GET) public String findAllCity(Model model) { List<City> cityList = cityService.findAllCity(); model.addAttribute("cityList",cityList); return "cityList"; } } a.这里不是走 HTTP + JSON 模式,使用了 @Controller 而不是先前的 @RestController b.方法返回值是 String 类型,和 application.properties 配置的 Freemarker 文件配置路径下的各个 *.ftl 文件名一致。这样才会准确地把数据渲染到 ftl 文件里面进行展示。 c.用 Model 类,向 Model 加入数据,并指定在该数据在 Freemarker 取值指定的名称。 四、小结 FreeMarker 是常用的模板引擎,很多开发 Web 的必选。 推荐阅读《Springboot 那些事》 欢迎扫一扫我的公众号关注 — 及时得到博客订阅哦! — http://www.bysocket.com/ — — https://github.com/JeffLi1993 —
ysocket.com 泥瓦匠BYSocket 希望转载,保留摘要,谢谢! “怎样的人生才是没有遗憾的人生?我的体会是:(1)拥有健康;(2)创造“难忘时刻”;(3)尽力做好自己,不必改变世界;(4)活在当下。” – 《向死而生》李开复 基于上一篇《Springboot 整合 Mybatis 的完整 Web 案例》,这边我们着重在 控制层 讲讲。讲讲如何在 Springboot 实现 Restful 服务,基于 HTTP / JSON 传输。 一、运行 springboot-restful 工程 git clone 下载工程 springboot-learning-example ,项目地址见 GitHub – https://github.com/JeffLi1993/springboot-learning-example。下面开始运行工程步骤(Quick Start): 1.数据库准备 a.创建数据库 springbootdb: CREATE DATABASE springbootdb; b.创建表 city :(因为我喜欢徒步) DROP TABLE IF EXISTS `city`; CREATE TABLE `city` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT ‘城市编号’, `province_id` int(10) unsigned NOT NULL COMMENT ‘省份编号’, `city_name` varchar(25) DEFAULT NULL COMMENT ‘城市名称’, `description` varchar(25) DEFAULT NULL COMMENT ‘描述’, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; c.插入数据 INSERT city VALUES (1 ,1,’温岭市’,’BYSocket 的家在温岭。’); 2. springboot-restful 工程项目结构介绍 springboot-restful 工程项目结构如下图所示: org.spring.springboot.controller – Controller 层 org.spring.springboot.dao – 数据操作层 DAO org.spring.springboot.domain – 实体类 org.spring.springboot.service – 业务逻辑层 Application – 应用启动类 application.properties – 应用配置文件,应用启动会自动读取配置 3.改数据库配置 打开 application.properties 文件, 修改相应的数据源配置,比如数据源地址、账号、密码等。(如果不是用 MySQL,自行添加连接驱动 pom,然后修改驱动名配置。) 4.编译工程 在项目根目录 springboot-learning-example,运行 maven 指令: mvn clean install 5.运行工程 右键运行 springboot-restful 工程 Application 应用启动类的 main 函数。 用 postman 工具可以如下操作, 根据 ID,获取城市信息 GET http://127.0.0.1:8080/api/city/1 获取城市列表 GET http://127.0.0.1:8080/api/city 新增城市信息 POST http://127.0.0.1:8080/api/city 更新城市信息 PUT http://127.0.0.1:8080/api/city 删除城市信息 DELETE http://127.0.0.1:8080/api/city/2 二、springboot-restful 工程控制层实现详解 1.什么是 REST? REST 是属于 WEB 自身的一种架构风格,是在 HTTP 1.1 规范下实现的。Representational State Transfer 全称翻译为表现层状态转化。Resource:资源。比如 newsfeed;Representational:表现形式,比如用JSON,富文本等;State Transfer:状态变化。通过HTTP 动作实现。 理解 REST ,要明白五个关键要素: 资源(Resource) 资源的表述(Representation) 状态转移(State Transfer) 统一接口(Uniform Interface) 超文本驱动(Hypertext Driven) 6 个主要特性: 面向资源(Resource Oriented) 可寻址(Addressability) 连通性(Connectedness) 无状态(Statelessness) 统一接口(Uniform Interface) 超文本驱动(Hypertext Driven) 具体这里就不一一展开,详见 http://www.infoq.com/cn/articles/understanding-restful-style 2.Spring 对 REST 支持实现 CityRestController.java 城市 Controller 实现 Restful HTTP 服务 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 public class CityRestController { @Autowired private CityService cityService; @RequestMapping(value = "/api/city/{id}", method = RequestMethod.GET) public City findOneCity(@PathVariable("id") Long id) { return cityService.findCityById(id); } @RequestMapping(value = "/api/city", method = RequestMethod.GET) public List<City> findAllCity() { return cityService.findAllCity(); } @RequestMapping(value = "/api/city", method = RequestMethod.POST) public void createCity(@RequestBody City city) { cityService.saveCity(city); } @RequestMapping(value = "/api/city", method = RequestMethod.PUT) public void modifyCity(@RequestBody City city) { cityService.updateCity(city); } @RequestMapping(value = "/api/city/{id}", method = RequestMethod.DELETE) public void modifyCity(@PathVariable("id") Long id) { cityService.deleteCity(id); } } 具体 Service 、dao 层实现看代码GitHub https://github.com/JeffLi1993/springboot-learning-example/tree/master/springboot-restful 代码详解: @RequestMapping 处理请求地址映射。 method – 指定请求的方法类型:POST/GET/DELETE/PUT 等 value – 指定实际的请求地址 consumes – 指定处理请求的提交内容类型,例如 Content-Type 头部设置application/json, text/html produces – 指定返回的内容类型 @PathVariable URL 映射时,用于绑定请求参数到方法参数 @RequestBody 这里注解用于读取请求体 boy 的数据,通过 HttpMessageConverter 解析绑定到对象中 3.HTTP 知识补充 GET 请求获取Request-URI所标识的资源 POST 在Request-URI所标识的资源后附加新的数据 HEAD 请求获取由Request-URI所标识的资源的响应消息报头 PUT 请求服务器存储一个资源,并用Request-URI作为其标识 DELETE 请求服务器删除Request-URI所标识的资源 TRACE 请求服务器回送收到的请求信息,主要用于测试或诊断 CONNECT 保留将来使用 OPTIONS 请求查询服务器的性能,或者查询与资源相关的选项和需求 详情请看《JavaEE 要懂的小事:一、图解Http协议》 三、小结 Springboot 实现 Restful 服务,基于 HTTP / JSON 传输,适用于前后端分离。这只是个小demo,没有加入bean validation这种校验。还有各种业务场景。 推荐:《 Springboot 整合 Mybatis 的完整 Web 案例》 欢迎扫一扫我的公众号关注 — 及时得到博客订阅哦! — http://www.bysocket.com/ — — https://github.com/JeffLi1993 —
摘要: 原创出处:www.bysocket.com 泥瓦匠BYSocket 希望转载,保留摘要,谢谢! 推荐一本书《腾讯传》。 新年第一篇 Springboot 技术文诞生。泥瓦匠准备写写 Springboot 相关最佳实践。一方面总结下一些 Springboot 相关,一方面和大家交流交流 Springboot 框架。 现在业界互联网流行的数据操作层框架 Mybatis,下面详解下 Springboot 如何整合 Mybatis ,这边没有使用 Mybatis Annotation 这种,是使用 xml 配置 SQL。因为我觉得 SQL 和业务代码应该隔离,方便和 DBA 校对 SQL。二者 XML 对较长的 SQL 比较清晰。 一、运行 springboot-mybatis 工程 git clone 下载工程 springboot-learning-example ,项目地址见 GitHub。下面开始运行工程步骤(Quick Start): 1.数据库准备 a.创建数据库 springbootdb: 1 CREATE DATABASE springbootdb; b.创建表 city :(因为我喜欢徒步) 1 2 3 4 5 6 7 8 DROP TABLE IF EXISTS `city`; CREATE TABLE `city` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '城市编号', `province_id` int(10) unsigned NOT NULL COMMENT '省份编号', `city_name` varchar(25) DEFAULT NULL COMMENT '城市名称', `description` varchar(25) DEFAULT NULL COMMENT '描述', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; c.插入数据 1 INSERT city VALUES (1 ,1,'温岭市','BYSocket 的家在温岭。'); 2. 项目结构介绍 项目结构如下图所示: org.spring.springboot.controller – Controller 层 org.spring.springboot.dao – 数据操作层 DAO org.spring.springboot.domain – 实体类 org.spring.springboot.service – 业务逻辑层 Application – 应用启动类 application.properties – 应用配置文件,应用启动会自动读取配置 3.改数据库配置 打开 application.properties 文件, 修改相应的数据源配置,比如数据源地址、账号、密码等。(如果不是用 MySQL,自行添加连接驱动 pom,然后修改驱动名配置。) 4.编译工程 在项目根目录 springboot-learning-example,运行 maven 指令: 1 mvn clean install 5.运行工程 右键运行 Application 应用启动类的 main 函数,然后在浏览器访问: 1 http://localhost:8080/api/city?cityName=温岭市 可以看到返回的 JSON 结果: 1 2 3 4 5 6 { "id": 1, "provinceId": 1, "cityName": "温岭市", "description": "我的家在温岭。" } 如图: 二、springboot-mybatis 工程配置详解 1.pom 添加 Mybatis 依赖 1 2 3 4 5 6 <!-- Spring Boot Mybatis 依赖 --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>${mybatis-spring-boot}</version> </dependency> mybatis-spring-boot-starter 工程依赖如图: 2.在 application.properties 应用配置文件,增加 Mybatis 相关配置 1 2 3 ## Mybatis 配置 mybatis.typeAliasesPackage=org.spring.springboot.domain mybatis.mapperLocations=classpath:mapper/*.xml mybatis.typeAliasesPackage 配置为 org.spring.springboot.domain,指向实体类包路径。mybatis.mapperLocations 配置为 classpath 路径下 mapper 包下,* 代表会扫描所有 xml 文件。 mybatis 其他配置相关详解如下: mybatis.config = mybatis 配置文件名称 mybatis.mapperLocations = mapper xml 文件地址 mybatis.typeAliasesPackage = 实体类包路径 mybatis.typeHandlersPackage = type handlers 处理器包路径 mybatis.check-config-location = 检查 mybatis 配置是否存在,一般命名为 mybatis-config.xml mybatis.executorType = 执行模式。默认是 SIMPLE 3.在 Application 应用启动类添加注解 MapperScan Application.java 代码如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 /** * Spring Boot 应用启动类 * * Created by bysocket on 16/4/26. */ // Spring Boot 应用的标识 @SpringBootApplication // mapper 接口类扫描包配置 @MapperScan("org.spring.springboot.dao") public class Application { public static void main(String[] args) { // 程序启动入口 // 启动嵌入式的 Tomcat 并初始化 Spring 环境及其各 Spring 组件 SpringApplication.run(Application.class,args); } } mapper 接口类扫描包配置注解 MapperScan :用这个注解可以注册 Mybatis mapper 接口类。 4.添加相应的 City domain类、CityDao mapper接口类 City.java: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 /** * 城市实体类 * * Created by bysocket on 07/02/2017. */ public class City { /** * 城市编号 */ private Long id; /** * 省份编号 */ private Long provinceId; /** * 城市名称 */ private String cityName; /** * 描述 */ private String description; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public Long getProvinceId() { return provinceId; } public void setProvinceId(Long provinceId) { this.provinceId = provinceId; } public String getCityName() { return cityName; } public void setCityName(String cityName) { this.cityName = cityName; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } } CityDao.java: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 /** * 城市 DAO 接口类 * * Created by bysocket on 07/02/2017. */ public interface CityDao { /** * 根据城市名称,查询城市信息 * * @param cityName 城市名 */ City findByName(@Param("cityName") String cityName); } 其他不明白的,可以 git clone 下载工程 springboot-learning-example ,工程代码注解很详细。 https://github.com/JeffLi1993/springboot-learning-example。 三、其他 利用 Mybatis-generator自动生成代码 http://www.cnblogs.com/yjmyzz/p/4210554.html Mybatis 通用 Mapper3 https://github.com/abel533/Mapper Mybatis 分页插件 PageHelper https://github.com/pagehelper/Mybatis-PageHelper 最后,推荐阅读:《 Spring Boot 之 HelloWorld 详解》 欢迎扫一扫我的公众号关注 — 及时得到博客订阅哦! — http://www.bysocket.com/ — — https://github.com/JeffLi1993 —
博主:BYSocket 摘要: 原创出处:www.bysocket.com 泥瓦匠BYSocket 希望转载,保留摘要,谢谢! 新年我的本命年,大家辛苦了。新年快乐,健健康康就好。 关于计划 谈年计划,总是计划赶不上变化。但不计划,像我这种健忘的,曾经会老是在想我该干嘛。所以好记性不如烂笔头,我该记得都记着。比如天计划: 回到年计划。很多人会列出年计划,自然很多人会失败。但往往一开始很多人会感觉胜券在手中一样。年计划相比天计划,只不过是时间长点罢了。因此年计划,是每天做的事情一个阶段性小结的结果。 切勿切勿,假大空。比如“升职加薪迎娶白富美当上CTO”…升多少、加多少是个头、自然白富美看脸蛋和技巧、CTO那就胡扯吧。因此,年计划不是梦想。年计划是未来3-5年愿景的一个年度目标。是一个具体的、可实现、有结果导向、有时间约束的目标。最好最好不要超过 3 件事,因为欲望无止境,可能你先做好一件事试试会更好。 关于怎么做年计划 第一、正式心态 计划其实是种对生活对工作的记录。比如周报产生自工作日报。计划并不会夺走你很多的时间,恰恰可以帮你在更好的时间找到做更好的事情。 第二、从 3 年 5 年 10 年找出今年要干的事情 比如 5 年后,我想成为一名作者。我觉得如果不误人子弟,恰恰这是最简单的事情,做快乐的事,并帮助社会与人。但,我今年不可能成为一名作者。因为我不想误人子弟,同样得考虑销量。 年计划呢?我就先写,从一名博主开始。零散的写了个把年,系列写的不多。所以今年的目标是写技术系列文章。 第三、具体实施呢 那就简单了。从 5 年分割到年,从年分割到周就行了。具体计划是每周产出 2 篇技术系列文章。另外我用着,换着感觉就 Evernote 在手,就行了。顺便说下,我是个 Listful 控。 简单,有效,一件事就够了。这就是年计划
知识的工作者必须成为自己时间的首席执行官。 前言 这次泥瓦匠带来的是一个好玩的基于 JDK 8 time包的时间工具类 TimeUtil。本意,用 JDK 8 写个实战的时间工具类,初版泥瓦匠写的很不好,后来雨神分分钟将这个代码优化优化,谢谢雨神。就此分享下这个代码,让更多的人看到~ 一、 Java 8 time包 从 Java 1.0 有个 Date 类,想必大家不陌生,后面有了 Calendar 类(被废弃 )。API 确实比较难用,因此 Java 8 引入 java.time API,这次看看是不是很好用。大致引入了这几个对象: Instant - 瞬间类,表示时间线上的一点(与 Date 类似) Duration - 持续时间,表示两个 Instant 之间的时间 LocalDateTime - 本地日期时间,表示一个日期和时间。 本小文重点还是在使用 LocalDateTime 及其格式化类 DateTimeFormatter。 二、介绍 LocalDateTime & DateTimeFormatter APIs LocalDateTime 表示一个日期和时间,存储确定时区中的某个时间点。 例如某一次练书法弹琴活动。(ps:泥瓦匠有个小圈子,里面喜欢加入一些文艺的程序员。爱技术,爱生活,爱艺术~ 雨神就爱弹琴,日语思密达*&#@#% ) 常用api: now() 从系统中获取当前时间 parse(CharSequence text, DateTimeFormatter formatter) 从字符串按格式获取 LocalDateTime 实例 DateTimeFormatter 用于格式化时间,提供了公用的方法入口,打印和解析格式化的时间类。 常用api: format(TemporalAccessor temporal) 按格式格式化时间 ofPattern(String pattern) 按字符串指定的格式,生成时间格式 三、TimeUtil 代码详解 泥瓦匠一一道来这个代码的实现。先看代码: /** * 基于 JDK 8 time包的时间工具类 * <p/> * Created by bysocket on 16/8/23. */ public final class TimeUtil { /** * 获取默认时间格式: yyyy-MM-dd HH:mm:ss */ private static final DateTimeFormatter DEFAULT_DATETIME_FORMATTER = TimeFormat.LONG_DATE_PATTERN_LINE.formatter; private TimeUtil() { // no construct function } /** * String 转时间 * * @param timeStr * @return */ public static LocalDateTime parseTime(String timeStr) { return LocalDateTime.parse(timeStr, DEFAULT_DATETIME_FORMATTER); } /** * String 转时间 * * @param timeStr * @param format 时间格式 * @return */ public static LocalDateTime parseTime(String timeStr, TimeFormat format) { return LocalDateTime.parse(timeStr, format.formatter); } /** * 时间转 String * * @param time * @return */ public static String parseTime(LocalDateTime time) { return DEFAULT_DATETIME_FORMATTER.format(time); } /** * 时间转 String * * @param time * @param format 时间格式 * @return */ public static String parseTime(LocalDateTime time, TimeFormat format) { return format.formatter.format(time); } /** * 获取当前时间 * * @return */ public static String getCurrentDatetime() { return DEFAULT_DATETIME_FORMATTER.format(LocalDateTime.now()); } /** * 获取当前时间 * * @param format 时间格式 * @return */ public static String getCurrentDatetime(TimeFormat format) { return format.formatter.format(LocalDateTime.now()); } /** * 时间格式 */ public enum TimeFormat { /** * 短时间格式 */ SHORT_DATE_PATTERN_LINE("yyyy-MM-dd"), SHORT_DATE_PATTERN_SLASH("yyyy/MM/dd"), SHORT_DATE_PATTERN_DOUBLE_SLASH("yyyy\\MM\\dd"), SHORT_DATE_PATTERN_NONE("yyyyMMdd"), /** * 长时间格式 */ LONG_DATE_PATTERN_LINE("yyyy-MM-dd HH:mm:ss"), LONG_DATE_PATTERN_SLASH("yyyy/MM/dd HH:mm:ss"), LONG_DATE_PATTERN_DOUBLE_SLASH("yyyy\\MM\\dd HH:mm:ss"), LONG_DATE_PATTERN_NONE("yyyyMMdd HH:mm:ss"), /** * 长时间格式 带毫秒 */ LONG_DATE_PATTERN_WITH_MILSEC_LINE("yyyy-MM-dd HH:mm:ss.SSS"), LONG_DATE_PATTERN_WITH_MILSEC_SLASH("yyyy/MM/dd HH:mm:ss.SSS"), LONG_DATE_PATTERN_WITH_MILSEC_DOUBLE_SLASH("yyyy\\MM\\dd HH:mm:ss.SSS"), LONG_DATE_PATTERN_WITH_MILSEC_NONE("yyyyMMdd HH:mm:ss.SSS"); private transient DateTimeFormatter formatter; TimeFormat(String pattern) { formatter = DateTimeFormatter.ofPattern(pattern); } } } 工具类由 final TimeUtil类 及 其内部枚举类TimeFormat时间格式类 组成。 a. TimeUtil 具有私有构造函数,表示被保护,无法被外部 new 出实例。声明了默认的 DateTimeFormatter 时间格式:yyyy-MM-dd HH:mm:ss。其他则是提供了 获取当前时间 和 时间与String互转的方法。 b. TimeFormat 内部枚举类,首先它是单例的。transient 关键字目的是确保 DateTimeFormatter 无序列化存储。为啥单例,因为 DateTimeFormmatter 是无状态的,可以线程共享。 具体方法解析如下: 1. 获取当前时间 String now = TimeUtil.getCurrentDatetime(); System.out.println(now); output: 2016-08-28 16:35:23 2. 获取当前相应格式的当前时间 String now = TimeUtil.getCurrentDatetime(TimeUtil.TimeFormat.LONG_DATE_PATTERN_SLASH); System.out.println(now); output: 2016/08/28 16:36:24 3. String 转时间 默认格式:yyyy-MM-dd HH:mm:ss LocalDateTime expectedDateTime = LocalDateTime.of(2014, 11, 11, 10, 11, 11); LocalDateTime parsedTime = TimeUtil.parseTime("2014-11-11 10:11:11"); assertEquals(expectedDateTime, parsedTime); 其他格式之一:yyyy-MM-dd HH:mm:ss LocalDateTime expectedDateTime = LocalDateTime.of(2014, 11, 11, 10, 11, 11); LocalDateTime parsedTime = TimeUtil.parseTime("2014/11/11 10:11:11", LONG_DATE_PATTERN_SLASH); assertEquals(expectedDateTime, parsedTime); 4. 时间转 String 默认格式:yyyy-MM-dd HH:mm:ss LocalDateTime time = LocalDateTime.of(2014, 11, 11, 10, 11, 11); assertEquals(TimeUtil.parseTime(time), "2014-11-11 10:11:11"); 其他格式之一:yyyy-MM-dd HH:mm:ss LocalDateTime time = LocalDateTime.of(2014, 11, 11, 10, 11, 11); assertEquals(TimeUtil.parseTime(time, LONG_DATE_PATTERN_DOUBLE_SLASH), "2014\\11\\11 10:11:11"); 四、与 Old 代码互操作 java.time 类与 Old Date 代码互操作如下图: 五、小结 实战中的 JDK8 ,Show the code。 在用的项目,完善测试时候用起来才是关键。自然需要 JDK 环境支持,升级吧升级吧。 基于 JDK 8 time包的实践,这次只讲了 LocalDateTime 类,慢慢带来更多。 相关代码分享在 Github 主页 如以上文章或链接对你有帮助的话,别忘了在文章结尾处评论哈~ 你也可以点击页面右边“分享”悬浮按钮哦,让更多的人阅读这篇文章。
一、简介 Dubbo不单单只是高性能的RPC调用框架,更是SOA服务治理的一种方案。 核心: 1. 远程通信,向本地调用一样调用远程方法。 2. 集群容错 3. 服务自动发现和注册,可平滑添加或者删除服务提供者。 二、快速入门 环境:Maven,git,jdk 1. 克隆dubbo开源项目 cd ~ git clone https://github.com/alibaba/dubbo.git 2. Maven编译项目 cd ~/dubbo mvn clean install -Dmaven.test.skip ## 跳过测试 下面核心点有:zookeeper作为注册中心(服务订阅和发布依托于注册中心)、服务生产者(提供服务)项目、服务生产者(提供服务)项目和监控Web项目。 过程如下: 3. 下载启动zk cd ~ ## 下载解压 wget http://www.apache.org/dist//zookeeper/zookeeper-3.3.3/zookeeper-3.3.3.tar.gz tar zxvf zookeeper-3.3.3.tar.gz ## 启动 cd ../bin ./zkServer.sh start 下面项目遇到target目录中编译好的项目为xxx.tar.gz。请自行用下面命令解压: tar zxvf XXX.tar.gz 4. 启动服务消费者 cd ~/dubbo/dubbo-demo/dubbo-demo-consumer/target/dubbo-demo-consumer-2.5.4-SNAPSHOT/conf vim dubbo.properties - edit: dubbo.registry.adddress=zookeeper://127.0.0.1:2181 ## 更改注册中心为zk cd ../bin sh ./start.sh 5. 启动服务生产者 cd ~/dubbo/dubbo-demo/dubbo-demo-provider/target/dubbo-demo-provider-2.5.4-SNAPSHOT/conf vim dubbo.properties - edit: dubbo.registry.adddress=zookeeper://127.0.0.1:2181 cd ../bin sh ./start.sh 其实到这里已经o了,可以打开生产者消费者项目的log进行查看: ## 打开消费者的log cd dubbo-demo-consumer/target/dubbo-demo-consumer-2.5.4-SNAPSHOT/logs tail -f dubbo-demo-consumer.log 熟悉的Hello,World的案例coming… 6. 启动监控Web项目 cd ~/dubbo/dubbo-simple/dubbo-monitor-simple/target/dubbo-monitor-simple-2.5.4-SNAPSHOT/conf vim dubbo.properties - edit: dubbo.registry.adddress=zookeeper://127.0.0.1:2181 cd ../bin./start.sh ## 浏览器访问 http://127.0.0.1:8080 可以在监控中看到消费者,生产者实例等信息
设置相关 设置用户姓名和邮箱: 1 2 git config --global user.name "Your Name” git config --global user.email "email@example.com” 常用指令 克隆远程库到本地 1 git clone 将本地目录变成Git管理的仓库 1 git init 将文件添加到仓库 1 git add 'filename' 将文件提交到仓库 1 git commit -m "说明" 显示工作区的状态 1 git status 从上一个git status看出哪些文件被修改,则查看修改内容 1 git diff 显示提交的日志 1 2 git log git log --graph // 可以方便看出分支合并图 回滚到版本,也可以将工作区的回滚到暂存区 1 2 git reset git reset --hard 'commitid' // HEAD指向当前版本 回滚工作区的修改 1 git checkout -- 'filename' 主要考虑工作区,暂存区和仓库之间的操作: 1 2 3 4 git add files //把当前文件放入暂存区域。 git commit //给暂存区域生成快照并提交。 git reset -- files //用来撤销最后一次git add files,你也可以用git reset 撤销所有暂存区域文件。 git checkout -- files //把文件从暂存区域复制到工作目录,用来丢弃本地修改。 关于分支 远程库待续。。。
“房子是租的 但生活不是” 1.故事的开始 远程master分支下代码被不小心提交了很多垃圾代码或项目删掉,想要回滚到以前的某一版本并删除commit log。怎么办?情景如图: 情景很简单。老板上传了个文件,我把他删掉了。有一种办法,把文件再push下,但是也不想他看到图中那comment(ps:这样我才不会被fire)。实现上面场景的代码如下: 1 2 3 4 5 6 vim A.txt git add . git commit -a -m "add A.txt" git push rm A.txt git commit -a - 2.解决之道 2.1工作区,暂存区,本地版本库 & 远程版本库 No pic say 个 78。。。 ① 工作区:就是我们操作的目录 ② 暂存区:操作目录的快照 ③ 本地版本库:Git的精髓,人人都是中央仓库。也就是Git分布式的好处,自然对比SVN这种集中式 ④ 远程版本库:Github这种中央仓库,可以达到共享。 常用的操作也如图所示,不言而喻了。 2.2 实战解决 Talk is cheap,Show me the code or money~ 代码如下: 1 2 3 4 git log git reset --soft ${commit-id} git stash git push -f 详解如下: 第1行:git log 查看提交历史,然后找到要回滚的版本。历史如下, 1 2 3 4 5 6 7 8 commit 84686b426c3a8a3d569ae56b6788278c10b27e5b Author: JeffLi1993 <qiangqiangli1993@gmail.com> Date: Fri Apr 8 19:11:32 2016 +0800 我删除了老板的东西 commit 72bd6304c3c6e1cb7034114db1dd1b8376a6283a Author: JeffLi1993 <qiangqiangli1993@gmail.com> Date: Fri Apr 8 19:05:23 2016 +0800 add A.txt 我们想要回滚到的版本就是:72bd6304c3c6e1cb7034114db1dd1b8376a6283a 第2行,输入对应版本即可: 1 git reset --soft 72bd6304c3c6e1cb7034114db1dd1b8376a6283a 撤销到某个版本之前,之前的修改退回到暂存区(不懂看漂亮的图哦~)。soft 和 hard参数的区别就是,hard修改记录都没了,soft则会保留修改记录。 第3行:暂存为了安全起见。 第4行,覆盖 -f,对 1 git push -f 将本地master push 到远程版本库中, -f 强制覆盖。 3. 小结 1 2 1. git reset 回滚到某个版本之前 2. git push -f 强制push覆盖
一、下载解压 1 2 3 4 ## 下载Redis wget http://download.redis.io/releases/redis-2.8.17.tar.gz ## 解压 tar zxvf redis-2.8.17.tar.gz 二、编译安装 1 2 cd redis-2.8.17/ make ## 编译 三、Redis配置设置 1 2 3 4 ## 拷贝配置和启动命令到快捷目录 sudo cp redis.conf /etc/ cd src/ ## 启动命令在src目录 sudo cp redis-benchmark redis-cli redis-server /usr/bin/ 上面是方便启动罢了。 Redis配置设置 1 vim /etc/redis.conf 将 daemonize 从 no 修改成 yes,运行为了守护进程。 (ps:vim 搜索文本 esc — :/daemonize) 四、启动Redis 1 redis-server /etc/redis.conf 五、 验证 1 2 3 ps -ef | grep redis redis-cli ping ## response 'pong' 可以看到起进程。
“简单,踏实~ 读书写字放屁” 一、为何用RESTful API 1.1 RESTful是什么? RESTful(Representational State Transfer)架构风格,是一个Web自身的架构风格,底层主要基于HTTP协议(ps:提出者就是HTTP协议的作者),是分布式应用架构的伟大实践理论。RESTful架构是无状态的,表现为请求-响应的形式,有别于基于Bower的SessionId不同。 1.2理解REST有五点: 1.资源 2.资源的表述 3.状态的转移 4.统一接口 5.超文本驱动 需要理解详情,请点[传送门] 1.3 什么是REST API? 基于RESTful架构的一套互联网分布式的API设计理论。和上面资源,状态和统一接口有着密切的关系。 为啥分布式互联网架构很常见呢?请看下面两个模式 MVC模式: REST API模式: 1.4 权限怎么控制? RESTful针对资源的方法定义分简单和关联复杂两种。 基本方法定义: 1 2 3 4 5 GET /user # 获取user列表 GET /user/3 # 查看序号为3的user POST /user # 新建一个user PUT /user/3 # 更新序号为3的user DELETE /user/3 #删除user 3 资源之间的关联方法如下定义: 1 2 GET /admin/1/user/10 # 管理员1号,查看序号为3的user信息 ... 那么权限如何控制? 二、权限控制 前面说到,RESTful是无状态的,所以每次请求就需要对起进行认证和授权。 2.1 认证 身份认证,即登录验证用户是否拥有相应的身份。简单的说就是一个Web页面点击登录后,服务端进行用户密码的校验。 2.2 权限验证(授权) 也可以说成授权,就是在身份认证后,验证该身份具体拥有某种权限。即针对于某种资源的CRUD,不同用户的操作权限是不同的。 一般简单项目:做个sign(加密加盐参数)+ 针对用户的access_token 复杂的话,加入 SLL ,并使用OAuth2进行对token的安全传输。 自然,技术服务于应用场景。既简单又可以处理应用场景即可。简单,实用即可~ 三、Access Token权限解决 3.1 AccessToken 拦截器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 /** * Access Token拦截器 * <p/> * Created by bysocket on 16/4/18. */ @Component public class AccessTokenVerifyInterceptor extends HandlerInterceptorAdapter { @Autowired ValidationService validationService; private final static Logger LOG = LoggerFactory.getLogger(AccessTokenVerifyInterceptor.class); @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { LOG.info("AccessToken executing ..."); boolean flag = false; // token String accessToken = request.getParameter("token"); if (StringUtils.isNotBlank(accessToken)) { // 验证 ValidationModel v = validationService.verifyAccessToken(accessToken); // 时间过期 // 用户验证 if (v != null) { User user = userService.findById(v.getUid()); if(user != null) { request.setAttribute(CommonConst.PARAM_USER, user); LOG.info("AccessToken SUCCESS ... user:" + user.getUserName() + " - " + accessToken); flag = true; } } } if (!flag) { response.setStatus(HttpStatus.FORBIDDEN.value()); response.getWriter().print("AccessToken ERROR"); } return flag; } } 第一步:从request获取token 第二步:根据token获取校验对象信息(也可以加入过期时间校验,简单) 第三步:通过校验信息获取用户信息 3.2 配置拦截 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 /** * MVC 设置 * */ @Configuration public class WebMvcConfig extends WebMvcConfigurerAdapter { @Bean public AccessTokenVerifyInterceptor tokenVerifyInterceptor() { return new AccessTokenVerifyInterceptor(); } @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(tokenVerifyInterceptor()).addPathPatterns("/test"); super.addInterceptors(registry); } } 第一步:将拦截器配置成Bean 第二步:拦截器注册注入该拦截器,并配置拦截的URL token存哪里? ehcache,redis,db都可以。自然简单的当然是db。 四、小结 1. REST API 2. Spring Boot 拦截器
一、Spring Boot 自述 世界上最好的文档来源自官方的《Spring Boot Reference Guide》,是这样介绍的: Spring Boot makes it easy to create stand-alone, production-grade Spring based Applications that you can “just run”…Most Spring Boot applications need very little Spring configuration. Spring Boot(英文中是“引导”的意思),是用来简化Spring应用的搭建到开发的过程。应用开箱即用,只要通过 “just run”(可能是 java -jar 或 tomcat 或 maven插件run 或 shell脚本),就可以启动项目。二者,Spring Boot 只要很少的Spring配置文件(例如那些xml,property)。 因为“习惯优先于配置”的原则,使得Spring Boot在快速开发应用和微服务架构实践中得到广泛应用。 Javaer装好JDK环境和Maven工具就可以开始学习Boot了~ 二、HelloWorld实战详解 首先得有个maven基础项目,可以直接使用Maven骨架工程生成Maven骨架Web项目,即man archetype:generate命令: 1 mvn archetype:generate -DgroupId=springboot -DartifactId=springboot-helloworld -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false 2.1 pom.xml配置 代码如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>springboot</groupId> <artifactId>springboot-helloworld</artifactId> <version>0.0.1-SNAPSHOT</version> <name>springboot-helloworld :: HelloWorld Demo</name> <!-- Spring Boot 启动父依赖 --> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.3.3.RELEASE</version> </parent> <dependencies> <!-- Spring Boot web依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- Junit --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> </dependencies> </project> 只要加入一个 Spring Boot 启动父依赖即可。 2.2 Controller层 HelloWorldController的代码如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 /** * Spring Boot HelloWorld案例 * * Created by bysocket on 16/4/26. */ @RestController public class HelloWorldController { @RequestMapping("/") public String sayHello() { return "Hello,World!"; } } @RestController和@RequestMapping注解是来自SpringMVC的注解,它们不是SpringBoot的特定部分。 1. @RestController:提供实现了REST API,可以服务JSON,XML或者其他。这里是以String的形式渲染出结果。 2. @RequestMapping:提供路由信息,”/“路径的HTTP Request都会被映射到sayHello方法进行处理。 具体参考,世界上最好的文档来源自官方的《Spring Framework Document》 2.3 启动应用类 和第一段描述一样,开箱即用。如下面Application类: 1 2 3 4 5 6 7 8 9 10 11 12 /** * Spring Boot应用启动类 * * Created by bysocket on 16/4/26. */ @SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class,args); } } 1. @SpringBootApplication:Spring Boot 应用的标识 2. Application很简单,一个main函数作为主入口。SpringApplication引导应用,并将Application本身作为参数传递给run方法。具体run方法会启动嵌入式的Tomcat并初始化Spring环境及其各Spring组件。 2.4 Controller层测试类 一个好的程序,不能缺少好的UT。针对HelloWorldController的UT如下: 1 2 3 4 5 6 7 8 9 10 11 12 /** * Spring Boot HelloWorldController 测试 - {@link HelloWorldController} * * Created by bysocket on 16/4/26. */ public class HelloWorldControllerTest { @Test public void testSayHello() { assertEquals("Hello,World!",new HelloWorldController().sayHello()); } } 三、运行 Just Run的宗旨,运行很简单,直接右键Run运行Application类。同样你也可以Debug Run。可以在控制台中看到: 1 2 Tomcat started on port(s): 8080 (http) Started Application in 5.986 seconds (JVM running for 7.398) 然后访问 http://localhost:8080/ ,即可在页面中看到Spring Boot对你 say hello: 1 Hello,World! 四、小结 1. Spring Boot pom配置 2. Spring Boot 启动及原理 3. 对应代码分享在 Github 主页
摘自网上描述语段: Google Collections中的MapMaker融合了Weak Reference,线程安全,高并发性能,异步超时清理,自定义构建元素等强大功能于一身。 常阅读优秀源代码的童鞋都知道,一般叫Maker的对象都是Builder模式,而这个MapMaker就是来”Build“Map的. 一、google collection工具包的MapMaker使用: ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 public static void main(String[] args) { /** * expiration(3, TimeUnit.SECONDS)设置超时时间为3秒 */ ConcurrentMap<String , String> map = new MapMaker().concurrencyLevel(32).softKeys().weakValues() .expiration(3, TimeUnit.SECONDS).makeComputingMap( /** * 提供当Map里面不包含所get的项,可以自动加入到Map的功能 * 可以将这里的返回值放到对应的key的value中 */ new Function<String, String>() { public String apply(String s) { return "creating " + s + " -> Object"; } } ); map.put("a","testa"); map.put("b","testb"); System.out.println(map.get("a")); System.out.println(map.get("b")); System.out.println(map.get("c")); try { // 4秒后,大于超时时间,缓存失效。 Thread.sleep(4000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(map.get("a")); System.out.println(map.get("b")); System.out.println(map.get("c")); } 结果如下: ? 1 2 3 4 5 6 testa testb creating c -> Object creating a -> Object creating b -> Object creating c -> Object 二、先看下其api的相关demo片段: ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 // 使用案例:存储验证码 // <String, String> == <用户唯一,验证码> // expiration(15, TimeUnit.MINUTES) 有效期15分钟 ConcurrentMap<String,String> capthcaMap = new MapMaker().expiration(15, TimeUnit.MINUTES).makeMap(); // 设置ConcurrentMap的concurrencyLevel参数 ,例如ConcurrentHashMap是用来控制其Segment数组的大小 ConcurrentMap<String,Object> map1 = new MapMaker().concurrencyLevel(8).makeMap(); // 构造各种不同reference作为key和value的map ConcurrentMap<String,Object> map2 = new MapMaker().softKeys().weakValues().makeMap(); // 提供当Map里面不包含所get的项,可以自动加入到Map的功能 ConcurrentMap<String,Integer> map3 = new MapMaker() .makeComputingMap( new Function<String, Integer>() { public Integer apply(String key) { return 1; } } ); 可以看出过了4秒后,缓存失效,所以呈现如此结果。
“程序设计要通过编写程序的实践来学习”—Brian Kernighan 1.1 程序 何为程序?简单的说,就是为了使计算机能够做事,你需要在繁琐的细节中告诉它怎么做。对于怎么做的描述就是程序。编程是书写和测试怎么做的过程。维基百科上说,一个程序就像一个用汉语(程序设计语言)写下的红烧肉菜谱(程序),用于指导懂汉语和烹饪手法的人(体系结构)来做这个菜。做一个形象的比喻,在生活中某种意义上,你都见到程序。石器时代,“我们用石头打它”。就是个程序。 图1.1 ”我们用石头打它“ 摘自 《数学之美》 其中,他用怪叫和动作告诉小伙伴这行为,叫做程序。只有懂这个的伙伴才会一起拿起石头砸。但相对计算机来说,计算机很“笨”,为了像计算机解释这些,我们需要一种特定语法精确定义的语言。那就是编程语言,例如C++。 1.2 经典品味 ”Hello,world!“ 这是经典的例子,在屏幕中输出”Hello,world!“: ? 1 2 3 4 5 6 7 8 #include <iostream> using namespace std; int main() { cout << "Hello,World!\n"; return 0; } 运行的结果:打印Hello,World!然后,下面新的一行.一个光标位于下一行的开始。 图1.2 Hello,World!输出 程序中,主要工作的代码: ? 1 cout << "Hello,World!\n"; cout是一个标准输出流,使用 << 操作符可以将字符显示到屏幕上。很自然,这是你第一次看到缩写,cout:character outputstream的缩写。刚开始会不适应,但是当你遇见缩写多了的话,你会爱上他。 第一行: ? 1 #include <iostream> 他是个指令,这个具体是让我们使用标准的C++流I/O功能。这里,我们使用了cout和它的操作符<<.此外,使用#include包含的文件通常后缀有.h,称为头文件或头。我们基本上是使用C++自己提供的各种功能源代码。比如上面,我们需要什么功能就引入啥头文件。 程序入口: ? 1 2 3 4 int main() { return 0; } 大家都知道一个程序要告诉计算机哪里开始执行,C++每个程序必须有个main函数。一个函数基本是个指令集也称指令序列,计算机会按着原定的顺序执行。一个函数通常包括: 返回类型:这里是int,用来指定返回结果类型。 名字:这里是main 参数列表:这里为空,如果有参数,会写在() 函数体:{}里面写了函数要执行的语句。 程序的结束: ? 1 return 0; 要知道,main函数是系统调用的,自然我们需要告诉系统何时结束。返回值可以用于检查程序是否成功,特别在有些系统(unix/linux)。有main()返回的一个 0 表示程序成功终止。 1.3你看不到的程序代码之外:编译 链接 ”Hello,world!“程序的运行,通过C++编程语言。在它从人可读可改的格式转换为计算机可以理解的东西,这个过程我们需要一个成为“编译器”的程序。 何为编译器? 维基百科上说,编译器(Compiler),是一种电脑程序,它会将用某种编程语言写成的源代码(原始语言),转换成另一种编程语言(目标语言)。它主要的目的是将便于人编写,阅读,维护的高级计算机语言所写作的源代码程序,翻译为计算机能解读、运行的低阶机器语言的程序,也就是可执行文件。做个比喻,一个声控电灯,我们如果想说“把灯打开”,灯显然是不知道这句话的意思。它的世界是:”有声音我就亮“。进而,你的仆人就大叫一声,灯亮了。这个仆人的工作就是编译器的程序原理。 一个现代编译器的主要工作流程如下: 源代码(source code)→ 预处理器(preprocessor)→ 编译器(compiler)→ 汇编程序(assembler)→ 目标代码(object code)→ 链接器(Linker)→ 可执行文件(executables)。因此,可读可写的称为源代码,计算机可执行的是目标代码。在未来的日子里,你将接触到这个朋友,他非常严格。但请你记住,编译器是你编程中最好的朋友。下面我们展现程序的流程: 图1.3 Hello,World!的一生 1.4 第一章 小结 及 思考 Hello,World! 其实不重要,重要我们熟悉基本思想和工具。曾经看透菜谱,成为了大厨。曾经看破历史的,当了作家或者其他。勤奋,专注,锲而不舍。C++并不难。
图解服务化架构演进 许久没摘记了,继续告诫自己: 要静下心来,低调多做事 前言 来自dubbo的用户手册中的一句话:随着互联网的发展,网站应用的规模不断扩大,常规的垂直应用架构已无法应对,分布式服务架构以及流动计算架构势在必行,亟需一个治理系统确保架构有条不紊的演进。 常规的垂直应用架构就相当于传统的那种,现阶段传统垂直架构改造的核心就是对应用做服务化改造,服务话改造使用的核心技术架构就是分布式服务框架。 其实这篇是概念上的总结,技术概念软文,纪录此文让自己更明白什么是微服务化架构。 服务化架构演进 请看下图,也来自dubbo的用户手册,图中恰恰少了微服务架构的图。 那什么是微服务架构呢? 先从第一个图中第一个说起吧。 1.orm – 单一应用架构 我认为是一个高内聚版本,所有功能部署在一起。数据访问框架(orm)成为关键。这个架构很少被人使用,几乎接近灭绝了吧。 优点:成本低,适合功能少又简单 缺点:很多,比如无法适应高流量,二次开发难,部署成本高 2.mvc架构 - 垂直应用架构 当访问量渐渐增大,慢慢演化成用的很多的mvc架构。虽然还是所有的功能都是部署在同一个进程中,但是可以通过双机或者前置负载均衡来实现负载分流。这样应用也可以拆分成不同的几个应用,以提升性能和效率。 此时,mvc架构用于分离前后端逻辑。一方面,有一定的模块化。另一方面,加速和方便了开发。 3.rpc架构 - 分布式服务架构 当mvc垂直应用分成不同应用时,越来越多的情况下。不可避免的事应用a与应用b之间的交互。此时将核心和公共的 业务功能抽出来,作为单独的服务,并实现前后端逻辑分离。 此时则就需要提高业务的复用及整合的分布式rpc框架,例如dubbo等。 4.soa架构 - 流动计算架构 当rpc架构中的服务越来越多时,服务的生命周期的管控,容量的评估等各种问题会出现,使服务化成为瓶颈。需要增加一个调度中心来进行对服务管控,监督等。 然后,提到关键的 -- 5.微服务架构 问:什么是微服务架构? 答:它就是将功能分散到各个离散的服务中然后实现对方案的解耦。服务更原子,自治更小,然后高密度部署服务。 下面是对微服务架构的图解: 小结 伴随敏捷开发,持续交付,DevOps,Docker等高速发展,微服务必然是未来演进方向。加油~ 多了解吧。
Mac使用zsh后, mvn命令无效的解决方案 问题: 这里使用了zsh + iterm2 的配置 1.我配置好maven后,也source ~/.bash_profile。此时,我mvn -v 使是生效的。 2.问题就来了,我new了个窗口出来。mvn -v无效了。 解决: 将以下针对mvn的配置,放到.zshrc 中 export M2_HOME="/opt/maven" export PATH="$PATH:$M2_HOME/bin"export MAVEN_OPTS="-Xmx512m" 重启终端就可以了。 后来某大神说 在l.zshrc里加上source .bash_profile
前言 最近的状态:身子欠安需休息,马不停蹄安心里。身在福中不知福,却把埋怨往外送。一戒游戏除非开发其,二少心思做笔记,三需每天做备忘,四要做做人规划。多看书少放屁,多思考少做戏。像偶像学习,向目标前进。无所谓的事情,何必挂在心。健康第一,生活第二,技术第三。 真的,当自己生病了。我选择去看下《海伦的故事》,像她的《假如给我三天光明》,看了大半了…文艺类的书,我也就这样。看技术,写代码都不想的时候,看了10mins 20mins。随便看看,有时候不在意的看很神奇的是,你体会的很深刻。有时候做作的去,专注却没那么好。海伦书上的: 海伦的一生 海伦就像站在人生的起点一样,摸索着。属于她的人生。她确实知道有过小时候和正常人的生活。最后,她就像《人生七年》里面的人一样,她虽然有富裕的家庭,但是她让她自己创造了奇迹。这是她的现在的两本书 豆瓣:http://book.douban.com/doulist/12746050/ 我想这应该看看,不是说思考人生吧,也算思考人生吧。我想当今人的太浮躁,太不知道自己干嘛了。 就像海伦一样,她的人生经历,漫步,像C++的表达式一样。为什么这么说呢?慢慢的我讲着她的故事。她的故事其实就像顺序语句一样: 所谓顺序结构,就是指按照语句在程序中的先后次序一条一条的顺次执行。顺序控制语句是一类简单的语句,操作运算语句即是顺序控制语句,包括表达式语句,输入/输出等。 此处,我们借海伦来定义一个简单的程序: #include <iostream> #include <string> using namespace std; int main( ) { string name; cout << "What is your name?" << endl; cin >> name; cout << "Hi," << name << " I am Helen !"<<endl; system("PAUSE"); return 0; } 当你输入你的名字的时候,海伦向你问好。你会看到如下:(这里挺简单的我就不解释) 海伦的选择 选择语句 海伦得知自己遭遇了不幸,她虽然也会变得脾气不好。但后来她觉得对自己人生需要一个选择,做出选择的决心是很大的。一般都只是三分钟热度。为什么了,是心。是自己多少心多少才会。 C++中的选择语句又称为分支语句,它通过对给定的条件进行判断,从而决定执行两个或多个分支中的哪一支。因此,在编写选择语句之前,应该首先明确判断条件是什么,并确定当判断结果为“真”或“假”时应分别执行什么样的操作(算法)。C++程序中提供的选择语句有两种:if…else语句和switch语句。 就像选择一样,我从海伦故事里学到了(有些伤感): #include <iostream> #include <string> using namespace std; int main( ) { string lifeCarryOn; cout << "要活着,珍惜好每一天。当成最后几天是吗?(y/n)" << endl; cin >> lifeCarryOn; if (lifeCarryOn=="y") { cout << "if something happened to u,u must tell yourself :" << endl; cout << "'you must be strong and carry on.'" << endl; } else cout << "sorry!" << endl; system("PAUSE"); return 0; } 知识点: ==是关系运算符. 表示:判断是否相等的。如:if(a==b) /*判断a与b是否相等,如相等则为真*/ 你可以看到下面的输出: 海伦的尝试 循环语句 学会说话,对于你我而言,是一个多么自然简单的话语。而对海伦来说,学习语言是一个漫长重复的过程。但她的坚持,她让他的妹妹终于听懂了她的话。 就像C++里面的循环语句,C++提供了三种循环控制语句:while语句,do…while语句,for语句。三种语句都由相似的三部分组成:进入循环的条件,循环体,退出循环的条件;完成的功能也类似。所不同的只是三者进入与退出循环的方式不同。 在这里我们用循环来模拟海伦的发音过程: #include <iostream> #include <string> using namespace std; int main( ) { string status = "y"; int times = 1; while (status=="y") { cout << "正在努力发音第 " << times << " 次" << endl<<"继续吗?(y/n)"<<endl; times++; cin >> status; } system("PAUSE"); return 0; } 你可以看到下面的输出例子: 循环语句中,存在的跳转语句。break,goto,continue语句,这里就不详细讲了。 海伦的故事到自己,想着。每天也不会去碰游戏,回去听自己喜欢的歌。和好朋友聊天,分享生活。开学了,我虽然有点点讨厌中国教育。但是我不讨厌我的同伴呢。哈,去帮好兄弟一个忙了。smile go! 感谢知识来源和小结 顺序控制语句 选择控制语句 循环控制语句 跳转语句
自律,是以积极而主动的态度,去解决人生的痛苦~ 上一章,我们大谈了Hello,World的一生。下面我们细细品味基础中的一些,从一个简单的案例开始,了解对象,类型等概念。我喜欢解释例子,让大家听着有味,不枯燥~ 2.1 一个简单问候的例子 最常见的就是keyBorad input(键盘输入)。从HelloWorldd的输出,我们也想得到用户的输入。通常,实行中的程序会给它的输入产生输出。例如,输入你的名字,输出 一句问候。有时候它就在身边,像我最爱的苹果手机一样。人机交互就是那么简单~ 图2.1.1 人机交互(.net) 回到技术点,为了读取用户输入的数据,我们需要在计算机内存中某个地方放置读取所需的内容。这就是对象(Class)。对象是什么,对象时一个某种类型的内存区域。其中类型则确定了对象的信息。而一个有名字的对象,称为该对象的实例(也叫变量)。做个比喻,一看到对象大家想到的就是人,但这里人类才相当于对象。如果想到具体到你的对象,那是变量。 下面我们看下一个简单问候的例子: ? 1 2 3 4 5 6 7 8 9 10 #include <iostream> #include <string> using namespace std; int main() { string your_name; cin >> your_name; cout << "你好~" << your_name << endl; } 从代码中我们可以看出,这个程序很简单。当你输入你的名字,比如我的 “Jeff_Lee”。输出如下: 图2.1.2 显示问候程序界面 值得注意的是我的fisrt name 和 last name 不能和空格隔开。原因很简单: cin>>该操作符是根据后面变量的类型读取数据。输入结束条件 :遇到Enter、Space、Tab键。 剖析下刚刚那小程序(坚信:小程序也有学习地方): ? 1 cin >> your_name; 这个会保存一个字符串的内存区域,并将这个区域命名为your_name。意义在于计算机为这个变量分配内存空间。如图所示: 图2.1.3 内存分析 ? 1 cout << "你好~" << your_name << endl; 下面一句上面也讲过,它会将根据变量类型,让那个内存存入对象的值。本来上面空的内存区域则有了数据。如图: 图2.1.4 内存分析 2.2 变量及类型 正如我们上面所说,计算机内存存储数据的位置称为对象。我们需要用一个名字来访问,从而是这个对象成为变量。变量则有她特定的类型。基本类型罗列如下: bool 布尔型 char 字符型 8位 wchar_t 宽字符型 16位 short 短整型 16位 int 整形 16位 long 长整形 32位 float 单精度浮点型 6为有效数字 double 双精度浮点型 10位有效数字 long double 下面 看下一个好玩的例子: ? 1 2 3 4 5 6 7 8 9 10 11 12 #include <iostream> #include <string> using namespace std; int main() { string your_name; int your_age; cin >> your_name >> your_age; cout << "Hi!~" << your_name << " is " << your_age << " years old"; } 如果你尝试输入“ Jeff Lee 21”,按下回车键。你会很惊讶~,为什么不是出现 “Jeff Lee is 21 years old.”而是 图2.2 有趣例子的显示 这里我们关注的有两点,再次再次强调 cin>>该操作符是根据后面变量的类型读取数据。输入结束条件 :遇到Enter、Space、Tab键。其次,string型的“Lee”不会被int型age所读取。这是很属虎的将是随机数。为什么?因为age没有被初始化,当你执行的时候,会得到内存中某部分的垃圾值。比如上面的 -858993460.你可以想想,如果再次运行下上面的程序,输入一样的话,age会变~ 操作符>>相关资料:http://www.cplusplus.com/reference/istream/istream/operator%3E%3E/ 2.3 运算 运算,何为运算?运算的本质其实是集合的映射。最初的程序基本都是为了运算诞生的。这小块就仿佛来到了我们喜欢的数学领域。 图 2.3 早期计算机 但是,很多想数学细节情况要记住。比如: ? 1 string str = "jefflee"-"lee"; 编译器是拒绝对字符串的减运算。但编译器很笨,她不知道有些例如,age大于0; ? 1 int age = -99; 在你看来是荒唐的,在它那里确实正确的。 所以,运算要我们去符合编译器规则,结合问题定义自己规则。 小结 (这是第二章 上等下篇文章一起出来再总结。)
一.继续闲谈 闲谈中我发现思维很清晰,很有感觉。那场旅行,积淀已久。今年付出行动。经历了上次的健康风波健康,home? [java的内存浅析],坚定了自己改下一些恶习的决定。其他游戏少点玩,lol不打人人,玩人机娱乐(你说不玩吧,也不太现实。你说玩吧,玩太多玩物丧志)。就像于娟一样,得知是晚期,她并没有让自己痛苦的离开,而是写博客,记录她的日子。珍惜生命! 我有一个金点子的本子,里面记录着我想做的。我第一个,也是一直想实现的。我想为什么,不早点实现了。原来一直自己在否认自己。否认自己去实践,虽然想。总是忘了,或是投身于其他事情了故意把它搁置了。这里的旅行是我早就决定的。我想是这样的。 一场旅行,对一个小青年的帮助。少与多,让自己学会生出双手。 也画了张配图: 这篇博客,我也就穿插着我的计划来说下C++基础。 二.正文 C++起步 C++ 一直从事C的acm,不知不觉过了一年半了。搞了一年多的jEE android,发现有时候。高级语言仿佛是在用一个高级的框架一样,或者在一个高高的平台上。想起了C的算法时候,有苦也有累,但有欣喜。哈哈,我真正喜欢上C++,半年前吧。我的怪兽大师傅,为人 什么的 品位的 你懂的。我还是很无知,所以 树立偶像,学习。 就像C++一样,我慢慢的学会了怎么生活。我也慢慢学会了怎么去学习C++。就这样C++伴随着我的那条旅行一样,起步了。 一门语言无常的都讲述着是对数据的操作。所以我们必须先了解它所表达数据类型是如何的。是吧,我们就从例子里出发。 ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 #include <iostream> using namespace std; //使用命名空间std int main( ) { char c = 65; cout << c << endl; int a = 177; cout << a << endl; float f = (float)a; cout << f << endl; f = 0.999; cout << f << endl; double d = f; cout << d << endl; a = f; cout << a << endl; system("PAUSE"); return 0; } 可以在输出里面看到: 有时候就像出入门一样,你会觉得很神奇。但是有时候,你看透了就觉得它就那样。但我觉得最后我会发现他设计的美,或者设计里面有瑕疵。我幻想着,乘上动车,然后背个包。有个伴随,听着音乐,踏上了一块满是风土人情的,去寻找,去感受,去闻着土地的味道。希望这幸运之神让我认识一个小青年 再回到程序里面: 第一个表达式: ? 1 char c = 65; c是一个字符量,在内存中,字符数据以ASCII码存储,如字符‘a’的ASCII码为97。字符常量包括两类,一类是可显字符,如字母、数字和一些符号 ‘@’、‘+’等,另一类是不可显字符常量,如ASCII码为13的字符表示回车。 下面来个来个练习题: ? 1 2 3 4 int i,j; //i和j是整型变量 i='A'; //将一个字符常量赋给整型变量i j='B'; //将一个字符常量赋给整型变量j cout<<i<<' '<<j<<'\n'; //输出整型变量i和j的值,′\n′ 是换行符 下面是表达式中数据类型的转换: ? 1 float f = (float)a; 强制转换(但必须知道值是否适用于),有可能大家会被觉得很烦。但是这可是有妙用:举个例子,当地球人口剧增,你无法用上一个跨度小的去表示,但是他必须被记录。这就来了,转换的好处。下面是强转的图: 人嘛,有些麻木,有些感性。我是被妈妈从小故事里面长大的。我相信以后我的孩子,我也会去讲故事给他们听。故事感化人,妈妈一句话”好人有好报,要帮助别人“。我一直记着,所以我选择了旅途中,去帮助一个青年,有些支付不起学费的娃娃。让他们也会去好好长大,去帮助别人。 三.补充 符号常量的使用。这是一般小程序常用的方法: ? 1 2 3 4 5 6 7 8 9 10 11 12 #include <iostream> using namespace std; #define PRICE 30 //注意这不是语句,末尾不要加分号 int main ( ) { int num,total; num = 10; total = num * PRICE; cout<<"total="<<total<<endl; system("PAUSE"); return 0; } 什么叫常量? 常量是指在程序运行过程中其值不能改变的量。C++支持5种类型的常量:浮点型、整型、字符型、布尔型和枚举型。常量具有类型属性,类型决定了各种常量在内存中占据存储空间的大小。 要注意的是,对于一些经常使用,并且具有比较固定含义的常量,如圆周率3.1415926等常量在C++代码中习惯使用宏定义来表达,这样方便代码的修改;) 下面我们说这个(来自网络),我觉得这是C++指针里面的基础:指针变量的sizeof(求字节运算符) 学过数据结构的你应该知道指针是一个很重要的概念,它记录了另一个对象的地址。既然是来存放地址的,那么它当然等于计算机内部地址总线的宽度。所以在32位计算机中,一个指针变量的返回值必定是4(注意结果是以字节为单位),但是,在64位系统中指针变量的sizeof结果为8。 ? 1 2 3 4 5 6 7 8 9 10 char*pc = "abc"; int*pi; string*ps; char**ppc = &pc; void(*pf)();//函数指针 sizeof(pc);//结果为4 sizeof(pi);//结果为4 sizeof(ps);//结果为4 sizeof(ppc);//结果为4 sizeof(pf);//结果为4 注意:指针变量的sizeof值与指针所指的对象没有任何关系,正是由于所有的指针变量所占内存大小相等 留给大家一个小测试吧(32位环境): 提示下:2^32=? 那场旅行,我想摄摄影。我想交到一个小青年的心声,写写信。enough! 四.感谢知识来源和小结 C++数据类型和表达式 其实我没讲多少。有些我不想讲讲了很多遍基础。我只是把我认为的讲给大家听。谢谢
一、实战中的经验 注解相关 @Repository 标示一种作为单独在Model中使用的操作接口,没有封装的状态。 很常见的定义注解通过Spring定义Dao Bean。DAO(Data Access Object),项目中经常应用的数据库访问层可以使用该注释。但要注意和DDD(领域驱动设计)的区别。 @Service 表示一种作为服务类,也是一种特殊的@Component。 @Controller 标注一种是Controller层的类。也就是形象的Action层,我觉得Action更贴近生活点。因为我比较爱生活。和@Service一样一种特殊的@Component。 @Autowired 标注构造函数,字段,setter方法或配置方法,让其通过Spring依赖注入自动填充。 是否看过很常见的如下定义:在Action层自动注入service层Bean,可以字段也可以set方法,也就是说字段可以省了段代码(何乐而不为)。 那个配置方法,就是Java Config方法,比如说获取些主机名,端口号之类。 @RequestMapping 作用于类或者方法,用于映射Web请求。也就是一种在Servlet与Web组件之间的一种中庸之道吧。 二、配置 <context:component-scan base-package=”packagename.xxx”/> 扫描base-package下的包,将标注Spring注解(@Service、@Autowired、@Repository)的类自动转化为Bean,完成Bean的注入。比如DAO层,Service层的依赖注入。 事务配置及AOP配置提供事务增强: ①处配置了transaction事务管理器,引用了dataSource 。其内在应该是把数据库事务用Spring(代码)级别声明式管理 ②配了事务管理器,哪里用到了?当然要用的地方,比如Service层。利用AOP切面提供事务,使得事务得到打了鸡血式的使用。里面配置了③切入点(哪里用事务)及④利用Spring Advisor(类似拦截器)拦截至切入点,并引用⑤通知来正则匹配其Service方法来AOP,可以看出细粒度达到方法级别。 三、持续更新。。。
一、IOC的概念 IOC(Inverse of Control )控制反转,原本自身控制自身的权利转移到了其他身上。IOC是一个“协议”,或者理论。需要涉及到代码解耦,设计模式等一些问题考量。 其中包含了两层内容:控制 + 反转。意思明了 后来,IOC由于是种理论需要实战 — 就出现了依赖注入。 DI(Dependency Injection)依赖注入:即调用类让某一接口的实现类的依赖关系有第三方(容器或者协作类)注入,以移除调用类对某一接口实现类的依赖。这里就是将实战的武功秘籍传授了。实现类与类的依赖关系。 IOC容器还提供了Bean实例缓存、生命周期管理、Bean实例代理、事件发布、资源装载等高级服务。 二、注入的方式 依赖注入有三种注入方式: 1、通过构造函数注入 2、通过属性注入(常用方式) 3、接口注入 三、如何实现其注入呢? 那就涉及到相关的知识点,比如反射(类装载器 和 反射机制)、资源访问机制 a. 反射(Reflect) b. 资源装载器 资源,考虑资源的来源。可能是本地的File(jar、zip等),网络的URL(FTP等)。Spring提供的Resource接口比JDK访问资源API更强大个好用。其实就是一种VFS的特例吧。 1. 读取配置文件时,Ant风格资源地址支持3中匹配符: ?:匹配一个字符 *:任意一个字符 **:匹配多层路径 Resource 与 ResourceLoader UML图: BeanFactory类继承体系: ApplicationContext主要实现类: a. ClassPathXmlApplicationContext 从类路径加载配置文件 b. FileSystemXmlApplicationContext 从文件系统加载配置文件 Spring用于启动WebApplicationContext的Servlet和Web容器监听器 org.springframework.web.servlet.DispatcherServlet org.springframework.web.context.ContextLoaderListener 待续。。。
今天犯了个错: “接口变动,伤筋动骨,除非你确定只有你一个人在用”。哪怕只是throw了一个新的Exception。哈哈,这是我犯的错误。 一、接口和抽象类 类,即一个对象。 先抽象类,就是抽象出类的基础部分,即抽象基类(抽象类)。官方定义让人费解,但是记忆方法是也不错的 — 包含抽象方法的类叫做抽象类。 接口就是把抽象的深度更深,它就像用简短的非逻辑的一些规则表示类之间的关系。可以比作协议,比如通信使用的UDP/TCP协议等。 小结:类与接口是Java语言的基本抽象单元。 二、为什么有接口的两大原因 a. 向上转型为多个基类型 如此,会给开发带来相当大的灵活性。比如女神刘亦菲(Class),实现了 明星 和 女人 的接口。这样在复杂的继承结构的某类中使用它,以后在调用seeStar(Star star)或者seeWomen(Women women)方法时,只要传入其实现类(刘亦菲)即可。这也就是常说的接口可以多实现,达到了完全解耦。 b. 可复用性 即根据接口定义,让创建类有了遵循的”协议“(规则)。whatever~ 要做的仅仅建立一个接口,为了保证生成对象的非耦合。如此而来,接口的使用让代码更具可复用性,通用性和灵活性。但并不是那么万能。后面使用守则会讲到。 三、怎么用? 前人大牛总结了一些设计模式,也就是接口衍生出的一些设计模式。设计模式就是语法糖的甜蜜吧。接口让我们尝到了甜蜜。和我身边的一杯starBucks的热巧克力一样。有点太甜。比如: a.策略模式 — 方法中参数使用接口,传入的参数对象(实现类)即包含了执行的代码。如图: 调用过程如下,在方法中出入实现而已: b. 适配器模式 — 接口适配器(Interface Adapter)类,可以将不同源配到同一个目标。即暴露目标接口和实现源有共同的方法,适配器类怎么适配呢?实现目标接口,并关联了实现源对象,在实现方法中调用关联实现源真正对象,然后在里面进行各种适配操作。比如再关联一个源什么的。如图: 这其实有点AOP的味道。比如Spring AOP框架对BeforeAdvice、AfterAdvice、ThrowsAdvice三种通知类型的支持实际上是借助适配器模式来实现的。 c. 工厂模式 — 工厂对象将生成接口某个实现的对象。从而代码将实现和接口的实现分离,比较透明地将某个实现透明地替换成另一个实现。但是这里工厂调用方法是静态的,也就是简单工厂模式(静态工厂模式)。动态工厂模式无非是使用了反射达到了动态调用。 四、接口与抽象类的使用守则 第一、尽可能使每一个类或成员不被外界访问 这里的外界有个度,比如包级或者公有的。这样子可以更好地模块化,模块与模块之间通过暴露的api调动。这样如果有个模块改动接口或者类。只要担心该模块,而不会涉及其他模块。 第二、适当的使用类(抽象类)继承,更多的使用复合 继承,实现了代码重用。内部中使用继承非常安全,但是要记住什么时候使用继承。即当子类真正是超类的子类型时,才适用继承。否则尽可能使用复合,即在一个类中引用另一个类的实例。也就是说将另一个类包装了一下,这也就是装饰模式所体现的。 第三、优先考虑使用接口,相比抽象类 首先Java只许单继承,这导致抽象类定义收到极大的限制。二者,接口无法实现方法。但是Java 8提供了函数式接口。 但是接口在设计的时候注意,设计公有接口必须谨慎。接口如果被公开发行,则肯定会被广泛实现,那样改接口几乎不可能,会是巨大的工程。(这和我犯的错误一样。) 第四、占时没有第四了… 小结: 明白了 Java接口和抽象类何时用?怎么用?待续,有新的点补充吧
网站架构模式 分层 横向维度,每个部分负责单一的职责。上层对下层依赖和调用。 应用层,服务层和数据层 分割 网站越大,不同功能和服务分割出来形成高内聚低耦合模块单元。 分布式 分布式部署,即将不同模块部署在不同的服务器上,通过远程调用协同工作。 数据在分布式环境中保持数据一致性非常难,分布式难以保证。 分布式应用和服务、分布是静态资源、分布式数据和存储、分布式计算、并发和协同的分布式锁。 Zookeeper 的典型的应用场景(配置文件的管理、集群管理、同步锁、Leader 选举、队列管理等) 集群 多台服务器部署相同应用,通过负载均衡向外提供服务。 缓存 缓存就是放在离计算距离最近的位置以加快处理速度。 CDN:内容分发网络 、反向代理、本地缓存、分布式缓存。 缓存种类:1. LocalCache(独立式): 例如Ehcache、BigMemory Go 2. Standalone(单机) 3. Distributed(分布式):例如Redis-Cluster, memcache集群等等 异步 一个重要目标和驱动力是降低软件耦合性。 一个业务操作分成多个阶段,每个阶段之间通过共享数据的方式异步执行进行协作。 单一服务器内部通过多线程共享内存队列方式实现异步:处理业务操作前面的线程将输出写入到队列,后面的线程从队列中读取数据进行处理。 分布式中,多个服务器集群通过分布式消息队列实现异步,分布式消息队列可以看做内存队列的分布式部署。 异步架构(生产者消费者模式),异步消息队列的特性:可用性,加快网站响应和消除并发访问高峰。 冗余 数据库定期备份,存档保存,实现冷备份 数据库进行主从分离,实时同步实现热备份。 安全 web安全 《白帽子讲安全》 吴瀚清 架构模式在新浪微博的应用 异步推拉模式、多级缓存策略 小结 好的设计绝对不是模仿,不是生搬硬套某个模式。
软件架构,有关软件整体架构与组件的抽象描述,用于指导大型软件系统各个方面的设计。 性能 在浏览器端通过浏览器缓存,页面压缩,合理布局页面,减少Cookie传输等改善性能。 CDN、反向代理服务器,缓存热点文件 应用服务端,本地缓存和分布式缓存(缓存的主从备份,热备份) 代码层,多线程和改善内存管理 数据库服务端,索引,缓存,SQL优化 可用性 网站可用的主要的手段是冗余。多台服务器数据多台互相备份,任何一个宕机都不会影响应用的整体可用。 对应用服务器,多台服务器通过负载均衡组成一个集群共同对外服务 对于存储服务器,由于其上存储着数据,需要数据进行实时备份。 发布验证,自动化测试,自动化发布,灰度发布 伸缩性 伸缩性是指通过增加服务器,缓解不断上升的用户并发访问和不断增加 改进缓存路由算法保证缓存数据的可访问性 扩展性 如何设计网站的架构使用其能快速的响应需求变化 网站可扩展性架构主要手段:事件驱动架构和分布式服务 安全性 安全架构是保护网站不受恶意访问和攻击,保护网站的重要数据不被窃取。 小结 性能,可用性,伸缩性,扩展性和安全性是网站架构最核心的几个要素。
大型网站系统的特点 1、高并发,大流量 2、高可用:7*24小时不间断服务,不宕机 3、海量数据储存及管理 4、网络复杂 5、安全性 6、易扩展,可伸缩:需求快速变更,发布频繁 7、渐进式发展:脸谱网是宿舍,谷歌是斯坦福大学实验室,淘宝是马云家 一句话,高可用,高性能,易扩展,可伸缩且安全的网站。 大型网站架构演化发展历程 1、初始阶段 单机服务器:应用程序+数据库+文件都在一台服务器上。 2、应用服务和数据服务分离 应用服务器,文件服务器和数据库服务器。 中间通过通信的是:HTTP/Socket 3、缓存 二八定律。核心集中在百分之20 缓存分两种:应用服务器上的本地缓存 和 缓存在专门分布式缓存服务器的远程缓存。 其中Ehcache 简介:(通过RMI、JGroups或JMS进行的异步或同步的缓存复制、支持的通过RMI、JGroups或JMS进行的异步或同步的缓存复制、可靠的分发:使用TCP的内建分发机制、面向资源的RESTful,还有就是SOAP) 4、应用服务器集群 集群是常用手段,实现系统的可伸缩性。 通过负载均衡调度服务器,将请求分发任何一台服务器。 5、数据库读写分离 读部分(缓存不命中或者过期) 和 全部写操作要访问数据库 主从热备-数据库同步 6、反向代理nginx 、CDN加速网站响应 原理:缓存 CDN:就近的网络提供上机房。反向代理缓存这用户请求的资源。 7、分布式文件系统、分布式数据库系统 单表数据规模很大的情况,常用手段是业务分库。 8、NoSQL 、 搜索引擎 数据存储及大规模数据的检索 NoSQL场景: 场景:储存用户信息,比如会话、配置文件、参数、购物车等等。这些信息一般都和ID(键)挂钩,这种情景下键值数据库是个很好的选择。 9、业务拆分 分而治之,业务分成各个产品线。然后各个应用服务器。 应用之间通过超链接或者消息队列进行数据分发。 10、分布式服务 SOA、云服务 小结 云计算服务,可以让一切技术资源:计算,存储,网络按需购买即可。
网站性能测试 不同视角下的网站性能 用户视角的网站性能 前端架构优化手段,通过优化页面HTML样式,利用浏览器的并发和异步特性,调整浏览器缓存策略,使用CDN服务,反向代理等手段。 缓存加快数据读取,集群提高吞吐能力,异步消息加快请求响应及实现削峰 建设优化骨干网,使用高性能服务器 测试指标 响应时间,并发数,吞吐量(TPS QPS),性能计数器 前端性能优化 1. 减少HTTP请求:合并css,合并js,合并图片 2. 使用浏览器缓存:HTTP头 Cache-Control 和 Expires 3. 启用压缩:HTML CSS JS — GZip 4. css 放在页面上面 js放在页面下面 5. 减少Cookie传输 系统上层,三步走吧:缓存 ,集群,异步吧 分布式缓存架构 JBoss Cache : 更新同步 Memcached : 互不通信 异步操作 代码级别 1.多线程 : IO CPU 2.资源复用 3.数据结构 4.GC B+树 LSM树 HFDS
高可用架构 主要手段:数据和服务的冗余备份及失效转移。 负载均衡通过心跳检测监控服务器不可用。 其机制,实现服务器可用实时监控,自动转移,心跳检测。利用负载均衡 Session集群 1.Session复制 2. Session绑定 利用负载均衡的源地址Hash算法实现。 3. 利用Cookie记录Session 4.Session服务器
2016年的书 — A Year Of Books 分布式缓存 数据库存储服务器集群 分布式关系数据库产品: Amoeba 和 Cobar NoSQL集群: 产品: Apache HBase
2022年06月
2021年12月