Netkiller Java 手札

Java, Servlet, JavaBean, Struts, Spring ...

Mr. Neo Chan, 陈景峯(BG7NYT)

中国广东省深圳市望海路半岛城邦三期
518067
+86 13113668890

<netkiller@msn.com>

MMDVM Hotspot:

YSF80337 - CN China 1 - W24166/TG46001
 BM_China_46001 - DMR Radio ID 4600441

文档始创于2015-11-10

版权 © 2015-2019 Netkiller(Neo Chan). All rights reserved.

版权声明

转载请与作者联系，转载时请务必标明文章原始出处和作者信息及本声明。
http://www.netkiller.cn
http://netkiller.github.io
http://netkiller.sourceforge.net
微信订阅号 netkiller-ebook (微信扫描二维码）
QQ：13721218 请注明“读者”
QQ群：128659835 请注明“读者”

2017-11

关于《Netkiller Java 手札》

作者2002年开始在项目中使用Java，各种原因没有留下Java文档，2015因工作需要重新拾起Java并整理本文档。

本电子书重点内容是Spring boot, Spring cloud, Spring data, Spring security

我的系列文档

编程语言

Netkiller Architect 手札 | Netkiller Developer 手札 | Netkiller Java 手札 | Netkiller Spring 手札 | Netkiller PHP 手札 | Netkiller Python 手札
Netkiller Testing 手札 | Netkiller Cryptography 手札 | Netkiller Perl 手札 | Netkiller Docbook 手札 | Netkiller Project 手札 | Netkiller Database 手札

目录

自述
1. 系列文档
2. 作者简介
3. 写给读者
4. 文档下载
4.1. Netkiller 手札 (epub,kindle,chm,pdf) 下载
5. 打赏（Donations）
5.1. 微信（Wechat）
5.2. 支付宝（Alipay）
5.3. PayPal Donations
5.4. 银行(Bank)
1. Java
1.1. JVM
1.1.1. 安装 Java 6
1.1.1.1. HeapDumpOnOutOfMemoryError
1.1.2. java-1.8.0-openjdk
1.1.3. java - Launches a Java application.
1.1.3.1. -verbose:class 显示载入jar文件
1.1.3.2. java.io.tmpdir
1.1.3.3. 显示版本号
1.1.3.4. 列出java模块
1.1.4. jar
1.1.5. jdeps - Java class dependency analyzer.
1.2. System
1.2.1. user.dir
1.2.2. java.io.tmpdir
1.2.3. 自定义
1.2.4. System.in 标准输入(Stdin)
1.3. exec 运行shell
1.4. 类型
1.4.1. Integer
1.4.1.1. 前面补零
1.4.2. String
1.4.2.1. 随机字符串
1.4.2.2. 字符串替换处理
1.4.2.3. substring
1.4.2.4. string to timestamp
1.4.3. 类型转换
1.4.3.1. Long to String
1.4.4. Date
1.4.4.1. SimpleDateFormat
1.4.4.2. Timestamp
1.4.4.3. TimeZone
1.4.4.4. String to Date
1.4.4.5. 比较两个日期与时间
1.4.4.6. Calendar
1.4.4.7. getToday
1.4.4.8. Yesterday
1.4.4.9. ISO 8601
1.4.4.10. LocalDateTime
1.4.4.11. ZonedDateTime
1.4.5. Array
1.4.5.1. for each
1.4.5.2. Array to String
1.4.5.3.
1.4.6. float
1.4.7. double
1.4.7.1. String to double
1.4.8. BigDecimal
1.4.8.1. Convert BigDecimal Object to double value
1.4.8.2. 去除末尾多余的0
1.4.8.3. 禁用科学计数法
1.4.8.4. 移动小数点位置
1.4.9. StringBuffer
1.4.10. enum
1.4.11. byte 类型
1.4.11.1. string2byte
1.4.11.2. byte[] to String
1.4.11.3. BigInteger2byte
1.4.11.4. int to byte array
1.4.11.5. byte array to int
1.4.11.6. byte2char
1.4.11.7. longToByte64
1.4.11.8. byte64ToLong
1.4.11.9. short2byte
1.4.11.10. byte8ToDouble
1.4.11.11. byte4ToFloat
1.4.11.12. 无符号 byte
1.4.11.13. byte to hex
1.4.11.14. byte[] to hex
1.4.11.15. 连接两个 byte[]
1.4.11.16. List<Byte> to byte[]
1.5. Collection
1.5.1. ArrayList
1.5.1.1. ArrayList to String
1.5.1.2. Array to List
1.5.1.3. List to Array
1.5.2. Set 转为 List
1.5.3. 静态 List
1.5.4. List.of()
1.5.5. Set.of()
1.6. HashMap
1.6.1. static map
1.6.2. foreach hashmap
1.6.3. LinkedHashMap
1.6.4. 静态初始化 Map
1.7. Network
1.7.1. URL
1.7.2. java.io.tmpdir
1.8. JDBC
1.8.1. 安装 JDBC 包
1.8.2. MySQL
1.8.3. Oracle
1.8.3.1. SID
1.8.3.2. SERVICE_NAME
1.8.3.3. TNS
1.8.3.4. Oracle RAC Cluster
1.8.3.5. Oracle JDBC Demo
1.8.4. FAQ
1.8.4.1. java.sql.SQLRecoverableException: IO Error: The Network Adapter could not establish the connection
1.8.4.2. Exception in thread "main" java.lang.ClassNotFoundException: oracle.jdbc.driver.OracleDriver
1.9. Util
1.9.1. Properties 处理 *.properties 文件
1.9.1.1. 打开 properties 文件
1.9.1.2. propertyNames()
1.9.1.3. keySet()
1.9.1.4. entrySet()
1.9.1.5. 方法中返回 Properties
1.9.1.6.
1.9.1.7. getResourceAsStream()
1.9.1.8. store
1.9.1.9. 实现国际化
1.9.2. Logging
1.9.2.1. console
1.9.3. BASE64
1.9.4. Locale 国际化
1.9.5. ResourceBundle
1.9.6. Scanner
1.9.7. UUID
1.9.8. Arrays.equals 判断两个数组是否相等
1.10. IO
1.10.1. 取出文件名中的扩展名
1.10.1.1. getAbsolutePath() 获取绝对路径
1.10.1.2. 创建目录 mkdir()
1.10.2. 临时文件
1.10.3. FileWriter 文本写入文件
1.10.4. BufferedWriter
1.10.5. InputStreamReader
1.10.6. 获得 Resource 下文件路径
1.10.7. PrintWriter
1.10.8. OutputStreamWriter
1.10.9. FileOutputStream
1.10.10. FileInputStream
1.10.11. Scanner
1.10.12. 二进制文件
1.10.12.1. 理解二进制文件
1.10.12.2. byte 类型
1.10.12.3. boolean 布尔型
1.10.12.4. Long 型
1.10.12.5. chat 类型
1.10.12.6. UTF 字符串
1.10.12.7. Short 类型
1.10.12.8. float 单精度浮点类型
1.10.12.9. double 数据类型
1.10.12.10. 二进制文件操作演示
1.11. Reflection 反射
1.11.1. 获得所有变量
1.11.2. 批量赋值
1.11.3. 方法操作
1.11.3.1. 获得所有方法
1.11.3.2. set/get 方法
1.11.3.3. static 方法调用
1.12. Thread 线程
1.12.1. 实现异步执行
1.12.2. 继承 Thread 类实现多线程
1.12.3. 实现 Runnable 接口
1.12.4. 线程同步
1.13. java 脚本引擎
1.13.1. Maven
1.13.2. Helloworld
1.13.3. 运行脚本文件
1.13.4. 变量传递
1.13.5. 全局变量与局部变量定义
1.13.6. 调用脚本中的函数或方法
1.13.7. 脚本编译
1.13.8. jjs - Invokes the Nashorn engine.
1.14. Crypto
1.14.1. MD5
1.14.2. AES
1.14.3. AES/CBC/PKCS5PADDING
1.14.4. DES
1.15. java.security
1.15.1. 列出 Java 支持的数字摘要算法
1.15.2. 计算文件的 MD5，SHA 等 HASH 值
2. Build Tools
2.1. Apache Ant
2.1.1. 安装 ant
2.1.1.1. 1.8
2.1.1.2. 1.10.1
2.1.2. ANT
2.1.2.1. ant.project.name
2.1.2.2. 定义
2.1.3. Project
2.1.3.1. property
2.1.3.2. ant
2.1.3.3. environment
2.1.4. path
2.1.5. copy
2.1.6. javac
2.1.7. condition
2.1.8. exec
2.1.8.1. sshexec
2.1.9. if
2.1.10. macrodef
2.1.10.1. Git
2.1.10.2. Rsync
2.1.10.3. SSH
2.1.10.4. maven
2.1.11. Javascript
2.1.12. mail
2.1.13. basename
2.1.14. FAQ
2.1.14.1. warning: 'includeantruntime' was not set, defaulting to build.sysclasspath=last; set to false for repeatable builds
2.1.14.2. 调试 exec
2.2. Apache Ivy
2.2.1. Ivy Install
2.2.1.1. source code
2.2.1.2. apt-get
2.2.2. Test example
2.3. maven
2.3.1. 安装
2.3.1.1. Ubuntu
2.3.1.2. 源码安装
2.3.2. Maven 命令
2.3.2.1. help
2.3.2.2. archetype:create
2.3.2.3. clean
2.3.2.4. compile
2.3.2.5. test
2.3.2.6. package
2.3.2.7. install
2.3.2.8. war
2.3.2.9. exec
2.3.2.10. dependency
2.3.2.11. jar
2.3.2.12. help:describe
2.3.3. Maven 仓库
2.3.4. pom.xml
2.3.4.1. properties
2.3.4.2. repositories 仓库配置
2.3.4.3. dependencies
2.3.4.4. dependencyManagement
2.3.4.5. build
2.3.4.6. plugins
2.3.5. plugins
2.3.5.1. maven-compiler-plugin
2.3.5.2. maven-war-plugin
2.3.5.3. maven-antrun-plugin
2.3.5.4. maven-install-plugin
2.3.5.5. maven-surefire-plugin
2.3.5.6. maven-deploy-plugin
2.3.5.7. maven-jar-plugin
2.3.5.8. maven-dependency-plugin
2.3.5.9. spring-boot-maven-plugin
2.4. Gradle
2.4.1. 安装 Gradle
2.4.1.1. CentOS
2.4.1.2. Mac
2.4.2. gradle 命令
2.4.2.1. tasks 列出任务
2.4.3. build.gradle
2.4.3.1. repositories
2.4.3.2. dependencies
2.4.3.3. jar
2.4.4. gradle.properties
2.4.4.1. 列出 properties
2.4.4.2. 自定义 gradle.properties
2.4.4.3.
2.4.4.4. System.properties
2.4.5. Example
3. Servlet
3.1. Example
3.2. Session
3.3. HttpServletRequest
3.4. Filter
3.4.1. web.xml
3.4.2. Filter 类
3.5. Listener
3.5.1. web.xml
3.5.2. NewsListener 类
3.5.3. NewsTask 类
3.5.4. JSP 中心显示
3.6. JSP
3.6.1. 注释
3.6.2. pageContext
3.6.2.1. queryString
3.6.3. request
3.6.3.1. Form
3.6.4.
3.6.4.1. sendRedirect
3.6.5. cookie
3.6.6. session
3.6.7. page
3.6.7.1. Session
3.6.8. trimDirectiveWhitespaces
3.6.9. include
3.6.10. jsp
3.6.10.1. jsp:forward
3.6.11. error-page
3.6.12. JSP 编程
3.6.12.1. 随机数
3.6.13. FAQ
3.6.13.1. http://www.netkiller.cn/test.html;jsessionid=7D25CE666FF437F2094AA945E97CEB37
3.7. JSTL(JavaServer Pages Standard Tag Library)
3.7.1. c:set
3.7.1.1. c:remove
3.7.2. c:out
3.7.3. c:url
3.7.4. c:redirect
3.7.5. c:import
3.7.6. c:if
3.7.6.1. boolean
3.7.7. c:choose
3.7.8. c:forEach
3.7.8.1. List 处理
3.7.8.2. Map 处理
3.7.9. empty 判断是否为空
3.7.10. JSTL fmt Tag setBundle Example
3.7.10.1. fmt:message
3.7.10.2.
3.8. WebSocket
3.8.1. Server
3.8.2. Client
I. Spring Framework
4. Spring Tool Suite
4.1. 安装
4.2. Dashboard
5. Spring Boot
5.1. Spring Boot Quick start
5.1.1. pom.xml
5.1.2. Controller
5.2. Maven 插件
5.2.1. resource
5.3. 运行 Spring boot 项目
5.3.1. Maven run
5.3.2. Linux systemd
5.3.3. 传统 init.d 脚本
5.3.4. 编译用于Tomcat的 War
5.4. SpringApplication
5.4.1. @SpringBootApplication
5.4.2. @EnableAutoConfiguration
5.4.3. @ComponentScan
5.4.4. @EntityScan 实体扫描
5.4.5. @EnableJpaRepositories
5.4.6. CharacterEncodingFilter
5.4.7. 隐藏 Banner
5.4.8. 实体与仓库扫描
5.4.9. 列出 Beans
5.5. application.properties
5.5.1. application.properties 参考
5.5.2. 启动指定参数
5.5.3. 加载排除
5.5.4. PID FILE
5.5.5. 内嵌 tomcat server
5.5.6. JSON 输出与日期格式化
5.5.7. SMTP 相关配置
5.5.8. Redis
5.5.9. MongoDB
5.5.10. MySQL
5.5.11. Oracle
5.5.12. default_schema
5.5.13. datasource
5.5.14. velocity
5.5.15. Security 相关配置
5.5.16. MVC 配置
5.5.17. Kafka 相关配置
5.5.18.
5.6. Properties
5.6.1. @Value 注解
5.6.2. @PropertySource 注解载入 properties 文件
5.6.3. @EnableConfigurationProperties 引用自定义 *.properties 配置文件
5.6.4. 手工载入 *.properties 文件
5.6.5. spring.profiles.active 参数切换配置文件
5.6.6. SpringApplicationBuilder.properties() 方法添加配置项
5.7. Spring boot with Logging
5.7.1. 打印日志
5.7.2. 日志输出级别
5.7.3. 禁止控制台输出日志
5.7.4. 标准输出
5.7.5. 安天分隔日志
5.7.6. 按照文件尺寸分割日志
5.7.7. 指定Class过滤日志
5.7.8. 日志写入 MongoDB
5.7.9. logback 配置详解
5.8. Spring boot with Webpage
5.8.1. Maven
5.8.2. application.properties
5.8.3. Application
5.8.4. IndexController
5.8.5. src/main/webapp/WEB-INF/jsp/index.jsp
5.8.6. 集成模板引擎
5.9. Spring boot with Velocity template
5.9.1. Maven
5.9.2. Resource
5.9.3. Application
5.9.4. RestController
5.9.5. Test
5.10. Spring boot with Thymeleaf
5.10.1. Maven
5.10.2. application.properties
5.10.3. Controller
5.10.4. HTML5 Template
5.11. String boot with RestTemplate
5.11.1. 在控制器中完整实例
5.12. Spring boot with Session share
5.12.1. Redis
5.12.2. 测试 Session
5.12.3. JDBC
5.13. Spring boot with MongoDB
5.13.1. Maven
5.13.2. Application
5.13.3. MongoTemplate
5.13.4. Repository
5.14. Spring boot with MySQL
5.14.1. Maven
5.14.2. Resource
5.14.3. Application
5.14.4. JdbcTemplate
5.14.5. CrudRepository
5.15. Spring boot with Oracle
5.15.1. Maven
5.15.2. application.properties
5.15.3. Application
5.15.4. CrudRepository
5.15.5. JdbcTemplate
5.15.6. Controller
5.16. Spring boot with PostgreSQL
5.16.1. pom.xml
5.16.2. application.properties
5.16.3. Application
5.16.4. CrudRepository
5.16.5. JdbcTemplate
5.16.6. Controller
5.16.7. Test
5.17. 数据源配置
5.17.1. 多数据源配置
5.17.2. JPA 多数据源
5.17.3. Connection and Statement Pooling
5.18. Spring boot with Redis
5.18.1. Spring boot with Redis
5.18.2. Redis Pub/Sub
5.19. Spring boot with Caching
5.19.1. maven
5.19.2. 启用 Cache
5.19.3. 测试 Controller
5.19.4. @Cacheable 的用法
5.19.5. @CachePut 用法
5.19.6. 解决Expire 和 TTL 过期时间
5.19.7. SpEL表达式
5.20. Spring boot with Email
5.20.1. Maven
5.20.2. Resource
5.20.3. POJO
5.20.4. RestController
5.20.5. Test
5.21. Spring boot with Spring security
5.21.1. Maven
5.21.2. Reource
5.21.3. Application
5.21.4. WebSecurityConfigurer
5.21.5. RestController
5.21.6. 测试
5.21.7. Spring + Security + MongoDB
5.22. Spring boot with Oauth2
5.22.1. Maven
5.22.2. Password tools
5.22.3. Server
5.22.4. Spring boot with Oauth2 RestTemplate
5.23. Spring boot with Oauth2 jwt
5.23.1. Maven
5.23.2. Authorization Server
5.23.3. Resource Server
5.23.4. Web Security
5.23.5. 插入数据
5.23.6. 使用 CURL 测试 JWT
5.23.7. 测试 Shell
5.23.8. refresh_token
5.24. Spring boot with Oauth2 jwt 非对称证书
5.24.1. 创建证书
5.24.2. Authorization Server
5.24.3. Resource Server
5.25. Apple iOS 访问 Oauth2
5.26. Android Oauth2 + Jwt example
5.27. Spring boot with Scheduling
5.27.1. Application.java
5.27.2. Component
5.27.3. 查看日志
5.27.4. 计划任务控制开关
5.27.5. @Scheduled 详解
5.28. Spring boot with Hessian
5.28.1. Maven
5.28.2. Application
5.28.3. HessianServiceExporter
5.28.4. Service
5.28.5. RestController
5.29. Spring boot with Apache Kafka
5.29.1. 安装 kafka
5.29.2. maven
5.29.3. Spring boot Application
5.29.4. EnableKafka
5.29.5. KafkaListener
5.29.6. 测试
5.29.7. 完整的发布订阅实例
5.29.8. Spring cloud with Kafka
5.30. Spring boot with RabbitMQ(AMQP)
5.30.1. maven
5.30.2. RabbitMQConfig
5.30.3. 生产者
5.30.4. 消费者
5.31. Spring boot with Git version
5.31.1. CommonRestController 公共控制器
5.31.2. VersionRestController 测试控制器
5.31.3. 创建 .gitattributes 文件
5.32. Spring boot with HTTPS SSL
5.32.1. 生成自签名证书
5.32.2. application.properties 配置文件
5.32.3. 启动 Spring boot
5.32.4. restTemplate 调用实例
5.33. Spring boot with Elasticsearch 2.x
5.33.1. Maven
5.33.2. Application
5.33.3. application.properties
5.33.4. Domain
5.33.5. ElasticsearchRepository
5.34. Spring boot with Elasticsearch 5.5.x
5.34.1. Maven
5.34.2. Application
5.34.3. application.properties
5.34.4. ElasticsearchConfiguration
5.34.5. RestController
5.35. Spring boot with Apache Hive
5.35.1. Maven
5.35.2. application.properties
5.35.3. Configuration
5.35.4. CURD 操作实例
5.36. Spring boot with Phoenix
5.36.1. Maven
5.36.2. application.properties
5.36.3. Configuration
5.37. Spring boot with Data restful
5.37.1. Maven
5.38. Spring boot with ELK(Elasticsearch + Logstash + Kibana)
5.38.1. TCP 方案
5.38.2. Redis 方案
5.38.3. Kafka 方案
5.38.4. Other
5.39. Springboot with Ethereum (web3j)
5.39.1. Maven
5.39.2. application.properties
5.39.3. TestRestController
5.39.4. 测试
5.40. 异步执行
5.40.1. 最简单的配置
5.40.2.
5.41. Spring boot with export csv.
5.42. Spring boot with lombok
6. Spring MVC
6.1. @EnableWebMvc
6.1.1. CORS 跨域请求
6.1.2. Spring MVC CORS with WebMvcConfigurerAdapter
6.2. @Controller
6.2.1. @RequestMapping
6.2.2. RequestMapping with Path Variables - @PathVariable
6.2.3. RequestMapping with Request Parameters - @RequestParam
6.2.4. @RequestBody
6.2.5. @ModelAttribute
6.2.6. @CookieValue - 获取 Cookie 值
6.2.7. @RequestHeader - 获取 HTTP Header 信息
6.2.8. @SessionAttributes
6.2.9. @ResponseBody
6.2.10. @CrossOrigin
6.2.11. @SessionAttributes
6.2.12. ModelAndView
6.2.13. HttpServletRequest / HttpServletResponse
6.3. @RestController
6.3.1. 返回实体
6.3.2. JSON
6.3.3. 处理原始 RAW JSON 数据
6.3.4. 返回 JSON 对象 NULL 专为 "" 字符串
6.3.5. XML
6.3.6. 兼容传统 json 接口
6.3.7. @PageableDefault 分页
6.3.8. 上传文件
6.4. View
6.4.1. Using Spring’s form tag library
6.4.2. Thymeleaf
6.4.3. FreeMarker
6.5. Service
6.5.1. Application
6.5.2. 定义接口
6.5.3. 实现接口
6.5.4. 调用 Service
6.6. i18n 国际化
6.6.1. 在 appliction.properties 中配置启用 i18n
6.6.2. 创建语言包文件
6.6.3. 控制器重引用语言包
6.6.4. 参数传递
6.7. FAQ
6.7.1. o.s.web.servlet.PageNotFound
6.7.2. HTTP Status 500 - Handler processing failed; nested exception is java.lang.NoClassDefFoundError: javax/servlet/jsp/jstl/core/Config
6.7.3. 同时使用 Thymeleaf 与 JSP
6.7.4. 排除静态内容
6.7.5. HTTP Status 406
6.7.6. Caused by: java.lang.IllegalArgumentException: Not a managed type: class common.domain.Article
6.7.7. {"error":"unauthorized","error_description":"Full authentication is required to access this resource"}
7. RestTemplate - Spring4 Restful
7.1. RestTemplate Example
7.1.1. pom.xml
7.1.2. web.xml
7.1.3. springframework.xml
7.1.4. RestController
7.1.5. POJO
7.1.6. 测试
7.2. getForObject
7.2.1. 返回字符串
7.2.2. 传递 GET 参数
7.3. postForObject
7.3.1. 传递对象
7.3.2. 传递数据结构 MultiValueMap
7.4. postForEntity
7.5. PUT
7.6. Delete
7.7. 上传文件
7.8. HTTP Auth
7.8.1. Client
7.9. PKCS12
8. Spring Data
8.1. Spring Data Redis
8.1.1. 集成 Redis
8.1.2. RedisTemplate
8.1.3. Spring Data Redis - Repository Examples
8.2. Spring Data MongoDB
8.2.1. Example Spring Data MongoDB
8.2.2. @Document
8.2.3. MongoRepository
8.2.4. mongoTemplate
8.2.5. GeoJson 反序列化
8.2.6. FAQ
8.3. Spring Data MySQL
8.3.1. 选择数据库表引擎
8.3.2. @Entity
8.3.3. 创建复合主键
8.3.4. @EnableJpaAuditing 开启 JPA 审计功能
8.3.5. 实体集成
8.3.6. Repository
8.4. EntityManager
8.5. JdbcTemplate
8.5.1. execute
8.5.2. queryForInt
8.5.3. queryForInt
8.5.4. queryForObject
8.5.5. queryForList
8.5.6. queryForMap
8.5.7. query
8.5.8. queryForRowSet
8.5.9. update
8.5.10.
8.5.11. 实例参考
8.6. Elasticsearch
8.6.1. 内嵌 Elasticsearch
8.6.2. 集群模式
8.6.3. Document
8.6.4. Elasticsearch 删除操作
8.6.5. FAQ
8.7. Spring Data FAQ
8.7.1. No identifier specified for entity
8.7.2. Oracle Date 类型显示日期和时间
8.7.3. java.lang.ClassCastException: java.lang.Long cannot be cast to java.lang.Integer
8.7.4. Executing an update/delete query; nested exception is javax.persistence.TransactionRequiredException: Executing an update/delete query
9. Spring Security
9.1. 配置 Spring Security
9.1.1. 默认配置
9.1.2. 设置用户名和密码
9.1.3. 禁用 Security
9.2. EnableWebSecurity
9.3. 访问控制列表（Access Control List，ACL）
9.3.1. antMatchers
9.3.2. HTTP Auth
9.3.3. Rest
9.3.4. hasRole
9.3.5. hasAnyRole()
9.3.6. X-Frame-Options 安全
9.4. WebSecurity
9.4.1. 正则匹配
9.5. AuthenticationManagerBuilder
9.5.1. 添加用户
9.5.2. 添加多个用户，并指定角色
9.6. corsConfigurationSource()
9.7. 获取当前用户
10. Interceptor
10.1. WebMvcConfigurerAdapter
10.2. HandlerInterceptor
11. Spring Cloud
11.1. Spring Cloud Config
11.1.1. Git 仓库
11.1.2. Server
11.1.3. Client
11.1.4. Config server 用户认证
11.1.5. 加密敏感数据
11.1.6. Spring Cloud Config JDBC Backend
11.1.7. Old
11.2. Spring Cloud Netflix
11.2.1. Eureka Server
11.2.2. Eureka Client
11.2.3. Feign client
11.2.4. Zuul
11.3. Spring Cloud Finchley
11.4. Spring Cloud 相关的 application.properties 配置
11.4.1. 启用或禁用 bootstrap
11.5. FAQ
11.5.1. Feign请求超时
11.5.2. 已停止的微服务节点注销慢或不注销
11.5.3. Eureka Client (Dalston.SR1)
11.5.4. Feign 启动出错 PathVariable annotation was empty on param 0.
11.5.5. Feign 提示 Consider defining a bean of type 'common.feign.Cms' in your configuration.
12. WebFlux
12.1. Getting Started
12.1.1. Maven
12.1.2. Application
12.1.3. RestController
12.1.4. 测试
12.2. WebFlux Router
12.2.1. Component 原件
12.2.2. 路由配置
13. SpringBootTest
13.1. Maven 依赖
13.2. 测试类
13.2.1. Junit基本注解介绍
13.3.
13.3.1. Assert.assertEquals 判断相等
13.3.2. Assert.assertTrue
13.4. JPA 测试
13.5. Controller单元测试
13.6. WebTestClient
14. Tomcat Spring 运行环境
14.1. Maven
14.2. Spring Boot Quick start
14.2.1. pom.xml
14.2.2. Controller
14.3. Spring MVC configuration
14.4. Tomcat
14.5. 集成 Mybatis
14.5.1. pom.xml
14.5.2. properties
14.5.3. dataSource
14.5.4. SqlSessionFactory
14.5.5. Mapper 扫描
14.5.6. Mapper 单一class映射
14.5.7. Service
14.5.8. 测试实例
15. Miscellaneous
15.1. Object to Json
15.2. Json To Object
16. FAQ
16.1. org.hibernate.dialect.Oracle10gDialect does not support identity key generation
16.2. No identifier specified for entity
16.3. Could not read document: Invalid UTF-8 middle byte 0xd0
16.4. java.sql.SQLRecoverableException: IO Error: The Network Adapter could not establish the connection
16.5. Field javaMailSender in cn.netkiller.rest.EmailRestController required a bean of type 'org.springframework.mail.javamail.JavaMailSender' that could not be found.
16.6. org.postgresql.util.PSQLException: FATAL: no pg_hba.conf entry for host "172.16.0.3", user "test", database "test ", SSL off
16.7. Spring boot 怎样显示执行的SQL语句
16.8. Cannot determine embedded database driver class for database type NONE
16.9. Spring boot / Spring cloud 时区差8个小时
16.10. @Value 取不到值
16.11. Spring boot 2.1.0
17. MyBatis
17.1. Mybatis 入门
17.2. 接口注解
18. Apache Struts
18.1. struts.xml
18.1.1. include
18.2. Struts Tags
18.2.1. property
18.2.2. set
18.2.3. url
18.2.4. s:include
18.2.5. s:action
18.2.6. HTML Form
18.2.6.1. form
18.2.6.2. textfield
18.2.6.3. s:hidden
18.2.6.4. select
18.2.7. iterator
18.2.8. if elseif else
18.3. Action
18.3.1. redirect
18.3.2. redirectAction
18.3.3. JSON
18.3.3.1. enableGZIP 压缩传输
18.3.3.2. excludeProperties 排除 Properties
18.3.3.3.
18.3.4. 传递 Timestamp 变量
18.4. Ajax + JSON
18.4.1. GET/POST JSON
18.5. Json 内容展示
18.5.1. 禁止方法
18.5.2. 格式化日期
18.5.3. 重命名变量名
18.5.4. org.apache.struts2.json
18.6. Interceptor
18.6.1. Session
18.7. Action 中使用线程
18.8. 日志
18.9. FAQ
18.9.1. Struts 怎样判断用户来自电脑还是移动设备
19. Apache Tiles
19.1. 配置 Tiles
19.1.1. Maven
19.1.2. web.xml
19.2. Template 配置模板
19.3. Struts tiles
20. Play
21. Log
21.1. Logback
21.1.1. Maven 包
21.1.2. Example
21.2. slf4j
21.3. log4j
21.3.1. 安装 Log4j
21.3.1.1. 手工安装
21.3.1.2. Maven
21.3.2. log4j 环境变量
21.3.3. Log4j Example
21.3.4. log4j.properties
22. JSON (JavaScript Object Notation)
22.1. javax.json.*
22.1.1. Json 编码
22.1.2. Json 解码
22.1.3. URL获取Json
22.2. com.google.gson
22.2.1. map 处理
22.2.2. POJO
22.2.3. toJson
22.2.4. fromJson
22.2.5. JsonParser
22.2.6. Exmaple 范例
22.2.6.1. Map to Json
22.2.7. Exmaple 范例
22.2.7.1. Map to Json
22.2.8. 处理复杂的类型
22.3. JsonNode
23. AMQP(Advanced Message Queuing Protocol)
23.1. Send and Recv
23.2. direct
24. NoSQL
24.1. MongoDB
24.1.1. pom.xml
24.1.2. 插入操作
24.1.3. 读取操作
25. Elasticsearch API
25.1. Client
25.2. insert
25.3. Get
25.4. delete
25.5. Search
25.6. Query 查询
25.6.1. match all 匹配所有数据
25.6.2. match 匹配查询
25.6.3. match phrase 短语精准匹配
25.7. Filter 过滤
25.7.1. term
25.7.2. range
25.8. Sorting
25.9. 返回 Source 字段
25.10. Count
25.11. Example 范例
25.11.1. Spring boot 案例
25.12. FAQ
25.12.1. 显示查询 JSON 字符串
26. Jersey - RESTful Web Services in Java.
26.1. Client 2.x
26.1.1. Maven 版本
26.1.2. GET 操作
26.1.3. GET + Auth 用户认证
26.2. Client 1.x
26.2.1. Jersey + Auth + HTTP2 + SSL
27. Apache HttpComponents
27.1. org.apache.commons.lang3
27.1.1. HTML 标签处理
27.1.2. StringUtils.join 使用特定字符链接字符串
27.2. Apache HttpClient
27.2.1. Maven
27.2.2. HTTP POST 操作
27.2.2.1. Post Data
27.2.2.2. POST RAW 数据
27.2.2.3. POST GBK 编码得数据
27.2.3. HTTPS
27.2.3.1. Get https 接口
27.2.3.2. POST json 数据
27.2.4. Host name 'api.netkiller.cn' does not match the certificate subject provided
28. Cache
28.1. java memcached client
28.2. Jedis
28.2.1. 认证
28.2.2. jedis.keys
28.3. Ehcache
29. Kafka
29.1. 安装 Kafka 环境
29.2. Maven
29.3. 启动 kafka
29.4. 入门例子
29.4.1. 订阅例子
29.4.2. 发布例子
29.5. 线程例子
II. Java UnitTest
30. Junit5
30.1. JUnit 5 注解
30.1.1. @Disabled
30.1.2. @Tag
30.1.3. @Nested
30.1.4. @TestFactory
30.2. JUnit 5 断言
30.2.1. assertArrayEquals
30.2.2. assertAll
30.2.3.
30.2.4. fail
30.2.5. JUnit 5 前置条件
30.3. 依赖注入
30.3.1. TestInfo
30.3.2. TestReporter
30.4. Junit4
30.4.1. 生成 HTML 报告
30.4.2. Junit4 输出格式定义
31. TestNG
32. JaCoCo - Java code coverage tool
32.1. Maven
32.2. Gradle
33. Rest Assured
33.1. 打印出 response 的 body
34. Software Development Kit
34.1. Hessian
34.2. quartz-scheduler
34.3. Redisson
III. Android 9 Pie
35. Android Studio
35.1. 卸载 Android Studio
35.2. 代码格式化
35.3. 设置兼容最低SDK版本
36. AndroidManifest.xml
36.1.
36.2. 开启网络
36.3. 文件存储权限
36.4. 相机权限
36.5. GPS 定位权限
37. 配置文件
37.1. *.properties 文件
37.2. 再 AndroidManifest.xml 使用 meta-data element 定义
37.3. 再 build.gradle 文件中配置 productFlavors
38. UI Layout
38.1. 切换UI
38.1.1.
38.1.2. startActivity()
38.1.3. Activity 间数据传递
38.2. Button
38.2.1. 启用禁用
38.2.2. 实现 OnClickListener 接口
38.3. ListView
38.3.1. Array
38.3.2. List
38.3.3. setOnItemClickListener()
38.3.4. 用接口方法实现
38.4. Switch
38.5. GardView
38.6. GridView
38.7. ProgressBar
38.8. ImageView
38.9. Fragment
38.10. Dialog
38.11. Menu
39. Toast
39.1. 默认样式
39.2. 自定义样式
39.3. 带有图片的样式
40. Environment
40.1.
40.2.
40.3.
41. Schedule 计划任务
41.1. Time 和 TimerTask 定时刷新
41.2. 使用 Runnable 和 Handler 实现定时执行
42. Internationalization i18n with Android (国际化)
42.1. 创建国际化文件
42.2. strings.xml 文件
42.3. 翻译语言
42.4. 引用国际化文件
42.5. 切换语言
43. 存储
43.1. SharedPreferences
43.1.1. 操作模式
43.1.2. 保存数据
43.1.3. 读取数据
43.1.4. 通过 key 查询数据是否存在
43.1.5. 删除数据
43.1.6. 清空数据
43.1.7. 对象存储
43.1.8. SharedPreferences 读取物理存储文件
43.2. SD Card
43.2.1. SD Card 状态
44. 相机与相册
44.1. manifest 文件
44.2. layout
44.3. Activity
44.4. LED flash 做手电筒
45. 麦克风与录音
45.1. 开启麦克风和SD卡权限
45.2. layout
45.3. Activity
46. 多媒体开发
46.1. MediaPlayer
46.2. VideoView 开发
46.2.1. 播放网络视频
46.2.2. MediaController 添加翻页事件
46.2.3. 静音播放视频
46.2.4. 更新进度条
46.2.5. 完整的例子
46.3. SurfaceView
46.4. Vitamio
47. 定位
47.1. manifest 权限配置
47.2. layout
47.3. Activity
48. 电话
48.1. SIM 卡状态
48.2. 通信录与拨打电话
48.3.
49. 消息广播
49.1. 动态注册
49.2. 静态注册
49.3. 自定义用户消息广播
49.4. 本地广播
50. Service
50.1. Service的基本用法
50.1.1. manifest 文件
50.1.2. 创建 Service
50.1.3. Layout 代码
50.1.4. Activity 代码
50.2. Service 中启动线程
50.3. Service和Activity通信
50.3.1. Layout
50.3.2. Service
50.3.3. Activity
51. NFC (Near field communication)
51.1. AndroidManifest.xml 文件配置
51.2. Loyout 文件
51.3. Activity 文件
52. OkHttp - An HTTP & HTTP/2 client for Android and Java applications
52.1. Gradle
52.2. AndroidManifest.xml 开启网络访问权限
52.3. okhttp 默认是 HTTPS 开启 HTTP
52.4. GET
52.5. POST
52.5.1. POST Form Data
52.5.2. POST RAW JSON
52.5.3. 数据流提交
52.6. http header 相关设置
52.6.1. 设置 HTTP 头
52.6.2. Cookie 管理
52.6.3. 禁用缓存
52.6.4. 设置缓存 max-age
52.6.5. 强制缓存
52.7. HTTP Base Auth
52.8. HttpUrl.Builder 组装 URL 地址参数
52.9. Android Activity Example
52.10. Android Oauth2 + Jwt example
52.11. HTTP/2
53. EventBus
53.1. 添加 EventBus 依赖到项目Gradle文件
53.2. 快速开始一个演示例子
53.2.1. 创建 MessageEvent 类
53.2.2. Layout
53.2.3. Activity
53.3. Sticky Events
53.3.1. MainActivity
53.3.2. StickyActivity
53.3.3. MessageEvent
53.3.4. 删除粘性事件
53.4. 线程模型
53.5. 配置 EventBus
53.6. 事件优先级
53.7. 捕获异常事件
54. 设计模式
54.1. 单例模式
55.
55.1. java.net.UnknownServiceException: CLEARTEXT communication to 192.168.0.185 not permitted by network security policy
55.2. Caused by: android.os.NetworkOnMainThreadException

范例清单

1.1. /etc/profile.d/java.sh
2.1. Maven properties
5.1. Spring boot with Velocity template (pom.xml)
5.2. Example Spring boot with Oracle
5.3. RedisTemplate
5.4. Spring boot with Email (pom.xml)
5.5. Spring boot with Apache kafka.
5.6. Spring boot with Apache kafka.
5.7. Test Spring Kafka
8.1. Spring Data Redis Example
8.2. Spring Data MongoDB - springframework-servlet.xml
11.1. Share feign interface.
14.1. MyBatis
28.1. memcached.java


自述

目录

1. 系列文档
2. 作者简介
3. 写给读者
4. 文档下载
4.1. Netkiller 手札 (epub,kindle,chm,pdf) 下载
5. 打赏（Donations）
5.1. 微信（Wechat）
5.2. 支付宝（Alipay）
5.3. PayPal Donations
5.4. 银行(Bank)

目前技术书籍的价格一路飙升，动则￥80，￥100，少则￥50，￥60. 技术书籍有时效性，随着技术的革新或淘汰，大批书记成为废纸垃圾。并且这些书技术内容雷同，相互抄袭，质量越来越差，甚至里面给出的例子错误百出，只能购买影印版，或者翻译的版本。

在这种背景下我便萌生了自己写书的想法，资料主要来源是我的笔记与例子。我并不想出版，只为分享，所有我制作了基于CC License 发行的系列电子书。

本书注重例子，少理论（捞干货），只要你对着例子一步一步操作，就会成功，会让你有成就感并能坚持学下去，因为很多人遇到障碍就会放弃，其实我就是这种人，只要让他看到希望，就能坚持下去。

为什么写这篇文章

有很多想法,工作中也用不到所以未能实现，所以想写出来,和大家分享.有一点写一点,写得也不好,只要能看懂就行,就当学习笔记了.

开始零零碎碎写过一些文档，也向维基百科供过稿，但维基经常被ZF封锁，后来发现sf.net可以提供主机存放文档，便做了迁移。并开始了我的写作生涯。

这篇文档是作者10年来对工作的总结,是作者一点一滴的积累起来的，有些笔记已经丢失，所以并不完整。

因为工作太忙整理比较缓慢。目前的工作涉及面比较窄所以新文档比较少。

我现在花在技术上的时间越来越少，兴趣转向摄影，无线电。也想写写摄影方面的心得体会。

写作动力:

曾经在网上看到外国开源界对中国的评价，中国人对开源索取无度，但贡献却微乎其微.这句话一直记在我心中，发誓要为中国开源事业做我仅有的一点微薄贡献

另外写文档也是知识积累，还可以增加在圈内的影响力.

人跟动物的不同,就是人类可以把自己学习的经验教给下一代人.下一代在上一代的基础上再创新,不断积累才有今天.

所以我把自己的经验写出来,可以让经验传承

没有内容的章节:

目前我自己一人维护所有文档，写作时间有限，当我发现一个好主题就会加入到文档中，待我有时间再完善章节，所以你会发现很多章节是空无内容的.

文档目前几乎是流水帐试的写作，维护量很大，先将就着看吧.

我想到哪写到哪,你会发现文章没一个中心,今天这里写点,明天跳过本章写其它的.
文中例子绝对多,对喜欢复制然后粘贴朋友很有用,不用动手写,也省时间.
理论的东西,网上大把,我这里就不写了,需要可以去网上查.
我爱写错别字,还有一些是打错的,如果发现请指正.
文中大部分试验是在Debian/Ubuntu/Redhat AS上完成.

1. 系列文档

下面是我多年积累下来的经验总结，整理成文档供大家参考:

Netkiller Architect 手札
Netkiller Developer 手札
Netkiller PHP 手札
Netkiller Python 手札
Netkiller Testing 手札
Netkiller Cryptography 手札
Netkiller Linux 手札
Netkiller FreeBSD 手札
Netkiller Shell 手札
Netkiller Security 手札
Netkiller Web 手札
Netkiller Monitoring 手札
Netkiller Storage 手札
Netkiller Mail 手札
Netkiller Docbook 手札
Netkiller Version 手札
Netkiller Database 手札
Netkiller PostgreSQL 手札
Netkiller MySQL 手札
Netkiller NoSQL 手札
Netkiller LDAP 手札
Netkiller Network 手札
Netkiller Cisco IOS 手札
Netkiller H3C 手札
Netkiller Multimedia 手札
Netkiller Management 手札
Netkiller Spring 手札
Netkiller Perl 手札
Netkiller Amateur Radio 手札


2. 作者简介

陈景峯 (ㄔㄣ ㄐ丨ㄥ ㄈㄥ)

Nickname：netkiller | English name: Neo chen | Nippon name: ちんけいほう (音訳) | Korean name: 천징봉 | Thailand name: ภูมิภาพภูเขา | Vietnam: Trần Cảnh Phong

Callsign: BG7NYT | QTH: ZONE CQ24 ITU44 ShenZhen, China

程序猿，攻城狮，挨踢民工, Full Stack Developer, UNIX like Evangelist, 业余无线电爱好者（呼号：BG7NYT）,户外运动，山地骑行以及摄影与钢琴爱好者。

《Netkiller 系列 手札》的作者

成长阶段

1981年1月19日(庚申年腊月十四)出生于黑龙江省青冈县建设乡双富大队第一小队

1989年9岁随父母迁居至黑龙江省伊春市，悲剧的天朝教育，不知道那门子归定，转学必须降一级，我本应该上一年级，但体制让我上学前班，那年多都10岁了

1995年小学毕业，体制规定借读要交3000两银子(我曾想过不升初中)，亲戚单位分楼告别平房，楼里没有地方放东西，把2麻袋书送给我，无意中发现一本电脑书BASIC语言，我竟然看懂了，对于电脑知识追求一发而不可收，后面顶零花钱，压岁钱主要用来买电脑书《MSDOS 6.22》《新编Unix实用大全》《跟我学Foxbase》。。。。。。

1996年第一次接触UNIX操作系统，BSD UNIX, Microsoft Xinux(盖茨亲自写的微软Unix，知道的人不多)

1997年自学Turbo C语言，苦于没有电脑，后来学校建了微机室才第一次使用QBASIC(DOS 6.22 自带命令)，那个年代只能通过软盘拷贝转播，Trubo C编译器始终没有搞到，

1997年第一次上Internet网速只有9600Bps, 当时全国兴起各种信息港域名格式是www.xxxx.info.net, 访问的第一个网站是NASA下载了很多火星探路者拍回的照片，还有“淞沪”sohu的前身

1998~2000年在哈尔滨学习计算机，充足的上机时间，但老师让我们练打字（明伦五笔/WT）打字不超过80个/每分钟还要强化训练，不过这个给我的键盘功夫打了好底。

1999年学校的电脑终于安装了光驱，在一张工具盘上终于找到了Turbo C, Borland C++与Quick Basic编译器，当时对VGA图形编程非常感兴趣，通过INT33中断控制鼠标，使用绘图函数模仿windows界面。还有操作 UCDOS 中文字库，绘制矢量与点阵字体。

2000年沉迷于Windows NT与Back Office各种技术，神马主域控制器，DHCP，WINS，IIS，域名服务器，Exchange邮件服务器，MS Proxy, NetMeeting...以及ASP+MS SQL开发；用56K猫下载了一张LINUX。ISO镜像，安装后我兴奋的24小时没有睡觉。

职业生涯

2001年来深圳进城打工,成为一名外来务工者. 在一个4人公司做PHP开发，当时PHP的版本是2。0, 开始使用Linux Redhat 6.2.当时很多门户网站都是用FreeBSD,但很难搞到安装盘，在网易社区认识了一个网友,从广州给我寄了一张光盘，FreeBSD 3.2

2002年我发现不能埋头苦干,还要学会"做人".后辗转广州工作了半年，考了一个Cisco CCNA认证。回到深圳重新开始，在车公庙找到一家工作做Java开发

2003年这年最惨,公司拖欠工资16000元,打过两次官司2005才付清.

2004年开始加入分布式计算团队,目前成绩，工作仍然是Java开发并且开始使用PostgreSQL数据库。

2004-10月开始玩户外和摄影

2005-6月成为中国无线电运动协会会员,呼号BG7NYT,进了一部Yaesu FT-60R手台。公司的需要转回PHP与MySQL，相隔几年发现PHP进步很大。在前台展现方面无人能敌，于是便前台使用PHP，后台采用Java开发。

2006年单身生活了这么多年,终于找到归宿. 工作更多是研究PHP各种框架原理

2007物价上涨,金融危机，休息了4个月（其实是找不到工作）,关外很难上439.460中继，搞了一台Yaesu FT-7800.

2008终于找到英文学习方法， 《Netkiller Developer 手札》，《Netkiller Document 手札》

2008-8-8 08:08:08 结婚,后全家迁居湖南省常德市

2009《Netkiller Database 手札》,2009-6-13学车，年底拿到C1驾照

2010对电子打击乐产生兴趣，计划学习爵士鼓。由于我对Linux热爱，我轻松的接管了公司的运维部，然后开发运维两把抓。我印象最深刻的是公司一次上架10个机柜，我们用买服务器纸箱的钱改善伙食。我将40多台服务器安装BOINC做压力测试，获得了中国第二的名次。

2011 平凡的一年，户外运动停止，电台很少开，中继很少上，摄影主要是拍女儿与家人，年末买了一辆山地车

2012 对油笔画产生了兴趣，活动基本是骑行银湖山绿道，

2013开始学习民谣吉他，同时对电吉他也极有兴趣；最终都放弃了。这一年深圳开始推数字中继2013-7-6日入手Motorola MOTOTRBO XIR P8668，Netkiller 系列手札从Sourceforge向Github迁移；年底对MYSQL UDF，Engine与PHP扩展开发产生很浓的兴趣，拾起遗忘10+年的C，写了几个mysql扩展（图片处理，fifo管道与ZeroMQ），10月份入Toyota Rezi 2.5V并写了一篇《攻城狮的苦逼选车经历》

2014-9-8 在淘宝上买了一架电钢琴 Casio Privia PX-5S pro 开始陪女儿学习钢琴，由于这家钢琴是合成器电钢，里面有打击乐，我有对键盘鼓产生了兴趣。

2014-10-2号罗浮山两日游，对中国道教文化与音乐产生了兴趣，10月5号用了半天时间学会了简谱。10月8号入Canon 5D Mark III + Canon Speedlite 600EX-RT香港过关被查。

2014-12-20号对乐谱制作产生兴趣（https://github.com/SheetMusic/Piano），给女儿做了几首钢琴伴奏曲，并计划将小汤，大汤，巴斯蒂安，拜厄，车尔尼...制谱然后生成MIDI与WAV文件。

2015-09-01 晚饭后拿起爵士鼓基础教程尝试在Casio Privia PX-5S pro演练，经过反复琢磨加上之前学钢琴的乐理知识，终于在02号晚上，打出了简单的基本节奏，迈出了第一步。

2016 对弓箭（复合弓）产生兴趣，无奈兲朝法律法规不让玩。每周游泳轻松1500米无压力，年底入 xbox one s 和 Yaesu FT-2DR, 同时开始关注功放音响这块

2017 7月9号入 Yamaha RX-V581 功放一台，连接Xbox打游戏爽翻了，入Kindle电子书，计划学习蝶泳，果断放弃运维和开发知识体系转攻区块链。

2018 从溪山美地搬到半岛城邦，丢弃了多年攒下的家底。11 月开始玩 MMDVM，使用 Yaesu FT-7800 发射，连接MMDVM中继板，树莓派，覆盖深圳湾，散步骑车通联两不误。

职业生涯路上继续打怪升级

联系作者

http://netkiller.github.io/

http://netkiller.sourceforge.net/

Mobile: +86 13113668890

Callsign: BG7NYT QTH: Shenzhen, China, MMDVM：YSF China 1，TG4601

Email: netkiller@msn.com

MSN: netkiller@msn.com

ICQ: 101888222

QQ: 291379, 13721218

Yahoo/AIM/GTalk: bg7nyt

IRC: irc://irc.freenode.net /nickname netkiller or neo

注：请不要问我安装问题！

Xbox club

我再xbox上的ID是netkiller xbox，我创建了一个俱乐部 netkiller 欢迎加入。

CQ CQ CQ DE BG7NYT

写给火腿:

欢迎无线电爱好者和我QSO,我的QTH在深圳宝安区龙华镇溪山美地,设备YAESU FT-50R,FT-60R,FT-7800 144-430双段机, 拉杆天线/GP天线 Nagoya MAG-79EL-3W/Yagi

如果这篇文章对你有所帮助,请寄给我一张QSL卡片,qrz.cn or qrz.com or hamcall.net

Personal Amateur Radiostations of P.R.China

ZONE CQ24 ITU44 ShenZhen, China

Best Regards, VY 73! OP. BG7NYT

守听频率 DMR 438.460 -10 Slot 1 Color 12

Blogger

LinkedIn: http://cn.linkedin.com/in/netkiller

OSChina: http://my.oschina.net/neochen/

Facebook: https://www.facebook.com/bg7nyt

ITEYE: http://netkiller-github-com.iteye.com

Flickr: http://www.flickr.com/photos/bg7nyt/

Twitter: http://twitter.com/bg7nyt

Digg: http://www.digg.com/netkiller

Disqus: http://disqus.com/netkiller/

solidot: http://solidot.org/~netkiller/

51CTO: http://bg7nyt.blog.51cto.com/|http://g.51cto.com/netkiller

pengyou: http://www.pengyou.com/netkiller

weibo: http://weibo.com/bg7nyt

163 Blog: http://t.163.com/netkiller


3. 写给读者

写给读者

欢迎提出宝贵的建议,如有问题请到 邮件列表 讨论

至读者：
我不知道什么时候，我不再更新文档或者退出IT行业去从事其他工作，我必须给这些文档找一个归宿，让他能持续更新下去。
我想捐赠给某些基金会继续运转，或者建立一个团队维护它。
我用了10年时间坚持不停地写作，持续更新，才有今天你看到的《Netkiller 手扎》系列文档，在中国能坚持10年，同时没有任何收益的技术类文档，是非常不容易的。
有很多时候想放弃，看到外国读者的支持与国内社区的影响，我坚持了下来。
中国开源事业需要各位参与，不要成为局外人，不要让外国人说：中国对开源索取无度，贡献却微乎其微。
我们参与内核的开发还比较遥远，但是进个人能力，写一些文档还是可能的。


4. 文档下载

HTML 格式

http://netkiller.github.com/home/technology.html

http://netkiller.sourceforge.net/home/technology.html

你可以使用镜像下载获得HTML文档，例如使用wget软件下载整个网站

                $ wget -m http://netkiller.github.io/index.html

指定下载

                $ wget -m http://netkiller.github.io/centos/index.html

CHM 格式电子文档下载

https://sourceforge.net/projects/netkiller/files/htmlhelp.chm/

PDF 格式

Download PDF Document 下载PDF文档1

Download PDF Document 下载PDF文档2

EPUB 格式

http://netkiller.sourceforge.net/technology.html

通过 GIT 镜像整个网站

https://github.com/netkiller/netkiller.github.com.git

$ git clone https://github.com/netkiller/netkiller.github.com.git

其他方式

获得光盘介质，RPM包，DEB包，如有特别需要，请联系我

YUM 在线安装电子书

http://netkiller.sourceforge.net/pub/repo/

# cat >> /etc/yum.repos.d/netkiller.repo <<EOF
[netkiller]
name=Netkiller Free Books
baseurl=http://netkiller.sourceforge.net/pub/repo/
enabled=1
gpgcheck=0
gpgkey=
EOF

查找包

# yum search netkiller

netkiller-centos.x86_64 : Netkiller centos Cookbook
netkiller-cryptography.x86_64 : Netkiller cryptography Cookbook
netkiller-docbook.x86_64 : Netkiller docbook Cookbook
netkiller-linux.x86_64 : Netkiller linux Cookbook
netkiller-mysql.x86_64 : Netkiller mysql Cookbook
netkiller-php.x86_64 : Netkiller php Cookbook
netkiller-postgresql.x86_64 : Netkiller postgresql Cookbook
netkiller-python.x86_64 : Netkiller python Cookbook
netkiller-version.x86_64 : Netkiller version Cookbook

安装包

                yum install netkiller-docbook

4.1. Netkiller 手札 (epub,kindle,chm,pdf) 下载

Netkiller 手札 2018版
Netkiller 手札 2017版
Netkiller 手札 2017版


5. 打赏（Donations）

If you like this documents, please make a donation to support the authors' efforts. Thank you!

您可以通过微信，支付宝，贝宝给作者打赏。

5.1. 微信（Wechat）

5.2. 支付宝（Alipay）

5.3. PayPal Donations

https://www.paypal.me/netkiller

5.4. 银行(Bank)

招商银行(China Merchants Bank) 陈景峰 9555500000007459


第 1 章 Java

目录

1.1. JVM
1.1.1. 安装 Java 6
1.1.1.1. HeapDumpOnOutOfMemoryError
1.1.2. java-1.8.0-openjdk
1.1.3. java - Launches a Java application.
1.1.3.1. -verbose:class 显示载入jar文件
1.1.3.2. java.io.tmpdir
1.1.3.3. 显示版本号
1.1.3.4. 列出java模块
1.1.4. jar
1.1.5. jdeps - Java class dependency analyzer.
1.2. System
1.2.1. user.dir
1.2.2. java.io.tmpdir
1.2.3. 自定义
1.2.4. System.in 标准输入(Stdin)
1.3. exec 运行shell
1.4. 类型
1.4.1. Integer
1.4.1.1. 前面补零
1.4.2. String
1.4.2.1. 随机字符串
1.4.2.2. 字符串替换处理
1.4.2.3. substring
1.4.2.4. string to timestamp
1.4.3. 类型转换
1.4.3.1. Long to String
1.4.4. Date
1.4.4.1. SimpleDateFormat
1.4.4.2. Timestamp
1.4.4.3. TimeZone
1.4.4.4. String to Date
1.4.4.5. 比较两个日期与时间
1.4.4.6. Calendar
1.4.4.7. getToday
1.4.4.8. Yesterday
1.4.4.9. ISO 8601
1.4.4.10. LocalDateTime
1.4.4.11. ZonedDateTime
1.4.5. Array
1.4.5.1. for each
1.4.5.2. Array to String
1.4.5.3.
1.4.6. float
1.4.7. double
1.4.7.1. String to double
1.4.8. BigDecimal
1.4.8.1. Convert BigDecimal Object to double value
1.4.8.2. 去除末尾多余的0
1.4.8.3. 禁用科学计数法
1.4.8.4. 移动小数点位置
1.4.9. StringBuffer
1.4.10. enum
1.4.11. byte 类型
1.4.11.1. string2byte
1.4.11.2. byte[] to String
1.4.11.3. BigInteger2byte
1.4.11.4. int to byte array
1.4.11.5. byte array to int
1.4.11.6. byte2char
1.4.11.7. longToByte64
1.4.11.8. byte64ToLong
1.4.11.9. short2byte
1.4.11.10. byte8ToDouble
1.4.11.11. byte4ToFloat
1.4.11.12. 无符号 byte
1.4.11.13. byte to hex
1.4.11.14. byte[] to hex
1.4.11.15. 连接两个 byte[]
1.4.11.16. List<Byte> to byte[]
1.5. Collection
1.5.1. ArrayList
1.5.1.1. ArrayList to String
1.5.1.2. Array to List
1.5.1.3. List to Array
1.5.2. Set 转为 List
1.5.3. 静态 List
1.5.4. List.of()
1.5.5. Set.of()
1.6. HashMap
1.6.1. static map
1.6.2. foreach hashmap
1.6.3. LinkedHashMap
1.6.4. 静态初始化 Map
1.7. Network
1.7.1. URL
1.7.2. java.io.tmpdir
1.8. JDBC
1.8.1. 安装 JDBC 包
1.8.2. MySQL
1.8.3. Oracle
1.8.3.1. SID
1.8.3.2. SERVICE_NAME
1.8.3.3. TNS
1.8.3.4. Oracle RAC Cluster
1.8.3.5. Oracle JDBC Demo
1.8.4. FAQ
1.8.4.1. java.sql.SQLRecoverableException: IO Error: The Network Adapter could not establish the connection
1.8.4.2. Exception in thread "main" java.lang.ClassNotFoundException: oracle.jdbc.driver.OracleDriver
1.9. Util
1.9.1. Properties 处理 *.properties 文件
1.9.1.1. 打开 properties 文件
1.9.1.2. propertyNames()
1.9.1.3. keySet()
1.9.1.4. entrySet()
1.9.1.5. 方法中返回 Properties
1.9.1.6.
1.9.1.7. getResourceAsStream()
1.9.1.8. store
1.9.1.9. 实现国际化
1.9.2. Logging
1.9.2.1. console
1.9.3. BASE64
1.9.4. Locale 国际化
1.9.5. ResourceBundle
1.9.6. Scanner
1.9.7. UUID
1.9.8. Arrays.equals 判断两个数组是否相等
1.10. IO
1.10.1. 取出文件名中的扩展名
1.10.1.1. getAbsolutePath() 获取绝对路径
1.10.1.2. 创建目录 mkdir()
1.10.2. 临时文件
1.10.3. FileWriter 文本写入文件
1.10.4. BufferedWriter
1.10.5. InputStreamReader
1.10.6. 获得 Resource 下文件路径
1.10.7. PrintWriter
1.10.8. OutputStreamWriter
1.10.9. FileOutputStream
1.10.10. FileInputStream
1.10.11. Scanner
1.10.12. 二进制文件
1.10.12.1. 理解二进制文件
1.10.12.2. byte 类型
1.10.12.3. boolean 布尔型
1.10.12.4. Long 型
1.10.12.5. chat 类型
1.10.12.6. UTF 字符串
1.10.12.7. Short 类型
1.10.12.8. float 单精度浮点类型
1.10.12.9. double 数据类型
1.10.12.10. 二进制文件操作演示
1.11. Reflection 反射
1.11.1. 获得所有变量
1.11.2. 批量赋值
1.11.3. 方法操作
1.11.3.1. 获得所有方法
1.11.3.2. set/get 方法
1.11.3.3. static 方法调用
1.12. Thread 线程
1.12.1. 实现异步执行
1.12.2. 继承 Thread 类实现多线程
1.12.3. 实现 Runnable 接口
1.12.4. 线程同步
1.13. java 脚本引擎
1.13.1. Maven
1.13.2. Helloworld
1.13.3. 运行脚本文件
1.13.4. 变量传递
1.13.5. 全局变量与局部变量定义
1.13.6. 调用脚本中的函数或方法
1.13.7. 脚本编译
1.13.8. jjs - Invokes the Nashorn engine.
1.14. Crypto
1.14.1. MD5
1.14.2. AES
1.14.3. AES/CBC/PKCS5PADDING
1.14.4. DES
1.15. java.security
1.15.1. 列出 Java 支持的数字摘要算法
1.15.2. 计算文件的 MD5，SHA 等 HASH 值

1.1. JVM

1.1.1. 安装 Java 6

解压

chmod +x jdk-6u1-linux-i586.bin
./jdk-6u1-linux-i586.bin
输入"yes"回车

mv jdk1.6.0_01 /usr/local/
ln -s /usr/local/jdk1.6.0_01/ /usr/local/java

/etc/profile.d/java.sh

例 1.1. /etc/profile.d/java.sh

################################################
### Java environment by neo
################################################
export JAVA_HOME=/usr/local/java
export JRE_HOME=/usr/local/java/jre
export PATH=$PATH:/usr/local/java/bin:/usr/local/java/jre/bin
export CLASSPATH="./:/usr/local/java/lib:/usr/local/java/jre/lib:/usr/local/memcached/api/java"
export JAVA_OPTS="-Xms128m -Xmx1024m"

1.1.1.1. HeapDumpOnOutOfMemoryError

JAVA_OPTS = "$JAVA_OPTS -XX:+HeapDumpOnOutOfMemoryError"

如果针对Tomcat可以在catalina.sh加入

if [ "$1" = "debug" ] ; then
JAVA_OPTS = "$JAVA_OPTS -XX:+HeapDumpOnOutOfMemoryError"

1.1.2. java-1.8.0-openjdk

# yum install -y java-1.8.0-openjdk

1.1.3. java - Launches a Java application.

1.1.3.1. -verbose:class 显示载入jar文件

# java -verbose:class hello
[Opened /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.Object from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.io.Serializable from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.Comparable from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.CharSequence from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.String from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.reflect.AnnotatedElement from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.reflect.GenericDeclaration from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.reflect.Type from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.Class from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.Cloneable from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.ClassLoader from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.System from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.Throwable from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.Error from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.ThreadDeath from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.Exception from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.RuntimeException from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.SecurityManager from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.security.ProtectionDomain from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.security.AccessControlContext from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.security.SecureClassLoader from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.ReflectiveOperationException from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.ClassNotFoundException from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.LinkageError from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.NoClassDefFoundError from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.ClassCastException from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.ArrayStoreException from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.VirtualMachineError from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.OutOfMemoryError from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.StackOverflowError from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.IllegalMonitorStateException from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.ref.Reference from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.ref.SoftReference from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.ref.WeakReference from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.ref.FinalReference from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.ref.PhantomReference from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded sun.misc.Cleaner from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.ref.Finalizer from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.Runnable from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.Thread from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.Thread$UncaughtExceptionHandler from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.ThreadGroup from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.util.Map from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.util.Dictionary from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.util.Hashtable from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.util.Properties from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.reflect.AccessibleObject from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.reflect.Member from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.reflect.Field from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.reflect.Parameter from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.reflect.Executable from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.reflect.Method from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.reflect.Constructor from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded sun.reflect.MagicAccessorImpl from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded sun.reflect.MethodAccessor from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded sun.reflect.MethodAccessorImpl from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded sun.reflect.ConstructorAccessor from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded sun.reflect.ConstructorAccessorImpl from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded sun.reflect.DelegatingClassLoader from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded sun.reflect.ConstantPool from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded sun.reflect.FieldAccessor from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded sun.reflect.FieldAccessorImpl from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded sun.reflect.UnsafeFieldAccessorImpl from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded sun.reflect.UnsafeStaticFieldAccessorImpl from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.annotation.Annotation from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded sun.reflect.CallerSensitive from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.invoke.MethodHandle from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.invoke.DirectMethodHandle from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.invoke.MemberName from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.invoke.MethodHandleNatives from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.invoke.LambdaForm from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.invoke.MethodType from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.BootstrapMethodError from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.invoke.CallSite from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.invoke.ConstantCallSite from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.invoke.MutableCallSite from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.invoke.VolatileCallSite from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.Appendable from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.AbstractStringBuilder from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.StringBuffer from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.StringBuilder from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded sun.misc.Unsafe from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.AutoCloseable from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.io.Closeable from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.io.InputStream from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.io.ByteArrayInputStream from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.io.File from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.net.URLClassLoader from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.net.URL from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.util.jar.Manifest from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded sun.misc.Launcher from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded sun.misc.Launcher$AppClassLoader from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded sun.misc.Launcher$ExtClassLoader from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.security.CodeSource from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.StackTraceElement from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.nio.Buffer from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.Boolean from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.Character from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.Number from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.Float from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.Double from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.Byte from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.Short from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.Integer from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.Long from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.NullPointerException from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.ArithmeticException from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.io.ObjectStreamField from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.util.Comparator from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.String$CaseInsensitiveComparator from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.security.Guard from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.security.Permission from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.security.BasicPermission from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.RuntimePermission from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.security.AccessController from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.reflect.ReflectPermission from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.security.PrivilegedAction from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded sun.reflect.ReflectionFactory$GetReflectionFactoryAction from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.security.cert.Certificate from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.Iterable from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.util.Collection from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.util.List from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.util.RandomAccess from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.util.AbstractCollection from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.util.AbstractList from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.util.Vector from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.util.Stack from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded sun.reflect.ReflectionFactory from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.ref.Reference$Lock from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.ref.Reference$ReferenceHandler from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.ref.ReferenceQueue from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.ref.ReferenceQueue$Null from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.ref.ReferenceQueue$Lock from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.ref.Finalizer$FinalizerThread from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.util.Map$Entry from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.util.Hashtable$Entry from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded sun.misc.VM from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.Math from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.nio.charset.Charset from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.nio.charset.spi.CharsetProvider from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded sun.nio.cs.FastCharsetProvider from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded sun.nio.cs.StandardCharsets from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.util.AbstractMap from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded sun.util.PreHashedMap from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded sun.nio.cs.StandardCharsets$Aliases from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded sun.nio.cs.StandardCharsets$Classes from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded sun.nio.cs.StandardCharsets$Cache from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.ThreadLocal from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.util.concurrent.atomic.AtomicInteger from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.IncompatibleClassChangeError from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.NoSuchMethodError from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.util.ArrayList from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.util.Collections from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.util.Set from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.util.AbstractSet from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.util.Collections$EmptySet from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.util.Collections$EmptyList from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.util.Collections$EmptyMap from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.util.Collections$UnmodifiableCollection from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.util.Collections$UnmodifiableList from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.util.Collections$UnmodifiableRandomAccessList from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded sun.reflect.Reflection from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.util.HashMap from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.util.HashMap$Node from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.Class$3 from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.Class$ReflectionData from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.Class$Atomic from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded sun.reflect.generics.repository.AbstractRepository from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded sun.reflect.generics.repository.GenericDeclRepository from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded sun.reflect.generics.repository.ClassRepository from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.Class$AnnotationData from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded sun.reflect.annotation.AnnotationType from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.util.WeakHashMap from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.ClassValue$ClassValueMap from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.reflect.Modifier from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded sun.reflect.LangReflectAccess from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.reflect.ReflectAccess from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.util.Arrays from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded sun.nio.cs.HistoricallyNamedCharset from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded sun.nio.cs.Unicode from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded sun.nio.cs.UTF_8 from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.Class$1 from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded sun.reflect.ReflectionFactory$1 from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded sun.reflect.NativeConstructorAccessorImpl from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded sun.reflect.DelegatingConstructorAccessorImpl from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.StringCoding from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.ThreadLocal$ThreadLocalMap from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.ThreadLocal$ThreadLocalMap$Entry from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.StringCoding$StringDecoder from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded sun.nio.cs.ArrayDecoder from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.nio.charset.CharsetDecoder from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded sun.nio.cs.UTF_8$Decoder from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.nio.charset.CodingErrorAction from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.util.Hashtable$EntrySet from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.util.Collections$SynchronizedCollection from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.util.Collections$SynchronizedSet from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.util.Objects from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.util.Enumeration from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.util.Iterator from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.util.Hashtable$Enumerator from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.Runtime from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded sun.misc.Version from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.io.FileInputStream from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.io.FileDescriptor from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded sun.misc.JavaIOFileDescriptorAccess from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.io.FileDescriptor$1 from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded sun.misc.SharedSecrets from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.io.Flushable from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.io.OutputStream from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.io.FileOutputStream from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.io.FilterInputStream from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.io.BufferedInputStream from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.util.concurrent.atomic.AtomicReferenceFieldUpdater from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.util.concurrent.atomic.AtomicReferenceFieldUpdater$AtomicReferenceFieldUpdaterImpl from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.security.PrivilegedExceptionAction from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.util.concurrent.atomic.AtomicReferenceFieldUpdater$AtomicReferenceFieldUpdaterImpl$1 from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded sun.reflect.misc.ReflectUtil from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.io.FilterOutputStream from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.io.PrintStream from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.io.BufferedOutputStream from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.io.Writer from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.io.OutputStreamWriter from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded sun.nio.cs.StreamEncoder from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded sun.security.action.GetPropertyAction from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded sun.nio.cs.ArrayEncoder from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.nio.charset.CharsetEncoder from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded sun.nio.cs.UTF_8$Encoder from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.nio.ByteBuffer from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.nio.HeapByteBuffer from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.nio.Bits from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.nio.ByteOrder from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded sun.misc.JavaNioAccess from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.nio.Bits$1 from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.io.BufferedWriter from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.io.DefaultFileSystem from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.io.FileSystem from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.io.UnixFileSystem from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.io.ExpiringCache from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.util.LinkedHashMap from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.io.ExpiringCache$1 from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.Enum from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.io.File$PathStatus from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.nio.file.Watchable from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.nio.file.Path from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.StringCoding$StringEncoder from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.ClassLoader$3 from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.io.ExpiringCache$Entry from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.util.LinkedHashMap$Entry from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.ClassLoader$NativeLibrary from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.Terminator from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded sun.misc.SignalHandler from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.Terminator$1 from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded sun.misc.Signal from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded sun.misc.NativeSignalHandler from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.Integer$IntegerCache from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded sun.misc.OSEnvironment from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded sun.misc.JavaLangAccess from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.System$2 from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.IllegalArgumentException from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.Compiler from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.Compiler$1 from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.net.URLStreamHandlerFactory from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded sun.misc.Launcher$Factory from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded sun.security.util.Debug from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.ClassLoader$ParallelLoaders from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.util.WeakHashMap$Entry from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.util.Collections$SetFromMap from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.util.WeakHashMap$KeySet from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded sun.misc.JavaNetAccess from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.net.URLClassLoader$7 from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.util.StringTokenizer from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded sun.misc.Launcher$ExtClassLoader$1 from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded sun.misc.MetaIndex from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.Readable from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.io.Reader from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.io.BufferedReader from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.io.InputStreamReader from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.io.FileReader from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded sun.nio.cs.StreamDecoder from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.nio.CharBuffer from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.nio.HeapCharBuffer from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.nio.charset.CoderResult from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.nio.charset.CoderResult$Cache from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.nio.charset.CoderResult$1 from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.nio.charset.CoderResult$2 from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.reflect.Array from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.util.HashMap$TreeNode from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.io.FileInputStream$1 from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded sun.net.www.ParseUtil from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.util.BitSet from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.util.Locale from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded sun.util.locale.LocaleObjectCache from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.util.Locale$Cache from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.util.concurrent.ConcurrentMap from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.util.concurrent.ConcurrentHashMap from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.util.concurrent.locks.Lock from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.util.concurrent.locks.ReentrantLock from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.util.concurrent.ConcurrentHashMap$Segment from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.util.concurrent.ConcurrentHashMap$Node from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.util.concurrent.ConcurrentHashMap$CounterCell from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.util.concurrent.ConcurrentHashMap$CollectionView from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.util.concurrent.ConcurrentHashMap$KeySetView from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.util.concurrent.ConcurrentHashMap$ValuesView from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.util.concurrent.ConcurrentHashMap$EntrySetView from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded sun.util.locale.BaseLocale from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded sun.util.locale.BaseLocale$Cache from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded sun.util.locale.BaseLocale$Key from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded sun.util.locale.LocaleObjectCache$CacheEntry from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.util.Locale$LocaleKey from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded sun.util.locale.LocaleUtils from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.CharacterData from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.CharacterDataLatin1 from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.net.Parts from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.net.URLStreamHandler from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded sun.net.www.protocol.file.Handler from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded sun.misc.JavaSecurityAccess from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.security.ProtectionDomain$JavaSecurityAccessImpl from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded sun.misc.JavaSecurityProtectionDomainAccess from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.security.ProtectionDomain$2 from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.security.ProtectionDomain$Key from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.security.Principal from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.util.HashSet from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded sun.misc.URLClassPath from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded sun.net.www.protocol.jar.Handler from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded sun.misc.Launcher$AppClassLoader$1 from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.SystemClassLoaderAction from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.invoke.MethodHandleImpl from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.invoke.MethodHandleImpl$1 from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.util.function.Function from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.invoke.MethodHandleImpl$2 from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.invoke.MethodHandleImpl$3 from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.ClassValue from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.invoke.MethodHandleImpl$4 from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.ClassValue$Entry from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.ClassValue$Identity from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.ClassValue$Version from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.invoke.MemberName$Factory from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.invoke.MethodHandleStatics from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.invoke.MethodHandleStatics$1 from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded sun.misc.PostVMInitHook from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded sun.usagetracker.UsageTrackerClient from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.util.concurrent.atomic.AtomicBoolean from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded sun.usagetracker.UsageTrackerClient$1 from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded sun.usagetracker.UsageTrackerClient$4 from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded sun.usagetracker.UsageTrackerClient$3 from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.io.FileOutputStream$1 from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded sun.launcher.LauncherHelper from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.net.URLClassLoader$1 from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded sun.net.util.URLUtil from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded sun.misc.URLClassPath$3 from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded sun.misc.URLClassPath$Loader from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded sun.misc.URLClassPath$JarLoader from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.util.zip.ZipConstants from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.util.zip.ZipFile from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded sun.misc.JavaUtilZipFileAccess from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.util.zip.ZipFile$1 from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded sun.misc.URLClassPath$FileLoader from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded sun.misc.Resource from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded sun.misc.URLClassPath$FileLoader$1 from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded sun.nio.ByteBuffered from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded sun.misc.PerfCounter from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded sun.misc.Perf$GetPerfAction from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded sun.misc.Perf from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded sun.misc.PerfCounter$CoreCounters from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded sun.nio.ch.DirectBuffer from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.nio.MappedByteBuffer from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.nio.DirectByteBuffer from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.nio.LongBuffer from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.nio.DirectLongBufferU from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.security.PermissionCollection from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.security.Permissions from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.net.URLConnection from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded sun.net.www.URLConnection from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded sun.net.www.protocol.file.FileURLConnection from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded sun.net.www.MessageHeader from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.io.FilePermission from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.io.FilePermission$1 from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.io.FilePermissionCollection from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.security.AllPermission from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.security.UnresolvedPermission from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.security.BasicPermissionCollection from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded hello from file:/root/java/]
[Loaded sun.launcher.LauncherHelper$FXHelper from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.Class$MethodArray from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.Void from /srv/jdk1.8.0_60/jre/lib/rt.jar]
Hello
[Loaded java.lang.Shutdown from /srv/jdk1.8.0_60/jre/lib/rt.jar]
[Loaded java.lang.Shutdown$Lock from /srv/jdk1.8.0_60/jre/lib/rt.jar]

1.1.3.2. java.io.tmpdir

临时文件目录

java -Djava.io.tmpdir=/path/to/tmpdir

1.1.3.3. 显示版本号

$ java -version
java version "1.8.0_131"
Java(TM) SE Runtime Environment (build 1.8.0_131-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.131-b11, mixed mode)

1.1.3.4. 列出java模块

neo@MacBook-Pro ~ % java --list-modules
java.activation@10.0.1
java.base@10.0.1
java.compiler@10.0.1
java.corba@10.0.1
java.datatransfer@10.0.1
java.desktop@10.0.1
java.instrument@10.0.1
java.jnlp@10.0.1
java.logging@10.0.1
java.management@10.0.1
java.management.rmi@10.0.1
java.naming@10.0.1
java.prefs@10.0.1
java.rmi@10.0.1
java.scripting@10.0.1
java.se@10.0.1
java.se.ee@10.0.1
java.security.jgss@10.0.1
java.security.sasl@10.0.1
java.smartcardio@10.0.1
java.sql@10.0.1
java.sql.rowset@10.0.1
java.transaction@10.0.1
java.xml@10.0.1
java.xml.bind@10.0.1
java.xml.crypto@10.0.1
java.xml.ws@10.0.1
java.xml.ws.annotation@10.0.1
javafx.base@10.0.1
javafx.controls@10.0.1
javafx.deploy@10.0.1
javafx.fxml@10.0.1
javafx.graphics@10.0.1
javafx.media@10.0.1
javafx.swing@10.0.1
javafx.web@10.0.1
jdk.accessibility@10.0.1
jdk.aot@10.0.1
jdk.attach@10.0.1
jdk.charsets@10.0.1
jdk.compiler@10.0.1
jdk.crypto.cryptoki@10.0.1
jdk.crypto.ec@10.0.1
jdk.deploy@10.0.1
jdk.deploy.controlpanel@10.0.1
jdk.dynalink@10.0.1
jdk.editpad@10.0.1
jdk.hotspot.agent@10.0.1
jdk.httpserver@10.0.1
jdk.incubator.httpclient@10.0.1
jdk.internal.ed@10.0.1
jdk.internal.jvmstat@10.0.1
jdk.internal.le@10.0.1
jdk.internal.opt@10.0.1
jdk.internal.vm.ci@10.0.1
jdk.internal.vm.compiler@10.0.1
jdk.internal.vm.compiler.management@10.0.1
jdk.jartool@10.0.1
jdk.javadoc@10.0.1
jdk.javaws@10.0.1
jdk.jcmd@10.0.1
jdk.jconsole@10.0.1
jdk.jdeps@10.0.1
jdk.jdi@10.0.1
jdk.jdwp.agent@10.0.1
jdk.jfr@10.0.1
jdk.jlink@10.0.1
jdk.jshell@10.0.1
jdk.jsobject@10.0.1
jdk.jstatd@10.0.1
jdk.localedata@10.0.1
jdk.management@10.0.1
jdk.management.agent@10.0.1
jdk.management.cmm@10.0.1
jdk.management.jfr@10.0.1
jdk.management.resource@10.0.1
jdk.naming.dns@10.0.1
jdk.naming.rmi@10.0.1
jdk.net@10.0.1
jdk.pack@10.0.1
jdk.packager@10.0.1
jdk.packager.services@10.0.1
jdk.plugin@10.0.1
jdk.plugin.server@10.0.1
jdk.rmic@10.0.1
jdk.scripting.nashorn@10.0.1
jdk.scripting.nashorn.shell@10.0.1
jdk.sctp@10.0.1
jdk.security.auth@10.0.1
jdk.security.jgss@10.0.1
jdk.snmp@10.0.1
jdk.unsupported@10.0.1
jdk.xml.bind@10.0.1
jdk.xml.dom@10.0.1
jdk.xml.ws@10.0.1
jdk.zipfs@10.0.1
oracle.desktop@10.0.1
oracle.net@10.0.1

模块所在位置 /Library/Java/JavaVirtualMachines/jdk-10.0.1.jdk/Contents/Home/jmods

cd /Library/Java/JavaVirtualMachines/jdk-10.0.1.jdk/Contents/Home/jmods

neo@MacBook-Pro /Library/Java/JavaVirtualMachines/jdk-10.0.1.jdk/Contents/Home/jmods % ll -1
java.activation.jmod
java.base.jmod
java.compiler.jmod
java.corba.jmod
java.datatransfer.jmod
java.desktop.jmod
java.instrument.jmod
java.jnlp.jmod
java.logging.jmod
java.management.jmod
java.management.rmi.jmod
java.naming.jmod
java.prefs.jmod
java.rmi.jmod
java.scripting.jmod
java.se.ee.jmod
java.se.jmod
java.security.jgss.jmod
java.security.sasl.jmod
java.smartcardio.jmod
java.sql.jmod
java.sql.rowset.jmod
java.transaction.jmod
java.xml.bind.jmod
java.xml.crypto.jmod
java.xml.jmod
java.xml.ws.annotation.jmod
java.xml.ws.jmod
javafx.base.jmod
javafx.controls.jmod
javafx.deploy.jmod
javafx.fxml.jmod
javafx.graphics.jmod
javafx.media.jmod
javafx.swing.jmod
javafx.web.jmod
jdk.accessibility.jmod
jdk.aot.jmod
jdk.attach.jmod
jdk.charsets.jmod
jdk.compiler.jmod
jdk.crypto.cryptoki.jmod
jdk.crypto.ec.jmod
jdk.deploy.controlpanel.jmod
jdk.deploy.jmod
jdk.dynalink.jmod
jdk.editpad.jmod
jdk.hotspot.agent.jmod
jdk.httpserver.jmod
jdk.incubator.httpclient.jmod
jdk.internal.ed.jmod
jdk.internal.jvmstat.jmod
jdk.internal.le.jmod
jdk.internal.opt.jmod
jdk.internal.vm.ci.jmod
jdk.internal.vm.compiler.jmod
jdk.internal.vm.compiler.management.jmod
jdk.jartool.jmod
jdk.javadoc.jmod
jdk.javaws.jmod
jdk.jcmd.jmod
jdk.jconsole.jmod
jdk.jdeps.jmod
jdk.jdi.jmod
jdk.jdwp.agent.jmod
jdk.jfr.jmod
jdk.jlink.jmod
jdk.jshell.jmod
jdk.jsobject.jmod
jdk.jstatd.jmod
jdk.localedata.jmod
jdk.management.agent.jmod
jdk.management.cmm.jmod
jdk.management.jfr.jmod
jdk.management.jmod
jdk.management.resource.jmod
jdk.naming.dns.jmod
jdk.naming.rmi.jmod
jdk.net.jmod
jdk.pack.jmod
jdk.packager.jmod
jdk.packager.services.jmod
jdk.plugin.jmod
jdk.plugin.server.jmod
jdk.rmic.jmod
jdk.scripting.nashorn.jmod
jdk.scripting.nashorn.shell.jmod
jdk.sctp.jmod
jdk.security.auth.jmod
jdk.security.jgss.jmod
jdk.snmp.jmod
jdk.unsupported.jmod
jdk.xml.bind.jmod
jdk.xml.dom.jmod
jdk.xml.ws.jmod
jdk.zipfs.jmod
oracle.desktop.jmod
oracle.net.jmod

1.1.4. jar

查看包中的文件列表 jar -tf package.war/package.jar

$ /srv/java/bin/jar -tf mis.netkiller.cn-0.0.1.war |more
META-INF/
META-INF/MANIFEST.MF
WEB-INF/
WEB-INF/jsp/
WEB-INF/jsp/include/
WEB-INF/jsp/system/
WEB-INF/jsp/banner/

1.1.5. jdeps - Java class dependency analyzer.

包类依赖分析器

[net@netkiller lib]$ jdeps jersey-client-1.18.1.jar
jersey-client-1.18.1.jar -> not found
jersey-client-1.18.1.jar -> /usr/java/jdk1.8.0_73/jre/lib/rt.jar
   com.sun.jersey.api.client (jersey-client-1.18.1.jar)
      -> com.sun.jersey.api.client.async                    jersey-client-1.18.1.jar
      -> com.sun.jersey.api.client.config                   jersey-client-1.18.1.jar
      -> com.sun.jersey.api.client.filter                   jersey-client-1.18.1.jar
      -> com.sun.jersey.client.impl                         jersey-client-1.18.1.jar
      -> com.sun.jersey.client.impl.async                   jersey-client-1.18.1.jar
      -> com.sun.jersey.client.proxy                        jersey-client-1.18.1.jar
      -> com.sun.jersey.client.urlconnection                jersey-client-1.18.1.jar
      -> com.sun.jersey.core.header                         not found
      -> com.sun.jersey.core.provider                       not found
      -> com.sun.jersey.core.reflection                     not found
      -> com.sun.jersey.core.spi.component                  not found
      -> com.sun.jersey.core.spi.component.ioc              not found
      -> com.sun.jersey.core.spi.factory                    not found
      -> com.sun.jersey.core.util                           not found
      -> com.sun.jersey.spi                                 not found
      -> com.sun.jersey.spi.inject                          not found
      -> com.sun.jersey.spi.service                         not found
      -> java.io
      -> java.lang
      -> java.lang.annotation
      -> java.lang.reflect
      -> java.net
      -> java.util
      -> java.util.concurrent
      -> java.util.logging
      -> javax.ws.rs.core                                   not found
      -> javax.ws.rs.ext                                    not found
   com.sun.jersey.api.client.async (jersey-client-1.18.1.jar)
      -> com.sun.jersey.api.client                          jersey-client-1.18.1.jar
      -> java.lang
      -> java.util.concurrent
   com.sun.jersey.api.client.config (jersey-client-1.18.1.jar)
      -> com.sun.jersey.core.util                           not found
      -> java.lang
      -> java.util
   com.sun.jersey.api.client.filter (jersey-client-1.18.1.jar)
      -> com.sun.jersey.api.client                          jersey-client-1.18.1.jar
      -> com.sun.jersey.core.util                           not found
      -> java.io
      -> java.lang
      -> java.net
      -> java.nio.charset
      -> java.security
      -> java.util
      -> java.util.logging
      -> java.util.regex
      -> java.util.zip
      -> javax.ws.rs                                        not found
      -> javax.ws.rs.core                                   not found
   com.sun.jersey.client.impl (jersey-client-1.18.1.jar)
      -> com.sun.jersey.api.client                          jersey-client-1.18.1.jar
      -> com.sun.jersey.core.header                         not found
      -> java.io
      -> java.lang
      -> java.net
      -> java.util
      -> java.util.concurrent.atomic
      -> javax.ws.rs.core                                   not found
   com.sun.jersey.client.impl.async (jersey-client-1.18.1.jar)
      -> com.sun.jersey.api.client                          jersey-client-1.18.1.jar
      -> com.sun.jersey.api.client.async                    jersey-client-1.18.1.jar
      -> java.lang
      -> java.util.concurrent
   com.sun.jersey.client.proxy (jersey-client-1.18.1.jar)
      -> com.sun.jersey.api.client                          jersey-client-1.18.1.jar
      -> com.sun.jersey.api.client.async                    jersey-client-1.18.1.jar
      -> java.lang
      -> java.util.concurrent
   com.sun.jersey.client.urlconnection (jersey-client-1.18.1.jar)
      -> com.sun.jersey.api.client                          jersey-client-1.18.1.jar
      -> com.sun.jersey.core.header                         not found
      -> com.sun.jersey.spi                                 not found
      -> java.io
      -> java.lang
      -> java.lang.reflect
      -> java.net
      -> java.security
      -> java.util
      -> java.util.logging
      -> javax.net.ssl
      -> javax.ws.rs.core                                   not found
   com.sun.ws.rs.ext (jersey-client-1.18.1.jar)
      -> com.sun.jersey.core.spi.factory                    not found
      -> java.lang
      -> javax.ws.rs.core                                   not found


1.2. System

Java.version 运行时环境版本

java.vendor 运行时环境供应商

java.vendor.url 供应商的 URL

java.home 安装目录

java.vm.specification.version 虚拟机规范版本

java.vm.specification.vendor 虚拟机规范供应商

java.vm.specification.name 虚拟机规范名称

java.vm.version 虚拟机实现版本

java.vm.vendor 虚拟机实现供应商

java.vm.name 虚拟机实现名称

java.specification.version 运行时环境规范版本

java.specification.vendor Java 运行时环境规范供应商

java.specification.name Java 运行时环境规范名称

java.class.version Java 类格式版本号

java.class.path Java 类路径

java.library.path 加载库时搜索的路径列表

java.io.tmpdir 默认的临时文件路径

java.compiler 要使用的 JIT 编译器的名称

java.ext.dirs 一个或多个扩展目录的路径

os.name 操作系统的名称

os.arch 操作系统的架构

os.version 操作系统的版本

file.separator 文件分隔符（在 UNIX 系统中是“/”）

path.separator 路径分隔符（在 UNIX 系统中是“:”）

line.separator 行分隔符（在 UNIX 系统中是“/n”）

user.name 用户的账户名称

user.home 用户的主目录

user.dir 用户的当前工作目录

1.2.1. user.dir

public class Test {

    public static void main(String[] args) {
        System.out.println("Working Directory = " + System.getProperty("user.dir"));

        System.out.println(System.getProperty("user.home"));
        System.out.println(System.getProperty("java.version"));
        System.out.println(System.getProperty("os.name"));
        System.out.println(System.getProperty("java.vendor.url"));
    }
}

1.2.2. java.io.tmpdir

改变java.io.tmpdir的默认值

System.setProperty("java.io.tmpdir", "/vat/tmp");
System.out.println(System.getProperty("java.io.tmpdir"));

如果你希望从外部修改这个系统属性的话，你可以使用-D这个参数，例如 java -Djava.io.tmpdir=/path/to/tmpdir

System.out.println(System.getProperty("java.io.tmpdir"));

1.2.3. 自定义

    public static void main(String[] args) {
        try {
            Oracle oracle = new Oracle();
            if(System.getProperty("config") == null){
                System.exit(1);
            }
            oracle.openConfig(System.getProperty("config"));
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }

    }

上面程序编译打包后运行

neo@netkiller:~/project/Oracle$ java -jar -Dconfig=jdbc.properties target/oracle-0.0.1-SNAPSHOT.jar

1.2.4. System.in 标准输入(Stdin)

标准输入一般用于管道输入，例如：

cat test.txt | java cn.netkiller.ipo.test.StdinToStdout

下面的程序例子里从管道输入，并从标准输出打印。

package cn.netkiller.ipo.test;

import java.io.BufferedReader;
import java.io.InputStreamReader;

public class StdinToStdout {

    public StdinToStdout() {
        // TODO Auto-generated constructor stub
    }

    public static void main(String[] args) {

        String s = "";
        try {
            BufferedReader stdIn = new BufferedReader(new InputStreamReader(System.in));

            while ((s = stdIn.readLine()) != null) {
                System.out.println(s);
            }

        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}

默认 BufferedReader 缓冲区比较小，无法处理大于1000 行的输入，可以通过配置缓冲区来解决。

stdin = new BufferedReader(new InputStreamReader(System.in, "utf-8"), 1024 * 1024 * 1024); // 1G 缓冲区


1.3. exec 运行shell

exec(String[] cmdarray, String[] envp, File dir)
Executes the specified command and arguments in a separate process with the specified environment and working directory.

那个dir就是调用的程序的工作目录，这句其实还是很有用的。

Windows下调用程序
Process proc =Runtime.getRuntime().exec("file.exe");

Linux下调用程序就要改成下面的格式
Process proc =Runtime.getRuntime().exec("./file");

Windows下调用系统命令
String [] cmd={"cmd","/C","copy file1.txt file2.txt"};
Process proc =Runtime.getRuntime().exec(cmd);

Linux下调用系统命令就要改成下面的格式
String [] cmd={"/bin/sh","-c","ln -s file1 file2"};
Process proc =Runtime.getRuntime().exec(cmd);

Windows下调用系统命令并弹出命令行窗口
String [] cmd={"cmd","/C","start copy file1.txt file2.txt"};
Process proc =Runtime.getRuntime().exec(cmd);

Linux下调用系统命令并弹出终端窗口就要改成下面的格式
String [] cmd={"/bin/sh","-c","xterm -e ln -s file1 file2"};
Process proc =Runtime.getRuntime().exec(cmd);

还有要设置调用程序的工作目录就要
Process proc =Runtime.getRuntime().exec("ls",null, new File("/bin"));


1.4. 类型

数据类型的最大值和最小值。

基本类型：int 二进制位数：32
包装类：java.lang.Integer
最小值：Integer.MIN_VALUE= -2147483648 （-2的31次方）
最大值：Integer.MAX_VALUE= 2147483647  （2的31次方-1）

基本类型：short 二进制位数：16
包装类：java.lang.Short
最小值：Short.MIN_VALUE=-32768 （-2的15此方）
最大值：Short.MAX_VALUE=32767 （2的15次方-1）

基本类型：long 二进制位数：64
包装类：java.lang.Long
最小值：Long.MIN_VALUE=-9223372036854775808 （-2的63次方）
最大值：Long.MAX_VALUE=9223372036854775807 （2的63次方-1）

基本类型：float 二进制位数：32
包装类：java.lang.Float
最小值：Float.MIN_VALUE=1.4E-45 （2的-149次方）
最大值：Float.MAX_VALUE=3.4028235E38 （2的128次方-1）

基本类型：double 二进制位数：64
包装类：java.lang.Double
最小值：Double.MIN_VALUE=4.9E-324 （2的-1074次方）
最大值：Double.MAX_VALUE=1.7976931348623157E308 （2的1024次方-1）

1.4.1. Integer

十进制转成十六进制：

Integer.toHexString(int i)

十进制转成八进制

Integer.toOctalString(int i)

十进制转成二进制

Integer.toBinaryString(int i)

十六进制转成十进制

Integer.valueOf("FFFF",16).toString()

八进制转成十进制

Integer.valueOf("876",8).toString()

二进制转十进制

Integer.valueOf("0101",2).toString()

有什么方法可以直接将2,8,16进制直接转换为10进制的吗?

java.lang.Integer类

parseInt(String s, int radix)

使用第二个参数指定的基数，将字符串参数解析为有符号的整数。

examples from jdk:

parseInt("0", 10) returns 0

parseInt("473", 10) returns 473

parseInt("-0", 10) returns 0

parseInt("-FF", 16) returns -255

parseInt("1100110", 2) returns 102

parseInt("2147483647", 10) returns 2147483647

parseInt("-2147483648", 10) returns -2147483648

parseInt("2147483648", 10) throws a NumberFormatException

parseInt("99",throws a NumberFormatException

parseInt("Kona", 10) throws a NumberFormatException

parseInt("Kona", 27) returns 411787

例二

int i=100;

String binStr=Integer.toBinaryString(i);
String otcStr=Integer.toOctalString(i);
String hexStr=Integer.toHexString(i);
System.out.println(binStr);

例二

Integer factor = Integer.valueOf(args[0]);

String s;

s = String.format("%d", factor);
System.out.println(s);

s = String.format("%x", factor);
System.out.println(s);

s = String.format("%o", factor);
System.out.println(s);

其他方法：

Integer.toHexString(你的10进制数);

例如

String temp = Integer.toHexString(75);

输出temp就为 4b

1.4.1.1. 前面补零

public static String frontCompWithZore(int sourceDate,int formatLength) {

    String newString = String.format("%0"+formatLength+"d", sourceDate);
    return newString;
}

1.4.2. String

1.4.2.1. 随机字符串

    public String randomString(String chars, int length) {
        Random rand = new Random();
        StringBuilder buf = new StringBuilder();
        for (int i = 0; i < length; i++) {
            buf.append(chars.charAt(rand.nextInt(chars.length())));
        }
        return buf.toString();
    }

    /**
     * 获取4位随机验证码
     * @return
     */
    public static String getValidationCode(){
        return String.valueOf((new Random().nextInt(8999) + 1000));
    }

    /**
     * 获取6位随机验证码
     * @return
     */
    public static String getValidationCode(){
        return String.valueOf((new Random().nextInt(899999) + 100000));
    }

1.4.2.2. 字符串替换处理

public class Test {

    public Test() {
        // TODO Auto-generated constructor stub
    }

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        System.out.println("2010-09-11T20:00:30".replace("T", " "));
    }
}

                {"status":0,"message":"","bankcode":"ABOC;IBC;CCTB;ICBC"}
                转换后
                {\"status\":0,\"message\":\"\",\"bankcode\":\"ABOC;IBC;CCTB;ICBC\"}

package test;

public class str {

    public static void main(String[] args) {
        String jsonString = "{\"status\":0,\"message\":\"\",\"bankcode\":\"ABOC;IBC;CCTB;ICBC\"}";
        System.out.println(jsonString);
        System.out.println(jsonString.replace("\"", "\\\""));
    }

}
1.4.2.2.1. 正则表达式查找与替换

查找特定字符并替换为找到的内容

package cn.netkiller.type;

public class ragexTest {

    public ragexTest() {
        // TODO Auto-generated constructor stub
    }

    public static void main(String[] args) {
        // TODO Auto-generated method stub

        String str = "<html>Netkiller</html>";
        String regex = "<html>|</html>";
        //运行结果返回 Netkiller
        System.out.println(str.replaceAll(regex, ""));

        // 运行结果返回 Neo
        System.out.println("CN/NETKILLER/WWW/Neo_Chen".replaceAll(".*/(.+)_Chen", "$1"));
    }
}

1.4.2.3. substring

例如：
String str = "helloword!!!";

System.out.println(str.substring(1,4));

System.out.println(str.substring(3,5));

System.out.println(str.substring(0,4));

将得到结果为：

ell

lo

hell

1.4.2.4. string to timestamp

Timestamp转化为String:

                SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); //定义格式，不显示毫秒
                Timestamp now = new Timestamp(System.currentTimeMillis());
                //获取系统当前时间
                String str = df.format(now);

String转化为Timestamp:

                SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                String time = df.format(new Date());
                Timestamp ts = Timestamp.valueOf(time);

1.4.3. 类型转换

1.4.3.1. Long to String

    public class MainClass {

      public static void main(String[] arg) {
        long b = 12L;
        System.out.println(String.valueOf(b));

      }
    }

1.4.4. Date

java.util.Date, java.sql.Date, java.sql.Time, java.sql.Timestamp 区别

package cn.netkiller.java.date;

/**
 * Hello world!
 *
 */
public class App {
    public static void main(String[] args) {
        System.out.println("Hello World!");

        // Get standard date and time
        java.util.Date utilDate = new java.util.Date();
        long javaTime = utilDate.getTime();
        System.out.println("The Java Date is:" + utilDate.toString());

        // Get and display SQL DATE
        java.sql.Date sqlDate = new java.sql.Date(javaTime);
        System.out.println("The SQL DATE is: " + sqlDate.toString());

        // Get and display SQL TIME
        java.sql.Time sqlTime = new java.sql.Time(javaTime);
        System.out.println("The SQL TIME is: " + sqlTime.toString());

        // Get and display SQL TIMESTAMP
        java.sql.Timestamp sqlTimestamp = new java.sql.Timestamp(javaTime);
        System.out.println("The SQL TIMESTAMP is: " + sqlTimestamp.toString());
    }
}

            The Java Date is:Thu Aug 24 16:51:57 CST 2017
            The SQL DATE is: 2017-08-24
            The SQL TIME is: 16:51:57
            The SQL TIMESTAMP is: 2017-08-24 16:51:57.234

1.4.4.1. SimpleDateFormat

                public static void main(String[] args) {

                DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
                //get current date time with Date()
                Date date = new Date();
                System.out.println(dateFormat.format(date));

                //get current date time with Calendar()
                Calendar cal = Calendar.getInstance();
                System.out.println(dateFormat.format(cal.getTime()));

                }

1.4.4.2. Timestamp

                Timestamp timestamp = new Timestamp(System.currentTimeMillis());

                Date date = new Date();
                Timestamp timestamp = new Timestamp(date.getTime());

1.4.4.3. TimeZone

                package cn.netkiller.example;

                import java.sql.Timestamp;
                import java.text.SimpleDateFormat;
                import java.util.Calendar;
                import java.util.Date;
                import java.util.GregorianCalendar;
                import java.util.TimeZone;

                public class TimeZoneTest {

                public TimeZoneTest() {
                // TODO Auto-generated constructor stub
                }

                public static void main(String[] args) {
                // TODO Auto-generated method stub

                SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

                TimeZone timeZone = TimeZone.getTimeZone("Asia/Harbin");

                Date date = new Date();
                Timestamp timestamp = new Timestamp(date.getTime());

                System.out.println(timestamp);

                timestamp.setHours(timestamp.getHours()+8);
                System.out.println(timestamp);

                simpleDateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
                System.out.println(simpleDateFormat.format(date));

                simpleDateFormat.setTimeZone(TimeZone.getTimeZone("Asia/Harbin"));
                System.out.println(simpleDateFormat.format(date));

                Calendar calendar = new GregorianCalendar();
                calendar.setTime(date);
                calendar.setTimeZone(timeZone);
                System.out.println(simpleDateFormat.format(calendar.getTime()));
                }

                }

1.4.4.4. String to Date

package cn.netkiller.example;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class StringToDate {

    public StringToDate() {
        // TODO Auto-generated constructor stub
    }

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String dateString = "2008-8-8 8:8:8";

        try {

            Date date = formatter.parse(dateString);
            System.out.println(date);
            System.out.println(formatter.format(date));

        } catch (ParseException e) {
            e.printStackTrace();
        }
    }

}

1.4.4.5. 比较两个日期与时间

package cn.netkiller.example;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;

public class DateCompare {

    public DateCompare() {
        // TODO Auto-generated constructor stub
    }

    public void fun1() throws InterruptedException {
        Date d1 = new Date();
        Thread.sleep(5000);
        Date d2 = new Date();
        if (d1.before(d2)) {
            System.out.println(String.format("%s < %s", d1.toString(), d2.toString()));
        } else {
            System.out.println(String.format("%s > %s", d1.toString(), d2.toString()));
        }
        if (d2.after(d1)) {
            System.out.println(String.format("%s > %s", d2.toString(), d1.toString()));
        }

        System.out.println(String.format("%s : %s => %d", d2.toString(), d1.toString(), d1.compareTo(d2)));
        System.out.println(String.format("%s : %s => %d", d1.toString(), d2.toString(), d2.compareTo(d1)));
    }

    public void fun2() throws InterruptedException {

        DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

        Date date1 = new Date();
        Date date2 = new Date();
        String s1 = dateFormat.format(date1);
        String s2 = dateFormat.format(date2);
        System.out.println(String.format("%s : %s => %d", s1, s2, s1.compareTo(s2)));

        date1 = new Date();
        Thread.sleep(5000);
        date2 = new Date();
        s1 = dateFormat.format(date1);
        s2 = dateFormat.format(date2);
        System.out.println(String.format("%s : %s => %d", s1, s2, s1.compareTo(s2)));
        System.out.println(String.format("%s : %s => %d", s2, s1, s2.compareTo(s1)));
        System.out.println();
    }

    public void fun3() throws InterruptedException, ParseException {
        DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm");
        //Date time = formatter.parse("2016-09-27 16:29");
        Date time = formatter.parse("2016-08-09 09:15");
        Date startDate = formatter.parse("2016-08-09 09:15");
        Date endDate = formatter.parse("2016-09-27 16:30");

        if (time.before(startDate) || time.after(endDate)) {
            System.out.println("Skip");
        }
    }

    public static void main(String[] args) throws InterruptedException {
        // TODO Auto-generated method stub
        DateCompare dateCompare = new DateCompare();
        dateCompare.fun1();
        System.out.println();
        dateCompare.fun2();
        System.out.println();
        dateCompare.fun3();
    }

}

1.4.4.6. Calendar

        Calendar cal = Calendar.getInstance();
        int year = cal.get(Calendar.YEAR);
        int month = cal.get(Calendar.MONTH )+1;

        System.out.println(year + "年 " + month + "月");

1.4.4.7. getToday

    public Date getToday(String time) {
        final Calendar cal = Calendar.getInstance();
        DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd " + time);
        DateFormat fmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date date = null;
        try {
            date = fmt.parse(dateFormat.format(cal.getTime()));
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return date;
    }

    private Date addOneDay(Date date, int day) {
        Calendar cal = Calendar.getInstance();
        cal.setTime(date);
        cal.add(Calendar.DATE, day);
        return cal.getTime();
    }

1.4.4.8. Yesterday

package cn.netkiller.date;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;

public class Yesterday {

    public Yesterday() {
        // TODO Auto-generated constructor stub
    }

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Yesterday yesterday = new Yesterday();
        System.out.println(yesterday.yesterday());
        System.out.println(yesterday.getYesterday("00:00:00"));
        System.out.println(yesterday.getYesterday("23:59:59"));
    }
    private Date yesterday() {
        final Calendar cal = Calendar.getInstance();
        cal.add(Calendar.DATE, -1);
        return cal.getTime();
    }

    private Date getYesterday(String time) {
            DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd "+time);

            DateFormat fmt =new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            Date date = null;
            try {
                date = fmt.parse(dateFormat.format(yesterday()));
            } catch (ParseException e) {
                e.printStackTrace();
            }
            return date;
    }
}

1.4.4.9. ISO 8601

ISO 8601扩展格式 YYYY-MM-DDTHH:mm:ss.sssZ

1.4.4.10. LocalDateTime

    LocalDateTime localDateTime = LocalDateTime.of(2016, 1, 1, 13, 55);
    ZonedDateTime zonedDateTime = localDateTime.atZone(ZoneId.of("Asia/Shanghai"));
    Date date = Date.from(zonedDateTime.toInstant());

    Instant instant = Instant.ofEpochMilli(date.getTime());
    LocalDateTime ldt = LocalDateTime.ofInstant(instant, ZoneOffset.UTC);
    Instant instant = ldt.toInstant(ZoneOffset.UTC);
    Date date = Date.from(instant);

1.4.4.11. ZonedDateTime

Date.from(java.time.ZonedDateTime.now().toInstant());

1.4.5. Array

一定字符串数组

String[] str={"AAA","BBB","CCC"};
String str[]={"AAA","BBB","CCC"};

String to Array

package cn.netkiller.java;

public class StringToArray {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        String str="a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z";
        String[] array = null;
        array = str.split(",");
        for(int i=0; i<array.length; i++){
            System.out.println(array[i]);
        }
    }
}

1.4.5.1. for each

    public static void main(String[] args) {
        try {

            for (String arg : args) {
                System.out.println(arg);
            }
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
    }

1.4.5.2. Array to String

package cn.netkiller.java;

import java.util.Arrays;

public class ArrayToString {

    public static void main(String[] args) {
        String[] array = {"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"};
        System.out.println(Arrays.toString(array));
        System.out.println(Arrays.toString(array).replaceAll(", |\\[|\\]", ""));
    }

}

String[][] deepArray = new String[][] {{"John", "Mary"}, {"Alice", "Bob"}};
System.out.println(Arrays.toString(deepArray));
//output: [[Ljava.lang.String;@106d69c, [Ljava.lang.String;@52e922]
System.out.println(Arrays.deepToString(deepArray));

Output:
[[John, Mary], [Alice, Bob]]

1.4.5.3.

String[] array = {"neo", "chen"};
String string = String.join(",", array)
// 输出结果 "neo,chen"

1.4.6. float

float 不能直接做减法运算

            float a = 77.22f;
            float b = 77.2f;

            System.out.println((float)a-b);
            System.out.println((float)a+b);

            输出结果为：
            0.020004272
            154.42

package cn.netkiller.example;

import java.math.BigDecimal;

public class Math {

    public Math() {
        // TODO Auto-generated constructor stub
    }

    public static void main(String[] args) {

        float a = 77.22f;
        float b = 77.2f;

        System.out.println((float) a + b);
        System.out.println((float) a - b);
        System.out.println((float) a * b);
        System.out.println((float) a / b);

        System.out.println("-------------");

        System.out.println(add(a, b));
        System.out.println(sub(a, b));
        System.out.println(mul(a, b));
        System.out.println(div(a, b));

    }

    public static float add(float v1, float v2) {
        BigDecimal b1 = new BigDecimal(Float.toString(v1));
        BigDecimal b2 = new BigDecimal(Float.toString(v2));
        return b1.add(b2).floatValue();
    }

    public static float sub(float v1, float v2) {
        BigDecimal b1 = new BigDecimal(Float.toString(v1));
        BigDecimal b2 = new BigDecimal(Float.toString(v2));
        return b1.subtract(b2).floatValue();
    }

    public static float mul(float v1, float v2) {
        BigDecimal b1 = new BigDecimal(Float.toString(v1));
        BigDecimal b2 = new BigDecimal(Float.toString(v2));
        return b1.multiply(b2).floatValue();
    }

    public static float div(float v1, float v2) {
        return div(v1, v2, 5);
    }

    public static float div(float v1, float v2, int scale) {
        if (scale < 0) {
            throw new IllegalArgumentException("The scale must be a positive integer or zero");
        }
        BigDecimal b1 = new BigDecimal(Float.toString(v1));
        BigDecimal b2 = new BigDecimal(Float.toString(v2));
        return b1.divide(b2, scale, BigDecimal.ROUND_HALF_UP).floatValue();
    }

    public static float round(float v, int scale) {
        if (scale < 0) {
            throw new IllegalArgumentException("The scale must be a positive integer or zero");
        }
        BigDecimal b = new BigDecimal(Float.toString(v));
        BigDecimal one = new BigDecimal("1");
        return b.divide(one, scale, BigDecimal.ROUND_HALF_UP).floatValue();
    }
}

1.4.7. double

package cn.netkiller.example;

import java.math.BigDecimal;

public class Math {

    public Math() {
        // TODO Auto-generated constructor stub
    }

    public static double add(double v1, double v2) {
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        return b1.add(b2).doubleValue();
    }

    public static double sub(double v1, double v2) {
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        return b1.subtract(b2).doubleValue();
    }

    public static double mul(double v1, double v2) {
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        return b1.multiply(b2).doubleValue();
    }

    public static double div(double v1, double v2) {
        return div(v1, v2, 8);
    }

    public static double div(double v1, double v2, int scale) {
        if (scale < 0) {
            throw new IllegalArgumentException("The scale must be a positive integer or zero");
        }
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        return b1.divide(b2, scale, BigDecimal.ROUND_HALF_UP).doubleValue();
    }

    public static double round(double v, int scale) {
        if (scale < 0) {
            throw new IllegalArgumentException("The scale must be a positive integer or zero");
        }
        BigDecimal b = new BigDecimal(Double.toString(v));
        BigDecimal one = new BigDecimal("1");
        return b.divide(one, scale, BigDecimal.ROUND_HALF_UP).doubleValue();
    }
}

1.4.7.1. String to double

double amount = Double.parseDouble(value);

1.4.8. BigDecimal

package cn.netkiller.example;

import java.math.BigDecimal;

public class BigDecimalTest {

    public BigDecimalTest() {
        // TODO Auto-generated constructor stub
    }

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        BigDecimal first = new BigDecimal("1.0");
        BigDecimal second = new BigDecimal("1.77");
        System.out.println(String.format("%s, %s", first, second));

        if (first.equals(second))

            System.out.println("equals: true");

        else

            System.out.println("equals: false");

        if (first.compareTo(second) == 0)

            System.out.println("compareTo: true");
        else
            System.out.println("compareTo: false");

        BigDecimal zero = new BigDecimal("0");
        BigDecimal one = new BigDecimal("1");
        BigDecimal minus = new BigDecimal("-1");

        if (zero.compareTo(one) < 0)

            System.out.println("比較演算子[ <  ]: true");

        if (one.compareTo(one) == 0)

            System.out.println("比較演算子[ == ]: true");

        if (zero.compareTo(minus) > 0)

            System.out.println("比較演算子[ >  ]: true");

        if (zero.compareTo(minus) >= 0)

            System.out.println("比較演算子[ >= ]: true");

        if (zero.compareTo(minus) != 0)

            System.out.println("比較演算子[ != ]: true");

        if (zero.compareTo(one) <= 0)
            System.out.println("比較演算子[ <= ]: true");

    }

}

1.4.8.1. Convert BigDecimal Object to double value

BigDecimal.doubleValue()

1.4.8.2. 去除末尾多余的0

System.out.println( new BigDecimal("100.000").stripTrailingZeros().toString());

1.4.8.3. 禁用科学计数法

有时会输出 1E+2，如果你不希望这种科学计数法输出可以使用 toPlainString() 替代 toString()
System.out.println( new BigDecimal("100.000").stripTrailingZeros().toPlainString());

1.4.8.4. 移动小数点位置

package cn.netkiller.example.test;

import java.math.BigDecimal;
import java.math.BigInteger;

public class Test {

    public Test() {
        // TODO Auto-generated constructor stub
    }

    public static void main(String[] args) {
        // TODO Auto-generated method stub

        int decimal = 4;

        BigInteger amount = BigInteger.valueOf(10000000000L);
        BigDecimal balance = new BigDecimal(amount);
        BigDecimal point = new BigDecimal(0.1 / Math.pow(10, decimal));
        balance = balance.multiply(point);
        System.out.println(balance);
    }

}

发现输出有问题 100000.000000000008180305391403130954586231382563710212707519531250000000000

换种方法

package cn.netkiller.example.test;

import java.math.BigDecimal;
import java.math.BigInteger;

public class Test {

    public Test() {
        // TODO Auto-generated constructor stub
    }

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        // String i = Integer.valueOf("0x57c457",16).toString();
        // System.out.println(i);

        int decimal = 6;

        BigInteger amount = BigInteger.valueOf(10000000000L);

        System.out.println(amount);

        String tmp = amount.toString();

        String number = new StringBuffer(tmp).insert(tmp.length() - decimal, ".").toString();
        BigDecimal balance = new BigDecimal(number);

        System.out.println(balance);
    }

}

最佳方案

        int decimal = 6;

        System.out.println(BigDecimal.TEN.pow(decimal));
        BigDecimal balance1 = new BigDecimal("1234");
        BigDecimal value = balance1.divide(BigDecimal.TEN.pow(decimal));
        System.out.println(value);

        BigDecimal balance2 = new BigDecimal("12.107");
        BigDecimal value2 = balance2.multiply(BigDecimal.TEN.pow(decimal)).setScale(0, RoundingMode.DOWN);
        System.out.println(value2);

1.4.9. StringBuffer

String str = Integer.toString(j);
str = new StringBuilder(str).insert(str.length()-2, ".").toString();

Or if you need synchronization use the StringBuffer with similar usage :

String str = Integer.toString(j);
str = new StringBuffer(str).insert(str.length()-2, ".").toString();

1.4.10. enum

class EnumExample1{

    public enum Season { WINTER, SPRING, SUMMER, FALL }

    public static void main(String[] args) {
        for (Season s : Season.values())
            System.out.println(s);
    }
}

package cn.netkiller.api.util;

public enum HttpMethod {
    GET("GET"), POST("POST"), PUT("PUT"), PATCH("PATCH"), DELETE("DELETE");

    private String value;

    private HttpMethod(String value) {
        this.value = value;
    }

    public String toString() {
        return value;
    }
}

1.4.11. byte 类型

1.4.11.1. string2byte

    byte[] bytes = "Helloworld!!! - http://www.netkiller.cn".getBytes();

1.4.11.2. byte[] to String

    byte[] bytes = "Helloworld!!! - http://www.netkiller.cn".getBytes();
    String str = new String(bytes, StandardCharsets.UTF_8);
    System.out.println(str);

1.4.11.3. BigInteger2byte

BigInteger b= new BigInteger('300');
byte bytes= b.byteValue();

1.4.11.4. int to byte array

int a= 120;
byte b= (byte)a;

private byte[] bigIntToByteArray( final int i ) {
    BigInteger bigInt = BigInteger.valueOf(i);
    return bigInt.toByteArray();
}

private byte[] intToByteArray ( final int i ) throws IOException {
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    DataOutputStream dos = new DataOutputStream(bos);
    dos.writeInt(i);
    dos.flush();
    return bos.toByteArray();
}

private byte[] intToBytes( final int i ) {
    ByteBuffer bb = ByteBuffer.allocate(4);
    bb.putInt(i);
    return bb.array();
}

位移操作

private static byte[] intToBytes(final int a) {
    return new byte[] {
        (byte)((data >> 24) & 0xff),
        (byte)((data >> 16) & 0xff),
        (byte)((data >> 8) & 0xff),
        (byte)((data >> 0) & 0xff),
    };
}

1.4.11.5. byte array to int

private int convertByteArrayToInt(byte[] intBytes){
    ByteBuffer byteBuffer = ByteBuffer.wrap(intBytes);
    return byteBuffer.getInt();
}

private int convertByteArrayToInt(byte[] data) {
    if (data == null || data.length != 4) return 0x0;
    // ----------
    return (int)( // NOTE: type cast not necessary for int
            (0xff & data[0]) << 24  |
            (0xff & data[1]) << 16  |
            (0xff & data[2]) << 8   |
            (0xff & data[3]) << 0
            );
}

    public static byte[] intToByte32(int num) {
        byte[] arr = new byte[32];
        for (int i = 31; i >= 0; i--) {
            // &1 也可以改为num&0x01,表示取最地位数字.
            arr[i] = (byte) (num & 1);
            // 右移一位.
            num >>= 1;
        }
        return arr;
    }

    public static int byte32ToInt(byte[] arr) {
        if (arr == null || arr.length != 32) {
            throw new IllegalArgumentException("byte数组必须不为空,并且长度是32!");
        }
        int sum = 0;
        for (int i = 0; i < 32; ++i) {
            sum |= (arr[i] << (31 - i));
        }
        return sum;
    }

int array to byte array

private byte[] convertIntArrayToByteArray(int[] data) {
        if (data == null) return null;
        // ----------
        byte[] byts = new byte[data.length * 4];
        for (int i = 0; i < data.length; i++)
            System.arraycopy(convertIntToByteArray(data[i]), 0, byts, i * 4, 4);
        return byts;
    }

byte array to int array

public int[] convertByteArrayToIntArray(byte[] data) {
        if (data == null || data.length % 4 != 0) return null;
        // ----------
        int[] ints = new int[data.length / 4];
        for (int i = 0; i < ints.length; i++)
            ints[i] = ( convertByteArrayToInt(new byte[] {
                    data[(i*4)],
                    data[(i*4)+1],
                    data[(i*4)+2],
                    data[(i*4)+3],
            } ));
        return ints;
    }

1.4.11.6. byte2char

    byte b1 = 65;
    // char ch = b1;

    char ch = (char) b1;

    System.out.println("byte value: " + b1);             // prints 65
    System.out.println("Converted char value: " + ch);   // prints A (ASCII is 65 for A)

    public static char byte2ToChar(byte[] arr) {
        if (arr == null || arr.length != 2) {
            throw new IllegalArgumentException("byte数组必须不为空,并且是2位!");
        }
        return (char) (((char) (arr[0] << 8)) | ((char) arr[1]));
    }

    public static byte[] charToByte2(char c) {
        byte[] arr = new byte[2];
        arr[0] = (byte) (c >> 8);
        arr[1] = (byte) (c & 0xff);
        return arr;
    }

1.4.11.7. longToByte64

    public static byte[] longToByte64(long sum) {
        byte[] arr = new byte[64];
        for (int i = 63; i >= 0; i--) {
            arr[i] = (byte) (sum & 1);
            sum >>= 1;
        }
        return arr;
    }

1.4.11.8. byte64ToLong

    public static long byte8ToLong(byte[] arr) {
        if (arr == null || arr.length != 8) {
            throw new IllegalArgumentException("byte数组必须不为空,并且是8位!");
        }
        return (long) (((long) (arr[0] & 0xff) << 56) | ((long) (arr[1] & 0xff) << 48) | ((long) (arr[2] & 0xff) << 40)
                        | ((long) (arr[3] & 0xff) << 32) | ((long) (arr[4] & 0xff) << 24)
                        | ((long) (arr[5] & 0xff) << 16) | ((long) (arr[6] & 0xff) << 8) | ((long) (arr[7] & 0xff)));
    }

    public static byte[] longToByte8(long sum) {
        byte[] arr = new byte[8];
        arr[0] = (byte) (sum >> 56);
        arr[1] = (byte) (sum >> 48);
        arr[2] = (byte) (sum >> 40);
        arr[3] = (byte) (sum >> 32);
        arr[4] = (byte) (sum >> 24);
        arr[5] = (byte) (sum >> 16);
        arr[6] = (byte) (sum >> 8);
        arr[7] = (byte) (sum & 0xff);
        return arr;
    }

    public static long byte64ToLong(byte[] arr) {
        if (arr == null || arr.length != 64) {
            throw new IllegalArgumentException("byte数组必须不为空,并且长度是64!");
        }
        long sum = 0L;
        for (int i = 0; i < 64; ++i) {
            sum |= ((long) arr[i] << (63 - i));
        }
        return sum;
    }

1.4.11.9. short2byte

    public static byte[] shortToByte16(short s) {
        byte[] arr = new byte[16];
        for (int i = 15; i >= 0; i--) {
            arr[i] = (byte) (s & 1);
            s >>= 1;
        }
        return arr;
    }

    public static short byte16ToShort(byte[] arr) {
        if (arr == null || arr.length != 16) {
            throw new IllegalArgumentException("byte数组必须不为空,并且长度为16!");
        }
        short sum = 0;
        for (int i = 0; i < 16; ++i) {
            sum |= (arr[i] << (15 - i));
        }
        return sum;
    }

    public static short byte2ToShort(byte[] arr) {
        if (arr != null && arr.length != 2) {
            throw new IllegalArgumentException("byte数组必须不为空,并且是2位!");
        }
        return (short) (((short) arr[0] << 8) | ((short) arr[1] & 0xff));
    }

    public static byte[] shortToByte2(Short s) {
        byte[] arr = new byte[2];
        arr[0] = (byte) (s >> 8);
        arr[1] = (byte) (s & 0xff);
        return arr;
    }

1.4.11.10. byte8ToDouble

    public static double byte8ToDouble(byte[] arr) {
        if (arr == null || arr.length != 8) {
            throw new IllegalArgumentException("byte数组必须不为空,并且是8位!");
        }
        long l = byte8ToLong(arr);
        return Double.longBitsToDouble(l);
    }

    public static byte[] doubleToByte8(double i) {
        long j = Double.doubleToLongBits(i);
        return longToByte8(j);
    }

1.4.11.11. byte4ToFloat

    public static float byte4ToFloat(byte[] arr) {
        if (arr == null || arr.length != 4) {
            throw new IllegalArgumentException("byte数组必须不为空,并且是4位!");
        }
        int i = byte4ToInt(arr);
        return Float.intBitsToFloat(i);
    }

    public static byte[] floatToByte4(float f) {
        int i = Float.floatToIntBits(f);
        return intToByte4(i);
    }

1.4.11.12. 无符号 byte

byte b= -120;
int a= bytes & 0xff;

byte a = (byte)234;
System.out.println(a);

上面的代码，结果是-22，因为java中byte是有符号的，byte范围是-128~127。

如果想输出234，该怎么做呢，首先想到的是将a 赋给大一点的类型，如下：

byte a = (byte)234;
int i = a&0xff;
System.out.println(i);

原因是 0xff是int，占4个字节，a是byte，占1个字节，进行&操作的细节如下：

   00000000 00000000 00000000 11101010    (a)
&
   00000000 00000000 00000000 11111111    (0xff)
---------------------------------------------------------------------
=  00000000 00000000 00000000 11101010

结果是int，但是符号位是0，说明是正数，最后就是正整数234.

1.4.11.13. byte to hex

byte bv = 10;
String hexString = Integer.toHexString(bv);

1.4.11.14. byte[] to hex

byte bytes[] = {(byte)0, (byte)0, (byte)134, (byte)0, (byte)61};
System.out.println(javax.xml.bind.DatatypeConverter.printHexBinary(bytes));

public static String byteArrayToHex(byte[] bytes) {
   StringBuilder sb = new StringBuilder(a.length * 2);
   for(byte b: bytes)
      sb.append(String.format("%02x", b));
   return sb.toString();
}

BigInteger n = new BigInteger(byteArray);
String hexa = n.toString(16));

1.4.11.15. 连接两个 byte[]

byte[] one = { 1, 2, 3 };
byte[] two = { 6, 8, 9 };
int length = one.Length + two.Length;
byte[] sum = new byte[length];
one.CopyTo(sum,0);
two.CopyTo(sum,one.Length);

byte[] one = { 1, 2, 3 };
byte[] two = { 6, 8, 9 };
List<byte> list1 = new List<byte>(one);
List<byte> list2 = new List<byte>(two);
list1.AddRange(list2);
byte[] sum2 = list1.ToArray();

1.4.11.16. List<Byte> to byte[]

        List<Byte> byteList = new ArrayList<Byte>();
        byteList.add((byte) 1);
        byteList.add((byte) 2);
        byteList.add((byte) 3);
        byte[] byteArray = Bytes.toArray(byteList);
        System.out.println(Arrays.toString(byteArray));


1.5. Collection

Collection
 ├List
 │├LinkedList
 │├ArrayList
 │└Vector
 │　└Stack
 └Set

Collection接口
    Collection是最基本的集合接口，一个Collection代表一组Object，即Collection的元素（Elements）。一些 Collection允许相同的元素而另一些不行。一些能排序而另一些不行。Java SDK不提供直接继承自Collection的类，Java SDK提供的类都是继承自Collection的“子接口”如List和Set。
    所有实现Collection接口的类都必须提供两个标准的构造函数：无参数的构造函数用于创建一个空的Collection，有一个 Collection参数的构造函数用于创建一个新的Collection，这个新的Collection与传入的Collection有相同的元素。后 一个构造函数允许用户复制一个Collection。
    如何遍历Collection中的每一个元素？不论Collection的实际类型如何，它都支持一个iterator()的方法，该方法返回一个迭代子，使用该迭代子即可逐一访问Collection中每一个元素。典型的用法如下：
        Iterator it = collection.iterator(); // 获得一个迭代子
        while(it.hasNext()) {
            Object obj = it.next(); // 得到下一个元素
        }
    由Collection接口派生的两个接口是List和Set。
List接口
    List是有序的Collection，使用此接口能够精确的控制每个元素插入的位置。用户能够使用索引（元素在List中的位置，类似于数组下标）来访问List中的元素，这类似于Java的数组。
和下面要提到的Set不同，List允许有相同的元素。
    除了具有Collection接口必备的iterator()方法外，List还提供一个listIterator()方法，返回一个 ListIterator接口，和标准的Iterator接口相比，ListIterator多了一些add()之类的方法，允许添加，删除，设定元素， 还能向前或向后遍历。
    实现List接口的常用类有LinkedList，ArrayList，Vector和Stack。
LinkedList类
    LinkedList实现了List接口，允许null元素。此外LinkedList提供额外的get，remove，insert方法在 LinkedList的首部或尾部。这些操作使LinkedList可被用作堆栈（stack），队列（queue）或双向队列（deque）。
    注意LinkedList没有同步方法。如果多个线程同时访问一个List，则必须自己实现访问同步。一种解决方法是在创建List时构造一个同步的List：
        List list = Collections.synchronizedList(new LinkedList(...));
ArrayList类
    ArrayList实现了可变大小的数组。它允许所有元素，包括null。ArrayList没有同步。
size，isEmpty，get，set方法运行时间为常数。但是add方法开销为分摊的常数，添加n个元素需要O(n)的时间。其他的方法运行时间为线性。
    每个ArrayList实例都有一个容量（Capacity），即用于存储元素的数组的大小。这个容量可随着不断添加新元素而自动增加，但是增长算法 并没有定义。当需要插入大量元素时，在插入前可以调用ensureCapacity方法来增加ArrayList的容量以提高插入效率。
    和LinkedList一样，ArrayList也是非同步的（unsynchronized）。
Vector类
    Vector非常类似ArrayList，但是Vector是同步的。由Vector创建的Iterator，虽然和 ArrayList创建的Iterator是同一接口，但是，因为Vector是同步的，当一个Iterator被创建而且正在被使用，另一个线程改变了 Vector的状态（例如，添加或删除了一些元素），这时调用Iterator的方法时将抛出 ConcurrentModificationException，因此必须捕获该异常。
Stack 类
    Stack继承自Vector，实现一个后进先出的堆栈。Stack提供5个额外的方法使得Vector得以被当作堆栈使用。基本的push和pop 方法，还有peek方法得到栈顶的元素，empty方法测试堆栈是否为空，search方法检测一个元素在堆栈中的位置。Stack刚创建后是空栈。
Set接口
    Set是一种不包含重复的元素的Collection，即任意的两个元素e1和e2都有e1.equals(e2)=false，Set最多有一个null元素。
    很明显，Set的构造函数有一个约束条件，传入的Collection参数不能包含重复的元素。
    请注意：必须小心操作可变对象（Mutable Object）。如果一个Set中的可变元素改变了自身状态导致Object.equals(Object)=true将导致一些问题。
Map接口
    请注意，Map没有继承Collection接口，Map提供key到value的映射。一个Map中不能包含相同的key，每个key只能映射一个 value。Map接口提供3种集合的视图，Map的内容可以被当作一组key集合，一组value集合，或者一组key-value映射。

package cn.netkiller.example;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.TreeSet;

public class Test {

    public Test() {
        // TODO Auto-generated constructor stub
    }

    public static void main(String[] args) {
        // TODO Auto-generated method stub

        // A A B E F G C D
        String[] array = { "A", "A", "B", "E", "F", "G", "C", "D" };
        Collection<String> list = new ArrayList<String>(Arrays.asList(array));
        for (String str : list) {
            System.out.print(str + " ");
        }
        System.out.println();

        // A A B E F G C D
        Collection<String> linkedList = new LinkedList<String>(Arrays.asList(array));
        for (String str : linkedList) {
            System.out.print(str + " ");
        }
        System.out.println();

        // 无重复，无序 D E F G A B C
        Collection<String> hashSet = new HashSet<String>(Arrays.asList(array));
        for (String str : hashSet) {
            System.out.print(str + " ");
        }
        System.out.println();

        // 无重复 A B C D E F G
        Collection<String> treeSet = new TreeSet<String>(Arrays.asList(array));
        for (String str : treeSet) {
            System.out.print(str + " ");
        }
        System.out.println();

        // 无重复 A B E F G C D
        Collection<String> linkedHashSet = new LinkedHashSet<String>(Arrays.asList(array));
        for (String str : linkedHashSet) {
            System.out.print(str + " ");

        }

    }

}

输出结果

        A A B E F G C D
        A A B E F G C D
        A B C D E F G
        A B C D E F G
        A B E F G C D

1.5.1. ArrayList

判断元素是否存在

import java.util.ArrayList;

public class arraylist {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        ArrayList<String> whitelist = new ArrayList<String>();
        whitelist.add("Neo");
        whitelist.add("Jam");
        whitelist.add("Sam");

        if (whitelist.contains("Neo")) {
            System.out.println("Found!");
        }else{
            System.out.println("Not Found!");
        }
    }

}

package cn.netkiller.type;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class ArrayListExample {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        List<String> list = new ArrayList<String>();
        list.add("Jack");
        list.add("Jet");
        list.add("Jack");
        list.add("Mike");
        list.add("Kitty");
        list.add("Tom");

        //while 循环
        Iterator<String> it = list.iterator();
        while (it.hasNext()) {
            System.out.println(it.next());
        }

        for (Iterator<String> it1 = list.iterator(); it1.hasNext();) {
            System.out.println(it1.next());
        }

        // for 循环
        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
        }

        // for 循环加强版
        for (String i : list) {
            System.out.println(i);
        }

    }

}

ArrayList 转为 Array

        String[] array = {"/bin/sh","-c"};
        List<String> list = new ArrayList<String>(Arrays.asList(array));
        list.add("command");
        list.add("param");

        String[] command = (String[]) list.toArray(new String[0]);
        System.out.println(Arrays.toString(command));

1.5.1.1. ArrayList to String

        List<String> list = new ArrayList<String>();
        list.add("command");
        list.add("param");

        String listString = String.join(", ", list);

        System.out.println(listString);

1.5.1.2. Array to List

                Arrays.asList(array)

1.5.1.3. List to Array

        List<String> list = new ArrayList<String>();
        list.add("str1");
        list.add("str2");

        String[] array = (String[]) list.toArray();
        System.out.println(array);

1.5.2. Set 转为 List

        // 将Map Key 转化为List
        List<String> mapKeyList = new ArrayList<String>(map.keySet());
        System.out.println("mapKeyList:"+mapKeyList);

        // 将Map Key 转化为List
        List<String> mapValuesList = new ArrayList<String>(map.values());
        System.out.println("mapValuesList:"+mapValuesList);

Set<Type> set = new Set<>();
Set<Type> set = new HashSet<>();

1.5.3. 静态 List

    public static List<String> list = new ArrayList<String>();
    static {
        list.add("录入");
        list.add("变更");
        list.add("收藏");
        list.add("在售");
        list.add("展出");
    }

1.5.4. List.of()

List<String> strings = List.of("first", "second");

1.5.5. Set.of()

Set<Integer> ints = Set.of(1, 2, 3);


1.6. HashMap

Map
 ├Hashtable
 ├HashMap
 └WeakHashMap

 Map接口
    请注意，Map没有继承Collection接口，Map提供key到value的映射。一个Map中不能包含相同的key，每个key只能映射一个 value。Map接口提供3种集合的视图，Map的内容可以被当作一组key集合，一组value集合，或者一组key-value映射。
Hashtable类
    Hashtable继承Map接口，实现一个key-value映射的哈希表。任何非空（non-null）的对象都可作为key或者value。
    添加数据使用put(key, value)，取出数据使用get(key)，这两个基本操作的时间开销为常数。
Hashtable通过initial capacity和load factor两个参数调整性能。通常缺省的load factor 0.75较好地实现了时间和空间的均衡。增大load factor可以节省空间但相应的查找时间将增大，这会影响像get和put这样的操作。
使用Hashtable的简单示例如下，将1，2，3放到Hashtable中，他们的key分别是”one”，”two”，”three”：
        Hashtable numbers = new Hashtable();
        numbers.put(“one”, new Integer(1));
        numbers.put(“two”, new Integer(2));
        numbers.put(“three”, new Integer(3));
    要取出一个数，比如2，用相应的key：
        Integer n = (Integer)numbers.get(“two”);
        System.out.println(“two = ” + n);
    由于作为key的对象将通过计算其散列函数来确定与之对应的value的位置，因此任何作为key的对象都必须实现hashCode和equals方 法。hashCode和equals方法继承自根类Object，如果你用自定义的类当作key的话，要相当小心，按照散列函数的定义，如果两个对象相 同，即obj1.equals(obj2)=true，则它们的hashCode必须相同，但如果两个对象不同，则它们的hashCode不一定不同，如 果两个不同对象的hashCode相同，这种现象称为冲突，冲突会导致操作哈希表的时间开销增大，所以尽量定义好的hashCode()方法，能加快哈希 表的操作。
    如果相同的对象有不同的hashCode，对哈希表的操作会出现意想不到的结果（期待的get方法返回null），要避免这种问题，只需要牢记一条：要同时复写equals方法和hashCode方法，而不要只写其中一个。
    Hashtable是同步的。
HashMap类
    HashMap和Hashtable类似，不同之处在于HashMap是非同步的，并且允许null，即null value和null key。，但是将HashMap视为Collection时（values()方法可返回Collection），其迭代子操作时间开销和HashMap 的容量成比例。因此，如果迭代操作的性能相当重要的话，不要将HashMap的初始化容量设得过高，或者load factor过低。
WeakHashMap类
    WeakHashMap是一种改进的HashMap，它对key实行“弱引用”，如果一个key不再被外部所引用，那么该key可以被GC回收。

1.6.1. static map

    private static final Map<String, String> point;
    static {
        point = new HashMap<String, String>();
        point.put("CN", "China");
        point.put("HK", "Hongkong");
        point.put("TW", "Taiwan");
    };

1.6.2. foreach hashmap

Map<String, Integer> session = new HashMap<String, Integer>();

session.put("A",1);
...
...
session.put("Z",26)

for (Map.Entry<String, Integer> entry : session.entrySet()) {
    System.out.println(String.format("%s:%d", entry.getKey(), entry.getValue()));
}

Map<Integer, Integer> map = new HashMap<Integer, Integer>();

for (Map.Entry<Integer, Integer> entry : map.entrySet()) {

    System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue());

}

Map<Integer, Integer> map = new HashMap<Integer, Integer>();

//遍历map中的键

for (Integer key : map.keySet()) {

    System.out.println("Key = " + key);

}

//遍历map中的值

for (Integer value : map.values()) {

    System.out.println("Value = " + value);

}

Map<Integer, Integer> map = new HashMap<Integer, Integer>();

for (Integer key : map.keySet()) {

    Integer value = map.get(key);

    System.out.println("Key = " + key + ", Value = " + value);

}

Map<Integer, Integer> map = new HashMap<Integer, Integer>();

Iterator<Map.Entry<Integer, Integer>> entries = map.entrySet().iterator();

while (entries.hasNext()) {

    Map.Entry<Integer, Integer> entry = entries.next();

    System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue());

}

Map map = new HashMap();

Iterator entries = map.entrySet().iterator();

while (entries.hasNext()) {

    Map.Entry entry = (Map.Entry) entries.next();

    Integer key = (Integer)entry.getKey();

    Integer value = (Integer)entry.getValue();

    System.out.println("Key = " + key + ", Value = " + value);

}

1.6.3. LinkedHashMap

import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
public class TestLinkedHashMap {

  public static void main(String args[])
  {
   System.out.println("*** LinkedHashMap ***");
   Map<Integer,String> map = new LinkedHashMap<Integer,String>();
   map.put(6, "apple");
   map.put(3, "banana");
   map.put(2,"pear");

   for (Iterator it =  map.keySet().iterator();it.hasNext();)
   {
    Object key = it.next();
    System.out.println( key+"="+ map.get(key));
   }

   System.out.println("*** HashMap ***");
   Map<Integer,String> map1 = new  HashMap<Integer,String>();
   map1.put(6, "apple");
   map1.put(3, "banana");
   map1.put(2,"pear");

   for (Iterator it =  map1.keySet().iterator();it.hasNext();)
   {
    Object key = it.next();
    System.out.println( key+"="+ map1.get(key));
   }
  }
}

1.6.4. 静态初始化 Map

    public final static Map<String, String> hostMap = new HashMap<String, String>() {
        {
            put("redis", "127.0.0.1");
            put("solr", "127.0.0.1");
        }
    };

    public final static Map map = new HashMap() {{
        put("key1", "value1");
        put("key2", "value2");
    }};


1.7. Network

Java 网络相关操作

1.7.1. URL

import java.net.*;
import java.io.*;

public class URLReader {
    public static void main(String[] args) throws Exception {

        URL url = new URL("http://www.netkiller.cn/");
        BufferedReader in = new BufferedReader(new InputStreamReader(url.openStream()));

        String inputLine;
        while ((inputLine = in.readLine()) != null)
            System.out.println(inputLine);
        in.close();
    }
}

1.7.2. java.io.tmpdir

改变java.io.tmpdir的默认值

System.setProperty("java.io.tmpdir", "/vat/tmp");
System.out.println(System.getProperty("java.io.tmpdir"));


1.8. JDBC

1.8.1. 安装 JDBC 包

将ojdbc6.jar包复制到jre/lib/ext目录下

# mv ojdbc6.jar /srv/jdk1.8.0_60/jre/lib/ext/

1.8.2. MySQL

1.8.3. Oracle

1.8.3.1. SID

jdbc:oracle:thin:@<host>:<port>:<SID> Example:

jdbc:oracle:thin:@192.168.2.1:1521:oral

1.8.3.2. SERVICE_NAME

jdbc:oracle:thin:@//<host>:<port>/<service_name>

Example:

jdbc:oracle:thin:@//192.168.2.1:1521/orcl.example.com

1.8.3.3. TNS

jdbc:oracle:thin:@<TNSName> Example:

jdbc:oracle:thin:@TNS

1.8.3.4. Oracle RAC Cluster

Oracle 11G 不能直接链接 RAC 的 VIP

jdbc:oracle:thin:@(DESCRIPTION =
    (ADDRESS_LIST =
        (ADDRESS = (PROTOCOL = TCP)(HOST = 172.16.10.10)(PORT = 1521))
        (ADDRESS = (PROTOCOL = TCP)(HOST = 172.16.10.20)(PORT = 1521))
        (LOAD_BALANCE = yes)
    )
    (CONNECT_DATA =
        (SERVER = DEDICATED)
        (SERVICE_NAME = orcl.example.com)
        (FAILOVER_MODE =(TYPE = SELECT )(METHOD = BASIC)(RETRIES = 120)(DELAY = 5)))
    )

jdbc:oracle:thin:@(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=16.50.26.29)(PORT=1521))(LOAD_BALANCE=yes)(FAILOVER=ON)(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=orcl.example.com)(FAILOVER_MODE=(TYPE=SESSION)(METHOD=BASIC))))

1.8.3.5. Oracle JDBC Demo

package cn.netkiller.zabbix;

import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;

/**
 * Java JDBC Oracle Demo!
 *
 */
public class Oracle {

    String url = null; // 数据库链接地址
    String username = null;// 用户名,系统默认的账户名
    String password = null;// 你安装时选设置的密码

    public void openConfig() {
        String connectionfig = "jdbc.properties";
        Properties properties = new Properties();
        try {
            properties.load(new FileInputStream(connectionfig));
            this.url = properties.getProperty("jdbc.url");
            this.username = properties.getProperty("jdbc.username");
            this.password = properties.getProperty("jdbc.password");
        } catch (FileNotFoundException e) {
            System.out.println(
                    e.getMessage() + " Working Directory = " + System.getProperty("user.dir") + "/" + connectionfig);
        } catch (IOException e) {
            System.out.println(e.getMessage());
        }
        if (this.url == null || this.username == null || this.password == null) {
            System.out.println("This Propertie file is invalid");
            // throw new Exception("");
        }

    }

    public void testOracle() {
        Connection connection = null;// 创建一个数据库连接
        ResultSet result = null;// 创建一个结果集对象
        Statement statement = null;
        try {
            Class.forName("oracle.jdbc.driver.OracleDriver");// 加载Oracle驱动程序
            connection = DriverManager.getConnection(this.url, this.username, this.password);
            String sql = "select current_date from dual";
            statement = connection.createStatement();
            result = statement.executeQuery(sql);
            result.next();
            System.out.printf("%s %s", result.getDate(1), result.getTime(1));
        } catch (ClassNotFoundException e) {
            System.out.println(e.getMessage());
        } catch (SQLException e) {
            System.out.println(e.getMessage());
        } finally {
            try {
                if (result != null)
                    result.close();
                if (connection != null)
                    connection.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        Oracle oracle = new Oracle();
        oracle.openConfig();
        oracle.testOracle();
    }
}

1.8.4. FAQ

1.8.4.1. java.sql.SQLRecoverableException: IO Error: The Network Adapter could not establish the connection

直接连接 RAC VIP 提示

java.sql.SQLRecoverableException: IO Error: The Network Adapter could not establish the connection

解决方案

jdbc:oracle:thin:@(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=16.50.26.29)(PORT=1521))(LOAD_BALANCE=yes)(FAILOVER=ON)(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=orcl.example.com)(FAILOVER_MODE=(TYPE=SESSION)(METHOD=BASIC))))

1.8.4.2. Exception in thread "main" java.lang.ClassNotFoundException: oracle.jdbc.driver.OracleDriver

检查 /srv/jdk1.8.0_60/jre/lib/ext/ 是否有jdbc包，然后运行下面程序检查是否已经载入

# java -verbose:class JdbcTest | grep jdbc

[Loaded oracle.jdbc.driver.OracleDriver from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.jdbc.OracleDriver from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.jdbc.driver.OracleDriverExtension from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.jdbc.driver.OracleDriver$1 from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.jdbc.driver.ClassRef from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.jdbc.driver.ClassRef$XMLTypeClassRef from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.jdbc.driver.ClassRef$Locale from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.jdbc.driver.ClassRef$LocaleCategoryClassRef from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.jdbc.driver.DiagnosabilityMXBean from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.jdbc.driver.OracleDiagnosabilityMBean from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.jdbc.driver.DatabaseError from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.jdbc.driver.OracleSQLException from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.jdbc.driver.SQLStateMapping from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.jdbc.driver.SQLStateMapping$Tokenizer from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.jdbc.driver.Message from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.jdbc.driver.Message11 from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.jdbc.OracleConnection from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.jdbc.internal.OracleConnection from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.jdbc.internal.ClientDataSupport from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.jdbc.OracleConnectionWrapper from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.jdbc.driver.OracleConnection from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.jdbc.driver.PhysicalConnection from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.jdbc.driver.T4CDriverExtension from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.jdbc.OracleStatement from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.jdbc.OraclePreparedStatement from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.jdbc.OracleCallableStatement from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.jdbc.internal.OracleStatement from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.jdbc.internal.OraclePreparedStatement from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.jdbc.internal.OracleCallableStatement from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.jdbc.driver.ScrollRsetStatement from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.jdbc.driver.OracleStatement from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.jdbc.driver.OraclePreparedStatement from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.jdbc.driver.OracleCallableStatement from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.jdbc.driver.T4CCallableStatement from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.jdbc.driver.T4CStatement from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.jdbc.driver.T4CPreparedStatement from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.jdbc.driver.OracleBufferedStream from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.jdbc.driver.OracleInputStream from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.jdbc.driver.T4CInputStream from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.sql.BfileDBAccess from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.sql.BlobDBAccess from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.sql.ClobDBAccess from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.jdbc.driver.T4CConnection from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.jdbc.internal.OracleDatumWithConnection from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.jdbc.OracleClob from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.jdbc.internal.OracleClob from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.sql.Datum from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.sql.DatumWithConnection from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.sql.CLOB from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.jdbc.OracleNClob from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.jdbc.internal.OracleNClob from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.sql.NCLOB from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.jdbc.OracleSQLPermission from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.jdbc.AdditionalDatabaseMetaData from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.jdbc.OracleDatabaseMetaData from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.jdbc.driver.OracleDatabaseMetaData from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.jdbc.OracleSavepoint from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.jdbc.driver.PhysicalConnection$2 from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.jdbc.aq.AQMessage from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.jdbc.driver.NTFRegistration from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.jdbc.NotificationRegistration from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.jdbc.dcn.DatabaseChangeRegistration from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.jdbc.driver.NTFDCNRegistration from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.jdbc.OracleTypeMetaData from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.jdbc.internal.ObjectData from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.sql.ORAData from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.jdbc.OracleData from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.sql.TypeDescriptor from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.jdbc.OracleTypeMetaData$Struct from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.sql.StructDescriptor from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.jdbc.OracleTypeMetaData$Array from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.sql.ArrayDescriptor from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.jdbc.driver.OracleClobInputStream from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.jdbc.driver.OracleBlobInputStream from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.jdbc.driver.OracleConversionInputStream from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.jdbc.driver.OracleConversionReader from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.net.ns.NetException from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.jdbc.aq.AQNotificationRegistration from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.jdbc.driver.NTFAQRegistration from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.jdbc.driver.OracleBlobOutputStream from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.jdbc.driver.OracleClobOutputStream from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.jdbc.driver.OracleClobReader from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.jdbc.driver.OracleClobWriter from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.jdbc.internal.XSEvent from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.jdbc.driver.NTFXSEvent from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.net.ns.Communication from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.jdbc.driver.CRC64 from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.jdbc.driver.NTFManager from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.jdbc.driver.PhysicalConnection$1 from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.net.ns.SQLnetDef from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.net.ns.NSProtocol from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.net.ns.NetOutputStream from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.net.jdbc.nl.NLException from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.net.ns.NetInputStream from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.net.ns.SessionAtts from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.net.jdbc.nl.NVFactory from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.net.jdbc.nl.InvalidSyntaxException from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.net.jdbc.nl.NVNavigator from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.net.resolver.AddrResolution from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.net.jdbc.TNSAddress.SOException from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.net.ns.ClientProfile from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.net.nt.ConnStrategy from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.net.jdbc.TNSAddress.SchemaObjectFactoryInterface from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.net.resolver.NavSchemaObjectFactory from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.net.jdbc.TNSAddress.SchemaObject from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.net.resolver.NavSchemaObject from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.net.jdbc.TNSAddress.ServiceAlias from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.net.resolver.NavServiceAlias from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.net.jdbc.nl.NVTokens from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.net.jdbc.nl.UninitializedObjectException from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.net.jdbc.nl.NVPair from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.net.jdbc.TNSAddress.Description from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.net.resolver.NavDescription from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.net.jdbc.TNSAddress.Address from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.net.resolver.NavAddress from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.net.jdbc.TNSAddress.DescriptionList from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.net.resolver.NavDescriptionList from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.net.nt.ConnOption from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.net.nt.NTAdapter from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.net.nt.TcpNTAdapter from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.net.ns.Packet from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.net.ns.DataPacket from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.net.ns.DataDescriptorPacket from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.net.ns.BreakNetException from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.net.ano.Ano from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.net.ano.AnoNetInputStream from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.net.ano.AnoNetOutputStream from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.net.ano.Service from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.net.ano.AnoComm from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.net.ano.SupervisorService from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.net.ano.AuthenticationService from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.net.ano.EncryptionService from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.net.aso.g from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.net.ano.DataIntegrityService from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.net.aso.d from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.net.aso.i from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.net.aso.b from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.net.ns.ConnectPacket from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]
[Loaded oracle.net.ns.RedirectPacket from file:/srv/jdk1.8.0_60/jre/lib/ext/ojdbc6.jar]


1.9. Util

1.9.1. Properties 处理 *.properties 文件

package netkiller.test;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.FileInputStream;
import java.util.Properties;

public class PropertiesTest {

    public static void main(String[] args) {

        System.out.println("Working Directory = " + System.getProperty("user.dir"));

        Properties ps = new Properties();
        try {
            ps.load(new FileInputStream("netkiller.properties"));
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println(ps.getProperty("key"));
    }

}

1.9.1.1. 打开 properties 文件

1.9.1.1.1. 文件方式打开

BufferedReader br = null;
Properties properties = new Properties();
br = new BufferedReader(new InputStreamReader(new  FileInputStream(new File("data.properties")), "UTF8"));
properties.load(br);
1.9.1.1.2. 输入流

Properties properties = new Properties();
InputStream in = getClass().getResourceAsStream("/IcisReport.properties");
properties.load(in);
Set keyValue = properties.keySet();
for (Iterator it = keyValue.iterator(); it.hasNext();)
{
    String key = (String) it.next();
}

1.9.1.2. propertyNames()

package cn.netkiller.properties;

import java.util.Enumeration;
import java.util.Map.Entry;
import java.util.Properties;

public class PropertiesTest {

    public PropertiesTest() {
        // TODO Auto-generated constructor stub
    }

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Properties properties = new Properties();

        properties.put("K1", "V1");
        properties.put("K2", "V2");

        for (Entry<Object, Object> x : properties.entrySet()) {
            System.out.println(x.getKey() + " " + x.getValue());
        }

        Enumeration<?> e = properties.propertyNames();
        while (e.hasMoreElements()) {
            String key = (String) e.nextElement();
            String value = properties.getProperty(key);
            System.out.println(key + ": " + value);
        }
    }

}

import java.io.FileInputStream;
import java.util.Enumeration;
import java.util.Properties;

public class MainClass {
  public static void main(String args[]) throws Exception {
    Properties p = new Properties();
    p.load(new FileInputStream("test.properties"));
    Enumeration e = p.propertyNames();

    for (; e.hasMoreElements();) {
      System.out.println(e.nextElement());

    }
  }
}

1.9.1.3. keySet()

package cn.netkiller.properties;

import java.util.Properties;
import java.util.Set;

public class PropertiesTest {

    public PropertiesTest() {
        // TODO Auto-generated constructor stub
    }

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Properties properties = new Properties();

        properties.put("K1", "V1");
        properties.put("K2", "V2");

        Set<Object> states = properties.keySet();

        for (Object name : states)
            System.out.println("The value of " + name + " is " + properties.getProperty((String) name) + ".");
    }

}

1.9.1.4. entrySet()

package cn.netkiller.properties;

import java.util.Map.Entry;
import java.util.Properties;

public class PropertiesTest {

    public PropertiesTest() {
        // TODO Auto-generated constructor stub
    }

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Properties properties = new Properties();

        properties.put("K1", "V1");
        properties.put("K2", "V2");

        for (Entry<Object, Object> x : properties.entrySet()) {
            System.out.println(x.getKey() + " " + x.getValue());
        }
    }

}

1.9.1.5. 方法中返回 Properties

    @RequestMapping("/host")
    public Enumeration<Object> host() throws IOException {
        Properties properties = PropertiesLoaderUtils.loadProperties(new ClassPathResource(String.format("/%s.properties", "host")));
        return properties.keys();
    }

    @RequestMapping("/mail")
    public Collection<Object> mail() throws IOException {
        Properties properties = PropertiesLoaderUtils.loadProperties(new ClassPathResource(String.format("/%s.properties", "mail")));
        return properties.values();
    }
    @RequestMapping("/nameserver")
    public Set<Entry<Object, Object>> nameserver() throws IOException {
        Properties properties = PropertiesLoaderUtils.loadProperties(new ClassPathResource(String.format("/%s.properties", "dns")));
        return properties.entrySet();
    }
    @RequestMapping("/dns")
    public Properties dns() throws IOException {
        Properties properties = PropertiesLoaderUtils.loadProperties(new ClassPathResource(String.format("/%s.properties", "dns")));
        return properties;
    }

1.9.1.6.

1.9.1.7. getResourceAsStream()

Properties prop = new Properties();
prop.load(getServletContext().getResourceAsStream("/WEB-INF/resource/sample.properties"));

prop.load(Thread.currentThread().getContextClassLoader().getResourceAsStream("netkiller.properties"));

1.9.1.8. store

package cn.netkiller.config;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Properties;

public class Config {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Properties prop = new Properties();
        OutputStream output = null;

        try {

            output = new FileOutputStream("config.properties");

            // set the properties value
            prop.setProperty("host", "localhost");
            prop.setProperty("port", "8000");
            prop.setProperty("user", "neo");
            prop.setProperty("pass", "password");

            // save properties to project root folder
            prop.store(output, null);

        } catch (IOException io) {
            io.printStackTrace();
        } finally {
            if (output != null) {
                try {
                    output.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

        }
    }

}

getProperty 取出key的值

package cn.netkiller.config;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

public class LoadConfig {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Properties prop = new Properties();
        InputStream input = null;

        try {

            input = new FileInputStream("config.properties");

            // load a properties file
            prop.load(input);

            // get the property value and print it out
            System.out.println(prop.getProperty("host"));
            System.out.println(prop.getProperty("port"));
            System.out.println(prop.getProperty("user"));
            System.out.println(prop.getProperty("pass"));

        } catch (IOException ex) {
            ex.printStackTrace();
        } finally {
            if (input != null) {
                try {
                    input.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }

}

循环打印所有 Properties 内容

package test;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.Properties;

public class Application {

    public static void main(String[] args) {
        Application app = new Application();
        app.config();

    }

    private void config() {
        Properties prop = new Properties();
        InputStream input = null;
        try {

            String filename = "config.properties";

            prop.load(new FileInputStream(filename));

            Enumeration<?> e = prop.propertyNames();
            while (e.hasMoreElements()) {
                String key = (String) e.nextElement();
                String value = prop.getProperty(key);
                System.out.println(key + ": " + value);
            }

        } catch (IOException ex) {
            ex.printStackTrace();
        } finally {
            if (input != null) {
                try {
                    input.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

}

1.9.1.9. 实现国际化

准备语言包文件 chinese.properties 内容如下

hello=你好世界

english.properties

hello=Helloworld

    @GetMapping("/lang")
    public String lang(@RequestHeader String lang) throws IOException {
        System.out.println(lang);
        Properties properties = PropertiesLoaderUtils.loadProperties(new ClassPathResource(lang + ".properties"));
        String tmp = properties.getProperty("hello");
        return tmp;
    }

测试效果

curl -s -H lang:chinese http://localhost:8080/lang
你好世界

curl -s -H lang:english http://localhost:8080/lang
Helloworld

1.9.2. Logging

import java.util.logging.*;
public class Main {
    public static void main(String[] args) {
        Logger log = Logger.getLogger("test");
        log.setLevel(Level.INFO);
        log.info("--------------------------");
        log.info("Test");
        log.info("--------------------------");

    }
}

XML

import java.io.IOException;
import java.util.logging.*;

public class Main {
    public static void main(String[] args) {

        try {
            Logger log = Logger.getLogger("test");
            FileHandler fileHandler = new FileHandler("test.%g.log");
            fileHandler.setLevel(Level.INFO);
            log.addHandler(fileHandler);

            log.setLevel(Level.INFO);
            log.info("One");
            log.info("Two");
            log.info("Three");

        } catch (SecurityException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }
}

XML 输出结果

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE log SYSTEM "logger.dtd">
<log>
<record>
  <date>2016-04-19T15:57:19</date>
  <millis>1461052639360</millis>
  <sequence>0</sequence>
  <logger>test</logger>
  <level>INFO</level>
  <class>Main</class>
  <method>main</method>
  <thread>1</thread>
  <message>One</message>
</record>
<record>
  <date>2016-04-19T15:57:19</date>
  <millis>1461052639394</millis>
  <sequence>1</sequence>
  <logger>test</logger>
  <level>INFO</level>
  <class>Main</class>
  <method>main</method>
  <thread>1</thread>
  <message>Two</message>
</record>
<record>
  <date>2016-04-19T15:57:19</date>
  <millis>1461052639395</millis>
  <sequence>2</sequence>
  <logger>test</logger>
  <level>INFO</level>
  <class>Main</class>
  <method>main</method>
  <thread>1</thread>
  <message>Three</message>
</record>
</log>

Formatter 日志格式化

import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.logging.*;

class LogFormatter extends Formatter {
    @Override
    public String format(LogRecord record) {
        return String.format("%s %s\t%s\n", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date()) , record.getLevel(), record.getMessage());
    }
}

public class Main {
    public static void main(String[] args) {

        try {
            Logger log = Logger.getLogger("test");
            FileHandler fileHandler = new FileHandler("test.%g.log");
            fileHandler.setLevel(Level.INFO);
            log.addHandler(fileHandler);
            fileHandler.setFormatter(new LogFormatter());
            log.setLevel(Level.INFO);
            log.info("One");
            log.info("Two");
            log.info("Three");

        } catch (SecurityException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }
}

输出样式

2016-04-19 16:05:53.324 INFO	One
2016-04-19 16:05:53.352 INFO	Two
2016-04-19 16:05:53.353 INFO	Three

1.9.2.1. console

控制台日志输入格式定义

                ConsoleHandler consoleHandler = new ConsoleHandler();
                consoleHandler.setLevel(Level.OFF);
                logger.addHandler(consoleHandler);

禁止 Console 输出

                logger.setUseParentHandlers(false);

1.9.3. BASE64

package cn.netkiller.test;

import java.nio.charset.StandardCharsets;
import java.util.Base64;

public class Base64Test {
    public static void main(String[] args) {
        final String text = "http://www.netkiller.cn/index.html";

        final String encoded = Base64.getEncoder().encodeToString(text.getBytes(StandardCharsets.UTF_8));
        System.out.println(encoded);

        final String decoded = new String(Base64.getDecoder().decode(encoded), StandardCharsets.UTF_8);
        System.out.println(decoded);
    }
}

1.9.4. Locale 国际化

package cn.netkiller.i18n;

import java.util.Locale;

public class Lang {

    public Lang() {
        // TODO Auto-generated constructor stub
    }

    public static void main(String[] args) {
        // create a new locale
        Locale locale = new Locale("ENGLISH", "US");

        // print locale
        System.out.println("Locale:" + locale);

        // print display name for locale - based on inLocale
        System.out.println("Name:" + locale.getDisplayName(new Locale("GERMAN", "GERMANY")));

    }

}

Locale locale = new Locale("zh", "CN");
Locale locale = Locale.US;
Locale locale = Locale.getDefault();	// 默认语言

1.9.5. ResourceBundle

java.util.ResourceBundle resourceBundle = java.util.ResourceBundle.getBundle("message");

resourceBundle.getString("nickname");

// 指定语言

Locale  locale = new Locale("zh", "CN");
ResourceBundle res = ResourceBundle.getBundle("message", locale);

1.9.6. Scanner

Scanner in = new Scanner(System.in);
System.out.println("Username:");
String username = in.next();

1.9.7. UUID

package cn.netkiller.example.uuid;

import java.util.UUID;

public class UuidTest {

    public UuidTest() {
        // TODO Auto-generated constructor stub
    }

    public static void main(String[] args) {
        UUID uuid = UUID.randomUUID();
        String randomUUIDString = uuid.toString();

        System.out.println("Random UUID String = " + randomUUIDString);
        System.out.println("UUID version       = " + uuid.version());
        System.out.println("UUID variant       = " + uuid.variant());
    }

}

1.9.8. Arrays.equals 判断两个数组是否相等

static boolean equals(type[] a, type[] b)

Arrays.equals(array1, array2)


1.10. IO

1.10.1. 取出文件名中的扩展名

    File file = new File("HelloWorld.jpeg");
    String fileName = file.getName();
    String suffix = fileName.substring(fileName.lastIndexOf(".") + 1);
    System.out.println(suffix);

1.10.1.1. getAbsolutePath() 获取绝对路径

    File configFile = new File(System.getProperty("user.dir") + configPath);
    System.out.printf("configFile : %s\n", configFile.getAbsolutePath());

1.10.1.2. 创建目录 mkdir()

//判断cache目录是否存在的代码，如果不存在则创建cache目录
String path="/tmp/cache";
File dirname = new File(path);

//目录不存在
if (!dirname.isDirectory())
{
     dirname.mkdir(); //创建目录
}

1.10.2. 临时文件

package cn.netkiller.file;

import java.io.File;
import java.io.IOException;

public class CreateTempFileExample
{
    public static void main(String[] args)
    {

        try{

           //create a temp file
           File temp = File.createTempFile("temp-file-name", ".tmp");

           System.out.println("Temp file : " + temp.getAbsolutePath());

        }catch(IOException e){

           e.printStackTrace();

        }

    }
}

Temp file : C:\Users\mkyong\AppData\Local\Temp\temp-file-name623426.tmp

1.10.3. FileWriter 文本写入文件

import java.io.*;

public class Main {

    public static void main(String[] args) {

        try {
            String str = "SomeMoreTextIsHere";
            File newTextFile = new File("thetextfile.txt");

            FileWriter fw = new FileWriter(newTextFile);
            fw.write(str);
            fw.close();

        } catch (IOException iox) {
            //do stuff with exception
            iox.printStackTrace();
        }
    }
}

1.10.4. BufferedWriter

     File file = new File( fileName );

    // if file doesnt exists, then create it
    if ( ! file.exists( ) )
    {
        file.createNewFile( );
    }

    FileWriter fw = new FileWriter( file.getAbsoluteFile( ) );
    BufferedWriter bw = new BufferedWriter( fw );
    bw.write( text );
    bw.close( );

1.10.5. InputStreamReader

InputStreamReader stream = new InputStreamReader(this.getClass().getClassLoader().getResourceAsStream(filename));

1.10.6. 获得 Resource 下文件路径

String path = Main.class.getClassLoader().getResource("netkiller-config.yaml").getPath();
String path = Main.class.getClassLoader().getResource("netkiller-config.yaml").getPath();

1.10.7. PrintWriter

package cn.netkiller.io;

import java.io.FileNotFoundException;
import java.io.PrintWriter;

public class PrintWriterTest {

    public PrintWriterTest() {
        // TODO Auto-generated constructor stub
    }

    public static void main(String[] args) throws FileNotFoundException {
        // TODO Auto-generated method stub
        PrintWriter output = new PrintWriter("temp.txt");
        output.print("Welcome to Java!");
        output.close();
    }

}

1.10.8. OutputStreamWriter

        File file = new File("/tmp" + File.separator + "netkiller.txt");
        OutputStreamWriter outputStreamWriter = new OutputStreamWriter(new FileOutputStream(file), "utf-8");
        outputStreamWriter.write("Netkiller Java 手札");
        outputStreamWriter.close();

1.10.9. FileOutputStream

byte[] data = ...

FileOutputStream fos = ...
fos.write(data, 0, data.length);
fos.flush();
fos.close();

1.10.10. FileInputStream

File inputFile = new File(filePath);
byte[] data = new byte[inputFile.length()];
FileInputStream fis = new FileInputStream(inputFile);
fis.read(data, 0, data.length);
fis.close();

1.10.11. Scanner

Scanner input = new Scanner(new File("temp.txt"));
System.out.print(input.nextLine());

package cn.netkiller.io;

import java.io.File;
import java.io.FileNotFoundException;
//import java.io.PrintWriter;
import java.util.Scanner;

public class PrintWriterTest {

    private static Scanner input;

    public PrintWriterTest() {
        // TODO Auto-generated constructor stub
    }

    public static void main(String[] args) throws FileNotFoundException {
        // TODO Auto-generated method stub
        // PrintWriter output = new PrintWriter("temp.txt");
        // output.print("Welcome to Java!");
        // output.close();

        input = new Scanner(new File("temp.txt"));
        System.out.print(input.nextLine());
    }

}

1.10.12. 二进制文件

1.10.12.1. 理解二进制文件

我们运行下面一段程序，向文件 netkiller.bin 中写入一个整形数值 1 ，然后观察文件变化

        String filename = "netkiller.bin";
        DataOutputStream out = new DataOutputStream(new FileOutputStream(filename));
        out.writeInt(1);
        out.close();

打开终端，使用 xxd 命令查看二进制文件

neo@MacBook-Pro ~/workspace/netkiller % xxd -b netkiller.bin
00000000: 00000000 00000000 00000000 00000001                    ....

可以看到一串二进制 00000000 00000000 00000000 00000001，运行下面程序可以讲二进制转换为十进制，注意替换掉空格。

        int n = Integer.valueOf("00000000 00000000 00000000 00000001".replaceAll(" ", ""), 2);
        System.out.println(n);

运行结果是 1 ，为什前面那么多 0 呢？请运行下面一段程序

        String filename = "netkiller.bin";
        DataOutputStream out = new DataOutputStream(new FileOutputStream(filename));
        out.writeInt(Integer.MAX_VALUE);
        out.close();

现在观察结果

neo@MacBook-Pro ~/workspace/netkiller % xxd -b netkiller.bin
00000000: 01111111 11111111 11111111 11111111                    ....

        int n = Integer.valueOf("01111111 11111111 11111111 11111111".replaceAll(" ", ""), 2);
        System.out.println(n);

输出结果是 2147483647, 这是 int 得最大值，2147483647 + 1 会怎么样呢？

        String filename = "netkiller.bin";
        DataOutputStream out = new DataOutputStream(new FileOutputStream(filename));
        out.writeInt(Integer.MAX_VALUE + 1);
        out.close();

        System.out.println(Integer.MAX_VALUE + 1);

输出结果是 -2147483648，正确应该是 2147483648 这就是整形溢出。整形变量得二进制表示方法是4个字节长度32位 00000000 00000000 00000000 00000000 到 01111111 11111111 11111111 11111111 ， 其中第一位0表示正数1表示负数。

neo@MacBook-Pro ~/workspace/netkiller % xxd -b netkiller.bin
00000000: 10000000 00000000 00000000 00000000                    ....

整形溢出演示，超出整形范围怎么办？ 使用 Long 型。

System.out.println(Integer.MAX_VALUE);
System.out.println(Integer.MAX_VALUE + 1);
System.out.println(Integer.MIN_VALUE);
System.out.println(Integer.MIN_VALUE - 1);

输出结果如下：

2147483647
-2147483648
-2147483648
2147483647

负数演示

        String filename = "netkiller.bin";
        DataOutputStream out = new DataOutputStream(new FileOutputStream(filename));
        out.writeInt(-1);
        out.writeInt(Integer.MAX_VALUE + 1);
        out.close();

-1 得结果是 11111111 11111111 11111111 11111111

neo@MacBook-Pro ~/workspace/netkiller % xxd -b netkiller.bin
00000000: 11111111 11111111 11111111 11111111                    ....

现在我们存储两个整形数值

        String filename = "netkiller.bin";
        DataOutputStream out = new DataOutputStream(new FileOutputStream(filename));
        out.writeInt(1);
        out.writeInt(-1);
        out.close();

很清楚的看到里面有两个数值，1 和 -1

neo@MacBook-Pro ~/workspace/netkiller % xxd -c 4 -b netkiller.bin
00000000: 00000000 00000000 00000000 00000001  ....
00000004: 11111111 11111111 11111111 11111111  ....

读取二进制文件中的 int 数据

        DataInputStream in = new DataInputStream(new BufferedInputStream(new FileInputStream(filename)));
        try {
            int i = in.readInt();
            System.out.println(i);
        } catch (EOFException e) {
            e.printStackTrace();
        }

1.10.12.2. byte 类型

        String filename = "netkiller.bin";
        DataOutputStream out = new DataOutputStream(new FileOutputStream(filename));
        out.writeByte(1);
        out.close();

byte 只占用一个字节8位

neo@MacBook-Pro ~/workspace/netkiller % xxd -c 4 -b netkiller.bin
00000000: 00000001

如果写入 -1 结果是，由此得出 第一位 0 是正数，1 是负数，可以得出他的取值范围 -128 ~ 127。超出范围也会溢出。

neo@MacBook-Pro ~/workspace/netkiller % xxd -c 4 -b netkiller.bin
00000000: 11111111

常常写入最小值与最大值

        String filename = "netkiller.bin";
        DataOutputStream out = new DataOutputStream(new FileOutputStream(filename));
        out.writeByte(Byte.MIN_VALUE);
        out.writeByte(Byte.MAX_VALUE);
        out.close();

运行结果

neo@MacBook-Pro ~/workspace/netkiller % xxd -c 1 -b netkiller.bin
00000000: 10000000  .
00000001: 01111111  .

写入一个字符

        String filename = "netkiller.bin";
        DataOutputStream out = new DataOutputStream(new FileOutputStream(filename));
        out.writeBytes("a");
        out.close();

写入结果

neo@MacBook-Pro ~/workspace/netkiller % xxd -c 1 -b netkiller.bin
00000000: 01100001  a

从 ASCII 表中查出 01100001 十进制 97 十六进制 61 对应字母 a

写入一段字符串

        String filename = "netkiller.bin";
        DataOutputStream out = new DataOutputStream(new FileOutputStream(filename));
        out.writeBytes("http://www.netkiller.cn");
        out.close();

运行结果

neo@MacBook-Pro ~/workspace/netkiller % xxd -c 8 -b netkiller.bin
00000000: 01101000 01110100 01110100 01110000 00111010 00101111 00101111 01110111  http://w
00000008: 01110111 01110111 00101110 01101110 01100101 01110100 01101011 01101001  ww.netki
00000010: 01101100 01101100 01100101 01110010 00101110 01100011 01101110           ller.cn

读取二进制文件中的 byte 字符串，readAllBytes() 可以一次读取所有 byte 到 byte[] 中。

        DataInputStream in = new DataInputStream(new BufferedInputStream(new FileInputStream(filename)));
        try {
            System.out.println(new String(in.readAllBytes()));
        } catch (EOFException e) {
            e.printStackTrace();
        }

readByte() 逐字节读取

        DataInputStream in = new DataInputStream(new BufferedInputStream(new FileInputStream(filename)));
        try {
            char c = ' ';
            while (true) {
                try {
                    c = (char) in.readByte();
                    System.out.print(c);

                } catch (EOFException e) {
                    System.out.println();
                    break;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

现在我们已经掌握了 byte 的操作方法，现在我们来做一个例子，读取 int 数据，int 是由 4 个字节组成一组。所以我们每次取 4个字节。

        // 这个例子中，我们写入三个数值到 netkiller.bin 文件，分别是 1024，-128，2147483647
        String filename = "netkiller.bin";
        DataOutputStream out = new DataOutputStream(new FileOutputStream(filename));
        out.writeInt(1024);
        out.writeInt(-128);
        out.writeInt(Integer.MAX_VALUE);
        out.close();

二进制文件如下

neo@MacBook-Pro ~/workspace/netkiller % xxd -c 4 -b netkiller.bin
00000000: 00000000 00000000 00000100 00000000  ....
00000004: 11111111 11111111 11111111 10000000  ....
00000008: 01111111 11111111 11111111 11111111  ....

从二进制文件读出 int 数据。

        String filename = "netkiller.bin";
        FileInputStream stream = new FileInputStream(filename);

        byte[] buffer = new byte[4];

        while (stream.read(buffer) != -1) {
            ByteBuffer byteBuffer = ByteBuffer.wrap(buffer);
            System.out.println(byteBuffer.getInt());
        }

运行结果

1024
-128
2147483647

1.10.12.3. boolean 布尔型

我们想文件写入两个布尔类型，一个是 true, 另一个是 false

        String filename = "netkiller.bin";
        DataOutputStream out = new DataOutputStream(new FileOutputStream(filename));
        out.writeBoolean(true);
        out.writeBoolean(false);
        out.close();

运行结果可以看出 boolean 使用了一个字节，最后一位 1 表示true, 0 表示 false。所以对于二进制文件最小单位就是 byte 字节，虽然boolean型只需要一个 1 bit 位，但是存储的最小单位是字节，所以前面需要补7个零 0000000。

neo@MacBook-Pro ~/workspace/netkiller % xxd -c 1 -b netkiller.bin
00000000: 00000001  .
00000001: 00000000  .

使用 ls 命令可以看这个文件占用了 2B（两个字节）

neo@MacBook-Pro ~/workspace/netkiller % ll netkiller.bin
-rw-r--r--  1 neo  staff     2B Oct 18 13:47 netkiller.bin

读取二进制文件中的 boolean 数据

        DataInputStream in = new DataInputStream(new BufferedInputStream(new FileInputStream(filename)));
        try {
            boolean bool = in.readBoolean();
            System.out.println(bool);
        } catch (EOFException e) {
            e.printStackTrace();
        }

1.10.12.4. Long 型

        String filename = "netkiller.bin";
        DataOutputStream out = new DataOutputStream(new FileOutputStream(filename));
        out.writeLong(1);
        out.close();

有了上面 int 型数据的经验，下面一看你就会明白。long 型采用 8 个字节保存数据，是 int 的一倍。取值范围这里就不多说了，也会存在溢出现象。

neo@MacBook-Pro ~/workspace/netkiller % xxd -c 8 -b netkiller.bin
00000000: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001  ........

取值范围

        String filename = "netkiller.bin";
        DataOutputStream out = new DataOutputStream(new FileOutputStream(filename));
        out.writeLong(Long.MIN_VALUE);
        out.writeLong(Long.MAX_VALUE);
        out.close();

输出文件

neo@MacBook-Pro ~/workspace/netkiller % xxd -c 8 -b netkiller.bin
00000000: 10000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000  ........
00000008: 01111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111  ........

读取二进制文件中的 long 数据

        DataInputStream in = new DataInputStream(new BufferedInputStream(new FileInputStream(filename)));
        try {

            long l = in.readLong();
            System.out.println(l);

        } catch (EOFException e) {
            e.printStackTrace();
        }

1.10.12.5. chat 类型

有符号 signed char 类型的范围为 -128~127

无符号 unsigned char 的范围为0~ 255

char 与 byte 操作类似，我们首先去 ASCII 表查找字符 A 对应 65，我们将 65 写入二进制文件。然后读取该字符，输出结果是 A。

        String filename = "netkiller.bin";
        DataOutputStream out = new DataOutputStream(new FileOutputStream(filename));
        out.writeChar(65);
        out.close();

        DataInputStream in = new DataInputStream(new BufferedInputStream(new FileInputStream(filename)));
        try {

            char c = in.readChar();
            System.out.println(c);

        } catch (EOFException e) {
            e.printStackTrace();
        }

从二进制文件中我们可以看到 char 类型占用2个字节16位

neo@MacBook-Pro ~/workspace/netkiller % xxd -c 2 -b netkiller.bin
00000000: 00000000 01000001  .A

使用 writeChars()写入字符串到二进制文件

        String filename = "netkiller.bin";
        DataOutputStream out = new DataOutputStream(new FileOutputStream(filename));
        out.writeChars("http://www.netkiller.cn");
        out.close();

        DataInputStream in = new DataInputStream(new BufferedInputStream(new FileInputStream(filename)));

        char c = ' ';
        while (true) {
            try {
                c = in.readChar();
                System.out.print(c);

            } catch (EOFException e) {
                System.out.println();
                break;
            }
        }

二进制文件如下，你会发现第一个字节没有用到，很多 00000000 所以如果存储英文 byte 更适合，char 是双倍 byte 开销。

neo@MacBook-Pro ~/workspace/netkiller % xxd -c 8 -b netkiller.bin
00000000: 00000000 01101000 00000000 01110100 00000000 01110100 00000000 01110000  .h.t.t.p
00000008: 00000000 00111010 00000000 00101111 00000000 00101111 00000000 01110111  .:././.w
00000010: 00000000 01110111 00000000 01110111 00000000 00101110 00000000 01101110  .w.w...n
00000018: 00000000 01100101 00000000 01110100 00000000 01101011 00000000 01101001  .e.t.k.i
00000020: 00000000 01101100 00000000 01101100 00000000 01100101 00000000 01110010  .l.l.e.r
00000028: 00000000 00101110 00000000 01100011 00000000 01101110                    ...c.n

存储汉字

        String filename = "netkiller.bin";
        DataOutputStream out = new DataOutputStream(new FileOutputStream(filename));
        String s = "陈";
        char name = s.charAt(s.length() - 1);
        out.writeChar(name);
        out.close();

        DataInputStream in = new DataInputStream(new BufferedInputStream(new FileInputStream(filename)));

        char c = ' ';
        while (true) {
            try {
                c = in.readChar();
                System.out.print(c);

            } catch (EOFException e) {
                System.out.println();
                break;
            }
        }

二进制文件如下，使用两个字节表示一个汉字

neo@MacBook-Pro ~/workspace/netkiller % xxd -c 2 -b netkiller.bin
00000000: 10010110 01001000  .H

转成 Hex 十六进制，得到 96 48 两个数字。

neo@MacBook-Pro ~/workspace/netkiller % hexdump  netkiller.bin
0000000 96 48
0000002

现在去搜索引擎搜索“汉字内码”，然后查询“陈”这个汉字，可以看到 Unicode编码16进制就是 96 48

尝试写入汉字字符串

        String filename = "netkiller.bin";
        DataOutputStream out = new DataOutputStream(new FileOutputStream(filename));
        out.writeChars("陈景峰");
        out.close();

        DataInputStream in = new DataInputStream(new BufferedInputStream(new FileInputStream(filename)));
        try {
            char c = ' ';
            while (true) {
                try {
                    c = in.readChar();
                    System.out.print(c);

                } catch (EOFException e) {
                    System.out.println();
                    break;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

neo@MacBook-Pro ~/workspace/netkiller % xxd -b netkiller.bin
00000000: 10010110 01001000 01100110 01101111 01011100 11110000  .Hfo\.

1.10.12.6. UTF 字符串

这次我们使用新的文件名 netkiller.txt

        String filename = "netkiller.txt";
        DataOutputStream out = new DataOutputStream(new FileOutputStream(filename));

        out.writeUTF("峰");

        out.close();

        DataInputStream in = new DataInputStream(new BufferedInputStream(new FileInputStream(filename)));
        try {
            System.out.println(in.readUTF());
        } catch (EOFException e) {
            e.printStackTrace();
        }

查看二进制文件，一个汉字怎么这么多字节？

neo@MacBook-Pro ~/workspace/netkiller % xxd -b netkiller.txt
00000000: 00000000 00000011 11100101 10110011 10110000           .....

转成 16 禁止看看。

neo@MacBook-Pro ~/workspace/netkiller % hexdump netkiller.txt
0000000 00 03 e5 b3 b0
0000005

我们在网上查询 “峰” 字的汉字内码，可以看到UTF-8 内码是 E5 B3 B0。这是因为UTF8使用三个字节存储汉字。 00000000 00000011 可能是 UTF 标志位，具体我也不太清楚，总之不是 BOM 信息。

我们现在写入一个字符串试试

        out.writeUTF("陈景峰");

xxd -s 2 -c 3 表示跳过两个字节，三列显示

neo@MacBook-Pro ~/workspace/netkiller % xxd -s 2 -c 3 -b netkiller.txt
00000002: 11101001 10011001 10001000  ...
00000005: 11100110 10011001 10101111  ...
00000008: 11100101 10110011 10110000  ...

UTF字符是可以直接使用文本工具查看的。

neo@MacBook-Pro ~/workspace/netkiller % cat netkiller.txt
    陈景峰

1.10.12.7. Short 类型

        String filename = "netkiller.bin";
        DataOutputStream out = new DataOutputStream(new FileOutputStream(filename));

        out.writeShort(1);

        out.flush();
        out.close();

输出结果，Short 使用两个字节16位表示。

neo@MacBook-Pro ~/workspace/netkiller % xxd -c 2 -b netkiller.bin
00000000: 00000000 00000001  ..

Short 分为有符号和无符号类型

        String filename = "netkiller.bin";
        DataOutputStream out = new DataOutputStream(new FileOutputStream(filename));

        out.writeShort(1);
        out.writeShort(1);
        out.writeShort(-1);
        out.writeShort(-1);

        out.flush();
        out.close();

        DataInputStream in = new DataInputStream(new BufferedInputStream(new FileInputStream(filename)));
        try {
            System.out.println(in.readShort());
            System.out.println(in.readUnsignedShort());
            System.out.println(in.readShort());
            System.out.println(in.readUnsignedShort());
        } catch (EOFException e) {
            e.printStackTrace();
        }

运行结果

1
1
-1
65535

有符号的取值范围

最小值：Short.MIN_VALUE=-32768 （-2的15此方）
最大值：Short.MAX_VALUE=32767 （2的15次方-1）

无符号的取值范围是 0 ~ 65535

1.10.12.8. float 单精度浮点类型

        String filename = "netkiller.bin";
        DataOutputStream out = new DataOutputStream(new FileOutputStream(filename));

        out.writeFloat(0);
        out.writeFloat(1.0f);
        out.writeFloat(1.1f);
        out.flush();
        out.close();

        DataInputStream in = new DataInputStream(new BufferedInputStream(new FileInputStream(filename)));

        float c = 0;
        while (true) {
            try {
                c = in.readFloat();
                System.out.println(c);

            } catch (EOFException e) {
                System.out.println();
                break;
            }
        }

float 使用 4 字节 32 为表示浮点类型，float 不同于前面数据类型，无法直接读取浮点数，需要经过计算才能得出，有点复杂。

neo@MacBook-Pro ~/workspace/netkiller % xxd -c 4 -b netkiller.bin
00000000: 00000000 00000000 00000000 00000000  ....
00000004: 00111111 10000000 00000000 00000000  ?...
00000008: 00111111 10001100 11001100 11001101  ?...

浮点型示意图

 /------------- 32 bit ----------------\
 | 1 |     8  |           23            |
 |--------------------------------------|
 31 30        22                        0
  ^       ^               ^
符号位   指数位     	    尾数部分         32位

首先float二进制是从后向前读。与上面所有类型相反。
符号位(Sign) : 0代表正，1代表为负
指数位（Exponent）:用于存储科学计数法中的指数数据，并且采用移位存储
尾数部分（Mantissa）：尾数部分

将一个内存存储的float二进制格式转化为十进制的步骤：
（1）将第22位到第0位的二进制数写出来，在最左边补一位“1”，得到二十四位有效数字。将小数点点在最左边那个“1”的右边。
（2）取出第29到第23位所表示的值n。当30位是“0”时将n各位求反。当30位是“1”时将n增1。
（3）将小数点左移n位（当30位是“0”时）或右移n位（当30位是“1”时），得到一个二进制表示的实数。
（4）将这个二进制实数化为十进制，并根据第31位是“0”还是“1”加上正号或负号即可。

1.0f = 00111111 10000000 00000000 00000000

Sign 31 位是 0 表示正数
Exponent 23~30 位 0111111 1
Mantissa 0~22 位 0000000 00000000 00000000

得到

| 0 | 0111111 1 | 0000000 00000000 00000000 |

具体细节请参考 IEEE R32.24

1.10.12.9. double 数据类型

        String filename = "netkiller.bin";
        DataOutputStream out = new DataOutputStream(new FileOutputStream(filename));
        out.writeDouble(12.5d);
        out.flush();
        out.close();

        DataInputStream in = new DataInputStream(new BufferedInputStream(new FileInputStream(filename)));

        double d = 0d;
        while (true) {
            try {
                d = in.readDouble();
                System.out.println(d);

            } catch (EOFException e) {
                System.out.println();
                break;
            }
        }

二进制文件

neo@MacBook-Pro ~/workspace/netkiller % xxd -c 8 -b netkiller.bin
00000000: 01000000 00101001 00000000 00000000 00000000 00000000 00000000 00000000  @)......

 /------------------------- 64 bit ------------------------------\
 | 1 |    11    |           52                                    |
 |----------------------------------------------------------------|
 63 62          51                                                0
  ^         ^                  ^
符号位     指数位       	    尾数部分                              64位

首先float二进制是从后向前读。与上面所有类型相反。
符号位(Sign) : 0代表正，1代表为负
指数位（Exponent）:用于存储科学计数法中的指数数据，并且采用移位存储
尾数部分（Mantissa）：尾数部分

详细参加考 IEEE R64.53

1.10.12.10. 二进制文件操作演示

1.10.12.10.1. 所有类型演示一遍

        String filename = "netkiller.bin";
        DataOutputStream out = new DataOutputStream(new FileOutputStream(filename));
        out.writeInt(1024);
        out.writeShort(255);
        out.writeLong(100000000000L);
        out.writeFloat(3.14f);
        out.writeDouble(3.141592653579d);
        out.writeBoolean(true);
        out.writeChar(165);
        out.writeChars("陈景峰");
        out.writeUTF("Netkiller Java 手札 - http://www.netkiller.cn");
        out.writeChars("这是最后一行\r\n");
        out.flush();
        out.close();

        DataInputStream in = new DataInputStream(new BufferedInputStream(new FileInputStream(filename)));

        System.out.println(in.readInt());
        System.out.println(in.readUnsignedShort());
        System.out.println(in.readLong());
        System.out.println(in.readFloat());
        System.out.println(in.readDouble());
        System.out.println(in.readBoolean());
        System.out.println(in.readChar());

        int i = 0;
        String name = "";
        while (i < 3) {
            try {
                char c = in.readChar();
                name += c;
            } catch (EOFException e) {
                break;
            }
            i++;
        }

        System.out.println(name);
        System.out.println(in.readUTF());
        System.out.println(in.readUTF());

需要注意的一点是 out.writeChars("陈景峰"); 写入char字符串，在读取的时候你需要知道字符串的长度。然后循环取出char数据。

二进制文件内容

neo@MacBook-Pro ~/workspace/netkiller % xxd -c 8 -b netkiller.bin
00000000: 00000000 00000000 00000100 00000000 00000000 11111111 00000000 00000000  ........
00000008: 00000000 00010111 01001000 01110110 11101000 00000000 01000000 01001000  ..Hv..@H
00000010: 11110101 11000011 01000000 00001001 00100001 11111011 01010100 01000011  ..@.!.TC
00000018: 11001110 00101000 00000001 00000000 10100101 10010110 01001000 01100110  .(....Hf
00000020: 01101111 01011100 11110000 00000000 00101111 01001110 01100101 01110100  o\../Net
00000028: 01101011 01101001 01101100 01101100 01100101 01110010 00100000 01001010  killer J
00000030: 01100001 01110110 01100001 00100000 11100110 10001001 10001011 11100110  ava ....
00000038: 10011100 10101101 00100000 00101101 00100000 01101000 01110100 01110100  .. - htt
00000040: 01110000 00111010 00101111 00101111 01110111 01110111 01110111 00101110  p://www.
00000048: 01101110 01100101 01110100 01101011 01101001 01101100 01101100 01100101  netkille
00000050: 01110010 00101110 01100011 01101110 10001111 11011001 01100110 00101111  r.cn..f/
00000058: 01100111 00000000 01010100 00001110 01001110 00000000 10001000 01001100  g.T.N..L
00000060: 00000000 00001101 00000000 00001010                                      ....

16 进制编辑器更好阅读一些

neo@MacBook-Pro ~/workspace/netkiller % hexdump -C netkiller.bin
00000000  00 00 04 00 00 ff 00 00  00 17 48 76 e8 00 40 48  |..........Hv..@H|
00000010  f5 c3 40 09 21 fb 54 43  ce 28 01 00 a5 96 48 66  |..@.!.TC.(....Hf|
00000020  6f 5c f0 00 2f 4e 65 74  6b 69 6c 6c 65 72 20 4a  |o\../Netkiller J|
00000030  61 76 61 20 e6 89 8b e6  9c ad 20 2d 20 68 74 74  |ava ...... - htt|
00000040  70 3a 2f 2f 77 77 77 2e  6e 65 74 6b 69 6c 6c 65  |p://www.netkille|
00000050  72 2e 63 6e 8f d9 66 2f  67 00 54 0e 4e 00 88 4c  |r.cn..f/g.T.N..L|
00000060  00 0d 00 0a                                       |....|
00000064
1.10.12.10.2. 检查文件是否是 png 文件

package cn.netkiller.io;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;

public class PngFile {

    private static InputStream inputStream;

    public PngFile() {
    }

    public static void main(String[] args) throws FileNotFoundException {

        int[] pngSignature = { 137, 80, 78, 71, 13, 10, 26, 10 };

        try {
            inputStream = new FileInputStream("/Users/neo/Downloads/NBRC.png");

            byte[] headerBytes = new byte[8];
            inputStream.read(headerBytes);

            // for (byte b : headerBytes) {
            // System.out.println(b);
            // }

            int[] pngHeader = new int[8];

            for (int i = 0; i < headerBytes.length; i++) {
                pngHeader[i] = (int) headerBytes[i] & 0xff;
            }

            if (Arrays.equals(pngHeader, pngSignature)) {
                System.out.println("This is a PNG file! ");
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

}


1.11. Reflection 反射

this.getClass().getName() //当前Class名字
Thread.currentThread().getStackTrace()[1].getMethodName()); //当前方法名

1.11.1. 获得所有变量

    Field[] fields = objClass.getFields();
    for (Field field : fields) {
        System.out.println(field.getName());
    }

注意：只能去除 public变量

1.11.2. 批量赋值

1.11.3. 方法操作

JAVA反射调用方法的步骤有三步

得到要调用类的class
得到要调用的类中的方法(Method)
方法调用(invoke)

1.11.3.1. 获得所有方法

    Class<?> objClass = a.getClass();
    Method[] methods =  objClass.getDeclaredMethods();
    for (Method method : methods) {
        System.out.println(method);
    }

1.11.3.2. set/get 方法

package cn.netkiller.reflect;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class Member {
    public String name;
    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "ClassA [name=" + name + ", age=" + age + "]";
    }

    public Member() {
        // TODO Auto-generated constructor stub
    }

    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, SecurityException, IllegalArgumentException, InvocationTargetException {
        Class<?> cls = Class.forName("cn.netkiller.reflect.Member");
        Object member = cls.newInstance();
        Method setMethod = cls.getDeclaredMethod("setAge", int.class);
        setMethod.invoke(member, 15);

        Method getMethod = cls.getDeclaredMethod("getAge");
        System.out.println(getMethod.invoke(member));

    }

}

下面做一个稍微复杂点的例子，ClassB继承ClassA，取出ClassA的成员变量赋值到ClassA。

package cn.netkiller.reflect;

public class ClassA {
    public String name;
    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public ClassA() {
        // TODO Auto-generated constructor stub
    }

    @Override
    public String toString() {
        return "ClassA [name=" + name + ", age=" + age + "]";
    }
}

package cn.netkiller.reflect;

public class ClassB extends ClassA{

    public ClassB() {
        // TODO Auto-generated constructor stub
    }
    private String address;

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    @Override
    public String toString() {
        return "ClassB [address=" + address + "]";
    }

}

package cn.netkiller.reflect;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class ReflectionTest {

    public ReflectionTest() {
        // TODO Auto-generated constructor stub
    }

    public void testSetMethod() throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, InstantiationException {

        // ClassA a = new ClassA();

        ClassB b = new ClassB();
        b.setAddress("Shenzhen");

        Class<ClassA> classA = ClassA.class;
        ClassA a = classA.newInstance();
        a.setName("Neo");
        a.setAge(30);

        System.out.println(classA.getDeclaredMethod("getAge").invoke(a));

        Method m = classA.getDeclaredMethod("setAge", int.class);
        m.setAccessible(true); // 因为写成private 所以这里必须设置
        m.invoke(b, 26);

        System.out.println(a.toString());
        System.out.println(b.toString());

        System.out.println(b.getName());
        System.out.println(b.getAge());
    }

    public static void main(String[] args) throws InvocationTargetException {

        ReflectionTest rt = new ReflectionTest();
        try {
            rt.testSetMethod();

        } catch (NoSuchMethodException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (SecurityException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (InstantiationException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }

}

set 方法

System.out.println(classA.getDeclaredMethod("getAge").invoke(a));

get 方法

    Method m = classA.getDeclaredMethod("setAge", int.class);
    m.setAccessible(true);	//因为写成private 所以这里必须设置
    m.invoke(b, 26);

1.11.3.3. static 方法调用

Class cls = Class.forName("cn.netkiller.reflect.Student");
Method setMethod = cls.getDeclaredMethod("setAge",int.class);
setMethod.invoke(cls.newInstance(), 15);

static 方法调用时，不必得实例化对象

Class cls = Class.forName("cn.netkiller.reflect.Student");
Method staticMethod = cls.getDeclaredMethod("setAge",int.class);
staticMethod.invoke(cls,20); //这里不需要newInstance


1.12. Thread 线程

1.12.1. 实现异步执行

    public void testThread() throws Exception {
        try {
            Thread sendmail = new Thread(new Runnable() {
                @Override
                public void run() {
                    // Sendmail
                    log.info("Sendmail OK");
                }
            });
            sendmail.setName("sendmail");
            sendmail.start();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

1.12.2. 继承 Thread 类实现多线程

package cn.netkiller.ipo.test;

public class MyThread extends Thread {

    private String name;

    public MyThread(String name) {
        super();
        this.name = name;
    }

    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("Thread:" + this.name + ",i=" + i);
        }
    }

    public static void main(String[] args) {
        MyThread mt1 = new MyThread("A");
        MyThread mt2 = new MyThread("B");
        mt1.start();
        mt2.start();
    }
}

1.12.3. 实现 Runnable 接口

package cn.netkiller.ipo.test;

public class MyRunnable implements Runnable {

    private String name;

    public MyRunnable(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("Thread:" + this.name + ",i=" + i);
        }

    }

    public static void main(String[] args) {

        MyRunnable mr1 = new MyRunnable("A");
        MyRunnable mr2 = new MyRunnable("B");

        new Thread(mr1).start();
        new Thread(mr2).start();
        new Thread(new MyRunnable("C")).start();
    }
}

1.12.4. 线程同步

package cn.netkiller.thread;

public class SynchronizedThread extends Thread {
    private int count = 0;

    @Override
    public /*synchronized*/ void run() {
        count++;
        System.out.println(Thread.currentThread().getName() + " count:" + count);
    }

    public static void main(String[] args) {
        SynchronizedThread myThread = new SynchronizedThread();
        Thread thread1 = new Thread(myThread, "thread1");
        Thread thread2 = new Thread(myThread, "thread2");
        Thread thread3 = new Thread(myThread, "thread3");
        Thread thread4 = new Thread(myThread, "thread4");
        Thread thread5 = new Thread(myThread, "thread5");
        thread1.start();
        thread2.start();
        thread3.start();
        thread4.start();
        thread5.start();
    }
}

线程运行不分先后

thread2 count:3
thread4 count:4
thread1 count:3
thread3 count:3
thread5 count:5

package cn.netkiller.thread;

public class SynchronizedThread extends Thread {
    private int count = 0;

    @Override
    public synchronized void run() {
        count++;
        System.out.println(Thread.currentThread().getName() + " count:" + count);
    }

    public static void main(String[] args) {
        SynchronizedThread myThread = new SynchronizedThread();
        Thread thread1 = new Thread(myThread, "thread1");
        Thread thread2 = new Thread(myThread, "thread2");
        Thread thread3 = new Thread(myThread, "thread3");
        Thread thread4 = new Thread(myThread, "thread4");
        Thread thread5 = new Thread(myThread, "thread5");
        thread1.start();
        thread2.start();
        thread3.start();
        thread4.start();
        thread5.start();
    }
}

thread1 count:1
thread5 count:2
thread4 count:3
thread2 count:4
thread3 count:5

package cn.netkiller.thread;

public class MultiThread {
    private static int count = 0;

    public synchronized void add() {
        count++;
        System.out.println(Thread.currentThread().getName() + " count:" + count);
    }

    public static void main(String[] args) throws InterruptedException {

        final MultiThread multiThread1 = new MultiThread();
        final MultiThread multiThread2 = new MultiThread();
        final MultiThread multiThread3 = new MultiThread();
        final MultiThread multiThread4 = new MultiThread();
        final MultiThread multiThread5 = new MultiThread();

        new Thread(new Runnable() {
            public void run() {
                multiThread1.add();
            }
        }).start();

        new Thread(new Runnable() {
            public void run() {
                multiThread2.add();
            }
        }).start();

        new Thread(new Runnable() {
            public void run() {
                multiThread3.add();
            }
        }).start();

        new Thread(new Runnable() {
            public void run() {
                multiThread4.add();
            }
        }).start();

        new Thread(new Runnable() {
            public void run() {
                multiThread5.add();
            }
        }).start();
    }
}


1.13. java 脚本引擎

什么是脚本引擎，脚本引擎是指在程序运行期间嵌入另一种脚本语言，并与其交互，产生最终运行结果

脚本引擎存在的意义是什么？脚本引擎可以改变编译语言的内部运行逻辑，弥补编译语言的不足，使编译语言具备动态语言的一部分特性。

是否有成功案例？最成功的案例就是基于C++和Lua语言开发的端游（网游一种，需要按照客户端），编译语言最大的缺点就是客户端升级需要重新安装并且安装之后重启应用程序才能生效。脚本引擎弥补了这项致命的缺点，用户只需升级剧情脚本，而不需要退出整个游戏然后重新进入。

1.13.1. Maven

<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>cn.netkiller</groupId>
    <artifactId>script</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>Java Script</name>
    <description>Java Script Engine</description>
    <dependencies>
        <!-- https://mvnrepository.com/artifact/org.mockito/mockito-all -->
        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-all</artifactId>
            <version>1.10.19</version>
        </dependency>
    </dependencies>
    <build>
        <sourceDirectory>src</sourceDirectory>
        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.5.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

1.13.2. Helloworld

package cn.netkiller.javascript;

import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.script.ScriptEngine;
import javax.script.ScriptEngineFactory;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;

public class Helloworld {

    public Helloworld() {
        ScriptEngineManager manager = new ScriptEngineManager();
        List<ScriptEngineFactory> factories = manager.getEngineFactories();
        for (ScriptEngineFactory f : factories) {
            System.out.println("egine name:" + f.getEngineName());
            System.out.println("engine version:" + f.getEngineVersion());
            System.out.println("language name:" + f.getLanguageName());
            System.out.println("language version:" + f.getLanguageVersion());
            System.out.println("names:" + f.getNames());
            System.out.println("mime:" + f.getMimeTypes());
            System.out.println("extension:" + f.getExtensions());
            System.out.println("-----------------------------------------------");
        }
    }

    public void hello() throws ScriptException {
        ScriptEngineManager manager = new ScriptEngineManager();
        ScriptEngine engine = manager.getEngineByName("JavaScript");
        // ScriptEngine engine = manager.getEngineByExtension("js");
        // ScriptEngine engine = manager.getEngineByMimeType("text/javascript");
        engine.eval("print('Hello, World')");
    }

    public static void main(String[] args) {
        try {
            new Helloworld().hello();
        } catch (ScriptException ex) {
            Logger.getLogger(Helloworld.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
}

1.13.3. 运行脚本文件

将脚本放入外部文件

package javascript;

import java.io.FileNotFoundException;
import java.net.URL;

import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;

public class EvalFile {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        ScriptEngineManager manager = new ScriptEngineManager();
        ScriptEngine engine = manager.getEngineByExtension("js");
        // ScriptEngine engine = manager.getEngineByMimeType("text/javascript");

        try {

            URL location = EvalFile.class.getProtectionDomain().getCodeSource().getLocation();
            String file = location.getFile() + "test.js";
            System.out.println(file);

            engine.eval(new java.io.FileReader(file));

        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (ScriptException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

}

test.js

print("This is hello from test.js");

1.13.4. 变量传递

package javascript;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import javax.script.Bindings;
import javax.script.ScriptContext;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import javax.script.SimpleBindings;

public class ScriptVars {

    ScriptEngine engine = null;

    public ScriptVars() {
        ScriptEngineManager manager = new ScriptEngineManager();
        engine = manager.getEngineByMimeType("text/javascript");
    }

    public void variable() throws ScriptException {
        engine.put("name", "Neo");
        engine.eval("var message = 'Hello, ' + name;");
        engine.eval("print(message);");
        Object obj = engine.get("message");
        System.out.println(obj);
    }

    public void simpleBindings() throws ScriptException {
        Bindings bindings = new SimpleBindings();
        bindings.put("name", "Neo");
        engine.eval("print('I am ' + name);", bindings);
    }

    public void function() throws ScriptException {
        engine.put("a", 4);
        engine.put("b", 6);
        Object maxNum = engine.eval("function max_num(a,b){return (a>b)?a:b;}max_num(a,b);");
        System.out.println("max_num:" + maxNum + ", (class = " + maxNum.getClass() + ")");

        Map<String, Integer> m = new HashMap<String, Integer>();
        m.put("c", 10);
        engine.put("m", m);
        engine.eval("var x= max_num(a,m.get('c'));");
        System.out.println("max_num:" + engine.get("x"));
    }

    public void object() throws ScriptException {
        File f = new File("test.txt");
        // expose File object as variable to script
        engine.put("file", f);

        // evaluate a script string. The script accesses "file"
        // variable and calls method on it
        engine.eval("print(file.getAbsolutePath())");
    }

    public void outputToFile() throws IOException, ScriptException {
        ScriptContext context = engine.getContext();
        context.setWriter(new FileWriter("script.log"));
        engine.eval("print('Hello World!');");
    }

    public static void main(String[] args) {
        // TODO Auto-generated method stub

        try {
            ScriptVars sv = new ScriptVars();
            sv.variable();
            sv.simpleBindings();
            sv.outputToFile();
            sv.function();
            sv.object();
            sv.outputToFile();

        } catch (ScriptException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

}

1.13.5. 全局变量与局部变量定义

ScriptContext.GLOBAL_SCOPE 作用于 ScriptEngineManager， ScriptContext.ENGINE_SCOPE 作用于 ScriptEngine

package javascript;

import javax.script.ScriptContext;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;

public class ContextVariable {

    public static void main(String[] args) throws ScriptException {
        // TODO Auto-generated method stub
        ScriptEngineManager manager = new ScriptEngineManager();
        ScriptEngine engine = manager.getEngineByName("JavaScript");
        ScriptContext context = engine.getContext();
        context.setAttribute("name", "Netkiller", ScriptContext.GLOBAL_SCOPE);
        context.setAttribute("name", "Neo", ScriptContext.ENGINE_SCOPE);
        //context.getAttribute("name");
        engine.eval("print('I am ' + name);", context);

        // engine1,context1 并没有定义 name 但输出结果却显示 Netkiller，所以ScriptContext.GLOBAL_SCOPE定义的变量是全局的。
        ScriptEngine engine1 = manager.getEngineByName("JavaScript");
        ScriptContext context1 = engine1.getContext();
        engine.eval("print('I am ' + name);", context1);

    }

}

import javax.script.*;

public class MultiScopes {
    public static void main(String[] args) throws Exception {
        ScriptEngineManager manager = new ScriptEngineManager();
        ScriptEngine engine = manager.getEngineByName("JavaScript");

        engine.put("x", "hello");
        // print global variable "x"
        engine.eval("print(x);");
        // the above line prints "hello"

        // Now, pass a different script context
        ScriptContext newContext = new SimpleScriptContext();
        Bindings engineScope = newContext.getBindings(ScriptContext.ENGINE_SCOPE);

        // add new variable "x" to the new engineScope
        engineScope.put("x", "world");

        // execute the same script - but this time pass a different script context
        engine.eval("print(x);", newContext);
        // the above line prints "world"
    }
}

1.13.6. 调用脚本中的函数或方法

package javascript;

import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;

public class InvokeScriptFunction {

    public static void main(String[] args) throws ScriptException, NoSuchMethodException {
        // TODO Auto-generated method stub
        ScriptEngineManager manager = new ScriptEngineManager();
        ScriptEngine engine = manager.getEngineByName("JavaScript");

        // JavaScript code in a String
        String script = "function hello(name) { print('Hello, ' + name); }";
        // evaluate script
        engine.eval(script);

        // javax.script.Invocable is an optional interface.
        // Check whether your script engine implements or not!
        // Note that the JavaScript engine implements Invocable interface.
        Invocable inv = (Invocable) engine;

        // invoke the global function named "hello"
        inv.invokeFunction("hello", "Scripting!!");

        // JavaScript code in a String. This code defines a script object 'obj'
        // with one method called 'hello'.
        script = "var obj = new Object(); obj.hello = function(name) { print('Hello, ' + name); }";
        // evaluate script
        engine.eval(script);
        // get script object on which we want to call the method
        Object obj = engine.get("obj");

        // invoke the method named "hello" on the script object "obj"
        inv.invokeMethod(obj, "hello", "Script Method !!");
    }
}

1.13.7. 脚本编译

只有重复执行脚本时才需要编译。只运行一次不建议编译运行。

package javascript;

import javax.script.*;

public class ScriptCompile {
    public CompiledScript compile(String script) throws ScriptException {

        ScriptEngineManager manager = new ScriptEngineManager();
        ScriptEngine engine = manager.getEngineByName("JavaScript");

        return ((Compilable) engine).compile(script);

    }

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        ScriptCompile sc = new ScriptCompile();
        try {

            CompiledScript script = sc.compile("print('Helloworld!!!');");

            for (int i = 0; i < 100; i++) {
                script.eval();
            }

        } catch (ScriptException ex) {
            ex.printStackTrace();
        }
    }

}

1.13.8. jjs - Invokes the Nashorn engine.

jjs 是一个在命令行运行脚本的命令

创建脚本文件

[neo@netkiller tmp]$ cat test.js
function f() {
     return 1;
};

print( f() + 1 );

运行结果

[neo@netkiller tmp]$ jjs test.js
2


1.14. Crypto

1.14.1. MD5

JDK 1.8

            String hash = DatatypeConverter.printHexBinary(MessageDigest.getInstance("MD5").digest("SOMESTRING".getBytes("UTF-8")));

1.14.2. AES

package cn.netkiller.crypto;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.MessageDigest;
import java.security.SecureRandom;

public class TestAES {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        String key = "fm6I1D2HTFVVOWUKny76TThagNq5Czrv";
        String clean = "Helloworld!!!";

        try {
            byte[] encrypted = encrypt(clean, key);
            String decrypted = decrypt(encrypted, key);
            System.out.println(decrypted);
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }

    public static byte[] encrypt(String plainText, String key) throws Exception {
        byte[] clean = plainText.getBytes();

        // Generating IV.
        int ivSize = 16;
        byte[] iv = new byte[ivSize];
        SecureRandom random = new SecureRandom();
        random.nextBytes(iv);
        IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);

        // Hashing key.
        MessageDigest digest = MessageDigest.getInstance("SHA-256");
        digest.update(key.getBytes("UTF-8"));
        byte[] keyBytes = new byte[16];
        System.arraycopy(digest.digest(), 0, keyBytes, 0, keyBytes.length);
        SecretKeySpec secretKeySpec = new SecretKeySpec(keyBytes, "AES");

        // Encrypt.
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);
        byte[] encrypted = cipher.doFinal(clean);

        // Combine IV and encrypted part.
        byte[] encryptedIVAndText = new byte[ivSize + encrypted.length];
        System.arraycopy(iv, 0, encryptedIVAndText, 0, ivSize);
        System.arraycopy(encrypted, 0, encryptedIVAndText, ivSize, encrypted.length);

        return encryptedIVAndText;
    }

    public static String decrypt(byte[] encryptedIvTextBytes, String key) throws Exception {
        int ivSize = 16;
        int keySize = 16;

        // Extract IV.
        byte[] iv = new byte[ivSize];
        System.arraycopy(encryptedIvTextBytes, 0, iv, 0, iv.length);
        IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);

        // Extract encrypted part.
        int encryptedSize = encryptedIvTextBytes.length - ivSize;
        byte[] encryptedBytes = new byte[encryptedSize];
        System.arraycopy(encryptedIvTextBytes, ivSize, encryptedBytes, 0, encryptedSize);

        // Hash key.
        byte[] keyBytes = new byte[keySize];
        MessageDigest md = MessageDigest.getInstance("SHA-256");
        md.update(key.getBytes());
        System.arraycopy(md.digest(), 0, keyBytes, 0, keyBytes.length);
        SecretKeySpec secretKeySpec = new SecretKeySpec(keyBytes, "AES");

        // Decrypt.
        Cipher cipherDecrypt = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipherDecrypt.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec);
        byte[] decrypted = cipherDecrypt.doFinal(encryptedBytes);

        return new String(decrypted);
    }

}

1.14.3. AES/CBC/PKCS5PADDING

package cn.netkiller.security;

import java.util.Base64;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

public class AES {
    private static final String initVector = "encryptionIntVec";
    private String key;

    public AES(String key) {
        // TODO Auto-generated constructor stub
        this.key = key;
    }

    public String encrypt(String value) {
        return this.encrypt(value, this.key);
    }

    public String encrypt(String value, String key) {
        try {
            IvParameterSpec ivParameterSpec = new IvParameterSpec(initVector.getBytes("UTF-8"));
            SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");

            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
            cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);

            byte[] encrypted = cipher.doFinal(value.getBytes());
            return Base64.getEncoder().encodeToString(encrypted);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return null;
    }

    public String decrypt(String encrypted) {
        return this.decrypt(encrypted, this.key);
    }

    public String decrypt(String encrypted, String key) {
        try {
            IvParameterSpec ivParameterSpec = new IvParameterSpec(initVector.getBytes("UTF-8"));
            SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");

            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
            cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec);
            byte[] original = cipher.doFinal(Base64.getDecoder().decode(encrypted));

            return new String(original);
        } catch (Exception ex) {
            ex.printStackTrace();
        }

        return null;
    }

    public static void main(String[] args) {
        // key 长度16个字节
        String key = "www.netkiller.cn";
        System.out.println(key.length());
        AES aes = new AES(key);
        String en = aes.encrypt("Helloworld!!!");
        String de = aes.decrypt(en);
        System.out.println(en);
        System.out.println(de);

    }
}

1.14.4. DES

package cn.netkiller.security;

import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.Base64;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;

public class DES {

    public DES() {
        // TODO Auto-generated constructor stub
    }

    public static String encrypt(String text, String password) {
        try {
            SecureRandom random = new SecureRandom();
            DESKeySpec desKey = new DESKeySpec(password.getBytes());
            // 创建一个密匙工厂，然后用它把DESKeySpec转换成
            SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
            SecretKey securekey = keyFactory.generateSecret(desKey);
            // Cipher对象实际完成加密操作
            Cipher cipher = Cipher.getInstance("DES");
            // 用密匙初始化Cipher对象
            cipher.init(Cipher.ENCRYPT_MODE, securekey, random);
            // 现在，获取数据并加密
            // 正式执行加密操作

            return Base64.getEncoder().encodeToString(cipher.doFinal(text.getBytes(StandardCharsets.UTF_8)));
        } catch (Throwable e) {
            e.printStackTrace();
        }
        return null;
    }

    private static String decrypt(String text, String password) throws Exception {
        try {
            // DES算法要求有一个可信任的随机数源
            SecureRandom random = new SecureRandom();
            // 创建一个DESKeySpec对象
            DESKeySpec desKey = new DESKeySpec(password.getBytes());
            // 创建一个密匙工厂
            SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
            // 将DESKeySpec对象转换成SecretKey对象
            SecretKey securekey = keyFactory.generateSecret(desKey);
            // Cipher对象实际完成解密操作
            Cipher cipher = Cipher.getInstance("DES");
            // 用密匙初始化Cipher对象
            cipher.init(Cipher.DECRYPT_MODE, securekey, random);
            // 真正开始解密操作
            return new String(cipher.doFinal(Base64.getDecoder().decode(text)), StandardCharsets.UTF_8);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public static void main(String[] args) throws Exception {
        // TODO Auto-generated method stub
        String en = DES.encrypt("Helloworld!!!", "www.netkiller.cn");
        String de = DES.decrypt(en, "www.netkiller.cn");
        System.out.println(en);
        System.out.println(de);

    }

}


1.15. java.security

1.15.1. 列出 Java 支持的数字摘要算法

package cn.netkiller.security;

import java.security.Security;
import java.util.Set;

public class ListMessageDigest {

    public static void main(String[] args) {

        // List of available MessageDigest Algorithms
        Set<String> messageDigest = Security.getAlgorithms("MessageDigest");
        messageDigest.forEach(x -> System.out.println(x));

    }

}

运行结果

SHA3-512
SHA-384
SHA
SHA3-384
SHA-224
SHA-512/256
SHA-256
MD2
SHA-512/224
SHA3-256
SHA-512
MD5
SHA3-224

1.15.2. 计算文件的 MD5，SHA 等 HASH 值

package cn.netkiller.security;

import java.io.FileInputStream;
import java.io.IOException;
import java.security.DigestInputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class sha1sum {

    public sha1sum() {
        // TODO Auto-generated constructor stub
    }

    public static void main(String[] args) throws NoSuchAlgorithmException, IOException {

        String hex = checksum("/etc/hosts");
        System.out.println(hex);
    }

    private static String checksum(String filepath) throws IOException, NoSuchAlgorithmException {

        MessageDigest md = MessageDigest.getInstance("SHA"); // SHA, MD2, MD5, SHA-256, SHA-384...
        // file hashing with DigestInputStream
        try (DigestInputStream dis = new DigestInputStream(new FileInputStream(filepath), md)) {
            while (dis.read() != -1)
                ; // empty loop to clear the data
            md = dis.getMessageDigest();
        }

        // bytes to hex
        StringBuilder result = new StringBuilder();
        for (byte b : md.digest()) {
            result.append(String.format("%02x", b));
        }
        return result.toString();

    }

}


第 2 章 Build Tools

目录

2.1. Apache Ant
2.1.1. 安装 ant
2.1.1.1. 1.8
2.1.1.2. 1.10.1
2.1.2. ANT
2.1.2.1. ant.project.name
2.1.2.2. 定义
2.1.3. Project
2.1.3.1. property
2.1.3.2. ant
2.1.3.3. environment
2.1.4. path
2.1.5. copy
2.1.6. javac
2.1.7. condition
2.1.8. exec
2.1.8.1. sshexec
2.1.9. if
2.1.10. macrodef
2.1.10.1. Git
2.1.10.2. Rsync
2.1.10.3. SSH
2.1.10.4. maven
2.1.11. Javascript
2.1.12. mail
2.1.13. basename
2.1.14. FAQ
2.1.14.1. warning: 'includeantruntime' was not set, defaulting to build.sysclasspath=last; set to false for repeatable builds
2.1.14.2. 调试 exec
2.2. Apache Ivy
2.2.1. Ivy Install
2.2.1.1. source code
2.2.1.2. apt-get
2.2.2. Test example
2.3. maven
2.3.1. 安装
2.3.1.1. Ubuntu
2.3.1.2. 源码安装
2.3.2. Maven 命令
2.3.2.1. help
2.3.2.2. archetype:create
2.3.2.3. clean
2.3.2.4. compile
2.3.2.5. test
2.3.2.6. package
2.3.2.7. install
2.3.2.8. war
2.3.2.9. exec
2.3.2.10. dependency
2.3.2.11. jar
2.3.2.12. help:describe
2.3.3. Maven 仓库
2.3.4. pom.xml
2.3.4.1. properties
2.3.4.2. repositories 仓库配置
2.3.4.3. dependencies
2.3.4.4. dependencyManagement
2.3.4.5. build
2.3.4.6. plugins
2.3.5. plugins
2.3.5.1. maven-compiler-plugin
2.3.5.2. maven-war-plugin
2.3.5.3. maven-antrun-plugin
2.3.5.4. maven-install-plugin
2.3.5.5. maven-surefire-plugin
2.3.5.6. maven-deploy-plugin
2.3.5.7. maven-jar-plugin
2.3.5.8. maven-dependency-plugin
2.3.5.9. spring-boot-maven-plugin
2.4. Gradle
2.4.1. 安装 Gradle
2.4.1.1. CentOS
2.4.1.2. Mac
2.4.2. gradle 命令
2.4.2.1. tasks 列出任务
2.4.3. build.gradle
2.4.3.1. repositories
2.4.3.2. dependencies
2.4.3.3. jar
2.4.4. gradle.properties
2.4.4.1. 列出 properties
2.4.4.2. 自定义 gradle.properties
2.4.4.3.
2.4.4.4. System.properties
2.4.5. Example

2.1. Apache Ant

http://ant.apache.org/

2.1.1. 安装 ant

2.1.1.1. 1.8

cd /usr/local/src
wget http://mirror.bjtu.edu.cn/apache//ant/binaries/apache-ant-1.8.1-bin.tar.gz
tar zxvf apache-ant-1.8.1-bin.tar.gz
mv apache-ant-1.8.1 /usr/local/
cd ..
ln -s apache-ant-1.8.1 apache-ant

ANT_HOME=/usr/local/apache-ant
export CLASSPATH=$CLASSPATH:$ANT_HOME/lib

2.1.1.2. 1.10.1

Netkiller OSCM 一键安装

curl -s https://raw.githubusercontent.com/oscm/shell/master/lang/java/ant/apache-ant-1.10.1.sh | bash

2.1.2. ANT

2.1.2.1. ant.project.name

ant.project.name 一般式定义在

<project name="www.netkiller.cn" default="compile" basedir="." xmlns:if="ant:if">
<echo>${ant.project.name}</echo>

我们希望从命令行传递这个值

<project default="compile" basedir="." xmlns:if="ant:if">
<echo>${ant.project.name}</echo>

你需要将 project 中的定义去掉才能从命令行传递

ant -Dant.project.name=www.netkiller.cn -f build.xml

你也可以从 build.properties 文件定义 ant.project.name

MacBook-Pro:deployment neo$ cat build.properties
ant.project.name=www.netkiller.cn

ant -f build.xml -propertyfile build.properties

2.1.2.2. 定义

2.1.3. Project

<description>project Name</description>

2.1.3.1. property

在 build.xml 中定义 property

<property name="src" value="src"/>
<property name="dest" value="classes"/>
<property name="hello" value="hello.jar"/>

引用 properties 文件

<property file="build.properties" />
<propety resource="build.properties"/>

设置系统的环境变量为前缀

<property environment="env"/>
<property name="tomcat.home" value="${env.CATALINA_HOME}"/>

命令行传值，使用-D参数是会覆盖build.xml中的先前定义的变量

$ ant --help | grep property
  -D<property>=<value>   use value for given property
  -propertyfile <name>   load all properties from file with -D

2.1.3.2. ant

Project name

${ant.project.name}

2.1.3.3. environment

<property environment="env"/>
<echo message="JAVA_HOME is set to = ${env.JAVA_HOME}" />

2.1.4. path

定义

    <path id="classpath">
        <fileset dir="${env.JAVA_HOME}/lib">
            <include name="*.jar" />
        </fileset>
        <fileset dir="${env.CATALINA_HOME}/lib">
            <include name="*.jar" />
        </fileset>
        <fileset dir="WebRoot/WEB-INF/lib" includes="*.jar"/>
    </path>

引用

    <javac srcdir="${src.dir}" destdir="${classes.dir}" source="1.5" target="1.5">
        <classpath refid="classpath" />
    </javac>

    <classpath>
        <path refid="classpath"/>
    </classpath>

2.1.5. copy

复制目录

    <copy todir="${basedir}/WebContent">
        <fileset dir="${basedir}/WebRoot" includes="**/*"/>
    </copy>

复制指定扩展名文件

    <copy todir="${dest}">
        <fileset dir="${src}">
            <include name="**/*.xml" />
            <include name="**/*.properties" />
        </fileset>
    </copy>

2.1.6. javac

    <path id="classpath">
        <fileset dir="${env.JAVA_HOME}/lib">
            <include name="*.jar" />
        </fileset>
        <fileset dir="${env.CATALINA_HOME}/lib">
            <include name="*.jar" />
        </fileset>
        <fileset dir="${project.dir}/WebRoot/WEB-INF/lib" includes="*.jar" />
    </path>

    <javac srcdir="${project.src}" destdir="${project.build}/WEB-INF/classes" debug="true" listfiles="true">
            <classpath refid="classpath" />
            <include name="**/*.java"/>
            <exclude name="**/*.xml"/>
            <exclude name="**/*.properties"/>
    </javac>

listfiles 显示编译文件列表

debug 显示调试信息，编译错误信息

2.1.7. condition

<?xml version="1.0"?>
<project name="test" default="doFoo" basedir=".">
  <property name="directory" value="/www/directory"/>

  <target name="doFoo" depends="dir.check" if="dir.exists">
    <echo>${directory} exists</echo>
  </target>

  <target name="doBar" depends="dir.check" unless="dir.exists">
    <echo>${directory} missing"</echo>
  </target>

  <target name="dir.check">
    <condition property="dir.exists">
      <available file="${directory}" type="dir"/>
    </condition>
  </target>
</project>

2.1.8. exec

<project name="{{ name }}" default="help" basedir=".">

  <property name="username" value="{{ username }}"/>
  <property name="host" value="{{ host }}"/>
  <property name="dir" value="/srv/{{ path }}/"/>

  <tstamp>
    <format property="TODAY_UK" pattern="yyyyMMddhhmmss" locale="en,UK"/>
  </tstamp>

  <target name="help" description="show available commands" >
    <exec executable="ant" dir="." failonerror="true">
      <arg value="-p"/>
    </exec>
  </target>

  <target name="deploy-to" description="show where we are deploying to" >
    <echo>${username}@${host}:${dir}</echo>
  </target>

  <target name="deploy" description="deploy usng rsync" >
    <exec executable="rsync" dir="." failonerror="true">
      <arg value="-r"/>
      <arg value="."/>
      <arg value="${username}@${host}:${dir}"/>
      <arg value="--exclude-from=rsync.excludes"/>
      <arg value="-v"/>
    </exec>
  </target>

  <target name="deploy-test" description="test deploy usng rsync with the dry run flag set" >
    <exec executable="rsync" dir="." failonerror="true">
      <arg value="-r"/>
      <arg value="."/>
      <arg value="${username}@${host}:${dir}"/>
      <arg value="--exclude-from=rsync.excludes"/>
      <arg value="--dry-run"/>
      <arg value="-v"/>
    </exec>
  </target>

  <target name="backup" description="backup site" >
    <exec executable="scp" dir="." failonerror="true">
      <arg value="-r"/>
      <arg value="${username}@${host}:${dir}"/>
      <arg value="backups/${TODAY_UK}"/>
    </exec>
  </target>

</project>

2.1.8.1. sshexec

<sshexec host="${remove}" keyfile="~/.ssh/id_rsa" command="/srv/apache-tomcat/bin/catalina.sh stop -force" />

2.1.9. if

<if>
  <available file="my_directory" type="dir" />
  <then>
    <echo message="Directory exists" />
  </then>
  <else>
    <echo message="Directory does not exist" />
  </else>
</if>

Ant 1.9.x 新增 xmlns:if="ant:if"

<project name="tryit"
 xmlns:if="ant:if"
 xmlns:unless="ant:unless"
>
 <exec executable="java">
   <arg line="-X" if:true="${showextendedparams}"/>
   <arg line="-version" unless:true="${showextendedparams}"/>
 </exec>
 <condition property="onmac">
   <os family="mac"/>
 </condition>
 <echo if:set="onmac">running on MacOS</echo>
 <echo unless:set="onmac">not running on MacOS</echo>
</project>

<!DOCTYPE project>
<project xmlns:if="ant:if" xmlns:unless="ant:unless">
  <property unless:set="property" name="property.is.set" value="false"/>
  <property if:set="property" name="property.is.set" value="true"/>
  <echo>${property.is.set}</echo>
</project>

2.1.10. macrodef

arg value 与 arg line

arg line 可以处理参数的空格， 而arg value则不能. arg line 可以处理空参数， arg value传递空参数会报错.

<exec executable = "sh" dir = "@{dir}">
    <arg line = "ls -l /var/log" />
</exec>

<exec executable = "ls" dir = "@{dir}">
    <arg value = "-l" />
    <arg value = "/var/log" />
</exec>

        <macrodef name="mvn">
                <attribute name="options" default="" />
                <attribute name="goal" default="" />
                <attribute name="phase" default=" " />
                <attribute name="dir" default="" />
                <element name="args" optional="false" />
                <sequential>
                        <exec executable="mvn" dir="@{dir}" >
                                <arg value="@{options}" />
                                <arg value="@{goal}" />
                                <arg value="@{phase}" />
                        </exec>
                </sequential>
        </macrodef>

<!-- 执行下面宏将会出错，你必须传递options，phase参数 -->
<mvn goal="package" dir="${project.dir}"/>
<!-- 将vale改为line后正常 -->
        <exec executable="mvn" dir="@{dir}" >
                                <arg line="@{options}" />
                                <arg line="@{goal}" />
                                <arg line="@{phase}" />
        </exec>

运行方式sequential为顺序执行， parallel为并行执行。

2.1.10.1. Git

<macrodef name = "git">
    <attribute name = "command" />
    <attribute name = "dir" default = "" />
    <element name = "args" optional = "true" />
    <sequential>
        <echo message = "git @{command}" />
        <exec executable = "git" dir = "@{dir}">
            <arg value = "@{command}" />
            <args/>
        </exec>
    </sequential>
</macrodef>
<macrodef name = "git-clone-pull">
    <attribute name = "repository" />
    <attribute name = "dest" />
    <sequential>
        <git command = "clone">
            <args>
                <arg value = "@{repository}" />
                <arg value = "@{dest}" />
            </args>
        </git>
        <git command = "pull" dir = "@{dest}" />
    </sequential>
</macrodef>

<git command = "clone">
    <args>
        <arg value = "git://github.com/280north/ojunit.git" />
        <arg value = "ojunit" />
    </args>
</git>

<git command = "pull" dir = "repository_path" />

<git-clone-pull repository="git://github.com/280north/ojunit.git" dest="ojunit" />

2.1.10.2. Rsync

    <macrodef name="rsync">
        <attribute name="option" default="auzv" />
        <attribute name="src" default="" />
        <attribute name="dest" default="" />
        <element name="args" optional="true" />
        <sequential>
            <exec executable="rsync">
                <arg value="@{option}" />
                <arg value="@{src}" />
                <arg value="@{dest}" />
                <args />
            </exec>
        </sequential>
    </macrodef>

    <target name="deploy" depends="compile">
        <rsync option="-auzv" src="${project.dest}" dest="${remote}:${destination}">
            <args>
                <arg value="-P" />
            </args>
        </rsync>
    </target>

2.1.10.3. SSH

    <macrodef name="ssh">
        <attribute name="host" />
        <attribute name="command" />
        <attribute name="keyfile" default="~/.ssh/id_rsa" />
        <element name="args" optional="true" />
        <sequential>
            <exec executable="ssh">
                <arg value="@{host}" />
                <!-- arg value="-i @{keyfile}" / -->
                <args />
                <arg value="@{command}" />
            </exec>
        </sequential>
    </macrodef>

    <target name="stop" depends="">
        <!-- ssh host="${remote}" command="/srv/apache-tomcat/bin/catalina.sh stop -force" keyfile="~/.ssh/id_rsa" / -->
        <ssh host="${remote}" command="/srv/apache-tomcat/bin/shutdown.sh" />
    </target>
    <target name="start" depends="">
        <ssh host="${remote}" command="/srv/apache-tomcat/bin/startup.sh" keyfile="~/.ssh/id_rsa" />
    </target>

2.1.10.4. maven

        <macrodef name="mvn">
                <attribute name="options" default="" />
                <attribute name="goal" default="" />
                <attribute name="phase" default=" " />
                <attribute name="dir" default="" />
                <element name="args" optional="false" />
                <sequential>
                        <exec executable="mvn" dir="@{dir}" >
                                <arg line="@{options}" />
                                <arg value="@{goal}" />
                                <arg line="@{phase}" />
                        </exec>
                </sequential>
        </macrodef>

2.1.11. Javascript

$ cat build.xml
<project name="Attachments" default="print">
    <property name="numAttachments" value="20" />
    <target name="generate">
        <script language="javascript"><![CDATA[
            var list = '1';
            var limit = project.getProperty( "numAttachments" );
            for (var i=2;i<limit;i++)
            {
                list = list + ',' + i;
            }
            project.setNewProperty("list", list);
        print(list);
        ]] >
        </script>
    </target>
</project>

$ ant generate
Buildfile: /www/testing/build.xml

generate:
   [script] 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19

BUILD SUCCESSFUL
Total time: 0 seconds

2.1.12. mail

https://ant.apache.org/manual/Tasks/mail.html

cp ~/.m2/repository/com/sun/mail/javax.mail/1.5.6/javax.mail-1.5.6.jar /srv/apache-ant-1.9.6/lib
cp /www/.m2/repository/com/sun/mail/javax.mail/1.5.6/javax.mail-1.5.6.jar /srv/apache-ant-1.10.1/lib/

Examples

<mail from="me"
      tolist="you"
      subject="Results of nightly build"
      files="build.log"/>
Sends an email from me to you with a subject of Results of nightly build and includes the contents of the file build.log in the body of the message.

<mail mailhost="smtp.myisp.com" mailport="1025" subject="Test build">
  <from address="config@myisp.com"/>
  <replyto address="me@myisp.com"/>
  <to address="all@xyz.com"/>
  <message>The ${buildname} nightly build has completed</message>
  <attachments>
    <fileset dir="dist">
      <include name="**/*.zip"/>
    </fileset>
  </attachments>
</mail>

2.1.13. basename

<basename property="jar.filename" file="${lib.jarfile}"/>
will set jar.filename to myjar.jar, if lib.jarfile is defined as either a full-path filename (eg., /usr/local/lib/myjar.jar), a relative-path filename (eg., lib/myjar.jar), or a simple filename (eg., myjar.jar).
<basename property="cmdname" file="D:/usr/local/foo.exe"
          suffix=".exe"/>
will set cmdname to foo.
<property environment="env"/>
<basename property="temp.dirname" file="${env.TEMP}"/>

2.1.14. FAQ

2.1.14.1. warning: 'includeantruntime' was not set, defaulting to build.sysclasspath=last; set to false for repeatable builds

includeantruntime="false"

<target name="compile" depends="init">
   <javac includeantruntime="false" srcdir="${src}" destdir="${dest}"/>
</target>

or

<property name="build.sysclasspath" value="last"/>

2.1.14.2. 调试 exec

将 executable="echo" 设置成 echo 是一种不错的调试手段

        <macrodef name="gulp">
                <attribute name="stage" default=""/>
                <attribute name="src" default=""/>
                <attribute name="dir" default="" />
                <sequential>
                        <exec vmlauncher="false" executable="echo" dir="@{dir}" osfamily="unix">
                                <arg line="--stage @{stage} --src @{src}"/>
                                <!-- arg value="stage @{stage}" / -->
                        </exec>
                </sequential>
        </macrodef>

        <target name="gulp">
                <gulp stage="${git.branch}" src="cn" dir="."/>
        </target>


2.2. Apache Ivy

http://ant.apache.org/ivy/index.html

2.2.1. Ivy Install

2.2.1.1. source code

cd /usr/local/src
wget http://labs.renren.com/apache-mirror//ant/ivy/2.2.0/apache-ivy-2.2.0-bin.tar.gz
tar zxvf apache-ivy-2.2.0-bin.tar.gz
mv apache-ivy-2.2.0 /usr/local/
cd ..
ln -s apache-ivy-2.2.0 apache-ivy

IVY_HOME=/usr/local/apache-ivy

cp $IVY_HOME/ivy-2.2.0.jar $ANT_HOME/lib/

2.2.1.2. apt-get

$ sudo apt-get install ant
$ sudo apt-get install ivy

To know more about this package, you can use dpkg

$ dpkg -s ivy

2.2.2. Test example

ant

cd $IVY_HOME/src/example/hello-ivy
ant

Buildfile: /usr/local/apache-ivy-2.2.0/src/example/hello-ivy/build.xml

resolve:
[ivy:retrieve] :: Ivy 2.2.0 - 20100923230623 :: http://ant.apache.org/ivy/ ::
[ivy:retrieve] :: loading settings :: url = jar:file:/usr/local/apache-ant/lib/ivy-2.2.0.jar!/org/apache/ivy/core/settings/ivysettings.xml
[ivy:retrieve] :: resolving dependencies :: org.apache#hello-ivy;working@example.com
[ivy:retrieve]  confs: [default]
[ivy:retrieve]  found commons-lang#commons-lang;2.0 in public
[ivy:retrieve]  found commons-cli#commons-cli;1.0 in public
[ivy:retrieve]  found commons-logging#commons-logging;1.0 in public
[ivy:retrieve] downloading http://repo1.maven.org/maven2/commons-lang/commons-lang/2.0/commons-lang-2.0.jar ...
[ivy:retrieve] .......................................................................................
[ivy:retrieve] ..................................................................................................................................................................
[ivy:retrieve] ........................................................................................... (165kB)
[ivy:retrieve] .. (0kB)
[ivy:retrieve]  [SUCCESSFUL ] commons-lang#commons-lang;2.0!commons-lang.jar (4790ms)
[ivy:retrieve] downloading http://repo1.maven.org/maven2/commons-lang/commons-lang/2.0/commons-lang-2.0-javadoc.jar ...
[ivy:retrieve] ................................................................................................................
[ivy:retrieve] .........................................
[ivy:retrieve] ..................................................
[ivy:retrieve] .....................................................
[ivy:retrieve] ..................................................................................................................................
[ivy:retrieve] ..................................................................................................................................
[ivy:retrieve] .................................................................................................................
[ivy:retrieve] ...........................................................................................................................................................................
[ivy:retrieve] .............................................................................................................................................................................................. (467kB)
[ivy:retrieve] .. (0kB)
[ivy:retrieve]  [SUCCESSFUL ] commons-lang#commons-lang;2.0!commons-lang.jar(javadoc) (14878ms)
[ivy:retrieve] downloading http://repo1.maven.org/maven2/commons-lang/commons-lang/2.0/commons-lang-2.0-sources.jar ...
[ivy:retrieve] ...........................................................................................................................................................................
[ivy:retrieve] ................................................................................................................................................................................................................
[ivy:retrieve] .............................................................................................................................................. (245kB)
[ivy:retrieve] .. (0kB)
[ivy:retrieve]  [SUCCESSFUL ] commons-lang#commons-lang;2.0!commons-lang.jar(source) (5046ms)
[ivy:retrieve] downloading http://repo1.maven.org/maven2/commons-cli/commons-cli/1.0/commons-cli-1.0-javadoc.jar ...
[ivy:retrieve] ....................................................................................................................................................
[ivy:retrieve] ...................................... (92kB)
[ivy:retrieve] .. (0kB)
[ivy:retrieve]  [SUCCESSFUL ] commons-cli#commons-cli;1.0!commons-cli.jar(javadoc) (2838ms)
[ivy:retrieve] downloading http://repo1.maven.org/maven2/commons-cli/commons-cli/1.0/commons-cli-1.0.jar ...
[ivy:retrieve] ......................................................... (29kB)
[ivy:retrieve] .. (0kB)
[ivy:retrieve]  [SUCCESSFUL ] commons-cli#commons-cli;1.0!commons-cli.jar (5147ms)
[ivy:retrieve] downloading http://repo1.maven.org/maven2/commons-cli/commons-cli/1.0/commons-cli-1.0-sources.jar ...
[ivy:retrieve] ...................................................................................................... (48kB)
[ivy:retrieve] .. (0kB)
[ivy:retrieve]  [SUCCESSFUL ] commons-cli#commons-cli;1.0!commons-cli.jar(source) (2163ms)
[ivy:retrieve] downloading http://repo1.maven.org/maven2/commons-logging/commons-logging/1.0/commons-logging-1.0.jar ...
[ivy:retrieve] ............................................ (21kB)
[ivy:retrieve] ... (0kB)
[ivy:retrieve]  [SUCCESSFUL ] commons-logging#commons-logging;1.0!commons-logging.jar (2638ms)
[ivy:retrieve] :: resolution report :: resolve 30806ms :: artifacts dl 37511ms
[ivy:retrieve]  :: evicted modules:
[ivy:retrieve]  commons-lang#commons-lang;1.0 by [commons-lang#commons-lang;2.0] in [default]
        ---------------------------------------------------------------------
        |                  |            modules            ||   artifacts   |
        |       conf       | number| search|dwnlded|evicted|| number|dwnlded|
        ---------------------------------------------------------------------
        |      default     |   4   |   3   |   3   |   1   ||   7   |   7   |
        ---------------------------------------------------------------------
[ivy:retrieve] :: retrieving :: org.apache#hello-ivy
[ivy:retrieve]  confs: [default]
[ivy:retrieve]  7 artifacts copied, 0 already retrieved (1069kB/11ms)

run:
    [mkdir] Created dir: /usr/local/apache-ivy-2.2.0/src/example/hello-ivy/build
    [javac] /usr/local/apache-ivy-2.2.0/src/example/hello-ivy/build.xml:53: warning: 'includeantruntime' was not set, defaulting to build.sysclasspath=last; set to false for repeatable builds
    [javac] Compiling 1 source file to /usr/local/apache-ivy-2.2.0/src/example/hello-ivy/build
     [java] standard message : hello ivy !
     [java] capitalized by org.apache.commons.lang.WordUtils : Hello Ivy !

BUILD SUCCESSFUL
Total time: 1 second

run it

neo@debian:/usr/local/apache-ivy/src/example/hello-ivy/build$ export CLASSPATH=$CLASSPATH:/usr/local/apache-ivy/src/example/hello-ivy/lib/*
neo@debian:/usr/local/apache-ivy/src/example/hello-ivy/build$ /usr/local/java/bin/java example.Hello
standard message : hello ivy !
capitalized by org.apache.commons.lang.WordUtils : Hello Ivy !


2.3. maven

http://maven.apache.org/

2.3.1. 安装

2.3.1.1. Ubuntu

$ sudo apt-get install maven2

$ mvn -version
Apache Maven 2.2.1 (rdebian-4)
Java version: 1.6.0_22
Java home: /usr/lib/jvm/java-6-openjdk/jre
Default locale: en_US, platform encoding: UTF-8
OS name: "linux" version: "2.6.38-8-generic" arch: "amd64" Family: "unix"

JAVA_HOME="/usr/lib/jvm/java-6-openjdk/jre"
MAVEN_HOME="/usr/share/maven2/"

2.3.1.2. 源码安装

curl -s https://raw.githubusercontent.com/oscm/shell/master/lang/java/maven/maven.sh | bash

2.3.2. Maven 命令

2.3.2.1. help

查看可用的pom定义

mvn help:effective-pom

2.3.2.2. archetype:create

创建 Maven 项目

mvn archetype:create   -DarchetypeGroupId=org.apache.maven.archetypes   -DgroupId=com.company -DartifactId=my-app

mvn archetype:create
    -DgroupId=packageName
    -DartifactId=webappName
    -DarchetypeArtifactId=maven-archetype-webapp

2.3.2.3. clean

清楚

$ mvn clean

2.3.2.4. compile

编译项目的源代码

$ mvn compile

2.3.2.5. test

编译测试的源代码

mvn test-compile

运行测试

$ mvn test

打包但不测试

mvn -D maven.test.skip=true package

2.3.2.6. package

将编译后的代码打包

$ mvn package

$ ls target/*.war
target/www.netkiller.cn-0.0.1-SNAPSHOT.war

2.3.2.7. install

将项目打包后安装到本地仓库，可以作为其它项目的本地依赖。

$ mvn install

跳过测试

mvn install -Dmaven.test.skip=true
2.3.2.7.1. install-file

安装本地jar文件到Maven的.m2库中。

mvn install:install-file -Dfile=Path/to/your.jar} -DgroupId=com.example -DartifactId=project -Dversion=1.2.0 -Dpackaging=jar

2.3.2.8. war

mvn compile war:war

mvn compile war:exploded

mvn compile war:inplace

2.3.2.9. exec

$ mvn exec:java -Dexec.mainClass="cn.netkiller.Test"

2.3.2.10. dependency

2.3.2.10.1. build-classpath
$ mvn dependency:build-classpath
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building api.netkiller.cn 0.0.1-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- maven-dependency-plugin:2.10:build-classpath (default-cli) @ api.cf88.com ---
[INFO] Dependencies classpath:
/www/.m2/repository/org/springframework/boot/spring-boot-starter-web/1.3.0.RELEASE/spring-boot-starter-web-1.3.0.RELEASE.jar:......:/www/.m2/repository/junit/junit/4.12/junit-4.12.jar
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2.692 s
[INFO] Finished at: 2016-08-10T17:32:49+08:00
[INFO] Final Memory: 25M/162M
[INFO] ------------------------------------------------------------------------
2.3.2.10.2. dependency:tree 显示包依赖树
[www@iZ62yln3rjjZ repository]$ mvn dependency:tree
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building api.example.com 0.0.1-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- maven-dependency-plugin:2.10:tree (default-cli) @ api.cf88.com ---
[INFO] cf88.com:api.cf88.com:jar:0.0.1-SNAPSHOT
[INFO] +- org.springframework.boot:spring-boot-starter-web:jar:1.3.0.RELEASE:compile
[INFO] |  +- org.springframework.boot:spring-boot-starter:jar:1.3.0.RELEASE:compile
[INFO] |  |  +- org.springframework.boot:spring-boot-starter-logging:jar:1.3.0.RELEASE:compile
[INFO] |  |  |  +- ch.qos.logback:logback-classic:jar:1.1.3:compile
[INFO] |  |  |  |  \- ch.qos.logback:logback-core:jar:1.1.3:compile
[INFO] |  |  |  +- org.slf4j:jul-to-slf4j:jar:1.7.13:compile
[INFO] |  |  |  \- org.slf4j:log4j-over-slf4j:jar:1.7.13:compile
[INFO] |  |  \- org.yaml:snakeyaml:jar:1.16:runtime
[INFO] |  +- org.springframework.boot:spring-boot-starter-tomcat:jar:1.3.0.RELEASE:compile
[INFO] |  |  +- org.apache.tomcat.embed:tomcat-embed-core:jar:8.0.28:compile
[INFO] |  |  +- org.apache.tomcat.embed:tomcat-embed-el:jar:8.0.28:compile
[INFO] |  |  +- org.apache.tomcat.embed:tomcat-embed-logging-juli:jar:8.0.28:compile
[INFO] |  |  \- org.apache.tomcat.embed:tomcat-embed-websocket:jar:8.0.28:compile
[INFO] |  +- org.springframework.boot:spring-boot-starter-validation:jar:1.3.0.RELEASE:compile
[INFO] |  |  \- org.hibernate:hibernate-validator:jar:5.2.2.Final:compile
[INFO] |  |     +- javax.validation:validation-api:jar:1.1.0.Final:compile
[INFO] |  |     \- com.fasterxml:classmate:jar:1.1.0:compile
[INFO] |  +- com.fasterxml.jackson.core:jackson-databind:jar:2.6.3:compile
[INFO] |  |  +- com.fasterxml.jackson.core:jackson-annotations:jar:2.6.3:compile
[INFO] |  |  \- com.fasterxml.jackson.core:jackson-core:jar:2.6.3:compile
[INFO] |  +- org.springframework:spring-web:jar:4.2.3.RELEASE:compile
[INFO] |  |  \- org.springframework:spring-aop:jar:4.2.3.RELEASE:compile
[INFO] |  |     \- aopalliance:aopalliance:jar:1.0:compile
[INFO] |  \- org.springframework:spring-webmvc:jar:4.2.3.RELEASE:compile
[INFO] +- org.springframework.boot:spring-boot-starter-data-jpa:jar:1.3.0.RELEASE:compile
[INFO] |  +- org.springframework.boot:spring-boot-starter-aop:jar:1.3.0.RELEASE:compile
[INFO] |  |  \- org.aspectj:aspectjweaver:jar:1.8.7:compile
[INFO] |  +- org.hibernate:hibernate-entitymanager:jar:4.3.11.Final:compile
[INFO] |  |  +- org.jboss.logging:jboss-logging:jar:3.3.0.Final:compile
[INFO] |  |  +- org.jboss.logging:jboss-logging-annotations:jar:1.2.0.Beta1:compile
[INFO] |  |  +- org.hibernate:hibernate-core:jar:4.3.11.Final:compile
[INFO] |  |  |  +- antlr:antlr:jar:2.7.7:compile
[INFO] |  |  |  \- org.jboss:jandex:jar:1.1.0.Final:compile
[INFO] |  |  +- dom4j:dom4j:jar:1.6.1:compile
[INFO] |  |  |  \- xml-apis:xml-apis:jar:1.0.b2:compile
[INFO] |  |  +- org.hibernate.common:hibernate-commons-annotations:jar:4.0.5.Final:compile
[INFO] |  |  +- org.hibernate.javax.persistence:hibernate-jpa-2.1-api:jar:1.0.0.Final:compile
[INFO] |  |  \- org.javassist:javassist:jar:3.18.1-GA:compile
[INFO] |  +- javax.transaction:javax.transaction-api:jar:1.2:compile
[INFO] |  +- org.springframework.data:spring-data-jpa:jar:1.9.1.RELEASE:compile
[INFO] |  |  \- org.springframework:spring-orm:jar:4.2.3.RELEASE:compile
[INFO] |  \- org.springframework:spring-aspects:jar:4.2.3.RELEASE:compile
[INFO] +- org.springframework.boot:spring-boot-starter-jdbc:jar:1.3.0.RELEASE:compile
[INFO] |  +- org.apache.tomcat:tomcat-jdbc:jar:8.0.28:compile
[INFO] |  |  \- org.apache.tomcat:tomcat-juli:jar:8.0.28:compile
[INFO] |  \- org.springframework:spring-jdbc:jar:4.2.3.RELEASE:compile
[INFO] +- org.springframework.boot:spring-boot-starter-redis:jar:1.3.0.RELEASE:compile
[INFO] |  +- org.springframework.data:spring-data-redis:jar:1.6.1.RELEASE:compile
[INFO] |  |  \- org.springframework:spring-context-support:jar:4.2.3.RELEASE:compile
[INFO] |  \- redis.clients:jedis:jar:2.7.3:compile
[INFO] |     \- org.apache.commons:commons-pool2:jar:2.4.2:compile
[INFO] +- org.springframework.boot:spring-boot-starter-data-mongodb:jar:1.3.0.RELEASE:compile
[INFO] |  \- org.mongodb:mongo-java-driver:jar:2.13.3:compile
[INFO] +- org.springframework.boot:spring-boot-starter-amqp:jar:1.3.0.RELEASE:compile
[INFO] |  +- org.springframework:spring-messaging:jar:4.2.3.RELEASE:compile
[INFO] |  \- org.springframework.amqp:spring-rabbit:jar:1.5.2.RELEASE:compile
[INFO] |     +- org.springframework.retry:spring-retry:jar:1.1.2.RELEASE:compile
[INFO] |     +- org.springframework.amqp:spring-amqp:jar:1.5.2.RELEASE:compile
[INFO] |     +- com.rabbitmq:http-client:jar:1.0.0.RELEASE:compile
[INFO] |     |  \- org.apache.httpcomponents:httpclient:jar:4.5.1:compile
[INFO] |     |     +- org.apache.httpcomponents:httpcore:jar:4.4.4:compile
[INFO] |     |     \- commons-codec:commons-codec:jar:1.9:compile
[INFO] |     \- com.rabbitmq:amqp-client:jar:3.5.6:compile
[INFO] +- org.springframework.boot:spring-boot-devtools:jar:1.3.0.RELEASE:compile
[INFO] |  +- org.springframework.boot:spring-boot:jar:1.3.0.RELEASE:compile
[INFO] |  \- org.springframework.boot:spring-boot-autoconfigure:jar:1.3.0.RELEASE:compile
[INFO] +- org.springframework.boot:spring-boot-starter-test:jar:1.3.0.RELEASE:test
[INFO] |  +- org.mockito:mockito-core:jar:1.10.19:test
[INFO] |  |  \- org.objenesis:objenesis:jar:2.1:test
[INFO] |  +- org.hamcrest:hamcrest-core:jar:1.3:test
[INFO] |  +- org.hamcrest:hamcrest-library:jar:1.3:test
[INFO] |  +- org.springframework:spring-core:jar:4.2.3.RELEASE:compile
[INFO] |  \- org.springframework:spring-test:jar:4.2.3.RELEASE:test
[INFO] +- org.springframework.data:spring-data-mongodb:jar:1.8.1.RELEASE:compile
[INFO] |  +- org.springframework:spring-tx:jar:4.2.3.RELEASE:compile
[INFO] |  +- org.springframework:spring-context:jar:4.2.3.RELEASE:compile
[INFO] |  +- org.springframework:spring-beans:jar:4.2.3.RELEASE:compile
[INFO] |  +- org.springframework:spring-expression:jar:4.2.3.RELEASE:compile
[INFO] |  +- org.springframework.data:spring-data-commons:jar:1.11.1.RELEASE:compile
[INFO] |  +- org.slf4j:slf4j-api:jar:1.7.13:compile
[INFO] |  \- org.slf4j:jcl-over-slf4j:jar:1.7.13:compile
[INFO] +- mysql:mysql-connector-java:jar:5.1.37:compile
[INFO] +- com.google.code.gson:gson:jar:2.3.1:compile
[INFO] \- junit:junit:jar:4.12:test
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 4.400 s
[INFO] Finished at: 2016-08-09T10:25:07+08:00
[INFO] Final Memory: 23M/163M
[INFO] ------------------------------------------------------------------------
2.3.2.10.3. copy-dependencies 导出依赖包
$ mvn dependency:copy-dependencies

$ ls target/dependency/
antlr-2.7.7.jar                                javassist-3.18.1-GA.jar                    snakeyaml-1.16.jar                                spring-boot-starter-web-1.3.0.RELEASE.jar        spring-webmvc-4.2.3.RELEASE.jar
aopalliance-1.0.jar                            javax.transaction-api-1.2.jar              spring-aop-4.2.3.RELEASE.jar                      spring-boot-starter-websocket-1.3.0.RELEASE.jar  spring-websocket-4.2.3.RELEASE.jar
aspectjweaver-1.8.7.jar                        jboss-logging-3.3.0.Final.jar              spring-aspects-4.2.3.RELEASE.jar                  spring-context-4.2.3.RELEASE.jar                 thymeleaf-2.1.4.RELEASE.jar
classmate-1.1.0.jar                            jboss-logging-annotations-1.2.0.Beta1.jar  spring-beans-4.2.3.RELEASE.jar                    spring-core-4.2.3.RELEASE.jar                    thymeleaf-layout-dialect-1.3.1.jar
dom4j-1.6.1.jar                                jcl-over-slf4j-1.7.13.jar                  spring-boot-1.3.0.RELEASE.jar                     spring-data-commons-1.11.1.RELEASE.jar           thymeleaf-spring4-2.1.4.RELEASE.jar
groovy-2.4.4.jar                               jstl-1.2.jar                               spring-boot-autoconfigure-1.3.0.RELEASE.jar       spring-data-jpa-1.9.1.RELEASE.jar                tomcat-embed-core-8.0.28.jar
hibernate-commons-annotations-4.0.5.Final.jar  jul-to-slf4j-1.7.13.jar                    spring-boot-starter-1.3.0.RELEASE.jar             spring-expression-4.2.3.RELEASE.jar              tomcat-embed-el-8.0.28.jar
hibernate-core-4.3.11.Final.jar                log4j-over-slf4j-1.7.13.jar                spring-boot-starter-aop-1.3.0.RELEASE.jar         spring-jdbc-4.2.3.RELEASE.jar                    tomcat-embed-logging-juli-8.0.28.jar
hibernate-entitymanager-4.3.11.Final.jar       logback-classic-1.1.3.jar                  spring-boot-starter-data-jpa-1.3.0.RELEASE.jar    spring-messaging-4.2.3.RELEASE.jar               tomcat-embed-websocket-8.0.28.jar
hibernate-jpa-2.1-api-1.0.0.Final.jar          logback-core-1.1.3.jar                     spring-boot-starter-jdbc-1.3.0.RELEASE.jar        spring-orm-4.2.3.RELEASE.jar                     tomcat-jdbc-8.0.28.jar
hibernate-validator-5.2.2.Final.jar            mybatis-3.3.0.jar                          spring-boot-starter-logging-1.3.0.RELEASE.jar     spring-security-config-4.0.3.RELEASE.jar         tomcat-juli-8.0.28.jar
jackson-annotations-2.6.3.jar                  mybatis-spring-1.2.3.jar                   spring-boot-starter-security-1.3.0.RELEASE.jar    spring-security-core-4.0.3.RELEASE.jar           unbescape-1.1.0.RELEASE.jar
jackson-core-2.6.3.jar                         mysql-connector-java-5.1.37.jar            spring-boot-starter-thymeleaf-1.3.0.RELEASE.jar   spring-security-web-4.0.3.RELEASE.jar            validation-api-1.1.0.Final.jar
jackson-databind-2.6.3.jar                     ognl-3.0.8.jar                             spring-boot-starter-tomcat-1.3.0.RELEASE.jar      spring-tx-4.2.3.RELEASE.jar                      xml-apis-1.0.b2.jar
jandex-1.1.0.Final.jar                         slf4j-api-1.7.13.jar                       spring-boot-starter-validation-1.3.0.RELEASE.jar  spring-web-4.2.3.RELEASE.jar
2.3.2.10.4. analyze 查看未被使用的包
$ mvn dependency:analyze
2.3.2.10.5. sources 下载源码
mvn dependency:sources

2.3.2.11. jar

mvn -Djar.finalName=myCustomName package

mvn jar:jar -Djar.finalName=custom-jar-name

2.3.2.12. help:describe

显示插件的详细信息

MacBook-Pro:deployment neo$ mvn help:describe -DgroupId=org.apache.maven.plugins                   -DartifactId=maven-war-plugin                   -Ddetail=true
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building Maven Stub Project (No POM) 1
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- maven-help-plugin:2.2:describe (default-cli) @ standalone-pom ---
[INFO] org.apache.maven.plugins:maven-war-plugin:3.1.0

Name: Apache Maven WAR Plugin
Description: Builds a Web Application Archive (WAR) file from the project
  output and its dependencies.
Group Id: org.apache.maven.plugins
Artifact Id: maven-war-plugin
Version: 3.1.0
Goal Prefix: war

This plugin has 4 goals:

war:exploded
  Description: Create an exploded webapp in a specified directory.
  Implementation: org.apache.maven.plugins.war.WarExplodedMojo
  Language: java
  Bound to phase: package

  Available parameters:

    archive
      The archive configuration to use. See Maven Archiver Reference.

    archiveClasses (Default: false)
      Whether a JAR file will be created for the classes in the webapp. Using
      this optional configuration parameter will make the compiled classes to
      be archived into a JAR file and the classes directory will then be
      excluded from the webapp.

    cacheFile (Default: ${project.build.directory}/war/work/webapp-cache.xml)
      Required: true
      The file containing the webapp structure cache.

    containerConfigXML
      The path to a configuration file for the servlet container. Note that the
      file name may be different for different servlet containers. Apache
      Tomcat uses a configuration file named context.xml. The file will be
      copied to the META-INF directory.

    delimiters
      Set of delimiters for expressions to filter within the resources. These
      delimiters are specified in the form 'beginToken*endToken'. If no '*' is
      given, the delimiter is assumed to be the same for start and end.

      So, the default filtering delimiters might be specified as:

      <delimiters>
        <delimiter>${*}</delimiter>
        <delimiter>@</delimiter>
      </delimiters>

      Since the '@' delimiter is the same on both ends, we don't need to
      specify '@*@' (though we can).

    dependentWarExcludes
      The comma separated list of tokens to exclude when doing a WAR overlay.
      Default is Overlay.DEFAULT_EXCLUDES

    dependentWarIncludes
      The comma separated list of tokens to include when doing a WAR overlay.
      Default is Overlay.DEFAULT_INCLUDES

    escapedBackslashesInFilePath (Default: false)
      To escape interpolated values with Windows path c:\foo\bar will be
      replaced with c:\\foo\\bar.

    escapeString
      Expression preceded with this String won't be interpolated. \${foo} will
      be replaced with ${foo}.

    filteringDeploymentDescriptors (Default: false)
      To filter deployment descriptors. Disabled by default.

    filters
      Filters (property files) to include during the interpolation of the
      pom.xml.

    includeEmptyDirectories (Default: false)
      (no description available)

    nonFilteredFileExtensions
      A list of file extensions that should not be filtered. Will be used when
      filtering webResources and overlays.

    outputFileNameMapping
      The file name mapping to use when copying libraries and TLDs. If no file
      mapping is set (default) the files are copied with their standard names.

    overlays
      The overlays to apply. Each <overlay> element may contain:
      - id (defaults to currentBuild)
      - groupId (if this and artifactId are null, then the current project is
        treated as its own overlay)
      - artifactId (see above)
      - classifier
      - type
      - includes (a list of string patterns)
      - excludes (a list of string patterns)
      - filtered (defaults to false)
      - skip (defaults to false)
      - targetPath (defaults to root of webapp structure)

    recompressZippedFiles (Default: true)
      Indicates if zip archives (jar,zip etc) being added to the war should be
      compressed again. Compressing again can result in smaller archive size,
      but gives noticeably longer execution time.

    resourceEncoding (Default: ${project.build.sourceEncoding})
      The encoding to use when copying filtered web resources.

    supportMultiLineFiltering (Default: false)
      Stop searching endToken at the end of line

    useCache (Default: false)
      Whether the cache should be used to save the status of the webapp across
      multiple runs. Experimental feature so disabled by default.

    useDefaultDelimiters (Default: true)
      Use default delimiters in addition to custom delimiters, if any.

    useJvmChmod (Default: true)
      use jvmChmod rather that cli chmod and forking process

    warSourceDirectory (Default: ${basedir}/src/main/webapp)
      Required: true
      Single directory for extra files to include in the WAR. This is where you
      place your JSP files.

    warSourceExcludes
      The comma separated list of tokens to exclude when copying the content of
      the warSourceDirectory.

    warSourceIncludes (Default: **)
      The comma separated list of tokens to include when copying the content of
      the warSourceDirectory.

    webappDirectory (Default:
    ${project.build.directory}/${project.build.finalName})
      Required: true
      The directory where the webapp is built.

    webResources
      The list of webResources we want to transfer.

    webXml
      The path to the web.xml file to use.

    workDirectory (Default: ${project.build.directory}/war/work)
      Required: true
      Directory to unpack dependent WARs into if needed.

war:help
  Description: Display help information on maven-war-plugin.
    Call mvn war:help -Ddetail=true -Dgoal=<goal-name> to display parameter
    details.
  Implementation: org.apache.maven.plugins.war.HelpMojo
  Language: java

  Available parameters:

    detail (Default: false)
      User property: detail
      If true, display all settable properties for each goal.

    goal
      User property: goal
      The name of the goal for which to show help. If unspecified, all goals
      will be displayed.

    indentSize (Default: 2)
      User property: indentSize
      The number of spaces per indentation level, should be positive.

    lineLength (Default: 80)
      User property: lineLength
      The maximum length of a display line, should be positive.

war:inplace
  Description: Generate the webapp in the WAR source directory.
  Implementation: org.apache.maven.plugins.war.WarInPlaceMojo
  Language: java

  Available parameters:

    archive
      The archive configuration to use. See Maven Archiver Reference.

    archiveClasses (Default: false)
      Whether a JAR file will be created for the classes in the webapp. Using
      this optional configuration parameter will make the compiled classes to
      be archived into a JAR file and the classes directory will then be
      excluded from the webapp.

    cacheFile (Default: ${project.build.directory}/war/work/webapp-cache.xml)
      Required: true
      The file containing the webapp structure cache.

    containerConfigXML
      The path to a configuration file for the servlet container. Note that the
      file name may be different for different servlet containers. Apache
      Tomcat uses a configuration file named context.xml. The file will be
      copied to the META-INF directory.

    delimiters
      Set of delimiters for expressions to filter within the resources. These
      delimiters are specified in the form 'beginToken*endToken'. If no '*' is
      given, the delimiter is assumed to be the same for start and end.

      So, the default filtering delimiters might be specified as:

      <delimiters>
        <delimiter>${*}</delimiter>
        <delimiter>@</delimiter>
      </delimiters>

      Since the '@' delimiter is the same on both ends, we don't need to
      specify '@*@' (though we can).

    dependentWarExcludes
      The comma separated list of tokens to exclude when doing a WAR overlay.
      Default is Overlay.DEFAULT_EXCLUDES

    dependentWarIncludes
      The comma separated list of tokens to include when doing a WAR overlay.
      Default is Overlay.DEFAULT_INCLUDES

    escapedBackslashesInFilePath (Default: false)
      To escape interpolated values with Windows path c:\foo\bar will be
      replaced with c:\\foo\\bar.

    escapeString
      Expression preceded with this String won't be interpolated. \${foo} will
      be replaced with ${foo}.

    filteringDeploymentDescriptors (Default: false)
      To filter deployment descriptors. Disabled by default.

    filters
      Filters (property files) to include during the interpolation of the
      pom.xml.

    includeEmptyDirectories (Default: false)
      (no description available)

    nonFilteredFileExtensions
      A list of file extensions that should not be filtered. Will be used when
      filtering webResources and overlays.

    outputFileNameMapping
      The file name mapping to use when copying libraries and TLDs. If no file
      mapping is set (default) the files are copied with their standard names.

    overlays
      The overlays to apply. Each <overlay> element may contain:
      - id (defaults to currentBuild)
      - groupId (if this and artifactId are null, then the current project is
        treated as its own overlay)
      - artifactId (see above)
      - classifier
      - type
      - includes (a list of string patterns)
      - excludes (a list of string patterns)
      - filtered (defaults to false)
      - skip (defaults to false)
      - targetPath (defaults to root of webapp structure)

    recompressZippedFiles (Default: true)
      Indicates if zip archives (jar,zip etc) being added to the war should be
      compressed again. Compressing again can result in smaller archive size,
      but gives noticeably longer execution time.

    resourceEncoding (Default: ${project.build.sourceEncoding})
      The encoding to use when copying filtered web resources.

    supportMultiLineFiltering (Default: false)
      Stop searching endToken at the end of line

    useCache (Default: false)
      Whether the cache should be used to save the status of the webapp across
      multiple runs. Experimental feature so disabled by default.

    useDefaultDelimiters (Default: true)
      Use default delimiters in addition to custom delimiters, if any.

    useJvmChmod (Default: true)
      use jvmChmod rather that cli chmod and forking process

    warSourceDirectory (Default: ${basedir}/src/main/webapp)
      Required: true
      Single directory for extra files to include in the WAR. This is where you
      place your JSP files.

    warSourceExcludes
      The comma separated list of tokens to exclude when copying the content of
      the warSourceDirectory.

    warSourceIncludes (Default: **)
      The comma separated list of tokens to include when copying the content of
      the warSourceDirectory.

    webappDirectory (Default:
    ${project.build.directory}/${project.build.finalName})
      Required: true
      The directory where the webapp is built.

    webResources
      The list of webResources we want to transfer.

    webXml
      The path to the web.xml file to use.

    workDirectory (Default: ${project.build.directory}/war/work)
      Required: true
      Directory to unpack dependent WARs into if needed.

war:war
  Description: Build a WAR file.
  Implementation: org.apache.maven.plugins.war.WarMojo
  Language: java
  Bound to phase: package

  Available parameters:

    archive
      The archive configuration to use. See Maven Archiver Reference.

    archiveClasses (Default: false)
      Whether a JAR file will be created for the classes in the webapp. Using
      this optional configuration parameter will make the compiled classes to
      be archived into a JAR file and the classes directory will then be
      excluded from the webapp.

    attachClasses (Default: false)
      Whether classes (that is the content of the WEB-INF/classes directory)
      should be attached to the project as an additional artifact.
      By default the classifier for the additional artifact is 'classes'. You
      can change it with the someclassifier parameter.

      If this parameter true, another project can depend on the classes by
      writing something like:

        myGroup
        myArtifact
        myVersion
        classes

    cacheFile (Default: ${project.build.directory}/war/work/webapp-cache.xml)
      Required: true
      The file containing the webapp structure cache.

    classesClassifier (Default: classes)
      The classifier to use for the attached classes artifact.

    classifier
      Classifier to add to the generated WAR. If given, the artifact will be an
      attachment instead. The classifier will not be applied to the JAR file of
      the project - only to the WAR file.

    containerConfigXML
      The path to a configuration file for the servlet container. Note that the
      file name may be different for different servlet containers. Apache
      Tomcat uses a configuration file named context.xml. The file will be
      copied to the META-INF directory.

    delimiters
      Set of delimiters for expressions to filter within the resources. These
      delimiters are specified in the form 'beginToken*endToken'. If no '*' is
      given, the delimiter is assumed to be the same for start and end.

      So, the default filtering delimiters might be specified as:

      Since the '@' delimiter is the same on both ends, we don't need to
      specify '@*@' (though we can).

    dependentWarExcludes
      The comma separated list of tokens to exclude when doing a WAR overlay.
      Default is Overlay.DEFAULT_EXCLUDES

    dependentWarIncludes
      The comma separated list of tokens to include when doing a WAR overlay.
      Default is Overlay.DEFAULT_INCLUDES

    escapedBackslashesInFilePath (Default: false)
      To escape interpolated values with Windows path c:\foo\bar will be
      replaced with c:\\foo\\bar.

    escapeString
      Expression preceded with this String won't be interpolated. \${foo} will
      be replaced with ${foo}.

    failOnMissingWebXml
      Whether or not to fail the build if the web.xml file is missing. Set to
      false if you want your WAR built without a web.xml file. This may be
      useful if you are building an overlay that has no web.xml file.
      Starting with 3.1.0, this property defaults to false if the project
      depends on the Servlet 3.0 API or newer.

    filteringDeploymentDescriptors (Default: false)
      To filter deployment descriptors. Disabled by default.

    filters
      Filters (property files) to include during the interpolation of the
      pom.xml.

    includeEmptyDirectories (Default: false)
      (no description available)

    nonFilteredFileExtensions
      A list of file extensions that should not be filtered. Will be used when
      filtering webResources and overlays.

    outputDirectory (Default: ${project.build.directory})
      Required: true
      The directory for the generated WAR.

    outputFileNameMapping
      The file name mapping to use when copying libraries and TLDs. If no file
      mapping is set (default) the files are copied with their standard names.

    overlays
      The overlays to apply. Each overlay element may contain:
      - id (defaults to currentBuild)
      - groupId (if this and artifactId are null, then the current project is
        treated as its own overlay)
      - artifactId (see above)
      - classifier
      - type
      - includes (a list of string patterns)
      - excludes (a list of string patterns)
      - filtered (defaults to false)
      - skip (defaults to false)
      - targetPath (defaults to root of webapp structure)

    packagingExcludes
      The comma separated list of tokens to exclude from the WAR before
      packaging. This option may be used to implement the skinny WAR use case.
      Note that you can use the Java Regular Expressions engine to include and
      exclude specific pattern using the expression %regex[]. Hint: read the
      about (?!Pattern).

    packagingIncludes
      The comma separated list of tokens to include in the WAR before
      packaging. By default everything is included. This option may be used to
      implement the skinny WAR use case. Note that you can use the Java Regular
      Expressions engine to include and exclude specific pattern using the
      expression %regex[].

    primaryArtifact (Default: true)
      Whether this is the main artifact being built. Set to false if you don't
      want to install or deploy it to the local repository instead of the
      default one in an execution.

    recompressZippedFiles (Default: true)
      Indicates if zip archives (jar,zip etc) being added to the war should be
      compressed again. Compressing again can result in smaller archive size,
      but gives noticeably longer execution time.

    resourceEncoding (Default: ${project.build.sourceEncoding})
      The encoding to use when copying filtered web resources.

    skip (Default: false)
      User property: maven.war.skip
      You can skip the execution of the plugin if you need to. Its use is NOT
      RECOMMENDED, but quite convenient on occasion.

    supportMultiLineFiltering (Default: false)
      Stop searching endToken at the end of line

    useCache (Default: false)
      Whether the cache should be used to save the status of the webapp across
      multiple runs. Experimental feature so disabled by default.

    useDefaultDelimiters (Default: true)
      Use default delimiters in addition to custom delimiters, if any.

    useJvmChmod (Default: true)
      use jvmChmod rather that cli chmod and forking process

    warSourceDirectory (Default: ${basedir}/src/main/webapp)
      Required: true
      Single directory for extra files to include in the WAR. This is where you
      place your JSP files.

    warSourceExcludes
      The comma separated list of tokens to exclude when copying the content of
      the warSourceDirectory.

    warSourceIncludes (Default: **)
      The comma separated list of tokens to include when copying the content of
      the warSourceDirectory.

    webappDirectory (Default:
    ${project.build.directory}/${project.build.finalName})
      Required: true
      The directory where the webapp is built.

    webResources
      The list of webResources we want to transfer.

    webXml
      The path to the web.xml file to use.

    workDirectory (Default: ${project.build.directory}/war/work)
      Required: true
      Directory to unpack dependent WARs into if needed.

[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 0.960 s
[INFO] Finished at: 2017-08-03T14:33:18+08:00
[INFO] Final Memory: 10M/155M
[INFO] ------------------------------------------------------------------------

MacBook-Pro:api.netkiller.cn neo$ mvn help:describe \
    -DgroupId=org.springframework.boot \
    -DartifactId=spring-boot-maven-plugin \
    -Ddetail=true

2.3.3. Maven 仓库

http://search.maven.org/

http://mvnrepository.com/

本地下载目录

$ ls ~/.m2

2.3.4. pom.xml

2.3.4.1. properties

定义 properties

<properties>
    <spring.version>4.0.1.RELEASE</spring.version>
</properties>

引用 properties

  <!-- Spring dependencies -->
   <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>${spring.version}</version>
  </dependency>

例 2.1. Maven properties

pom.xml 文件

<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/maven-v4_0_0.xsd">

 <modelVersion>4.0.0</modelVersion>
 <groupId>com.javahash.web</groupId>
 <artifactId>Spring4MVCHelloWorld</artifactId>
 <packaging>war</packaging>
 <version>1.0-SNAPSHOT</version>
 <name>Spring4MVCHelloWorld Maven Webapp</name>
 <url>http://maven.apache.org</url>

 <properties>
    <spring.version>4.0.1.RELEASE</spring.version>
 </properties>

 <dependencies>

    <dependency>
     <groupId>junit</groupId>
     <artifactId>junit</artifactId>
     <version>4.12</version>
     <scope>test</scope>
    </dependency>

   <!-- Spring dependencies -->
   <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>${spring.version}</version>
  </dependency>

 <dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-web</artifactId>
   <version>${spring.version}</version>
 </dependency>

 <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>${spring.version}</version>
 </dependency>
</dependencies>

 <build>
 <finalName>Spring4MVCHelloWorld</finalName>
 </build>
</project>

2.3.4.1.1. java.version

    <properties>
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
         <java.version>1.8</java.version>
    </properties>

2.3.4.2. repositories 仓库配置

2.3.4.2.1. 默认仓库

        <repository>
            <id>mvnrepository</id>
            <name>mvnrepository</name>
            <url>http://www.mvnrepository.com/</url>
            <layout>default</layout>
            <releases>
                <enabled>true</enabled>
            </releases>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
2.3.4.2.2. 阿里云仓库

    <repositories>
        <repository>
            <id>alimaven</id>
            <name>aliyun maven</name>
            <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
            <releases>
                <enabled>true</enabled>
            </releases>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
    </repositories>

2.3.4.3. dependencies

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.apache.struts</groupId>
        <artifactId>struts2-core</artifactId>
        <version>2.3.24.1</version>
    </dependency>
  </dependencies>

2.3.4.4. dependencyManagement

声明父项目引用包的版本号。

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-netflix</artifactId>
                <version>1.3.5.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-config</artifactId>
                <version>1.3.3.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

2.3.4.5. build

2.3.4.5.1. finalName

最终 jar 包得名字

<build>
        <finalName>crawler</finalName>
</build>
2.3.4.5.2. sourceDirectory

编译源码目录

<sourceDirectory>src</sourceDirectory>
2.3.4.5.3. resources 文件处理

resources 用来处理 src/main/resources 目录中得内容

        <resources>
            <resource>
                <directory>src/main/resources</directory>
                <targetPath>${project.build.directory}/conf</targetPath>
            </resource>
            <resource>
                <directory>src/main/resources</directory>
                <excludes>
                    <exclude>development.properties</exclude>
                </excludes>
            </resource>
            <resource>
                <directory>src/main/resources</directory>
                <includes>
                    <include>development.properties</include>
                </includes>
                <targetPath>resources</targetPath>
            </resource>

            <resource>
                <directory>lib</directory>
                <includes>
                    <include>**/*.sh</include>
                    <include>**/*.bat</include>
                </includes>
                <targetPath>${project.build.directory}/lib</targetPath>
            </resource>
        </resources>
2.3.4.5.3.1. resources

将资源文件编译后复制到 WEB-INF/classes 目录中

        <resources>
            <resource>
                <directory>src/resources</directory>
            </resource>
        </resources>

include / exclude

<resources>
    <!-- Filter jdbc.properties & mail.properties. NOTE: We don't filter applicationContext-infrastructure.xml,
        let it go with spring's resource process mechanism. -->
    <resource>
        <directory>src/main/resources</directory>
        <filtering>true</filtering>
        <includes>
            <include>jdbc.properties</include>
            <include>mail.properties</include>
        </includes>
    </resource>
    <!-- Include other files as resources files. -->
    <resource>
        <directory>src/main/resources</directory>
        <filtering>false</filtering>
        <excludes>
            <exclude>jdbc.properties</exclude>
            <exclude>mail.properties</exclude>
        </excludes>
    </resource>
</resources>

2.3.4.6. plugins

2.3.4.6.1. 跳过Unit test

<project>
  [...]
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <configuration>
          <skip>true</skip>
        </configuration>
      </plugin>
    </plugins>
  </build>
  [...]
</project>
2.3.4.6.2. maven-shade-plugin

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>1.4</version>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                        <configuration>
                            <transformers>
                                <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                                    <mainClass>cn.netkiller.Oracle</mainClass>
                                </transformer>
                            </transformers>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

2.3.5. plugins

2.3.5.1. maven-compiler-plugin

禁止编译警告 -Xlint:unchecked，-Xlint:deprecation

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                    <compilerArgs>
                        <arg>-verbose</arg>
                        <arg>-Xlint:unchecked</arg>
                        <arg>-Xlint:deprecation</arg>
                    </compilerArgs>
                </configuration>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                    <compilerArgs>
                        <arg>-verbose</arg>
                        <arg>-Xlint:unchecked</arg>
                        <arg>-Xlint:deprecation</arg>
                        <arg>-bootclasspath</arg>
                        <arg>${env.JAVA_HOME}/jre/lib/rt.jar</arg>
                        <arg>-extdirs</arg>
                        <arg>${project.basedir}/libs</arg>
                    </compilerArgs>
                </configuration>
            </plugin>

compilerArgs可以实现编译参数的传递

2.3.5.2. maven-war-plugin

设置 war 文件名 warName

<project>
  ...
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-war-plugin</artifactId>
        <version>2.3</version>
        <configuration>
          <warName>bird.war</warName>
        </configuration>
      </plugin>
    </plugins>
  </build>
  ...
</project>

2.3.5.3. maven-antrun-plugin

查看可用的pom定义

or alternatively, we can use an external build.xml.

<project>
  <modelVersion>4.0.0</modelVersion>
  <artifactId>my-test-app</artifactId>
  <groupId>my-test-group</groupId>
  <version>1.0-SNAPSHOT</version>

  <build>
    <plugins>

      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-antrun-plugin</artifactId>
        <version>1.8</version>
        <executions>
          <execution>
            <id>compile</id>
            <phase>compile</phase>
            <configuration>
              <target>
                <property name="compile_classpath" refid="maven.compile.classpath"/>
                <property name="runtime_classpath" refid="maven.runtime.classpath"/>
                <property name="test_classpath" refid="maven.test.classpath"/>
                <property name="plugin_classpath" refid="maven.plugin.classpath"/>

                <echo message="compile classpath: ${compile_classpath}"/>
                <echo message="runtime classpath: ${runtime_classpath}"/>
                <echo message="test classpath:    ${test_classpath}"/>
                <echo message="plugin classpath:  ${plugin_classpath}"/>

                <ant antfile="${basedir}/build.xml">
                  <target name="test"/>
                </ant>
              </target>
            </configuration>
            <goals>
              <goal>run</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>

The build.xml:

<?xml version="1.0"?>
<project name="test6">

    <target name="test">

      <echo message="compile classpath: ${compile_classpath}"/>
      <echo message="runtime classpath: ${runtime_classpath}"/>
      <echo message="test classpath:    ${test_classpath}"/>
      <echo message="plugin classpath:  ${plugin_classpath}"/>

    </target>

</project>

2.3.5.4. maven-install-plugin

        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-install-plugin</artifactId>
            <version>2.5.2</version>
            <inherited>false</inherited>
            <executions>
                <execution>
                    <id>install:com.oracle:ojdbc6:11g</id>
                    <phase>validate</phase>
                    <goals>
                        <goal>install-file</goal>
                    </goals>
                    <configuration>
                        <file>${project.basedir}/lib/ojdbc6.jar</file>
                        <groupId>com.oracle</groupId>
                        <artifactId>ojdbc6</artifactId>
                        <version>11.2.0.3</version>
                        <packaging>jar</packaging>
                        <createChecksum>true</createChecksum>
                        <generatePom>true</generatePom>
                    </configuration>
                </execution>
            </executions>
        </plugin>

2.3.5.5. maven-surefire-plugin

    <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <version>2.20</version>
        <configuration>
            <skip>true</skip>
        </configuration>
    </plugin>

2.3.5.6. maven-deploy-plugin

            <plugin>
              <!--skip deploy (this is just a test module) -->
              <artifactId>maven-deploy-plugin</artifactId>
              <configuration>
                <skip>true</skip>
              </configuration>
            </plugin>

2.3.5.7. maven-jar-plugin

指定jar创建目录，下面配置运行 mvn package 后将在 target 目录下创建一个 project 目录。

    <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-jar-plugin</artifactId>
        <version>2.3.1</version>
        <configuration>
            <outputDirectory>${project.build.directory}/project</outputDirectory>
        </configuration>
    </plugin>

知道 jar 文件的默认 mainClass

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <version>2.6</version>
                <configuration>
                    <outputDirectory>${project.build.directory}/project/lib</outputDirectory>
                    <archive>
                        <manifest>
                            <addClasspath>true</addClasspath>
                            <classpathPrefix>lib/</classpathPrefix>
                            <mainClass>cn.netkiller.web.App</mainClass>
                        </manifest>
                    </archive>
                </configuration>
            </plugin>

2.3.5.8. maven-dependency-plugin

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-dependency-plugin</artifactId>
                <!-- <version>2.10</version> -->
                <executions>
                    <execution>
                        <id>copy-dependencies</id>
                        <phase>package</phase>
                        <goals>
                            <goal>copy-dependencies</goal>
                        </goals>
                        <configuration>
                            <!-- 把依赖的所有maven jar包拷贝到lib目录中（这样所有的jar包都在lib目录中） -->
                            <outputDirectory>${project.build.directory}/project/lib</outputDirectory>
                            <overWriteReleases>false</overWriteReleases>
                            <overWriteSnapshots>false</overWriteSnapshots>
                            <overWriteIfNewer>true</overWriteIfNewer>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

2.3.5.9. spring-boot-maven-plugin

            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <mainClass>cn.netkiller.Application</mainClass>
                </configuration>
            </plugin>

MacBook-Pro:api.netkiller.cn neo$ mvn help:describe -DgroupId=org.springframework.boot                   -DartifactId=spring-boot-maven-plugin                   -Ddetail=true
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building api 0.0.1-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- maven-help-plugin:2.2:describe (default-cli) @ api ---
[INFO] org.springframework.boot:spring-boot-maven-plugin:1.5.6.RELEASE

Name: Spring Boot Maven Plugin
Description: Spring Boot Maven Plugin
Group Id: org.springframework.boot
Artifact Id: spring-boot-maven-plugin
Version: 1.5.6.RELEASE
Goal Prefix: spring-boot

This plugin has 6 goals:

spring-boot:build-info
  Description: Generate a build-info.properties file based the content of the
    current MavenProject.
  Implementation: org.springframework.boot.maven.BuildInfoMojo
  Language: java
  Bound to phase: generate-resources

  Available parameters:

    additionalProperties
      Additional properties to store in the build-info.properties. Each entry
      is prefixed by build. in the generated build-info.properties.

    outputFile (Default:
    ${project.build.outputDirectory}/META-INF/build-info.properties)
      The location of the generated build-info.properties.

spring-boot:help
  Description: Display help information on spring-boot-maven-plugin.
    Call mvn spring-boot:help -Ddetail=true -Dgoal=<goal-name> to display
    parameter details.
  Implementation: org.springframework.boot.maven.HelpMojo
  Language: java

  Available parameters:

    detail (Default: false)
      User property: detail
      If true, display all settable properties for each goal.

    goal
      User property: goal
      The name of the goal for which to show help. If unspecified, all goals
      will be displayed.

    indentSize (Default: 2)
      User property: indentSize
      The number of spaces per indentation level, should be positive.

    lineLength (Default: 80)
      User property: lineLength
      The maximum length of a display line, should be positive.

spring-boot:repackage
  Description: Repackages existing JAR and WAR archives so that they can be
    executed from the command line using java -jar. With layout=NONE can also
    be used simply to package a JAR with nested dependencies (and no main
    class, so not executable).
  Implementation: org.springframework.boot.maven.RepackageMojo
  Language: java
  Bound to phase: package

  Available parameters:

    attach (Default: true)
      Attach the repackaged archive to be installed and deployed.

    classifier
      Classifier to add to the artifact generated. If given, the artifact will
      be attached with that classifier and the main artifact will be deployed
      as the main artifact. If this is not given (default), it will replace the
      main artifact and only the repackaged artifact will be deployed.
      Attaching the artifact allows to deploy it alongside to the original one,
      see the maven documentation for more details.

    embeddedLaunchScript
      The embedded launch script to prepend to the front of the jar if it is
      fully executable. If not specified the 'Spring Boot' default script will
      be used.

    embeddedLaunchScriptProperties
      Properties that should be expanded in the embedded launch script.

    excludeArtifactIds
      User property: excludeArtifactIds
      Comma separated list of artifact names to exclude (exact match).

    excludeDevtools (Default: true)
      Exclude Spring Boot devtools from the repackaged archive.

    excludeGroupIds
      User property: excludeGroupIds
      Comma separated list of groupId names to exclude (exact match).

    excludes
      Collection of artifact definitions to exclude. The Exclude element
      defines a groupId and artifactId mandatory properties and an optional
      classifier property.

    executable (Default: false)
      Make a fully executable jar for *nix machines by prepending a launch
      script to the jar.
      Currently, some tools do not accept this format so you may not always be
      able to use this technique. For example, jar -xf may silently fail to
      extract a jar or war that has been made fully-executable. It is
      recommended that you only enable this option if you intend to execute it
      directly, rather than running it with java -jar or deploying it to a
      servlet container.

    finalName (Default: ${project.build.finalName})
      Required: true
      Name of the generated archive.

    includes
      Collection of artifact definitions to include. The Include element
      defines a groupId and artifactId mandatory properties and an optional
      classifier property.

    includeSystemScope (Default: false)
      Include system scoped dependencies.

    layout
      The type of archive (which corresponds to how the dependencies are laid
      out inside it). Possible values are JAR, WAR, ZIP, DIR, NONE. Defaults to
      a guess based on the archive type.

    layoutFactory
      The layout factory that will be used to create the executable archive if
      no explicit layout is set. Alternative layouts implementations can be
      provided by 3rd parties.

    mainClass
      The name of the main class. If not specified the first compiled class
      found that contains a 'main' method will be used.

    outputDirectory (Default: ${project.build.directory})
      Required: true
      Directory containing the generated archive.

    requiresUnpack
      A list of the libraries that must be unpacked from fat jars in order to
      run. Specify each library as a <dependency> with a <groupId> and a
      <artifactId> and they will be unpacked at runtime.

    skip (Default: false)
      User property: skip
      Skip the execution.

spring-boot:run
  Description: Run an executable archive application.
  Implementation: org.springframework.boot.maven.RunMojo
  Language: java
  Bound to phase: validate
  Before this mojo executes, it will call:
    Phase: 'test-compile'

  Available parameters:

    addResources (Default: false)
      User property: run.addResources
      Add maven resources to the classpath directly, this allows live in-place
      editing of resources. Duplicate resources are removed from target/classes
      to prevent them to appear twice if ClassLoader.getResources() is called.
      Please consider adding spring-boot-devtools to your project instead as it
      provides this feature and many more.

    agent
      User property: run.agent
      Path to agent jar. NOTE: the use of agents means that processes will be
      started by forking a new JVM.

    arguments
      User property: run.arguments
      Arguments that should be passed to the application. On command line use
      commas to separate multiple arguments.

    classesDirectory (Default: ${project.build.outputDirectory})
      Required: true
      Directory containing the classes and resource files that should be
      packaged into the archive.

    excludeArtifactIds
      User property: excludeArtifactIds
      Comma separated list of artifact names to exclude (exact match).

    excludeGroupIds
      User property: excludeGroupIds
      Comma separated list of groupId names to exclude (exact match).

    excludes
      Collection of artifact definitions to exclude. The Exclude element
      defines a groupId and artifactId mandatory properties and an optional
      classifier property.

    folders
      Additional folders besides the classes directory that should be added to
      the classpath.

    fork
      User property: fork
      Flag to indicate if the run processes should be forked. fork is
      automatically enabled if an agent, jvmArguments or working directory are
      specified, or if devtools is present.

    includes
      Collection of artifact definitions to include. The Include element
      defines a groupId and artifactId mandatory properties and an optional
      classifier property.

    jvmArguments
      User property: run.jvmArguments
      JVM arguments that should be associated with the forked process used to
      run the application. On command line, make sure to wrap multiple values
      between quotes. NOTE: the use of JVM arguments means that processes will
      be started by forking a new JVM.

    mainClass
      The name of the main class. If not specified the first compiled class
      found that contains a 'main' method will be used.

    noverify
      User property: run.noverify
      Flag to say that the agent requires -noverify.

    profiles
      User property: run.profiles
      The spring profiles to activate. Convenience shortcut of specifying the
      'spring.profiles.active' argument. On command line use commas to separate
      multiple profiles.

    skip (Default: false)
      User property: skip
      Skip the execution.

    useTestClasspath (Default: false)
      User property: useTestClasspath
      Flag to include the test classpath when running.

    workingDirectory
      User property: run.workingDirectory
      Current working directory to use for the application. If not specified,
      basedir will be used. NOTE: the use of working directory means that
      processes will be started by forking a new JVM.

spring-boot:start
  Description: Start a spring application. Contrary to the run goal, this
    does not block and allows other goal to operate on the application. This
    goal is typically used in integration test scenario where the application
    is started before a test suite and stopped after.
  Implementation: org.springframework.boot.maven.StartMojo
  Language: java
  Bound to phase: pre-integration-test

  Available parameters:

    addResources (Default: false)
      User property: run.addResources
      Add maven resources to the classpath directly, this allows live in-place
      editing of resources. Duplicate resources are removed from target/classes
      to prevent them to appear twice if ClassLoader.getResources() is called.
      Please consider adding spring-boot-devtools to your project instead as it
      provides this feature and many more.

    agent
      User property: run.agent
      Path to agent jar. NOTE: the use of agents means that processes will be
      started by forking a new JVM.

    arguments
      User property: run.arguments
      Arguments that should be passed to the application. On command line use
      commas to separate multiple arguments.

    classesDirectory (Default: ${project.build.outputDirectory})
      Required: true
      Directory containing the classes and resource files that should be
      packaged into the archive.

    excludeArtifactIds
      User property: excludeArtifactIds
      Comma separated list of artifact names to exclude (exact match).

    excludeGroupIds
      User property: excludeGroupIds
      Comma separated list of groupId names to exclude (exact match).

    excludes
      Collection of artifact definitions to exclude. The Exclude element
      defines a groupId and artifactId mandatory properties and an optional
      classifier property.

    folders
      Additional folders besides the classes directory that should be added to
      the classpath.

    fork
      User property: fork
      Flag to indicate if the run processes should be forked. fork is
      automatically enabled if an agent, jvmArguments or working directory are
      specified, or if devtools is present.

    includes
      Collection of artifact definitions to include. The Include element
      defines a groupId and artifactId mandatory properties and an optional
      classifier property.

    jmxName
      The JMX name of the automatically deployed MBean managing the lifecycle
      of the spring application.

    jmxPort
      The port to use to expose the platform MBeanServer if the application
      needs to be forked.

    jvmArguments
      User property: run.jvmArguments
      JVM arguments that should be associated with the forked process used to
      run the application. On command line, make sure to wrap multiple values
      between quotes. NOTE: the use of JVM arguments means that processes will
      be started by forking a new JVM.

    mainClass
      The name of the main class. If not specified the first compiled class
      found that contains a 'main' method will be used.

    maxAttempts
      The maximum number of attempts to check if the spring application is
      ready. Combined with the 'wait' argument, this gives a global timeout
      value (30 sec by default)

    noverify
      User property: run.noverify
      Flag to say that the agent requires -noverify.

    profiles
      User property: run.profiles
      The spring profiles to activate. Convenience shortcut of specifying the
      'spring.profiles.active' argument. On command line use commas to separate
      multiple profiles.

    skip (Default: false)
      User property: skip
      Skip the execution.

    useTestClasspath (Default: false)
      User property: useTestClasspath
      Flag to include the test classpath when running.

    wait
      The number of milli-seconds to wait between each attempt to check if the
      spring application is ready.

    workingDirectory
      User property: run.workingDirectory
      Current working directory to use for the application. If not specified,
      basedir will be used. NOTE: the use of working directory means that
      processes will be started by forking a new JVM.

spring-boot:stop
  Description: Stop a spring application that has been started by the 'start'
    goal. Typically invoked once a test suite has completed.
  Implementation: org.springframework.boot.maven.StopMojo
  Language: java
  Bound to phase: post-integration-test

  Available parameters:

    fork
      User property: fork
      Flag to indicate if process to stop was forked. By default, the value is
      inherited from the MavenProject. If it is set, it must match the value
      used to StartMojo start the process.

    jmxName
      The JMX name of the automatically deployed MBean managing the lifecycle
      of the application.

    jmxPort
      The port to use to lookup the platform MBeanServer if the application has
      been forked.

    skip (Default: false)
      User property: skip
      Skip the execution.

[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 0.976 s
[INFO] Finished at: 2017-08-03T15:05:53+08:00
[INFO] Final Memory: 12M/155M
[INFO] ------------------------------------------------------------------------


2.4. Gradle

2.4.1. 安装 Gradle

2.4.1.1. CentOS

安装 Gradle

                curl -s https://raw.githubusercontent.com/oscm/shell/38c2d4d307ce7a0760fc88d5d9703eef64d3b81c/lang/java/gradle/gradle-2.9.sh | bash

安装后运行 gradle --help 验证释放正确安装。

# gradle --help

USAGE: gradle [option...] [task...]

-?, -h, --help          Shows this help message.
-a, --no-rebuild        Do not rebuild project dependencies.
-b, --build-file        Specifies the build file.
-c, --settings-file     Specifies the settings file.
--configure-on-demand   Only relevant projects are configured in this build run. This means faster build for large multi-project builds. [incubating]
--console               Specifies which type of console output to generate. Values are 'plain', 'auto' (default) or 'rich'.
--continue              Continues task execution after a task failure.
-D, --system-prop       Set system property of the JVM (e.g. -Dmyprop=myvalue).
-d, --debug             Log in debug mode (includes normal stacktrace).
--daemon                Uses the Gradle daemon to run the build. Starts the daemon if not running.
--foreground            Starts the Gradle daemon in the foreground. [incubating]
-g, --gradle-user-home  Specifies the gradle user home directory.
--gui                   Launches the Gradle GUI.
-I, --init-script       Specifies an initialization script.
-i, --info              Set log level to info.
-m, --dry-run           Runs the builds with all task actions disabled.
--max-workers           Configure the number of concurrent workers Gradle is allowed to use. [incubating]
--no-color              Do not use color in the console output. [deprecated - use --console=plain instead]
--no-daemon             Do not use the Gradle daemon to run the build.
--offline               The build should operate without accessing network resources.
-P, --project-prop      Set project property for the build script (e.g. -Pmyprop=myvalue).
-p, --project-dir       Specifies the start directory for Gradle. Defaults to current directory.
--parallel              Build projects in parallel. Gradle will attempt to determine the optimal number of executor threads to use. [incubating]
--parallel-threads      Build projects in parallel, using the specified number of executor threads. [deprecated - Please use --parallel, optionally in conjunction with --max-workers.] [incubating]
--profile               Profiles build execution time and generates a report in the <build_dir>/reports/profile directory.
--project-cache-dir     Specifies the project-specific cache directory. Defaults to .gradle in the root project directory.
-q, --quiet             Log errors only.
--recompile-scripts     Force build script recompiling.
--refresh-dependencies  Refresh the state of dependencies.
--rerun-tasks           Ignore previously cached task results.
-S, --full-stacktrace   Print out the full (very verbose) stacktrace for all exceptions.
-s, --stacktrace        Print out the stacktrace for all exceptions.
--stop                  Stops the Gradle daemon if it is running.
-t, --continuous        Enables continuous build. Gradle does not exit and will re-execute tasks when task file inputs change. [incubating]
-u, --no-search-upward  Don't search in parent folders for a settings.gradle file.
-v, --version           Print version info.
-x, --exclude-task      Specify a task to be excluded from execution.

2.4.1.2. Mac

neo@MacBook-Pro ~ %  brew install gradle

2.4.2. gradle 命令

2.4.2.1. tasks 列出任务

[neo@netkiller test]$ gradle tasks
:tasks

------------------------------------------------------------
All tasks runnable from root project
------------------------------------------------------------

Application tasks
-----------------
installApp - Installs the project as a JVM application along with libs and OS specific scripts.
run - Runs this project as a JVM application

Build tasks
-----------
assemble - Assembles the outputs of this project.
build - Assembles and tests this project.
buildDependents - Assembles and tests this project and all projects that depend on it.
buildNeeded - Assembles and tests this project and all projects it depends on.
classes - Assembles main classes.
clean - Deletes the build directory.
jar - Assembles a jar archive containing the main classes.
testClasses - Assembles test classes.

Build Setup tasks
-----------------
init - Initializes a new Gradle build. [incubating]

Distribution tasks
------------------
assembleDist - Assembles the main distributions
distTar - Bundles the project as a distribution.
distZip - Bundles the project as a distribution.
installDist - Installs the project as a distribution as-is.

Documentation tasks
-------------------
javadoc - Generates Javadoc API documentation for the main source code.

Help tasks
----------
components - Displays the components produced by root project 'test'. [incubating]
dependencies - Displays all dependencies declared in root project 'test'.
dependencyInsight - Displays the insight into a specific dependency in root project 'test'.
help - Displays a help message.
model - Displays the configuration model of root project 'test'. [incubating]
projects - Displays the sub-projects of root project 'test'.
properties - Displays the properties of root project 'test'.
tasks - Displays the tasks runnable from root project 'test'.

Verification tasks
------------------
check - Runs all checks.
test - Runs the unit tests.

Other tasks
-----------
wrapper

Rules
-----
Pattern: clean<TaskName>: Cleans the output files of a task.
Pattern: build<ConfigurationName>: Assembles the artifacts of a configuration.
Pattern: upload<ConfigurationName>: Assembles and uploads the artifacts belonging to a configuration.

To see all tasks and more detail, run gradle tasks --all

To see more detail about a task, run gradle help --task <task>

BUILD SUCCESSFUL

Total time: 5.157 secs

2.4.3. build.gradle

apply plugin

            apply plugin: 'java'

2.4.3.1. repositories

repositories {
    mavenCentral()
}

配置阿里云仓库

allprojects {
    repositories {
        maven{ url 'http://maven.aliyun.com/nexus/content/groups/public/'}
    }
}

2.4.3.2. dependencies

                dependencies {
                compile 'org.springframework:spring-context:4.2.2.RELEASE'
                }

2.4.3.3. jar

                jar {
                baseName = 'hello'
                version = '0.1.0'
                }

2.4.4. gradle.properties

2.4.4.1. 列出 properties

                [neo@netkiller gradle]$ gradle properties
                :properties

                ------------------------------------------------------------
                Root project
                ------------------------------------------------------------

                allprojects: [root project 'gradle']
                ant: org.gradle.api.internal.project.DefaultAntBuilder@12072edc
                antBuilderFactory: org.gradle.api.internal.project.DefaultAntBuilderFactory@159576c3
                artifacts: org.gradle.api.internal.artifacts.dsl.DefaultArtifactHandler_Decorated@7a80747
                asDynamicObject:
                org.gradle.api.internal.ExtensibleDynamicObject@2875ca3e
                baseClassLoaderScope: org.gradle.api.internal.initialization.DefaultClassLoaderScope@4d30c132
                buildDir: /opt/www/gradle/build
                buildFile: /opt/www/gradle/build.gradle
                buildScriptSource: org.gradle.groovy.scripts.UriScriptSource@3bdbe135
                buildscript: org.gradle.api.internal.initialization.DefaultScriptHandler@609e7d46
                childProjects: {}
                class: class org.gradle.api.internal.project.DefaultProject_Decorated
                classLoaderScope:
                org.gradle.api.internal.initialization.DefaultClassLoaderScope@4532b038
                clone: task ':clone'
                compile: task ':compile'
                compileTest: task ':compileTest'
                components: []
                configurationActions: org.gradle.configuration.project.DefaultProjectConfigurationActionContainer@2cf5006
                configurations: []
                convention: org.gradle.api.internal.plugins.DefaultConvention@788ebb5a
                defaultTasks: []
                deferredProjectConfiguration: org.gradle.api.internal.project.DeferredProjectConfiguration@62ae4f8b
                dependencies:
                org.gradle.api.internal.artifacts.dsl.dependencies.DefaultDependencyHandler_Decorated@21e8614a
                depth: 0
                description: null
                dist: task ':dist'
                ext: org.gradle.api.internal.plugins.DefaultExtraPropertiesExtension@1f4b52aa
                extensions: org.gradle.api.internal.plugins.DefaultConvention@788ebb5a
                fileOperations: org.gradle.api.internal.file.DefaultFileOperations@a2026f3
                fileResolver: org.gradle.api.internal.file.BaseDirFileResolver@44dd20b6
                gradle: build 'gradle'
                group:
                inheritedScope:
                org.gradle.api.internal.ExtensibleDynamicObject$InheritedDynamicObject@118eb00c
                logger: org.gradle.logging.internal.slf4j.OutputEventListenerBackedLogger@2ec7ecd5
                logging: org.gradle.logging.internal.DefaultLoggingManager@478dabf1
                modelRegistry: org.gradle.model.internal.registry.DefaultModelRegistry@26137fea
                modelSchemaStore: org.gradle.model.internal.manage.schema.extract.DefaultModelSchemaStore@4a32ef2d
                module: org.gradle.api.internal.artifacts.ProjectBackedModule@31bca1c3
                name: gradle
                parent: null
                parentIdentifier: null
                path: :
                pluginManager: org.gradle.api.internal.plugins.DefaultPluginManager_Decorated@55f49969
                plugins: [org.gradle.api.plugins.HelpTasksPlugin@4f88f506]
                processOperations: org.gradle.api.internal.file.DefaultFileOperations@a2026f3
                project: root project 'gradle'
                project.dir: repo
                project.url: git@172.16.0.1:example.com/admin.example.com.git
                projectDir: /opt/www/gradle
                projectEvaluationBroadcaster: ProjectEvaluationListener broadcast
                projectEvaluator:
                org.gradle.configuration.project.LifecycleProjectEvaluator@19035ff9
                projectRegistry: org.gradle.api.internal.project.DefaultProjectRegistry@2c91e143
                properties: {...}
                pull: task ':pull'
                repositories: []
                resources: org.gradle.api.internal.resources.DefaultResourceHandler@1d5c0c91
                rootDir: /opt/www/gradle
                rootProject: root project 'gradle'
                scriptHandlerFactory: org.gradle.api.internal.initialization.DefaultScriptHandlerFactory@63d12a6
                scriptPluginFactory:
                org.gradle.configuration.DefaultScriptPluginFactory@1393537d
                serviceRegistryFactory: org.gradle.internal.service.scopes.ProjectScopeServices$4@2d4e3d95
                services: ProjectScopeServices
                standardOutputCapture: org.gradle.logging.internal.DefaultLoggingManager@478dabf1
                state: project state 'EXECUTED'
                status: release
                stop: task ':stop'
                subprojects: []
                tasks: [task ':clone', task ':compile', task ':compileTest', task ':dist', task ':properties', task ':pull', task ':stop', task ':test']
                test: task ':test'
                version:
                unspecified

                BUILD SUCCESSFUL

                Total time: 4.672 secs

2.4.4.2. 自定义 gradle.properties

                [neo@netkiller gradle]$ cat gradle.properties
                Name=Netkiller
                Email=netkiller@msn.com

                [neo@netkiller gradle]$ gradle properties | egrep "Name|Email"
                Email: netkiller@msn.com
                Name: Netkiller

[neo@netkiller gradle]$ cat build.gradle
task hello << {
     println "hello, $Name<$Email>"
}

[neo@netkiller gradle]$ gradle hello -q
hello, Netkiller<netkiller@msn.com>

通过 systemProp 前缀传递 System.properties 参数

[neo@netkiller gradle]$ cat gradle.properties
systemProp.Name = 'Neo Chen'

[neo@netkiller gradle]$ cat build.gradle

task hello << {
      println "hello, " + System.properties['Name']
}

2.4.4.3.

ext {
     Name='Neo'
}

task hello << {
     println "hello, $Name"
}

2.4.4.4. System.properties

-D 参数传递

task hello << {
     println System.properties['Name']
}

$ gradle hello -DName='Neo' -q
Neo

-P 参数传递

task hello << {
      println "hello, $Name"
}

$ gradle hello -PName='Neo' -q
hello, Neo

2.4.5. Example

            mkdir -p src/main/java/hello

            vim src/main/java/hello/HelloWorld.java

package hello;

public class HelloWorld {
  public static void main(String[] args) {
    System.out.println("Helloworld!!!");
  }
}

            $ gradle build
            $ gradle run -q
            Helloworld!!!


第 3 章 Servlet

目录

3.1. Example
3.2. Session
3.3. HttpServletRequest
3.4. Filter
3.4.1. web.xml
3.4.2. Filter 类
3.5. Listener
3.5.1. web.xml
3.5.2. NewsListener 类
3.5.3. NewsTask 类
3.5.4. JSP 中心显示
3.6. JSP
3.6.1. 注释
3.6.2. pageContext
3.6.2.1. queryString
3.6.3. request
3.6.3.1. Form
3.6.4.
3.6.4.1. sendRedirect
3.6.5. cookie
3.6.6. session
3.6.7. page
3.6.7.1. Session
3.6.8. trimDirectiveWhitespaces
3.6.9. include
3.6.10. jsp
3.6.10.1. jsp:forward
3.6.11. error-page
3.6.12. JSP 编程
3.6.12.1. 随机数
3.6.13. FAQ
3.6.13.1. http://www.netkiller.cn/test.html;jsessionid=7D25CE666FF437F2094AA945E97CEB37
3.7. JSTL(JavaServer Pages Standard Tag Library)
3.7.1. c:set
3.7.1.1. c:remove
3.7.2. c:out
3.7.3. c:url
3.7.4. c:redirect
3.7.5. c:import
3.7.6. c:if
3.7.6.1. boolean
3.7.7. c:choose
3.7.8. c:forEach
3.7.8.1. List 处理
3.7.8.2. Map 处理
3.7.9. empty 判断是否为空
3.7.10. JSTL fmt Tag setBundle Example
3.7.10.1. fmt:message
3.7.10.2.
3.8. WebSocket
3.8.1. Server
3.8.2. Client

3.1. Example

package cn.netkiller.helloworld;

import java.io.IOException;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebInitParam;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * Servlet implementation class HelloWorld
 */
@WebServlet("/HelloWorld")
public final class HelloWorld extends HttpServlet {
    private static final long serialVersionUID = 1L;

    /**
     * @see HttpServlet#HttpServlet()
     */
    public HelloWorld() {
        super();
        // TODO Auto-generated constructor stub
    }

    /**
     * @see Servlet#init(ServletConfig)
     */
    public void init(ServletConfig config) throws ServletException {
        // TODO Auto-generated method stub
    }

    /**
     * @see Servlet#destroy()
     */
    public void destroy() {
        // TODO Auto-generated method stub
    }

    /**
     * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
     */
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // TODO Auto-generated method stub
        response.getWriter().append("Served at: ").append(request.getContextPath()).append("<br></br> Helloworld");
    }

    /**
     * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
     */
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // TODO Auto-generated method stub
        doGet(request, response);
    }

}


3.2. Session

web.xml 定义默认过期时间

<web-app ...>
    <session-config>
        <session-timeout>Minutes</session-timeout>
    </session-config>
</web-app>

修改Session时间

HttpSession session = request.getSession();
session.setMaxInactiveInterval(20*60);


3.3. HttpServletRequest

request.getParameter() 获取form参数

HttpServletRequest request = ServletActionContext.getRequest();
String name = request.getParameter("name");
String age = request.getParameter("age");


3.4. Filter

3.4.1. web.xml

配置过滤器

<filter>
    <filter-name>LoginFilter</filter-name>
    <filter-class>cn.netkiller.Filter</filter-class>
    <init-param>
        <param-name>username</param-name>
        <param-value>netkiller</param-value>
    </init-param>
</filter>

<filter>
      <filter-name>LoginFilter</filter-name>
      <filter-class>cn.netkiller.Filter</filter-class>
</filter>
<filter-mapping>
      <filter-name>LoginFilter</filter-name>
      <url-pattern>/*</url-pattern>
</filter-mapping>

3.4.2. Filter 类

实现 Filter 接口

public class LoginFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("init LoginFilter");
        //获取Filter初始化参数
        String username = filterConfig.getInitParameter("username");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException {
        //把ServletRequest和ServletResponse转换成真正的类型
        HttpServletRequest req = (HttpServletRequest)request;
        HttpSession session = req.getSession();

        //由于web.xml中设置Filter过滤全部请求，可以排除不需要过滤的url
        String requestURI = req.getRequestURI();
        if(requestURI.endsWith("login.jsp")){
            chain.doFilter(request, response);
            return;
        }

        //判断用户是否登录，进行页面的处理
        if(null == session.getAttribute("user")){
            //未登录用户，重定向到登录页面
            ((HttpServletResponse)response).sendRedirect("login.jsp");
            return;
        } else {
            //已登录用户，允许访问
            chain.doFilter(request, response);
        }
    }

    @Override
    public void destroy() {
        System.out.println("destroy!!!");
    }
}


3.5. Listener

3.5.1. web.xml

配置监听器

    <listener>
        <listener-class>cn.netkiller.listener.NewsListener</listener-class>
    </listener>

3.5.2. NewsListener 类

实现 ServletContextListener 接口

package cn.netkiller.listener;

import java.util.Timer;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

import org.apache.log4j.Logger;

public class NewsListener implements ServletContextListener {

    private static final Logger log = Logger.getLogger(NewsListener.class);

    private Timer timer = null;

    @Override
    public void contextInitialized(ServletContextEvent event) {

        log.info("Listener start");

        timer = new Timer(true);
        timer.schedule(new NewsTask(event.getServletContext()), 3*1000, 5*60*1000);
    }

    @Override
    public void contextDestroyed(ServletContextEvent event) {
        if (timer != null) {
            timer.cancel();
        }
        log.info("Listener end");
    }
}

3.5.3. NewsTask 类

继承 TimerTask

package cn.netkiller.listener;

import java.util.List;
import java.util.TimerTask;
import javax.servlet.ServletContext;

import org.apache.log4j.Logger;
import org.springframework.web.context.support.WebApplicationContextUtils;

import cn.netkiller.service.interface.NewsService;

public class NewsTask extends TimerTask{

    private ServletContext context;
    private static final Logger log = Logger.getLogger(NewsTask.class);

    public NewsTask(ServletContext context) {
        this.context = context;
    }

    @Override
    public void run() {
        NewsService newsService = (NewsService) WebApplicationContextUtils.getWebApplicationContext(context).getBean("newsService");

        try {
            List<cn.netkiller.listener.News> newsList = newsService.getNews();
            context.setAttribute("newsList", newsList);

            log.info("Getting News Finished");
        } catch (Exception e) { e.printStackTrace(); }
    }
}

3.5.4. JSP 中心显示

使用c:forEach显示列表

    <div class="news">
           <c:forEach items="${newsList}" var="news" varStatus="index">
               <a href="/news/${news.Id}">${news.title}</a>
           </c:forEach>
    </div>


3.6. JSP

3.6.1. 注释

JSP页面中的注释有以下几种样式的注释方法：

JSP页面中的HTML注释
     <!-- 注释内容 -->

JSP页面中的普通注释,不会再浏览器中输出

    <% // 注释内容 %>
    <% /* 注释内容 */ %>

JSP页面中的隐藏注释,不会再浏览器中输出
<%-- 注释内容 --%>

3.6.2. pageContext

3.6.2.1. queryString

输出？问号后面的变量

${pageContext.request.queryString}
<c:out value = "${pageContext.request.queryString}" />

3.6.3. request

new URL(
    request.getScheme(),
    request.getServerName(),
    request.getServerPort(),
    request.getContextPath()
    );

获取主机名

<%=request.getServerName() %>

3.6.3.1. Form

<%
   Enumeration paramNames = request.getParameterNames();

   while(paramNames.hasMoreElements()) {
      String paramName = (String)paramNames.nextElement();
      String paramValue = request.getParameter(paramName);
      out.println(paramName + ": " + paramValue + "<br/>\n");
   }
%>

<%= request.getParameter("first_name")%>

<c:set var="UA" value="${param.first_name}" />
<c:out value="${param.first_name}"/>

3.6.4.

3.6.4.1. sendRedirect

页面跳转

response.sendRedirect("/content/test.jsp");

3.6.5. cookie

获取 cookie 值

<html>
<head>
<title>Reading Cookies</title>
</head>
<body>
<center>
<h1>Reading Cookies</h1>
</center>
<%
   Cookie cookie = null;
   Cookie[] cookies = null;
   // Get an array of Cookies associated with this domain
   cookies = request.getCookies();
   if( cookies != null ){
      out.println("<h2> Found Cookies Name and Value</h2>");
      for (int i = 0; i < cookies.length; i++){
         cookie = cookies[i];
         out.print("Name : " + cookie.getName( ) + ",  ");
         out.print("Value: " + cookie.getValue( )+" <br/>");
      }
  }else{
      out.println("<h2>No cookies founds</h2>");
  }
%>
</body>
</html>

3.6.6. session

获取 Session 值

<% out.print(session.getAttribute("random")); %>

3.6.7. page

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>

3.6.7.1. Session

禁用Session

<% @page session="false" %>

启用Session

<%@ page session="true" %>

3.6.8. trimDirectiveWhitespaces

删除JSP编译产生的空行

<%@ page trimDirectiveWhitespaces="true" %>

3.6.9. include

<%@ include file="analytics.jsp" %>

<jsp:include page="telephone.jsp" />
<jsp:include page="address.jsp" flush="true"/>
<jsp:include page="${request.getContentType()}/module/html.body.begin.html" flush="true" />

file 与 page 的区别是，file 将文件包含进当前文件，然后变成成servlet。 page 是运行的时候才会包含文件输出结果包含进当前文件。

<pre>

<%@include file="inc.html" %>
=============
<%@include file="/inc.html" %>
=============
<jsp:include page="inc.html" />
=============
<jsp:include page="inc.html" flush="true" />
==============
<jsp:include page="/inc.html" flush="true" />
==============
<jsp:include page="${request.getContentType()}/inc.html" flush="true" />

</pre>

注意上面包含结果相同，使用上略有差异。

3.6.10. jsp

3.6.10.1. jsp:forward

jsp:forward 只能跳转到存在的物理页面

<jsp:forward page="/test.jsp" />

3.6.11. error-page

在 web.xml 中配置 error-page

<error-page>
    <error-code>400</error-code>
    <location>/error.jsp</location>
</error-page>

<error-page>
    <error-code>404</error-code>
    <location>/error.jsp</location>
</error-page>

<error-page>
    <error-code>500</error-code>
    <location>/error.jsp</location>
</error-page>
<!-- java.lang.Exception异常错误,依据这个标记可定义多个类似错误提示 -->
<error-page>
    <exception-type>java.lang.Exception</exception-type>
    <location>/error.jsp</location>
</error-page>
<!-- java.lang.NullPointerException异常错误,依据这个标记可定义多个类似错误提示 -->
<error-page>
    <exception-type>java.lang.NullPointerException </exception-type>
    <location>/error.jsp</location>
</error-page>

<error-page>
    <exception-type>javax.servlet.ServletException</exception-type>
    <location>/error.jsp</location>
</error-page>

3.6.12. JSP 编程

3.6.12.1. 随机数

<%= (int) (Math.random() * 1000) %>

3.6.13. FAQ

3.6.13.1. http://www.netkiller.cn/test.html;jsessionid=7D25CE666FF437F2094AA945E97CEB37

URL经常会出现 jsessionid，这是因为当你使用c:url的时候，它会判断当前域下是否已经创建jsessionid的cookie变量，如果没有并且你链接的是静态页面，那么c:url将传递jsessionid变量，迫使Tomcat为当前域创建jsessionid cookie。

当你使用 Apache Nginx 作为Tomcat前端是，由于Apache Nginx不识别;jsessionid=7D25CE666FF437F2094AA945E97CEB37这种URL，会跳出404页面。

解决方案一，放弃使用c:url，如果你需要保持context 的 path 设置，可以使用下面方法

<a href="${pageContext.request.contextPath}/test.html">Netkiller Test</a>

方案二，使用rewrite截取jsessionid做忽略处理

Apache：

ReWriteRule ^/(\w+);jsessionid=\w+$ /$1 [L,R=301]
ReWriteRule ^/(\w+\.do);jsessionid=\w+$ /$1 [L,R=301]

Nginx：

rewrite ^(.*)\;jsessionid=(.*)$ $1 break;


3.7. JSTL(JavaServer Pages Standard Tag Library)

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>

3.7.1. c:set

设置变量

<c:set var="foo" scope="request" value="helloworld">

or

<%request.setAttribute("foo","helloworld") %>

<c:out value="${requestScope.foo}"/>

3.7.1.1. c:remove

<c:remove var="message" scope="session" />

3.7.2. c:out

输出变量variable的内容

<c:out value="${variable}"/>

<%=request.getParameter("UA")%>
相当于
<c:out value="${param.UA}"/>

默认值
<c:out value="${param.UA}" default="UA-69658002-1" />

3.7.3. c:url

生成URL

<c:url value="/news/china/"/>

3.7.4. c:redirect

<c:redirect url="/index.html"/>
<c:redirect url="http://www.netkiller.cn"/>

3.7.5. c:import

<c:import url="http://www.netkiller.cn" />

<c:import var="html" url="http://www.netkiller.cn"/>
<c:out value="${html}"/>

传递GET参数

<c:import url="http://www.netkiller.cn" >
    <c:param name="id" value="10" />
    <c:param name="name" value="neo" />
</c:import>

异常处理

<c:catch var="exception">

  <c:import url="ftp://ftp.example.com/package/README"/>

</c:catch>

<c:if test="${not empty exception}">

  Sorry, the remote content is not currently available.

</c:if>

在Context间切换

server.conf:
<Context  reloadable="true" crossContext="true" />

<c:import url="/path/to/file.jsp" context=/project1" />
<c:import url="/path/to/file.jsp" context=/project2" />

3.7.6. c:if

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<html>
<head>
<title><c:if> Tag Example</title>
</head>
<body>
<c:set var="salary" scope="session" value="${2000*2}"/>
<c:if test="${salary > 2000}">
   <p>My salary is: <c:out value="${salary}"/><p>
</c:if>
</body>
</html>

3.7.6.1. boolean

<c:if test="${theBooleanVariable}">It's true!</c:if>
<c:if test="${not theBooleanVariable}">It's false!</c:if>

3.7.7. c:choose

<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<c:choose>
    <c:when test="${session.auth eq 'true' }">

    </c:when>
    <c:otherwise>

    </c:otherwise>
</c:choose>

实现 if else/else if / else

<c:choose>
   <c:when test="${..}">...</c:when> <!-- if condition -->
   <c:when test="${..}">...</c:when> <!-- else if condition -->
   <c:otherwise>...</c:otherwise>    <!-- else condition -->
</c:choose>

3.7.8. c:forEach

3.7.8.1. List 处理

<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
    pageEncoding="ISO-8859-1"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Insert title here</title>
</head>
<body>
    ${bookList}
    <br>

    <c:forEach items="${bookList}" var="node">
        <c:out value="${node}"></c:out><br>
    </c:forEach>

</body>
</html>

3.7.8.2. Map 处理

<%@ page language="java" contentType="text/html; charset=utf-8"
    pageEncoding="utf-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Insert title here</title>
</head>
<body>
    <c:forEach items="${channel}" var="node">
        <a href="<c:out value="${node.value}"></c:out>"><c:out value="${node.key}"></c:out></a>
        <br/>
    </c:forEach>
</body>
</html>

3.7.9. empty 判断是否为空

    <c:if test="${empty session.member }">

    </c:if>

3.7.10. JSTL fmt Tag setBundle Example

3.7.10.1. fmt:message

src/resources/config.properties

Name=Neo
Address=Shenzhen
Number=13322993040

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>JSTL fmt:setBundle Tag</title>
</head>
<body>
    <fmt:setBundle basename="resources.config" var="config" />
    <fmt:message key="Name" bundle="${config}" />

    <fmt:message key="Address" bundle="${config}" />

    <fmt:message key="Number" bundle="${config}" />
</body>
</html>

    <fmt:bundle basename="lang">
        <fmt:message key="Name" />
        <fmt:message key="Address" />
    </fmt:bundle>
3.7.10.1.1. Language Package

<fmt:setLocale value="en" />
3.7.10.1.2. fmt:message 变量

<fmt:message key="js" bundle="${config}" var="val" />
<c:out value="${val}"/>

3.7.10.2.

<fmt:setTimeZone value="Europe/London" scope="session"/>

<fmt:formatDate value="${isoDate}" type="both"/>
2004-5-31 23:59:59

<fmt:formatDate value="${date}" type="date"/>
2004-4-1

<fmt:formatDate value="${isoDate}" type="time"/>
23:59:59

<fmt:formatDate value="${isoDate}" type="date" dateStyle="default"/>
2004-5-31

<fmt:formatDate value="${isoDate}" type="date" dateStyle="short"/>
04-5-31

<fmt:formatDate value="${isoDate}" type="date" dateStyle="medium"/>
2004-5-31

<fmt:formatDate value="${isoDate}" type="date" dateStyle="long"/>
2004年5月31日

<fmt:formatDate value="${isoDate}" type="date" dateStyle="full"/>
2004年5月31日 星期一

<fmt:formatDate value="${isoDate}" type="time" timeStyle="default"/>
23:59:59

<fmt:formatDate value="${isoDate}" type="time" timeStyle="short"/>
下午11:59

<fmt:formatDate value="${isoDate}" type="time" timeStyle="medium"/>
23:59:59

<fmt:formatDate value="${isoDate}" type="time" timeStyle="long"/>
下午11时59分59秒

<fmt:formatDate value="${isoDate}" type="time" timeStyle="full"/>
下午11时59分59秒 CDT

<fmt:formatDate value="${date}" type="both" pattern="EEEE, MMMM d, yyyy HH:mm:ss Z"/>
星期四, 四月 1, 2004 13:30:00 -0600

<fmt:formatDate value="${isoDate}" type="both" pattern="d MMM yy, h:m:s a zzzz/>
31 五月 04, 11:59:59 下午 中央夏令时

格式模式：
  d   月中的某一天。一位数的日期没有前导零。
  dd   月中的某一天。一位数的日期有一个前导零。
  ddd   周中某天的缩写名称，在   AbbreviatedDayNames   中定义。
  dddd   周中某天的完整名称，在   DayNames   中定义。
  M   月份数字。一位数的月份没有前导零。
  MM   月份数字。一位数的月份有一个前导零。
  MMM   月份的缩写名称，在   AbbreviatedMonthNames   中定义。
  MMMM   月份的完整名称，在   MonthNames   中定义。
  y   不包含纪元的年份。如果不包含纪元的年份小于   10，则显示不具有前导零的年份。
  yy   不包含纪元的年份。如果不包含纪元的年份小于   10，则显示具有前导零的年份。
  yyyy   包括纪元的四位数的年份。
  gg   时期或纪元。如果要设置格式的日期不具有关联的时期或纪元字符串，则忽略该模式。
  h   12   小时制的小时。一位数的小时数没有前导零。
  hh   12   小时制的小时。一位数的小时数有前导零。
  H   24   小时制的小时。一位数的小时数没有前导零。
  HH   24   小时制的小时。一位数的小时数有前导零。
  m   分钟。一位数的分钟数没有前导零。
  mm   分钟。一位数的分钟数有一个前导零。
  s   秒。一位数的秒数没有前导零。
  ss   秒。一位数的秒数有一个前导零。

<fmt:formatDate value="${xx}" pattern="dd/MM/yyyy HH:mm aa"/>和

<fmt:formatDate value="${xx}" pattern="dd/MM/yyyy hh:mm aa"/>  对于0点显示的结果不一样

<fmt:formatDate value="${dateValue}" pattern="yyyy-MM-dd HH:mm:ss z" timeZone="GMT"/>

<fmt:formatDate value="${dateValue}" pattern="yyyy-MM-dd HH:mm:ss z" timeZone="US/Eastern"/>


3.8. WebSocket

环境：Java8 + Tomcat8

3.8.1. Server

package websocket;

/**
 * Websocket Server
 *
 * @author netkiller<netkiller@msn.com>
 */

import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;

@ServerEndpoint(value = "/echo")
public class PriceServer {

    private Set<Session> sessions = Collections.synchronizedSet(new HashSet<Session>());

    /**
     * Callback hook for Connection open events. This method will be invoked
     * when a client requests for a WebSocket connection.
     *
     * @param session
     *            the session which is opened.
     */
    @OnOpen
    public void onOpen(Session session) {
        sessions.add(session);
    }

    /**
     * Callback hook for Connection close events. This method will be invoked
     * when a client closes a WebSocket connection.
     *
     * @param session
     *            the session which is opened.
     */
    @OnClose
    public void onClose(Session session) {
        sessions.remove(session);
    }

    /**
     * Callback hook for Message Events. This method will be invoked when a
     * client send a message.
     *
     * @param message
     *            The text message
     * @param session
     *            The session of the client
     */
    @OnMessage
    public void onMessage(String message, Session session) {
        System.out.println("Message Received: " + message);
        for (Session remote : sessions) {
            System.out.println("Sending to " + remote.getId());
            remote.getAsyncRemote().sendText(message);
        }
    }
}

3.8.2. Client

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>

    <script language="JavaScript">
        var wsuri = "ws://localhost:8080/m.example.com/echo";
        var ws = null;

        function connectEndpoint() {
            ws = new WebSocket(wsuri);
            ws.onmessage = function(evt) {
                //alert(evt.data);
                document.getElementById("echo").value = evt.data;
            };

            ws.onclose = function(evt) {
                //alert("close");
                document.getElementById("echo").value = "end";
            };

            ws.onopen = function(evt) {
                //alert("open");
                document.getElementById("echo").value = "open";
            };
        }

        function sendmsg() {
            ws.send(document.getElementById("send").value);
        }
    </script>
<body onload="connectEndpoint()">
    <input type="text" size="20" value="5" id="send">
    <input type="button" value="send" onclick="sendmsg()">
    <br>
    <input type="text" id="echo">
</body>
</html>


部分 I. Spring Framework

目录

4. Spring Tool Suite
4.1. 安装
4.2. Dashboard
5. Spring Boot
5.1. Spring Boot Quick start
5.1.1. pom.xml
5.1.2. Controller
5.2. Maven 插件
5.2.1. resource
5.3. 运行 Spring boot 项目
5.3.1. Maven run
5.3.2. Linux systemd
5.3.3. 传统 init.d 脚本
5.3.4. 编译用于Tomcat的 War
5.4. SpringApplication
5.4.1. @SpringBootApplication
5.4.1.1. 排除 @EnableAutoConfiguration 加载项
5.4.2. @EnableAutoConfiguration
5.4.3. @ComponentScan
5.4.4. @EntityScan 实体扫描
5.4.5. @EnableJpaRepositories
5.4.6. CharacterEncodingFilter
5.4.7. 隐藏 Banner
5.4.8. 实体与仓库扫描
5.4.9. 列出 Beans
5.5. application.properties
5.5.1. application.properties 参考
5.5.2. 启动指定参数
5.5.3. 加载排除
5.5.4. PID FILE
5.5.5. 内嵌 tomcat server
5.5.5.1. server.tomcat.basedir
5.5.5.2. server
5.5.5.3. access.log
5.5.5.4. logging
5.5.5.5. charset
5.5.5.6. Session 配置
5.5.5.7. cookie
5.5.5.8. error 路径
5.5.5.9. 压缩传输
5.5.5.10. server.servlet.context-path
5.5.5.11. ssl
5.5.5.12. 上传限制
5.5.6. JSON 输出与日期格式化
5.5.7. SMTP 相关配置
5.5.8. Redis
5.5.9. MongoDB
5.5.10. MySQL
5.5.11. Oracle
5.5.12. default_schema
5.5.13. datasource
5.5.14. velocity
5.5.15. Security 相关配置
5.5.16. MVC 配置
5.5.17. Kafka 相关配置
5.5.18.
5.6. Properties
5.6.1. @Value 注解
5.6.2. @PropertySource 注解载入 properties 文件
5.6.3. @EnableConfigurationProperties 引用自定义 *.properties 配置文件
5.6.4. 手工载入 *.properties 文件
5.6.5. spring.profiles.active 参数切换配置文件
5.6.6. SpringApplicationBuilder.properties() 方法添加配置项
5.7. Spring boot with Logging
5.7.1. 打印日志
5.7.2. 日志输出级别
5.7.3. 禁止控制台输出日志
5.7.4. 标准输出
5.7.5. 安天分隔日志
5.7.6. 按照文件尺寸分割日志
5.7.7. 指定Class过滤日志
5.7.8. 日志写入 MongoDB
5.7.9. logback 配置详解
5.7.9.1. configuration 属性配置
5.7.9.2. contextName 设置上下文名称
5.7.9.3. property 设置变量
5.7.9.4. encoder 日志格式设置
5.7.9.5. RollingFileAppender
5.8. Spring boot with Webpage
5.8.1. Maven
5.8.2. application.properties
5.8.3. Application
5.8.4. IndexController
5.8.5. src/main/webapp/WEB-INF/jsp/index.jsp
5.8.6. 集成模板引擎
5.9. Spring boot with Velocity template
5.9.1. Maven
5.9.2. Resource
5.9.3. Application
5.9.4. RestController
5.9.5. Test
5.10. Spring boot with Thymeleaf
5.10.1. Maven
5.10.2. application.properties
5.10.3. Controller
5.10.4. HTML5 Template
5.11. String boot with RestTemplate
5.11.1. 在控制器中完整实例
5.12. Spring boot with Session share
5.12.1. Redis
5.12.1.1. Maven
5.12.1.2. application.properties
5.12.1.3. Application
5.12.2. 测试 Session
5.12.3. JDBC
5.13. Spring boot with MongoDB
5.13.1. Maven
5.13.2. Application
5.13.3. MongoTemplate
5.13.4. Repository
5.14. Spring boot with MySQL
5.14.1. Maven
5.14.2. Resource
5.14.3. Application
5.14.4. JdbcTemplate
5.14.5. CrudRepository
5.15. Spring boot with Oracle
5.15.1. Maven
5.15.2. application.properties
5.15.3. Application
5.15.4. CrudRepository
5.15.5. JdbcTemplate
5.15.6. Controller
5.16. Spring boot with PostgreSQL
5.16.1. pom.xml
5.16.2. application.properties
5.16.3. Application
5.16.4. CrudRepository
5.16.5. JdbcTemplate
5.16.6. Controller
5.16.7. Test
5.17. 数据源配置
5.17.1. 多数据源配置
5.17.2. JPA 多数据源
5.17.3. Connection and Statement Pooling
5.17.3.1. org.apache.tomcat.jdbc.pool.DataSource
5.17.3.2. druid
5.17.3.3. c3p0 - JDBC3 Connection and Statement Pooling
5.17.3.4. dbcp2
5.17.3.5. bonecp
5.17.3.6. dbcp2
5.18. Spring boot with Redis
5.18.1. Spring boot with Redis
5.18.1.1. maven
5.18.1.2. application.properties
5.18.1.3. JUnit
5.18.1.4. Controller
5.18.2. Redis Pub/Sub
5.18.2.1. Redis配置类
5.18.2.2. 订阅和发布类
5.18.2.3. 消息发布演示
5.19. Spring boot with Caching
5.19.1. maven
5.19.2. 启用 Cache
5.19.3. 测试 Controller
5.19.4. @Cacheable 的用法
5.19.5. @CachePut 用法
5.19.6. 解决Expire 和 TTL 过期时间
5.19.7. SpEL表达式
5.20. Spring boot with Email
5.20.1. Maven
5.20.2. Resource
5.20.3. POJO
5.20.4. RestController
5.20.5. Test
5.21. Spring boot with Spring security
5.21.1. Maven
5.21.2. Reource
5.21.3. Application
5.21.4. WebSecurityConfigurer
5.21.5. RestController
5.21.6. 测试
5.21.7. Spring + Security + MongoDB
5.21.7.1. Account
5.21.7.2. AccountRepository
5.21.7.3. WebSecurityConfiguration
5.22. Spring boot with Oauth2
5.22.1. Maven
5.22.2. Password tools
5.22.3. Server
5.22.3.1. Maven
5.22.3.2. application.properties
5.22.3.3. EnableAuthorizationServer
5.22.3.4. EnableResourceServer
5.22.3.5. Entity Table
5.22.3.6. UserRepository
5.22.3.7. UserService
5.22.3.8. TestRestController
5.22.3.9. 数据库初始化
5.22.3.10. Test
5.22.4. Spring boot with Oauth2 RestTemplate
5.22.4.1. Maven
5.22.4.2. OAuth2ClientConfiguration.java
5.22.4.3. Application.java
5.22.4.4. application.properties
5.22.4.5. Controller
5.22.4.6. Test
5.23. Spring boot with Oauth2 jwt
5.23.1. Maven
5.23.2. Authorization Server
5.23.3. Resource Server
5.23.4. Web Security
5.23.5. 插入数据
5.23.6. 使用 CURL 测试 JWT
5.23.7. 测试 Shell
5.23.8. refresh_token
5.24. Spring boot with Oauth2 jwt 非对称证书
5.24.1. 创建证书
5.24.2. Authorization Server
5.24.3. Resource Server
5.25. Apple iOS 访问 Oauth2
5.26. Android Oauth2 + Jwt example
5.27. Spring boot with Scheduling
5.27.1. Application.java
5.27.2. Component
5.27.3. 查看日志
5.27.4. 计划任务控制开关
5.27.5. @Scheduled 详解
5.27.5.1. 每3秒钟一运行一次
5.27.5.2. 凌晨23点运行
5.28. Spring boot with Hessian
5.28.1. Maven
5.28.2. Application
5.28.3. HessianServiceExporter
5.28.4. Service
5.28.5. RestController
5.29. Spring boot with Apache Kafka
5.29.1. 安装 kafka
5.29.2. maven
5.29.3. Spring boot Application
5.29.4. EnableKafka
5.29.5. KafkaListener
5.29.6. 测试
5.29.7. 完整的发布订阅实例
5.29.7.1. Consumer
5.29.7.2. Producer
5.29.7.3. Test
5.29.8. Spring cloud with Kafka
5.29.8.1. Application 主文件
5.29.8.2. 资源配置文件
5.29.8.3. 启用 kafka
5.29.8.4. 消息发布主程序
5.30. Spring boot with RabbitMQ(AMQP)
5.30.1. maven
5.30.2. RabbitMQConfig
5.30.3. 生产者
5.30.4. 消费者
5.31. Spring boot with Git version
5.31.1. CommonRestController 公共控制器
5.31.2. VersionRestController 测试控制器
5.31.3. 创建 .gitattributes 文件
5.32. Spring boot with HTTPS SSL
5.32.1. 生成自签名证书
5.32.2. application.properties 配置文件
5.32.3. 启动 Spring boot
5.32.4. restTemplate 调用实例
5.33. Spring boot with Elasticsearch 2.x
5.33.1. Maven
5.33.2. Application
5.33.3. application.properties
5.33.4. Domain
5.33.5. ElasticsearchRepository
5.34. Spring boot with Elasticsearch 5.5.x
5.34.1. Maven
5.34.2. Application
5.34.3. application.properties
5.34.4. ElasticsearchConfiguration
5.34.5. RestController
5.35. Spring boot with Apache Hive
5.35.1. Maven
5.35.2. application.properties
5.35.3. Configuration
5.35.4. CURD 操作实例
5.36. Spring boot with Phoenix
5.36.1. Maven
5.36.2. application.properties
5.36.3. Configuration
5.37. Spring boot with Data restful
5.37.1. Maven
5.38. Spring boot with ELK(Elasticsearch + Logstash + Kibana)
5.38.1. TCP 方案
5.38.2. Redis 方案
5.38.3. Kafka 方案
5.38.4. Other
5.39. Springboot with Ethereum (web3j)
5.39.1. Maven
5.39.2. application.properties
5.39.3. TestRestController
5.39.4. 测试
5.40. 异步执行
5.40.1. 最简单的配置
5.40.2.
5.41. Spring boot with export csv.
5.42. Spring boot with lombok
6. Spring MVC
6.1. @EnableWebMvc
6.1.1. CORS 跨域请求
6.1.2. Spring MVC CORS with WebMvcConfigurerAdapter
6.2. @Controller
6.2.1. @RequestMapping
6.2.1.1. @RequestMapping("/")
6.2.1.2. 映射多个URL
6.2.1.3. 匹配通配符
6.2.1.4. headers
6.2.1.5. @GetMapping
6.2.1.6. @PostMapping
6.2.2. RequestMapping with Path Variables - @PathVariable
6.2.2.1. URL 参数传递
6.2.2.2. 默认值
6.2.2.3. URL 传递 Date 类型
6.2.2.4. 处理特殊字符
6.2.2.5. @PathVariable 注意事项
6.2.3. RequestMapping with Request Parameters - @RequestParam
6.2.3.1. HTTP GET
6.2.3.2. HTTP POST
6.2.3.3. @RequestParam 传递特殊字符串
6.2.3.4. 传递日期参数
6.2.3.5. 上传文件
6.2.3.6. @RequestParam - POST 数组
6.2.4. @RequestBody
6.2.4.1. @RequestBody 传递 List
6.2.4.2. 传递 Map 数据
6.2.5. @ModelAttribute
6.2.6. @CookieValue - 获取 Cookie 值
6.2.7. @RequestHeader - 获取 HTTP Header 信息
6.2.7.1. @RequestHeader 从 Http 头中获取变量
6.2.8. @SessionAttributes
6.2.9. @ResponseBody
6.2.9.1. 直接返回HTML
6.2.10. @CrossOrigin
6.2.10.1. maxAge
6.2.11. @SessionAttributes
6.2.12. ModelAndView
6.2.12.1. 变量传递
6.2.12.2. ModelMap 传递多个变量
6.2.12.3. redirect
6.2.12.4. ArrayList
6.2.12.5. HashMap
6.2.12.6. 传递对象
6.2.12.7.
6.2.13. HttpServletRequest / HttpServletResponse
6.2.13.1. HttpServletResponse
6.2.13.2. HttpServletRequest
6.3. @RestController
6.3.1. 返回实体
6.3.2. JSON
6.3.3. 处理原始 RAW JSON 数据
6.3.4. 返回 JSON 对象 NULL 专为 "" 字符串
6.3.5. XML
6.3.6. 兼容传统 json 接口
6.3.7. @PageableDefault 分页
6.3.8. 上传文件
6.4. View
6.4.1. Using Spring’s form tag library
6.4.1.1. css
6.4.1.2. cssClass
6.4.2. Thymeleaf
6.4.2.1. Maven pom.xml
6.4.2.2. Spring 配置
6.4.2.3. controller
6.4.2.4. HTML5 Template
6.4.2.5. thymeleaf 渲染表格
6.4.2.6. URL 链接
6.4.2.7. 拆分字符串
6.4.2.8. 日期格式化
6.4.3. FreeMarker
6.5. Service
6.5.1. Application
6.5.2. 定义接口
6.5.3. 实现接口
6.5.4. 调用 Service
6.6. i18n 国际化
6.6.1. 在 appliction.properties 中配置启用 i18n
6.6.2. 创建语言包文件
6.6.3. 控制器重引用语言包
6.6.4. 参数传递
6.7. FAQ
6.7.1. o.s.web.servlet.PageNotFound
6.7.2. HTTP Status 500 - Handler processing failed; nested exception is java.lang.NoClassDefFoundError: javax/servlet/jsp/jstl/core/Config
6.7.3. 同时使用 Thymeleaf 与 JSP
6.7.4. 排除静态内容
6.7.5. HTTP Status 406
6.7.6. Caused by: java.lang.IllegalArgumentException: Not a managed type: class common.domain.Article
6.7.7. {"error":"unauthorized","error_description":"Full authentication is required to access this resource"}
7. RestTemplate - Spring4 Restful
7.1. RestTemplate Example
7.1.1. pom.xml
7.1.2. web.xml
7.1.3. springframework.xml
7.1.4. RestController
7.1.5. POJO
7.1.6. 测试
7.2. getForObject
7.2.1. 返回字符串
7.2.2. 传递 GET 参数
7.3. postForObject
7.3.1. 传递对象
7.3.2. 传递数据结构 MultiValueMap
7.4. postForEntity
7.5. PUT
7.6. Delete
7.7. 上传文件
7.8. HTTP Auth
7.8.1. Client
7.9. PKCS12
8. Spring Data
8.1. Spring Data Redis
8.1.1. 集成 Redis
8.1.1.1. pom.xml
8.1.1.2. springframework-servlet.xml
8.1.1.3. Controller
8.1.1.4. index.jsp
8.1.1.5. 测试
8.1.2. RedisTemplate
8.1.2.1. stringRedisTemplate
8.1.2.2. 删除 key
8.1.2.3. ZSET 数据类型
8.1.2.4. Hash
8.1.2.5. List
8.1.2.6. 过期时间未执行
8.1.2.7. 存储 Json 对象
8.1.3. Spring Data Redis - Repository Examples
8.1.3.1. @EnableRedisRepositories 启动 Redis 仓库
8.1.3.2. 定义 Domain 类
8.1.3.3. Repository 接口
8.1.3.4. 测试代码
8.2. Spring Data MongoDB
8.2.1. Example Spring Data MongoDB
8.2.1.1. pom.xml
8.2.1.2. springframework-servlet.xml
8.2.1.3. POJO
8.2.1.4. Controller
8.2.1.5. 查看测试结果
8.2.1.6. 条件查询
8.2.2. @Document
8.2.2.1. 指定表名
8.2.2.2. @Id
8.2.2.3. @Version
8.2.2.4. @Field 定义字段名
8.2.2.5. @Indexed
8.2.2.6. @CompoundIndex 复合索引
8.2.2.7. @TextIndexed
8.2.2.8. @GeoSpatialIndex 地理位置索引
8.2.2.9. @Transient 丢弃数据，不存到 mongodb
8.2.2.10. @DBRef 做外外键引用
8.2.2.11. @DateTimeFormat
8.2.2.12. @JsonFormat 格式化 json 时间格式
8.2.2.13. @NumberFormat
8.2.2.14. 在 @Document 中使用 Enum 类型
8.2.2.15. 在 @Document 中定义数据结构 List/Map
8.2.2.16. GeoJson 数据类型
8.2.3. MongoRepository
8.2.3.1. 扫描仓库接口
8.2.3.2. findAll()
8.2.3.3. deleteAll()
8.2.3.4. save()
8.2.3.5. count()
8.2.3.6. exists() 判断是否存在
8.2.3.7. existsById()
8.2.3.8. findByXXXX
8.2.3.9. findAll with OrderBy
8.2.3.10. findAll with Sort
8.2.3.11. FindAll with Pageable
8.2.3.12. StartingWith 和 EndingWith
8.2.3.13. Between
8.2.3.14. Before / After
8.2.3.15. @Query
8.2.4. mongoTemplate
8.2.4.1. Save 保存
8.2.4.2. Insert
8.2.4.3. updateFirst 修改符合条件第一条记录
8.2.4.4. updateMulti 修改符合条件的所有
8.2.4.5. 查找并保存
8.2.4.6. upsert - 修改符合条件时如果不存在则添加
8.2.4.7. 删除
8.2.4.8. 查找一条数据
8.2.4.9. 查找所有数据
8.2.4.10. Query
8.2.4.11. Criteria
8.2.4.12. Update
8.2.4.13. BasicUpdate
8.2.4.14. Sort
8.2.4.15. Query + PageRequest
8.2.4.16. newAggregation
8.2.4.17. 创建索引
8.2.4.18. 子对象操作
8.2.5. GeoJson 反序列化
8.2.6. FAQ
8.2.6.1. location object expected, location array not in correct format; nested exception is com.mongodb.MongoWriteException: location object expected, location array not in correct format
8.3. Spring Data MySQL
8.3.1. 选择数据库表引擎
8.3.2. @Entity
8.3.2.1. @Table
8.3.2.2. @Id
8.3.2.3. @Column
8.3.2.4. @Lob 注解属性将被持久化为 Blog 或 Clob 类型
8.3.2.5. @NotNull 不能为空声明
8.3.2.6. @JsonIgnore 返回json是不含有该字段
8.3.2.7. @Temporal 日期定义
8.3.2.8. @DateTimeFormat 处理日期时间格式
8.3.2.9. 默认时间规则
8.3.2.10. 索引
8.3.2.11. Enum 枚举数据类型
8.3.2.12. SET 数据结构
8.3.2.13. 整形数据类型
8.3.2.14. JSON 数据类型
8.3.2.15. @JoinColumn
8.3.2.16. @OneToOne
8.3.2.17. OneToMany 一对多
8.3.2.18. ManyToMany 多对多
8.3.2.19. 外键级联删除
8.3.2.20. 其他
8.3.3. 创建复合主键
8.3.4. @EnableJpaAuditing 开启 JPA 审计功能
8.3.5. 实体集成
8.3.6. Repository
8.3.6.1. CrudRepository
8.3.6.2. JpaRepository
8.3.6.3. findByXXX
8.3.6.4. count 操作
8.3.6.5. OrderBy
8.3.6.6. GreaterThan
8.3.6.7. PageRequest 翻页操作
8.3.6.8. Sort 排序操作操作
8.3.6.9. Query
8.3.6.10. @Transactional
8.3.6.11. 锁 @Lock
8.4. EntityManager
8.5. JdbcTemplate
8.5.1. execute
8.5.2. queryForInt
8.5.3. queryForInt
8.5.4. queryForObject
8.5.4.1. 返回整形与字符型
8.5.4.2. 查询 Double 类型数据库
8.5.4.3. 返回日期
8.5.4.4. 返回结果集
8.5.4.5. 通过 "?" 向SQL传递参数
8.5.4.6. RowMapper 记录映射
8.5.5. queryForList
8.5.6. queryForMap
8.5.7. query
8.5.7.1. ResultSet
8.5.7.2. ResultSetExtractor
8.5.7.3. RowMapper
8.5.8. queryForRowSet
8.5.9. update
8.5.10.
8.5.11. 实例参考
8.5.11.1. 参数传递技巧
8.6. Elasticsearch
8.6.1. 内嵌 Elasticsearch
8.6.1.1. Maven
8.6.1.2. src/main/resources/application.properties
8.6.1.3. Domain Class
8.6.1.4. ElasticsearchRepository
8.6.1.5. SearchRestController
8.6.1.6. 测试
8.6.2. 集群模式
8.6.3. Document
8.6.4. Elasticsearch 删除操作
8.6.5. FAQ
8.6.5.1. java.lang.IllegalStateException: Received message from unsupported version: [2.0.0] minimal compatible version is: [5.0.0]
8.7. Spring Data FAQ
8.7.1. No identifier specified for entity
8.7.2. Oracle Date 类型显示日期和时间
8.7.3. java.lang.ClassCastException: java.lang.Long cannot be cast to java.lang.Integer
8.7.4. Executing an update/delete query; nested exception is javax.persistence.TransactionRequiredException: Executing an update/delete query
9. Spring Security
9.1. 配置 Spring Security
9.1.1. 默认配置
9.1.2. 设置用户名和密码
9.1.3. 禁用 Security
9.2. EnableWebSecurity
9.3. 访问控制列表（Access Control List，ACL）
9.3.1. antMatchers
9.3.2. HTTP Auth
9.3.3. Rest
9.3.4. hasRole
9.3.5. hasAnyRole()
9.3.6. X-Frame-Options 安全
9.4. WebSecurity
9.4.1. 正则匹配
9.5. AuthenticationManagerBuilder
9.5.1. 添加用户
9.5.2. 添加多个用户，并指定角色
9.6. corsConfigurationSource()
9.7. 获取当前用户
10. Interceptor
10.1. WebMvcConfigurerAdapter
10.2. HandlerInterceptor
11. Spring Cloud
11.1. Spring Cloud Config
11.1.1. Git 仓库
11.1.1.1. 分支
11.1.1.2. HTTP Auth
11.1.1.3. basedir
11.1.1.4. 本地git仓库
11.1.2. Server
11.1.2.1. Maven
11.1.2.2. Application
11.1.2.3. application.properties
11.1.2.4. 测试服务器
11.1.3. Client
11.1.3.1. Maven pom.xml
11.1.3.2. Application
11.1.3.3. bootstrap.properties
11.1.3.4. 测试 client
11.1.4. Config server 用户认证
11.1.4.1. Server 配置
11.1.4.2. Client 配置
11.1.5. 加密敏感数据
11.1.6. Spring Cloud Config JDBC Backend
11.1.6.1.
11.1.6.2.
11.1.6.3.
11.1.6.4. application.properties
11.1.7. Old
11.1.7.1. Server (Camden.SR5)
11.1.7.2. Client (Camden.SR5)
11.2. Spring Cloud Netflix
11.2.1. Eureka Server
11.2.1.1. Maven
11.2.1.2. Application
11.2.1.3. application.properties
11.2.1.4. 检查注册服务器
11.2.1.5. 为 Eureka Server 增加用户认证
11.2.2. Eureka Client
11.2.2.1. Maven
11.2.2.2. Application
11.2.2.3. RestController
11.2.2.4. application.properties
11.2.2.5. 测试
11.2.3. Feign client
11.2.3.1. Maven
11.2.3.2. Application
11.2.3.3. interface
11.2.3.4. application.properties
11.2.3.5. 测试
11.2.3.6. fallback
11.2.4. Zuul
11.2.4.1. Maven
11.2.4.2. EnableZuulProxy
11.2.4.3. application.yml
11.2.4.4. 负载均衡配置
11.3. Spring Cloud Finchley
11.4. Spring Cloud 相关的 application.properties 配置
11.4.1. 启用或禁用 bootstrap
11.5. FAQ
11.5.1. Feign请求超时
11.5.2. 已停止的微服务节点注销慢或不注销
11.5.3. Eureka Client (Dalston.SR1)
11.5.3.1. Maven
11.5.3.2. Application
11.5.3.3. RestController
11.5.3.4. application.properties
11.5.3.5. 测试
11.5.4. Feign 启动出错 PathVariable annotation was empty on param 0.
11.5.5. Feign 提示 Consider defining a bean of type 'common.feign.Cms' in your configuration.
12. WebFlux
12.1. Getting Started
12.1.1. Maven
12.1.2. Application
12.1.3. RestController
12.1.4. 测试
12.2. WebFlux Router
12.2.1. Component 原件
12.2.2. 路由配置
13. SpringBootTest
13.1. Maven 依赖
13.2. 测试类
13.2.1. Junit基本注解介绍
13.3.
13.3.1. Assert.assertEquals 判断相等
13.3.2. Assert.assertTrue
13.4. JPA 测试
13.5. Controller单元测试
13.6. WebTestClient
14. Tomcat Spring 运行环境
14.1. Maven
14.2. Spring Boot Quick start
14.2.1. pom.xml
14.2.2. Controller
14.3. Spring MVC configuration
14.4. Tomcat
14.5. 集成 Mybatis
14.5.1. pom.xml
14.5.2. properties
14.5.3. dataSource
14.5.4. SqlSessionFactory
14.5.5. Mapper 扫描
14.5.6. Mapper 单一class映射
14.5.7. Service
14.5.8. 测试实例
15. Miscellaneous
15.1. Object to Json
15.2. Json To Object
16. FAQ
16.1. org.hibernate.dialect.Oracle10gDialect does not support identity key generation
16.2. No identifier specified for entity
16.3. Could not read document: Invalid UTF-8 middle byte 0xd0
16.4. java.sql.SQLRecoverableException: IO Error: The Network Adapter could not establish the connection
16.5. Field javaMailSender in cn.netkiller.rest.EmailRestController required a bean of type 'org.springframework.mail.javamail.JavaMailSender' that could not be found.
16.6. org.postgresql.util.PSQLException: FATAL: no pg_hba.conf entry for host "172.16.0.3", user "test", database "test ", SSL off
16.7. Spring boot 怎样显示执行的SQL语句
16.8. Cannot determine embedded database driver class for database type NONE
16.9. Spring boot / Spring cloud 时区差8个小时
16.10. @Value 取不到值
16.11. Spring boot 2.1.0


第 4 章 Spring Tool Suite

目录

4.1. 安装
4.2. Dashboard

https://spring.io/tools/sts/

4.1. 安装

环境 Eclipse Jee Neon

进入菜单 Help -> Marketpalce...

索搜 Spring Tool Suite 注意版本号

点击Confirm按钮

点击Finish按钮，等候漫长的下载，同时Progress窗口中显示 Installing Software，安装成功会提示重新启动Eclipse.

点击 Yes 按钮重启 Eclipse


4.2. Dashboard

进入菜单 Help -> Dashboard


第 5 章 Spring Boot

目录

5.1. Spring Boot Quick start
5.1.1. pom.xml
5.1.2. Controller
5.2. Maven 插件
5.2.1. resource
5.3. 运行 Spring boot 项目
5.3.1. Maven run
5.3.2. Linux systemd
5.3.3. 传统 init.d 脚本
5.3.4. 编译用于Tomcat的 War
5.4. SpringApplication
5.4.1. @SpringBootApplication
5.4.1.1. 排除 @EnableAutoConfiguration 加载项
5.4.2. @EnableAutoConfiguration
5.4.3. @ComponentScan
5.4.4. @EntityScan 实体扫描
5.4.5. @EnableJpaRepositories
5.4.6. CharacterEncodingFilter
5.4.7. 隐藏 Banner
5.4.8. 实体与仓库扫描
5.4.9. 列出 Beans
5.5. application.properties
5.5.1. application.properties 参考
5.5.2. 启动指定参数
5.5.3. 加载排除
5.5.4. PID FILE
5.5.5. 内嵌 tomcat server
5.5.5.1. server.tomcat.basedir
5.5.5.2. server
5.5.5.3. access.log
5.5.5.4. logging
5.5.5.5. charset
5.5.5.6. Session 配置
5.5.5.7. cookie
5.5.5.8. error 路径
5.5.5.9. 压缩传输
5.5.5.10. server.servlet.context-path
5.5.5.11. ssl
5.5.5.12. 上传限制
5.5.6. JSON 输出与日期格式化
5.5.7. SMTP 相关配置
5.5.8. Redis
5.5.9. MongoDB
5.5.10. MySQL
5.5.11. Oracle
5.5.12. default_schema
5.5.13. datasource
5.5.14. velocity
5.5.15. Security 相关配置
5.5.16. MVC 配置
5.5.17. Kafka 相关配置
5.5.18.
5.6. Properties
5.6.1. @Value 注解
5.6.2. @PropertySource 注解载入 properties 文件
5.6.3. @EnableConfigurationProperties 引用自定义 *.properties 配置文件
5.6.4. 手工载入 *.properties 文件
5.6.5. spring.profiles.active 参数切换配置文件
5.6.6. SpringApplicationBuilder.properties() 方法添加配置项
5.7. Spring boot with Logging
5.7.1. 打印日志
5.7.2. 日志输出级别
5.7.3. 禁止控制台输出日志
5.7.4. 标准输出
5.7.5. 安天分隔日志
5.7.6. 按照文件尺寸分割日志
5.7.7. 指定Class过滤日志
5.7.8. 日志写入 MongoDB
5.7.9. logback 配置详解
5.7.9.1. configuration 属性配置
5.7.9.2. contextName 设置上下文名称
5.7.9.3. property 设置变量
5.7.9.4. encoder 日志格式设置
5.7.9.5. RollingFileAppender
5.8. Spring boot with Webpage
5.8.1. Maven
5.8.2. application.properties
5.8.3. Application
5.8.4. IndexController
5.8.5. src/main/webapp/WEB-INF/jsp/index.jsp
5.8.6. 集成模板引擎
5.9. Spring boot with Velocity template
5.9.1. Maven
5.9.2. Resource
5.9.3. Application
5.9.4. RestController
5.9.5. Test
5.10. Spring boot with Thymeleaf
5.10.1. Maven
5.10.2. application.properties
5.10.3. Controller
5.10.4. HTML5 Template
5.11. String boot with RestTemplate
5.11.1. 在控制器中完整实例
5.12. Spring boot with Session share
5.12.1. Redis
5.12.1.1. Maven
5.12.1.2. application.properties
5.12.1.3. Application
5.12.2. 测试 Session
5.12.3. JDBC
5.13. Spring boot with MongoDB
5.13.1. Maven
5.13.2. Application
5.13.3. MongoTemplate
5.13.4. Repository
5.14. Spring boot with MySQL
5.14.1. Maven
5.14.2. Resource
5.14.3. Application
5.14.4. JdbcTemplate
5.14.5. CrudRepository
5.15. Spring boot with Oracle
5.15.1. Maven
5.15.2. application.properties
5.15.3. Application
5.15.4. CrudRepository
5.15.5. JdbcTemplate
5.15.6. Controller
5.16. Spring boot with PostgreSQL
5.16.1. pom.xml
5.16.2. application.properties
5.16.3. Application
5.16.4. CrudRepository
5.16.5. JdbcTemplate
5.16.6. Controller
5.16.7. Test
5.17. 数据源配置
5.17.1. 多数据源配置
5.17.2. JPA 多数据源
5.17.3. Connection and Statement Pooling
5.17.3.1. org.apache.tomcat.jdbc.pool.DataSource
5.17.3.2. druid
5.17.3.3. c3p0 - JDBC3 Connection and Statement Pooling
5.17.3.4. dbcp2
5.17.3.5. bonecp
5.17.3.6. dbcp2
5.18. Spring boot with Redis
5.18.1. Spring boot with Redis
5.18.1.1. maven
5.18.1.2. application.properties
5.18.1.3. JUnit
5.18.1.4. Controller
5.18.2. Redis Pub/Sub
5.18.2.1. Redis配置类
5.18.2.2. 订阅和发布类
5.18.2.3. 消息发布演示
5.19. Spring boot with Caching
5.19.1. maven
5.19.2. 启用 Cache
5.19.3. 测试 Controller
5.19.4. @Cacheable 的用法
5.19.5. @CachePut 用法
5.19.6. 解决Expire 和 TTL 过期时间
5.19.7. SpEL表达式
5.20. Spring boot with Email
5.20.1. Maven
5.20.2. Resource
5.20.3. POJO
5.20.4. RestController
5.20.5. Test
5.21. Spring boot with Spring security
5.21.1. Maven
5.21.2. Reource
5.21.3. Application
5.21.4. WebSecurityConfigurer
5.21.5. RestController
5.21.6. 测试
5.21.7. Spring + Security + MongoDB
5.21.7.1. Account
5.21.7.2. AccountRepository
5.21.7.3. WebSecurityConfiguration
5.22. Spring boot with Oauth2
5.22.1. Maven
5.22.2. Password tools
5.22.3. Server
5.22.3.1. Maven
5.22.3.2. application.properties
5.22.3.3. EnableAuthorizationServer
5.22.3.4. EnableResourceServer
5.22.3.5. Entity Table
5.22.3.6. UserRepository
5.22.3.7. UserService
5.22.3.8. TestRestController
5.22.3.9. 数据库初始化
5.22.3.10. Test
5.22.4. Spring boot with Oauth2 RestTemplate
5.22.4.1. Maven
5.22.4.2. OAuth2ClientConfiguration.java
5.22.4.3. Application.java
5.22.4.4. application.properties
5.22.4.5. Controller
5.22.4.6. Test
5.23. Spring boot with Oauth2 jwt
5.23.1. Maven
5.23.2. Authorization Server
5.23.3. Resource Server
5.23.4. Web Security
5.23.5. 插入数据
5.23.6. 使用 CURL 测试 JWT
5.23.7. 测试 Shell
5.23.8. refresh_token
5.24. Spring boot with Oauth2 jwt 非对称证书
5.24.1. 创建证书
5.24.2. Authorization Server
5.24.3. Resource Server
5.25. Apple iOS 访问 Oauth2
5.26. Android Oauth2 + Jwt example
5.27. Spring boot with Scheduling
5.27.1. Application.java
5.27.2. Component
5.27.3. 查看日志
5.27.4. 计划任务控制开关
5.27.5. @Scheduled 详解
5.27.5.1. 每3秒钟一运行一次
5.27.5.2. 凌晨23点运行
5.28. Spring boot with Hessian
5.28.1. Maven
5.28.2. Application
5.28.3. HessianServiceExporter
5.28.4. Service
5.28.5. RestController
5.29. Spring boot with Apache Kafka
5.29.1. 安装 kafka
5.29.2. maven
5.29.3. Spring boot Application
5.29.4. EnableKafka
5.29.5. KafkaListener
5.29.6. 测试
5.29.7. 完整的发布订阅实例
5.29.7.1. Consumer
5.29.7.2. Producer
5.29.7.3. Test
5.29.8. Spring cloud with Kafka
5.29.8.1. Application 主文件
5.29.8.2. 资源配置文件
5.29.8.3. 启用 kafka
5.29.8.4. 消息发布主程序
5.30. Spring boot with RabbitMQ(AMQP)
5.30.1. maven
5.30.2. RabbitMQConfig
5.30.3. 生产者
5.30.4. 消费者
5.31. Spring boot with Git version
5.31.1. CommonRestController 公共控制器
5.31.2. VersionRestController 测试控制器
5.31.3. 创建 .gitattributes 文件
5.32. Spring boot with HTTPS SSL
5.32.1. 生成自签名证书
5.32.2. application.properties 配置文件
5.32.3. 启动 Spring boot
5.32.4. restTemplate 调用实例
5.33. Spring boot with Elasticsearch 2.x
5.33.1. Maven
5.33.2. Application
5.33.3. application.properties
5.33.4. Domain
5.33.5. ElasticsearchRepository
5.34. Spring boot with Elasticsearch 5.5.x
5.34.1. Maven
5.34.2. Application
5.34.3. application.properties
5.34.4. ElasticsearchConfiguration
5.34.5. RestController
5.35. Spring boot with Apache Hive
5.35.1. Maven
5.35.2. application.properties
5.35.3. Configuration
5.35.4. CURD 操作实例
5.36. Spring boot with Phoenix
5.36.1. Maven
5.36.2. application.properties
5.36.3. Configuration
5.37. Spring boot with Data restful
5.37.1. Maven
5.38. Spring boot with ELK(Elasticsearch + Logstash + Kibana)
5.38.1. TCP 方案
5.38.2. Redis 方案
5.38.3. Kafka 方案
5.38.4. Other
5.39. Springboot with Ethereum (web3j)
5.39.1. Maven
5.39.2. application.properties
5.39.3. TestRestController
5.39.4. 测试
5.40. 异步执行
5.40.1. 最简单的配置
5.40.2.
5.41. Spring boot with export csv.
5.42. Spring boot with lombok

注意以下使用 Spring boot 2.0.2

5.1. Spring Boot Quick start

5.1.1. pom.xml

<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>api.netkiller.cn</groupId>
    <artifactId>api.netkiller.cn</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>Skyline</name>
    <description>skylinechencf@gmail.com</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.4.0.RELEASE</version>
    </parent>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>

    <build>
        <sourceDirectory>src</sourceDirectory>
        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.3</version>
                <configuration>
                    <source />
                    <target />
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

5.1.2. Controller

package hello;

import org.springframework.boot.*;
import org.springframework.boot.autoconfigure.*;
import org.springframework.stereotype.*;
import org.springframework.web.bind.annotation.*;

@Controller
@EnableAutoConfiguration
public class SampleController {

    @RequestMapping("/")
    @ResponseBody
    String home() {
        return "Hello World!";
    }

    public static void main(String[] args) throws Exception {
        SpringApplication.run(SampleController.class, args);
    }
}

测试

curl http://127.0.0.1:8080/


5.2. Maven 插件

5.2.1. resource

将 resource 添加应用程序

<build>
    <resources>
        <resource>
            <directory>src/main/java/resources</directory>
            <filtering>true</filtering>
            <excludes>
                <exclude>*.jks</exclude>
            </excludes>
        </resource>
    </resources>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <configuration>
                <addResources>true</addResources>
            </configuration>
        </plugin>
    </plugins>
</build>


5.3. 运行 Spring boot 项目

5.3.1. Maven run

$ mvn spring-boot:run
$ mvn -P prod spring-boot:run

-P 指定 profile

打包后，使用jar包运行

$ mvn verify
$ mvn package
$ java -jar target/api.netkiller.cn-0.0.1-SNAPSHOT.jar

5.3.2. Linux systemd

/etc/systemd/system/spring.service

####################################################
# Homepage: http://netkiller.github.io
# Author: netkiller<netkiller@msn.com>
# Script: https://github.com/oscm/shell
# Date: 2015-11-03
####################################################
[Unit]
Description=Spring Boot Application
After=network.target

[Service]
User=www
Group=www
Type=oneshot
WorkingDirectory=/www/netkiller.cn/api.netkiller.cn
ExecStart=/usr/bin/java -jar your_jar_file.jar --spring.config.location=appliction-production.properties --spring.profiles.active=profile
#ExecStop=pkill -9 -f
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target

5.3.3. 传统 init.d 脚本

#!/bin/bash
##############################################
# Author: netkiller<netkiller@msn.com>
# Homepage: http://www.netkiller.cn
# Date: 2017-02-08
# $Author$
# $Id$
##############################################
# chkconfig: 345 100 02
# description: Spring boot application
# processname: springbootd
# File : springbootd
##############################################
BASEDIR="/www/netkiller.cn/api.netkiller.cn"
JAVA_HOME=/srv/java
JAVA_OPTS="-server -Xms2048m -Xmx8192m -Djava.security.egd=file:/dev/./urandom"
PACKAGE="api.netkiller.cn-0.0.2-release.jar"
CONFIG="--spring.config.location=$BASEDIR/application.properties"
USER=www
##############################################
NAME=springbootd
PROG="$JAVA_HOME/bin/java $JAVA_OPTS -jar $BASEDIR/$PACKAGE $CONFIG"
LOGFILE=/var/tmp/$NAME.log
PIDFILE=/var/tmp/$NAME.pid
ACCESS_LOG=/var/tmp/$NAME.access.log
##############################################

function log(){
    echo "$(date -d "today" +"%Y-%m-%d %H:%M:%S") $1	$2" >> $LOGFILE
}

function start(){
    if [ -f "$PIDFILE" ]; then
        echo $PIDFILE
        exit 2
    fi

    su - $USER -c "$PROG & echo \$! > $PIDFILE"
    log info start
}
function stop(){
    [ -f $PIDFILE ] && kill `cat $PIDFILE` && rm -rf $PIDFILE
    log info stop
}
function status(){
    ps aux | grep $PACKAGE | grep -v grep | grep -v status
    log info status
}
function reset(){
    pkill -f $PACKAGE
      [ -f $PIDFILE ] && rm -rf $PIDFILE
    log info reset
}

case "$1" in
    start)
        start
        ;;
    stop)
        stop
        ;;
    status)
        status
        ;;
    restart)
        stop
        start
        ;;
    log)
        tail -f $LOGFILE
        ;;
    reset)
        reset
        ;;
    *)
        echo $"Usage: $0 {start|stop|status|restart|log|reset}"
esac
exit $?

5.3.4. 编译用于Tomcat的 War

package demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.context.web.SpringBootServletInitializer;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Configuration
@ComponentScan
@EnableAutoConfiguration
public class Application extends SpringBootServletInitializer {

    private static Class<Application> applicationClass = Application.class;

    public static void main(String[] args) {
        SpringApplication.run(applicationClass, args);
    }

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        return application.sources(applicationClass);
    }
}


5.4. SpringApplication

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;

@SpringBootApplication
@EnableAutoConfiguration(exclude={DataSourceAutoConfiguration.class})
@ComponentScan({"cn.netkiller.controller"})
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}

5.4.1. @SpringBootApplication

@SpringBootApplication 是 @Configuration, @EnableAutoConfiguration 跟 @ComponentScan 的集合。

@SpringBootApplication

5.4.1.1. 排除 @EnableAutoConfiguration 加载项

@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)

5.4.2. @EnableAutoConfiguration

exclude 排除配置，下面例子是排除 DataSource配置

@EnableAutoConfiguration(exclude={DataSourceAutoConfiguration.class})

5.4.3. @ComponentScan

@ComponentScan 注入会扫描 @Controller 与 @RestController

@ComponentScan
@ComponentScan({"cn.netkiller.controller"})
@ComponentScan({"cn.netkiller.controller", "cn.netkiller.rest"})

5.4.4. @EntityScan 实体扫描

@EntityScan("common.domain")

5.4.5. @EnableJpaRepositories

扫描 Jpa 仓库

@EnableJpaRepositories("common.domain")

5.4.6. CharacterEncodingFilter

    public @Bean Filter characterEncodingFilter() {
        CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
        characterEncodingFilter.setEncoding("UTF-8");
        characterEncodingFilter.setForceEncoding(true);
        return characterEncodingFilter;
    }

5.4.7. 隐藏 Banner

隐藏 Spring Boot Banner

                . ____ _ __ _ _
                /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
                ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
                \\/ ___)| |_)| | | | | || (_| | ) ) ) )
                ' |____| .__|_| |_|_| |_\__, | / / / /
                =========|_|==============|___/=/_/_/_/
                :: Spring Boot :: (v2.0.2.RELEASE)

public static void main(String[] args) {
    SpringApplication app = new SpringApplication(Application.class);
    app.setShowBanner(false);
    app.run(args);
}

5.4.8. 实体与仓库扫描

@EntityScan(basePackages = { "cn.netkiller.model" })
@EnableJpaRepositories(basePackages = { "cn.netkiller.repository" })

5.4.9. 列出 Beans

package cn.netkiller;

import java.util.Arrays;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;
import org.springframework.scheduling.annotation.EnableScheduling;

@SpringBootApplication
@EnableAutoConfiguration
@ComponentScan
@EnableMongoRepositories
@EnableJpaRepositories
@EnableScheduling
public class Application {

    public static void main(String[] args) {
        //SpringApplication.run(Application.class, args);

        ApplicationContext ctx = SpringApplication.run(Application.class, args);

        System.out.println("Let's inspect the beans provided by Spring Boot:");

        String[] beanNames = ctx.getBeanDefinitionNames();
        Arrays.sort(beanNames);
        for (String beanName : beanNames) {
            System.out.println(beanName);
        }

    }
}


5.5. application.properties

https://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html

5.5.1. application.properties 参考

http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html

# ===================================================================
# COMMON SPRING BOOT PROPERTIES
#
# This sample file is provided as a guideline. Do NOT copy it in its
# entirety to your own application.               ^^^
# ===================================================================

# ----------------------------------------
# CORE PROPERTIES
# ----------------------------------------
debug=false # Enable debug logs.
trace=false # Enable trace logs.

# LOGGING
logging.config= # Location of the logging configuration file. For instance, `classpath:logback.xml` for Logback.
logging.exception-conversion-word=%wEx # Conversion word used when logging exceptions.
logging.file= # Log file name (for instance, `myapp.log`). Names can be an exact location or relative to the current directory.
logging.file.max-history=0 # Maximum of archive log files to keep. Only supported with the default logback setup.
logging.file.max-size=10MB # Maximum log file size. Only supported with the default logback setup.
logging.level.*= # Log levels severity mapping. For instance, `logging.level.org.springframework=DEBUG`.
logging.path= # Location of the log file. For instance, `/var/log`.
logging.pattern.console= # Appender pattern for output to the console. Supported only with the default Logback setup.
logging.pattern.dateformat=yyyy-MM-dd HH:mm:ss.SSS # Appender pattern for log date format. Supported only with the default Logback setup.
logging.pattern.file= # Appender pattern for output to a file. Supported only with the default Logback setup.
logging.pattern.level=%5p # Appender pattern for log level. Supported only with the default Logback setup.
logging.register-shutdown-hook=false # Register a shutdown hook for the logging system when it is initialized.

# AOP
spring.aop.auto=true # Add @EnableAspectJAutoProxy.
spring.aop.proxy-target-class=true # Whether subclass-based (CGLIB) proxies are to be created (true), as opposed to standard Java interface-based proxies (false).

# IDENTITY (ContextIdApplicationContextInitializer)
spring.application.name= # Application name.

# ADMIN (SpringApplicationAdminJmxAutoConfiguration)
spring.application.admin.enabled=false # Whether to enable admin features for the application.
spring.application.admin.jmx-name=org.springframework.boot:type=Admin,name=SpringApplication # JMX name of the application admin MBean.

# AUTO-CONFIGURATION
spring.autoconfigure.exclude= # Auto-configuration classes to exclude.

# BANNER
spring.banner.charset=UTF-8 # Banner file encoding.
spring.banner.location=classpath:banner.txt # Banner text resource location.
spring.banner.image.location=classpath:banner.gif # Banner image file location (jpg or png can also be used).
spring.banner.image.width=76 # Width of the banner image in chars.
spring.banner.image.height= # Height of the banner image in chars (default based on image height).
spring.banner.image.margin=2 # Left hand image margin in chars.
spring.banner.image.invert=false # Whether images should be inverted for dark terminal themes.

# SPRING CORE
spring.beaninfo.ignore=true # Whether to skip search of BeanInfo classes.

# SPRING CACHE (CacheProperties)
spring.cache.cache-names= # Comma-separated list of cache names to create if supported by the underlying cache manager.
spring.cache.caffeine.spec= # The spec to use to create caches. See CaffeineSpec for more details on the spec format.
spring.cache.couchbase.expiration=0ms # Entry expiration. By default the entries never expire. Note that this value is ultimately converted to seconds.
spring.cache.ehcache.config= # The location of the configuration file to use to initialize EhCache.
spring.cache.infinispan.config= # The location of the configuration file to use to initialize Infinispan.
spring.cache.jcache.config= # The location of the configuration file to use to initialize the cache manager.
spring.cache.jcache.provider= # Fully qualified name of the CachingProvider implementation to use to retrieve the JSR-107 compliant cache manager. Needed only if more than one JSR-107 implementation is available on the classpath.
spring.cache.redis.cache-null-values=true # Allow caching null values.
spring.cache.redis.key-prefix= # Key prefix.
spring.cache.redis.time-to-live=0ms # Entry expiration. By default the entries never expire.
spring.cache.redis.use-key-prefix=true # Whether to use the key prefix when writing to Redis.
spring.cache.type= # Cache type. By default, auto-detected according to the environment.

# SPRING CONFIG - using environment property only (ConfigFileApplicationListener)
spring.config.additional-location= # Config file locations used in addition to the defaults.
spring.config.location= # Config file locations that replace the defaults.
spring.config.name=application # Config file name.

# HAZELCAST (HazelcastProperties)
spring.hazelcast.config= # The location of the configuration file to use to initialize Hazelcast.

# PROJECT INFORMATION (ProjectInfoProperties)
spring.info.build.location=classpath:META-INF/build-info.properties # Location of the generated build-info.properties file.
spring.info.git.location=classpath:git.properties # Location of the generated git.properties file.

# JMX
spring.jmx.default-domain= # JMX domain name.
spring.jmx.enabled=true # Expose management beans to the JMX domain.
spring.jmx.server=mbeanServer # MBeanServer bean name.

# Email (MailProperties)
spring.mail.default-encoding=UTF-8 # Default MimeMessage encoding.
spring.mail.host= # SMTP server host. For instance, `smtp.example.com`.
spring.mail.jndi-name= # Session JNDI name. When set, takes precedence over other Session settings.
spring.mail.password= # Login password of the SMTP server.
spring.mail.port= # SMTP server port.
spring.mail.properties.*= # Additional JavaMail Session properties.
spring.mail.protocol=smtp # Protocol used by the SMTP server.
spring.mail.test-connection=false # Whether to test that the mail server is available on startup.
spring.mail.username= # Login user of the SMTP server.

# APPLICATION SETTINGS (SpringApplication)
spring.main.banner-mode=console # Mode used to display the banner when the application runs.
spring.main.sources= # Sources (class names, package names, or XML resource locations) to include in the ApplicationContext.
spring.main.web-application-type= # Flag to explicitly request a specific type of web application. If not set, auto-detected based on the classpath.

# FILE ENCODING (FileEncodingApplicationListener)
spring.mandatory-file-encoding= # Expected character encoding the application must use.

# INTERNATIONALIZATION (MessageSourceProperties)
spring.messages.always-use-message-format=false # Whether to always apply the MessageFormat rules, parsing even messages without arguments.
spring.messages.basename=messages # Comma-separated list of basenames (essentially a fully-qualified classpath location), each following the ResourceBundle convention with relaxed support for slash based locations.
spring.messages.cache-duration= # Loaded resource bundle files cache duration. When not set, bundles are cached forever. If a duration suffix is not specified, seconds will be used.
spring.messages.encoding=UTF-8 # Message bundles encoding.
spring.messages.fallback-to-system-locale=true # Whether to fall back to the system Locale if no files for a specific Locale have been found.
spring.messages.use-code-as-default-message=false # Whether to use the message code as the default message instead of throwing a "NoSuchMessageException". Recommended during development only.

# OUTPUT
spring.output.ansi.enabled=detect # Configures the ANSI output.

# PID FILE (ApplicationPidFileWriter)
spring.pid.fail-on-write-error= # Fails if ApplicationPidFileWriter is used but it cannot write the PID file.
spring.pid.file= # Location of the PID file to write (if ApplicationPidFileWriter is used).

# PROFILES
spring.profiles.active= # Comma-separated list of active profiles. Can be overridden by a command line switch.
spring.profiles.include= # Unconditionally activate the specified comma-separated list of profiles (or list of profiles if using YAML).

# QUARTZ SCHEDULER (QuartzProperties)
spring.quartz.jdbc.comment-prefix=-- # Prefix for single-line comments in SQL initialization scripts.
spring.quartz.jdbc.initialize-schema=embedded # Database schema initialization mode.
spring.quartz.jdbc.schema=classpath:org/quartz/impl/jdbcjobstore/tables_@@platform@@.sql # Path to the SQL file to use to initialize the database schema.
spring.quartz.job-store-type=memory # Quartz job store type.
spring.quartz.properties.*= # Additional Quartz Scheduler properties.

# REACTOR (ReactorCoreProperties)
spring.reactor.stacktrace-mode.enabled=false # Whether Reactor should collect stacktrace information at runtime.

# SENDGRID (SendGridAutoConfiguration)
spring.sendgrid.api-key= # SendGrid API key.
spring.sendgrid.proxy.host= # SendGrid proxy host.
spring.sendgrid.proxy.port= # SendGrid proxy port.

# ----------------------------------------
# WEB PROPERTIES
# ----------------------------------------

# EMBEDDED SERVER CONFIGURATION (ServerProperties)
server.address= # Network address to which the server should bind.
server.compression.enabled=false # Whether response compression is enabled.
server.compression.excluded-user-agents= # List of user-agents to exclude from compression.
server.compression.mime-types=text/html,text/xml,text/plain,text/css,text/javascript,application/javascript # Comma-separated list of MIME types that should be compressed.
server.compression.min-response-size=2048 # Minimum "Content-Length" value that is required for compression to be performed.
server.connection-timeout= # Time that connectors wait for another HTTP request before closing the connection. When not set, the connector's container-specific default is used. Use a value of -1 to indicate no (that is, an infinite) timeout.
server.error.include-exception=false # Include the "exception" attribute.
server.error.include-stacktrace=never # When to include a "stacktrace" attribute.
server.error.path=/error # Path of the error controller.
server.error.whitelabel.enabled=true # Whether to enable the default error page displayed in browsers in case of a server error.
server.http2.enabled=false # Whether to enable HTTP/2 support, if the current environment supports it.
server.jetty.acceptors=-1 # Number of acceptor threads to use. When the value is -1, the default, the number of acceptors is derived from the operating environment.
server.jetty.accesslog.append=false # Append to log.
server.jetty.accesslog.date-format=dd/MMM/yyyy:HH:mm:ss Z # Timestamp format of the request log.
server.jetty.accesslog.enabled=false # Enable access log.
server.jetty.accesslog.extended-format=false # Enable extended NCSA format.
server.jetty.accesslog.file-date-format= # Date format to place in log file name.
server.jetty.accesslog.filename= # Log filename. If not specified, logs redirect to "System.err".
server.jetty.accesslog.locale= # Locale of the request log.
server.jetty.accesslog.log-cookies=false # Enable logging of the request cookies.
server.jetty.accesslog.log-latency=false # Enable logging of request processing time.
server.jetty.accesslog.log-server=false # Enable logging of the request hostname.
server.jetty.accesslog.retention-period=31 # Number of days before rotated log files are deleted.
server.jetty.accesslog.time-zone=GMT # Timezone of the request log.
server.jetty.max-http-post-size=200000 # Maximum size in bytes of the HTTP post or put content.
server.jetty.selectors=-1 # Number of selector threads to use. When the value is -1, the default, the number of selectors is derived from the operating environment.
server.max-http-header-size=0 # Maximum size, in bytes, of the HTTP message header.
server.port=8080 # Server HTTP port.
server.server-header= # Value to use for the Server response header (if empty, no header is sent).
server.use-forward-headers= # Whether X-Forwarded-* headers should be applied to the HttpRequest.
server.servlet.context-parameters.*= # Servlet context init parameters.
server.servlet.context-path= # Context path of the application.
server.servlet.application-display-name=application # Display name of the application.
server.servlet.jsp.class-name=org.apache.jasper.servlet.JspServlet # The class name of the JSP servlet.
server.servlet.jsp.init-parameters.*= # Init parameters used to configure the JSP servlet.
server.servlet.jsp.registered=true # Whether the JSP servlet is registered.
server.servlet.path=/ # Path of the main dispatcher servlet.
server.servlet.session.cookie.comment= # Comment for the session cookie.
server.servlet.session.cookie.domain= # Domain for the session cookie.
server.servlet.session.cookie.http-only= # "HttpOnly" flag for the session cookie.
server.servlet.session.cookie.max-age= # Maximum age of the session cookie. If a duration suffix is not specified, seconds will be used.
server.servlet.session.cookie.name= # Session cookie name.
server.servlet.session.cookie.path= # Path of the session cookie.
server.servlet.session.cookie.secure= # "Secure" flag for the session cookie.
server.servlet.session.persistent=false # Whether to persist session data between restarts.
server.servlet.session.store-dir= # Directory used to store session data.
server.servlet.session.timeout= # Session timeout. If a duration suffix is not specified, seconds will be used.
server.servlet.session.tracking-modes= # Session tracking modes (one or more of the following: "cookie", "url", "ssl").
server.ssl.ciphers= # Supported SSL ciphers.
server.ssl.client-auth= # Whether client authentication is wanted ("want") or needed ("need"). Requires a trust store.
server.ssl.enabled= # Enable SSL support.
server.ssl.enabled-protocols= # Enabled SSL protocols.
server.ssl.key-alias= # Alias that identifies the key in the key store.
server.ssl.key-password= # Password used to access the key in the key store.
server.ssl.key-store= # Path to the key store that holds the SSL certificate (typically a jks file).
server.ssl.key-store-password= # Password used to access the key store.
server.ssl.key-store-provider= # Provider for the key store.
server.ssl.key-store-type= # Type of the key store.
server.ssl.protocol=TLS # SSL protocol to use.
server.ssl.trust-store= # Trust store that holds SSL certificates.
server.ssl.trust-store-password= # Password used to access the trust store.
server.ssl.trust-store-provider= # Provider for the trust store.
server.ssl.trust-store-type= # Type of the trust store.
server.tomcat.accept-count=100 # Maximum queue length for incoming connection requests when all possible request processing threads are in use.
server.tomcat.accesslog.buffered=true # Whether to buffer output such that it is flushed only periodically.
server.tomcat.accesslog.directory=logs # Directory in which log files are created. Can be absolute or relative to the Tomcat base dir.
server.tomcat.accesslog.enabled=false # Enable access log.
server.tomcat.accesslog.file-date-format=.yyyy-MM-dd # Date format to place in the log file name.
server.tomcat.accesslog.pattern=common # Format pattern for access logs.
server.tomcat.accesslog.prefix=access_log # Log file name prefix.
server.tomcat.accesslog.rename-on-rotate=false # Whether to defer inclusion of the date stamp in the file name until rotate time.
server.tomcat.accesslog.request-attributes-enabled=false # Set request attributes for the IP address, Hostname, protocol, and port used for the request.
server.tomcat.accesslog.rotate=true # Whether to enable access log rotation.
server.tomcat.accesslog.suffix=.log # Log file name suffix.
server.tomcat.additional-tld-skip-patterns= # Comma-separated list of additional patterns that match jars to ignore for TLD scanning.
server.tomcat.background-processor-delay=10 # Delay in seconds between the invocation of backgroundProcess methods.
server.tomcat.basedir= # Tomcat base directory. If not specified, a temporary directory is used.
server.tomcat.internal-proxies=10\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|\\
        192\\.168\\.\\d{1,3}\\.\\d{1,3}|\\
        169\\.254\\.\\d{1,3}\\.\\d{1,3}|\\
        127\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|\\
        172\\.1[6-9]{1}\\.\\d{1,3}\\.\\d{1,3}|\\
        172\\.2[0-9]{1}\\.\\d{1,3}\\.\\d{1,3}|\\
        172\\.3[0-1]{1}\\.\\d{1,3}\\.\\d{1,3} # Regular expression matching trusted IP addresses.
server.tomcat.max-connections=10000 # Maximum number of connections that the server will accept and process at any given time.
server.tomcat.max-http-header-size=0 # Maximum size in bytes of the HTTP message header.
server.tomcat.max-http-post-size=2097152 # Maximum size in bytes of the HTTP post content.
server.tomcat.max-threads=200 # Maximum amount of worker threads.
server.tomcat.min-spare-threads=10 # Minimum amount of worker threads.
server.tomcat.port-header=X-Forwarded-Port # Name of the HTTP header used to override the original port value.
server.tomcat.protocol-header= # Header that holds the incoming protocol, usually named "X-Forwarded-Proto".
server.tomcat.protocol-header-https-value=https # Value of the protocol header indicating whether the incoming request uses SSL.
server.tomcat.redirect-context-root=true # Whether requests to the context root should be redirected by appending a / to the path.
server.tomcat.remote-ip-header= # Name of the HTTP header from which the remote IP is extracted. For instance, `X-FORWARDED-FOR`.
server.tomcat.resource.cache-ttl= # Time-to-live of the static resource cache.
server.tomcat.uri-encoding=UTF-8 # Character encoding to use to decode the URI.
server.tomcat.use-relative-redirects= # Whether HTTP 1.1 and later location headers generated by a call to sendRedirect will use relative or absolute redirects.
server.undertow.accesslog.dir= # Undertow access log directory.
server.undertow.accesslog.enabled=false # Whether to enable the access log.
server.undertow.accesslog.pattern=common # Format pattern for access logs.
server.undertow.accesslog.prefix=access_log. # Log file name prefix.
server.undertow.accesslog.rotate=true # Whether to enable access log rotation.
server.undertow.accesslog.suffix=log # Log file name suffix.
server.undertow.buffer-size= # Size of each buffer, in bytes.
server.undertow.direct-buffers= # Allocate buffers outside the Java heap. The default is derived from the maximum amount of memory that is available to the JVM.
server.undertow.eager-filter-init=true # Whether servlet filters should be initialized on startup.
server.undertow.io-threads= # Number of I/O threads to create for the worker. The default is derived from the number of available processors.
server.undertow.max-http-post-size=-1 # Maximum size in bytes of the HTTP post content. When the value is -1, the default, the size is unlimited.
server.undertow.worker-threads= # Number of worker threads. The default is 8 times the number of I/O threads.

# FREEMARKER (FreeMarkerProperties)
spring.freemarker.allow-request-override=false # Whether HttpServletRequest attributes are allowed to override (hide) controller generated model attributes of the same name.
spring.freemarker.allow-session-override=false # Whether HttpSession attributes are allowed to override (hide) controller generated model attributes of the same name.
spring.freemarker.cache=false # Whether to enable template caching.
spring.freemarker.charset=UTF-8 # Template encoding.
spring.freemarker.check-template-location=true # Whether to check that the templates location exists.
spring.freemarker.content-type=text/html # Content-Type value.
spring.freemarker.enabled=true # Whether to enable MVC view resolution for this technology.
spring.freemarker.expose-request-attributes=false # Whether all request attributes should be added to the model prior to merging with the template.
spring.freemarker.expose-session-attributes=false # Whether all HttpSession attributes should be added to the model prior to merging with the template.
spring.freemarker.expose-spring-macro-helpers=true # Whether to expose a RequestContext for use by Spring's macro library, under the name "springMacroRequestContext".
spring.freemarker.prefer-file-system-access=true # Whether to 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 are passed to FreeMarker's Configuration.
spring.freemarker.suffix=.ftl # 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.

# GROOVY TEMPLATES (GroovyTemplateProperties)
spring.groovy.template.allow-request-override=false # Whether HttpServletRequest attributes are allowed to override (hide) controller generated model attributes of the same name.
spring.groovy.template.allow-session-override=false # Whether HttpSession attributes are allowed to override (hide) controller generated model attributes of the same name.
spring.groovy.template.cache=false # Whether to enable template caching.
spring.groovy.template.charset=UTF-8 # Template encoding.
spring.groovy.template.check-template-location=true # Whether to check that the templates location exists.
spring.groovy.template.configuration.*= # See GroovyMarkupConfigurer
spring.groovy.template.content-type=text/html # Content-Type value.
spring.groovy.template.enabled=true # Whether to enable MVC view resolution for this technology.
spring.groovy.template.expose-request-attributes=false # Whether all request attributes should be added to the model prior to merging with the template.
spring.groovy.template.expose-session-attributes=false # Whether all HttpSession attributes should be added to the model prior to merging with the template.
spring.groovy.template.expose-spring-macro-helpers=true # Whether to expose a RequestContext for use by Spring's macro library, under the name "springMacroRequestContext".
spring.groovy.template.prefix= # Prefix that gets prepended to view names when building a URL.
spring.groovy.template.request-context-attribute= # Name of the RequestContext attribute for all views.
spring.groovy.template.resource-loader-path=classpath:/templates/ # Template path.
spring.groovy.template.suffix=.tpl # Suffix that gets appended to view names when building a URL.
spring.groovy.template.view-names= # White list of view names that can be resolved.

# SPRING HATEOAS (HateoasProperties)
spring.hateoas.use-hal-as-default-json-media-type=true # Whether application/hal+json responses should be sent to requests that accept application/json.

# HTTP message conversion
spring.http.converters.preferred-json-mapper= # Preferred JSON mapper to use for HTTP message conversion. By default, auto-detected according to the environment.

# HTTP encoding (HttpEncodingProperties)
spring.http.encoding.charset=UTF-8 # Charset of HTTP requests and responses. Added to the "Content-Type" header if not set explicitly.
spring.http.encoding.enabled=true # Whether to enable http encoding support.
spring.http.encoding.force= # Whether to force the encoding to the configured charset on HTTP requests and responses.
spring.http.encoding.force-request= # Whether to force the encoding to the configured charset on HTTP requests. Defaults to true when "force" has not been specified.
spring.http.encoding.force-response= # Whether to force the encoding to the configured charset on HTTP responses.
spring.http.encoding.mapping= # Locale in which to encode mapping.

# MULTIPART (MultipartProperties)
spring.servlet.multipart.enabled=true # Whether to enable support of multipart uploads.
spring.servlet.multipart.file-size-threshold=0 # Threshold after which files are written to disk. Values can use the suffixes "MB" or "KB" to indicate megabytes or kilobytes, respectively.
spring.servlet.multipart.location= # Intermediate location of uploaded files.
spring.servlet.multipart.max-file-size=1MB # Max file size. Values can use the suffixes "MB" or "KB" to indicate megabytes or kilobytes, respectively.
spring.servlet.multipart.max-request-size=10MB # Max request size. Values can use the suffixes "MB" or "KB" to indicate megabytes or kilobytes, respectively.
spring.servlet.multipart.resolve-lazily=false # Whether to resolve the multipart request lazily at the time of file or parameter access.

# JACKSON (JacksonProperties)
spring.jackson.date-format= # Date format string or a fully-qualified date format class name. For instance, `yyyy-MM-dd HH:mm:ss`.
spring.jackson.default-property-inclusion= # Controls the inclusion of properties during serialization. Configured with one of the values in Jackson's JsonInclude.Include enumeration.
spring.jackson.deserialization.*= # Jackson on/off features that affect the way Java objects are deserialized.
spring.jackson.generator.*= # Jackson on/off features for generators.
spring.jackson.joda-date-time-format= # Joda date time format string. If not configured, "date-format" is used as a fallback if it is configured with a format string.
spring.jackson.locale= # Locale used for formatting.
spring.jackson.mapper.*= # Jackson general purpose on/off features.
spring.jackson.parser.*= # Jackson on/off features for parsers.
spring.jackson.property-naming-strategy= # One of the constants on Jackson's PropertyNamingStrategy. Can also be a fully-qualified class name of a PropertyNamingStrategy subclass.
spring.jackson.serialization.*= # Jackson on/off features that affect the way Java objects are serialized.
spring.jackson.time-zone= #  Time zone used when formatting dates. For instance, "America/Los_Angeles" or "GMT+10".

# GSON (GsonProperties)
spring.gson.date-format= # Format to use when serializing Date objects.
spring.gson.disable-html-escaping= # Whether to disable the escaping of HTML characters such as '<', '>', etc.
spring.gson.disable-inner-class-serialization= # Whether to exclude inner classes during serialization.
spring.gson.enable-complex-map-key-serialization= # Whether to enable serialization of complex map keys (i.e. non-primitives).
spring.gson.exclude-fields-without-expose-annotation= # Whether to exclude all fields from consideration for serialization or deserialization that do not have the "Expose" annotation.
spring.gson.field-naming-policy= # Naming policy that should be applied to an object's field during serialization and deserialization.
spring.gson.generate-non-executable-json= # Whether to generate non executable JSON by prefixing the output with some special text.
spring.gson.lenient= # Whether to be lenient about parsing JSON that doesn't conform to RFC 4627.
spring.gson.long-serialization-policy= # Serialization policy for Long and long types.
spring.gson.pretty-printing= # Whether to output serialized JSON that fits in a page for pretty printing.
spring.gson.serialize-nulls= # Whether to serialize null fields.

# JERSEY (JerseyProperties)
spring.jersey.application-path= # Path that serves as the base URI for the application. If specified, overrides the value of "@ApplicationPath".
spring.jersey.filter.order=0 # Jersey filter chain order.
spring.jersey.init.*= # Init parameters to pass to Jersey through the servlet or filter.
spring.jersey.servlet.load-on-startup=-1 # Load on startup priority of the Jersey servlet.
spring.jersey.type=servlet # Jersey integration type.

# SPRING LDAP (LdapProperties)
spring.ldap.anonymous-read-only=false # Whether read-only operations should use an anonymous environment.
spring.ldap.base= # Base suffix from which all operations should originate.
spring.ldap.base-environment.*= # LDAP specification settings.
spring.ldap.password= # Login password of the server.
spring.ldap.urls= # LDAP URLs of the server.
spring.ldap.username= # Login username of the server.

# EMBEDDED LDAP (EmbeddedLdapProperties)
spring.ldap.embedded.base-dn= # List of base DNs.
spring.ldap.embedded.credential.username= # Embedded LDAP username.
spring.ldap.embedded.credential.password= # Embedded LDAP password.
spring.ldap.embedded.ldif=classpath:schema.ldif # Schema (LDIF) script resource reference.
spring.ldap.embedded.port=0 # Embedded LDAP port.
spring.ldap.embedded.validation.enabled=true # Whether to enable LDAP schema validation.
spring.ldap.embedded.validation.schema= # Path to the custom schema.

# MUSTACHE TEMPLATES (MustacheAutoConfiguration)
spring.mustache.allow-request-override=false # Whether HttpServletRequest attributes are allowed to override (hide) controller generated model attributes of the same name.
spring.mustache.allow-session-override=false # Whether HttpSession attributes are allowed to override (hide) controller generated model attributes of the same name.
spring.mustache.cache=false # Whether to enable template caching.
spring.mustache.charset=UTF-8 # Template encoding.
spring.mustache.check-template-location=true # Whether to check that the templates location exists.
spring.mustache.content-type=text/html # Content-Type value.
spring.mustache.enabled=true # Whether to enable MVC view resolution for this technology.
spring.mustache.expose-request-attributes=false # Whether all request attributes should be added to the model prior to merging with the template.
spring.mustache.expose-session-attributes=false # Whether all HttpSession attributes should be added to the model prior to merging with the template.
spring.mustache.expose-spring-macro-helpers=true # Whether to expose a RequestContext for use by Spring's macro library, under the name "springMacroRequestContext".
spring.mustache.prefix=classpath:/templates/ # Prefix to apply to template names.
spring.mustache.request-context-attribute= # Name of the RequestContext attribute for all views.
spring.mustache.suffix=.mustache # Suffix to apply to template names.
spring.mustache.view-names= # White list of view names that can be resolved.

# SPRING MVC (WebMvcProperties)
spring.mvc.async.request-timeout= # Amount of time before asynchronous request handling times out.
spring.mvc.contentnegotiation.favor-parameter=false # Whether a request parameter ("format" by default) should be used to determine the requested media type.
spring.mvc.contentnegotiation.favor-path-extension=false # Whether the path extension in the URL path should be used to determine the requested media type.
spring.mvc.contentnegotiation.media-types.*= # Map file extensions to media types for content negotiation. For instance, yml to text/yaml.
spring.mvc.contentnegotiation.parameter-name= # Query parameter name to use when "favor-parameter" is enabled.
spring.mvc.date-format= # Date format to use. For instance, `dd/MM/yyyy`.
spring.mvc.dispatch-trace-request=false # Whether to dispatch TRACE requests to the FrameworkServlet doService method.
spring.mvc.dispatch-options-request=true # Whether to dispatch OPTIONS requests to the FrameworkServlet doService method.
spring.mvc.favicon.enabled=true # Whether to enable resolution of favicon.ico.
spring.mvc.formcontent.putfilter.enabled=true # Whether to enable Spring's HttpPutFormContentFilter.
spring.mvc.ignore-default-model-on-redirect=true # Whether the content of the "default" model should be ignored during redirect scenarios.
spring.mvc.locale= # Locale to use. By default, this locale is overridden by the "Accept-Language" header.
spring.mvc.locale-resolver=accept-header # Define how the locale should be resolved.
spring.mvc.log-resolved-exception=false # Whether to enable warn logging of exceptions resolved by a "HandlerExceptionResolver".
spring.mvc.message-codes-resolver-format= # Formatting strategy for message codes. For instance, `PREFIX_ERROR_CODE`.
spring.mvc.pathmatch.use-registered-suffix-pattern=false # Whether suffix pattern matching should work only against extensions registered with "spring.mvc.contentnegotiation.media-types.*".
spring.mvc.pathmatch.use-suffix-pattern=false # Whether to use suffix pattern match (".*") when matching patterns to requests.
spring.mvc.servlet.load-on-startup=-1 # Load on startup priority of the dispatcher servlet.
spring.mvc.static-path-pattern=/** # Path pattern used for static resources.
spring.mvc.throw-exception-if-no-handler-found=false # Whether a "NoHandlerFoundException" should be thrown if no Handler was found to process a request.
spring.mvc.view.prefix= # Spring MVC view prefix.
spring.mvc.view.suffix= # Spring MVC view suffix.

# SPRING RESOURCES HANDLING (ResourceProperties)
spring.resources.add-mappings=true # Whether to enable default resource handling.
spring.resources.cache.cachecontrol.cache-private= # Indicate that the response message is intended for a single user and must not be stored by a shared cache.
spring.resources.cache.cachecontrol.cache-public= # Indicate that any cache may store the response.
spring.resources.cache.cachecontrol.max-age= # Maximum time the response should be cached, in seconds if no duration suffix is not specified.
spring.resources.cache.cachecontrol.must-revalidate= # Indicate that once it has become stale, a cache must not use the response without re-validating it with the server.
spring.resources.cache.cachecontrol.no-cache= # Indicate that the cached response can be reused only if re-validated with the server.
spring.resources.cache.cachecontrol.no-store= # Indicate to not cache the response in any case.
spring.resources.cache.cachecontrol.no-transform= # Indicate intermediaries (caches and others) that they should not transform the response content.
spring.resources.cache.cachecontrol.proxy-revalidate= # Same meaning as the "must-revalidate" directive, except that it does not apply to private caches.
spring.resources.cache.cachecontrol.s-max-age= # Maximum time the response should be cached by shared caches, in seconds if no duration suffix is not specified.
spring.resources.cache.cachecontrol.stale-if-error= # Maximum time the response may be used when errors are encountered, in seconds if no duration suffix is not specified.
spring.resources.cache.cachecontrol.stale-while-revalidate= # Maximum time the response can be served after it becomes stale, in seconds if no duration suffix is not specified.
spring.resources.cache.period= # Cache period for the resources served by the resource handler. If a duration suffix is not specified, seconds will be used.
spring.resources.chain.cache=true # Whether to enable caching in the Resource chain.
spring.resources.chain.enabled= # Whether to enable the Spring Resource Handling chain. By default, disabled unless at least one strategy has been enabled.
spring.resources.chain.gzipped=false # Whether to enable resolution of already gzipped resources.
spring.resources.chain.html-application-cache=false # Whether to enable HTML5 application cache manifest rewriting.
spring.resources.chain.strategy.content.enabled=false # Whether to enable the content Version Strategy.
spring.resources.chain.strategy.content.paths=/** # Comma-separated list of patterns to apply to the content Version Strategy.
spring.resources.chain.strategy.fixed.enabled=false # Whether to enable the fixed Version Strategy.
spring.resources.chain.strategy.fixed.paths=/** # Comma-separated list of patterns to apply to the fixed Version Strategy.
spring.resources.chain.strategy.fixed.version= # Version string to use for the fixed Version Strategy.
spring.resources.static-locations=classpath:/META-INF/resources/,classpath:/resources/,classpath:/static/,classpath:/public/ # Locations of static resources.

# SPRING SESSION (SessionProperties)
spring.session.store-type= # Session store type.
spring.session.timeout= # Session timeout. If a duration suffix is not specified, seconds will be used.
spring.session.servlet.filter-order=-2147483598 # Session repository filter order.
spring.session.servlet.filter-dispatcher-types=async,error,request # Session repository filter dispatcher types.

# SPRING SESSION HAZELCAST (HazelcastSessionProperties)
spring.session.hazelcast.flush-mode=on-save # Sessions flush mode.
spring.session.hazelcast.map-name=spring:session:sessions # Name of the map used to store sessions.

# SPRING SESSION JDBC (JdbcSessionProperties)
spring.session.jdbc.cleanup-cron=0 * * * * * # Cron expression for expired session cleanup job.
spring.session.jdbc.initialize-schema=embedded # Database schema initialization mode.
spring.session.jdbc.schema=classpath:org/springframework/session/jdbc/schema-@@platform@@.sql # Path to the SQL file to use to initialize the database schema.
spring.session.jdbc.table-name=SPRING_SESSION # Name of the database table used to store sessions.

# SPRING SESSION MONGODB (MongoSessionProperties)
spring.session.mongodb.collection-name=sessions # Collection name used to store sessions.

# SPRING SESSION REDIS (RedisSessionProperties)
spring.session.redis.cleanup-cron=0 * * * * * # Cron expression for expired session cleanup job.
spring.session.redis.flush-mode=on-save # Sessions flush mode.
spring.session.redis.namespace=spring:session # Namespace for keys used to store sessions.

# THYMELEAF (ThymeleafAutoConfiguration)
spring.thymeleaf.cache=true # Whether to enable template caching.
spring.thymeleaf.check-template=true # Whether to check that the template exists before rendering it.
spring.thymeleaf.check-template-location=true # Whether to check that the templates location exists.
spring.thymeleaf.enabled=true # Whether to enable Thymeleaf view resolution for Web frameworks.
spring.thymeleaf.enable-spring-el-compiler=false # Enable the SpringEL compiler in SpringEL expressions.
spring.thymeleaf.encoding=UTF-8 # Template files encoding.
spring.thymeleaf.excluded-view-names= # Comma-separated list of view names (patterns allowed) that should be excluded from resolution.
spring.thymeleaf.mode=HTML # Template mode to be applied to templates. See also Thymeleaf's TemplateMode enum.
spring.thymeleaf.prefix=classpath:/templates/ # Prefix that gets prepended to view names when building a URL.
spring.thymeleaf.reactive.chunked-mode-view-names= # Comma-separated list of view names (patterns allowed) that should be the only ones executed in CHUNKED mode when a max chunk size is set.
spring.thymeleaf.reactive.full-mode-view-names= # Comma-separated list of view names (patterns allowed) that should be executed in FULL mode even if a max chunk size is set.
spring.thymeleaf.reactive.max-chunk-size=0 # 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 (patterns allowed) that can be resolved.

# SPRING WEBFLUX (WebFluxProperties)
spring.webflux.date-format= # Date format to use. For instance, `dd/MM/yyyy`.
spring.webflux.static-path-pattern=/** # Path pattern used for static resources.

# SPRING WEB SERVICES (WebServicesProperties)
spring.webservices.path=/services # Path that serves as the base URI for the services.
spring.webservices.servlet.init= # Servlet init parameters to pass to Spring Web Services.
spring.webservices.servlet.load-on-startup=-1 # Load on startup priority of the Spring Web Services servlet.
spring.webservices.wsdl-locations= # Comma-separated list of locations of WSDLs and accompanying XSDs to be exposed as beans.

# ----------------------------------------
# SECURITY PROPERTIES
# ----------------------------------------
# SECURITY (SecurityProperties)
spring.security.filter.order=-100 # Security filter chain order.
spring.security.filter.dispatcher-types=async,error,request # Security filter chain dispatcher types.
spring.security.user.name=user # Default user name.
spring.security.user.password= # Password for the default user name.
spring.security.user.roles= # Granted roles for the default user name.

# SECURITY OAUTH2 CLIENT (OAuth2ClientProperties)
spring.security.oauth2.client.provider.*= # OAuth provider details.
spring.security.oauth2.client.registration.*= # OAuth client registrations.

# ----------------------------------------
# DATA PROPERTIES
# ----------------------------------------

# FLYWAY (FlywayProperties)
spring.flyway.baseline-description= #
spring.flyway.baseline-on-migrate= #
spring.flyway.baseline-version=1 # Version to start migration
spring.flyway.check-location=true # Whether to check that migration scripts location exists.
spring.flyway.clean-disabled= #
spring.flyway.clean-on-validation-error= #
spring.flyway.dry-run-output= #
spring.flyway.enabled=true # Whether to enable flyway.
spring.flyway.encoding= #
spring.flyway.error-handlers= #
spring.flyway.group= #
spring.flyway.ignore-future-migrations= #
spring.flyway.ignore-missing-migrations= #
spring.flyway.init-sqls= # SQL statements to execute to initialize a connection immediately after obtaining it.
spring.flyway.installed-by= #
spring.flyway.locations=classpath:db/migration # The locations of migrations scripts.
spring.flyway.mixed= #
spring.flyway.out-of-order= #
spring.flyway.password= # JDBC password to use if you want Flyway to create its own DataSource.
spring.flyway.placeholder-prefix= #
spring.flyway.placeholder-replacement= #
spring.flyway.placeholder-suffix= #
spring.flyway.placeholders.*= #
spring.flyway.repeatable-sql-migration-prefix= #
spring.flyway.schemas= # schemas to update
spring.flyway.skip-default-callbacks= #
spring.flyway.skip-default-resolvers= #
spring.flyway.sql-migration-prefix=V #
spring.flyway.sql-migration-separator= #
spring.flyway.sql-migration-suffix=.sql #
spring.flyway.sql-migration-suffixes= #
spring.flyway.table= #
spring.flyway.target= #
spring.flyway.undo-sql-migration-prefix= #
spring.flyway.url= # JDBC url of the database to migrate. If not set, the primary configured data source is used.
spring.flyway.user= # Login user of the database to migrate.
spring.flyway.validate-on-migrate= #

# LIQUIBASE (LiquibaseProperties)
spring.liquibase.change-log=classpath:/db/changelog/db.changelog-master.yaml # Change log configuration path.
spring.liquibase.check-change-log-location=true # Whether to check that the change log location exists.
spring.liquibase.contexts= # Comma-separated list of runtime contexts to use.
spring.liquibase.default-schema= # Default database schema.
spring.liquibase.drop-first=false # Whether to first drop the database schema.
spring.liquibase.enabled=true # Whether to enable Liquibase support.
spring.liquibase.labels= # Comma-separated list of runtime labels to use.
spring.liquibase.parameters.*= # Change log parameters.
spring.liquibase.password= # Login password of the database to migrate.
spring.liquibase.rollback-file= # File to which rollback SQL is written when an update is performed.
spring.liquibase.url= # JDBC URL of the database to migrate. If not set, the primary configured data source is used.
spring.liquibase.user= # Login user of the database to migrate.

# COUCHBASE (CouchbaseProperties)
spring.couchbase.bootstrap-hosts= # Couchbase nodes (host or IP address) to bootstrap from.
spring.couchbase.bucket.name=default # Name of the bucket to connect to.
spring.couchbase.bucket.password=  # Password of the bucket.
spring.couchbase.env.endpoints.key-value=1 # Number of sockets per node against the key/value service.
spring.couchbase.env.endpoints.queryservice.min-endpoints=1 # Minimum number of sockets per node.
spring.couchbase.env.endpoints.queryservice.max-endpoints=1 # Maximum number of sockets per node.
spring.couchbase.env.endpoints.viewservice.min-endpoints=1 # Minimum number of sockets per node.
spring.couchbase.env.endpoints.viewservice.max-endpoints=1 # Maximum number of sockets per node.
spring.couchbase.env.ssl.enabled= # Whether to enable SSL support. Enabled automatically if a "keyStore" is provided unless specified otherwise.
spring.couchbase.env.ssl.key-store= # Path to the JVM key store that holds the certificates.
spring.couchbase.env.ssl.key-store-password= # Password used to access the key store.
spring.couchbase.env.timeouts.connect=5000ms # Bucket connections timeouts.
spring.couchbase.env.timeouts.key-value=2500ms # Blocking operations performed on a specific key timeout.
spring.couchbase.env.timeouts.query=7500ms # N1QL query operations timeout.
spring.couchbase.env.timeouts.socket-connect=1000ms # Socket connect connections timeout.
spring.couchbase.env.timeouts.view=7500ms # Regular and geospatial view operations timeout.

# DAO (PersistenceExceptionTranslationAutoConfiguration)
spring.dao.exceptiontranslation.enabled=true # Whether to enable the PersistenceExceptionTranslationPostProcessor.

# CASSANDRA (CassandraProperties)
spring.data.cassandra.cluster-name= # Name of the Cassandra cluster.
spring.data.cassandra.compression=none # Compression supported by the Cassandra binary protocol.
spring.data.cassandra.connect-timeout= # Socket option: connection time out.
spring.data.cassandra.consistency-level= # Queries consistency level.
spring.data.cassandra.contact-points=localhost # Cluster node addresses.
spring.data.cassandra.fetch-size= # Queries default fetch size.
spring.data.cassandra.keyspace-name= # Keyspace name to use.
spring.data.cassandra.load-balancing-policy= # Class name of the load balancing policy.
spring.data.cassandra.port= # Port of the Cassandra server.
spring.data.cassandra.password= # Login password of the server.
spring.data.cassandra.pool.heartbeat-interval=30s # Heartbeat interval after which a message is sent on an idle connection to make sure it's still alive. If a duration suffix is not specified, seconds will be used.
spring.data.cassandra.pool.idle-timeout=120s # Idle timeout before an idle connection is removed. If a duration suffix is not specified, seconds will be used.
spring.data.cassandra.pool.max-queue-size=256 # Maximum number of requests that get queued if no connection is available.
spring.data.cassandra.pool.pool-timeout=5000ms # Pool timeout when trying to acquire a connection from a host's pool.
spring.data.cassandra.read-timeout= # Socket option: read time out.
spring.data.cassandra.reconnection-policy= # Reconnection policy class.
spring.data.cassandra.repositories.type=auto # Type of Cassandra repositories to enable.
spring.data.cassandra.retry-policy= # Class name of the retry policy.
spring.data.cassandra.serial-consistency-level= # Queries serial consistency level.
spring.data.cassandra.schema-action=none # Schema action to take at startup.
spring.data.cassandra.ssl=false # Enable SSL support.
spring.data.cassandra.username= # Login user of the server.

# DATA COUCHBASE (CouchbaseDataProperties)
spring.data.couchbase.auto-index=false # Automatically create views and indexes.
spring.data.couchbase.consistency=read-your-own-writes # Consistency to apply by default on generated queries.
spring.data.couchbase.repositories.type=auto # Type of Couchbase repositories to enable.

# ELASTICSEARCH (ElasticsearchProperties)
spring.data.elasticsearch.cluster-name=elasticsearch # Elasticsearch cluster name.
spring.data.elasticsearch.cluster-nodes= # Comma-separated list of cluster node addresses.
spring.data.elasticsearch.properties.*= # Additional properties used to configure the client.
spring.data.elasticsearch.repositories.enabled=true # Whether to enable Elasticsearch repositories.

# DATA LDAP
spring.data.ldap.repositories.enabled=true # Whether to enable LDAP repositories.

# MONGODB (MongoProperties)
spring.data.mongodb.authentication-database= # Authentication database name.
spring.data.mongodb.database= # Database name.
spring.data.mongodb.field-naming-strategy= # Fully qualified name of the FieldNamingStrategy to use.
spring.data.mongodb.grid-fs-database= # GridFS database name.
spring.data.mongodb.host= # Mongo server host. Cannot be set with URI.
spring.data.mongodb.password= # Login password of the mongo server. Cannot be set with URI.
spring.data.mongodb.port= # Mongo server port. Cannot be set with URI.
spring.data.mongodb.repositories.type=auto # Type of Mongo repositories to enable.
spring.data.mongodb.uri=mongodb://localhost/test # Mongo database URI. Cannot be set with host, port and credentials.
spring.data.mongodb.username= # Login user of the mongo server. Cannot be set with URI.

# DATA REDIS
spring.data.redis.repositories.enabled=true # Whether to enable Redis repositories.

# NEO4J (Neo4jProperties)
spring.data.neo4j.auto-index=none # Auto index mode.
spring.data.neo4j.embedded.enabled=true # Whether to enable embedded mode if the embedded driver is available.
spring.data.neo4j.open-in-view=true # Register OpenSessionInViewInterceptor. Binds a Neo4j Session to the thread for the entire processing of the request.
spring.data.neo4j.password= # Login password of the server.
spring.data.neo4j.repositories.enabled=true # Whether to enable Neo4j repositories.
spring.data.neo4j.uri= # URI used by the driver. Auto-detected by default.
spring.data.neo4j.username= # Login user of the server.

# DATA REST (RepositoryRestProperties)
spring.data.rest.base-path= # Base path to be used by Spring Data REST to expose repository resources.
spring.data.rest.default-media-type= # Content type to use as a default when none is specified.
spring.data.rest.default-page-size= # Default size of pages.
spring.data.rest.detection-strategy=default # Strategy to use to determine which repositories get exposed.
spring.data.rest.enable-enum-translation= # Whether to enable enum value translation through the Spring Data REST default resource bundle.
spring.data.rest.limit-param-name= # Name of the URL query string parameter that indicates how many results to return at once.
spring.data.rest.max-page-size= # Maximum size of pages.
spring.data.rest.page-param-name= # Name of the URL query string parameter that indicates what page to return.
spring.data.rest.return-body-on-create= # Whether to return a response body after creating an entity.
spring.data.rest.return-body-on-update= # Whether to return a response body after updating an entity.
spring.data.rest.sort-param-name= # Name of the URL query string parameter that indicates what direction to sort results.

# SOLR (SolrProperties)
spring.data.solr.host=http://127.0.0.1:8983/solr # Solr host. Ignored if "zk-host" is set.
spring.data.solr.repositories.enabled=true # Whether to enable Solr repositories.
spring.data.solr.zk-host= # ZooKeeper host address in the form HOST:PORT.

# DATA WEB (SpringDataWebProperties)
spring.data.web.pageable.default-page-size=20 # Default page size.
spring.data.web.pageable.max-page-size=2000 # Maximum page size to be accepted.
spring.data.web.pageable.one-indexed-parameters=false # Whether to expose and assume 1-based page number indexes.
spring.data.web.pageable.page-parameter=page # Page index parameter name.
spring.data.web.pageable.prefix= # General prefix to be prepended to the page number and page size parameters.
spring.data.web.pageable.qualifier-delimiter=_ # Delimiter to be used between the qualifier and the actual page number and size properties.
spring.data.web.pageable.size-parameter=size # Page size parameter name.
spring.data.web.sort.sort-parameter=sort # Sort parameter name.

# DATASOURCE (DataSourceAutoConfiguration & DataSourceProperties)
spring.datasource.continue-on-error=false # Whether to stop if an error occurs while initializing the database.
spring.datasource.data= # Data (DML) script resource references.
spring.datasource.data-username= # Username of the database to execute DML scripts (if different).
spring.datasource.data-password= # Password of the database to execute DML scripts (if different).
spring.datasource.dbcp2.*= # Commons DBCP2 specific settings
spring.datasource.driver-class-name= # Fully qualified name of the JDBC driver. Auto-detected based on the URL by default.
spring.datasource.generate-unique-name=false # Whether to generate a random datasource name.
spring.datasource.hikari.*= # Hikari specific settings
spring.datasource.initialization-mode=embedded # Initialize the datasource with available DDL and DML scripts.
spring.datasource.jmx-enabled=false # Whether to enable JMX support (if provided by the underlying pool).
spring.datasource.jndi-name= # JNDI location of the datasource. Class, url, username & password are ignored when set.
spring.datasource.name= # Name of the datasource. Default to "testdb" when using an embedded database.
spring.datasource.password= # Login password of the database.
spring.datasource.platform=all # Platform to use in the DDL or DML scripts (such as schema-${platform}.sql or data-${platform}.sql).
spring.datasource.schema= # Schema (DDL) script resource references.
spring.datasource.schema-username= # Username of the database to execute DDL scripts (if different).
spring.datasource.schema-password= # Password of the database to execute DDL scripts (if different).
spring.datasource.separator=; # Statement separator in SQL initialization scripts.
spring.datasource.sql-script-encoding= # SQL scripts encoding.
spring.datasource.tomcat.*= # Tomcat datasource specific settings
spring.datasource.type= # Fully qualified name of the connection pool implementation to use. By default, it is auto-detected from the classpath.
spring.datasource.url= # JDBC URL of the database.
spring.datasource.username= # Login username of the database.
spring.datasource.xa.data-source-class-name= # XA datasource fully qualified name.
spring.datasource.xa.properties= # Properties to pass to the XA data source.

# JEST (Elasticsearch HTTP client) (JestProperties)
spring.elasticsearch.jest.connection-timeout=3s # Connection timeout.
spring.elasticsearch.jest.multi-threaded=true # Whether to enable connection requests from multiple execution threads.
spring.elasticsearch.jest.password= # Login password.
spring.elasticsearch.jest.proxy.host= # Proxy host the HTTP client should use.
spring.elasticsearch.jest.proxy.port= # Proxy port the HTTP client should use.
spring.elasticsearch.jest.read-timeout=3s # Read timeout.
spring.elasticsearch.jest.uris=http://localhost:9200 # Comma-separated list of the Elasticsearch instances to use.
spring.elasticsearch.jest.username= # Login username.

# H2 Web Console (H2ConsoleProperties)
spring.h2.console.enabled=false # Whether to enable the console.
spring.h2.console.path=/h2-console # Path at which the console is available.
spring.h2.console.settings.trace=false # Whether to enable trace output.
spring.h2.console.settings.web-allow-others=false # Whether to enable remote access.

# InfluxDB (InfluxDbProperties)
spring.influx.password= # Login password.
spring.influx.url= # URL of the InfluxDB instance to which to connect.
spring.influx.user= # Login user.

# JOOQ (JooqProperties)
spring.jooq.sql-dialect= # SQL dialect to use. Auto-detected by default.

# JDBC (JdbcProperties)
spring.jdbc.template.fetch-size=-1 # Number of rows that should be fetched from the database when more rows are needed.
spring.jdbc.template.max-rows=-1 # Maximum number of rows.
spring.jdbc.template.query-timeout= # Query timeout. Default is to use the JDBC driver's default configuration. If a duration suffix is not specified, seconds will be used.

# JPA (JpaBaseConfiguration, HibernateJpaAutoConfiguration)
spring.data.jpa.repositories.enabled=true # Whether to enable JPA repositories.
spring.jpa.database= # Target database to operate on, auto-detected by default. Can be alternatively set using the "databasePlatform" property.
spring.jpa.database-platform= # Name of the target database to operate on, auto-detected by default. Can be alternatively set using the "Database" enum.
spring.jpa.generate-ddl=false # Whether to initialize the schema on startup.
spring.jpa.hibernate.ddl-auto= # DDL mode. This is actually a shortcut for the "hibernate.hbm2ddl.auto" property. Defaults to "create-drop" when using an embedded database and no schema manager was detected. Otherwise, defaults to "none".
spring.jpa.hibernate.naming.implicit-strategy= # Fully qualified name of the implicit naming strategy.
spring.jpa.hibernate.naming.physical-strategy= # Fully qualified name of the physical naming strategy.
spring.jpa.hibernate.use-new-id-generator-mappings= # Whether to use Hibernate's newer IdentifierGenerator for AUTO, TABLE and SEQUENCE.
spring.jpa.mapping-resources= # Mapping resources (equivalent to "mapping-file" entries in persistence.xml).
spring.jpa.open-in-view=true # Register OpenEntityManagerInViewInterceptor. Binds a JPA EntityManager to the thread for the entire processing of the request.
spring.jpa.properties.*= # Additional native properties to set on the JPA provider.
spring.jpa.show-sql=false # Whether to enable logging of SQL statements.

# JTA (JtaAutoConfiguration)
spring.jta.enabled=true # Whether to enable JTA support.
spring.jta.log-dir= # Transaction logs directory.
spring.jta.transaction-manager-id= # Transaction manager unique identifier.

# ATOMIKOS (AtomikosProperties)
spring.jta.atomikos.connectionfactory.borrow-connection-timeout=30 # Timeout, in seconds, for borrowing connections from the pool.
spring.jta.atomikos.connectionfactory.ignore-session-transacted-flag=true # Whether to ignore the transacted flag when creating session.
spring.jta.atomikos.connectionfactory.local-transaction-mode=false # Whether local transactions are desired.
spring.jta.atomikos.connectionfactory.maintenance-interval=60 # The time, in seconds, between runs of the pool's maintenance thread.
spring.jta.atomikos.connectionfactory.max-idle-time=60 # The time, in seconds, after which connections are cleaned up from the pool.
spring.jta.atomikos.connectionfactory.max-lifetime=0 # The time, in seconds, that a connection can be pooled for before being destroyed. 0 denotes no limit.
spring.jta.atomikos.connectionfactory.max-pool-size=1 # The maximum size of the pool.
spring.jta.atomikos.connectionfactory.min-pool-size=1 # The minimum size of the pool.
spring.jta.atomikos.connectionfactory.reap-timeout=0 # The reap timeout, in seconds, for borrowed connections. 0 denotes no limit.
spring.jta.atomikos.connectionfactory.unique-resource-name=jmsConnectionFactory # The unique name used to identify the resource during recovery.
spring.jta.atomikos.connectionfactory.xa-connection-factory-class-name= # Vendor-specific implementation of XAConnectionFactory.
spring.jta.atomikos.connectionfactory.xa-properties= # Vendor-specific XA properties.
spring.jta.atomikos.datasource.borrow-connection-timeout=30 # Timeout, in seconds, for borrowing connections from the pool.
spring.jta.atomikos.datasource.concurrent-connection-validation= # Whether to use concurrent connection validation.
spring.jta.atomikos.datasource.default-isolation-level= # Default isolation level of connections provided by the pool.
spring.jta.atomikos.datasource.login-timeout= # Timeout, in seconds, for establishing a database connection.
spring.jta.atomikos.datasource.maintenance-interval=60 # The time, in seconds, between runs of the pool's maintenance thread.
spring.jta.atomikos.datasource.max-idle-time=60 # The time, in seconds, after which connections are cleaned up from the pool.
spring.jta.atomikos.datasource.max-lifetime=0 # The time, in seconds, that a connection can be pooled for before being destroyed. 0 denotes no limit.
spring.jta.atomikos.datasource.max-pool-size=1 # The maximum size of the pool.
spring.jta.atomikos.datasource.min-pool-size=1 # The minimum size of the pool.
spring.jta.atomikos.datasource.reap-timeout=0 # The reap timeout, in seconds, for borrowed connections. 0 denotes no limit.
spring.jta.atomikos.datasource.test-query= # SQL query or statement used to validate a connection before returning it.
spring.jta.atomikos.datasource.unique-resource-name=dataSource # The unique name used to identify the resource during recovery.
spring.jta.atomikos.datasource.xa-data-source-class-name= # Vendor-specific implementation of XAConnectionFactory.
spring.jta.atomikos.datasource.xa-properties= # Vendor-specific XA properties.
spring.jta.atomikos.properties.allow-sub-transactions=true # Specify whether sub-transactions are allowed.
spring.jta.atomikos.properties.checkpoint-interval=500 # Interval between checkpoints, expressed as the number of log writes between two checkpoints.
spring.jta.atomikos.properties.default-jta-timeout=10000ms # Default timeout for JTA transactions.
spring.jta.atomikos.properties.default-max-wait-time-on-shutdown=9223372036854775807 # How long should normal shutdown (no-force) wait for transactions to complete.
spring.jta.atomikos.properties.enable-logging=true # Whether to enable disk logging.
spring.jta.atomikos.properties.force-shutdown-on-vm-exit=false # Whether a VM shutdown should trigger forced shutdown of the transaction core.
spring.jta.atomikos.properties.log-base-dir= # Directory in which the log files should be stored.
spring.jta.atomikos.properties.log-base-name=tmlog # Transactions log file base name.
spring.jta.atomikos.properties.max-actives=50 # Maximum number of active transactions.
spring.jta.atomikos.properties.max-timeout=300000ms # Maximum timeout that can be allowed for transactions.
spring.jta.atomikos.properties.recovery.delay=10000ms # Delay between two recovery scans.
spring.jta.atomikos.properties.recovery.forget-orphaned-log-entries-delay=86400000ms # Delay after which recovery can cleanup pending ('orphaned') log entries.
spring.jta.atomikos.properties.recovery.max-retries=5 # Number of retry attempts to commit the transaction before throwing an exception.
spring.jta.atomikos.properties.recovery.retry-interval=10000ms # Delay between retry attempts.
spring.jta.atomikos.properties.serial-jta-transactions=true # Whether sub-transactions should be joined when possible.
spring.jta.atomikos.properties.service= # Transaction manager implementation that should be started.
spring.jta.atomikos.properties.threaded-two-phase-commit=false # Whether to use different (and concurrent) threads for two-phase commit on the participating resources.
spring.jta.atomikos.properties.transaction-manager-unique-name= # The transaction manager's unique name.

# BITRONIX
spring.jta.bitronix.connectionfactory.acquire-increment=1 # Number of connections to create when growing the pool.
spring.jta.bitronix.connectionfactory.acquisition-interval=1 # Time, in seconds, to wait before trying to acquire a connection again after an invalid connection was acquired.
spring.jta.bitronix.connectionfactory.acquisition-timeout=30 # Timeout, in seconds, for acquiring connections from the pool.
spring.jta.bitronix.connectionfactory.allow-local-transactions=true # Whether the transaction manager should allow mixing XA and non-XA transactions.
spring.jta.bitronix.connectionfactory.apply-transaction-timeout=false # Whether the transaction timeout should be set on the XAResource when it is enlisted.
spring.jta.bitronix.connectionfactory.automatic-enlisting-enabled=true # Whether resources should be enlisted and delisted automatically.
spring.jta.bitronix.connectionfactory.cache-producers-consumers=true # Whether producers and consumers should be cached.
spring.jta.bitronix.connectionfactory.class-name= # Underlying implementation class name of the XA resource.
spring.jta.bitronix.connectionfactory.defer-connection-release=true # Whether the provider can run many transactions on the same connection and supports transaction interleaving.
spring.jta.bitronix.connectionfactory.disabled= # Whether this resource is disabled, meaning it's temporarily forbidden to acquire a connection from its pool.
spring.jta.bitronix.connectionfactory.driver-properties= # Properties that should be set on the underlying implementation.
spring.jta.bitronix.connectionfactory.failed= # Mark this resource producer as failed.
spring.jta.bitronix.connectionfactory.ignore-recovery-failures=false # Whether recovery failures should be ignored.
spring.jta.bitronix.connectionfactory.max-idle-time=60 # The time, in seconds, after which connections are cleaned up from the pool.
spring.jta.bitronix.connectionfactory.max-pool-size=10 # The maximum size of the pool. 0 denotes no limit.
spring.jta.bitronix.connectionfactory.min-pool-size=0 # The minimum size of the pool.
spring.jta.bitronix.connectionfactory.password= # The password to use to connect to the JMS provider.
spring.jta.bitronix.connectionfactory.share-transaction-connections=false #  Whether connections in the ACCESSIBLE state can be shared within the context of a transaction.
spring.jta.bitronix.connectionfactory.test-connections=true # Whether connections should be tested when acquired from the pool.
spring.jta.bitronix.connectionfactory.two-pc-ordering-position=1 # The position that this resource should take during two-phase commit (always first is Integer.MIN_VALUE, always last is Integer.MAX_VALUE).
spring.jta.bitronix.connectionfactory.unique-name=jmsConnectionFactory # The unique name used to identify the resource during recovery.
spring.jta.bitronix.connectionfactory.use-tm-join=true # Whether TMJOIN should be used when starting XAResources.
spring.jta.bitronix.connectionfactory.user= # The user to use to connect to the JMS provider.
spring.jta.bitronix.datasource.acquire-increment=1 # Number of connections to create when growing the pool.
spring.jta.bitronix.datasource.acquisition-interval=1 # Time, in seconds, to wait before trying to acquire a connection again after an invalid connection was acquired.
spring.jta.bitronix.datasource.acquisition-timeout=30 # Timeout, in seconds, for acquiring connections from the pool.
spring.jta.bitronix.datasource.allow-local-transactions=true # Whether the transaction manager should allow mixing XA and non-XA transactions.
spring.jta.bitronix.datasource.apply-transaction-timeout=false # Whether the transaction timeout should be set on the XAResource when it is enlisted.
spring.jta.bitronix.datasource.automatic-enlisting-enabled=true # Whether resources should be enlisted and delisted automatically.
spring.jta.bitronix.datasource.class-name= # Underlying implementation class name of the XA resource.
spring.jta.bitronix.datasource.cursor-holdability= # The default cursor holdability for connections.
spring.jta.bitronix.datasource.defer-connection-release=true # Whether the database can run many transactions on the same connection and supports transaction interleaving.
spring.jta.bitronix.datasource.disabled= # Whether this resource is disabled, meaning it's temporarily forbidden to acquire a connection from its pool.
spring.jta.bitronix.datasource.driver-properties= # Properties that should be set on the underlying implementation.
spring.jta.bitronix.datasource.enable-jdbc4-connection-test= # Whether Connection.isValid() is called when acquiring a connection from the pool.
spring.jta.bitronix.datasource.failed= # Mark this resource producer as failed.
spring.jta.bitronix.datasource.ignore-recovery-failures=false # Whether recovery failures should be ignored.
spring.jta.bitronix.datasource.isolation-level= # The default isolation level for connections.
spring.jta.bitronix.datasource.local-auto-commit= # The default auto-commit mode for local transactions.
spring.jta.bitronix.datasource.login-timeout= # Timeout, in seconds, for establishing a database connection.
spring.jta.bitronix.datasource.max-idle-time=60 # The time, in seconds, after which connections are cleaned up from the pool.
spring.jta.bitronix.datasource.max-pool-size=10 # The maximum size of the pool. 0 denotes no limit.
spring.jta.bitronix.datasource.min-pool-size=0 # The minimum size of the pool.
spring.jta.bitronix.datasource.prepared-statement-cache-size=0 # The target size of the prepared statement cache. 0 disables the cache.
spring.jta.bitronix.datasource.share-transaction-connections=false #  Whether connections in the ACCESSIBLE state can be shared within the context of a transaction.
spring.jta.bitronix.datasource.test-query= # SQL query or statement used to validate a connection before returning it.
spring.jta.bitronix.datasource.two-pc-ordering-position=1 # The position that this resource should take during two-phase commit (always first is Integer.MIN_VALUE, and always last is Integer.MAX_VALUE).
spring.jta.bitronix.datasource.unique-name=dataSource # The unique name used to identify the resource during recovery.
spring.jta.bitronix.datasource.use-tm-join=true # Whether TMJOIN should be used when starting XAResources.
spring.jta.bitronix.properties.allow-multiple-lrc=false # Whether to allow multiple LRC resources to be enlisted into the same transaction.
spring.jta.bitronix.properties.asynchronous2-pc=false # Whether to enable asynchronously execution of two phase commit.
spring.jta.bitronix.properties.background-recovery-interval-seconds=60 # Interval in seconds at which to run the recovery process in the background.
spring.jta.bitronix.properties.current-node-only-recovery=true # Whether to recover only the current node.
spring.jta.bitronix.properties.debug-zero-resource-transaction=false # Whether to log the creation and commit call stacks of transactions executed without a single enlisted resource.
spring.jta.bitronix.properties.default-transaction-timeout=60 # Default transaction timeout, in seconds.
spring.jta.bitronix.properties.disable-jmx=false # Whether to enable JMX support.
spring.jta.bitronix.properties.exception-analyzer= # Set the fully qualified name of the exception analyzer implementation to use.
spring.jta.bitronix.properties.filter-log-status=false # Whether to enable filtering of logs so that only mandatory logs are written.
spring.jta.bitronix.properties.force-batching-enabled=true #  Whether disk forces are batched.
spring.jta.bitronix.properties.forced-write-enabled=true # Whether logs are forced to disk.
spring.jta.bitronix.properties.graceful-shutdown-interval=60 # Maximum amount of seconds the TM waits for transactions to get done before aborting them at shutdown time.
spring.jta.bitronix.properties.jndi-transaction-synchronization-registry-name= # JNDI name of the TransactionSynchronizationRegistry.
spring.jta.bitronix.properties.jndi-user-transaction-name= # JNDI name of the UserTransaction.
spring.jta.bitronix.properties.journal=disk # Name of the journal. Can be 'disk', 'null', or a class name.
spring.jta.bitronix.properties.log-part1-filename=btm1.tlog # Name of the first fragment of the journal.
spring.jta.bitronix.properties.log-part2-filename=btm2.tlog # Name of the second fragment of the journal.
spring.jta.bitronix.properties.max-log-size-in-mb=2 # Maximum size in megabytes of the journal fragments.
spring.jta.bitronix.properties.resource-configuration-filename= # ResourceLoader configuration file name.
spring.jta.bitronix.properties.server-id= # ASCII ID that must uniquely identify this TM instance. Defaults to the machine's IP address.
spring.jta.bitronix.properties.skip-corrupted-logs=false # Skip corrupted transactions log entries.
spring.jta.bitronix.properties.warn-about-zero-resource-transaction=true # Whether to log a warning for transactions executed without a single enlisted resource.

# NARAYANA (NarayanaProperties)
spring.jta.narayana.default-timeout=60s # Transaction timeout. If a duration suffix is not specified, seconds will be used.
spring.jta.narayana.expiry-scanners=com.arjuna.ats.internal.arjuna.recovery.ExpiredTransactionStatusManagerScanner # Comma-separated list of expiry scanners.
spring.jta.narayana.log-dir= # Transaction object store directory.
spring.jta.narayana.one-phase-commit=true # Whether to enable one phase commit optimization.
spring.jta.narayana.periodic-recovery-period=120s # Interval in which periodic recovery scans are performed. If a duration suffix is not specified, seconds will be used.
spring.jta.narayana.recovery-backoff-period=10s # Back off period between first and second phases of the recovery scan. If a duration suffix is not specified, seconds will be used.
spring.jta.narayana.recovery-db-pass= # Database password to be used by the recovery manager.
spring.jta.narayana.recovery-db-user= # Database username to be used by the recovery manager.
spring.jta.narayana.recovery-jms-pass= # JMS password to be used by the recovery manager.
spring.jta.narayana.recovery-jms-user= # JMS username to be used by the recovery manager.
spring.jta.narayana.recovery-modules= # Comma-separated list of recovery modules.
spring.jta.narayana.transaction-manager-id=1 # Unique transaction manager id.
spring.jta.narayana.xa-resource-orphan-filters= # Comma-separated list of orphan filters.

# EMBEDDED MONGODB (EmbeddedMongoProperties)
spring.mongodb.embedded.features=sync_delay # Comma-separated list of features to enable.
spring.mongodb.embedded.storage.database-dir= # Directory used for data storage.
spring.mongodb.embedded.storage.oplog-size= # Maximum size of the oplog, in megabytes.
spring.mongodb.embedded.storage.repl-set-name= # Name of the replica set.
spring.mongodb.embedded.version=3.2.2 # Version of Mongo to use.

# REDIS (RedisProperties)
spring.redis.cluster.max-redirects= # Maximum number of redirects to follow when executing commands across the cluster.
spring.redis.cluster.nodes= # Comma-separated list of "host:port" pairs to bootstrap from.
spring.redis.database=0 # Database index used by the connection factory.
spring.redis.url= # Connection URL. Overrides host, port, and password. User is ignored. Example: redis://user:password@example.com:6379
spring.redis.host=localhost # Redis server host.
spring.redis.jedis.pool.max-active=8 # Maximum number of connections that can be allocated by the pool at a given time. Use a negative value for no limit.
spring.redis.jedis.pool.max-idle=8 # Maximum number of "idle" connections in the pool. Use a negative value to indicate an unlimited number of idle connections.
spring.redis.jedis.pool.max-wait=-1ms # Maximum amount of time a connection allocation should block before throwing an exception when the pool is exhausted. Use a negative value to block indefinitely.
spring.redis.jedis.pool.min-idle=0 # Target for the minimum number of idle connections to maintain in the pool. This setting only has an effect if it is positive.
spring.redis.lettuce.pool.max-active=8 # Maximum number of connections that can be allocated by the pool at a given time. Use a negative value for no limit.
spring.redis.lettuce.pool.max-idle=8 # Maximum number of "idle" connections in the pool. Use a negative value to indicate an unlimited number of idle connections.
spring.redis.lettuce.pool.max-wait=-1ms # Maximum amount of time a connection allocation should block before throwing an exception when the pool is exhausted. Use a negative value to block indefinitely.
spring.redis.lettuce.pool.min-idle=0 # Target for the minimum number of idle connections to maintain in the pool. This setting only has an effect if it is positive.
spring.redis.lettuce.shutdown-timeout=100ms # Shutdown timeout.
spring.redis.password= # Login password of the redis server.
spring.redis.port=6379 # Redis server port.
spring.redis.sentinel.master= # Name of the Redis server.
spring.redis.sentinel.nodes= # Comma-separated list of "host:port" pairs.
spring.redis.ssl=false # Whether to enable SSL support.
spring.redis.timeout= # Connection timeout.

# TRANSACTION (TransactionProperties)
spring.transaction.default-timeout= # Default transaction timeout. If a duration suffix is not specified, seconds will be used.
spring.transaction.rollback-on-commit-failure= # Whether to roll back on commit failures.

# ----------------------------------------
# INTEGRATION PROPERTIES
# ----------------------------------------

# ACTIVEMQ (ActiveMQProperties)
spring.activemq.broker-url= # URL of the ActiveMQ broker. Auto-generated by default.
spring.activemq.close-timeout=15s # Time to wait before considering a close complete.
spring.activemq.in-memory=true # Whether the default broker URL should be in memory. Ignored if an explicit broker has been specified.
spring.activemq.non-blocking-redelivery=false # Whether to stop message delivery before re-delivering messages from a rolled back transaction. This implies that message order is not preserved when this is enabled.
spring.activemq.password= # Login password of the broker.
spring.activemq.send-timeout=0ms # Time to wait on message sends for a response. Set it to 0 to wait forever.
spring.activemq.user= # Login user of the broker.
spring.activemq.packages.trust-all= # Whether to trust all packages.
spring.activemq.packages.trusted= # Comma-separated list of specific packages to trust (when not trusting all packages).
spring.activemq.pool.block-if-full=true # Whether to block when a connection is requested and the pool is full. Set it to false to throw a "JMSException" instead.
spring.activemq.pool.block-if-full-timeout=-1ms # Blocking period before throwing an exception if the pool is still full.
spring.activemq.pool.enabled=false # Whether a PooledConnectionFactory should be created, instead of a regular ConnectionFactory.
spring.activemq.pool.idle-timeout=30s # Connection idle timeout.
spring.activemq.pool.max-connections=1 # Maximum number of pooled connections.
spring.activemq.pool.maximum-active-session-per-connection=500 # Maximum number of active sessions per connection.
spring.activemq.pool.time-between-expiration-check=-1ms # Time to sleep between runs of the idle connection eviction thread. When negative, no idle connection eviction thread runs.
spring.activemq.pool.use-anonymous-producers=true # Whether to use only one anonymous "MessageProducer" instance. Set it to false to create one "MessageProducer" every time one is required.

# ARTEMIS (ArtemisProperties)
spring.artemis.embedded.cluster-password= # Cluster password. Randomly generated on startup by default.
spring.artemis.embedded.data-directory= # Journal file directory. Not necessary if persistence is turned off.
spring.artemis.embedded.enabled=true # Whether to enable embedded mode if the Artemis server APIs are available.
spring.artemis.embedded.persistent=false # Whether to enable persistent store.
spring.artemis.embedded.queues= # Comma-separated list of queues to create on startup.
spring.artemis.embedded.server-id= # Server ID. By default, an auto-incremented counter is used.
spring.artemis.embedded.topics= # Comma-separated list of topics to create on startup.
spring.artemis.host=localhost # Artemis broker host.
spring.artemis.mode= # Artemis deployment mode, auto-detected by default.
spring.artemis.password= # Login password of the broker.
spring.artemis.port=61616 # Artemis broker port.
spring.artemis.user= # Login user of the broker.

# SPRING BATCH (BatchProperties)
spring.batch.initialize-schema=embedded # Database schema initialization mode.
spring.batch.job.enabled=true # Execute all Spring Batch jobs in the context on startup.
spring.batch.job.names= # Comma-separated list of job names to execute on startup (for instance, `job1,job2`). By default, all Jobs found in the context are executed.
spring.batch.schema=classpath:org/springframework/batch/core/schema-@@platform@@.sql # Path to the SQL file to use to initialize the database schema.
spring.batch.table-prefix= # Table prefix for all the batch meta-data tables.

# SPRING INTEGRATION (IntegrationProperties)
spring.integration.jdbc.initialize-schema=embedded # Database schema initialization mode.
spring.integration.jdbc.schema=classpath:org/springframework/integration/jdbc/schema-@@platform@@.sql # Path to the SQL file to use to initialize the database schema.

# JMS (JmsProperties)
spring.jms.jndi-name= # Connection factory JNDI name. When set, takes precedence to others connection factory auto-configurations.
spring.jms.listener.acknowledge-mode= # Acknowledge mode of the container. By default, the listener is transacted with automatic acknowledgment.
spring.jms.listener.auto-startup=true # Start the container automatically on startup.
spring.jms.listener.concurrency= # Minimum number of concurrent consumers.
spring.jms.listener.max-concurrency= # Maximum number of concurrent consumers.
spring.jms.pub-sub-domain=false # Whether the default destination type is topic.
spring.jms.template.default-destination= # Default destination to use on send and receive operations that do not have a destination parameter.
spring.jms.template.delivery-delay= # Delivery delay to use for send calls.
spring.jms.template.delivery-mode= # Delivery mode. Enables QoS (Quality of Service) when set.
spring.jms.template.priority= # Priority of a message when sending. Enables QoS (Quality of Service) when set.
spring.jms.template.qos-enabled= # Whether to enable explicit QoS (Quality of Service) when sending a message.
spring.jms.template.receive-timeout= # Timeout to use for receive calls.
spring.jms.template.time-to-live= # Time-to-live of a message when sending. Enables QoS (Quality of Service) when set.

# APACHE KAFKA (KafkaProperties)
spring.kafka.admin.client-id= # ID to pass to the server when making requests. Used for server-side logging.
spring.kafka.admin.fail-fast=false # Whether to fail fast if the broker is not available on startup.
spring.kafka.admin.properties.*= # Additional admin-specific properties used to configure the client.
spring.kafka.admin.ssl.key-password= # Password of the private key in the key store file.
spring.kafka.admin.ssl.keystore-location= # Location of the key store file.
spring.kafka.admin.ssl.keystore-password= # Store password for the key store file.
spring.kafka.admin.ssl.keystore-type= # Type of the key store.
spring.kafka.admin.ssl.protocol= # SSL protocol to use.
spring.kafka.admin.ssl.truststore-location= # Location of the trust store file.
spring.kafka.admin.ssl.truststore-password= # Store password for the trust store file.
spring.kafka.admin.ssl.truststore-type= # Type of the trust store.
spring.kafka.bootstrap-servers= # Comma-delimited list of host:port pairs to use for establishing the initial connections to the Kafka cluster. Applies to all components unless overridden.
spring.kafka.client-id= # ID to pass to the server when making requests. Used for server-side logging.
spring.kafka.consumer.auto-commit-interval= # Frequency with which the consumer offsets are auto-committed to Kafka if 'enable.auto.commit' is set to true.
spring.kafka.consumer.auto-offset-reset= # What to do when there is no initial offset in Kafka or if the current offset no longer exists on the server.
spring.kafka.consumer.bootstrap-servers= # Comma-delimited list of host:port pairs to use for establishing the initial connections to the Kafka cluster. Overrides the global property, for consumers.
spring.kafka.consumer.client-id= # ID to pass to the server when making requests. Used for server-side logging.
spring.kafka.consumer.enable-auto-commit= # Whether the consumer's offset is periodically committed in the background.
spring.kafka.consumer.fetch-max-wait= # Maximum amount of time the server blocks before answering the fetch request if there isn't sufficient data to immediately satisfy the requirement given by "fetch.min.bytes".
spring.kafka.consumer.fetch-min-size= # Minimum amount of data, in bytes, the server should return for a fetch request.
spring.kafka.consumer.group-id= # Unique string that identifies the consumer group to which this consumer belongs.
spring.kafka.consumer.heartbeat-interval= # Expected time between heartbeats to the consumer coordinator.
spring.kafka.consumer.key-deserializer= # Deserializer class for keys.
spring.kafka.consumer.max-poll-records= # Maximum number of records returned in a single call to poll().
spring.kafka.consumer.properties.*= # Additional consumer-specific properties used to configure the client.
spring.kafka.consumer.ssl.key-password= # Password of the private key in the key store file.
spring.kafka.consumer.ssl.keystore-location= # Location of the key store file.
spring.kafka.consumer.ssl.keystore-password= # Store password for the key store file.
spring.kafka.consumer.ssl.keystore-type= # Type of the key store.
spring.kafka.consumer.ssl.protocol= # SSL protocol to use.
spring.kafka.consumer.ssl.truststore-location= # Location of the trust store file.
spring.kafka.consumer.ssl.truststore-password= # Store password for the trust store file.
spring.kafka.consumer.ssl.truststore-type= # Type of the trust store.
spring.kafka.consumer.value-deserializer= # Deserializer class for values.
spring.kafka.jaas.control-flag=required # Control flag for login configuration.
spring.kafka.jaas.enabled=false # Whether to enable JAAS configuration.
spring.kafka.jaas.login-module=com.sun.security.auth.module.Krb5LoginModule # Login module.
spring.kafka.jaas.options= # Additional JAAS options.
spring.kafka.listener.ack-count= # Number of records between offset commits when ackMode is "COUNT" or "COUNT_TIME".
spring.kafka.listener.ack-mode= # Listener AckMode. See the spring-kafka documentation.
spring.kafka.listener.ack-time= # Time between offset commits when ackMode is "TIME" or "COUNT_TIME".
spring.kafka.listener.client-id= # Prefix for the listener's consumer client.id property.
spring.kafka.listener.concurrency= # Number of threads to run in the listener containers.
spring.kafka.listener.idle-event-interval= # Time between publishing idle consumer events (no data received).
spring.kafka.listener.log-container-config= # Whether to log the container configuration during initialization (INFO level).
spring.kafka.listener.monitor-interval= # Time between checks for non-responsive consumers. If a duration suffix is not specified, seconds will be used.
spring.kafka.listener.no-poll-threshold= # Multiplier applied to "pollTimeout" to determine if a consumer is non-responsive.
spring.kafka.listener.poll-timeout= # Timeout to use when polling the consumer.
spring.kafka.listener.type=single # Listener type.
spring.kafka.producer.acks= # Number of acknowledgments the producer requires the leader to have received before considering a request complete.
spring.kafka.producer.batch-size= # Default batch size in bytes.
spring.kafka.producer.bootstrap-servers= # Comma-delimited list of host:port pairs to use for establishing the initial connections to the Kafka cluster. Overrides the global property, for producers.
spring.kafka.producer.buffer-memory= # Total bytes of memory the producer can use to buffer records waiting to be sent to the server.
spring.kafka.producer.client-id= # ID to pass to the server when making requests. Used for server-side logging.
spring.kafka.producer.compression-type= # Compression type for all data generated by the producer.
spring.kafka.producer.key-serializer= # Serializer class for keys.
spring.kafka.producer.properties.*= # Additional producer-specific properties used to configure the client.
spring.kafka.producer.retries= # When greater than zero, enables retrying of failed sends.
spring.kafka.producer.ssl.key-password= # Password of the private key in the key store file.
spring.kafka.producer.ssl.keystore-location= # Location of the key store file.
spring.kafka.producer.ssl.keystore-password= # Store password for the key store file.
spring.kafka.producer.ssl.keystore-type= # Type of the key store.
spring.kafka.producer.ssl.protocol= # SSL protocol to use.
spring.kafka.producer.ssl.truststore-location= # Location of the trust store file.
spring.kafka.producer.ssl.truststore-password= # Store password for the trust store file.
spring.kafka.producer.ssl.truststore-type= # Type of the trust store.
spring.kafka.producer.transaction-id-prefix= # When non empty, enables transaction support for producer.
spring.kafka.producer.value-serializer= # Serializer class for values.
spring.kafka.properties.*= # Additional properties, common to producers and consumers, used to configure the client.
spring.kafka.ssl.key-password= # Password of the private key in the key store file.
spring.kafka.ssl.keystore-location= # Location of the key store file.
spring.kafka.ssl.keystore-password= # Store password for the key store file.
spring.kafka.ssl.keystore-type= # Type of the key store.
spring.kafka.ssl.protocol= # SSL protocol to use.
spring.kafka.ssl.truststore-location= # Location of the trust store file.
spring.kafka.ssl.truststore-password= # Store password for the trust store file.
spring.kafka.ssl.truststore-type= # Type of the trust store.
spring.kafka.template.default-topic= # Default topic to which messages are sent.

# RABBIT (RabbitProperties)
spring.rabbitmq.addresses= # Comma-separated list of addresses to which the client should connect.
spring.rabbitmq.cache.channel.checkout-timeout= # Duration to wait to obtain a channel if the cache size has been reached.
spring.rabbitmq.cache.channel.size= # Number of channels to retain in the cache.
spring.rabbitmq.cache.connection.mode=channel # Connection factory cache mode.
spring.rabbitmq.cache.connection.size= # Number of connections to cache.
spring.rabbitmq.connection-timeout= # Connection timeout. Set it to zero to wait forever.
spring.rabbitmq.dynamic=true # Whether to create an AmqpAdmin bean.
spring.rabbitmq.host=localhost # RabbitMQ host.
spring.rabbitmq.listener.direct.acknowledge-mode= # Acknowledge mode of container.
spring.rabbitmq.listener.direct.auto-startup=true # Whether to start the container automatically on startup.
spring.rabbitmq.listener.direct.consumers-per-queue= # Number of consumers per queue.
spring.rabbitmq.listener.direct.default-requeue-rejected= # Whether rejected deliveries are re-queued by default.
spring.rabbitmq.listener.direct.idle-event-interval= # How often idle container events should be published.
spring.rabbitmq.listener.direct.prefetch= # Number of messages to be handled in a single request. It should be greater than or equal to the transaction size (if used).
spring.rabbitmq.listener.direct.retry.enabled=false # Whether publishing retries are enabled.
spring.rabbitmq.listener.direct.retry.initial-interval=1000ms # Duration between the first and second attempt to deliver a message.
spring.rabbitmq.listener.direct.retry.max-attempts=3 # Maximum number of attempts to deliver a message.
spring.rabbitmq.listener.direct.retry.max-interval=10000ms # Maximum duration between attempts.
spring.rabbitmq.listener.direct.retry.multiplier=1 # Multiplier to apply to the previous retry interval.
spring.rabbitmq.listener.direct.retry.stateless=true # Whether retries are stateless or stateful.
spring.rabbitmq.listener.simple.acknowledge-mode= # Acknowledge mode of container.
spring.rabbitmq.listener.simple.auto-startup=true # Whether to start the container automatically on startup.
spring.rabbitmq.listener.simple.concurrency= # Minimum number of listener invoker threads.
spring.rabbitmq.listener.simple.default-requeue-rejected= # Whether rejected deliveries are re-queued by default.
spring.rabbitmq.listener.simple.idle-event-interval= # How often idle container events should be published.
spring.rabbitmq.listener.simple.max-concurrency= # Maximum number of listener invoker threads.
spring.rabbitmq.listener.simple.prefetch= # Number of messages to be handled in a single request. It should be greater than or equal to the transaction size (if used).
spring.rabbitmq.listener.simple.retry.enabled=false # Whether publishing retries are enabled.
spring.rabbitmq.listener.simple.retry.initial-interval=1000ms # Duration between the first and second attempt to deliver a message.
spring.rabbitmq.listener.simple.retry.max-attempts=3 # Maximum number of attempts to deliver a message.
spring.rabbitmq.listener.simple.retry.max-interval=10000ms # Maximum duration between attempts.
spring.rabbitmq.listener.simple.retry.multiplier=1 # Multiplier to apply to the previous retry interval.
spring.rabbitmq.listener.simple.retry.stateless=true # Whether retries are stateless or stateful.
spring.rabbitmq.listener.simple.transaction-size= # Number of messages to be processed in a transaction. That is, the number of messages between acks. For best results, it should be less than or equal to the prefetch count.
spring.rabbitmq.listener.type=simple # Listener container type.
spring.rabbitmq.password=guest # Login to authenticate against the broker.
spring.rabbitmq.port=5672 # RabbitMQ port.
spring.rabbitmq.publisher-confirms=false # Whether to enable publisher confirms.
spring.rabbitmq.publisher-returns=false # Whether to enable publisher returns.
spring.rabbitmq.requested-heartbeat= # Requested heartbeat timeout; zero for none. If a duration suffix is not specified, seconds will be used.
spring.rabbitmq.ssl.algorithm= # SSL algorithm to use. By default, configured by the Rabbit client library.
spring.rabbitmq.ssl.enabled=false # Whether to enable SSL support.
spring.rabbitmq.ssl.key-store= # Path to the key store that holds the SSL certificate.
spring.rabbitmq.ssl.key-store-password= # Password used to access the key store.
spring.rabbitmq.ssl.key-store-type=PKCS12 # Key store type.
spring.rabbitmq.ssl.trust-store= # Trust store that holds SSL certificates.
spring.rabbitmq.ssl.trust-store-password= # Password used to access the trust store.
spring.rabbitmq.ssl.trust-store-type=JKS # Trust store type.
spring.rabbitmq.ssl.validate-server-certificate=true # Whether to enable server side certificate validation.
spring.rabbitmq.ssl.verify-hostname= # Whether to enable hostname verification. Requires AMQP client 4.8 or above and defaults to true when a suitable client version is used.
spring.rabbitmq.template.exchange= # Name of the default exchange to use for send operations.
spring.rabbitmq.template.mandatory= # Whether to enable mandatory messages.
spring.rabbitmq.template.receive-timeout= # Timeout for `receive()` operations.
spring.rabbitmq.template.reply-timeout= # Timeout for `sendAndReceive()` operations.
spring.rabbitmq.template.retry.enabled=false # Whether publishing retries are enabled.
spring.rabbitmq.template.retry.initial-interval=1000ms # Duration between the first and second attempt to deliver a message.
spring.rabbitmq.template.retry.max-attempts=3 # Maximum number of attempts to deliver a message.
spring.rabbitmq.template.retry.max-interval=10000ms # Maximum duration between attempts.
spring.rabbitmq.template.retry.multiplier=1 # Multiplier to apply to the previous retry interval.
spring.rabbitmq.template.routing-key= # Value of a default routing key to use for send operations.
spring.rabbitmq.username=guest # Login user to authenticate to the broker.
spring.rabbitmq.virtual-host= # Virtual host to use when connecting to the broker.

# ----------------------------------------
# ACTUATOR PROPERTIES
# ----------------------------------------

# MANAGEMENT HTTP SERVER (ManagementServerProperties)
management.server.add-application-context-header=false # Add the "X-Application-Context" HTTP header in each response.
management.server.address= # Network address to which the management endpoints should bind. Requires a custom management.server.port.
management.server.port= # Management endpoint HTTP port (uses the same port as the application by default). Configure a different port to use management-specific SSL.
management.server.servlet.context-path= # Management endpoint context-path (for instance, `/management`). Requires a custom management.server.port.
management.server.ssl.ciphers= # Supported SSL ciphers. Requires a custom management.port.
management.server.ssl.client-auth= # Whether client authentication is wanted ("want") or needed ("need"). Requires a trust store. Requires a custom management.server.port.
management.server.ssl.enabled= # Whether to enable SSL support. Requires a custom management.server.port.
management.server.ssl.enabled-protocols= # Enabled SSL protocols. Requires a custom management.server.port.
management.server.ssl.key-alias= # Alias that identifies the key in the key store. Requires a custom management.server.port.
management.server.ssl.key-password= # Password used to access the key in the key store. Requires a custom management.server.port.
management.server.ssl.key-store= # Path to the key store that holds the SSL certificate (typically a jks file). Requires a custom management.server.port.
management.server.ssl.key-store-password= # Password used to access the key store. Requires a custom management.server.port.
management.server.ssl.key-store-provider= # Provider for the key store. Requires a custom management.server.port.
management.server.ssl.key-store-type= # Type of the key store. Requires a custom management.server.port.
management.server.ssl.protocol=TLS # SSL protocol to use. Requires a custom management.server.port.
management.server.ssl.trust-store= # Trust store that holds SSL certificates. Requires a custom management.server.port.
management.server.ssl.trust-store-password= # Password used to access the trust store. Requires a custom management.server.port.
management.server.ssl.trust-store-provider= # Provider for the trust store. Requires a custom management.server.port.
management.server.ssl.trust-store-type= # Type of the trust store. Requires a custom management.server.port.

# CLOUDFOUNDRY
management.cloudfoundry.enabled=true # Whether to enable extended Cloud Foundry actuator endpoints.
management.cloudfoundry.skip-ssl-validation=false # Whether to skip SSL verification for Cloud Foundry actuator endpoint security calls.

# ENDPOINTS GENERAL CONFIGURATION
management.endpoints.enabled-by-default= # Whether to enable or disable all endpoints by default.

# ENDPOINTS JMX CONFIGURATION (JmxEndpointProperties)
management.endpoints.jmx.domain=org.springframework.boot # Endpoints JMX domain name. Fallback to 'spring.jmx.default-domain' if set.
management.endpoints.jmx.exposure.include=* # Endpoint IDs that should be included or '*' for all.
management.endpoints.jmx.exposure.exclude= # Endpoint IDs that should be excluded or '*' for all.
management.endpoints.jmx.static-names= # Additional static properties to append to all ObjectNames of MBeans representing Endpoints.
management.endpoints.jmx.unique-names=false # Whether to ensure that ObjectNames are modified in case of conflict.

# ENDPOINTS WEB CONFIGURATION (WebEndpointProperties)
management.endpoints.web.exposure.include=health,info # Endpoint IDs that should be included or '*' for all.
management.endpoints.web.exposure.exclude= # Endpoint IDs that should be excluded or '*' for all.
management.endpoints.web.base-path=/actuator # Base path for Web endpoints. Relative to server.servlet.context-path or management.server.servlet.context-path if management.server.port is configured.
management.endpoints.web.path-mapping= # Mapping between endpoint IDs and the path that should expose them.

# ENDPOINTS CORS CONFIGURATION (CorsEndpointProperties)
management.endpoints.web.cors.allow-credentials= # Whether credentials are supported. When not set, credentials are not supported.
management.endpoints.web.cors.allowed-headers= # Comma-separated list of headers to allow in a request. '*' allows all headers.
management.endpoints.web.cors.allowed-methods= # Comma-separated list of methods to allow. '*' allows all methods. When not set, defaults to GET.
management.endpoints.web.cors.allowed-origins= # Comma-separated list of origins to allow. '*' allows all origins. When not set, CORS support is disabled.
management.endpoints.web.cors.exposed-headers= # Comma-separated list of headers to include in a response.
management.endpoints.web.cors.max-age=1800s # How long the response from a pre-flight request can be cached by clients. If a duration suffix is not specified, seconds will be used.

# AUDIT EVENTS ENDPOINT (AuditEventsEndpoint)
management.endpoint.auditevents.cache.time-to-live=0ms # Maximum time that a response can be cached.
management.endpoint.auditevents.enabled=true # Whether to enable the auditevents endpoint.

# BEANS ENDPOINT (BeansEndpoint)
management.endpoint.beans.cache.time-to-live=0ms # Maximum time that a response can be cached.
management.endpoint.beans.enabled=true # Whether to enable the beans endpoint.

# CONDITIONS REPORT ENDPOINT (ConditionsReportEndpoint)
management.endpoint.conditions.cache.time-to-live=0ms # Maximum time that a response can be cached.
management.endpoint.conditions.enabled=true # Whether to enable the conditions endpoint.

# CONFIGURATION PROPERTIES REPORT ENDPOINT (ConfigurationPropertiesReportEndpoint, ConfigurationPropertiesReportEndpointProperties)
management.endpoint.configprops.cache.time-to-live=0ms # Maximum time that a response can be cached.
management.endpoint.configprops.enabled=true # Whether to enable the configprops endpoint.
management.endpoint.configprops.keys-to-sanitize=password,secret,key,token,.*credentials.*,vcap_services,sun.java.command # Keys that should be sanitized. Keys can be simple strings that the property ends with or regular expressions.

# ENVIRONMENT ENDPOINT (EnvironmentEndpoint, EnvironmentEndpointProperties)
management.endpoint.env.cache.time-to-live=0ms # Maximum time that a response can be cached.
management.endpoint.env.enabled=true # Whether to enable the env endpoint.
management.endpoint.env.keys-to-sanitize=password,secret,key,token,.*credentials.*,vcap_services,sun.java.command # Keys that should be sanitized. Keys can be simple strings that the property ends with or regular expressions.

# FLYWAY ENDPOINT (FlywayEndpoint)
management.endpoint.flyway.cache.time-to-live=0ms # Maximum time that a response can be cached.
management.endpoint.flyway.enabled=true # Whether to enable the flyway endpoint.

# HEALTH ENDPOINT (HealthEndpoint, HealthEndpointProperties)
management.endpoint.health.cache.time-to-live=0ms # Maximum time that a response can be cached.
management.endpoint.health.enabled=true # Whether to enable the health endpoint.
management.endpoint.health.roles= # Roles used to determine whether or not a user is authorized to be shown details. When empty, all authenticated users are authorized.
management.endpoint.health.show-details=never # When to show full health details.

# HEAP DUMP ENDPOINT (HeapDumpWebEndpoint)
management.endpoint.heapdump.cache.time-to-live=0ms # Maximum time that a response can be cached.
management.endpoint.heapdump.enabled=true # Whether to enable the heapdump endpoint.

# HTTP TRACE ENDPOINT (HttpTraceEndpoint)
management.endpoint.httptrace.cache.time-to-live=0ms # Maximum time that a response can be cached.
management.endpoint.httptrace.enabled=true # Whether to enable the httptrace endpoint.

# INFO ENDPOINT (InfoEndpoint)
info= # Arbitrary properties to add to the info endpoint.
management.endpoint.info.cache.time-to-live=0ms # Maximum time that a response can be cached.
management.endpoint.info.enabled=true # Whether to enable the info endpoint.

# JOLOKIA ENDPOINT (JolokiaProperties)
management.endpoint.jolokia.config.*= # Jolokia settings. Refer to the documentation of Jolokia for more details.
management.endpoint.jolokia.enabled=true # Whether to enable the jolokia endpoint.

# LIQUIBASE ENDPOINT (LiquibaseEndpoint)
management.endpoint.liquibase.cache.time-to-live=0ms # Maximum time that a response can be cached.
management.endpoint.liquibase.enabled=true # Whether to enable the liquibase endpoint.

# LOG FILE ENDPOINT (LogFileWebEndpoint, LogFileWebEndpointProperties)
management.endpoint.logfile.cache.time-to-live=0ms # Maximum time that a response can be cached.
management.endpoint.logfile.enabled=true # Whether to enable the logfile endpoint.
management.endpoint.logfile.external-file= # External Logfile to be accessed. Can be used if the logfile is written by output redirect and not by the logging system itself.

# LOGGERS ENDPOINT (LoggersEndpoint)
management.endpoint.loggers.cache.time-to-live=0ms # Maximum time that a response can be cached.
management.endpoint.loggers.enabled=true # Whether to enable the loggers endpoint.

# REQUEST MAPPING ENDPOINT  (MappingsEndpoint)
management.endpoint.mappings.cache.time-to-live=0ms # Maximum time that a response can be cached.
management.endpoint.mappings.enabled=true # Whether to enable the mappings endpoint.

# METRICS ENDPOINT (MetricsEndpoint)
management.endpoint.metrics.cache.time-to-live=0ms # Maximum time that a response can be cached.
management.endpoint.metrics.enabled=true # Whether to enable the metrics endpoint.

# PROMETHEUS ENDPOINT (PrometheusScrapeEndpoint)
management.endpoint.prometheus.cache.time-to-live=0ms # Maximum time that a response can be cached.
management.endpoint.prometheus.enabled=true # Whether to enable the prometheus endpoint.

# SCHEDULED TASKS ENDPOINT (ScheduledTasksEndpoint)
management.endpoint.scheduledtasks.cache.time-to-live=0ms # Maximum time that a response can be cached.
management.endpoint.scheduledtasks.enabled=true # Whether to enable the scheduledtasks endpoint.

# SESSIONS ENDPOINT (SessionsEndpoint)
management.endpoint.sessions.enabled=true # Whether to enable the sessions endpoint.

# SHUTDOWN ENDPOINT (ShutdownEndpoint)
management.endpoint.shutdown.enabled=false # Whether to enable the shutdown endpoint.

# THREAD DUMP ENDPOINT (ThreadDumpEndpoint)
management.endpoint.threaddump.cache.time-to-live=0ms # Maximum time that a response can be cached.
management.endpoint.threaddump.enabled=true # Whether to enable the threaddump endpoint.

# HEALTH INDICATORS
management.health.db.enabled=true # Whether to enable database health check.
management.health.cassandra.enabled=true # Whether to enable Cassandra health check.
management.health.couchbase.enabled=true # Whether to enable Couchbase health check.
management.health.couchbase.timeout=1000ms # Timeout for getting the Bucket information from the server.
management.health.defaults.enabled=true # Whether to enable default health indicators.
management.health.diskspace.enabled=true # Whether to enable disk space health check.
management.health.diskspace.path= # Path used to compute the available disk space.
management.health.diskspace.threshold=0 # Minimum disk space, in bytes, that should be available.
management.health.elasticsearch.enabled=true # Whether to enable Elasticsearch health check.
management.health.elasticsearch.indices= # Comma-separated index names.
management.health.elasticsearch.response-timeout=100ms # Time to wait for a response from the cluster.
management.health.influxdb.enabled=true # Whether to enable InfluxDB health check.
management.health.jms.enabled=true # Whether to enable JMS health check.
management.health.ldap.enabled=true # Whether to enable LDAP health check.
management.health.mail.enabled=true # Whether to enable Mail health check.
management.health.mongo.enabled=true # Whether to enable MongoDB health check.
management.health.neo4j.enabled=true # Whether to enable Neo4j health check.
management.health.rabbit.enabled=true # Whether to enable RabbitMQ health check.
management.health.redis.enabled=true # Whether to enable Redis health check.
management.health.solr.enabled=true # Whether to enable Solr health check.
management.health.status.http-mapping= # Mapping of health statuses to HTTP status codes. By default, registered health statuses map to sensible defaults (for example, UP maps to 200).
management.health.status.order=DOWN,OUT_OF_SERVICE,UP,UNKNOWN # Comma-separated list of health statuses in order of severity.

# HTTP TRACING (HttpTraceProperties)
management.trace.http.enabled=true # Whether to enable HTTP request-response tracing.
management.trace.http.include=request-headers,response-headers,cookies,errors # Items to be included in the trace.

# INFO CONTRIBUTORS (InfoContributorProperties)
management.info.build.enabled=true # Whether to enable build info.
management.info.defaults.enabled=true # Whether to enable default info contributors.
management.info.env.enabled=true # Whether to enable environment info.
management.info.git.enabled=true # Whether to enable git info.
management.info.git.mode=simple # Mode to use to expose git information.

# METRICS
management.metrics.binders.files.enabled=true # Whether to enable files metrics.
management.metrics.binders.jvm.enabled=true # Whether to enable JVM metrics.
management.metrics.binders.logback.enabled=true # Whether to enable Logback metrics.
management.metrics.binders.processor.enabled=true # Whether to enable processor metrics.
management.metrics.binders.uptime.enabled=true # Whether to enable uptime metrics.
management.metrics.distribution.percentiles-histogram.*= # Whether meter IDs starting-with the specified name should be publish percentile histograms.
management.metrics.distribution.percentiles.*= # Specific computed non-aggregable percentiles to ship to the backend for meter IDs starting-with the specified name.
management.metrics.distribution.sla.*= # Specific SLA boundaries for meter IDs starting-with the specified name. The longest match wins, the key `all` can also be used to configure all meters.
management.metrics.enable.*= # Whether meter IDs starting-with the specified name should be enabled. The longest match wins, the key `all` can also be used to configure all meters.
management.metrics.export.atlas.batch-size=10000 # Number of measurements per request to use for this backend. If more measurements are found, then multiple requests will be made.
management.metrics.export.atlas.config-refresh-frequency=10s # Frequency for refreshing config settings from the LWC service.
management.metrics.export.atlas.config-time-to-live=150s # Time to live for subscriptions from the LWC service.
management.metrics.export.atlas.config-uri=http://localhost:7101/lwc/api/v1/expressions/local-dev # URI for the Atlas LWC endpoint to retrieve current subscriptions.
management.metrics.export.atlas.connect-timeout=1s # Connection timeout for requests to this backend.
management.metrics.export.atlas.enabled=true # Whether exporting of metrics to this backend is enabled.
management.metrics.export.atlas.eval-uri=http://localhost:7101/lwc/api/v1/evaluate # URI for the Atlas LWC endpoint to evaluate the data for a subscription.
management.metrics.export.atlas.lwc-enabled=false # Whether to enable streaming to Atlas LWC.
management.metrics.export.atlas.meter-time-to-live=15m # Time to live for meters that do not have any activity. After this period the meter will be considered expired and will not get reported.
management.metrics.export.atlas.num-threads=2 # Number of threads to use with the metrics publishing scheduler.
management.metrics.export.atlas.read-timeout=10s # Read timeout for requests to this backend.
management.metrics.export.atlas.step=1m # Step size (i.e. reporting frequency) to use.
management.metrics.export.atlas.uri=http://localhost:7101/api/v1/publish # URI of the Atlas server.
management.metrics.export.datadog.api-key= # Datadog API key.
management.metrics.export.datadog.application-key= # Datadog application key. Not strictly required, but improves the Datadog experience by sending meter descriptions, types, and base units to Datadog.
management.metrics.export.datadog.batch-size=10000 # Number of measurements per request to use for this backend. If more measurements are found, then multiple requests will be made.
management.metrics.export.datadog.connect-timeout=1s # Connection timeout for requests to this backend.
management.metrics.export.datadog.descriptions=true # Whether to publish descriptions metadata to Datadog. Turn this off to minimize the amount of metadata sent.
management.metrics.export.datadog.enabled=true # Whether exporting of metrics to this backend is enabled.
management.metrics.export.datadog.host-tag=instance # Tag that will be mapped to "host" when shipping metrics to Datadog.
management.metrics.export.datadog.num-threads=2 # Number of threads to use with the metrics publishing scheduler.
management.metrics.export.datadog.read-timeout=10s # Read timeout for requests to this backend.
management.metrics.export.datadog.step=1m # Step size (i.e. reporting frequency) to use.
management.metrics.export.datadog.uri=https://app.datadoghq.com # URI to ship metrics to. If you need to publish metrics to an internal proxy en-route to Datadog, you can define the location of the proxy with this.
management.metrics.export.ganglia.addressing-mode=multicast # UDP addressing mode, either unicast or multicast.
management.metrics.export.ganglia.duration-units=milliseconds # Base time unit used to report durations.
management.metrics.export.ganglia.enabled=true # Whether exporting of metrics to Ganglia is enabled.
management.metrics.export.ganglia.host=localhost # Host of the Ganglia server to receive exported metrics.
management.metrics.export.ganglia.port=8649 # Port of the Ganglia server to receive exported metrics.
management.metrics.export.ganglia.protocol-version=3.1 # Ganglia protocol version. Must be either 3.1 or 3.0.
management.metrics.export.ganglia.rate-units=seconds # Base time unit used to report rates.
management.metrics.export.ganglia.step=1m # Step size (i.e. reporting frequency) to use.
management.metrics.export.ganglia.time-to-live=1 # Time to live for metrics on Ganglia. Set the multi-cast Time-To-Live to be one greater than the number of hops (routers) between the hosts.
management.metrics.export.graphite.duration-units=milliseconds # Base time unit used to report durations.
management.metrics.export.graphite.enabled=true # Whether exporting of metrics to Graphite is enabled.
management.metrics.export.graphite.host=localhost # Host of the Graphite server to receive exported metrics.
management.metrics.export.graphite.port=2004 # Port of the Graphite server to receive exported metrics.
management.metrics.export.graphite.protocol=pickled # Protocol to use while shipping data to Graphite.
management.metrics.export.graphite.rate-units=seconds # Base time unit used to report rates.
management.metrics.export.graphite.step=1m # Step size (i.e. reporting frequency) to use.
management.metrics.export.graphite.tags-as-prefix= # For the default naming convention, turn the specified tag keys into part of the metric prefix.
management.metrics.export.influx.auto-create-db=true # Whether to create the Influx database if it does not exist before attempting to publish metrics to it.
management.metrics.export.influx.batch-size=10000 # Number of measurements per request to use for this backend. If more measurements are found, then multiple requests will be made.
management.metrics.export.influx.compressed=true # Whether to enable GZIP compression of metrics batches published to Influx.
management.metrics.export.influx.connect-timeout=1s # Connection timeout for requests to this backend.
management.metrics.export.influx.consistency=one # Write consistency for each point.
management.metrics.export.influx.db=mydb # Tag that will be mapped to "host" when shipping metrics to Influx.
management.metrics.export.influx.enabled=true # Whether exporting of metrics to this backend is enabled.
management.metrics.export.influx.num-threads=2 # Number of threads to use with the metrics publishing scheduler.
management.metrics.export.influx.password= # Login password of the Influx server.
management.metrics.export.influx.read-timeout=10s # Read timeout for requests to this backend.
management.metrics.export.influx.retention-duration= # Time period for which Influx should retain data in the current database.
management.metrics.export.influx.retention-shard-duration= # Time range covered by a shard group.
management.metrics.export.influx.retention-policy= # Retention policy to use (Influx writes to the DEFAULT retention policy if one is not specified).
management.metrics.export.influx.retention-replication-factor= # How many copies of the data are stored in the cluster.
management.metrics.export.influx.step=1m # Step size (i.e. reporting frequency) to use.
management.metrics.export.influx.uri=http://localhost:8086 # URI of the Influx server.
management.metrics.export.influx.user-name= # Login user of the Influx server.
management.metrics.export.jmx.domain=metrics # Metrics JMX domain name.
management.metrics.export.jmx.enabled=true # Whether exporting of metrics to JMX is enabled.
management.metrics.export.jmx.step=1m # Step size (i.e. reporting frequency) to use.
management.metrics.export.newrelic.account-id= # New Relic account ID.
management.metrics.export.newrelic.api-key= # New Relic API key.
management.metrics.export.newrelic.batch-size=10000 # Number of measurements per request to use for this backend. If more measurements are found, then multiple requests will be made.
management.metrics.export.newrelic.connect-timeout=1s # Connection timeout for requests to this backend.
management.metrics.export.newrelic.enabled=true # Whether exporting of metrics to this backend is enabled.
management.metrics.export.newrelic.num-threads=2 # Number of threads to use with the metrics publishing scheduler.
management.metrics.export.newrelic.read-timeout=10s # Read timeout for requests to this backend.
management.metrics.export.newrelic.step=1m # Step size (i.e. reporting frequency) to use.
management.metrics.export.newrelic.uri=https://insights-collector.newrelic.com # URI to ship metrics to.
management.metrics.export.prometheus.descriptions=true # Whether to enable publishing descriptions as part of the scrape payload to Prometheus. Turn this off to minimize the amount of data sent on each scrape.
management.metrics.export.prometheus.enabled=true # Whether exporting of metrics to Prometheus is enabled.
management.metrics.export.prometheus.step=1m # Step size (i.e. reporting frequency) to use.
management.metrics.export.signalfx.access-token= # SignalFX access token.
management.metrics.export.signalfx.batch-size=10000 # Number of measurements per request to use for this backend. If more measurements are found, then multiple requests will be made.
management.metrics.export.signalfx.connect-timeout=1s # Connection timeout for requests to this backend.
management.metrics.export.signalfx.enabled=true # Whether exporting of metrics to this backend is enabled.
management.metrics.export.signalfx.num-threads=2 # Number of threads to use with the metrics publishing scheduler.
management.metrics.export.signalfx.read-timeout=10s # Read timeout for requests to this backend.
management.metrics.export.signalfx.source= # Uniquely identifies the app instance that is publishing metrics to SignalFx. Defaults to the local host name.
management.metrics.export.signalfx.step=10s # Step size (i.e. reporting frequency) to use.
management.metrics.export.signalfx.uri=https://ingest.signalfx.com # URI to ship metrics to.
management.metrics.export.simple.enabled=true # Whether, in the absence of any other exporter, exporting of metrics to an in-memory backend is enabled.
management.metrics.export.simple.mode=cumulative # Counting mode.
management.metrics.export.simple.step=1m # Step size (i.e. reporting frequency) to use.
management.metrics.export.statsd.enabled=true # Whether exporting of metrics to StatsD is enabled.
management.metrics.export.statsd.flavor=datadog # StatsD line protocol to use.
management.metrics.export.statsd.host=localhost # Host of the StatsD server to receive exported metrics.
management.metrics.export.statsd.max-packet-length=1400 # Total length of a single payload should be kept within your network's MTU.
management.metrics.export.statsd.polling-frequency=10s # How often gauges will be polled. When a gauge is polled, its value is recalculated and if the value has changed (or publishUnchangedMeters is true), it is sent to the StatsD server.
management.metrics.export.statsd.port=8125 # Port of the StatsD server to receive exported metrics.
management.metrics.export.statsd.publish-unchanged-meters=true # Whether to send unchanged meters to the StatsD server.
management.metrics.export.wavefront.api-token= # API token used when publishing metrics directly to the Wavefront API host.
management.metrics.export.wavefront.batch-size=10000 # Number of measurements per request to use for this backend. If more measurements are found, then multiple requests will be made.
management.metrics.export.wavefront.connect-timeout=1s # Connection timeout for requests to this backend.
management.metrics.export.wavefront.enabled=true # Whether exporting of metrics to this backend is enabled.
management.metrics.export.wavefront.global-prefix= # Global prefix to separate metrics originating from this app's white box instrumentation from those originating from other Wavefront integrations when viewed in the Wavefront UI.
management.metrics.export.wavefront.num-threads=2 # Number of threads to use with the metrics publishing scheduler.
management.metrics.export.wavefront.read-timeout=10s # Read timeout for requests to this backend.
management.metrics.export.wavefront.source= # Unique identifier for the app instance that is the source of metrics being published to Wavefront. Defaults to the local host name.
management.metrics.export.wavefront.step=10s # Step size (i.e. reporting frequency) to use.
management.metrics.export.wavefront.uri=https://longboard.wavefront.com # URI to ship metrics to.
management.metrics.use-global-registry=true # Whether auto-configured MeterRegistry implementations should be bound to the global static registry on Metrics.
management.metrics.web.client.max-uri-tags=100 # Maximum number of unique URI tag values allowed. After the max number of tag values is reached, metrics with additional tag values are denied by filter.
management.metrics.web.client.requests-metric-name=http.client.requests # Name of the metric for sent requests.
management.metrics.web.server.auto-time-requests=true # Whether requests handled by Spring MVC or WebFlux should be automatically timed.
management.metrics.web.server.max-uri-tags=100 # Maximum number of unique URI tag values allowed. After the max number of tag values is reached, metrics with additional tag values are denied by filter.
management.metrics.web.server.requests-metric-name=http.server.requests # Name of the metric for received requests.

# ----------------------------------------
# DEVTOOLS PROPERTIES
# ----------------------------------------

# DEVTOOLS (DevToolsProperties)
spring.devtools.livereload.enabled=true # Whether to enable a livereload.com-compatible server.
spring.devtools.livereload.port=35729 # Server port.
spring.devtools.restart.additional-exclude= # Additional patterns that should be excluded from triggering a full restart.
spring.devtools.restart.additional-paths= # Additional paths to watch for changes.
spring.devtools.restart.enabled=true # Whether to enable automatic restart.
spring.devtools.restart.exclude=META-INF/maven/**,META-INF/resources/**,resources/**,static/**,public/**,templates/**,**/*Test.class,**/*Tests.class,git.properties,META-INF/build-info.properties # Patterns that should be excluded from triggering a full restart.
spring.devtools.restart.log-condition-evaluation-delta=true # Whether to log the condition evaluation delta upon restart.
spring.devtools.restart.poll-interval=1s # Amount of time to wait between polling for classpath changes.
spring.devtools.restart.quiet-period=400ms # Amount of quiet time required without any classpath changes before a restart is triggered.
spring.devtools.restart.trigger-file= # Name of a specific file that, when changed, triggers the restart check. If not specified, any classpath file change triggers the restart.

# REMOTE DEVTOOLS (RemoteDevToolsProperties)
spring.devtools.remote.context-path=/.~~spring-boot!~ # Context path used to handle the remote connection.
spring.devtools.remote.proxy.host= # The host of the proxy to use to connect to the remote application.
spring.devtools.remote.proxy.port= # The port of the proxy to use to connect to the remote application.
spring.devtools.remote.restart.enabled=true # Whether to enable remote restart.
spring.devtools.remote.secret= # A shared secret required to establish a connection (required to enable remote support).
spring.devtools.remote.secret-header-name=X-AUTH-TOKEN # HTTP header used to transfer the shared secret.

# ----------------------------------------
# TESTING PROPERTIES
# ----------------------------------------

spring.test.database.replace=any # Type of existing DataSource to replace.
spring.test.mockmvc.print=default # MVC Print option.

5.5.2. 启动指定参数

配置资源文件位置，默认application.properties是放在jar包中的，通过spring.config.location可以制定外部配置文件，这样更便于运维。

java -jar demo.jar --spring.config.location=/opt/config/application.properties

5.5.3. 加载排除

spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration

5.5.4. PID FILE

spring.pid.fail-on-write-error= # Fail if ApplicationPidFileWriter is used but it cannot write the PID file.
spring.pid.file= # Location of the PID file to write (if ApplicationPidFileWriter is used).

5.5.5. 内嵌 tomcat server

5.5.5.1. server.tomcat.basedir

设置 Tomcat 工作目录，默认 /tmp/tomcat-docbase.7057591687859485145.7000 通过下面配置修改

server.tomcat.basedir=/tmp/your_project

5.5.5.2. server

server.port=8080 # 监听端口
server.address= # 绑定的地址

连接数配置

server.tomcat.max-threads=2048 # 最大线程数

5.5.5.3. access.log

如果前端有 nginx 代理这个配置可以禁用

server.tomcat.accesslog.enabled=true
server.tomcat.accesslog.directory=/tmp/logs
server.tomcat.accesslog.pattern=common
server.tomcat.accesslog.prefix=www.netkiller.cn.access
server.tomcat.accesslog.suffix=.log

5.5.5.4. logging

                logging.path=/tmp # 日志目录默认为 /tmp
                logging.file=your.log # 日志文件名称，默认为spring.log

                java -jar spring-boot-app.jar --logging.file=/tmp/spring-boot-app.log

5.5.5.5. charset

Spring boot 默认并非UTF-8 所以下面配置必设，否则将会出现

                spring.messages.encoding=UTF-8
                server.tomcat.uri-encoding=UTF-8
                spring.http.encoding.charset=UTF-8
                spring.http.encoding.enabled=true
                spring.http.encoding.force=true

5.5.5.6. Session 配置

                server.session.persistent 重启时是否持久化session，默认false
                server.session.timeout session的超时时间
                server.session.tracking-modes 设定Session的追踪模式(cookie, url, ssl).
                server.session.timeout=1800 #session有效时长

5.5.5.7. cookie

                server.session.cookie.comment 指定session cookie的comment
                server.session.cookie.domain 指定session cookie的domain
                server.session.cookie.http-only 否开启HttpOnly.
                server.session.cookie.max-age 设定session cookie的最大age.
                server.session.cookie.name 设定Session cookie 的名称.
                server.session.cookie.path 设定session cookie的路径.
                server.session.cookie.secure 设定session cookie的“Secure” flag.

案例

                server.session.cookie.name=PHPSESSID
                server.session.cookie.domain=.example.com
                server.session.cookie.http-only=true
                server.session.cookie.path=/

5.5.5.8. error 路径

                server.error.path=/error

5.5.5.9. 压缩传输

                server.compression.enabled=true #是否开启压缩，默认为false.
                server.compression.excluded-user-agents #指定不压缩的user-agent，多个以逗号分隔，默认值为:text/html,text/xml,text/plain,text/css
                server.compression.mime-types #指定要压缩的MIME type，多个以逗号分隔.
                server.compression.min-response-size #执行压缩的阈值，默认为2048

                server.compression.enabled=true
                server.compression.mime-types=application/json,application/xml,text/html,text/xml,text/plain,text/css,application/javascript
                server.compression.min-response-size=1024

5.5.5.10. server.servlet.context-path

                server.servlet.context-path=/your

5.5.5.11. ssl

                server.ssl.ciphers 是否支持SSL ciphers.
                server.ssl.client-auth 设定client authentication是wanted 还是 needed.
                server.ssl.enabled 是否开启ssl，默认: true
                server.ssl.key-alias 设定key store中key的别名.
                server.ssl.key-password 访问key store中key的密码.
                server.ssl.key-store 设定持有SSL certificate的key store的路径，通常是一个.jks文件.
                server.ssl.key-store-password设定访问key store的密码.
                server.ssl.key-store-provider设定key store的提供者.
                server.ssl.key-store-type 设定key store的类型.
                server.ssl.protocol 使用的SSL协议，默认: TLS
                server.ssl.trust-store 持有SSL certificates的Trust
                store.
                server.ssl.trust-store-password访问trust store的密码.
                server.ssl.trust-store-provider设定trust store的提供者.
                server.ssl.trust-store-type 指定trust store的类型.

生成证书

server.ssl.* #ssl相关配置

                keytool -genkey -alias springboot -keyalg RSA -keystore /www/ssl/spring
                设置密码 123456

配置 application.properties

                server.ssl.enabled 启动tomcat ssl配置
                server.ssl.keyAlias 别名
                server.ssl.keyPassword 密码
                server.ssl.keyStore 位置

                server.port=8443
                server.ssl.enabled=true
                server.ssl.keyAlias=springboot
                server.ssl.keyPassword=123456
                server.ssl.keyStore=/www/ssl/spring

5.5.5.12. 上传限制

spring.servlet.multipart.max-file-size=2MB
spring.servlet.multipart.max-request-size=10MB
spring.http.multipart.enabled=false

5.5.6. JSON 输出与日期格式化

            # Pretty-print JSON responses
            spring.jackson.serialization.indent_output=true

#序列化时间格式
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.mvc.date-format=yyyy-MM-dd HH:mm:ss
#mvc序列化时候时区选择
spring.jackson.time-zone=GMT+8

5.5.7. SMTP 相关配置

            spring.mail.host=smtp.netkiller.cn
            #spring.mail.username=
            #spring.mail.password=
            #spring.mail.properties.mail.smtp.auth=true
            #spring.mail.properties.mail.smtp.starttls.enable=true
            #spring.mail.properties.mail.smtp.starttls.required=true
            mail.smtp.debug=true

5.5.8. Redis

            # REDIS (RedisProperties)
            # Redis数据库索引（默认为0）
            spring.redis.database=0
            # Redis服务器地址
            spring.redis.host=localhost
            # 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

5.5.9. MongoDB

格式：mongodb://用户名:密码@主机地址/数据库

            spring.data.mongodb.uri=mongodb://user:passw0rd@mdb.netkiller.cn/test
            spring.data.mongodb.repositories.enabled=true

5.5.10. MySQL

            spring.datasource.driver-class-name=com.mysql.jdbc.Driver
            spring.datasource.url=jdbc:mysql://主机地址:端口号/数据库
            spring.datasource.username=用户名
            spring.datasource.password=密码
            spring.jpa.database=MYSQL # 启用JPA支持

5.5.11. Oracle

            spring.datasource.driver-class-name=oracle.jdbc.OracleDriver
            spring.datasource.url=jdbc:oracle:thin:@//odb.netkiller.cn:1521/orcl
            spring.datasource.username=orcl
            spring.datasource.password=passw0rd
            spring.datasource.connection-test-query="SELECT 1 FROM DUAL"
            spring.jpa.database-platform=org.hibernate.dialect.Oracle10gDialect

            spring.jpa.show-sql=true
            #spring.jpa.hibernate.ddl-auto=none
            #spring.jpa.hibernate.ddl-auto=create-drop
            spring.datasource.max-active=50
            spring.datasource.initial-size=5
            spring.datasource.max-idle=10
            spring.datasource.min-idle=5
            spring.datasource.test-while-idle=true
            spring.datasource.test-on-borrow=false
            spring.datasource.validation-query=SELECT 1 FROM DUAL
            spring.datasource.time-between-eviction-runs-millis=5000
            spring.datasource.min-evictable-idle-time-millis=60000

5.5.12. default_schema

            spring.jpa.properties.hibernate.default_schema=schema

5.5.13. datasource

启用/禁用 导入 schema.sql 和 data.sql / data-${platform}.sql 其中 platform 是 spring.datasource.platform 所定义的平台

            spring.datasource.initialize=false
            spring.datasource.platform=MYSQL

5.5.14. velocity

            spring.velocity.resourceLoaderPath=classpath:/templates/
            spring.velocity.prefix=
            spring.velocity.suffix=.vm
            spring.velocity.cache=false
            spring.velocity.check-template-location=true
            spring.velocity.content-type=text/html
            spring.velocity.charset=UTF-8
            spring.velocity.properties.input.encoding=UTF-8
            spring.velocity.properties.output.encoding=UTF-8

禁用 velocity 模板引擎

            spring.velocity.enabled=false
            spring.velocity.check-template-location=false

5.5.15. Security 相关配置

            security.user.name=user
            security.user.password=password
            security.user.role=USER

Web 安全

            # X-Frame-Options: DENY
            security.headers.frame=false

            security.headers.cache
            security.headers.content-type
            security.headers.hsts
            security.headers.xss

参考 https://github.com/spring-projects/spring-boot/blob/master/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/SecurityProperties.java#L171

5.5.16. MVC 配置

是否支持favicon.ico，默认为: true

spring.mvc.favicon.enabled=false

5.5.17. Kafka 相关配置

# APACHE KAFKA (KafkaProperties)
spring.kafka.admin.client-id= # ID to pass to the server when making requests. Used for server-side logging.
spring.kafka.admin.fail-fast=false # Whether to fail fast if the broker is not available on startup.
spring.kafka.admin.properties.*= # Additional admin-specific properties used to configure the client.
spring.kafka.admin.ssl.key-password= # Password of the private key in the key store file.
spring.kafka.admin.ssl.keystore-location= # Location of the key store file.
spring.kafka.admin.ssl.keystore-password= # Store password for the key store file.
spring.kafka.admin.ssl.keystore-type= # Type of the key store.
spring.kafka.admin.ssl.protocol= # SSL protocol to use.
spring.kafka.admin.ssl.truststore-location= # Location of the trust store file.
spring.kafka.admin.ssl.truststore-password= # Store password for the trust store file.
spring.kafka.admin.ssl.truststore-type= # Type of the trust store.
spring.kafka.bootstrap-servers= # Comma-delimited list of host:port pairs to use for establishing the initial connection to the Kafka cluster.
spring.kafka.client-id= # ID to pass to the server when making requests. Used for server-side logging.
spring.kafka.consumer.auto-commit-interval= # Frequency with which the consumer offsets are auto-committed to Kafka if 'enable.auto.commit' is set to true.
spring.kafka.consumer.auto-offset-reset= # What to do when there is no initial offset in Kafka or if the current offset no longer exists on the server.
spring.kafka.consumer.bootstrap-servers= # Comma-delimited list of host:port pairs to use for establishing the initial connection to the Kafka cluster.
spring.kafka.consumer.client-id= # ID to pass to the server when making requests. Used for server-side logging.
spring.kafka.consumer.enable-auto-commit= # Whether the consumer's offset is periodically committed in the background.
spring.kafka.consumer.fetch-max-wait= # Maximum amount of time the server blocks before answering the fetch request if there isn't sufficient data to immediately satisfy the requirement given by "fetch.min.bytes".
spring.kafka.consumer.fetch-min-size= # Minimum amount of data, in bytes, the server should return for a fetch request.
spring.kafka.consumer.group-id= # Unique string that identifies the consumer group to which this consumer belongs.
spring.kafka.consumer.heartbeat-interval= # Expected time between heartbeats to the consumer coordinator.
spring.kafka.consumer.key-deserializer= # Deserializer class for keys.
spring.kafka.consumer.max-poll-records= # Maximum number of records returned in a single call to poll().
spring.kafka.consumer.properties.*= # Additional consumer-specific properties used to configure the client.
spring.kafka.consumer.ssl.key-password= # Password of the private key in the key store file.
spring.kafka.consumer.ssl.keystore-location= # Location of the key store file.
spring.kafka.consumer.ssl.keystore-password= # Store password for the key store file.
spring.kafka.consumer.ssl.keystore-type= # Type of the key store.
spring.kafka.consumer.ssl.protocol= # SSL protocol to use.
spring.kafka.consumer.ssl.truststore-location= # Location of the trust store file.
spring.kafka.consumer.ssl.truststore-password= # Store password for the trust store file.
spring.kafka.consumer.ssl.truststore-type= # Type of the trust store.
spring.kafka.consumer.value-deserializer= # Deserializer class for values.
spring.kafka.jaas.control-flag=required # Control flag for login configuration.
spring.kafka.jaas.enabled=false # Whether to enable JAAS configuration.
spring.kafka.jaas.login-module=com.sun.security.auth.module.Krb5LoginModule # Login module.
spring.kafka.jaas.options= # Additional JAAS options.
spring.kafka.listener.ack-count= # Number of records between offset commits when ackMode is "COUNT" or "COUNT_TIME".
spring.kafka.listener.ack-mode= # Listener AckMode. See the spring-kafka documentation.
spring.kafka.listener.ack-time= # Time between offset commits when ackMode is "TIME" or "COUNT_TIME".
spring.kafka.listener.client-id= # Prefix for the listener's consumer client.id property.
spring.kafka.listener.concurrency= # Number of threads to run in the listener containers.
spring.kafka.listener.idle-event-interval= # Time between publishing idle consumer events (no data received).
spring.kafka.listener.log-container-config= # Whether to log the container configuration during initialization (INFO level).
spring.kafka.listener.monitor-interval= # Time between checks for non-responsive consumers. If a duration suffix is not specified, seconds will be used.
spring.kafka.listener.no-poll-threshold= # Multiplier applied to "pollTimeout" to determine if a consumer is non-responsive.
spring.kafka.listener.poll-timeout= # Timeout to use when polling the consumer.
spring.kafka.listener.type=single # Listener type.
spring.kafka.producer.acks= # Number of acknowledgments the producer requires the leader to have received before considering a request complete.
spring.kafka.producer.batch-size= # Default batch size in bytes.
spring.kafka.producer.bootstrap-servers= # Comma-delimited list of host:port pairs to use for establishing the initial connection to the Kafka cluster.
spring.kafka.producer.buffer-memory= # Total bytes of memory the producer can use to buffer records waiting to be sent to the server.
spring.kafka.producer.client-id= # ID to pass to the server when making requests. Used for server-side logging.
spring.kafka.producer.compression-type= # Compression type for all data generated by the producer.
spring.kafka.producer.key-serializer= # Serializer class for keys.
spring.kafka.producer.properties.*= # Additional producer-specific properties used to configure the client.
spring.kafka.producer.retries= # When greater than zero, enables retrying of failed sends.
spring.kafka.producer.ssl.key-password= # Password of the private key in the key store file.
spring.kafka.producer.ssl.keystore-location= # Location of the key store file.
spring.kafka.producer.ssl.keystore-password= # Store password for the key store file.
spring.kafka.producer.ssl.keystore-type= # Type of the key store.
spring.kafka.producer.ssl.protocol= # SSL protocol to use.
spring.kafka.producer.ssl.truststore-location= # Location of the trust store file.
spring.kafka.producer.ssl.truststore-password= # Store password for the trust store file.
spring.kafka.producer.ssl.truststore-type= # Type of the trust store.
spring.kafka.producer.transaction-id-prefix= # When non empty, enables transaction support for producer.
spring.kafka.producer.value-serializer= # Serializer class for values.
spring.kafka.properties.*= # Additional properties, common to producers and consumers, used to configure the client.
spring.kafka.ssl.key-password= # Password of the private key in the key store file.
spring.kafka.ssl.keystore-location= # Location of the key store file.
spring.kafka.ssl.keystore-password= # Store password for the key store file.
spring.kafka.ssl.keystore-type= # Type of the key store.
spring.kafka.ssl.protocol= # SSL protocol to use.
spring.kafka.ssl.truststore-location= # Location of the trust store file.
spring.kafka.ssl.truststore-password= # Store password for the trust store file.
spring.kafka.ssl.truststore-type= # Type of the trust store.
spring.kafka.template.default-topic= # Default topic to which messages are sent.

5.5.18.


5.6. Properties

5.6.1. @Value 注解

application.properties

server.name=Linux
server.host=192.168.0.1,172.16.0.1

    @Value("${server.name}")
    private String name;

处理逗号分割得值

     @Value("#{'${server.host}'.split(',')}")
     private List<String> host;

默认值

@Value("${server.name:Windows}") 如果application.properties没有配置server.name那么默认值将是 Windows
private String name;

@Value("${some.key:my default value}")
private String stringWithDefaultValue;

@Value("${some.key:true}")
private boolean booleanWithDefaultValue;

@Value("${some.key:42}")
private int intWithDefaultValue;

@Value("${some.key:one,two,three}")
private String[] stringArrayWithDefaults;

@Value("${some.key:1,2,3}")
private int[] intArrayWithDefaults;

// Using SpEL
@Value("#{systemProperties['some.key'] ?: 'my default system property value'}")
private String spelWithDefaultValue;

Null 默认值

    @Value("${app.name:@null}") // app.name = null
     private String name;

5.6.2. @PropertySource 注解载入 properties 文件

@PropertySource("classpath:/config.properties}")

忽略FileNotFoundException，当配置文件不存在系统抛出FileNotFoundException并终止程序运行，ignoreResourceNotFound=true 会跳过使程序能够正常运行
@PropertySource(value="classpath:config.properties", ignoreResourceNotFound=true)

载入多个配置文件

@PropertySources({
        @PropertySource("classpath:config.properties"),
        @PropertySource("classpath:db.properties")
})

test.properties

name=Neo
age=30

package cn.netkiller.web;

import java.util.Date;

import javax.servlet.http.HttpSession;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
@PropertySource("classpath:test.properties")
public class TestController {

    @Autowired
    Environment environment;

    @Value("${age}")
    private String age;

    public TestController() {
        // TODO Auto-generated constructor stub
    }

    // 环境变量方式
    @RequestMapping("/test/env")
    @ResponseBody
    public String env() {
        String message = environment.getProperty("name");
        return message;
    }

    @RequestMapping("/test/age")
    @ResponseBody
    public String age() {
        String message = age;
        return message;
    }

}

5.6.3. @EnableConfigurationProperties 引用自定义 *.properties 配置文件

Application.java 涮锅配置NetkillerProperties.java是 @ComponentScan 扫描范围，可以不用声明下面注解。

@EnableConfigurationProperties(NetkillerProperties.class)

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.authentication.UserCredentials;
import org.springframework.data.mongodb.MongoDbFactory;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.SimpleMongoDbFactory;
import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;

import com.mongodb.Mongo;

import pojo.NetkillerProperties;

@Configuration
@SpringBootApplication
@EnableConfigurationProperties(NetkillerProperties.class)
@EnableAutoConfiguration(exclude = { DataSourceAutoConfiguration.class })
@ComponentScan({ "web", "rest" })
@EnableMongoRepositories
public class Application {

    @SuppressWarnings("deprecation")
    public @Bean MongoDbFactory mongoDbFactory() throws Exception {
        UserCredentials userCredentials = new UserCredentials("finance", "your_password");
        return new SimpleMongoDbFactory(new Mongo("mdb.netkiller.cn"), "finance", userCredentials);
    }

    public @Bean MongoTemplate mongoTemplate() throws Exception {
        return new MongoTemplate(mongoDbFactory());
    }

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}

NetkillerProperties.java

package pojo;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

@ConfigurationProperties(prefix="netkiller")
public class NetkillerProperties {
    private String name;
    private String email;
    private String home;

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getEmail() {
        return email;
    }
    public void setEmail(String email) {
        this.email = email;
    }
    public String getHome() {
        return home;
    }
    public void setHome(String home) {
        this.home = home;
    }
    @Override
    public String toString() {
        return "NetkillerProperties [name=" + name + ", email=" + email + ", home=" + home + "]";
    }
}

IndexController.java

package web;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import domain.City;
import pojo.NetkillerProperties;
import repository.CityRepository;

@Controller
public class IndexController {

    @Autowired
    private CityRepository repository;

    @Autowired
    private NetkillerProperties propertie;

    @RequestMapping("/index")
    @ResponseBody
    public String index() {
    //public ModelAndView index() {

        String message = "Hello";
        //return new ModelAndView("home/welcome", "variable", message);
        return message;
    }

    @RequestMapping("/config")
    @ResponseBody
    public String config() {
        return propertie.toString();
    }
}

src/main/resource/application.properties

netkiller.name=Neo
netkiller.email=netkiller@msn.com
netkiller.home=http://www.netkiller.cn

@ConfigurationProperties 默认配置是 application.properties

你可以通过 locations 指向特定配置文件

                @ConfigurationProperties(prefix = "message.api",locations = "classpath:config/message.properties")

@EnableConfigurationProperties 可以导入多个配置文件

                @EnableConfigurationProperties({NetkillerProperties.class, NeoProperties.class})

5.6.4. 手工载入 *.properties 文件

    @RequestMapping("/config")
    @ResponseBody
    public void config() {
        try {
            Properties properties = PropertiesLoaderUtils.loadProperties(new ClassPathResource("/config.properties"));
            for(String key : properties.stringPropertyNames()) {
                  String value = properties.getProperty(key);
                  System.out.println(key + " => " + value);
            }
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

5.6.5. spring.profiles.active 参数切换配置文件

首先我们准备三个配置文件

src/main/resource/application-development.properties
src/main/resource/application-testing.properties
src/main/resource/application-production.properties

使用下面--spring.profiles.active参数切换运行环境配置文件

java -jar application.jar --spring.profiles.active=development
java -jar application.jar --spring.profiles.active=testing
java -jar application.jar --spring.profiles.active=production

分别为三个环境打包

mvn clean package -Pdevelopment
mvn clean package -Ptesting
mvn clean package -Pproduction

5.6.6. SpringApplicationBuilder.properties() 方法添加配置项

  public static void main(String[] args) {
    new SpringApplicationBuilder(Application.class).properties("spring.config.name=client").run(args);
  }


5.7. Spring boot with Logging

一般的日志需求可以通过配置 application.properties实现。

logging.file，设置文件，可以是绝对路径，也可以是相对路径。如：logging.file=my.log

logging.path，设置目录，如果 logging.file 没有设置，会在该目录下创建spring.log文件作为默认日志文件。

logging.file=target/spring.log
#logging.path=

如果仍不能满足可以使用 logback.xml 配置日志。

logging.path=/tmp
logging.config=classpath:logback.xml

5.7.1. 打印日志

日志的用法，首先开发中我们根据实际的需要打印不同级别的日志。

package cn.netkiller.web;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class TestController {

    private static final Logger log = LoggerFactory.getLogger(TestController.class);

    @RequestMapping("/test/log")
    @ResponseBody
    public String log() {
        String message = "Test";
        log.debug(message);
        log.info(message);
        log.warn(message);
        log.error(message);
        log.trace(message);
        return message;
    }
}

然后通过application.properties配置那些需要显示，那些不需要，以及显示的级别是什么。

5.7.2. 日志输出级别

显示所有DEBUG信息

logging.level.root=DEBUG

仅仅显示 springframework 调试信息

logging.level.org.springframework.web=DEBUG

仅仅显示 cn.netkiller.web.TestController 调试信息

private static final Logger log = LoggerFactory.getLogger(TestController.class);

log.debug(message);

logging.level.cn.netkiller.web.TestController=DEBUG

5.7.3. 禁止控制台输出日志

禁止控制台日志输出，同时将日志写入日志文件.

src/main/resources/application.properties

logging.path=/tmp
logging.file=/tmp/spring.log
logging.level.root=INFO
logging.level.org.springframework.web=DEBUG
logging.level.org.hibernate=ERROR

src/main/resources/logback.xml

$ cat src/main/resources/logback.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  <include resource="org/springframework/boot/logging/logback/defaults.xml" />
  <include resource="org/springframework/boot/logging/logback/file-appender.xml" />

  <root level="INFO">
    <appender-ref ref="FILE" />
  </root>
</configuration>

使用 java -jar project-version-xxx.jar 启动后控制不会再输出日志

5.7.4. 标准输出

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <include resource="org/springframework/boot/logging/logback/defaults.xml" />
    <include resource="org/springframework/boot/logging/logback/file-appender.xml" />
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%date{yyyy-MM-dd HH:mm:ss} %-4relative [%thread] %-5level %logger{35} : %msg %n</pattern>
        </encoder>
    </appender>
    <root level="INFO">
        <appender-ref ref="STDOUT" />
        <appender-ref ref="FILE" />
    </root>
</configuration>

5.7.5. 安天分隔日志

<?xml version="1.0" encoding="UTF-8"?>
<configuration  scan="true" scanPeriod="60 seconds" debug="false">
    <contextName>logback</contextName>
    <property name="log.path" value="target" />
    <!--输出到控制台-->
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} %contextName [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

    <!--输出到文件-->
    <appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${log.path}/spring.%d{yyyy-MM-dd}.log</fileNamePattern>
        </rollingPolicy>
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} %contextName [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

    <root level="info">
        <appender-ref ref="console" />
        <appender-ref ref="file" />
    </root>
</configuration>

5.7.6. 按照文件尺寸分割日志

按日期分割文件

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  <include resource="org/springframework/boot/logging/logback/defaults.xml" />
  <include resource="org/springframework/boot/logging/logback/file-appender.xml" />

<appender name="dailyRollingFileAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
  <File>logs/spring.log</File>
  <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
    <!-- daily rollover -->
    <FileNamePattern>spring.%d{yyyy-MM-dd}.log</FileNamePattern>
    <!-- keep 30 days' worth of history -->
    <maxHistory>60</maxHistory>
  </rollingPolicy>
  <encoder>
    <Pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{35} - %msg %n</Pattern>
  </encoder>
</appender>

  <root level="INFO">
    <appender-ref ref="FILE" />
    <appender-ref ref="dailyRollingFileAppender" />
  </root>
</configuration>

通过级别分割日志将 info, error, debug 分割到指定文件中。

<configuration scan="true" scanPeriod="10 seconds">
     <!-- 控制台日志输出-->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d %p (%file:%line\)- %m%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>
    <!-- info日志输出-->
    <appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <encoder>
            <pattern>%d %p (%file:%line\)- %m%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>INFO</level>
        </filter>
        <File>${LOG_PATH}/www.netkiller.cn.info.log</File>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${LOG_PATH}/www.netkiller.cn.info-%d{yyyyMMdd}.log.%i
            </fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>10MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <maxHistory>30</maxHistory>
        </rollingPolicy>
        <layout class="ch.qos.logback.classic.PatternLayout">
            <Pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} -%msg%n
            </Pattern>
        </layout>
    </appender>
    <!-- debug 日志输出-->
    <appender name="DEBUG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <encoder>
            <pattern>%d %p (%file:%line\)- %m%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>DEBUG</level>
        </filter>
        <File>${LOG_PATH}/www.netkiller.cn.debug.log</File>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${LOG_PATH}/www.netkiller.cn.debug-%d{yyyyMMdd}.log.%i
            </fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>10MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <maxHistory>30</maxHistory>
        </rollingPolicy>
        <layout class="ch.qos.logback.classic.PatternLayout">
            <Pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} -%msg%n
            </Pattern>
        </layout>
    </appender>

      <!--error 日志输出配置 -->
    <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <encoder>
            <pattern>%d %p (%file:%line\)- %m%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>ERROR</level>
        </filter>
        <File>${LOG_PATH}/www.netkiller.cn.error.log</File>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${LOG_PATH}/www.netkiller.cn.error-%d{yyyyMMdd}.log.%i</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>10MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <maxHistory>30</maxHistory>
        </rollingPolicy>
        <layout class="ch.qos.logback.classic.PatternLayout">
            <Pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} -%msg%n</Pattern>
        </layout>
    </appender>

    <root level="DEBUG">
        <!--
        <appender-ref ref="STDOUT" />
        <appender-ref ref="INFO_FILE" />
        <appender-ref ref="ERROR_FILE" />
        <appender-ref ref="DEBUG_FILE" />
        -->
        <appender-ref ref="ERROR_FILE" />
        <appender-ref ref="INFO_FILE" />
        <appender-ref ref="DEBUG_FILE" />
    </root>

</configuration>

5.7.7. 指定Class过滤日志

    <logger name="cn.netkiller.controller"/>

    <logger name="cn.netkiller.controller.HomeController" level="WARN" additivity="false">
        <appender-ref ref="console"/>
    </logger>

5.7.8. 日志写入 MongoDB

5.7.9. logback 配置详解

5.7.9.1. configuration 属性配置

scan:当此属性设置为true时，配置文件如果发生改变，将会被重新加载，默认值为true。
scanPeriod:设置监测配置文件是否有修改的时间间隔，如果没有给出时间单位，默认单位是毫秒。当scan为true时，此属性生效。默认的时间间隔为1分钟。
debug:当此属性设置为true时，将打印出logback内部日志信息，实时查看logback运行状态。默认值为false。

5.7.9.2. contextName 设置上下文名称

每个logger都关联到logger上下文，默认上下文名称为“default”。但可以使用设置成其他名字，用于区分不同应用程序的记录。设置后可以通过%contextName来打印日志上下文名称。
<contextName>logback</contextName>

5.7.9.3. property 设置变量

用来定义变量值的标签， 有两个属性，name和value；其中name的值是变量的名称，value的值时变量定义的值。通过定义的值会被插入到logger上下文中。定义变量后，可以使“${}”来使用变量。

<property name="log.path" value="/tmp" />

5.7.9.4. encoder 日志格式设置

<encoder>表示对日志进行编码：

%d{HH: mm:ss.SSS}——日志输出时间
%thread——输出日志的进程名字，这在Web应用以及异步任务处理中很有用
%-5level——日志级别，并且使用5个字符靠左对齐
%logger{36}——日志输出者的名字
%msg——日志消息
%n——平台的换行符

5.7.9.5. RollingFileAppender

上例中<fileNamePattern>${log.path}/logback.%d{yyyy-MM-dd}.log</fileNamePattern>定义了日志的切分方式——把每一天的日志归档到一个文件中，同理，可以使用%d{yyyy-MM-dd_HH-mm}来定义精确到分的日志切分方式。
<maxHistory>30</maxHistory>表示只保留最近30天的日志，以防止日志填满整个磁盘空间。
<totalSizeCap>1GB</totalSizeCap>用来指定日志文件的上限大小，例如设置为1GB的话，那么到了这个值，就会删除旧的日志。


5.8. Spring boot with Webpage

ViewResolver

5.8.1. Maven

        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-jasper</artifactId>
        </dependency>

5.8.2. application.properties

                spring.mvc.view.prefix=/WEB-INF/jsp/
                spring.mvc.view.suffix=.jsp

5.8.3. Application

package cn.netkiller;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.scheduling.annotation.EnableScheduling;

@SpringBootApplication
@EnableAutoConfiguration
@ComponentScan
@EnableScheduling
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

5.8.4. IndexController

package cn.netkiller.web;

import java.util.HashMap;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class IndexController {

    @RequestMapping("/welcome")
    @ResponseBody
    public String welcome() {
        String message = "Welcome";
        return message;
    }

    @RequestMapping("/index")
    public ModelAndView index() {
        String message = "Helloworld";
        return new ModelAndView("index").addObject("message", message);
    }
}

5.8.5. src/main/webapp/WEB-INF/jsp/index.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Home</title>
</head>
<body>
${message}
</body>
</html>

5.8.6. 集成模板引擎

如果你需要使用其他模板引擎可以采用 Bean 注解方式。

package cn.netkiller.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.view.InternalResourceViewResolver;

@Configuration
@EnableWebMvc
public class WebMvcConfig extends WebMvcConfigurerAdapter {

    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }

    @Bean
    public InternalResourceViewResolver viewResolver() {
        InternalResourceViewResolver resolver = new InternalResourceViewResolver();
        resolver.setPrefix("WEB-INF/jsp/");
        resolver.setSuffix(".jsp");
        return resolver;
    }

}


5.9. Spring boot with Velocity template

5.9.1. Maven

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

例 5.1. Spring boot with Velocity template (pom.xml)

<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>netkiller.cn</groupId>
    <artifactId>api.netkiller.cn</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>api.netkiller.cn</name>
    <url>http://maven.apache.org</url>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.8</java.version>
    </properties>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.2.RELEASE</version>
    </parent>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-mongodb</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-mongodb</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-oracle</artifactId>
            <version>1.0.0.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>com.oracle</groupId>
            <artifactId>ojdbc6</artifactId>
            <!-- <version>12.1.0.1</version> -->
            <version>11.2.0.3</version>
            <scope>system</scope>
            <systemPath>${basedir}/lib/ojdbc6.jar</systemPath>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-mail</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-velocity</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.velocity</groupId>
            <artifactId>velocity</artifactId>
        </dependency>
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <sourceDirectory>src</sourceDirectory>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.3</version>
                <configuration>
                    <source />
                    <target />
                </configuration>
            </plugin>
            <plugin>
                <artifactId>maven-war-plugin</artifactId>
                <version>2.6</version>
                <configuration>
                    <warSourceDirectory>WebContent</warSourceDirectory>
                    <failOnMissingWebXml>false</failOnMissingWebXml>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

5.9.2. Resource

src/main/resources/application.properties

spring.velocity.resourceLoaderPath=classpath:/templates/
spring.velocity.prefix=
spring.velocity.suffix=.vm
spring.velocity.cache=false
spring.velocity.check-template-location=true
spring.velocity.content-type=text/html
spring.velocity.charset=UTF-8
spring.velocity.properties.input.encoding=UTF-8
spring.velocity.properties.output.encoding=UTF-8

src/main/resources/templates/email.vm

<html>
<body>
    <h3>${title}!</h3>
    <p>${body}</p>
</body>
</html>

5.9.3. Application

package api;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

import api.ApplicationConfiguration;

@SpringBootApplication
@EnableConfigurationProperties(ApplicationConfiguration.class)
@EnableAutoConfiguration
@ComponentScan({ "api.web", "api.rest", "api.service" })
@EnableMongoRepositories
@EnableJpaRepositories
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}

5.9.4. RestController

package api.rest;

import java.io.File;
import java.util.HashMap;
import java.util.Map;

import javax.mail.internet.MimeMessage;

import org.apache.velocity.app.VelocityEngine;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.FileSystemResource;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.ui.velocity.VelocityEngineUtils;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

import api.pojo.Email;

@RestController
@RequestMapping("/v1/email")
public class EmailRestController extends CommonRestController {

    @Autowired
    private JavaMailSender javaMailSender;

    @Autowired
    private VelocityEngine velocityEngine;

    @RequestMapping("version")
    @ResponseStatus(HttpStatus.OK)
    public String version() {
        return "[OK] Welcome to withdraw Restful version 1.0";
    }

    @RequestMapping(value = "send", method = RequestMethod.POST, produces = { "application/xml", "application/json" })
    public ResponseEntity<Email> sendSimpleMail(@RequestBody Email email) {
        SimpleMailMessage message = new SimpleMailMessage();
        message.setFrom(email.getFrom());
        message.setTo(email.getTo());
        message.setSubject(email.getSubject());
        message.setText(email.getText());
        javaMailSender.send(message);
        email.setStatus(true);

        return new ResponseEntity<Email>(email, HttpStatus.OK);
    }

    @RequestMapping(value = "attachments", method = RequestMethod.POST, produces = { "application/xml", "application/json" })
    public ResponseEntity<Email> attachments(@RequestBody Email email) throws Exception {

        MimeMessage mimeMessage = javaMailSender.createMimeMessage();

        MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage, true);
        mimeMessageHelper.setFrom(email.getFrom());
        mimeMessageHelper.setTo(email.getTo());
        mimeMessageHelper.setSubject(email.getSubject());
        mimeMessageHelper.setText("<html><body><img src=\"cid:banner\" >" + email.getText() + "</body></html>", true);

        FileSystemResource file = new FileSystemResource(new File("banner.jpg"));
        mimeMessageHelper.addInline("banner", file);

        FileSystemResource fileSystemResource = new FileSystemResource(new File("Attachment.jpg"));
        mimeMessageHelper.addAttachment("Attachment.jpg", fileSystemResource);

        javaMailSender.send(mimeMessage);
        email.setStatus(true);

        return new ResponseEntity<Email>(email, HttpStatus.OK);
    }

    @RequestMapping(value = "template", method = RequestMethod.POST, produces = { "application/xml", "application/json" })
    public ResponseEntity<Email> template(@RequestBody Email email) throws Exception {

        Map<String, Object> model = new HashMap<String, Object>();
        model.put("title", email.getSubject());
        model.put("body", email.getText());
        String text = VelocityEngineUtils.mergeTemplateIntoString(velocityEngine, "email.vm", "UTF-8", model);

        System.out.println(text);

        MimeMessage mimeMessage = javaMailSender.createMimeMessage();

        MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage, true);
        mimeMessageHelper.setFrom(email.getFrom());
        mimeMessageHelper.setTo(email.getTo());
        mimeMessageHelper.setSubject(email.getSubject());
        mimeMessageHelper.setText(text, true);

        javaMailSender.send(mimeMessage);

        email.setStatus(true);

        return new ResponseEntity<Email>(email, HttpStatus.OK);
    }
}

5.9.5. Test

$ curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X POST -d '{"from":"www@netkiller.cn", "to":"21214094@qq.com","subject":"Hello","text":"Hello world!!!"}' http://172.16.0.20:8080/v1/email/template.json


5.10. Spring boot with Thymeleaf

5.10.1. Maven

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

5.10.2. application.properties

创建目录 src/main/resources/templates/ 用户存放模板文件

spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
spring.thymeleaf.mode=HTML5
spring.thymeleaf.encoding=UTF-8
spring.thymeleaf.content-type=text/html
spring.thymeleaf.cache=false

5.10.3. Controller

package cn.netkiller.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
@RequestMapping("/")
public class HelloController {

    @GetMapping("/hello")
    // 如果此处使用 @ResponseBody，将会返回 "hello" 字符串，而不是模板
    public String test() {
        return "hello";

    }

    @RequestMapping("/hello1")
    public String hello1(Map<String, Object> map) {
        // 传递参数测试
        map.put("name", "Neo");
        return "thymeleaf";
    }

    @RequestMapping("/hello2")
    public ModelAndView hello2() {
        ModelAndView mv = new ModelAndView();
        mv.addObject("name", "Amy");
        mv.setViewName("thymeleaf");
        return mv;
    }

    @RequestMapping(value = "/{name}", method = RequestMethod.GET)
    public String getMovie(@PathVariable String name, ModelMap model) {
        model.addAttribute("name", name);
        return "hello";
    }

}

5.10.4. HTML5 Template

在 src/main/resources/templates/ 目录下创建模板文件 hello.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8" />
<title>Spring MVC + Thymeleaf Example</title>
</head>
<body>
    <h1>Welcome to Thymeleaf</h1>
    <span th:text="${name}"></span>
</body>
</html>


5.11. String boot with RestTemplate

RestTemplate 是 Spring Restful Client 用于调用restful接口

5.11.1. 在控制器中完整实例

package api.web;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.client.RestTemplate;

import api.domain.City;
import api.repository.CityRepository;

@Controller
public class IndexController {

    @Autowired
    private CityRepository repository;

    // Spring RESTFul Client

    @RequestMapping("/restful/get")
    @ResponseBody
    public String restfulGet() {
        RestTemplate restTemplate = new RestTemplate();
        String text = restTemplate.getForObject("http://inf.netkiller.cn/detail/html/2/2/42564.html", String.class);
        return text;
    }

    @RequestMapping("/restful/get/{id}")
    @ResponseBody
    private static String restfulGetId(@PathVariable String id) {
        final String uri = "http://inf.netkiller.cn/detail/html/{tid}/{cid}/{id}.html";

        Map<String, String> params = new HashMap<String, String>();
        params.put("tid", "2");
        params.put("cid", "2");
        params.put("id", id);
        RestTemplate restTemplate = new RestTemplate();
        String result = restTemplate.getForObject(uri, String.class, params);

        return (result);
    }

    @RequestMapping("/restful/post/{id}")
    @ResponseBody
    private static String restfullPost(@PathVariable String id) {

        final String uri = "http://inf.netkiller.cn/detail/html/{tid}/{cid}/{id}.html";

        Map<String, String> params = new HashMap<String, String>();
        params.put("tid", "2");
        params.put("cid", "2");
        params.put("id", id);

        City city = new City("Shenzhen", "Guangdong");

        RestTemplate restTemplate = new RestTemplate();
        String result = restTemplate.postForObject(uri, city, String.class, params);
        return result;
    }

    @RequestMapping("/restful/put/{id}")
    private static void restfulPut(@PathVariable String id) {
        final String uri = "http://inf.netkiller.cn/detail/html/{tid}/{cid}/{id}.html";

        Map<String, String> params = new HashMap<String, String>();
        params.put("id", id);

        City city = new City("Shenzhen", "Guangdong");

        RestTemplate restTemplate = new RestTemplate();
        restTemplate.put(uri, city, params);
    }

    @RequestMapping("/restful/delete/{id}")
    private static void restfulDelete(@PathVariable String id) {
        final String uri = "http://inf.netkiller.cn/detail/html/{tid}/{cid}/{id}.html";

        Map<String, String> params = new HashMap<String, String>();
        params.put("id", id);

        RestTemplate restTemplate = new RestTemplate();
        restTemplate.delete(uri, params);
    }
}


5.12. Spring boot with Session share

5.12.1. Redis

5.12.1.1. Maven

增加下面代码到pom.xml

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.session</groupId>
            <artifactId>spring-session-data-redis</artifactId>
        </dependency>

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>cn.netkiller</groupId>
    <artifactId>deploy</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>deploy.netkiller.cn</name>
    <description>Deploy project for Spring Boot</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.2.RELEASE</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>
        <!-- <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> -->
        <!-- <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> -->
        <!-- <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-mongodb</artifactId> </dependency> -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.session</groupId>
            <artifactId>spring-session-data-redis</artifactId>
        </dependency>
        <!-- <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
        </dependency>
        <dependency>
            <groupId>org.webjars</groupId>
            <artifactId>webjars-locator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.webjars</groupId>
            <artifactId>sockjs-client</artifactId>
            <version>1.0.2</version>
        </dependency>
        <dependency>
            <groupId>org.webjars</groupId>
            <artifactId>stomp-websocket</artifactId>
            <version>2.3.3</version>
        </dependency>
        <dependency>
            <groupId>org.webjars</groupId>
            <artifactId>bootstrap</artifactId>
            <version>3.3.7</version>
        </dependency>
        <dependency>
            <groupId>org.webjars</groupId>
            <artifactId>jquery</artifactId>
            <version>3.1.0</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-jasper</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <scope>test</scope>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

    <repositories>
        <repository>
            <id>spring-snapshots</id>
            <name>Spring Snapshots</name>
            <url>https://repo.spring.io/snapshot</url>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
        </repository>
        <repository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
    </repositories>
    <pluginRepositories>
        <pluginRepository>
            <id>spring-snapshots</id>
            <name>Spring Snapshots</name>
            <url>https://repo.spring.io/snapshot</url>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
        </pluginRepository>
        <pluginRepository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </pluginRepository>
    </pluginRepositories>

</project>

5.12.1.2. application.properties

spring.session.store-type=redis 将Session 存储在Redis中

spring.redis.database=0
spring.redis.host=192.168.4.1
spring.redis.port=6379
#spring.redis.password=
spring.redis.pool.max-active=8
spring.redis.pool.max-wait=30
spring.redis.pool.max-idle=8
spring.redis.pool.min-idle=0
spring.redis.timeout=10

spring.session.store-type=redis

5.12.1.3. Application

package cn.netkiller;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;

@SpringBootApplication
@EnableAutoConfiguration
@ComponentScan
@EnableMongoRepositories
@EnableJpaRepositories
@EnableScheduling
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);

    }
}

RedisHttpSessionConfig.java

package cn.netkiller.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;

@Configuration
@EnableRedisHttpSession
public class RedisHttpSessionConfig {

    public RedisHttpSessionConfig() {
        // TODO Auto-generated constructor stub
    }

}

5.12.2. 测试 Session

package cn.netkiller.web;

import java.util.Date;

import javax.servlet.http.HttpSession;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class TestController {

    public TestController() {
        // TODO Auto-generated constructor stub
    }

    @RequestMapping("/session/set")
    @ResponseBody
    public String set(HttpSession session) {
        String key = "test";
        session.setAttribute(key, new Date());
        return key;
    }

    @RequestMapping("/session/get")
    @ResponseBody
    public String get(HttpSession session) {
        String value = (String) session.getAttribute("test").toString();
        return value;
    }

}

keys spring:session:* 查看 Session Key

$ telnet 192.168.4.1 6379
Connecting to 192.168.4.1:6379...
Connection established.
To escape to local shell, press 'Ctrl+Alt+]'.
keys spring:session:*
*7
$68
spring:session:sessions:expires:a510f46f-0a2f-4649-af05-34bd750562c1
$40
spring:session:expirations:1476100200000
$40
spring:session:expirations:1476098400000
$60
spring:session:sessions:f6494a2f-591e-42ba-b381-ce2596f4046d
$60
spring:session:sessions:a510f46f-0a2f-4649-af05-34bd750562c1
$112
spring:session:index:org.springframework.session.FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME:user
$60
spring:session:sessions:627018c8-243e-43ac-87b9-fc07f130c899

5.12.3. JDBC

spring.session.store-type=jdbc
spring.session.jdbc.table-name=SESSIONS


5.13. Spring boot with MongoDB

5.13.1. Maven

pom.xml

<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>netkiller.cn</groupId>
    <artifactId>api.netkiller.cn</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>api.netkiller.cn</name>
    <url>http://maven.apache.org</url>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.2.RELEASE</version>
    </parent>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
<!--
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-mongodb</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-mongodb</artifactId>
        </dependency>

        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <sourceDirectory>src</sourceDirectory>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.3</version>
                <configuration>
                    <source />
                    <target />
                </configuration>
            </plugin>
            <plugin>
                <artifactId>maven-war-plugin</artifactId>
                <version>2.6</version>
                <configuration>
                    <warSourceDirectory>WebContent</warSourceDirectory>
                    <failOnMissingWebXml>false</failOnMissingWebXml>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

5.13.2. Application

Application.java

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.authentication.UserCredentials;
import org.springframework.data.mongodb.MongoDbFactory;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.SimpleMongoDbFactory;
import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;

import com.mongodb.Mongo;

@Configuration
@SpringBootApplication
@EnableAutoConfiguration(exclude = { DataSourceAutoConfiguration.class })
@ComponentScan({ "web", "rest" })
@EnableMongoRepositories
public class Application {

    @SuppressWarnings("deprecation")
    public @Bean MongoDbFactory mongoDbFactory() throws Exception {
        UserCredentials userCredentials = new UserCredentials("finance", "En7d0l0wssXQ8owzedjb82I0BMd4pFoZ");
        return new SimpleMongoDbFactory(new Mongo("db.netkiller.cn"), "finance", userCredentials);
    }

    public @Bean MongoTemplate mongoTemplate() throws Exception {
        return new MongoTemplate(mongoDbFactory());
    }

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}

5.13.3. MongoTemplate

package web;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import api.domain.City;
import api.domain.Article;
import api.ApplicationConfiguration;
import api.repository.CityRepository;
import api.repository.ArticleRepository;
import api.service.TestService;

@Controller
public class IndexController {

    @Autowired
    private CityRepository repository;

    @Autowired
    private TestService testService;

    @Autowired
    private ApplicationConfiguration propertie;

    @RequestMapping("/repository")
    @ResponseBody
    public String repository() {

        repository.deleteAll();

        // save a couple of city
        repository.save(new City("Shenzhen", "China"));
        repository.save(new City("Beijing", "China"));

        System.out.println("--------------------------------");
        // fetch all city
        for (City city : repository.findAll()) {
            System.out.println(city);
        }
        // fetch an individual city
        System.out.println("--------------------------------");
        System.out.println(repository.findByName("Shenzhen"));
        System.out.println("--------------------------------");
        for (City city : repository.findByCountry("China")) {
            System.out.println(city);
        }

        String message = "Hello";
        return message;
    }

}

5.13.4. Repository

在上一节 MongoTemplate 中，继续添加下面代码。

package api;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

@SpringBootApplication
@EnableAutoConfiguration(exclude = { DataSourceAutoConfiguration.class })
@ComponentScan({ "api.web", "api.rest","api.service" })
@EnableMongoRepositories
public class Application {

    public @Bean WebMvcConfigurer corsConfigurer() {
        return new WebMvcConfigurerAdapter() {
            @Override
            public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping("/**");
            }
        };
    }
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}

Resource: src/main/resources/application.properties

spring.data.mongodb.uri=mongodb://finance:XQ8os82I0pFoZBMd4@mdb.netkiller.cn/finance
spring.data.mongodb.repositories.enabled=true

CityRepository.java

package repository;

import java.util.List;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.mongodb.repository.MongoRepository;

import domain.City;

public interface CityRepository extends MongoRepository<City, String> {
    public Page<City> findAll(Pageable pageable);

    public City findByNameAndCountry(String name, String country);

    public City findByName(String name);

    public List<City> findByCountry(String country);

}

City.java

package domain;

import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;

@Document(collection = "city")
public class City {

    @Id
    private String id;
    public String name;
    public String country;

    public City(String name, String country){
        this.setName(name);
        this.setCountry(country);
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getCountry() {
        return country;
    }
    public void setCountry(String country) {
        this.country = country;
    }
    @Override
    public String toString() {
        return "City [id=" + id + ", name=" + name + ", country=" + country + "]";
    }

}

在 IndexController 中调用 CityRepository

package web;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import repository.CityRepository;

import domain.City;
import repository.CityRepository;

@Controller
public class IndexController {

    @Autowired
    private CityRepository repository;

    @RequestMapping("/index")
    @ResponseBody
    public ModelAndView index() {
        String message = "Hello";
        return new ModelAndView("home/welcome", "variable", message);
    }

    @RequestMapping("/curd")
    @ResponseBody
    public String curd() {

        repository.deleteAll();

        // save a couple of city
        repository.save(new City("Shenzhen", "China"));
        repository.save(new City("Beijing", "China"));

        System.out.println("--------------------------------");
        // fetch all city
        for (City city : repository.findAll()) {
            System.out.println(city);
        }
        // fetch an individual city
        System.out.println("--------------------------------");
        System.out.println(repository.findByName("Shenzhen"));
        System.out.println("--------------------------------");
        for (City city : repository.findByCountry("China")) {
            System.out.println(city);
        }

        String message = "Hello";
        return message;
    }
}


5.14. Spring boot with MySQL

5.14.1. Maven

pom.xml

<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>netkiller.cn</groupId>
    <artifactId>api.netkiller.cn</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>api.netkiller.cn</name>
    <url>http://maven.apache.org</url>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.2.RELEASE</version>
    </parent>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
<!--
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-mongodb</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-mongodb</artifactId>
        </dependency>
         <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <sourceDirectory>src</sourceDirectory>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.3</version>
                <configuration>
                    <source />
                    <target />
                </configuration>
            </plugin>
            <plugin>
                <artifactId>maven-war-plugin</artifactId>
                <version>2.6</version>
                <configuration>
                    <warSourceDirectory>WebContent</warSourceDirectory>
                    <failOnMissingWebXml>false</failOnMissingWebXml>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

5.14.2. Resource

src/main/resources/application.properties

spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://192.168.6.1:3306/test
spring.datasource.username=root
spring.datasource.password=password

spring.jpa.database=MYSQL
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=update
#spring.jpa.hibernate.ddl-auto=create-drop

            spring.datasource.url=jdbc:mysql://localhost:3306/test
            spring.datasource.username=root
            spring.datasource.password=pasword
            spring.datasource.driver-class-name=com.mysql.jdbc.Driver
            spring.datasource.max-idle=10
            spring.datasource.max-wait=10000
            spring.datasource.min-idle=5
            spring.datasource.initial-size=5
            spring.datasource.validation-query=SELECT 1
            spring.datasource.test-on-borrow=false
            spring.datasource.test-while-idle=true
            spring.datasource.time-between-eviction-runs-millis=18800
            spring.datasource.jdbc-interceptors=ConnectionState;SlowQueryReport(threshold=0)

5.14.3. Application

package api;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;

import api.ApplicationConfiguration;

@SpringBootApplication
@EnableConfigurationProperties(ApplicationConfiguration.class)
@EnableAutoConfiguration
@ComponentScan({ "api.web", "api.rest","api.service" })
@EnableMongoRepositories
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}

5.14.4. JdbcTemplate

package api.web;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import api.domain.City;
import api.domain.Article;
import api.ApplicationConfiguration;
import api.repository.CityRepository;
import api.repository.ArticleRepository;
import api.service.TestService;

@Controller
public class IndexController {

    @Autowired
    private CityRepository repository;

    @Autowired
    private TestService testService;

    @Autowired
    private ApplicationConfiguration propertie;

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @RequestMapping(value = "/article")
    public @ResponseBody String dailyStats(@RequestParam Integer id) {
        String query = "SELECT id, title, content from article where id = " + id;

        return jdbcTemplate.queryForObject(query, (resultSet, i) -> {
            System.out.println(resultSet.getLong(1)+","+ resultSet.getString(2)+","+ resultSet.getString(3));
            return (resultSet.getLong(1)+","+ resultSet.getString(2)+","+ resultSet.getString(3));
        });
    }
}

5.14.5. CrudRepository

ArticleRepository

package api.repository;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;

import api.domain.Article;

@Repository
public interface ArticleRepository extends CrudRepository<Article, Long> {

    Page<Article> findAll(Pageable pageable);

}

Article.java

package api.domain;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Table;

@Entity
@Table(name = "article")
public class Article implements Serializable {
    private static final long serialVersionUID = 7998903421265538801L;

    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(name = "id", unique=true, nullable=false, insertable=true, updatable = false)
    private Long id;
    private String title;
    private String content;

    public Article(){

    }
    public Article(String title, String content) {
        this.title = title;
        this.content = content;
    }

    public Long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    @Override
    public String toString() {
        return "Article [id=" + id + ", title=" + title + ", content=" + content + "]";
    }

}

package api.web;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import api.domain.City;
import api.domain.Article;
import api.ApplicationConfiguration;
import api.repository.CityRepository;
import api.repository.ArticleRepository;
import api.service.TestService;

@Controller
public class IndexController {

    @Autowired
    private CityRepository repository;

    @Autowired
    private TestService testService;

    @Autowired
    private ApplicationConfiguration propertie;

    @Autowired
    private ArticleRepository articleRepository;

    @RequestMapping("/save")
    @ResponseBody
    public String save() {
        articleRepository.save(new Article("Neo", "Chen"));
        return "OK";
    }

    @RequestMapping("/mysql")
    @ResponseBody
    public String mysql() {

        for (Article article : articleRepository.findAll()) {
            System.out.println(article);
        }
        return "OK";
    }
}


5.15. Spring boot with Oracle

5.15.1. Maven

首先到oracle官网，根据你的Oracle数据库，下载ojdbc6.jar(Oracle 11) 或者 ojdbc7.jar (Oracle 12)

        <dependency>
            <groupId>com.oracle</groupId>
            <artifactId>ojdbc7</artifactId>
            <version>12.1.0.1</version>
        </dependency>

            mvn install:install-file -DgroupId=com.oracle -DartifactId=ojdbc6 -Dversion=11.2.0.3 -Dpackaging=jar -Dfile=ojdbc6.jar -DgeneratePom=true
            mvn install:install-file -Dfile=ojdbc7.jar -DgroupId=com.oracle -DartifactId=ojdbc7 -Dversion=12.1.0.1 -Dpackaging=jar

另一种方案是在项目根目录下创建一个/lib文件夹，将下载的驱动放入该文件夹中。然后pom.xml 加入下面代码

ojdbc6.jar 例子

        <dependency>
            <groupId>com.oracle</groupId>
            <artifactId>ojdbc6</artifactId>
            <version>11.2.0.3</version>
            <scope>system</scope>
            <systemPath>${basedir}/lib/ojdbc6.jar</systemPath>
        </dependency>

ojdbc7.jar 例子

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

例 5.2. Example Spring boot with Oracle

<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>netkiller.cn</groupId>
    <artifactId>api.netkiller.cn</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>api.netkiller.cn</name>
    <url>http://maven.apache.org</url>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.8</java.version>
    </properties>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.2.RELEASE</version>
    </parent>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency> -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-mongodb</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-mongodb</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-oracle</artifactId>
            <version>1.0.0.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>com.oracle</groupId>
            <artifactId>ojdbc6</artifactId>
            <!-- <version>12.1.0.1</version> -->
            <version>11.2.0.3</version>
            <scope>system</scope>
            <systemPath>${basedir}/lib/ojdbc6.jar</systemPath>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <sourceDirectory>src</sourceDirectory>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.3</version>
                <configuration>
                    <source />
                    <target />
                </configuration>
            </plugin>
            <plugin>
                <artifactId>maven-war-plugin</artifactId>
                <version>2.6</version>
                <configuration>
                    <warSourceDirectory>WebContent</warSourceDirectory>
                    <failOnMissingWebXml>false</failOnMissingWebXml>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

5.15.2. application.properties

spring.datasource.driver-class-name=oracle.jdbc.OracleDriver
spring.datasource.url=jdbc:oracle:thin:@//192.168.4.9:1521/orcl.example.com
spring.datasource.username=www
spring.datasource.password=123123
spring.jpa.database-platform=org.hibernate.dialect.Oracle10gDialect

spring.jpa.show-sql=true
#spring.jpa.hibernate.ddl-auto=update
spring.jpa.hibernate.ddl-auto=create-drop

其他配置

            spring.datasource.connection-test-query="SELECT 1 FROM DUAL"
            spring.datasource.test-while-idle=true
            spring.datasource.test-on-borrow=true

Oracle RAC

            jdbc:oracle:thin@(DESCRIPTION=
            (LOAD_BALANCE=on)
            (ADDRESS=(PROTOCOL=TCP)(HOST=racnode1) (PORT=1521))
            (ADDRESS=(PROTOCOL=TCP)(HOST=racnode2) (PORT=1521))
            (CONNECT_DATA=(SERVICE_NAME=service_name)))

            jdbc:oracle:thin:@(DESCRIPTION=
            (ADDRESS=(PROTOCOL=TCP)(HOST=125.22.42.68)(PORT=1521))
            (LOAD_BALANCE=on)
            (FAILOVER=ON)
            (CONNECT_DATA=
            (SERVER=DEDICATED)
            (SERVICE_NAME=service_name)
            (FAILOVER_MODE=(TYPE=SESSION)(METHOD=BASIC))
            )
            )

5.15.3. Application

package api;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

import api.ApplicationConfiguration;

@SpringBootApplication
@EnableConfigurationProperties(ApplicationConfiguration.class)
@EnableAutoConfiguration
@ComponentScan({ "api.web", "api.rest", "api.service" })
@EnableMongoRepositories
@EnableJpaRepositories
public class Application {

    public @Bean WebMvcConfigurer corsConfigurer() {
        return new WebMvcConfigurerAdapter() {
            @Override
            public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping("/**");
            }
        };
    }

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}

5.15.4. CrudRepository

package api.repository;

import java.util.List;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;

import api.domain.Article;

@Repository
public interface ArticleRepository extends CrudRepository<Article, Long> {

    Page<Article> findAll(Pageable pageable);

    Article findByTitle(String title);

    //@Query("select id,title,content from Article where id > ?1")
    //public List<Article> findBySearch(@Param("id")long id);

}

package api.domain;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.SequenceGenerator;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Table;

@Entity
@Table(name = "article")
public class Article implements Serializable {
    private static final long serialVersionUID = 7998903421265538801L;

    @Id
    @Column(name = "ID")
    @GeneratedValue(strategy=GenerationType.SEQUENCE, generator = "id_Sequence")
    @SequenceGenerator(name = "id_Sequence", sequenceName = "ID_SEQ")
    private Long id;
    private String title;
    private String content;

    public Article(){

    }
    public Article(String title, String content) {
        this.title = title;
        this.content = content;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    @Override
    public String toString() {
        return "Article [id=" + id + ", title=" + title + ", content=" + content + "]";
    }

}

5.15.5. JdbcTemplate

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @RequestMapping(value = "/article")
    public @ResponseBody String dailyStats(@RequestParam Integer id) {
        String query = "SELECT id, title, content from article where id = " + id;

        return jdbcTemplate.queryForObject(query, (resultSet, i) -> {
            System.out.println(resultSet.getLong(1)+","+ resultSet.getString(2)+","+ resultSet.getString(3));
            return (resultSet.getLong(1)+","+ resultSet.getString(2)+","+ resultSet.getString(3));
        });
    }

5.15.6. Controller

package api.web;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import api.domain.Article;
import api.repository.ArticleRepository;

@Controller
public class IndexController {

    @Autowired
    private ArticleRepository articleRepository;

    @RequestMapping("/mysql")
    @ResponseBody
    public String mysql() {
        repository.deleteAll();
        return "Deleted"
    }

    @RequestMapping("/mysql")
    @ResponseBody
    public String mysql() {
        articleRepository.save(new Article("Neo", "Chen"));
        for (Article article : articleRepository.findAll()) {
            System.out.println(article);
        }
        Article tmp = articleRepository.findByTitle("Neo");
        return tmp.getTitle();
    }

    /*
    @RequestMapping("/search")
    @ResponseBody
    public String search() {

        /*for (Article article : articleRepository.findBySearch(1)) {
            System.out.println(article);
        }*/
        List<Article> tmp = articleRepository.findBySearch(1L);

        tmp.forEach((temp) -> {
            System.out.println(temp.toString());
        });

        return tmp.get(0).getTitle();
    }
    */

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @RequestMapping(value = "/article")
    public @ResponseBody String dailyStats(@RequestParam Integer id) {
        String query = "SELECT id, title, content from article where id = " + id;

        return jdbcTemplate.queryForObject(query, (resultSet, i) -> {
            System.out.println(resultSet.getLong(1)+","+ resultSet.getString(2)+","+ resultSet.getString(3));
            return (resultSet.getLong(1)+","+ resultSet.getString(2)+","+ resultSet.getString(3));
        });

    }
}


5.16. Spring boot with PostgreSQL

5.16.1. pom.xml

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- https://mvnrepository.com/artifact/org.postgresql/postgresql -->
<dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
    <version>9.4.1212</version>
</dependency>

5.16.2. application.properties

spring.datasource.url=jdbc:postgresql://localhost:5432/your-database
spring.datasource.username=postgres
spring.datasource.password=postgres

spring.jpa.database=POSTGRESQL
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.generate-ddl=true

5.16.3. Application

package cn.netkiller;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;
import org.springframework.scheduling.annotation.EnableScheduling;

@SpringBootApplication
@EnableAutoConfiguration
@ComponentScan
@EnableMongoRepositories
@EnableJpaRepositories
@EnableScheduling
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

5.16.4. CrudRepository

Model Class

package cn.netkiller.model;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name = "customer")
public class Customer implements Serializable {

    private static final long serialVersionUID = -3009077722242246666L;
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;

    @Column(name = "firstname")
    private String firstName;

    @Column(name = "lastname")
    private String lastName;

    protected Customer() {
    }

    public Customer(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    @Override
    public String toString() {
        return String.format("Customer[id=%d, firstName='%s', lastName='%s']", id, firstName, lastName);
    }
}

CrudRepository

package cn.netkiller.repository;

import java.util.List;

import org.springframework.data.repository.CrudRepository;

import cn.netkiller.model.Customer;

public interface CustomerRepository extends CrudRepository<Customer, Long>{
    List<Customer> findByFirstName(String firstName);
    List<Customer> findByLastName(String lastName);
}

5.16.5. JdbcTemplate

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @RequestMapping(value = "/jdbc")
    public @ResponseBody String dailyStats(@RequestParam Integer id) {
        String query = "SELECT id, firstname, lastname from customer where id = " + id;

        return jdbcTemplate.queryForObject(query, (resultSet, i) -> {
            System.out.println(resultSet.getLong(1)+","+ resultSet.getString(2)+","+ resultSet.getString(3));
            return (resultSet.getLong(1)+","+ resultSet.getString(2)+","+ resultSet.getString(3));
        });
    }

5.16.6. Controller

package cn.netkiller.web;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import cn.netkiller.model.Customer;
import cn.netkiller.repository.CustomerRepository;

@Controller
@RequestMapping("/test/pgsql")
public class TestPostgreSQLController {

    @Autowired
    private CustomerRepository customerRepository;

    @RequestMapping("/save")
    public @ResponseBody String process() {
        customerRepository.save(new Customer("Neo", "Chan"));
        customerRepository.save(new Customer("Luke", "Liu"));
        customerRepository.save(new Customer("Ran", "Guo"));
        customerRepository.save(new Customer("Joey", "Chen"));
        customerRepository.save(new Customer("Larry", "Huang"));
        return "Done";
    }

    @RequestMapping("/findall")
    public @ResponseBody String findAll() {
        String result = "<html>";

        for (Customer cust : customerRepository.findAll()) {
            result += "<div>" + cust.toString() + "</div>";
        }

        return result + "</html>";
    }

    @RequestMapping("/findbyid")
    public @ResponseBody String findById(@RequestParam("id") long id) {
        String result = "";
        result = customerRepository.findOne(id).toString();
        return result;
    }

    @RequestMapping("/findbylastname")
    public @ResponseBody String fetchDataByLastName(@RequestParam("lastname") String lastName) {
        String result = "<html>";

        for (Customer cust : customerRepository.findByLastName(lastName)) {
            result += "<div>" + cust.toString() + "</div>";
        }

        return result + "</html>";
    }

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @RequestMapping(value = "/jdbc")
    public @ResponseBody String dailyStats(@RequestParam Integer id) {
        String query = "SELECT id, firstname, lastname from customer where id = " + id;

        return jdbcTemplate.queryForObject(query, (resultSet, i) -> {
            System.out.println(resultSet.getLong(1)+","+ resultSet.getString(2)+","+ resultSet.getString(3));
            return (resultSet.getLong(1)+","+ resultSet.getString(2)+","+ resultSet.getString(3));
        });
    }
}

5.16.7. Test

            curl http://127.0.0.1:7000/test/pgsql/save
            curl http://127.0.0.1:7000/test/pgsql/findall
            curl http://127.0.0.1:7000/test/pgsql/findbyid?id=1
            curl http://127.0.0.1:7000/test/pgsql/jdbc?id=1


5.17. 数据源配置

5.17.1. 多数据源配置

package cn.netkiller.project.config;

import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;

@Configuration
public class DataSourceConfig {

    @Bean(name = "wwwDataSource")
    @Qualifier("wwwDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.www")
    public DataSource wwwDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean(name = "apiDataSource")
    @Qualifier("apiDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.api")
    public DataSource apiDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean(name = "cmsDataSource")
    @Qualifier("cmsDataSource")
    @Primary
    @ConfigurationProperties(prefix = "spring.datasource.cms")
    public DataSource cmsDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean
    PlatformTransactionManager transactionManager() {
        return new DataSourceTransactionManager(apiDataSource());
    }

    @Bean(name = "wwwJdbcTemplate")
    public JdbcTemplate wwwJdbcTemplate(@Qualifier("wwwDataSource") DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }

    @Bean(name = "appJdbcTemplate")
    public JdbcTemplate appJdbcTemplate(@Qualifier("apiDataSource") DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }

    @Bean(name = "cmsJdbcTemplate")
    public JdbcTemplate cmsJdbcTemplate(@Qualifier("cmsDataSource") DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }
}

对应 application.properties 的配置方法

spring.datasource.www.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.www.url=jdbc:mysql://localhost:3306/www?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false
spring.datasource.www.username=www
spring.datasource.www.password=passw0rd
spring.datasource.www.max-idle=10
spring.datasource.www.max-wait=10000
spring.datasource.www.min-idle=5
spring.datasource.www.initial-size=5
spring.datasource.www.validation-query=SELECT 1
spring.datasource.www.test-on-borrow=false
spring.datasource.www.test-while-idle=true
spring.datasource.www.time-between-eviction-runs-millis=18800
spring.datasource.www.jdbc-interceptors=ConnectionState;SlowQueryReport(threshold=0)

spring.datasource.api.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.api.url=jdbc:mysql://localhost:3306/api?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false
spring.datasource.api.username=api
spring.datasource.api.password=passw0rd
spring.datasource.api.max-idle=10
spring.datasource.api.max-wait=10000
spring.datasource.api.min-idle=5
spring.datasource.api.initial-size=5
spring.datasource.api.validation-query=SELECT 1
spring.datasource.api.test-on-borrow=false
spring.datasource.api.test-while-idle=true
spring.datasource.api.time-between-eviction-runs-millis=18800
spring.datasource.api.jdbc-interceptors=ConnectionState;SlowQueryReport(threshold=0)

选择数据库

    @Autowired
    @Qualifier("apiJdbcTemplate")
    JdbcTemplate jdbcTempalte;

5.17.2. JPA 多数据源

多个 JPA 数据配置非常简单，请参考下面的例子。注意两点，第一点是设置Repository的位置：

@EnableJpaRepositories(
        entityManagerFactoryRef="entityManagerFactoryPrimary",
        transactionManagerRef="transactionManagerPrimary",
        basePackages= { "cn.netkiller.repository.primary" }) //设置Repository所在位置

第二点是设置 Domain 位置，与 Repository 成套出现

    public LocalContainerEntityManagerFactoryBean entityManagerFactoryPrimary (EntityManagerFactoryBuilder builder) {
        return builder
                .dataSource(primaryDataSource)
                .properties(getVendorProperties(primaryDataSource))
                .packages("cn.netkiller.domain.primary") //设置实体类所在包
                .persistenceUnit("primaryPersistenceUnit")
                .build();
    }

首先配置第一组数据源。

package cn.netkiller.project.config;

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
        entityManagerFactoryRef="entityManagerFactoryPrimary",
        transactionManagerRef="transactionManagerPrimary",
        basePackages= { "cn.netkiller.repository.primary" }) //设置Repository所在位置
public class PrimaryConfig {

    @Autowired @Qualifier("primaryDataSource")
    private DataSource primaryDataSource;

    @Primary
    @Bean(name = "entityManagerPrimary")
    public EntityManager entityManager(EntityManagerFactoryBuilder builder) {
        return entityManagerFactoryPrimary(builder).getObject().createEntityManager();
    }

    @Primary
    @Bean(name = "entityManagerFactoryPrimary")
    public LocalContainerEntityManagerFactoryBean entityManagerFactoryPrimary (EntityManagerFactoryBuilder builder) {
        return builder
                .dataSource(primaryDataSource)
                .properties(getVendorProperties(primaryDataSource))
                .packages("cn.netkiller.domain.primary") //设置实体类所在包
                .persistenceUnit("primaryPersistenceUnit")
                .build();
    }

    @Autowired
    private JpaProperties jpaProperties;

    private Map<String, String> getVendorProperties(DataSource dataSource) {
        return jpaProperties.getHibernateProperties(dataSource);
    }

    @Primary
    @Bean(name = "transactionManagerPrimary")
    public PlatformTransactionManager transactionManagerPrimary(EntityManagerFactoryBuilder builder) {
        return new JpaTransactionManager(entityManagerFactoryPrimary(builder).getObject());
    }

}

设置第二组数据源

package cn.netkiller.project.config;

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
        entityManagerFactoryRef="entityManagerFactorySecondary",
        transactionManagerRef="transactionManagerSecondary",
        basePackages= { "cn.netkiller.repository.secondary" }) //设置Repository所在位置
public class SecondaryConfig {

    @Autowired @Qualifier("secondaryDataSource")
    private DataSource secondaryDataSource;

    @Bean(name = "entityManagerSecondary")
    public EntityManager entityManager(EntityManagerFactoryBuilder builder) {
        return entityManagerFactorySecondary(builder).getObject().createEntityManager();
    }

    @Bean(name = "entityManagerFactorySecondary")
    public LocalContainerEntityManagerFactoryBean entityManagerFactorySecondary (EntityManagerFactoryBuilder builder) {
        return builder
                .dataSource(secondaryDataSource)
                .properties(getVendorProperties(secondaryDataSource))
                .packages("cn.netkiller.repository.domain.secondary") //设置Domain实体类所在位置
                .persistenceUnit("secondaryPersistenceUnit")
                .build();
    }

    @Autowired
    private JpaProperties jpaProperties;

    private Map<String, String> getVendorProperties(DataSource dataSource) {
        return jpaProperties.getHibernateProperties(dataSource);
    }

    @Bean(name = "transactionManagerSecondary")
    PlatformTransactionManager transactionManagerSecondary(EntityManagerFactoryBuilder builder) {
        return new JpaTransactionManager(entityManagerFactorySecondary(builder).getObject());
    }

}

5.17.3. Connection and Statement Pooling

注意：下面的实例仅限 Spring boot 2.0.2.RELEASE

5.17.3.1. org.apache.tomcat.jdbc.pool.DataSource

默认连接池，可以忽略配置

                spring.datasource.type = org.apache.tomcat.jdbc.pool.DataSource

5.17.3.2. druid

pom.xml

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.0.24</version>
        </dependency>

application.properties

spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.initialSize=5
spring.datasource.minIdle=5
spring.datasource.maxActive=20
spring.datasource.maxWait=60000
spring.datasource.timeBetweenEvictionRunsMillis=60000
spring.datasource.minEvictableIdleTimeMillis=300000
spring.datasource.validationQuery=SELECT 1 FROM DUAL
spring.datasource.testWhileIdle=true
spring.datasource.testOnBorrow=false
spring.datasource.testOnReturn=false
spring.datasource.poolPreparedStatements=true
spring.datasource.maxPoolPreparedStatementPerConnectionSize=20
spring.datasource.filters=stat,wall,log4j
spring.datasource.connectionProperties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
#spring.datasource.useGlobalDataSourceStat=true

spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://192.168.6.1:3306/test
spring.datasource.username=inf
spring.datasource.password=inf
spring.jpa.database=MYSQL

5.17.3.3. c3p0 - JDBC3 Connection and Statement Pooling

pom.xml

        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-c3p0</artifactId>
            <version>4.3.6.Final</version>
        </dependency>
        <dependency>
            <groupId>c3p0</groupId>
            <artifactId>c3p0</artifactId>
            <version>0.9.1.2</version>
        </dependency>

application.properties

spring.datasource.type=com.mchange.v2.c3p0.ComboPooledDataSource

5.17.3.4. dbcp2

spring.datasource.type = org.apache.commons.dbcp2.BasicDataSource

5.17.3.5. bonecp

spring.datasource.type = com.jolbox.bonecp.BoneCPDataSource

5.17.3.6. dbcp2

spring.datasource.type = org.apache.commons.dbcp2.BasicDataSource


5.18. Spring boot with Redis

5.18.1. Spring boot with Redis

5.18.1.1. maven

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

5.18.1.2. application.properties

spring.redis.database=10
spring.redis.host=localhost
spring.redis.port=6379
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

5.18.1.3. JUnit

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(Application.class)
public class ApplicationTests {
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Test
    public void test() throws Exception {
        // 保存字符串
        stringRedisTemplate.opsForValue().set("neo", "chen");
        Assert.assertEquals("chen", stringRedisTemplate.opsForValue().get("neo"));
    }
}

5.18.1.4. Controller

stringRedisTemplate模板用于存储key,value为字符串的数据

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @RequestMapping("/test")
    @ResponseBody
    public String test() {
        String message = "";
        stringRedisTemplate.opsForValue().set("hello", "world");
        message = stringRedisTemplate.opsForValue().get("hello");
        return message;
    }

等同于

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

ListOperations

public class Example {

    // inject the actual template
    @Autowired
    private RedisTemplate<String, String> template;

    // inject the template as ListOperations
    // can also inject as Value, Set, ZSet, and HashOperations
    @Resource(name="redisTemplate")
    private ListOperations<String, String> listOps;

    public void addLink(String userId, URL url) {
        listOps.leftPush(userId, url.toExternalForm());
        // or use template directly
        redisTemplate.boundListOps(userId).leftPush(url.toExternalForm());
    }
}

例 5.3. RedisTemplate

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    public List<Protocol> getProtocol() {
        List<Protocol> protocols = new ArrayList<Protocol>();
        Gson gson = new Gson();
        Type type = new TypeToken<List<Protocol>>(){}.getType();
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new StringRedisSerializer());

        String cacheKey = String.format("%s:%s", this.getClass().getName(), Thread.currentThread().getStackTrace()[1].getMethodName());
        long expireTime = 5;

        if(redisTemplate.hasKey(cacheKey)){
            String cacheValue = redisTemplate.opsForValue().get(cacheKey);
            System.out.println(cacheValue);
            protocols = gson.fromJson(cacheValue, type);
        }else{
            Protocol protocol = new Protocol();
            protocol.setRequest(new Date().toString());
            protocols.add(protocol);

            String jsonString = gson.toJson(protocols, type);
            System.out.println( jsonString );

            redisTemplate.opsForValue().set(cacheKey, jsonString);
            redisTemplate.expire(cacheKey, expireTime, TimeUnit.SECONDS);
        }
        return protocols;
    }

5.18.2. Redis Pub/Sub

5.18.2.1. Redis配置类

package cn.netkiller.wallet.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;

import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.listener.ChannelTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.listener.adapter.MessageListenerAdapter;

import cn.netkiller.wallet.redis.RedisMessageSubscriber;

@Configuration
public class RedisConfig {

    public RedisConfig() {
    }

    @Bean
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory connectionFactory) {
        StringRedisTemplate redisTemplate = new StringRedisTemplate();
        redisTemplate.setConnectionFactory(connectionFactory);
        return redisTemplate;
    }

    @Bean
    public MessageListenerAdapter messageListener() {
        return new MessageListenerAdapter(new RedisMessageSubscriber());
    }

    @Bean
    public ChannelTopic topic() {
        return new ChannelTopic("demo");
    }

    @Bean
    public RedisMessageListenerContainer redisContainer(RedisConnectionFactory connectionFactory, MessageListenerAdapter messageListener) {
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();

        container.setConnectionFactory(connectionFactory);
        container.addMessageListener(messageListener(), topic());
        container.addMessageListener(messageListener(), new ChannelTopic("test"));
        return container;
    }

}

5.18.2.2. 订阅和发布类

package cn.netkiller.wallet.redis;

import java.nio.charset.StandardCharsets;

import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;

public class RedisMessageSubscriber implements MessageListener {
    public void onMessage(final Message message, final byte[] pattern) {
        System.out.println("Topic : " + new String(message.getChannel(), StandardCharsets.UTF_8));
        System.out.println("Message : " + message.toString());
    }
}

package cn.netkiller.wallet.redis;

import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.listener.ChannelTopic;

public class RedisMessagePublisher {

    private final StringRedisTemplate redisTemplate;

    private final ChannelTopic topic;

    public RedisMessagePublisher(StringRedisTemplate redisTemplate, ChannelTopic topic) {
        this.redisTemplate = redisTemplate;
        this.topic = topic;
    }

    public void publish(String message) {
        redisTemplate.convertAndSend(topic.getTopic(), message);
    }
}

5.18.2.3. 消息发布演示

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @GetMapping("/pub/demo")
    public String pub() {

        RedisMessagePublisher publisher = new RedisMessagePublisher(stringRedisTemplate, new ChannelTopic("demo"));
        String message = "Message " + UUID.randomUUID();
        publisher.publish(message);
        return message;
    }

    @GetMapping("/pub/test")
    public String pub(@RequestParam String message) {

        RedisMessagePublisher publisher = new RedisMessagePublisher(stringRedisTemplate, new ChannelTopic("test"));
        publisher.publish(message);
        return message;
    }


5.19. Spring boot with Caching

5.19.1. maven

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

5.19.2. 启用 Cache

添加 @EnableCaching

package hello;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;

@SpringBootApplication
@EnableCaching
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}

5.19.3. 测试 Controller

缓存返回结果

    @Cacheable("cacheable")
    @RequestMapping("/test/cacheable")
    @ResponseBody
    public String cacheable() {
        Date date = new Date();
        String message = date.toString();
        return message;
    }

5秒钟清楚一次缓存

    @Scheduled(fixedDelay = 5000)
    @CacheEvict(allEntries = true, value = "cacheable")
    public void expire() {
        Date date = new Date();
        String message = date.toString();
        System.out.println(message);
    }

5.19.4. @Cacheable 的用法

@Cacheable(value="users", key="#id")
public User find(Integer id) {

   return null;

}

引用对象

@Cacheable(value="users", key="#user.id")
public User find(User user) {

   returnnull;

}

条件判断

@Cacheable(value="messagecache", key="#id", condition="id < 10")
public String getMessage(int id){

    return "hello"+id;

}

@Cacheable(value="test",condition="#userName.length()>2")
@Cacheable(value={"users"}, key="#user.id", condition="#user.id%2==0")

#p0 参数索引，p0表示第一个参数

@Cacheable(value="users", key="#p0")
public User find(Integer id) {

   return null;

}

@Cacheable(value="users", key="#p0.id")
public User find(User user) {

   return null;

}

@Cacheable 如果没有任何参数将会自动生成 key ，前提是必须设置 @CacheConfig(cacheNames = "test")

    @GetMapping("/cache/auto")
    @Cacheable()
    public Attribute auto() {
        Attribute attribute = new Attribute();
        attribute.setName("sdfsdf");
        return attribute;
    }

127.0.0.1:6379> keys *
1) "test::SimpleKey []"

5.19.5. @CachePut 用法

@CachePut 每次都会执行方法，都会将结果存入指定key的缓存中，@CachePut 不会判断是否 key 已经存在，二是始终覆盖。

@CachePut("users")
public User find(Integer id) {

   return null;

}

5.19.6. 解决Expire 和 TTL 过期时间

Springboot 1.x

    @Bean
    public CacheManager cacheManager(RedisTemplate redisTemplate) {
        RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate);
        cacheManager.setDefaultExpiration(60);	//缓存默认 60 秒
        Map<String, Long> expiresMap = new HashMap<>();
        expiresMap.put("Product", 5L);  //设置 key = Product 时 5秒缓存。你可以添加很多规则。
        cacheManager.setExpires(expiresMap);
        return cacheManager;
    }

Springboot 2.x

package api.config;

import java.time.Duration;
import java.util.HashMap;
import java.util.Map;

import org.springframework.cache.CacheManager;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;

@Configuration
public class CachingConfigurer {

    public CachingConfigurer() {
        // TODO Auto-generated constructor stub
    }

    @Bean
    public KeyGenerator simpleKeyGenerator() {
        return (o, method, objects) -> {
            StringBuilder stringBuilder = new StringBuilder();
            stringBuilder.append(o.getClass().getSimpleName());
            stringBuilder.append(".");
            stringBuilder.append(method.getName());
            stringBuilder.append("[");
            for (Object obj : objects) {
                stringBuilder.append(obj.toString());
            }
            stringBuilder.append("]");

            return stringBuilder.toString();
        };
    }

    @Bean
    public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
        return new RedisCacheManager(RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory),
        this.redisCacheConfiguration(600), 	// 默认配置
        this.initialCacheConfigurations());	// 指定key过期时间配置
    }

    private Map<String, RedisCacheConfiguration> initialCacheConfigurations() {
        Map<String, RedisCacheConfiguration> redisCacheConfigurationMap = new HashMap<>();
        redisCacheConfigurationMap.put("UserInfoList", this.redisCacheConfiguration(3000));
        redisCacheConfigurationMap.put("UserInfoListAnother", this.redisCacheConfiguration(18000));

        return redisCacheConfigurationMap;
    }

    private RedisCacheConfiguration redisCacheConfiguration(Integer seconds) {
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);

        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();
        redisCacheConfiguration = redisCacheConfiguration.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer)).entryTtl(Duration.ofSeconds(seconds));

        return redisCacheConfiguration;
    }

}

    @Cacheable(value = "DefaultKey", keyGenerator = "simpleKeyGenerator") // 600秒，使用默认策略
    @Cacheable(value = "UserInfoList", keyGenerator = "simpleKeyGenerator") // 3000秒
    @Cacheable(value = "UserInfoListAnother", keyGenerator = "simpleKeyGenerator") // 18000秒

127.0.0.1:6379> keys *
1) "test2::SimpleKey []"

127.0.0.1:6379> ttl "test2::SimpleKey []"
(integer) 584

5.19.7. SpEL表达式

    @GetMapping("/cache/expire")
    @Cacheable("test1#${select.cache.timeout:1000}")
    public String expire() {
        return "Test";
    }

    @GetMapping("/cache/expire")
    @Cacheable("test1#${select.cache.timeout:1000}#${select.cache.refresh:600}")
    public String expire() {
        return "Test";
    }


5.20. Spring boot with Email

5.20.1. Maven

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

例 5.4. Spring boot with Email (pom.xml)

<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>netkiller.cn</groupId>
    <artifactId>api.netkiller.cn</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>api.netkiller.cn</name>
    <url>http://maven.apache.org</url>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.8</java.version>
    </properties>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.2.RELEASE</version>
    </parent>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-mongodb</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-mongodb</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-oracle</artifactId>
            <version>1.0.0.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>com.oracle</groupId>
            <artifactId>ojdbc6</artifactId>
            <!-- <version>12.1.0.1</version> -->
            <version>11.2.0.3</version>
            <scope>system</scope>
            <systemPath>${basedir}/lib/ojdbc6.jar</systemPath>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

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

        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <sourceDirectory>src</sourceDirectory>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.3</version>
                <configuration>
                    <source />
                    <target />
                </configuration>
            </plugin>
            <plugin>
                <artifactId>maven-war-plugin</artifactId>
                <version>2.6</version>
                <configuration>
                    <warSourceDirectory>WebContent</warSourceDirectory>
                    <failOnMissingWebXml>false</failOnMissingWebXml>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

5.20.2. Resource

application.properties

Postfix / Exam4 / Sendmail 邮件服务器配置

spring.mail.host=smtp.163.com

SMTP 配置

spring.mail.host=smtp.163.com
spring.mail.username=openunix@163.com
spring.mail.password=your_password
spring.mail.properties.mail.smtp.auth=true
#spring.mail.properties.mail.smtp.starttls.enable=true
#spring.mail.properties.mail.smtp.starttls.required=true

5.20.3. POJO

package api.pojo;

public class Email {
    public String from;
    public String to;
    public String subject;
    public String text;
    public boolean status;

    public String getFrom() {
        return from;
    }
    public void setFrom(String from) {
        this.from = from;
    }
    public String getTo() {
        return to;
    }
    public void setTo(String to) {
        this.to = to;
    }
    public String getSubject() {
        return subject;
    }
    public void setSubject(String subject) {
        this.subject = subject;
    }
    public String getText() {
        return text;
    }
    public void setText(String text) {
        this.text = text;
    }

    public boolean isStatus() {
        return status;
    }
    public void setStatus(boolean status) {
        this.status = status;
    }

    @Override
    public String toString() {
        return "Email [from=" + from + ", to=" + to + ", subject=" + subject + ", text=" + text + "]";
    }
    public Email() {

    }
    public Email(String from, String to, String subject, String text) {
        super();
        this.from = from;
        this.to = to;
        this.subject = subject;
        this.text = text;
    }

}

5.20.4. RestController

package api.rest;

import java.io.File;

import javax.mail.internet.MimeMessage;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.FileSystemResource;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

import api.pojo.Email;

@RestController
@RequestMapping("/v1/email")
public class EmailRestController extends CommonRestController {

    @Autowired
    private JavaMailSender javaMailSender;

    @RequestMapping("version")
    @ResponseStatus(HttpStatus.OK)
    public String version() {
        return "[OK] Welcome to withdraw Restful version 1.0";
    }

    @RequestMapping(value = "send", method = RequestMethod.POST, produces = { "application/xml", "application/json" })
    public ResponseEntity<Email> sendSimpleMail(@RequestBody Email email) {
        SimpleMailMessage message = new SimpleMailMessage();
        message.setFrom(email.getFrom());
        message.setTo(email.getTo());
        message.setSubject(email.getSubject());
        message.setText(email.getText());
        javaMailSender.send(message);
        email.setStatus(true);

        return new ResponseEntity<Email>(email, HttpStatus.OK);
    }

    @RequestMapping(value = "attachments", method = RequestMethod.POST, produces = { "application/xml", "application/json" })
    public ResponseEntity<Email> attachments(@RequestBody Email email) throws Exception {

        MimeMessage mimeMessage = javaMailSender.createMimeMessage();

        MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage, true);
        mimeMessageHelper.setFrom(email.getFrom());
        mimeMessageHelper.setTo(email.getTo());
        mimeMessageHelper.setSubject(email.getSubject());
        mimeMessageHelper.setText("<html><body><img src=\"cid:banner\" >" + email.getText() + "</body></html>", true);

        FileSystemResource file = new FileSystemResource(new File("banner.jpg"));
        mimeMessageHelper.addInline("banner", file);

        FileSystemResource fileSystemResource = new FileSystemResource(new File("Attachment.jpg"));
        mimeMessageHelper.addAttachment("Attachment.jpg", fileSystemResource);

        javaMailSender.send(mimeMessage);
        email.setStatus(true);

        return new ResponseEntity<Email>(email, HttpStatus.OK);
    }

    // 如果你不想使用 application.properties 中的 spring.mail.host 配置，想自行配置SMTP主机可以参考下面例子
    @RequestMapping(value = "sendmail", method = RequestMethod.POST, produces = { "application/xml", "application/json" })
    public ResponseEntity<Email> sendmail(@RequestBody Email email) {
        JavaMailSenderImpl javaMailSender = new JavaMailSenderImpl();
        javaMailSender.setHost(email.getHost());
        SimpleMailMessage message = new SimpleMailMessage();
        message.setFrom(email.getFrom());
        message.setTo(email.getTo());
        message.setSubject(email.getSubject());
        message.setText(email.getText());
        try{
            javaMailSender.send(message);
            email.setStatus(true);
        }catch(Exception e){
            email.setText(e.getMessage());
            email.setStatus(false);
        }

        return new ResponseEntity<Email>(email, HttpStatus.OK);
    }
}

5.20.5. Test

                $ curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X POST -d '{"from":"root@netkiller.cn", "to":"21214094@qq.com","subject":"Hello","text":"Hello world!!!"}' http://172.16.0.20:8080/v1/email/send.json
                HTTP/1.1 200 OK
                Server: Apache-Coyote/1.1
                Content-Type: application/json;charset=UTF-8
                Transfer-Encoding: chunked
                Date: Wed, 10 Aug 2016 06:38:00 GMT

                {"from":"root@netkiller.cn","to":"21214094@qq.com","subject":"Hello","text":"Hello world!!!","status":true}


5.21. Spring boot with Spring security

5.21.1. Maven

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

<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>netkiller.cn</groupId>
    <artifactId>api.netkiller.cn</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>api.netkiller.cn</name>
    <url>http://maven.apache.org</url>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.8</java.version>
    </properties>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.2.RELEASE</version>
    </parent>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-mongodb</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-mongodb</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-oracle</artifactId>
            <version>1.0.0.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>com.oracle</groupId>
            <artifactId>ojdbc6</artifactId>
            <!-- <version>12.1.0.1</version> -->
            <version>11.2.0.3</version>
            <scope>system</scope>
            <systemPath>${basedir}/lib/ojdbc6.jar</systemPath>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-mail</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-velocity</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.velocity</groupId>
            <artifactId>velocity</artifactId>
        </dependency>
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <sourceDirectory>src</sourceDirectory>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.3</version>
                <configuration>
                    <source />
                    <target />
                </configuration>
            </plugin>
            <plugin>
                <artifactId>maven-war-plugin</artifactId>
                <version>2.6</version>
                <configuration>
                    <warSourceDirectory>WebContent</warSourceDirectory>
                    <failOnMissingWebXml>false</failOnMissingWebXml>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

5.21.2. Reource

src/main/resources/application.properties

添加默认用户，角色user,用户名neo,密码password

security.user.name=neo
security.user.password=password
security.user.role=USER

现在启动Application，然后尝试访问url，这时会弹出对话框，提示用户用户输入用户名与密码。使用上面的密码便可登陆。

5.21.3. Application

package api;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

@SpringBootApplication
@EnableAutoConfiguration
@ComponentScan({ "api.config", "api.web", "api.rest", "api.service" })
@EnableMongoRepositories
@EnableJpaRepositories
public class Application {

    public @Bean WebMvcConfigurer corsConfigurer() {
        return new WebMvcConfigurerAdapter() {
            @Override
            public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping("/**");
            }
        };
    }

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}

5.21.4. WebSecurityConfigurer

注意WebSecurityConfigurer必须在 ComponentScan 的扫描范围

package api.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
@EnableWebSecurity
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication().
        withUser("user1").password("secret1").roles("USER")
        .and().
        withUser("user2").password("secret2").roles("USER")
        .and().
        withUser("admin").password("secret").roles("ADMIN");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().anyRequest().fullyAuthenticated();
        http.httpBasic();
        http.csrf().disable();
    }

}

5.21.5. RestController

@RestController
@RequestMapping("/service")
public class UserService {
    @RequestMapping(value = "/echo/{in}", method = RequestMethod.GET)
    public String echo(@PathVariable(value = "in") final String in, @AuthenticationPrincipal final UserDetails user) {
        return "Hello " + user.getUsername() + ", you said: " + in;
    }
}

5.21.6. 测试

curl -u user:password http://172.16.0.20:8080/index.html
curl http://user:password@172.16.0.20:8080/index.html

5.21.7. Spring + Security + MongoDB

MongoDB 为 Security 用户认证提供数据存储。

5.21.7.1. Account

package mis.domain;

import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.index.Indexed;

public class Administrator {
    @Id
    private String id;

    @Indexed(unique = true)
    private String username;
    private String password;
    private String authority;

    public Administrator() {
        // TODO Auto-generated constructor stub
    }

    public Administrator(String username, String password) {
        this.username = username;
        this.password = password;
      }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getAuthority() {
        return authority;
    }

    public void setAuthority(String authority) {
        this.authority = authority;
    }

    @Override
    public String toString() {
        return "User [id=" + id + ", username=" + username + ", password=" + password + ", authority=" + authority + "]";
    }
}

5.21.7.2. AccountRepository

package mis.repository;

import org.springframework.data.mongodb.repository.MongoRepository;

import mis.domain.Administrator;

public interface AdministratorRepository extends MongoRepository<Administrator, String> {

    public Administrator findByUsername(String username);

}

5.21.7.3. WebSecurityConfiguration

package mis.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.authentication.configurers.GlobalAuthenticationConfigurerAdapter;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

import mis.domain.Administrator;
import mis.repository.AdministratorRepository;

@Configuration
class GlobalAuthenticationConfigurer extends GlobalAuthenticationConfigurerAdapter {

    @Autowired
    AdministratorRepository administratorRepository;

    @Override
    public void init(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService());
    }

    @Bean
    UserDetailsService userDetailsService() {
        return new UserDetailsService() {

            @Override
            public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
                Administrator administrator = administratorRepository.findByUsername(username);
                if (administrator != null) {
                    return new User(administrator.getUsername(), administrator.getPassword(), AuthorityUtils.createAuthorityList(administrator.getAuthority()));
                } else {
                    throw new UsernameNotFoundException("could not find the administrator '" + username + "'");
                }
            }

        };
    }
}

@Configuration
@EnableWebSecurity
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {

    public WebSecurityConfigurer() {
        // TODO Auto-generated constructor stub
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // http.authorizeRequests().anyRequest().fullyAuthenticated().and().httpBasic().and().csrf().disable();

        // http.authorizeRequests().antMatchers("/", "/index.html", "/css/**",
        // "/js/**","/static/**","/setup.html").permitAll().anyRequest().authenticated().and().formLogin().loginPage("/login.html").permitAll().and().logout().permitAll().and().httpBasic();
        // http.authorizeRequests().antMatchers("/**"
        // ).permitAll().and().httpBasic();

        http.authorizeRequests().antMatchers("/ping", "/v1/*/ping", "/v1/public/**").permitAll().anyRequest().authenticated().and().rememberMe().and().httpBasic().and().csrf().disable();

    }
}


5.22. Spring boot with Oauth2

下面例子由三个项目组成，分别是 tools, server, client。

其中 tools 是密码生成工具

5.22.1. Maven

<?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>cn.netkiller</groupId>
    <artifactId>oauth</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>pom</packaging>

    <name>oauth</name>
    <url>http://maven.apache.org</url>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.2.RELEASE</version>
        <relativePath /> <!-- lookup parent from repository -->
    </parent>
    <dependencies>

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

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <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>

        <!-- Security -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.security.oauth</groupId>
            <artifactId>spring-security-oauth2</artifactId>
        </dependency>

    </dependencies>
    <modules>
        <module>server</module>
        <module>client</module>
    </modules>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

5.22.2. Password tools

Maven

<?xml version="1.0"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>cn.netkiller</groupId>
    <artifactId>oauth</artifactId>
    <version>0.0.1-SNAPSHOT</version>
  </parent>
  <artifactId>tools</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <name>tools</name>
  <url>http://maven.apache.org</url>
  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>
  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
</project>

下面的代码用于生成 Spring security 所用的密码

package cn.netkiller.oauth.tools;

import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

/**
 * Hello world!
 *
 */
public class App {
    public static void main(String[] args) {
        int i = 0;
        while (i < 10) {
            String password = "123456";
            BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
            String hashedPassword = passwordEncoder.encode(password);

            System.out.println(hashedPassword);
            i++;
        }
    }
}

运行后生成类似的密码

$2a$10$IrDG5Yr3CGorEg9gKG8smeRLnNUieCVyBCJHA80U6h20HDFCxd43W
$2a$10$wseh5xFv1L3a3uHQId0MAOqAN0rKKcuX.RDaBPQ.pV/hsr80TqwxO
$2a$10$xP3Gc/5/PN03BdkDfhUjAemTRVaiwr0lsaqPqD18UI.ho9nRC/ebW
$2a$10$S.wLZ6e5YvmQA6mkX8yXWOdJbvahtDOesRu0ZwPOzAPCwpo7eDAsi
$2a$10$Jo/yuWyiAZ2Lj8.ywoPl7OeOJYuP7RVq8l.qc/zOwtW8MTFp3NYGO
$2a$10$eEvvjPok0fRK.DU6yF0qI.aucuiWr3y5G93SLq9/76ovcOwIuQAuS
$2a$10$BWEkANxbgwATNQCEI9/uNevNEUNlomGY7cZ2CQVm.qCRcnyukT.Si
$2a$10$69wSpyJQvjzJY7ou5PFWlOlEIecQukHV9WEq0nebsz5V6IZKfOVv2
$2a$10$Cyj3hM39V34r5pMeQ.Y9peuUqYMBSvsJ7GTBgp4.stWaTtWMboYGS
$2a$10$0/o4cRN2.tmnc58sH.N4WOsreVI6sWlPl4CCBrmUfJ332TMfRzA42

5.22.3. Server

5.22.3.1. Maven

<?xml version="1.0"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>cn.netkiller</groupId>
        <artifactId>oauth</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
    <groupId>cn.netkiller</groupId>
    <artifactId>server</artifactId>
    <name>server</name>
    <description>Example Spring Boot REST project</description>

    <url>http://maven.apache.org</url>
    <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>

    </dependencies>

</project>

5.22.3.2. application.properties

server.port=8000
server.contextPath=/api

logging.level.com.gigy=DEBUG

# Data source properties
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/netkiller?useSSL=false
spring.datasource.username=netkiller
spring.datasource.password=123123
spring.datasource.max-idle=10
spring.datasource.max-wait=10000
spring.datasource.min-idle=5
spring.datasource.initial-size=5
spring.datasource.validation-query=SELECT 1
spring.datasource.test-on-borrow=false
spring.datasource.test-while-idle=true
spring.datasource.time-between-eviction-runs-millis=18800
spring.datasource.jdbc-interceptors=ConnectionState;SlowQueryReport(threshold=0)

spring.jpa.database=MYSQL
spring.jpa.show-sql=true
#spring.jpa.hibernate.ddl-auto=update
spring.jpa.hibernate.ddl-auto=create-drop
#spring.jpa.hibernate.ddl-auto=validate
#spring.jpa.show-sql=true

5.22.3.3. EnableAuthorizationServer

package cn.netkiller.oauth.server.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfigurer extends AuthorizationServerConfigurerAdapter {

    @Autowired
    @Qualifier("userDetailsService")
    private UserDetailsService userDetailsService;

    @Autowired
    private AuthenticationManager authenticationManager;

    @Value("${oauth.tokenTimeout:3600}")
    private int expiration;

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer configurer) throws Exception {
        configurer.authenticationManager(authenticationManager);
        configurer.userDetailsService(userDetailsService);
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory().withClient("api").secret("secret").accessTokenValiditySeconds(expiration).scopes("read", "write").authorizedGrantTypes("password", "refresh_token").resourceIds("resource");
    }

}

Spring boot 2.0

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory().withClient("api").secret(passwordEncoder().encode("secret")).accessTokenValiditySeconds(expiration).scopes("read", "write").authorizedGrantTypes("password", "refresh_token").resourceIds("resource");
    }

5.22.3.4. EnableResourceServer

package cn.netkiller.oauth.server.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;

@Configuration
@EnableWebSecurity
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {

    /**
     * Constructor disables the default security settings
     */
    public WebSecurityConfiguration() {
        super(true);
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/login");
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.antMatcher("/api/**").authorizeRequests().anyRequest().authenticated();
    }

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
}

5.22.3.5. Entity Table

package cn.netkiller.oauth.server.model;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

@Entity
@Table(name = "users")
public class User implements UserDetails {

    static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "user_id", nullable = false, updatable = false)
    private Long id;

    @Column(name = "username", nullable = false, unique = true)
    private String username;

    @Column(name = "password", nullable = false)
    private String password;

    @Column(name = "enabled", nullable = false)
    private boolean enabled;

    public Collection<? extends GrantedAuthority> getAuthorities() {
        List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();

        return authorities;
    }

    public boolean isAccountNonExpired() {
        return true;
    }

    public boolean isAccountNonLocked() {
        // we never lock accounts
        return true;
    }

    public boolean isCredentialsNonExpired() {
        // credentials never expire
        return true;
    }

    public boolean isEnabled() {
        return enabled;
    }

    public String getPassword() {
        return password;
    }

    public String getUsername() {
        return username;
    }

}

5.22.3.6. UserRepository

package cn.netkiller.oauth.server.repository;

import org.springframework.data.jpa.repository.JpaRepository;

import cn.netkiller.oauth.server.model.User;

public interface UserRepository extends JpaRepository<User, Long> {

    /**
     * Find a user by username
     *
     * @param username
     *            the user's username
     * @return user which contains the user with the given username or null.
     */
    User findOneByUsername(String username);

}

5.22.3.7. UserService

package cn.netkiller.oauth.server.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import cn.netkiller.oauth.server.repository.UserRepository;

@Service("userDetailsService")
public class UserService implements UserDetailsService {

    @Autowired
    private UserRepository userRepository;

    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        return userRepository.findOneByUsername(username);
    }
}

5.22.3.8. TestRestController

package cn.netkiller.oauth.server.controller;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/test")
public class TestRestController {

    @RequestMapping(value = "hello", method = RequestMethod.GET)
    public ResponseEntity<String> hello() {
        return new ResponseEntity<String>("Hello world !", HttpStatus.OK);
    }

    @PreAuthorize("#oauth2.hasScope('write')")
    @RequestMapping(value = "set/{string}", method = RequestMethod.GET)
    public ResponseEntity<String> set(String string) {
        return new ResponseEntity<String>(string, HttpStatus.OK);
    }
}

5.22.3.9. 数据库初始化

在 src/main/resources 目录创建 data.sql 文件

INSERT INTO users (user_id, username, password, enabled) VALUES
    ('1', 'netkiller@msn.com', '$2a$10$Cyj3hM39V34r5pMeQ.Y9peuUqYMBSvsJ7GTBgp4.stWaTtWMboYGS', true);

此处密码 $2a$10$Cyj3hM39V34r5pMeQ.Y9peuUqYMBSvsJ7GTBgp4.stWaTtWMboYGS 请使用上面提供的工具生成。

5.22.3.10. Test

启动 Spring boot Server 项目

mvn spring-boot:run

启动后 Spring boot 会导入 data.sql 文件

mysql> select * from users where username="netkiller@msn.com";
+---------+---------+--------------------------------------------------------------+-------------------+
| user_id | enabled | password                                                     | username          |
+---------+---------+--------------------------------------------------------------+-------------------+
|       4 |        | $2a$10$Cyj3hM39V34r5pMeQ.Y9peuUqYMBSvsJ7GTBgp4.stWaTtWMboYGS | netkiller@msn.com |
+---------+---------+--------------------------------------------------------------+-------------------+
1 row in set (0.00 sec)

MacBook-Pro:Application neo$ curl -X POST --user 'api:secret' -d 'grant_type=password&username=netkiller@msn.com&password=123456' http://localhost:8000/api/oauth/token
{"access_token":"5bc0ee89-cd6d-47be-b31f-89c9e028159b","token_type":"bearer","refresh_token":"5107c09b-de85-4faf-8396-941572cf30d2","expires_in":3599,"scope":"read write"}MacBook-Pro:Application neo$

MacBook-Pro:Application neo$ curl -H "Accept: application/json" -H "Content-Type: application/json" -H "Authorization: Bearer 5bc0ee89-cd6d-47be-b31f-89c9e028159b" -X GET http://localhost:8000/api/test/hello
Hello world !

5.22.4. Spring boot with Oauth2 RestTemplate

5.22.4.1. Maven

        <dependency>
            <groupId>org.springframework.security.oauth</groupId>
            <artifactId>spring-security-oauth2</artifactId>
        </dependency>

5.22.4.2. OAuth2ClientConfiguration.java

package cn.netkiller.config;

import static java.util.Arrays.asList;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.client.DefaultOAuth2ClientContext;
import org.springframework.security.oauth2.client.OAuth2RestOperations;
import org.springframework.security.oauth2.client.OAuth2RestTemplate;
import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails;
import org.springframework.security.oauth2.client.token.AccessTokenRequest;
import org.springframework.security.oauth2.client.token.DefaultAccessTokenRequest;
import org.springframework.security.oauth2.client.token.grant.password.ResourceOwnerPasswordResourceDetails;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableOAuth2Client;

@EnableOAuth2Client
@Configuration
public class OAuth2ClientConfiguration {

    @Value("${oauth.resource:http://localhost:8080}")
    private String baseUrl;
    @Value("${oauth.authorize:http://localhost:8080/oauth/authorize}")
    private String authorizeUrl;
    @Value("${oauth.token:http://localhost:8080/oauth/token}")
    private String tokenUrl;

    @Bean
    protected OAuth2ProtectedResourceDetails resource() {

        ResourceOwnerPasswordResourceDetails resource = new ResourceOwnerPasswordResourceDetails();

        resource.setAccessTokenUri(tokenUrl);
        resource.setClientId("api");
        resource.setClientSecret("secret");
        resource.setGrantType("password");
        resource.setScope(asList("read", "write"));

        resource.setUsername("netkiller@msn.com");
        resource.setPassword("123456");

        return resource;
    }

    @Bean
    public OAuth2RestOperations restTemplate() {
        AccessTokenRequest accessTokenRequest = new DefaultAccessTokenRequest();
        return new OAuth2RestTemplate(resource(), new DefaultOAuth2ClientContext(accessTokenRequest));
    }

}

5.22.4.3. Application.java

package cn.netkiller;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {

    public static void main(String[] args) {

        System.out.println("Spring boot with Oauth2RestTemplate!");
        SpringApplication.run(Application.class, args);
    }
}

5.22.4.4. application.properties

security.basic.enabled=false

5.22.4.5. Controller

package cn.netkiller.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.oauth2.client.OAuth2RestOperations;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class TestController {
    @Autowired
    private OAuth2RestOperations restTemplate;

    @GetMapping("/")
    @ResponseBody
    public String index() {
        OAuth2AccessToken token = restTemplate.getAccessToken();
        System.out.println(token.getValue());
        String tmp = restTemplate.getForObject("http://api.netkiller.cn/test.json", String.class);
        System.out.println(tmp);
        return tmp;
    }
}

5.22.4.6. Test

neo@MacBook-Pro ~/deployment % curl http://localhost:8080

OK


5.23. Spring boot with Oauth2 jwt

5.23.1. Maven

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

        <dependency>
            <groupId>org.springframework.security.oauth</groupId>
            <artifactId>spring-security-oauth2</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-jwt</artifactId>
        </dependency>

如果是 Springboot 2.0.4 需要制定版本号

        <dependency>
            <groupId>org.springframework.security.oauth</groupId>
            <artifactId>spring-security-oauth2</artifactId>
            <version>2.3.3.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-jwt</artifactId>
            <version>1.0.9.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>javax.xml.bind</groupId>
            <artifactId>jaxb-api</artifactId>
        </dependency>
        <dependency>
            <groupId>com.sun.xml.bind</groupId>
            <artifactId>jaxb-impl</artifactId>
            <version>2.3.0</version>
        </dependency>
        <dependency>
            <groupId>com.sun.xml.bind</groupId>
            <artifactId>jaxb-core</artifactId>
            <version>2.3.0</version>
        </dependency>
        <dependency>
            <groupId>com.sun.activation</groupId>
            <artifactId>javax.activation</artifactId>
            <version>1.2.0</version>
        </dependency>

5.23.2. Authorization Server

package api.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfigurer extends AuthorizationServerConfigurerAdapter {

    @Autowired
    @Qualifier("userDetailsService")
    private UserDetailsService userDetailsService;

    @Autowired
    private AuthenticationManager authenticationManager;

    @Value("${oauth.tokenTimeout:3600}")
    private int expiration;

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    // @Override
    // public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
    // oauthServer.tokenKeyAccess("isAnonymous() || hasAuthority('ROLE_TRUSTED_CLIENT')");
    // oauthServer.checkTokenAccess("hasAuthority('ROLE_TRUSTED_CLIENT')");
    // }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory().withClient("api").secret(passwordEncoder().encode("secret")).accessTokenValiditySeconds(expiration).refreshTokenValiditySeconds(expiration).scopes("read", "write").authorizedGrantTypes("password", "refresh_token").authorities("USER").resourceIds("blockchain");
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer configurer) throws Exception {
        configurer.tokenStore(tokenStore()).accessTokenConverter(accessTokenConverter());
        configurer.authenticationManager(authenticationManager);
        configurer.userDetailsService(userDetailsService);
    }

    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(accessTokenConverter());
    }

    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setSigningKey("123");
        return converter;
    }

    @Bean
    @Primary
    public DefaultTokenServices tokenServices() {
        DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
        defaultTokenServices.setTokenStore(tokenStore());
        defaultTokenServices.setSupportRefreshToken(true);
        return defaultTokenServices;
    }

}

5.23.3. Resource Server

package api.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;

@Configuration
@EnableResourceServer
public class ResourceServerConfigurer extends ResourceServerConfigurerAdapter {
    public ResourceServerConfigurer() {
        // TODO Auto-generated constructor stub
    }

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) {
        resources.resourceId("blockchain");
        resources.tokenServices(tokenServices());

    }

    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(accessTokenConverter());
    }

    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setSigningKey("123");
        return converter;
    }

    @Bean
    @Primary
    public DefaultTokenServices tokenServices() {
        DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
        defaultTokenServices.setTokenStore(tokenStore());
        return defaultTokenServices;
    }

}

5.23.4. Web Security

package api.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/login");
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        // http.antMatcher("/**").authorizeRequests().anyRequest().authenticated();
        http.authorizeRequests().antMatchers(HttpMethod.OPTIONS).permitAll().anyRequest().authenticated().and().httpBasic().and().csrf().disable();
    }

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

}

5.23.5. 插入数据

INSERT INTO `user` (username, password, enabled) VALUES ('netkiller@msn.com', '$2a$10$Cyj3hM39V34r5pMeQ.Y9peuUqYMBSvsJ7GTBgp4.stWaTtWMboYGS', true);

5.23.6. 使用 CURL 测试 JWT

neo@MacBook-Pro ~ % curl -X POST --user 'api:secret' -d 'grant_type=password&username=netkiller@msn.com&password=123456' http://localhost:8080/oauth/token
{"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsicmVzb3VyY2UiXSwiZXhwIjoxNTM1MTE4MzYxLCJ1c2VyX25hbWUiOiJuZXRraWxsZXJAbXNuLmNvbSIsImp0aSI6ImM5YzA4ODczLWZlMTctNGM2My05MDA1LTIzZGJlNTE1NTQ0YiIsImNsaWVudF9pZCI6ImFwaSIsInNjb3BlIjpbInJlYWQiLCJ3cml0ZSJdfQ.ydxJQKtdBNWHELL8_aXVoZETNMygXs8T1HQ5XWDBELA","token_type":"bearer","refresh_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsicmVzb3VyY2UiXSwidXNlcl9uYW1lIjoibmV0a2lsbGVyQG1zbi5jb20iLCJzY29wZSI6WyJyZWFkIiwid3JpdGUiXSwiYXRpIjoiYzljMDg4NzMtZmUxNy00YzYzLTkwMDUtMjNkYmU1MTU1NDRiIiwiZXhwIjoxNTM1MTE4MzYxLCJqdGkiOiI3ODJiNmY2NC0xYzdiLTQ0YWYtOThlZC1iZDk3Y2QzYzE1NjEiLCJjbGllbnRfaWQiOiJhcGkifQ.16QUHOrVPUBF97E_972AcLA83zEK7UzaI424PeAmX6E","expires_in":3599,"scope":"read write","jti":"c9c08873-fe17-4c63-9005-23dbe515544b"}

复制 access_token 粘贴到 Authorization: Bearer 后面

neo@MacBook-Pro ~ % curl -H "Accept: application/json" -H "Content-Type: application/json" -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiYmxvY2tjaGFpbiJdLCJleHAiOjE1MzUxMTkyMTUsInVzZXJfbmFtZSI6Im5ldGtpbGxlckBtc24uY29tIiwianRpIjoiZTNjYmIxNTItYmFkZS00MGM1LTkxOGItZjJjMzZiYTBiMTE2IiwiY2xpZW50X2lkIjoiYXBpIiwic2NvcGUiOlsicmVhZCIsIndyaXRlIl19.ophWAvIT1ZmWCIQyAgyuzmQ98TJsN49OeR3CBSJ_X54" -X GET http://localhost:8080/test/mongo
{"id":"5b800709e18a211e83c7f355","name":"Netkiller"}

5.23.7. 测试 Shell

URL=http://localhost:8080
TOKEN=$(curl -s  -X POST --user 'api:secret' -d 'grant_type=password&username=netkiller@msn.com&password=123456' ${URL}/oauth/token | sed 's/.*"access_token":"\([^"]*\)".*/\1/g')

curl -s -k -H "Accept: application/json" -H "Content-Type: application/json" -H "Authorization: Bearer ${TOKEN}" -X GET ${URL}/test/hello.json

5.23.8. refresh_token

curl -s  -X POST --user 'api:secret' -d 'grant_type=refresh_token&client_id=api&client_secret=secret&refresh_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiYmxvY2tjaGFpbiJdLCJ1c2VyX25hbWUiOiJibG9ja2NoYWluIiwic2NvcGUiOlsicmVhZCIsIndyaXRlIl0sImF0aSI6ImU2OGE4ODUzLWJlODUtNGUwNy1hYzE0LTM0MDI1ZDBiZGY0ZiIsImV4cCI6MTU0MDM4MzA5NCwianRpIjoiY2JjNTk2ZWMtOTkyZi00NmQ2LWFiZGYtMTcyYWFiZmEwNDFiIiwiY2xpZW50X2lkIjoiYXBpIn0.PqsGl5Dm8WBqwbhHf-LqmUP1toCpsFS4gLeOP2bMwR4' ${URL}/oauth/token


5.24. Spring boot with Oauth2 jwt 非对称证书

5.24.1. 创建证书

创建证书 keytool -genkeypair -alias jwt -keyalg RSA -keypass passw0rd -keystore jwt.jks -storepass passw0rd

neo@MacBook-Pro /tmp/oauth % keytool -genkeypair -alias jwt -keyalg RSA -keypass passw0rd -keystore jwt.jks -storepass passw0rd
What is your first and last name?
  [Unknown]:  Neo Chen
What is the name of your organizational unit?
  [Unknown]:  netkiller.cn
What is the name of your organization?
  [Unknown]:  netkiller.cn
What is the name of your City or Locality?
  [Unknown]:  Shenzhen
What is the name of your State or Province?
  [Unknown]:  Guangdong
What is the two-letter country code for this unit?
  [Unknown]:  CN
Is CN=Neo Chen, OU=netkiller.cn, O=netkiller.cn, L=Shenzhen, ST=Guangdong, C=CN correct?
  [no]:  yes

该命令将生成一个名为jwt.jks的文件，其中包含我们的密钥 - 公钥和私钥。 还要牢记keypass和storepass密码。

导出公钥，接下来，我们需要从刚刚生成的JKS中导出我们的公钥，我们可以使用下面的命令来实现：

neo@MacBook-Pro /tmp/oauth % keytool -list -rfc --keystore jwt.jks | openssl x509 -inform pem -pubkey -out certificate.crt > public.crt

Enter keystore password:  passw0rd

公钥内容

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAj6ePdDwBrHKX3kNFnbve
T1rTTbyA9GjaiZNwj2X4Y0In7RCFl8auXXBn2DxztQMGqHY2Ydc3/26Gu9Vri441
r8/RInA6UpzzDRl5SeYYTobcgfIVpfQ0hTX0xzuMDVLVoLibGfcvGy7ZkrJjQFX8
lIaO84K8KP/yzma5622XJ+f5hkXmTX5e0tXGDCPjVO1dSrouPWqhcbM0Kf6y3RdE
JkNRTHLky6afx8MNobakz1Ab9K7cjD8De6LwScwMQMFU46traN/3Fw0lZFxKkpay
+sEUHvHDUYWTuVovUmfiKMX8fj5QCm4imPdA3pF/jjM+xeeVcTID3qffDGOKrGTF
HQIDAQAB
-----END PUBLIC KEY-----

复制 jwt.jks 和 public.crt 到 src/main/resources 目录下

5.24.2. Authorization Server

    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource("jwt.jks"), "passw0rd".toCharArray());
        converter.setKeyPair(keyStoreKeyFactory.getKeyPair("passw0rd"));
        return converter;
    }

5.24.3. Resource Server

    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();

        String publicKey = "-----BEGIN PUBLIC KEY-----\n" +
                "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAj6ePdDwBrHKX3kNFnbve\n" +
                "T1rTTbyA9GjaiZNwj2X4Y0In7RCFl8auXXBn2DxztQMGqHY2Ydc3/26Gu9Vri441\n" +
                "r8/RInA6UpzzDRl5SeYYTobcgfIVpfQ0hTX0xzuMDVLVoLibGfcvGy7ZkrJjQFX8\n" +
                "lIaO84K8KP/yzma5622XJ+f5hkXmTX5e0tXGDCPjVO1dSrouPWqhcbM0Kf6y3RdE\n" +
                "JkNRTHLky6afx8MNobakz1Ab9K7cjD8De6LwScwMQMFU46traN/3Fw0lZFxKkpay\n" +
                "+sEUHvHDUYWTuVovUmfiKMX8fj5QCm4imPdA3pF/jjM+xeeVcTID3qffDGOKrGTF\n" +
                "HQIDAQAB\n" +
                "-----END PUBLIC KEY-----";

        converter.setVerifierKey(publicKey);
        return converter;
    }


5.25. Apple iOS 访问 Oauth2

//
//  AppDelegate.m
//
//  Created by Apple on 2018/9/8.
//  Copyright © 2018年 Apple. All rights reserved.
//

#import "AppDelegate.h"
#import "YTKNetworkConfig.h"
#import "AFOAuth2Manager.h"
#import <AFNetworking.h>
#import "NSAppConfig.h"

@interface AppDelegate ()
@property (nonatomic, strong) AFOAuthCredential *credential;
@property (nonatomic, strong) AFOAuth2Manager *OAuth2Manager;
@end

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

    //授权
    NSURL *url = [NSURL URLWithString:BASE_URL];

    AFOAuth2Manager *OAuth2Manager = [[AFOAuth2Manager alloc]initWithBaseURL:url clientID:CLIENTID secret:SECRET];
   self.OAuth2Manager = OAuth2Manager;
    [OAuth2Manager authenticateUsingOAuthWithURLString:OAUTH_TOKEN
                                              username:OAUTH_USERNAME
                                           password:OAUTH_PASSWORD
                                                 scope:@""
    success:^(AFOAuthCredential *credential) {
      // NSLog(@"********请求OauthToek成功***********");
        self.credential = credential;
        [self testPost];
        [self testGet];
        }
    failure:^(NSError *error) {
     //  NSLog(@"********请求OauthToekn失败begin***********\r\n%@\r\n*********请求OauthToekn失败end************",error);

    }];

//    YTKNetworkConfig *config = [YTKNetworkConfig sharedConfig]; config.baseUrl = @"http://yuantiku.com";
//
    // Override point for customization after application launch.
    return YES;
}

- (void)testGet{
    NSURL *baseURL = [NSURL URLWithString:@"http://192.168.0.185:8080"];
    AFHTTPSessionManager *manager =
    [[AFHTTPSessionManager alloc] initWithBaseURL:baseURL];

    [manager.requestSerializer setAuthorizationHeaderFieldWithCredential:self.credential];

    [manager GET:@"/test/hello"
      parameters:nil
        progress:nil
         success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
             NSLog(@"Success: %@", responseObject);
         }
         failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
             NSLog(@"Failure: %@", error);
         }];

}

- (void)testPost{
    NSURL *baseURL = [NSURL URLWithString:@"http://192.168.0.185:8080"];
    AFHTTPSessionManager *manager =
    [[AFHTTPSessionManager alloc] initWithBaseURL:baseURL];
    manager.responseSerializer = [AFJSONResponseSerializer serializer];
    manager.requestSerializer = [AFJSONRequestSerializer serializer];
    [manager.requestSerializer setAuthorizationHeaderFieldWithCredential:self.credential];

    NSDictionary *dic = @{@"mobile":@"13113676543",
                          @"password":@"123456",
                          @"role":@"Organization"
                          };

    [manager POST:@"/member/create"
      parameters:dic
        progress:nil
         success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
             NSLog(@"Success: %@", responseObject);
         }
         failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
             NSLog(@"Failure: %@ %@", error,task);
         }];

}

- (void)applicationWillResignActive:(UIApplication *)application {
    // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
    // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
}

- (void)applicationDidEnterBackground:(UIApplication *)application {
    // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
    // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}

- (void)applicationWillEnterForeground:(UIApplication *)application {
    // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
}

- (void)applicationDidBecomeActive:(UIApplication *)application {
    // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}

- (void)applicationWillTerminate:(UIApplication *)application {
    // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}

@end

Post 出去的数据是 Raw 格式。

AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
//申明返回的结果是json类型
manager.responseSerializer = [AFJSONResponseSerializer serializer];
//申明请求的数据是json类型
manager.requestSerializer=[AFJSONRequestSerializer serializer];
//如果报接受类型不一致请替换一致text/html或别的
manager.responseSerializer.acceptableContentTypes = [NSSet setWithObject:@"text/html"];
//传入的参数
NSDictionary *parameters = @{@"1":@"XXXX",@"2":@"XXXX",@"3":@"XXXXX"};
//你的接口地址
NSString *url=@"http://xxxxx";
//发送请求
[manager POST:url parameters:parameters success:^(AFHTTPRequestOperation *operation, id responseObject) {
    NSLog(@"JSON: %@", responseObject);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
    NSLog(@"Error: %@", error);
}];


5.26. Android Oauth2 + Jwt example

package cn.netkiller.okhttp.pojo;

public class Oauth {
    private String access_token;
    private String token_type;
    private String refresh_token;
    private String expires_in;
    private String scope;
    private String jti;

    public String getAccess_token() {
        return access_token;
    }

    public void setAccess_token(String access_token) {
        this.access_token = access_token;
    }

    public String getToken_type() {
        return token_type;
    }

    public void setToken_type(String token_type) {
        this.token_type = token_type;
    }

    public String getRefresh_token() {
        return refresh_token;
    }

    public void setRefresh_token(String refresh_token) {
        this.refresh_token = refresh_token;
    }

    public String getExpires_in() {
        return expires_in;
    }

    public void setExpires_in(String expires_in) {
        this.expires_in = expires_in;
    }

    public String getScope() {
        return scope;
    }

    public void setScope(String scope) {
        this.scope = scope;
    }

    public String getJti() {
        return jti;
    }

    public void setJti(String jti) {
        this.jti = jti;
    }

    @Override
    public String toString() {
        return "Oauth{" +
                "access_token='" + access_token + '\'' +
                ", token_type='" + token_type + '\'' +
                ", refresh_token='" + refresh_token + '\'' +
                ", expires_in='" + expires_in + '\'' +
                ", scope='" + scope + '\'' +
                ", jti='" + jti + '\'' +
                '}';
    }
}

Activity 文件

package cn.netkiller.okhttp;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;

import com.google.gson.Gson;

import java.io.IOException;

import cn.netkiller.okhttp.pojo.Oauth;
import okhttp3.Authenticator;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.Credentials;
import okhttp3.FormBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.Route;

public class Oauth2jwtActivity extends AppCompatActivity {

    private TextView token;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_oauth2jwt);

        token = (TextView) findViewById(R.id.token);

        try {
            get();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    public static Oauth accessToken() throws IOException {

        OkHttpClient client = new OkHttpClient.Builder().authenticator(
                new Authenticator() {
                    public Request authenticate(Route route, Response response) {
                        String credential = Credentials.basic("api", "secret");
                        return response.request().newBuilder().header("Authorization", credential).build();
                    }
                }
        ).build();

        String url = "https://api.netkiller.cn/oauth/token";

        RequestBody formBody = new FormBody.Builder()
                .add("grant_type", "password")
                .add("username", "blockchain")
                .add("password", "123456")
                .build();

        Request request = new Request.Builder()
                .url(url)
                .post(formBody)
                .build();

        Response response = client.newCall(request).execute();
        if (!response.isSuccessful()) {
            throw new IOException("服务器端错误: " + response);
        }

        Gson gson = new Gson();
        Oauth oauth = gson.fromJson(response.body().string(), Oauth.class);
        Log.i("oauth", oauth.toString());
        return oauth;
    }

    public void get() throws IOException {

        OkHttpClient client = new OkHttpClient.Builder().authenticator(
                new Authenticator() {
                    public Request authenticate(Route route, Response response) throws IOException {
                        return response.request().newBuilder().header("Authorization", "Bearer " + accessToken().getAccess_token()).build();
                    }
                }
        ).build();

        String url = "https://api.netkiller.cn/misc/compatibility";

        Request request = new Request.Builder()
                .url(url)
                .build();

        client.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                call.cancel();
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {

                final String myResponse = response.body().string();

                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        Log.i("oauth", myResponse);
                        token.setText(myResponse);
                    }
                });

            }
        });
    }

}


5.27. Spring boot with Scheduling

项目中经常会用到计划任务，spring Boot 中通过@EnableScheduling启用计划任务，再通过@Scheduled注解配置计划任务如果运行。

5.27.1. Application.java

Application.java

启用计划任务, 在Spring Boot启动类加上注解@EnableScheduling，表示该项目启用计划任务

package api;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

@SpringBootApplication
@EnableAutoConfiguration
@ComponentScan({ "api.config", "api.web", "api.rest", "api.service","api.schedule" })
@EnableMongoRepositories
@EnableJpaRepositories
@EnableScheduling
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}

开启计划任务 @EnableScheduling

确保你的计划任务在 @ComponentScan 包中。

5.27.2. Component

在计划任务方法上加上@Scheduled注解，表示该方法是一个计划任务，项目启动后会去扫描该注解的方法并加入计划任务列表。

package api.schedule;

import java.text.SimpleDateFormat;
import java.util.Date;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@Component
public class ScheduledTasks {

    private static final Logger log = LoggerFactory.getLogger(ScheduledTasks.class);
    private static final SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");
    public final static long ONE_DAY = 24 * 60 * 60 * 1000;
    public final static long ONE_HOUR = 60 * 60 * 1000;

    public ScheduledTasks() {
    // TODO Auto-generated constructor stub
    }

    @Scheduled(fixedRate = 5000) //5秒运行一次调度任务
        public void echoCurrentTime() {
        log.info("The time is now {}", dateFormat.format(new Date()));
    }

    @Scheduled(fixedRate = ONE_DAY)
        public void scheduledTask() {
        System.out.println("每隔一天执行一次调度任务");
    }

    @Scheduled(fixedDelay = ONE_HOUR)
        public void scheduleTask2() {
        System.out.println("运行完后隔一小时就执行任务");
    }

    @Scheduled(initialDelay = 1000, fixedRate = 5000)
    public void doSomething() {
        // something that should execute periodically
    }

    @Scheduled(cron = "0 0/1 * * * ? ")
        public void ScheduledTask3() {
        System.out.println(" 每隔一分钟执行一次任务");
    }

}

5.27.3. 查看日志

tail -f spring.log

5.27.4. 计划任务控制开关

matchIfMissing = true， 如果改属性条目不存在返回 true

@ConditionalOnProperty("batch.metrics.export.influxdb.enabled")

# mybean.enabled = true
@ConditionalOnProperty(value='mybean.enabled')
@ConditionalOnProperty(value = "endpoints.hal.enabled", matchIfMissing = true)

# server.host = localhost
@ConditionalOnProperty(name="server.host", havingValue="localhost")
@ConditionalOnExpression("'${server.host}'=='localhost'")

# spring.rabbitmq.dynamic = true
@ConditionalOnProperty(prefix = "spring.rabbitmq", name = "dynamic", matchIfMissing = true)
@ConditionalOnProperty(prefix = "extension.security.cors", name = "enabled", matchIfMissing = false)
@ConditionalOnProperty(prefix = "camunda.bpm.job-execution", name = "enabled", havingValue = "true", matchIfMissing = true)

# spring.social.auto-connection-views = true
@ConditionalOnProperty(prefix = "spring.social.", value = "auto-connection-views")

使用案例

package mis.schedule;

import java.text.SimpleDateFormat;
import java.util.Date;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@ConditionalOnProperty("mis.schedule.enabled")
@Component
public class ScheduledTasks {
    private static final Logger logger = LoggerFactory.getLogger(ScheduledTasks.class);
    private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-mm-dd HH:mm:ss");
    public final static long ONE_DAY = 24 * 60 * 60 * 1000;
    public final static long ONE_HOUR = 60 * 60 * 1000;
    public final static long ONE_SECOND = 1000;

    public ScheduledTasks() {
        // TODO Auto-generated constructor stub
    }

    @Scheduled(fixedDelay = ONE_SECOND)
    public void scheduleTaskSplitLine() {
        logger.info("==================== {} ====================", dateFormat.format(new Date()));
    }
}

application.properties 配置如下

mis.schedule.enabled=true

5.27.5. @Scheduled 详解

@Scheduled参数说明

@Scheduled注解有一些参数，用于配置计划任务执行频率，执行时段等。

cron ：cron表达式，e.g. {@code "0 * * * * ？"}从前到后依次表示秒 分 时 日 月 年
zone：设置时区，指明计划任务运行在哪个时区下，默认为空，采用操作系统默认时区
fixedDelay：同一个计划任务两次执行间隔固定时间，单位毫秒，上次执行结束到下次开始执行的时间，以long类型复制
fixedDelayString：同一个计划任务两次执行间隔固定时间，单位毫秒，上次执行结束到下次开始执行的时间，以String类型赋值
fixedRate：以一个固定频率执行，单位毫秒，表示每隔多久执行一次，以long类型赋值
fixedRateString：以一个固定频率执行，单位毫秒，表示每隔多久执行一次，以String类型赋值
initialDelay：延迟启动计划任务，单位毫秒，表示执行第一次计划任务前先延迟一段时间，以long类型赋值
initialDelayString：延迟启动计划任务，单位毫秒，表示执行第一次计划任务前先延迟一段时间，以String赋值

cron表达式使用空格分隔的时间元素。

字段   允许值   允许的特殊字符
秒	 	0-59	 	, - * /
分	 	0-59	 	, - * /
小时	 	0-23	 	, - * /
日期	 	1-31	 	, - * ? / L W C
月份	 	1-12 或者 JAN-DEC	 	, - * /
星期	 	1-7 或者 SUN-SAT	 	, - * ? / L C #
年（可选）	 	留空, 1970-2099	 	, - * /

按顺序依次为

秒（0~59）

分钟（0~59）

小时（0~23）

天（月）（0~31，但是你需要考虑你月的天数）

月（0~11）

天（星期）（1~7 1=SUN 或 SUN，MON，TUE，WED，THU，FRI，SAT）

7.年份（1970－2099）

其中每个元素可以是一个值(如6),一个连续区间(9-12),一个间隔时间(8-18/4)(/表示每隔4小时),一个列表(1,3,5),通配符。由于"月份中的日期"和"星期中的日期"这两个元素互斥的,必须要对其中一个设置?.

0 0 10,14,16 * * ? 每天上午10点，下午2点，4点
0 0/30 9-17 * * ?   朝九晚五工作时间内每半小时
0 0 12 ? * WED 表示每个星期三中午12点
"0 0 12 * * ?" 每天中午12点触发
"0 15 10 ? * *" 每天上午10:15触发
"0 15 10 * * ?" 每天上午10:15触发
"0 15 10 * * ? *" 每天上午10:15触发
"0 15 10 * * ? 2005" 2005年的每天上午10:15触发
"0 * 14 * * ?" 在每天下午2点到下午2:59期间的每1分钟触发
"0 0/5 14 * * ?" 在每天下午2点到下午2:55期间的每5分钟触发
"0 0/5 14,18 * * ?" 在每天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发
"0 0-5 14 * * ?" 在每天下午2点到下午2:05期间的每1分钟触发
"0 10,44 14 ? 3 WED" 每年三月的星期三的下午2:10和2:44触发
"0 15 10 ? * MON-FRI" 周一至周五的上午10:15触发
"0 15 10 15 * ?" 每月15日上午10:15触发
"0 15 10 L * ?" 每月最后一日的上午10:15触发
"0 15 10 ? * 6L" 每月的最后一个星期五上午10:15触发
"0 15 10 ? * 6L 2002-2005" 2002年至2005年的每月的最后一个星期五上午10:15触发
"0 15 10 ? * 6#3" 每月的第三个星期五上午10:15触发

有些子表达式能包含一些范围或列表

例如：子表达式（天（星期））可以为 “MON-FRI”，“MON，WED，FRI”，“MON-WED,SAT”

“*”字符代表所有可能的值

因此，“*”在子表达式（月）里表示每个月的含义，“*”在子表达式（天（星期））表示星期的每一天

“/”字符用来指定数值的增量

例如：在子表达式（分钟）里的“0/15”表示从第0分钟开始，每15分钟

         在子表达式（分钟）里的“3/20”表示从第3分钟开始，每20分钟（它和“3，23，43”）的含义一样

“？”字符仅被用于天（月）和天（星期）两个子表达式，表示不指定值

当2个子表达式其中之一被指定了值以后，为了避免冲突，需要将另一个子表达式的值设为“？”

“L” 字符仅被用于天（月）和天（星期）两个子表达式，它是单词“last”的缩写

但是它在两个子表达式里的含义是不同的。

在天（月）子表达式中，“L”表示一个月的最后一天

在天（星期）自表达式中，“L”表示一个星期的最后一天，也就是SAT

如果在“L”前有具体的内容，它就具有其他的含义了

例如：“6L”表示这个月的倒数第６天，“ＦＲＩＬ”表示这个月的最一个星期五

注意：在使用“L”参数时，不要指定列表或范围，因为这会导致问题

5.27.5.1. 每3秒钟一运行一次

@Component
@EnableScheduling
public class MyTask {

    @Scheduled(cron="*/3 * * * * *")
    public void myTaskMethod(){
        //do something
    }
}

5.27.5.2. 凌晨23点运行

    @Scheduled(cron = "0 0 23 * * ?")
    private void cleanNewToday() {
        long begin = System.currentTimeMillis();

        redisTemplate.delete("news:today");

        long end = System.currentTimeMillis();
        logger.info("Schedule clean redis {} 耗时 {} 秒", "cleanNewFlash()", (end-begin) / 1000 );
    }


5.28. Spring boot with Hessian

5.28.1. Maven

        <dependency>
            <groupId>com.caucho</groupId>
            <artifactId>hessian</artifactId>
            <version>4.0.38</version>
        </dependency>

5.28.2. Application

package cn.netkiller;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
//import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
//import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;
import org.springframework.scheduling.annotation.EnableScheduling;

@SpringBootApplication
@EnableAutoConfiguration
@ComponentScan
// @EnableMongoRepositories
// @EnableJpaRepositories
@EnableScheduling
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);

    }
}

5.28.3. HessianServiceExporter

package cn.netkiller.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.remoting.caucho.HessianProxyFactoryBean;
//import org.springframework.remoting.caucho.HessianProxyFactoryBean;
import org.springframework.remoting.caucho.HessianServiceExporter;

import cn.netkiller.service.HelloWorldService;

@Configuration
public class HessionConfig {
    @Autowired
    private HelloWorldService helloWorldService;

    @Bean(name = "/HelloWorldService")
    public HessianServiceExporter hessianServiceExporter() {
        HessianServiceExporter exporter = new HessianServiceExporter();
        exporter.setService(helloWorldService);
        exporter.setServiceInterface(HelloWorldService.class);
        return exporter;
    }

    @Bean
    public HessianProxyFactoryBean helloClient() {
        HessianProxyFactoryBean factory = new HessianProxyFactoryBean();
        factory.setServiceUrl("http://localhost:7000/HelloWorldService");
        factory.setServiceInterface(HelloWorldService.class);
        return factory;
    }
}

5.28.4. Service

package cn.netkiller.service;

public interface HelloWorldService {
    String sayHello(String name);
}

package cn.netkiller.service.impl;

import org.springframework.stereotype.Component;

import cn.netkiller.service.HelloWorldService;

@Component
public class HelloWorldServiceImpl implements HelloWorldService {
    @Override
    public String sayHello(String name) {
        return "Hello World! " + name;
    }
}

5.28.5. RestController

package cn.netkiller.rest.hession;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import cn.netkiller.service.HelloWorldService;

@RestController
@RequestMapping("/public/hession")
public class TestRestController {
    @Autowired
    HelloWorldService helloWorldService;

    @RequestMapping("/hello")
    public String test() {
        return helloWorldService.sayHello("Spring boot with Hessian.");
    }
}


5.29. Spring boot with Apache Kafka

Spring boot 1.5.1

5.29.1. 安装 kafka

一下安装仅仅适合开发环境，生产环境请使用这个脚本安装 https://github.com/oscm/shell/tree/master/mq/kafka

cd /usr/local/src
wget http://apache.communilink.net/kafka/0.10.2.0/kafka_2.12-0.10.2.0.tgz
tar zxvf kafka_2.12-0.10.2.0.tgz
mv kafka_2.12-0.10.2.0 /srv/
cp /srv/kafka_2.12-0.10.2.0/config/server.properties{,.original}
echo "advertised.host.name=localhost" >> /srv/kafka_2.12-0.10.2.0/config/server.properties
ln -s /srv/kafka_2.12-0.10.2.0 /srv/kafka

启动 Kafka 服务

/srv/kafka/bin/zookeeper-server-start.sh config/zookeeper.properties
/srv/kafka/bin/kafka-server-start.sh /srv/kafka/config/server.properties

-daemon 表示守护进程方式在后台启动

/srv/kafka/bin/zookeeper-server-start.sh -daemon config/zookeeper.properties
/srv/kafka/bin/kafka-server-start.sh -daemon /srv/kafka/config/server.properties

停止 Kafka 服务

/srv/kafka/bin/kafka-server-stop.sh
/srv/kafka/bin/zookeeper-server-stop.sh

5.29.2. maven

        <dependency>
            <groupId>org.springframework.kafka</groupId>
            <artifactId>spring-kafka</artifactId>
        </dependency>

<?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>cn.netkiller</groupId>
    <artifactId>deploy</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>war</packaging>

    <name>deploy.netkiller.cn</name>
    <description>Deploy project for Spring Boot</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.1.RELEASE</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>
        <!-- <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> -->
        <!-- <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> -->
        <!-- <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-mongodb</artifactId> </dependency> -->
        <!-- <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.session</groupId>
            <artifactId>spring-session-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
        </dependency>
        <dependency>
            <groupId>org.webjars</groupId>
            <artifactId>webjars-locator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.webjars</groupId>
            <artifactId>sockjs-client</artifactId>
            <version>1.0.2</version>
        </dependency>
        <dependency>
            <groupId>org.webjars</groupId>
            <artifactId>stomp-websocket</artifactId>
            <version>2.3.3</version>
        </dependency>
        <dependency>
            <groupId>org.webjars</groupId>
            <artifactId>bootstrap</artifactId>
            <version>3.3.7</version>
        </dependency>
        <dependency>
            <groupId>org.webjars</groupId>
            <artifactId>jquery</artifactId>
            <version>3.1.0</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-mail</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-jasper</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <!-- <version>2.7</version> -->
        </dependency>
        <dependency>
            <groupId>com.caucho</groupId>
            <artifactId>hessian</artifactId>
            <version>4.0.38</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.kafka</groupId>
            <artifactId>spring-kafka</artifactId>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <scope>test</scope>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <mainClass>cn.netkiller.Application</mainClass>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <configuration>
                    <skip>true</skip>
                </configuration>
            </plugin>
        </plugins>
    </build>

    <repositories>
        <repository>
            <id>spring-snapshots</id>
            <name>Spring Snapshots</name>
            <url>https://repo.spring.io/snapshot</url>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
        </repository>
        <repository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
    </repositories>
    <pluginRepositories>
        <pluginRepository>
            <id>spring-snapshots</id>
            <name>Spring Snapshots</name>
            <url>https://repo.spring.io/snapshot</url>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
        </pluginRepository>
        <pluginRepository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </pluginRepository>
    </pluginRepositories>

</project>

5.29.3. Spring boot Application

package cn.netkiller;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.scheduling.annotation.EnableScheduling;

@SpringBootApplication
@EnableAutoConfiguration
@ComponentScan
@EnableScheduling
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);

    }
}

5.29.4. EnableKafka

package cn.netkiller.kafka;

import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.common.serialization.IntegerDeserializer;
import org.apache.kafka.common.serialization.StringDeserializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.kafka.annotation.EnableKafka;
import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory;
import org.springframework.kafka.config.KafkaListenerContainerFactory;
import org.springframework.kafka.core.ConsumerFactory;
import org.springframework.kafka.core.DefaultKafkaConsumerFactory;
import org.springframework.kafka.listener.ConcurrentMessageListenerContainer;

import java.util.HashMap;
import java.util.Map;

@Configuration
@EnableKafka
public class KafkaConsumerConfig {

    public KafkaConsumerConfig() {
        // TODO Auto-generated constructor stub
    }

    @Bean
    KafkaListenerContainerFactory<ConcurrentMessageListenerContainer<String, String>> kafkaListenerContainerFactory() {
        ConcurrentKafkaListenerContainerFactory<String, String> factory = new ConcurrentKafkaListenerContainerFactory<String, String>();
        factory.setConsumerFactory(consumerFactory());
        // factory.setConcurrency(1);
        // factory.getContainerProperties().setPollTimeout(3000);
        return factory;
    }

    @Bean
    public ConsumerFactory<String, String> consumerFactory() {
        return new DefaultKafkaConsumerFactory<String, String>(consumerConfigs());
    }

    @Bean
    public Map<String, Object> consumerConfigs() {
        Map<String, Object> propsMap = new HashMap<String, Object>();
        propsMap.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "www.netkiller.cn:9092");
        propsMap.put(ConsumerConfig.GROUP_ID_CONFIG, "test-consumer-group");
        propsMap.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "latest");
        propsMap.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, true);
        propsMap.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, "100");
        propsMap.put(ConsumerConfig.SESSION_TIMEOUT_MS_CONFIG, "15000");
        propsMap.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, IntegerDeserializer.class);
        propsMap.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
        return propsMap;
    }

    @Bean
    public Listener listener() {
        return new Listener();
    }

}

5.29.5. KafkaListener

package cn.netkiller.kafka;

import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.springframework.kafka.annotation.KafkaListener;
import java.util.concurrent.CountDownLatch;
import java.util.logging.Logger;

public class Listener {

    public Listener() {
        // TODO Auto-generated constructor stub
    }

    protected Logger logger = Logger.getLogger(Listener.class.getName());

    public CountDownLatch getCountDownLatch1() {
        return countDownLatch1;
    }

    private CountDownLatch countDownLatch1 = new CountDownLatch(1);

    @KafkaListener(topics = "test")
    public void listen(ConsumerRecord<?, ?> record) {
        logger.info("Received message: " + record.toString());
        System.out.println("Received message: " + record);
        countDownLatch1.countDown();
    }
}

5.29.6. 测试

$ cd /srv/kafka
$ bin/kafka-console-producer.sh --broker-list 47.89.35.55:9092 --topic test
This is test message.

每输入一行回车后发送到你的Spring boot kafka 程序

5.29.7. 完整的发布订阅实例

上面的例子仅仅是做了一个热身，现在我们将实现 一个完整的例子。

5.29.7.1. Consumer

例 5.5. Spring boot with Apache kafka.

SpringApplication

package cn.netkiller;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
//import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
//import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;
import org.springframework.scheduling.annotation.EnableScheduling;

@SpringBootApplication
@EnableAutoConfiguration
@ComponentScan
// @EnableMongoRepositories
// @EnableJpaRepositories
@EnableScheduling
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);

    }
}

Consumer configuration

package cn.netkiller.kafka.config;

import java.util.HashMap;
import java.util.Map;

import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.common.serialization.IntegerDeserializer;
import org.apache.kafka.common.serialization.StringDeserializer;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.kafka.annotation.EnableKafka;
import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory;
import org.springframework.kafka.core.ConsumerFactory;
import org.springframework.kafka.core.DefaultKafkaConsumerFactory;

import cn.netkiller.kafka.consumer.Consumer;

@Configuration
@EnableKafka
public class ConsumerConfiguration {

    public ConsumerConfiguration() {
        // TODO Auto-generated constructor stub
    }

    @Bean
    public Map<String, Object> consumerConfigs() {
        HashMap<String, Object> props = new HashMap<>();
        // list of host:port pairs used for establishing the initial connections
        // to the Kakfa cluster
        props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "www.netkiller.cn:9092");
        props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, IntegerDeserializer.class);
        props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
        // consumer groups allow a pool of processes to divide the work of
        // consuming and processing records
        props.put(ConsumerConfig.GROUP_ID_CONFIG, "helloworld");

        return props;
    }

    @Bean
    public ConsumerFactory<String, String> consumerFactory() {
        return new DefaultKafkaConsumerFactory<String, String>(consumerConfigs());
    }

    @Bean
    public ConcurrentKafkaListenerContainerFactory<String, String> kafkaListenerContainerFactory() {
        ConcurrentKafkaListenerContainerFactory<String, String> factory = new ConcurrentKafkaListenerContainerFactory<String, String>();
        factory.setConsumerFactory(consumerFactory());
        return factory;
    }

    @Bean
    public Consumer receiver() {
        return new Consumer();
    }
}

Consumer

package cn.netkiller.kafka.consumer;
import java.util.concurrent.CountDownLatch;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.kafka.annotation.KafkaListener;

public class Consumer {

    public Consumer() {
        // TODO Auto-generated constructor stub
    }
    private static final Logger logger = LoggerFactory
            .getLogger(Consumer.class);

    private CountDownLatch latch = new CountDownLatch(1);

    @KafkaListener(topics = "helloworld.t")
    public void receiveMessage(String message) {
        logger.info("received message='{}'", message);
        latch.countDown();
    }

    public CountDownLatch getLatch() {
        return latch;
    }
}

5.29.7.2. Producer

例 5.6. Spring boot with Apache kafka.

Producer configuration

package cn.netkiller.kafka.config;

import java.util.HashMap;
import java.util.Map;

import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.common.serialization.IntegerSerializer;
import org.apache.kafka.common.serialization.StringSerializer;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.kafka.core.DefaultKafkaProducerFactory;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.kafka.core.ProducerFactory;

import cn.netkiller.kafka.producer.Producer;

@Configuration
public class ProducerConfiguration {

    public ProducerConfiguration() {
        // TODO Auto-generated constructor stub
    }

    @Bean
    public Map<String, Object> producerConfigs() {
        HashMap<String, Object> props = new HashMap<>();
        // list of host:port pairs used for establishing the initial connections
        // to the Kakfa cluster
        props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "www.netkiller.cn:9092");
        props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, IntegerSerializer.class);
        props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
        // value to block, after which it will throw a TimeoutException
        props.put(ProducerConfig.MAX_BLOCK_MS_CONFIG, 5000);

        return props;
    }

    @Bean
    public ProducerFactory<String, String> producerFactory() {
        return new DefaultKafkaProducerFactory<String, String>(producerConfigs());
    }

    @Bean
    public KafkaTemplate<String, String> kafkaTemplate() {
        return new KafkaTemplate<String, String>(producerFactory());
    }

    @Bean
    public Producer sender() {
        return new Producer();
    }
}

Producer

package cn.netkiller.kafka.producer;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.kafka.support.SendResult;
import org.springframework.util.concurrent.ListenableFuture;
import org.springframework.util.concurrent.ListenableFutureCallback;

public class Producer {

    private static final Logger logger = LoggerFactory.getLogger(Producer.class);

    /*
     * public Sender() { // TODO Auto-generated constructor stub }
     */

    @Autowired
    private KafkaTemplate<String, String> kafkaTemplate;

    public void sendMessage(String topic, String message) {
        // the KafkaTemplate provides asynchronous send methods returning a
        // Future
        ListenableFuture<SendResult<String, String>> future = kafkaTemplate.send(topic, message);

        // you can register a callback with the listener to receive the result
        // of the send asynchronously
        future.addCallback(new ListenableFutureCallback<SendResult<String, String>>() {

            @Override
            public void onSuccess(SendResult<String, String> result) {
                logger.info("sent message='{}' with offset={}", message, result.getRecordMetadata().offset());
            }

            @Override
            public void onFailure(Throwable ex) {
                logger.error("unable to send message='{}'", message, ex);
            }
        });

        // alternatively, to block the sending thread, to await the result,
        // invoke the future’s get() method
    }
}

Controller

package cn.netkiller.web;

import java.util.concurrent.TimeUnit;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import cn.netkiller.kafka.consumer.Consumer;
import cn.netkiller.kafka.producer.Producer;

@Controller
@RequestMapping("/test")
public class KafkaTestController {

    private static final Logger logger = LoggerFactory.getLogger(IndexController.class);

    public KafkaTestController() {
        // TODO Auto-generated constructor stub
    }

    @Autowired
    private Producer sender;

    @Autowired
    private Consumer receiver;

    @RequestMapping("/ping")
    @ResponseBody
    public String ping() {
        String message = "PONG";
        return message;
    }

    @RequestMapping("/kafka/send")
    @ResponseBody
    public String testReceiver() throws Exception {
        sender.sendMessage("helloworld.t", "Hello Spring Kafka!");

        receiver.getLatch().await(10000, TimeUnit.MILLISECONDS);
        logger.info(receiver.getLatch().getCount() + "");
        return "OK";
    }

}

5.29.7.3. Test

例 5.7. Test Spring Kafka

SpringBootTest

package cn.netkiller;
import static org.assertj.core.api.Assertions.assertThat;

import java.util.concurrent.TimeUnit;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import cn.netkiller.kafka.consumer.Consumer;
import cn.netkiller.kafka.producer.Producer;

@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringKafkaApplicationTests {

    public SpringKafkaApplicationTests() {
        // TODO Auto-generated constructor stub
    }
    @Autowired
    private Producer sender;

    @Autowired
    private Consumer receiver;

    @Test
    public void testReceiver() throws Exception {
        sender.sendMessage("helloworld.t", "Hello Spring Kafka!");

        receiver.getLatch().await(10000, TimeUnit.MILLISECONDS);
        assertThat(receiver.getLatch().getCount()).isEqualTo(0);
    }
}

5.29.8. Spring cloud with Kafka

5.29.8.1. Application 主文件

package schedule;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.scheduling.annotation.EnableScheduling;

@SpringBootApplication
@EnableScheduling
@EnableEurekaClient
@EntityScan("common.domain")
public class Application {

    public static void main(String[] args) {
        System.out.println("Service Schedule Starting...");
        SpringApplication.run(Application.class, args);
    }
}

5.29.8.2. 资源配置文件

5.29.8.2.1. application.properties

只需要两行，其余所有配置均放在配置中心。

# ==============================
spring.application.name=schedule
eureka.client.serviceUrl.defaultZone=http://eureka:s3cr3t@172.16.0.10:8761/eureka/
# ==============================
5.29.8.2.2. bootstrap.properties

配置中心服务器相关配置

#spring.application.name=schedule
spring.cloud.config.profile=development
spring.cloud.config.label=master
spring.cloud.config.uri=http://172.16.0.10:8888
management.security.enabled=false
spring.cloud.config.username=cfg
spring.cloud.config.password=s3cr3t
5.29.8.2.3. Git 仓库

在 git 仓库中加入 spring.kafka.bootstrap_servers 配置项

spring.kafka.bootstrap_servers=172.16.0.10:9092

5.29.8.3. 启用 kafka

使用 @EnableKafka 启用 Kafka 不需要其他@Bean等。这个配置文件可以省略，可以将 @EnableKafka 放到 Application.java 中。我还是喜欢独立配置。

package schedule.config;
@Configuration
@EnableKafka
public class KafkaConfiguration {
}

5.29.8.4. 消息发布主程序

package schedule.task;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.kafka.support.SendResult;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.util.concurrent.ListenableFuture;
import org.springframework.util.concurrent.ListenableFutureCallback;
import org.springframework.web.client.RestTemplate;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

import schedule.repository.CmsTrashRepository;
import schedule.repository.ArticleRepository;
import common.domain.Article;
import common.domain.CmsTrash;
import common.pojo.ResponseRestful;

@Component
public class CFPushTasks {
    private static final Logger logger = LoggerFactory.getLogger(CFPushTasks.class);

    private static final String TOPIC = "test";
    private static final SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    private static final ObjectMapper mapper = new ObjectMapper();

    @Autowired
    private ArticleRepository articleRepository;

    @Autowired
    private CmsTrashRepository cmsTrashRepository;

    @Autowired
    private KafkaTemplate<String, String> kafkaTemplate;

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @Value("${cf.cms.site_id}")
    private int siteId;

    public CFPushTasks() {
    }

    private Date getDate() {

        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.MINUTE, -1);
        Date date = calendar.getTime();
        return date;
    }

    private boolean setPostionDate(String key, Date value) {
        String cacheKey = String.format("schedule:CFPushTasks:%s", key);
        String date = simpleDateFormat.format(value);
        logger.info("setPostion({},{})", cacheKey, date);
        redisTemplate.opsForValue().set(cacheKey, date);

        if (value == this.getPostionDate(cacheKey)) {
            return true;
        }
        return false;
    }

    private Date getPostionDate(String key) {
        String cacheKey = String.format("schedule:CFPushTasks:%s", key);
        Date date = null;
        if (redisTemplate.hasKey(cacheKey)) {
            try {
                date = simpleDateFormat.parse(redisTemplate.opsForValue().get(cacheKey));
            } catch (ParseException e) {
                // TODO Auto-generated catch block
                // e.printStackTrace();
                logger.warn(e.getMessage());
            }
        }
        logger.debug("getPostion({}) => {}", cacheKey, date);
        return date;
    }

    private boolean setPostionId(String key, int id) {
        String cacheKey = String.format("schedule:CFPushTasks:PostionId:%s", key);
        logger.info("setPostionId({},{})", cacheKey, id);
        redisTemplate.opsForValue().set(cacheKey, String.valueOf(id));
        if (id == this.getPostionId(cacheKey)) {
            return true;
        }
        return false;
    }

    private int getPostionId(String key) {
        String cacheKey = String.format("schedule:CFPushTasks:PostionId:%s", key);
        int id = 0;
        if (redisTemplate.hasKey(cacheKey)) {
            id = Integer.valueOf(redisTemplate.opsForValue().get(cacheKey));
        }
        logger.debug("getPostion({}) => {}", cacheKey, id);
        return id;
    }

    @Scheduled(fixedRate = 1000 * 50)
    public void insert() {
        Iterable<Article> articles = null;
        int id = this.getPostionId("insert");

        if (id == 0) {
            articles = articleRepository.findBySiteId(this.siteId);

        } else {
            articles = articleRepository.findBySiteIdAndIdGreaterThan(this.siteId, id);
        }
        if (articles != null) {
            for (Article article : articles) {
                ResponseRestful responseRestful = new ResponseRestful(true, this.getPostionId("insert"), "INSERT", article);
                String jsonString;
                try {
                    jsonString = mapper.writeValueAsString(responseRestful);
                    this.send(TOPIC, jsonString);
                    if (!this.setPostionId("insert", article.getId())) {
                        return;
                    }

                } catch (JsonProcessingException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }

    }

    @Scheduled(fixedRate = 1000 * 50)
    public void update() {
        String message = "Hello";
        this.send(TOPIC, message);

    }

    @Scheduled(fixedRate = 1000 * 50)
    public void delete() {
        Date date = this.getPostionDate("delete");
        Iterable<CmsTrash> cmsTrashs;
        if (date == null) {
            cmsTrashs = cmsTrashRepository.findBySiteIdAndTypeOrderByCtime(this.siteId, "delete");
        } else {
            cmsTrashs = cmsTrashRepository.findBySiteIdAndTypeAndCtimeGreaterThanOrderByCtime(this.siteId, "delete", date);
        }
        if (cmsTrashs != null) {

            for (CmsTrash cmsTrash : cmsTrashs) {
                ResponseRestful responseRestful = new ResponseRestful(true, this.getPostionId("delete"), "DELETE", cmsTrash);
                String jsonString;
                try {
                    jsonString = mapper.writeValueAsString(responseRestful);
                    this.send(TOPIC, jsonString);
                    this.setPostionId("delete", cmsTrash.getId());
                    if (!this.setPostionDate("delete", cmsTrash.getCtime())) {
                        return;
                    } else {

                    }
                } catch (JsonProcessingException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }

            }
        }

    }

    private void send(String topic, String message) {

        ListenableFuture<SendResult<String, String>> future = kafkaTemplate.send(topic, message);

        future.addCallback(new ListenableFutureCallback<SendResult<String, String>>() {

            @Override
            public void onSuccess(SendResult<String, String> result) {
                logger.debug("sent message='{}' with offset={}", message, result.getRecordMetadata().offset());
            }

            @Override
            public void onFailure(Throwable ex) {
                logger.error("unable to send message='{}'", message, ex);
            }
        });
    }

    private void post(ResponseRestful responseRestful) {
        RestTemplate restTemplate = new RestTemplate();
        String response = restTemplate.postForObject("http://localhost:8440/test/cf/post.json", responseRestful, String.class);

        // logger.info(article.toString());
        if (response != null) {
            logger.info(response);
        }
    }
}


5.30. Spring boot with RabbitMQ(AMQP)

5.30.1. maven

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

5.30.2. RabbitMQConfig

@Configuration
public class RabbitMQConfig {

    public final static String QUEUE_NAME = "spring-boot-queue";
    public final static String EXCHANGE_NAME = "spring-boot-exchange";
    public final static String ROUTING_KEY = "spring-boot-key";

    // 创建队列
    @Bean
    public Queue queue() {
        return new Queue(QUEUE_NAME);
    }

    // 创建一个 topic 类型的交换器
    @Bean
    public TopicExchange exchange() {
        return new TopicExchange(EXCHANGE_NAME);
    }

    // 使用路由键（ROUTING_KEY）把队列（Queue）绑定到交换器（Exchange）
    @Bean
    public Binding binding(Queue queue, TopicExchange exchange) {
        return BindingBuilder.bind(queue).to(exchange).with(ROUTING_KEY);
    }

    @Bean
    public ConnectionFactory connectionFactory() {
        CachingConnectionFactory connectionFactory = new CachingConnectionFactory("127.0.0.1", 5672);
        connectionFactory.setUsername("guest");
        connectionFactory.setPassword("guest");
        return connectionFactory;
    }

    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
        return new RabbitTemplate(connectionFactory);
    }

}

5.30.3. 生产者

@RestController
public class ProducerController {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @GetMapping("/sendMessage")
    public String sendMessage() {
        new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                String value = new DateTime().toString("yyyy-MM-dd HH:mm:ss");
                System.out.println("send message {}", value);
                rabbitTemplate.convertAndSend(RabbitMQConfig.EXCHANGE_NAME, RabbitMQConfig.ROUTING_KEY, value);
            }
        }).start();
        return "ok";
    }

}

5.30.4. 消费者

@Component
public class Consumer {

    @RabbitListener(queues = RabbitMQConfig.QUEUE_NAME)
    public void consumeMessage(String message) {
        System.out.println("consume message {}", message);
    }
}


5.31. Spring boot with Git version

Spring boot 每次升级打包发给运维操作，常常运维操作不当致使升级失败，开发怎样确认线上的jar/war包与升级包一致呢？

请看下面的解决方案

5.31.1. CommonRestController 公共控制器

所有 RestController将会集成 CommonRestController

package cn.netkiller.api.rest;

import org.springframework.http.HttpStatus;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;

public class CommonRestController {

    @RequestMapping("ping")
    @ResponseStatus(HttpStatus.OK)
    public String welcome() {
        return "PONG";
    }

    @RequestMapping("commit")
    public String commit() {
        return "$Id$";
    }

    @RequestMapping("auth")
    @ResponseStatus(HttpStatus.OK)
    public String auth(@AuthenticationPrincipal final UserDetails user) {
        return String.format("%s: %s %s", user.getUsername(), user.getPassword(), user.getAuthorities());
    }
}

5.31.2. VersionRestController 测试控制器

我们创建一个RestController并继承CommonRestController用来测试

package cn.netkiller.api.rest;

@RestController
@RequestMapping("/public/version")
public class VersionRestController extends CommonRestController {
    private static final Logger logger = LoggerFactory.getLogger(VersionRestController.class);

    public VersionRestController() {
        // TODO Auto-generated constructor stub
    }

    @RequestMapping("welcome")
    @ResponseStatus(HttpStatus.OK)
    public String welcome() {
        return "Welcome to RestTemplate version 1.0.";
    }

}

5.31.3. 创建 .gitattributes 文件

# vim .gitattributes
src/main/java/cn/netkiller/api/rest/CommonRestController.java ident

使用curl命令调用commit接口可以显示当前war/jar最后一次提交的版本号码（你同样可以使用IE浏览器）

curl https://api.netkiller.cn/public/version/commit.json
$Id: 929bc9e4c90b4d68c25dc693618f23b33fd6ba0f $


5.32. Spring boot with HTTPS SSL

5.32.1. 生成自签名证书

keytool -genkey -alias www.netkiller.cn -keyalg RSA -keystore /www/netkiller.cn/www.netkiller.cn.keystore

导入证书（Windows）

keytool -selfcert -alias www.netkiller.cn -keystore www.netkiller.cn.keystore
keytool -export -alias www.netkiller.cn -keystore www.netkiller.cn.keystore -storepass passw0rd -rfc -file www.netkiller.cn.cer

导入证书（JVM）

keytool -importcert -alias www.netkiller.cn -file www.netkiller.cn.cer -keystore /srv/java/jre/lib/security/cacerts

5.32.2. application.properties 配置文件

配置Tomcat HTTPS 端口 8443（由于JVM不能fork和setuid，所以无法向nginx,apache httpd 那样设置 80 端口，除非你使用root用户运行，但这样做是不安全的。）

server.port=8443
server.ssl.enabled=true
server.ssl.key-store=/www/netkiller.cn/www.netkiller.cn.keystore
server.ssl.key-store-password=passw0rd
server.ssl.key-store-type=JKS
server.ssl.key-alias=www.netkiller.cn

keystore 文件可以放到 classpath 中，首先将证书文件放到 src/main/resources 目录中，然后配置 application.properties 如下：

                server.port=8443
                server.ssl.enabled=true
                server.ssl.key-store=classpath:www.netkiller.cn.keystore
                server.ssl.key-store-password=123456
                server.ssl.key-store-type=JKS
                server.ssl.key-alias=www.netkiller.cn

5.32.3. 启动 Spring boot

/srv/java/bin/java -server -Xms2048m -Xmx8192m -Djava.security.egd=file:/dev/./urandom -jar /www/netkiller.cn/www.netkiller.cn/www.netkiller.cn-0.0.1.war

5.32.4. restTemplate 调用实例

String url = "https://www.netkiller.cn:8443/public/test/version.json";
ResponseEntity<RestResponse<String>> result = restTemplate.exchange(url, HttpMethod.GET, null, new ParameterizedTypeReference<RestResponse<String>>() {});


5.33. Spring boot with Elasticsearch 2.x

5.33.1. Maven

<?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>cn.netkiller.springboot</groupId>
    <artifactId>elasticsearch</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>elasticsearch</name>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.3.RELEASE</version>
    </parent>
    <dependencies>

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

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

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
    </dependencies>
</project>

5.33.2. Application

package cn.netkiller;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;
import org.springframework.scheduling.annotation.EnableScheduling;

@SpringBootApplication
@EnableAutoConfiguration
@ComponentScan
@EnableMongoRepositories
@EnableJpaRepositories
@EnableScheduling
public class Application {

       public static void main(String[] args) {
               SpringApplication.run(Application.class, args);
       }
}

5.33.3. application.properties

spring.data.elasticsearch.repositories.enabled=true
spring.data.elasticsearch.cluster-nodes=127.0.0.1:9300

5.33.4. Domain

@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;

    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;
    }
}

5.33.5. ElasticsearchRepository

public interface CityRepository extends ElasticsearchRepository<City, Long> {
    List<City> findByNameLike(String name);
    Page<City> findByDescription(String description, Pageable page);
    Page<City> findByDescriptionNot(String description, Pageable page);
    Page<City> findByDescriptionLike(String description, Pageable page);
}


5.34. Spring boot with Elasticsearch 5.5.x

Spring data 目前还不支持 Elasticsearch 5.5.x 所以需要通过注入 TransportClient 这就意味着使用 5.5.x 版本你无法使用 ElasticsearchRepository 这种特性，只能通过官方的 TransportClient 操作 Elasticsearch。

5.34.1. Maven

Elasticsearch 依赖下来四个包

        <dependency>
            <groupId>org.elasticsearch</groupId>
            <artifactId>elasticsearch</artifactId>
            <version>5.5.1</version>
        </dependency>
        <dependency>
            <groupId>org.elasticsearch.client</groupId>
            <artifactId>transport</artifactId>
            <version>5.5.1</version>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-api</artifactId>
            <version>2.8.2</version>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>2.8.2</version>
        </dependency>

下面是我的完整例子

<?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>com.example</groupId>
    <artifactId>api</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>api</name>
    <description>Demo project for Spring Boot</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.2.RELEASE</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>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <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>
            </dependency> -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <!-- https://mvnrepository.com/artifact/javax.persistence/persistence-api -->
        <dependency>
            <groupId>javax.persistence</groupId>
            <artifactId>persistence-api</artifactId>
            <version>1.0.2</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.json/json -->
        <dependency>
            <groupId>org.json</groupId>
            <artifactId>json</artifactId>
        </dependency>
        <dependency>
            <groupId>org.elasticsearch</groupId>
            <artifactId>elasticsearch</artifactId>
            <version>5.5.1</version>
        </dependency>
        <dependency>
            <groupId>org.elasticsearch.client</groupId>
            <artifactId>transport</artifactId>
            <version>5.5.1</version>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-api</artifactId>
            <version>2.8.2</version>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>2.8.2</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <configuration>
                    <skip>true</skip>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

5.34.2. Application

package cn.netkiller;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;
import org.springframework.scheduling.annotation.EnableScheduling;

@SpringBootApplication
@EnableAutoConfiguration
@ComponentScan
@EnableMongoRepositories
@EnableJpaRepositories
@EnableScheduling
public class Application {

       public static void main(String[] args) {
               SpringApplication.run(Application.class, args);
       }
}

5.34.3. application.properties

注意：Elasticsearch 连接地址是 9300， 而不是 9200

spring.data.elasticsearch.cluster-nodes=172.0.0.1:9300
spring.data.elasticsearch.local=false
spring.data.elasticsearch.properties.transport.tcp.connect_timeout=60s

5.34.4. ElasticsearchConfiguration

package com.example.api.config;

import java.net.InetAddress;
import java.net.UnknownHostException;

import org.elasticsearch.client.transport.TransportClient;
import org.elasticsearch.common.transport.InetSocketTransportAddress;
import org.elasticsearch.transport.client.PreBuiltTransportClient;
import org.elasticsearch.common.settings.Settings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ElasticsearchConfiguration implements FactoryBean<TransportClient>, InitializingBean, DisposableBean {
    private static final Logger logger = LoggerFactory.getLogger(ElasticsearchConfiguration.class);

    @Value("${spring.data.elasticsearch.cluster-nodes}")
    private String clusterNodes;

    private TransportClient transportClient;
    private PreBuiltTransportClient preBuiltTransportClient;

    @Override
    public void destroy() throws Exception {
        try {
            logger.info("Closing elasticSearch client");
            if (transportClient != null) {
                transportClient.close();
            }
        } catch (final Exception e) {
            logger.error("Error closing ElasticSearch client: ", e);
        }
    }

    @Override
    public TransportClient getObject() throws Exception {
        return transportClient;
    }

    @Override
    public Class<TransportClient> getObjectType() {
        return TransportClient.class;
    }

    @Override
    public boolean isSingleton() {
        return false;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        buildClient();
    }

    protected void buildClient() {
        try {
            preBuiltTransportClient = new PreBuiltTransportClient(settings());

            String InetSocket[] = clusterNodes.split(":");
            String address = InetSocket[0];
            Integer port = Integer.valueOf(InetSocket[1]);
            transportClient = preBuiltTransportClient.addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName(address), port));

        } catch (UnknownHostException e) {
            logger.error(e.getMessage());
        }
    }

    /**
     * 初始化默认的client
     */
    private Settings settings() {
//		Settings settings = Settings.builder().put("cluster.name", clusterName).put("client.transport.sniff", true).build();
        Settings settings = Settings.builder().put("cluster.name", "elasticsearch").build();
        return settings;
    }
}

5.34.5. RestController

package com.example.api.restful;

import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.client.transport.TransportClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/restful/search")
public class SearchRestController {

    @Autowired
    private TransportClient client;

    @RequestMapping(value = "/client/{articleId}")
    public GetResponse test(@PathVariable String articleId) {
        GetResponse response = client.prepareGet("information", "article", articleId).get();
        return response;
    }
}

使用 Curl 测试 restful 接口

MacBook-Pro:~ neo$ curl -k https://test:test@localhost:8443/restful/search/client/1093.json
{"fields":{},"id":"1093","type":"article","source":{"@timestamp":"2017-07-31T05:41:00.248Z","author":"test","@version":"1","description":"test","ctime":"2017-07-31T05:40:35.000Z","id":1093,"source":"test","title":"test11111","content":"<p>test</p><p>aaaaaaaaaaaaaa</p>"},"version":3,"index":"information","sourceAsBytes":"eyJAdGltZXN0YW1wIjoiMjAxNy0wNy0zMVQwNTo0MTowMC4yNDhaIiwiYXV0aG9yIjoidGVzdCIsIkB2ZXJzaW9uIjoiMSIsImRlc2NyaXB0aW9uIjoidGVzdCIsImN0aW1lIjoiMjAxNy0wNy0zMVQwNTo0MDozNS4wMDBaIiwiaWQiOjEwOTMsInNvdXJjZSI6InRlc3QiLCJ0aXRsZSI6InRlc3QxMTExMSIsImNvbnRlbnQiOiI8cD50ZXN0PC9wPjxwPmFhYWFhYWFhYWFhYWFhPC9wPiJ9","sourceInternal":{"childResources":[]},"sourceAsString":"{\"@timestamp\":\"2017-07-31T05:41:00.248Z\",\"author\":\"test\",\"@version\":\"1\",\"description\":\"test\",\"ctime\":\"2017-07-31T05:40:35.000Z\",\"id\":1093,\"source\":\"test\",\"title\":\"test11111\",\"content\":\"<p>test</p><p>aaaaaaaaaaaaaa</p>\"}","sourceEmpty":false,"sourceAsMap":{"@timestamp":"2017-07-31T05:41:00.248Z","author":"test","@version":"1","description":"test","ctime":"2017-07-31T05:40:35.000Z","id":1093,"source":"test","title":"test11111","content":"<p>test</p><p>aaaaaaaaaaaaaa</p>"},"exists":true,"sourceAsBytesRef":{"childResources":[]},"fragment":false}


5.35. Spring boot with Apache Hive

5.35.1. Maven

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-hadoop</artifactId>
            <version>2.5.0.RELEASE</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.apache.hive/hive-jdbc -->
        <dependency>
            <groupId>org.apache.hive</groupId>
            <artifactId>hive-jdbc</artifactId>
            <version>2.3.0</version>
            <exclusions>
                <exclusion>
                    <groupId>org.eclipse.jetty.aggregate</groupId>
                    <artifactId>*</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.apache.tomcat</groupId>
            <artifactId>tomcat-jdbc</artifactId>
            <version>8.5.20</version>
        </dependency>

5.35.2. application.properties

hive 数据源配置项

hive.url=jdbc:hive2://172.16.0.10:10000/default
hive.driver-class-name=org.apache.hive.jdbc.HiveDriver
hive.username=hadoop
hive.password=

用户名是需要具有 hdfs 写入权限，密码可以不用写

如果使用 yaml 格式 application.yml 配置如下

hive:
  url: jdbc:hive2://172.16.0.10:10000/default
  driver-class-name: org.apache.hive.jdbc.HiveDriver
  type: com.alibaba.druid.pool.DruidDataSource
  username: hive
  password: hive

5.35.3. Configuration

package cn.netkiller.config;

import org.apache.tomcat.jdbc.pool.DataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.jdbc.core.JdbcTemplate;

@Configuration
public class HiveConfig {
    private static final Logger logger = LoggerFactory.getLogger(HiveConfig.class);

    @Autowired
    private Environment env;

    @Bean(name = "hiveJdbcDataSource")
    @Qualifier("hiveJdbcDataSource")
    public DataSource dataSource() {
        DataSource dataSource = new DataSource();
        dataSource.setUrl(env.getProperty("hive.url"));
        dataSource.setDriverClassName(env.getProperty("hive.driver-class-name"));
        dataSource.setUsername(env.getProperty("hive.username"));
        dataSource.setPassword(env.getProperty("hive.password"));
        logger.debug("Hive DataSource");
        return dataSource;
    }

    @Bean(name = "hiveJdbcTemplate")
    public JdbcTemplate hiveJdbcTemplate(@Qualifier("hiveJdbcDataSource") DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }
}

你也可以使用 DruidDataSource

package cn.netkiller.api.config;

@Configuration
public class HiveDataSource {

    @Autowired
    private Environment env;

    @Bean(name = "hiveJdbcDataSource")
    @Qualifier("hiveJdbcDataSource")
    public DataSource dataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setUrl(env.getProperty("hive.url"));
        dataSource.setDriverClassName(env.getProperty("hive.driver-class-name"));
        dataSource.setUsername(env.getProperty("hive.username"));
        dataSource.setPassword(env.getProperty("hive.password"));
        return dataSource;
    }

    @Bean(name = "hiveJdbcTemplate")
    public JdbcTemplate hiveJdbcTemplate(@Qualifier("hiveJdbcDataSource") DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }

}

5.35.4. CURD 操作实例

Hive 数据库的增删插改操作与其他数据库没有什么不同。

package cn.netkiller.web;

import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

@Controller
@RequestMapping("/hive")
public class HiveController {
    private static final Logger logger = LoggerFactory.getLogger(HiveController.class);

    @Autowired
    @Qualifier("hiveJdbcTemplate")
    private JdbcTemplate hiveJdbcTemplate;

    @RequestMapping("/create")
    public ModelAndView create() {

        StringBuffer sql = new StringBuffer("create table IF NOT EXISTS ");
        sql.append("HIVE_TEST");
        sql.append("(KEY INT, VALUE STRING)");
        sql.append("PARTITIONED BY (CTIME DATE)"); // 分区存储
        sql.append("ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t' LINES TERMINATED BY '\n' "); // 定义分隔符
        sql.append("STORED AS TEXTFILE"); // 作为文本存储

        logger.info(sql.toString());
        hiveJdbcTemplate.execute(sql.toString());

        return new ModelAndView("index");

    }

    @RequestMapping("/insert")
    public String insert() {
        hiveJdbcTemplate.execute("insert into hive_test(key, value) values('Neo','Chen')");
        return "Done";
    }

    @RequestMapping("/select")
    public String select() {
        String sql = "select * from HIVE_TEST";
        List<Map<String, Object>> rows = hiveJdbcTemplate.queryForList(sql);
        Iterator<Map<String, Object>> it = rows.iterator();
        while (it.hasNext()) {
            Map<String, Object> row = it.next();
            System.out.println(String.format("%s\t%s", row.get("key"), row.get("value")));
        }
        return "Done";
    }

    @RequestMapping("/delete")
    public String delete() {
        StringBuffer sql = new StringBuffer("DROP TABLE IF EXISTS ");
        sql.append("HIVE_TEST");
        logger.info(sql.toString());
        hiveJdbcTemplate.execute(sql.toString());
        return "Done";
    }
}


5.36. Spring boot with Phoenix

5.36.1. Maven

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jdbc</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.data</groupId>
        <artifactId>spring-data-hadoop</artifactId>
        <version>2.4.0.RELEASE</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.apache.phoenix/phoenix-queryserver-client -->
    <dependency>
        <groupId>org.apache.phoenix</groupId>
        <artifactId>phoenix-queryserver-client</artifactId>
        <version>4.12.0-HBase-1.3</version>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.1.0</version>
    </dependency>
</dependencies>

5.36.2. application.properties

phoenix 数据源配置项

phoenix:
  enable: true
  url: jdbc:phoenix:172.16.0.20
  type: com.alibaba.druid.pool.DruidDataSource
  driver-class-name: org.apache.phoenix.jdbc.PhoenixDriver
  username: //phoenix的用户名默认为空
  password: //phoenix的密码默认为空
  default-auto-commit: true

5.36.3. Configuration

package cn.netkiller.api.config;

@Configuration
public class PhoenixDataSource {

    @Autowired
    private Environment env;

    @Bean(name = "phoenixJdbcDataSource")
    @Qualifier("phoenixJdbcDataSource")
    public DataSource dataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setUrl(env.getProperty("phoenix.url"));
        dataSource.setDriverClassName(env.getProperty("phoenix.driver-class-name"));
        dataSource.setUsername(env.getProperty("phoenix.username"));
        dataSource.setPassword(env.getProperty("phoenix.password"));
        dataSource.setDefaultAutoCommit(Boolean.valueOf(env.getProperty("phoenix.default-auto-commit")));
        return dataSource;
    }

     @Bean(name = "phoenixJdbcTemplate")
     public JdbcTemplate phoenixJdbcTemplate(@Qualifier("phoenixJdbcDataSource") DataSource dataSource) {
          return new JdbcTemplate(dataSource);
     }
}


5.37. Spring boot with Data restful

spring-boot-starter-data-rest 能够提供将 Repository, CrudRepository 等接口直接提供给用户访问

5.37.1. Maven

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


5.38. Spring boot with ELK(Elasticsearch + Logstash + Kibana)

将 Spring boot 日志写入 ELK 有多种实现方式，这里仅提供三种方案：

  1. Spring boot -> logback -> Tcp/IP -> logstash -> elasticsearch

    这种方式实现非常方便不需要而外包或者软件

  2. Spring boot -> logback -> Redis -> logstash -> elasticsearch

    利用 Redis 提供的发布订阅功能将日志投递到 elasticsearch

  3. Spring boot -> logback -> Kafka -> logstash -> elasticsearch

    Kafka 方法适合大数据的情况。

5.38.1. TCP 方案

logstash 配置

input {
  tcp {
    host => "172.16.1.16"
    port => 9250
    mode => "server"
    tags => ["tags"]
    codec => json_lines  //可能需要更新logstash插件
  }
}

output {
 stdout{codec =>rubydebug}
  elasticsearch {
   hosts => ["localhost:9200"]  //这块配置需要带端口号
    flush_size => 1000

  }
}

Spring boot logback.xml 配置

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <property resource="properties/logback-variables.properties" />

    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder charset="UTF-8">
            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n
            </pattern>
        </encoder>
    </appender>
    <appender name="LOGSTASH" class="net.logstash.logback.appender.LogstashTcpSocketAppender">
        <destination>172.16.1.16:9250</destination>
        <encoder charset="UTF-8" class="net.logstash.logback.encoder.LogstashEncoder" />
    </appender>

    <!--<appender name="async" class="ch.qos.logback.classic.AsyncAppender">-->
        <!--<appender-ref ref="stash" />-->
    <!--</appender>-->

    <root level="info">                    <!-- 设置日志级别 -->
        <appender-ref ref="STDOUT" />
        <appender-ref ref="LOGSTASH" />
    </root>
</configuration>

5.38.2. Redis 方案

https://github.com/kmtong/logback-redis-appender

Maven pom.xml 增加 Logback Redis 依赖

<!-- https://mvnrepository.com/artifact/com.cwbase/logback-redis-appender -->
<dependency>
    <groupId>com.cwbase</groupId>
    <artifactId>logback-redis-appender</artifactId>
    <version>1.1.5</version>
</dependency>

Spring boot logback.xml 配置

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <include resource="org/springframework/boot/logging/logback/defaults.xml" />
    <include resource="org/springframework/boot/logging/logback/file-appender.xml" />
    <property name="type.name" value="test" />
    <appender name="LOGSTASH" class="com.cwbase.logback.RedisAppender">
        <source>spring-application</source>
        <type>${type.name}</type>
        <host>localhost</host>
        <key>logstash:redis</key>
        <tags>test-2</tags>
        <mdc>true</mdc>
        <location>true</location>
        <callerStackIndex>0</callerStackIndex>
        <!--additionalField添加附加字段 用于head插件显示 -->
        <additionalField>
            <key>MyKey</key>
            <value>MyValue</value>
        </additionalField>
        <additionalField>
            <key>MySecondKey</key>
            <value>MyOtherValue</value>
        </additionalField>
    </appender>
    <root level="INFO">
        <appender-ref ref="FILE" />
        <appender-ref ref="LOGSTASH" />
    </root>
</configuration>

logstash 配置

input {
    redis {
        host => 'localhost'
        data_type => 'list'
        port => "6379"
        key => 'logstash:redis' #自定义
        type => 'redis-input'   #自定义
    }
}
output {
    elasticsearch {
        host => "localhost"
        codec => "json"
        protocol => "http"
    }
}

5.38.3. Kafka 方案

5.38.4. Other


5.39. Springboot with Ethereum (web3j)

5.39.1. Maven

        <dependency>
            <groupId>org.web3j</groupId>
            <artifactId>web3j-spring-boot-starter</artifactId>
            <version>1.6.0</version>
        </dependency>

5.39.2. application.properties

web3j.client-address=https://ropsten.infura.io/CsS9shwaAab0z7B4LP2d
web3j.admin-client=true

5.39.3. TestRestController

package cn.netkiller.wallet.restful;

import java.io.IOException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.web3j.protocol.Web3j;
import org.web3j.protocol.core.methods.response.Web3ClientVersion;

@RestController
public class TestRestController {
    private static final Logger logger = LoggerFactory.getLogger(TestRestController.class);

    @Autowired
    private Web3j web3j;

    public TestRestController() {
        // TODO Auto-generated constructor stub
    }

    @GetMapping("/version")
    public String version() throws IOException {
        Web3ClientVersion web3ClientVersion = web3j.web3ClientVersion().send();
        String clientVersion = web3ClientVersion.getWeb3ClientVersion();
        logger.info(clientVersion);
        return clientVersion;
    }

}

5.39.4. 测试

neo@MacBook-Pro ~ % curl http://localhost:8080/version
Geth/v1.8.3-stable/linux-amd64/go1.10


5.40. 异步执行

5.40.1. 最简单的配置

@SpringBootApplication
@EnableAsync
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

@Component
public class Task {

    @Async
    public void doTaskOne() throws Exception {
        // 业务逻辑
    }

    @Async
    public void doTaskTwo() throws Exception {
        // 业务逻辑
    }

    @Async
    public void doTaskThree() throws Exception {
        // 业务逻辑
    }

}

设置线程池，并且运行完成后推出

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.Executor;

@SpringBootApplication
@EnableAsync
public class Application {

    public static void main(String[] args) {
        // close the application context to shut down the custom ExecutorService
        SpringApplication.run(Application.class, args).close();
    }

    @Bean
    public Executor asyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(2);
        executor.setMaxPoolSize(2);
        executor.setQueueCapacity(500);
        executor.setThreadNamePrefix("Netkiller -");
        executor.initialize();
        return executor;
    }

}

5.40.2.

package cn.netkiller.wallet.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;

@Configuration
@EnableAsync
public class ExecutorConfig {
    /** Set the ThreadPoolExecutor's core pool size. */
    private int corePoolSize = 10;
    /** Set the ThreadPoolExecutor's maximum pool size. */
    private int maxPoolSize = 200;
    /** Set the capacity for the ThreadPoolExecutor's BlockingQueue. */
    private int queueCapacity = 10;

    @Bean
    public Executor OneAsync() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(corePoolSize);
        executor.setMaxPoolSize(maxPoolSize);
        executor.setQueueCapacity(queueCapacity);
        executor.setThreadNamePrefix("MySimpleExecutor-");
        executor.initialize();
        return executor;
    }

    @Bean
    public Executor TwoAsync() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(corePoolSize);
        executor.setMaxPoolSize(maxPoolSize);
        executor.setQueueCapacity(queueCapacity);
        executor.setThreadNamePrefix("MyExecutor-");

        // rejection-policy：当pool已经达到max size的时候，如何处理新任务
        // CALLER_RUNS：不在新线程中执行任务，而是有调用者所在的线程来执行
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.initialize();
        return executor;
    }

}

@Service
public class DemoAsyncServiceImpl implements DemoAsyncService {

    public static Random random =new Random();

    @Async("OneAsync")
    public Future<String> doTaskOne() throws Exception {
        System.out.println("开始做任务一");
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        System.out.println("完成任务一，耗时：" + (end - start) + "毫秒");
        return new AsyncResult<>("任务一完成");
    }

    @Async("TwoAsync")
    public Future<String> doTaskTwo() throws Exception {
        System.out.println("开始做任务二");
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        System.out.println("完成任务二，耗时：" + (end - start) + "毫秒");
        return new AsyncResult<>("任务二完成");
    }

    @Async
    public Future<String> doTaskThree() throws Exception {
        System.out.println("开始做任务三");
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        System.out.println("完成任务三，耗时：" + (end - start) + "毫秒");
        return new AsyncResult<>("任务三完成");
    }

}


5.41. Spring boot with export csv.

    @GetMapping("/export")
    public void export(HttpServletResponse response) throws IOException {
        response.setContentType("application/csv");
        // response.setContentType("application/csv;charset=gb18030");
        response.setHeader("Content-Disposition", "attachment; filename=\"file.csv\"");

        BufferedWriter writer = new BufferedWriter(response.getWriter());

        // 需要写入 utf8bom 头否则会出现中文乱码
        // byte[] uft8bom = { (byte) 0xef, (byte) 0xbb, (byte) 0xbf };
        String bom = new String(new byte[] { (byte) 0xEF, (byte) 0xBB, (byte) 0xBF });
        writer.write(bom);
        writer.write("A,B,C");
        writer.newLine();
        tableRepository.findAll().forEach(table -> {
            try {
                String tmp = String.format("%s,%s,%s", table.getId(), table.getMethod(), table.getMoney());
                writer.write(tmp);
                writer.newLine();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }

        });

        writer.flush();
        writer.close();
    }


5.42. Spring boot with lombok

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>


第 6 章 Spring MVC

目录

6.1. @EnableWebMvc
6.1.1. CORS 跨域请求
6.1.2. Spring MVC CORS with WebMvcConfigurerAdapter
6.2. @Controller
6.2.1. @RequestMapping
6.2.1.1. @RequestMapping("/")
6.2.1.2. 映射多个URL
6.2.1.3. 匹配通配符
6.2.1.4. headers
6.2.1.5. @GetMapping
6.2.1.6. @PostMapping
6.2.2. RequestMapping with Path Variables - @PathVariable
6.2.2.1. URL 参数传递
6.2.2.2. 默认值
6.2.2.3. URL 传递 Date 类型
6.2.2.4. 处理特殊字符
6.2.2.5. @PathVariable 注意事项
6.2.3. RequestMapping with Request Parameters - @RequestParam
6.2.3.1. HTTP GET
6.2.3.2. HTTP POST
6.2.3.3. @RequestParam 传递特殊字符串
6.2.3.4. 传递日期参数
6.2.3.5. 上传文件
6.2.3.6. @RequestParam - POST 数组
6.2.4. @RequestBody
6.2.4.1. @RequestBody 传递 List
6.2.4.2. 传递 Map 数据
6.2.5. @ModelAttribute
6.2.6. @CookieValue - 获取 Cookie 值
6.2.7. @RequestHeader - 获取 HTTP Header 信息
6.2.7.1. @RequestHeader 从 Http 头中获取变量
6.2.8. @SessionAttributes
6.2.9. @ResponseBody
6.2.9.1. 直接返回HTML
6.2.10. @CrossOrigin
6.2.10.1. maxAge
6.2.11. @SessionAttributes
6.2.12. ModelAndView
6.2.12.1. 变量传递
6.2.12.2. ModelMap 传递多个变量
6.2.12.3. redirect
6.2.12.4. ArrayList
6.2.12.5. HashMap
6.2.12.6. 传递对象
6.2.12.7.
6.2.13. HttpServletRequest / HttpServletResponse
6.2.13.1. HttpServletResponse
6.2.13.2. HttpServletRequest
6.3. @RestController
6.3.1. 返回实体
6.3.2. JSON
6.3.3. 处理原始 RAW JSON 数据
6.3.4. 返回 JSON 对象 NULL 专为 "" 字符串
6.3.5. XML
6.3.6. 兼容传统 json 接口
6.3.7. @PageableDefault 分页
6.3.8. 上传文件
6.4. View
6.4.1. Using Spring’s form tag library
6.4.1.1. css
6.4.1.2. cssClass
6.4.2. Thymeleaf
6.4.2.1. Maven pom.xml
6.4.2.2. Spring 配置
6.4.2.3. controller
6.4.2.4. HTML5 Template
6.4.2.5. thymeleaf 渲染表格
6.4.2.6. URL 链接
6.4.2.7. 拆分字符串
6.4.2.8. 日期格式化
6.4.3. FreeMarker
6.5. Service
6.5.1. Application
6.5.2. 定义接口
6.5.3. 实现接口
6.5.4. 调用 Service
6.6. i18n 国际化
6.6.1. 在 appliction.properties 中配置启用 i18n
6.6.2. 创建语言包文件
6.6.3. 控制器重引用语言包
6.6.4. 参数传递
6.7. FAQ
6.7.1. o.s.web.servlet.PageNotFound
6.7.2. HTTP Status 500 - Handler processing failed; nested exception is java.lang.NoClassDefFoundError: javax/servlet/jsp/jstl/core/Config
6.7.3. 同时使用 Thymeleaf 与 JSP
6.7.4. 排除静态内容
6.7.5. HTTP Status 406
6.7.6. Caused by: java.lang.IllegalArgumentException: Not a managed type: class common.domain.Article
6.7.7. {"error":"unauthorized","error_description":"Full authentication is required to access this resource"}

Spring MVC 有两种启动模式，一种是传统Tomcat，需要配置很多XML文件。另一种方式是采用 Spring Boot 需要些一个Java程序，不需要写xml文件，这个程序会帮助你处理启动所需的一切，并且采用嵌入方式启动 Tomcat 或者 Jetty.

两种方式各有优缺点，Tomcat 方式配置繁琐，但是可以使用虚拟机，同一个IP地址使用不同域名访问，出现不同的内容。而Spring Boot一个应用一个容器一个端口，比不得不通过端口来区分应用。

6.1. @EnableWebMvc

package cn.netkiller.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.view.InternalResourceViewResolver;

@Configuration
@EnableWebMvc
public class WebMvcConfig extends WebMvcConfigurerAdapter {

    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }

    @Bean
    public InternalResourceViewResolver viewResolver() {
        InternalResourceViewResolver resolver = new InternalResourceViewResolver();
        resolver.setPrefix("WEB-INF/jsp/");
        resolver.setSuffix(".jsp");
        return resolver;
    }

}

6.1.1. CORS 跨域请求

@Configuration
public class CorsConfiguration
{
    @Bean
    public WebMvcConfigurer corsConfigurer()
    {
        return new WebMvcConfigurerAdapter() {
            @Override
            public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping("/**");
            }
        };
    }
}

     @Bean
    public WebMvcConfigurer corsConfigurer() {
        return new WebMvcConfigurerAdapter() {
            @Override
            public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping("/**").allowedOrigins("*");
            }
        };
    }

6.1.2. Spring MVC CORS with WebMvcConfigurerAdapter

@Configuration
@EnableWebMvc
public class CorsConfiguration extends WebMvcConfigurerAdapter
{
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**").allowedMethods("GET", "POST");
    }
}

@Configuration
@EnableWebMvc
public class AppConfig extends WebMvcConfigurerAdapter {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
      registry.addMapping("/info/**")
             .allowedOrigins("http://localhost:8080", "http://localhost:8000")
          .allowedMethods("POST", "GET",  "PUT", "OPTIONS", "DELETE")
          .allowedHeaders("X-Auth-Token", "Content-Type")
          .exposedHeaders("custom-header1", "custom-header2")
          .allowCredentials(false)
          .maxAge(4800);
    }
}


6.2. @Controller

package cn.netkiller.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class Welcome {

    @RequestMapping("/welcome")
    public ModelAndView helloWorld() {
        String message = "Helloworld!!!";
        return new ModelAndView("welcome", "message", message);
    }
}

6.2.1. @RequestMapping

@RequestMapping("/welcome")

@RequestMapping(value = "/list", method =
RequestMethod.GET)

6.2.1.1. @RequestMapping("/")

package com.cf88.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
@RequestMapping("/")
public class HelloController {

    @RequestMapping(value = "/{name}", method = RequestMethod.GET)
    public String getMovie(@PathVariable String name, ModelMap model) {
        model.addAttribute("name", name);
        return "hello";
    }

}

6.2.1.2. 映射多个URL

@RequestMapping({ "/news/zh-cn", "/news/zh-tw" })
@ResponseBody
public String getNewsByPath() {
    return "Hello";
}

6.2.1.3. 匹配通配符

@Controller
@RequestMapping("/test/*")

public class TestController {

    @RequestMapping
    public String default() {
        return "OK";
    }
}

6.2.1.4. headers

@RequestMapping(value = "/news/json", method = GET, headers = "Accept=application/json")
@ResponseBody
public String getFoosAsJsonFromBrowser() {
    return "{...}";
}

curl -H "Accept:application/json,text/html"
http://localhost:8080/spring/news/json.html

6.2.1.5. @GetMapping

@GetMapping 等效与 @RequestMapping

@RequestMapping(value = "/news/list", method = GET)

范例

import org.springframework.web.bind.annotation.GetMapping;

    @GetMapping("/finance/list")
    public String financeList() {
        return financeService.financeList();
    }

6.2.1.6. @PostMapping

@GetMapping 等效与 @RequestMapping

@RequestMapping(value = "/news/list", method = RequestMethod.POST)

范例

import org.springframework.web.bind.annotation.PostMapping;

    @PostMapping("/finance/list")
    public String financeList() {
        return financeService.financeList();
    }

Content-Type: multipart/form-data

@PostMapping(path = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)

6.2.2. RequestMapping with Path Variables - @PathVariable

PATHINFO 变量可通过 @Pathvariable注解绑定它传过来的值到方法的参数上。

6.2.2.1. URL 参数传递

需求，我们需要通过URL传递参数，所传递的值是分类ID与文章ID，例如 /news/1.html, /news/1/1.html。

package cn.netkiller.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class Pathinfo {
    @RequestMapping("/pathinfo/{id}")
    public ModelAndView urlTestId(@PathVariable String id) {

        return new ModelAndView("pathinfo/param", "id", id);
    }

    @RequestMapping("/pathinfo/{cid}/{id}")
    public ModelAndView urlTestId(@PathVariable String cid, @PathVariable String id) {

        ModelMap model = new ModelMap();

        model.addAttribute("cid", cid);
        model.addAttribute("id", id);

        return new ModelAndView("pathinfo/param", model);
    }
}

jsp测试文件

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
${ cid } <br>
${ id } <br>
</body>
</html>

6.2.2.2. 默认值

required 设置参数不是必须的

@PathVariable(required = false) String id

设置多个映射

@RequestMapping(value = {"/organization/{pageNumber}", "/organization"} , method = RequestMethod.GET)
public String list(@PathVariable(required = false) Integer pageNumber, ModelMap modelMap){
...
}

6.2.2.3. URL 传递 Date 类型

http://localhost:7000/history/2016-09-28%2000:00:00/

    @RequestMapping("/history/{datetime}")
    public String history(@PathVariable @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss") Date datetime) throws Exception {

        System.out.println(datetime)

        return null;
    }

6.2.2.4. 处理特殊字符

http://www.netkiller.cn/release/1.0.1

@RequestMapping(value =	"/release/{version:[a-zA-Z0-9\\.]+}", method = RequestMethod.GET)
public @ResponseBody
    String release(@PathVariable String version) {
    log.debug("version: ", version);
    return version;
}

http://www.netkiller.cn/release/1.0.1/other

@RequestMapping(value="/release/{version:.+}",method=RequestMethod.GET)
public void download(HttpSession
    session,@PathVariable("version")String version){
    return version;
}

6.2.2.5. @PathVariable 注意事项

@PathVariable 参数传统需要注意，参数中不能携带 “/”,斜杠会被视为目录。

    @RequestMapping("/PathVariable/{code}.html")
    @ResponseBody
    public String urlTestId(@PathVariable String code) {
        return code;
    }

6.2.3. RequestMapping with Request Parameters - @RequestParam

@RequestParam 用来处理 HTTP GET/POST 请求的变量

            import
            org.springframework.web.bind.annotation.RequestParam;

6.2.3.1. HTTP GET

    @RequestMapping("/request/param")
    @ResponseBody
    public String getBarBySimplePathWithRequestParam(@RequestParam("id") long id) {
        return "Get a specific Bar with id=" + id;
    }

                http://localhost:8080/Spring/request/param.html?id=100

6.2.3.2. HTTP POST

package cn.netkiller.controller;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class Http {

    @RequestMapping("/http/form")
    public ModelAndView createCustomer(){
        ModelMap model = new ModelMap();

        model.addAttribute("email", "netkiller@msn.com");
        model.addAttribute("phone", "13113668890");

        return new ModelAndView("http/form", model);
    }

    @RequestMapping(value= "/http/post", method = RequestMethod.POST)
    public ModelAndView saveCustomer(HttpServletRequest request,
            @RequestParam(value="Email", required=false) String email,
            @RequestParam(value="Password", required=false) String password,
            @RequestParam(value="Phone", required=false) String phone){

        ModelMap model = new ModelMap();

        model.addAttribute("email", email);
        model.addAttribute("password", password);
        model.addAttribute("phone", phone);

        return new ModelAndView("http/post", model);
    }

}

http/form.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>

    <form method="POST"
        action="http://localhost:8080/Spring/http/post.html" id="Register"
        name="Register">
        Email: <input class="register" type="text" id="Email" name="Email" value="${email}" /> <br />
        Password： <input class="register" type="password" id="Password" name="Password" value="" /><br />
        Phone： <input class="register" type="text" id="Phone" name="Phone" value="${phone}" /> <br />
        <input type="submit" id="btnRegister" name="btnRegister" value="Register" style="cursor: pointer" />
    </form>

</body>
</html>

http/post.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
    ${email}<br>
    ${password}	<br>
    ${phone} <br>
</body>
</html>

6.2.3.3. @RequestParam 传递特殊字符串

URL 中 “+” 有特殊意义，表示空格。

如果 @RequestParam 传递参数含有空格可以这样处理。

    @RequestMapping("/RequestParam")
    @ResponseBody
    public String query(@RequestParam("code") String code) {

        return code.replace(" ", "+");

    }

6.2.3.4. 传递日期参数

    @RequestMapping("/range")
    public ModelAndView range(@RequestParam("beginDate") @DateTimeFormat(pattern = "yyyy-MM-dd") Date beginDate, @RequestParam("endDate") @DateTimeFormat(pattern = "yyyy-MM-dd") Date endDate) {
        log.info("===== Begin ===== {}", beginDate);

        // 你的逻辑

        log.info("===== End ===== {}", endDate);
        return new ModelAndView("activity/index", "message", "操作成功");
    }

6.2.3.5. 上传文件

package cn.netkiller.restful;

import java.io.IOException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.multipart.MultipartFile;

@RestController
@RequestMapping("/upload")
public class UploadRestController {

    private static final Logger logger = LoggerFactory.getLogger(UploadRestController.class);

    public UploadRestController() {
        // TODO Auto-generated constructor stub
    }

    @PostMapping("/add")
    public String fileUpload(@RequestParam("file") MultipartFile multipartFile) throws IOException {

        String name = multipartFile.getOriginalFilename();
        System.out.println("File name: " + name);
        // todo save to a file via multipartFile.getInputStream()
        byte[] bytes = multipartFile.getBytes();
        System.out.println("File uploaded content:\n" + new String(bytes));
        return "file uploaded";
    }

}

操作演示，首先创建一个文件

echo "Helloworld!!!" > hello.txt

上传该文件

neo@MacBook-Pro /tmp % curl "http://localhost:8080/upload/add" \
    -X POST \
    -H "Content-Type: multipart/form-data" \
    -F file=@"hello.txt"

file uploaded

6.2.3.6. @RequestParam - POST 数组

HTTP 头

picture[]: gather/293a93baa02cb18a840631bac1f9eeb20b7d436f.jpeg
picture[]: gather/be7572e4df527b4389d605766ea65aafcf2d822a.jpg

    @PostMapping("/save")
    public String save(@RequestParam(value = "picture[]", required = true) String[] picture) {
        return String.join(",", picture);
    }

6.2.4. @RequestBody

处理 raw 原始数据，例如提交的时 application/json, application/xml等

@RequestMapping(value = "/something", method = RequestMethod.PUT)
public void handle(@RequestBody String body, Writer writer) throws IOException {
    writer.write(body);
}

6.2.4.1. @RequestBody 传递 List

package cn.netkiller.api.restful;

import java.util.List;

import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class TestRestController {

    @RequestMapping(value = "/test/list/{siteId}", method = RequestMethod.POST)
    public List<String> ping(@PathVariable("siteId") int siteId, @RequestBody List<String> tags) {
        System.out.println(String.format("%d, %s", siteId, tags));
        return (tags);
    }

}

$ curl -H "Content-Type: application/json" -X POST -d '["Neo","Netkiller"]' http://localhost:8440/test/list/22.json

["Neo","Netkiller"]

6.2.4.2. 传递 Map 数据

    @PostMapping("/finance/list")
    public String financeList(@RequestBody Map<String,String> map) {
        return financeService.financeList(map);
    }

% curl -H "Content-Type: application/json" -X POST -d '{"date":"2017-11-08"}' http://localhost:8440/finance/list.json

6.2.5. @ModelAttribute

@ModelAttribute 处理 HTML FORM POST 提交

package cn.netkiller.pojo;

import java.util.List;

public class Deploy {

    private String group;
    private String envionment;
    private String project;
    private List<String> arguments;
    public Deploy() {
        // TODO Auto-generated constructor stub
    }
    // Getters & Setters
}

    @RequestMapping(value="/deploy/post",  method = RequestMethod.POST)
    public ModelAndView post(@ModelAttribute("deploy")Deploy deploy, BindingResult result) {
        if (result.hasErrors()) {
            System.out.println(result.toString());
        }
        System.out.println(deploy.toString());
        return new ModelAndView("output").addObject("output", deploy.toString());
    }

6.2.6. @CookieValue - 获取 Cookie 值

@RequestMapping("/sessionInfo")
public void sessionInfo(@CookieValue("JSESSIONID") String cookie)  {

  //...

}

6.2.7. @RequestHeader - 获取 HTTP Header 信息

@RequestMapping("/displayHeaderInfo")
public void displayHeaderInfo(@RequestHeader("Accept-Encoding") String encoding,
                              @RequestHeader("Keep-Alive") long keepAlive)  {

  //...

}

获取用户当前语言

    @GetMapping("/lang")
    public String language(@RequestHeader("Accept-Language") String locale ) {
        System.out.println(locale);
        return locale;
    }

下面代码可以获得相同效果

    @GetMapping("/lang")
    public String language(Locale locale) {
        System.out.println(locale);
        return locale;
    }

    @GetMapping("/lang")
    public String language() {
        String locale = LocaleContextHolder.getLocale().toString();
        System.out.println(locale);
        return locale;
    }

6.2.7.1. @RequestHeader 从 Http 头中获取变量

    @PostMapping(value = "/token")
    public TokenResponse token(@RequestParam String symbol, @RequestHeader String token) {

            TokenResponse tokenResponse = walletService.getTokenBySymbol(symbol);

            return tokenResponse;
    }

6.2.8. @SessionAttributes

@SessionAttributes: 该注解用来绑定HttpSession中的attribute对象的值，便于在方法中的参数里使用。 该注解有value、types两个属性，可以通过名字和类型指定要使用的attribute 对象；

@Controller
@RequestMapping("/editProfile")
@SessionAttributes("profile")
public class ProfileForm {
    // ...
}

6.2.9. @ResponseBody

import org.springframework.web.bind.annotation.ResponseBody;

6.2.9.1. 直接返回HTML

package cn.netkiller.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class Pathinfo {

    @RequestMapping(value = "/news/shenzhen/{numericId:[\\d]+}")
    @ResponseBody
    public String getNewsWithPathVariable(@PathVariable final long numericId) {
        return "Get a specific Bar with id=" + numericId;
    }

}

6.2.10. @CrossOrigin

    @CrossOrigin(origins = "http://localhost:9000")
    @GetMapping("/greeting")
    public Greeting	greeting(@RequestParam(required=false, defaultValue="World") String name) {
        System.out.println("==== in greeting ====");
        return new Greeting(counter.incrementAndGet(), String.format(template,name));
    }

@CrossOrigin(origins = "*", allowedHeaders = "*")
@RestController
public class HomeController
{
    @GetMapping(path="/")
    public String home() {
        return "home";
    }
}

全局放行所有轻松，方法权限单独控制

@RestController
@CrossOrigin(origins = "*", allowedHeaders = "*")
public class HomeController
{
    @CrossOrigin(origins = "http://example.com")
    @GetMapping(path="/")
    public String home() {
        return "home";
    }
}

6.2.10.1. maxAge

@CrossOrigin(origins = {"http://localhost:8585"}, maxAge = 4800, allowCredentials = "false")
@RestController
@RequestMapping("/info")
public class PersonController {
    @Autowired
    private PersonService service;
    @CrossOrigin(origins = {"http://localhost:8080"}, maxAge = 6000)
    @RequestMapping("home")
    public List<Person> show() {
        List<Person> list = service.getAllPerson();
        return list;
     }
}

6.2.11. @SessionAttributes

@Controller
@SessionAttributes("myRequestObject")
public class MyController {
  ...
}

6.2.12. ModelAndView

6.2.12.1. 变量传递

@RequestMapping("/testString")
public ModelAndView	helloWorld() {
    String message = "Helloworld!!!";
    return new
    ModelAndView("welcome", "message", message);
}

public ModelAndView handleRequestInternal() {

    ModelAndView mav = new ModelAndView("test");//
    实例化一个VIew的ModelAndView实例
    mav.addObject("variable", "Hello World!");//
    添加一个带名的model对象
    return mav;
}

6.2.12.2. ModelMap 传递多个变量

传递多个字符串

@RequestMapping("/testModelMap")
public ModelAndView	testModelMap() {
    ModelMap model = new ModelMap();

    model.addAttribute("username", "Neo");
    model.addAttribute("password", "Netkiller");

    return new ModelAndView("test/modelmap", model);
}

推荐使用ModelMap

    @RequestMapping("/testMapString")
    public ModelAndView testMapString() {

        Map<String,String> data = new HashMap<String,String>();
        data.put("username","Neo");
        data.put("password","Netkiller");
        return new ModelAndView("test/modelmap",data);

    }

<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
    pageEncoding="ISO-8859-1"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Insert title here</title>
</head>
<body>
${username}<br>
${password}<br>
</body>
</html>

6.2.12.3. redirect

    @RequestMapping("/testRedirect")
    public ModelAndView testRedirect(){
           RedirectView view = new RedirectView("testMapString.html");
           return new ModelAndView(view);
    }

6.2.12.4. ArrayList

    @RequestMapping(value = "testList")
    public ModelAndView testList() {
        ModelAndView mav = new ModelAndView();
        mav.setViewName("/test/list");

        // List
        List<String> list = new ArrayList<String>();
        list.add("java");
        list.add("c++");
        list.add("oracle");
        mav.addObject("bookList", list);

        return mav;
    }

<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
    pageEncoding="ISO-8859-1"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Insert title here</title>
</head>
<body>
    ${bookList}
    <br>

    <c:forEach items="${bookList}" var="node">
        <c:out value="${node}"></c:out><br>
    </c:forEach>

</body>
</html>

6.2.12.5. HashMap

    @RequestMapping("/testMap")
    public ModelAndView testMap() {
        ModelAndView mav = new ModelAndView();
        mav.setViewName("test/map"); // 返回的文件名

        // Map
        Map<String, String> map = new HashMap<String, String>();
        map.put("Java", "http://www.netkiller.cn/java");
        map.put("PHP", "http://www.netkiller.cn/php");
        map.put("Home", "http://www.netkiller.cn");
        mav.addObject("channel", map);

        return mav;
    }

<%@ page language="java" contentType="text/html; charset=utf-8"
    pageEncoding="utf-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Insert title here</title>
</head>
<body>
    <c:forEach items="${channel}" var="node">
        <a href="<c:out value="${node.value}"></c:out>"><c:out value="${node.key}"></c:out></a>
        <br/>
    </c:forEach>
</body>
</html>

6.2.12.6. 传递对象

    @RequestMapping("/testObject")
    public ModelAndView testObject() {
        ModelMap model = new ModelMap();

        User user = new User("neo", "passw0rd");
        model.addAttribute("user", user);
        return new ModelAndView("test/object", model);

    }

package cn.netkiller;

public class User {
    public String username;
    public String password;
    public User(String username, String password){
        this.username = username;
        this.password = password;
    }
    public String getUsername(){
        return this.username;
    }
    public String getPassword(){
        return this.password;
    }
}

<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
    pageEncoding="ISO-8859-1"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Insert title here</title>
</head>
<body>
Username: ${user.username}<br>
Password: ${user.password}<br>
</body>
</html>

6.2.12.7.

6.2.13. HttpServletRequest / HttpServletResponse

6.2.13.1. HttpServletResponse

HttpServletResponse 实例

package cn.netkiller.api.rest;

import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.WriterException;
import com.google.zxing.common.BitMatrix;
import api.util.MatrixToImageWriter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Hashtable;

@Controller
@RequestMapping("/public/QRCode")
public class QRCodeController {
    private static final Logger log = LoggerFactory.getLogger(QRCodeController.class);

    @RequestMapping("/create/{code}" )
    @ResponseBody
    public void create(@PathVariable String code, HttpServletResponse httpServletResponse) throws WriterException, IOException {
        log.info(code);
        if (code != null && !"".equals(code)){
            ServletOutputStream stream = null;
            try {
                String text = code; 	// 二维码内容
                int width = 300; 		// 二维码图片宽度
                int height = 300; 		// 二维码图片高度
                String format = "gif";	// 二维码的图片格式

                Hashtable<EncodeHintType, String> hints = new Hashtable<EncodeHintType, String>();
                hints.put(EncodeHintType.CHARACTER_SET, "utf-8");   // 内容所使用字符集编码

                BitMatrix bitMatrix = new MultiFormatWriter().encode(text, BarcodeFormat.QR_CODE, width, height, hints);
                // 生成二维码
                stream = httpServletResponse.getOutputStream();
                MatrixToImageWriter.writeToStream(bitMatrix, format, stream);

            }catch (WriterException e) {
                e.printStackTrace();
            } finally {
                if (stream != null) {
                    stream.flush();
                    stream.close();
                }
            }
        }
    }

    @RequestMapping("show")
    @ResponseBody
    public ModelAndView show(){

        return new ModelAndView("/qrcode/qrcode");
    }
}

6.2.13.2. HttpServletRequest

package com.example.demo.controller;

import java.io.IOException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/test")
public class TestController {

    public TestController() {
        // TODO Auto-generated constructor stub
    }

    @GetMapping("/get")
    public String get(@RequestHeader String lang) throws IOException {
        System.out.println(lang);
        return lang;
    }

    @PostMapping("/post")
    public String post(@RequestHeader String lang) throws IOException {
        System.out.println(lang);
        return lang;
    }

    @GetMapping("/list")
    public Map<String, String> x(HttpServletRequest request) throws IOException {

        return getHeadersInfo(request);
    }

    private Map<String, String> getHeadersInfo(HttpServletRequest request) {

        Map<String, String> map = new HashMap<String, String>();

        Enumeration<?> headerNames = request.getHeaderNames();
        while (headerNames.hasMoreElements()) {
            String key = (String) headerNames.nextElement();
            String value = request.getHeader(key);
            map.put(key, value);
        }

        return map;
    }

}


6.3. @RestController

6.3.1. 返回实体

    @RequestMapping("/get/{id}")
    public Member getStatistics(@PathVariable long id) {
        Member statistics = memberRepostitory.findOne(id);
        if (statistics == null) {
            statistics = new Member();
        }
        return statistics;
    }

6.3.2. JSON

MediaType.APPLICATION_JSON_VALUE 执行结果反馈json数据

@RestController
@RequestMapping("/api/persons")
public class MainController {

    @RequestMapping(
            value = "/detail/{id}",
            method = RequestMethod.GET,
            produces = MediaType.APPLICATION_JSON_VALUE
            )
    public ResponseEntity<Persons> getUserDetail(@PathVariable Long id) {

        Persons user = personsRepository.findById(id);

        return new ResponseEntity<>(user, HttpStatus.OK);
    }

}

6.3.3. 处理原始 RAW JSON 数据

    @RequestMapping(value = "/create", method = RequestMethod.POST, produces = { "application/xml", "application/json" })
    public ResponseEntity<Member> create(@RequestBody Member member) {
        memberRepository.save(member);
        return new ResponseEntity<Member>(member, HttpStatus.OK);
    }

6.3.4. 返回 JSON 对象 NULL 专为 "" 字符串

package api.config;

import java.io.IOException;

import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializerProvider;

@Configuration
public class JacksonConfig {
    @Bean
    @Primary
    @ConditionalOnMissingBean(ObjectMapper.class)
    public ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) {
        ObjectMapper objectMapper = builder.createXmlMapper(false).build();
        objectMapper.getSerializerProvider().setNullValueSerializer(new JsonSerializer<Object>() {
            @Override
            public void serialize(Object o, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException, JsonProcessingException {
                jsonGenerator.writeString("");
            }
        });
        return objectMapper;
    }
}

6.3.5. XML

restful 将同时支持 json 和 xml 数据传递

package com.example.api.restful;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import com.example.api.domain.RecentRead;
import com.example.api.repository.RecentReadRepostitory;

@RestController
@RequestMapping("/restful/article")
public class ArticleRestController {

    @Autowired
    private RecentReadRepostitory recentReadRepostitory;

    @RequestMapping(value = "/recent/read/add/{memberId}/{articleId}", method = RequestMethod.GET, produces = { "application/xml", "application/json" })
    public ResponseEntity<RecentRead> recentAdd(@PathVariable long memberId, @PathVariable long articleId) {
        RecentRead recentRead = new RecentRead();
        recentRead.setMemberId(memberId);
        recentRead.setArticleId(articleId);
        recentReadRepostitory.save(recentRead);
        return new ResponseEntity<RecentRead>(recentRead, HttpStatus.OK);
    }

    @RequestMapping(value="/recent/read/list/{id}", produces = { "application/xml", "application/json" })
    public List<RecentRead> recentList(@PathVariable long id) {
        int page = 0;
        int limit = 20;
        List<RecentRead> recentRead = recentReadRepostitory.findByMemberId(id, new PageRequest(page, limit));
        return recentRead;
    }
}

6.3.6. 兼容传统 json 接口

开发中发现很多人不适应新的接口方式，有时候只能妥协，这些顽固不化的人需要这样的数据库格式

{
   "status":true,
   "reason":"登录成功",
   "code":1,
   "data":{
      "id":2,
      "name":null,
      "sex":null,
      "age":0,
      "wechat":null,
      "mobile":"13113668890",
      "picture":null,
      "ipAddress":"0:0:0:0:0:0:0:1"
   }
}

返回数据必须放在 data 字典中, 而我通常是采用 http status code 来返回状态，返回结果是对象。实现上面的需求我们需要加入一个data成员变量，因为我们不清楚最终要返回什么对象。所以声明为 java.lang.Object

package com.example.api.pojo;

import java.io.Serializable;

public class RestfulResponse implements Serializable {
    /**
     *
     */
    private static final long serialVersionUID = -4045645995352698349L;

    private boolean status;
    private String reason;
    private int code;
    private Object data;

    public RestfulResponse(boolean status, int code, String reason, Object data) {
        this.status = status;
        this.code = code;
        this.reason = reason;
        this.data = data;

    }

    public boolean isStatus() {
        return status;
    }

    public void setStatus(boolean status) {
        this.status = status;
    }

    public String getReason() {
        return reason;
    }

    public void setReason(String reason) {
        this.reason = reason;
    }

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }

    @Override
    public String toString() {
        return "RestfulResponse [status=" + status + ", reason=" + reason + ", code=" + code + ", data=" + data + "]";
    }

}

Service

    public RestfulResponse bindWechat(String mobile, String wechat) {
        Member member = memberRepository.findByMobile(mobile);
        member.setWechat(wechat);
        memberRepository.save(member);
        return new RestfulResponse(true, 1, "微信绑定成功", member);
    }

Controller

    @RequestMapping("/login/sms/{mobile}/{code}")
    public RestfulResponse sms(@PathVariable String mobile, @PathVariable String wechat) {
        return memberService.bindWechat(mobile, wechat);
    }

6.3.7. @PageableDefault 分页

@RequestMapping(value = "/list", method=RequestMethod.GET)
public Page<Blog> getEntryByPageable1(@PageableDefault( sort = { "id" }, direction = Sort.Direction.DESC)
    Pageable pageable) {
    return blogRepository.findAll(pageable);
}

@RequestMapping(value = "/blog", method=RequestMethod.GET)
public Page<Blog> getEntryByPageable(@PageableDefault(value = 15, sort = { "id" }, direction = Sort.Direction.DESC)
    Pageable pageable) {
    return blogRepository.findAll(pageable);
}

@RequestMapping(value = "/list", method=RequestMethod.GET)
public Page<Blog> getEntryByPageable2(@PageableDefault Pageable pageable) {
    return blogRepository.findAll(pageable);
}

@ModelAttribute("users")
public Page<User> users(@PageableDefault(size = 5) Pageable pageable) {
    return userManagement.findAll(pageable);
}

我们只需要在方法的参数中直接定义一个pageable类型的参数，当Spring发现这个参数时，Spring会自动的根据request的参数来组装该pageable对象，Spring支持的request参数如下：

page，第几页，从0开始，默认为第0页
size，每一页的大小，默认为20
sort，排序相关的信息，以property,property(,ASC|DESC)的方式组织，例如sort=firstname&sort=lastname,desc表示在按firstname正序排列基础上按lastname倒序排列
这样，我们就可以通过url的参数来进行多样化、个性化的查询，而不需要为每一种情况来写不同的方法了。

通过url来定制pageable很方便，但唯一的缺点是不太美观，因此我们需要为pageable设置一个默认配置，这样很多情况下我们都能够通过一个简洁的url来获取信息了。

Spring提供了@PageableDefault帮助我们个性化的设置pageable的默认配置。例如@PageableDefault(value = 15, sort = { "id" }, direction = Sort.Direction.DESC)表示默认情况下我们按照id倒序排列，每一页的大小为15。

6.3.8. 上传文件

spring.servlet.multipart.max-file-size=128KB
spring.servlet.multipart.max-request-size=128KB
spring.http.multipart.enabled=false

RestController

package api.restful;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;

import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import api.pojo.RestfulResponse;

@RestController
@RequestMapping("/upload")
public class UploadRestController {

    private final static String FOLDER = "/tmp";

    public UploadRestController() {

    }

    @PostMapping("/single")
    public RestfulResponse upload(@RequestParam("file") MultipartFile file, RedirectAttributes redirectAttributes) {

        if (file.isEmpty()) {
            return new RestfulResponse(false, 0, "Please select a file to upload", "");
        }
        try {
            byte[] bytes = file.getBytes();
            Path path = Paths.get(FOLDER + "/" + file.getOriginalFilename());
            Files.write(path, bytes);

            return new RestfulResponse(true, 0, "", path.toString());
        } catch (Exception e) {
            return new RestfulResponse(false, 0, e.getMessage(), null);
        }
    }

    @PostMapping(value = "/group")
    public RestfulResponse group(@RequestParam("files") MultipartFile[] files) {
        List<String> filelist = new ArrayList<String>();
        try {

            for (MultipartFile file : files) {
                File tmpfile = new File(FOLDER + "/" + file.getOriginalFilename());
                file.transferTo(tmpfile);
                filelist.add(tmpfile.getPath());
            }
            return new RestfulResponse(true, 0, null, filelist);
        } catch (Exception e) {
            return new RestfulResponse(false, 0, e.getMessage(), null);
        }
    }
}

由于上传文件名可能存在空格等特殊字符，这里使用UUID替代文件名

    @PostMapping(value = "/file")
    public RestfulResponse file(@RequestParam("file") MultipartFile[] files) {
        List<Object> filelist = new ArrayList<Object>();
        try {

            for (MultipartFile file : files) {

                UUID uuid = UUID.randomUUID();
                String filename = String.format("%s/%s.%s", folder, uuid.toString(), this.getExtensionName(filename));

                File tmpfile = new File(filename);
                String filepath = tmpfile.getPath();
                System.out.println(filepath);
                file.transferTo(tmpfile);
                filelist.add(tmpfile.toString());
            }
            return new RestfulResponse(true, 0, null, filelist);
        } catch (Exception e) {
            return new RestfulResponse(false, 0, e.getMessage(), null);
        }
    }

    private String getExtensionName(String filename) {
        if ((filename != null) && (filename.length() > 0)) {
            int dot = filename.lastIndexOf('.');
            if ((dot > -1) && (dot < (filename.length() - 1))) {
                return filename.substring(dot + 1);
            }
        }
        return filename;
    }


6.4. View

6.4.1. Using Spring’s form tag library

6.4.1.1. css
6.4.1.1.1. cssClass

cssClass 使用该属性指定表单元素CSS样式名，相当于HTML元素的class属性

<form:input path="userName" cssClass="inputStyle"/>
6.4.1.1.2. cssStyle

cssStyle 直接通过该属性指定样式，相当于HTML元素的style属性

<form:input path="userName" cssStyle="width:100px"/>
6.4.1.1.3. cssErrorClass

cssError Class表示表单元素发生错误时对应的样式

<form:input path="userName" cssClass="userNameClass" cssErrorClass= "userNameClassError"/>

6.4.1.2. cssClass

6.4.2. Thymeleaf

http://thymeleaf.org/

6.4.2.1. Maven pom.xml

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

6.4.2.2. Spring 配置

    <!-- **************************************************************** -->
    <!-- THYMELEAF-SPECIFIC ARTIFACTS -->
    <!-- TemplateResolver <- TemplateEngine <- ViewResolver -->
    <!-- **************************************************************** -->

    <bean id="templateResolver"
        class="org.thymeleaf.templateresolver.ServletContextTemplateResolver">
        <property name="prefix" value="/WEB-INF/templates/" />
        <property name="suffix" value=".html" />
        <property name="templateMode" value="HTML5" />
    </bean>

    <bean id="templateEngine" class="org.thymeleaf.spring4.SpringTemplateEngine">
        <property name="templateResolver" ref="templateResolver" />
    </bean>

    <bean class="org.thymeleaf.spring4.view.ThymeleafViewResolver">
        <property name="templateEngine" ref="templateEngine" />
    </bean>

6.4.2.3. controller

package cn.netkiller.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
@RequestMapping("/")
public class HelloController {

    @RequestMapping(value = "/{name}", method = RequestMethod.GET)
    public String getMovie(@PathVariable String name, ModelMap model) {
        model.addAttribute("name", name);
        return "hello";
    }

}

6.4.2.4. HTML5 Template

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Spring MVC + Thymeleaf Example</title>
</head>
<body>
    Hello, <span th:text="${name}" />!
</body>
</html>

6.4.2.5. thymeleaf 渲染表格

    @RequestMapping("/list")
    public ModelAndView list() {

        Iterable<User> users = userRepository.findAll();

        ModelAndView mv = new ModelAndView();
        mv.addObject("users", users);
        mv.setViewName("table");
        return mv;
    }

模板文件

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8" />
<title>用户登记</title>
</head>
<body>
    <h1>Welcome to Thymeleaf</h1>
    <table border="1" width="100%">
        <tr>
            <td>ID</td>
            <td>姓名</td>
            <td>联系方式</td>
            <td>详细地址</td>
            <td>图片</td>
        </tr>
        <tr th:each="user : ${users}">
            <td th:text="${user.id}"></td>
            <td th:text="${user.name}"></td>
            <td th:text="${user.tel}"></td>
            <td th:text="${user.address}"></td>
            <td th:text="${user.picture}"></td>
        </tr>
    </table>
</body>
</html>

6.4.2.6. URL 链接

    <span th:text="${number+1}"></span> /
    <span th:text="${totalPages}"></span>

    <a href="#"
        th:href="@{/api/user/browse?sort=id,desc&size=10(page=${number-1})}">上一页</a>
    <a href="#"
        th:href="@{/api/user/browse?sort=id,desc&size=10(page=${number+1})}">下一页</a>

拼接 URL 的方法

<img src="#" th:src="${'https://img.netkiller.cn/' + pic}" height="128" th:target="_blank" />

6.4.2.7. 拆分字符串

pictures 是一个以逗号分割得字符串。我们需要拆分并逐条显示。

    <div th:unless="${picture == null}">
        <a th:each="pic : ${#strings.arraySplit(pictures, ',')}" href="#" th:href="${pic}"> <img src="#" th:src="${pic}" height="64" /></a>
    </div>

6.4.2.8. 日期格式化

    <span th:text="${#dates.format(createDate, 'yyyy-MM-dd HH:mm')}"></span

// java.util.Date 处理

${#dates.day(date)}
${#dates.month(date)}
${#dates.monthName(date)}
${#dates.monthNameShort(date)}
${#dates.year(date)}
${#dates.dayOfWeek(date)}
${#dates.dayOfWeekName(date)}
${#dates.dayOfWeekNameShort(date)}
${#dates.hour(date)}
${#dates.minute(date)}
${#dates.second(date)}
${#dates.millisecond(date)}

// java.time 时间处理
${#temporals.day(date)}
${#temporals.month(date)}
${#temporals.monthName(date)}
${#temporals.monthNameShort(date)}
${#temporals.year(date)}
${#temporals.dayOfWeek(date)}
${#temporals.dayOfWeekName(date)}
${#temporals.dayOfWeekNameShort(date)}
${#temporals.hour(date)}
${#temporals.minute(date)}
${#temporals.second(date)}
${#temporals.millisecond(date)}

// 处理天实例

<p th:text="${#dates.day(standardDate)}"></p>
<p th:text="${#temporals.day(localDateTime)}"></p>
<p th:text="${#temporals.day(localDate)}"></p>

// 处理周实例

<p th:text="${#dates.dayOfWeekName(standardDate)}"></p>
<p th:text="${#temporals.dayOfWeekName(localDateTime)}"></p>
<p th:text="${#temporals.dayOfWeekName(localDate)}"></p>

// 处理秒实例

<p th:text="${#dates.second(standardDate)}"></p>
<p th:text="${#temporals.second(localDateTime)}"></p>

6.4.3. FreeMarker

http://freemarker.org/


6.5. Service

6.5.1. Application

@ComponentScan({ "web", "rest","service" }) 一定要包含 Service 目录。否则无法实现 @Autowired自动装配。你可以直接@ComponentScan扫描所有目录。

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.authentication.UserCredentials;
import org.springframework.data.mongodb.MongoDbFactory;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.SimpleMongoDbFactory;
import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;

import com.mongodb.Mongo;

import pojo.ApplicationConfiguration;

@Configuration
@SpringBootApplication
@EnableConfigurationProperties(ApplicationConfiguration.class)
@EnableAutoConfiguration(exclude = { DataSourceAutoConfiguration.class })
@ComponentScan({ "web", "rest","service" })
@EnableMongoRepositories
public class Application {

    @SuppressWarnings("deprecation")
    public @Bean MongoDbFactory mongoDbFactory() throws Exception {
        UserCredentials userCredentials = new UserCredentials("finance", "your_password");
        return new SimpleMongoDbFactory(new Mongo("mdb.netkiller.cn"), "finance", userCredentials);
    }

    public @Bean MongoTemplate mongoTemplate() throws Exception {
        return new MongoTemplate(mongoDbFactory());
    }

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}

6.5.2. 定义接口

TestService 接口

package service;

public interface TestService {

    public String getName();
    public String toString();
    public String helloUser(String user);
}

6.5.3. 实现接口

实现 TestService 接口

package service.impl;

import org.springframework.stereotype.Component;

import service.TestService;

@Component
public class TestServiceImpl implements TestService {

    public String name = "Test";

    public void TestService() {

    }

    @Override
    public String helloUser(String user) {
        return "hello " + user;
    }

    public String getName() {
        return this.name;
    }

    @Override
    public String toString() {
        return "TestServiceImpl [config=" + this.name + "]";
    }

}

6.5.4. 调用 Service

控制器中调用 Service

package web;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import domain.City;
import pojo.ApplicationConfiguration;
import repository.CityRepository;
import service.TestService;

@Controller
public class IndexController {

    @Autowired
    private TestService testService;

    @RequestMapping("/service")
    @ResponseBody
    public String service() {
        return testService.helloUser("Neo");
    }

}


6.6. i18n 国际化

6.6.1. 在 appliction.properties 中配置启用 i18n

spring.messages.basename=message
spring.messages.encoding=UTF-8

6.6.2. 创建语言包文件

创建默认语言包文件 message.properties，当匹配不到语言时使用默认配置

member.name=Name

message_en_US.properties

member.name=Name

message_zh_CN.properties

member.name=姓名

注意：Eclipse 需要安装 properties 编辑工具，否则中文会自动转换成UTF8编码，无法直接阅读。

6.6.3. 控制器重引用语言包

RestController

@RestController
public class HomeController {
    @Autowired
    private MessageSource messageSource;

    @GetMapping("/lang")
    public String language() {
        String message = messageSource.getMessage("member.name", null, LocaleContextHolder.getLocale());
        return message;
    }
}

Controller

package cn.netkiller.controller;

import org.springframework.stereotype.Controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.context.MessageSource;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.ui.Model;
import java.util.Locale;

@Controller
public class HomeController {

    @Autowired
    private MessageSource messageSource;

    @RequestMapping(value = "/", method = RequestMethod.GET)
    public String index(Locale locale, Model model){

        // add parametrized message from controller
        String welcome = messageSource.getMessage("welcome.message", new Object[]{"Neo Chan"}, locale);
        model.addAttribute("message", welcome);

        // obtain locale from LocaleContextHolder
        Locale currentLocale = LocaleContextHolder.getLocale();
        model.addAttribute("locale", currentLocale);
        model.addAttribute("startMeeting", "10:30");

        return "index";
    }

}

6.6.4. 参数传递

有时定义语言包会出现一种情况，一个句子中可能存在变量。例如：

恭喜你 XXXX 您已成为我们的会员

这样的需求，如果丁一两个key处理起来会非常麻烦。这里可以定义一个变量，通过参数传递来修改一句话中间的部分。

welcome=Welcom to {0}

    @GetMapping("/lang/args")
    public String welcome() {
        String[] args = { "China" };
        String message = messageSource.getMessage("welcome", args, LocaleContextHolder.getLocale());

        return message;
    }

参数以此类推 {0}, {1} ...... {n}

String welcome = messageSource.getMessage("welcome.message", new Object[]{"Neo chen"}, locale);


6.7. FAQ

6.7.1. o.s.web.servlet.PageNotFound

解决方法，加入下面代码到 dispatcher-servlet.xml 文件中

<mvc:annotation-driven />

dispatcher-servlet.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="cn.netkiller.controller" />
    <mvc:annotation-driven />
    <bean id="viewResolver"
        class="org.springframework.web.servlet.view.UrlBasedViewResolver">
        <property name="viewClass"
            value="org.springframework.web.servlet.view.JstlView" />
        <property name="prefix" value="/WEB-INF/jsp/" />
        <property name="suffix" value=".jsp" />
    </bean>

</beans>

6.7.2. HTTP Status 500 - Handler processing failed; nested exception is java.lang.NoClassDefFoundError: javax/servlet/jsp/jstl/core/Config

pom.xml 文件中加入依赖包

        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
            <version>1.2</version>
        </dependency>

6.7.3. 同时使用 Thymeleaf 与 JSP

Using both Thymeleaf and JSP

    <bean
        class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="viewClass"
            value="org.springframework.web.servlet.view.JstlView" />
        <property name="prefix" value="/WEB-INF/jsp/" />
        <!-- <property name="suffix" value=".jsp" /> -->
        <property name="viewNames" value="*.jsp" />
    </bean>

    <bean id="templateResolver"
        class="org.thymeleaf.templateresolver.ServletContextTemplateResolver">
        <property name="prefix" value="/WEB-INF/templates/" />
        <!-- <property name="suffix" value=".html" /> -->
        <property name="templateMode" value="HTML5" />
    </bean>

    <bean id="templateEngine" class="org.thymeleaf.spring4.SpringTemplateEngine">
        <property name="templateResolver" ref="templateResolver" />
    </bean>

    <bean class="org.thymeleaf.spring4.view.ThymeleafViewResolver">
        <property name="templateEngine" ref="templateEngine" />
        <property name="viewNames" value="*.html" />
    </bean>

@RequestMapping("/thymeleaf")
public String thymeleafView(){
    return "thymeleaf.html";
}

@RequestMapping("/jsp")
public String jspView(){
    return "jstl.jsp";
}

            <property name="viewNames" value="*thymeleaf/*" />

@RequestMapping(value="/test")
public ModelAndView dboxPrint(Model model){
    ModelAndView modelAndView = new ModelAndView("thymeleaf/test");
    return modelAndView;
}

6.7.4. 排除静态内容

方法一，排除静态内容如 images, css, js 等等

    <servlet>
        <servlet-name>springframework</servlet-name>
        <servlet-class>
            org.springframework.web.servlet.DispatcherServlet
        </servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>default</servlet-name>
        <url-pattern>/images/*</url-pattern>
        <url-pattern>*.css</url-pattern>
        <url-pattern>/js/*.js</url-pattern>
    </servlet-mapping>
    <servlet-mapping>
        <servlet-name>springframework</servlet-name>
        <url-pattern>/welcome.jsp</url-pattern>
        <url-pattern>/welcome.html</url-pattern>
        <url-pattern>*.html</url-pattern>
    </servlet-mapping>

方法二

    <mvc:resources location="/images/" mapping="/images/**" />
    <mvc:resources location="/css/" mapping="/css/**" />
    <mvc:resources location="/js/" mapping="/js/**" />

6.7.5. HTTP Status 406

配置 url-pattern 增加需要传递给Spring的扩展名

    <servlet>
        <servlet-name>springframework</servlet-name>
        <servlet-class>
            org.springframework.web.servlet.DispatcherServlet
        </servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>springframework</servlet-name>
        <url-pattern>/welcome.jsp</url-pattern>
        <url-pattern>/welcome.html</url-pattern>
        <url-pattern>*.json</url-pattern>
        <url-pattern>*.xml</url-pattern>
        <url-pattern>*.html</url-pattern>
    </servlet-mapping>

6.7.6. Caused by: java.lang.IllegalArgumentException: Not a managed type: class common.domain.Article

背景描述：Springboot 入口文件 Application.java 的包是 package api; 为了让 domain,pojo 共用，于是将 domain 放到Maven module下命令为 common。启动后出现这个故障。

解决方案增加 @EntityScan("common.domain") 即可。

package api;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.scheduling.annotation.EnableScheduling;

@SpringBootApplication
@EnableScheduling
@EnableEurekaClient
@EntityScan("common.domain")
public class Application {

    public static void main(String[] args) {
        System.out.println( "Service Api Starting..." );
        SpringApplication.run(Application.class, args);
    }
}

6.7.7. {"error":"unauthorized","error_description":"Full authentication is required to access this resource"}

Oauth @RestController 一切正常， @Controller 提示如下

{"error":"unauthorized","error_description":"Full authentication is required to access this resource"}

程序如下

package api.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping("/")
public class IndexController {

    public IndexController() {
        // TODO Auto-generated constructor stub
    }

    @GetMapping("/")
    public String index() {
        return "Helloworld!!!";

    }

    @GetMapping("/about")
    public String test() {
        return "Helloworld!!!";

    }
}

分析 @Controller 不允许直接返回字符串，必须使用 @ResponseBody 或者 ModelAndView，下改后问题解决。

package api.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
@RequestMapping("/web")
public class IndexController {

    public IndexController() {
        // TODO Auto-generated constructor stub
    }

    @GetMapping("/")
    @ResponseBody
    public String index() {
        return "Helloworld!!!";

    }

    @GetMapping("/about")
    @ResponseBody
    public String test() {
        return "Helloworld!!!";

    }
}

同时 @EnableWebSecurity 需要忽略 @Controller 的映射 URL

@Configuration
@EnableWebSecurity
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/**/json").antMatchers("/about", "/", "/css/**");
    }
}


第 7 章 RestTemplate - Spring4 Restful

@RestController

目录

7.1. RestTemplate Example
7.1.1. pom.xml
7.1.2. web.xml
7.1.3. springframework.xml
7.1.4. RestController
7.1.5. POJO
7.1.6. 测试
7.2. getForObject
7.2.1. 返回字符串
7.2.2. 传递 GET 参数
7.3. postForObject
7.3.1. 传递对象
7.3.2. 传递数据结构 MultiValueMap
7.4. postForEntity
7.5. PUT
7.6. Delete
7.7. 上传文件
7.8. HTTP Auth
7.8.1. Client
7.9. PKCS12

首先我要禁告各位，Spring发展过程中，每个版本都有一定差异。如果你做实验失败后在网上搜索答案，切记看一下版本号还有文章帖子的发布时间。否则你可能按照Spring3配置方法去Spring4。

@RestController 默认返回 @ResponseBody， 所以@ResponseBody可加可不加

7.1. RestTemplate Example

7.1.1. pom.xml

Maven 增加 jackson 开发包

        <dependency>
            <groupId>com.fasterxml.jackson.dataformat</groupId>
            <artifactId>jackson-dataformat-xml</artifactId>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-core</artifactId>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-annotations</artifactId>
        </dependency>

7.1.2. web.xml

url-pattern匹配中增加*.xml跟*.json

    <servlet>
        <servlet-name>springframework</servlet-name>
        <servlet-class>
            org.springframework.web.servlet.DispatcherServlet
        </servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>springframework</servlet-name>
        <url-pattern>/welcome.jsp</url-pattern>
        <url-pattern>/welcome.html</url-pattern>
        <url-pattern>*.json</url-pattern>
        <url-pattern>*.xml</url-pattern>
        <url-pattern>*.html</url-pattern>
    </servlet-mapping>

7.1.3. springframework.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:mongo="http://www.springframework.org/schema/data/mongo" xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/data/mongo
http://www.springframework.org/schema/data/mongo/spring-mongo-1.5.xsd
        ">

    <mvc:resources location="/images/" mapping="/images/**" />
    <mvc:resources location="/css/" mapping="/css/**" />
    <mvc:resources location="/js/" mapping="/js/**" />
    <mvc:resources location="/zt/" mapping="/zt/**" />
    <mvc:resources location="/sm/" mapping="/sm/**" />
    <mvc:resources location="/module/" mapping="/module/**" />

    <context:component-scan base-package="cn.netkiller.controller">

    </context:component-scan>
    <context:annotation-config />
    <mvc:annotation-driven />

    <bean id="viewResolver" class="org.springframework.web.servlet.view.UrlBasedViewResolver">
        <property name="viewClass" value="org.springframework.web.servlet.view.JstlView" />
        <property name="prefix" value="/WEB-INF/jsp/" />
        <property name="suffix" value=".jsp" />
    </bean>

    <bean id="configuracion" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="location" value="classpath:resources/development.properties" />
    </bean>

    <!-- Redis Connection Factory -->
    <bean id="jedisConnFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" p:host-name="192.168.2.1" p:port="6379" p:use-pool="true" />

    <!-- redis template definition -->
    <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate" p:connection-factory-ref="jedisConnFactory" />

    <mongo:db-factory id="mongoDbFactory" host="${mongo.host}" port="${mongo.port}" dbname="${mongo.database}" />
    <!-- username="${mongo.username}" password="${mongo.password}" -->

    <bean id="mongoTemplate" class="org.springframework.data.mongodb.core.MongoTemplate">
        <constructor-arg name="mongoDbFactory" ref="mongoDbFactory" />
    </bean>

    <mongo:mapping-converter id="converter" db-factory-ref="mongoDbFactory" />
    <bean id="gridFsTemplate" class="org.springframework.data.mongodb.gridfs.GridFsTemplate">
        <constructor-arg ref="mongoDbFactory" />
        <constructor-arg ref="converter" />
    </bean>

</beans>

7.1.4. RestController

package cn.netkiller.controller;

import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ResponseBody;

import cn.netkiller.pojo.Message;

@RestController
@RequestMapping("/rest")
public class TestRestController {

    public TrackerRestController() {
        // TODO Auto-generated constructor stub
    }

    @RequestMapping("welcome")
    @ResponseStatus(HttpStatus.OK)
    public String welcome() {
        return "Welcome to RestTemplate Example.";
    }

    @RequestMapping(value = "test", method = RequestMethod.GET, produces = { "application/xml", "application/json" })
    @ResponseStatus(HttpStatus.OK)
    public @ResponseBody Message test(@RequestHeader(value = "accept") String accept) {
        Message message = new Message();
        message.setTitle("test");
        message.setText("Helloworld!!!");
        System.out.println("accept: " + accept);
        System.out.println(message.toString());
        return message;
    }

    @RequestMapping("message/{name}")
    public ResponseEntity<Message> message(@PathVariable String name) {
        Message msg = new Message();
        msg.setTitle(name);
        return new ResponseEntity<Message>(msg, HttpStatus.OK);
    }

    @RequestMapping(value = "create", method = RequestMethod.POST, produces = { "application/xml", "application/json" })
    public ResponseEntity<Tracker> create(@RequestBody Tracker tracker) {
        this.mongoTemplate.insert(tracker);
        return new ResponseEntity<Tracker>(tracker, HttpStatus.OK);
    }

    @RequestMapping(value = "read", method = RequestMethod.GET, produces = { "application/xml", "application/json" })
    @ResponseStatus(HttpStatus.OK)
    public ArrayList<Tracker> read() {

        ArrayList<Tracker> trackers =  (ArrayList<Tracker>) mongoTemplate.findAll(Tracker.class);
        return trackers;
    }
}

7.1.5. POJO

package cn.netkiller.pojo;

import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
public class Message {

    String title;
    String text;

    public Message() {
        // TODO Auto-generated constructor stub
    }

    //@XmlElement
    @XmlAttribute
    public String getTitle() {
        return title;
    }
    public void setTitle(String title) {
        this.title = title;
    }

    //@XmlElement
    @XmlAttribute
    public String getText() {
        return text;
    }
    public void setText(String text) {
        this.text = text;
    }
    @Override
    public String toString() {
        return "Message [title=" + title + ", text=" + text + "]";
    }

}

7.1.6. 测试

neo@netkiller:~/www.netkiller.cn$ curl http://172.16.0.1:8080/spring4/rest/welcome.html
Welcome to RestTemplate Example.

neo@netkiller:~/www.netkiller.cn$ curl http://172.16.0.1:8080/spring4/rest/test.json
{"title":"test","text":"Helloworld!!!"}

neo@netkiller:~/www.netkiller.cn$ curl http://172.16.0.1:8080/spring4/rest/test.xml
<Message xmlns=""><title>test</title><text>Helloworld!!!</text></Message>

neo@netkiller:~/www.netkiller.cn$ curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X POST -d '{"login":"neo", "unique":"356770257607079474","hostname":"www.example.com","referrer":"http://www.netkiller.cn","href":"http://www.netkiller.cn"}' http://172.16.0.1:8080/spring4/rest/create.json
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Type: application/json
Transfer-Encoding: chunked
Date: Tue, 21 Jun 2016 03:08:26 GMT

{"name":"neo","unique":"356770257607079474","hostname":"www.netkiller.cn","referrer":"http://www.netkiller.cn","href":"http://www.netkiller.cn"}


7.2. getForObject

7.2.1. 返回字符串

    @RequestMapping("/restful/get")
    @ResponseBody
    public String restfulGet() {
        RestTemplate restTemplate = new RestTemplate();
        String text = restTemplate.getForObject("http://inf.netkiller.cn/detail/html/2/2/42564.html", String.class);
        return text;
    }

7.2.2. 传递 GET 参数

    @RequestMapping("/restful/get/{id}")
    @ResponseBody
    private static String restfulGetId(@PathVariable String id) {
        final String uri = "http://inf.netkiller.cn/detail/html/{tid}/{cid}/{id}.html";

        Map<String, String> params = new HashMap<String, String>();
        params.put("tid", "2");
        params.put("cid", "2");
        params.put("id", id);
        RestTemplate restTemplate = new RestTemplate();
        String result = restTemplate.getForObject(uri, String.class, params);

        return (result);
    }


7.3. postForObject

7.3.1. 传递对象

    @RequestMapping("/restful/post/{id}")
    @ResponseBody
    private static String restfullPost(@PathVariable String id) {

        final String uri = "http://inf.netkiller.cn/detail/html/{tid}/{cid}/{id}.html";

        Map<String, String> params = new HashMap<String, String>();
        params.put("tid", "2");
        params.put("cid", "2");
        params.put("id", id);

        City city = new City("Shenzhen", "Guangdong");

        RestTemplate restTemplate = new RestTemplate();
        String result = restTemplate.postForObject(uri, city, String.class, params);
        return result;
    }

7.3.2. 传递数据结构 MultiValueMap

    @RequestMapping("/findByMobile")
    public String findByMobile() {
        System.out.println("****************************findByMobile******************************");

        final String uri = "http://www.netkiller.cn/account/getMemberByMobile.json";
        MultiValueMap<String, String> map = new LinkedMultiValueMap<String, String>();
        try {

            map.add("prefix", "86");
            map.add("mobile", "13698041116");
            map.add("_pretty_", "false");

        } catch (Exception e) {
            e.printStackTrace();
        }

        RestTemplate restTemplate = new RestTemplate();
        String result = restTemplate.postForObject(uri, map, String.class);

        System.out.println(map.toString());
        System.out.println(result);

        return result;
    }


7.4. postForEntity

    @RequestMapping("/findByMobile")
    @ResponseBody
    public String findByMobile() {
        System.out.println("****************************findByMobile******************************");

        final String uri = "https://www.netkiller.cn/account/getMemberByMobile";
        MultiValueMap<String, String> map = new LinkedMultiValueMap<String, String>();
        try {

            map.add("prefix", "86");
            map.add("mobile", "13698041116");
            map.add("args", "[]");
            map.add("_pretty_", "false");

        } catch (Exception e) {
            e.printStackTrace();
        }

        RestTemplate restTemplate = new RestTemplate();
        ResponseEntity<String> response = restTemplate.postForEntity(uri, map, String.class);
        System.out.println(map.toString());
        System.out.println();
        return response.getBody();
    }


7.5. PUT

    @RequestMapping("/restful/put/{id}")
    private static void restfulPut(@PathVariable String id) {
        final String uri = "http://inf.netkiller.cn/detail/html/{tid}/{cid}/{id}.html";

        Map<String, String> params = new HashMap<String, String>();
        params.put("id", id);

        City city = new City("Shenzhen", "Guangdong");

        RestTemplate restTemplate = new RestTemplate();
        restTemplate.put(uri, city, params);
    }


7.6. Delete

    @RequestMapping("/restful/delete/{id}")
    private static void restfulDelete(@PathVariable String id) {
        final String uri = "http://inf.netkiller.cn/detail/html/{tid}/{cid}/{id}.html";

        Map<String, String> params = new HashMap<String, String>();
        params.put("id", id);

        RestTemplate restTemplate = new RestTemplate();
        restTemplate.delete(uri, params);
    }


7.7. 上传文件

package cn.netkiller.file;

import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.http.*;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;

public class UploadClient {

    public static void main(String[] args) throws IOException {
        MultiValueMap<String, Object> bodyMap = new LinkedMultiValueMap<>();
        bodyMap.add("user-file", getUserFileResource());
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.MULTIPART_FORM_DATA);
        HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(bodyMap, headers);

        RestTemplate restTemplate = new RestTemplate();
        ResponseEntity<String> response = restTemplate.exchange("http://localhost:8080/upload", HttpMethod.POST, requestEntity, String.class);
        System.out.println("response status: " + response.getStatusCode());
        System.out.println("response body: " + response.getBody());
    }

    public static Resource getUserFileResource() throws IOException {
        //todo replace tempFile with a real file
        Path tempFile = Files.createTempFile("hello", ".txt");
        Files.write(tempFile, "Helloworld, http://www.netkiller.cn".getBytes());
        System.out.println("uploading: " + tempFile);
        File file = tempFile.toFile();
        //to upload in-memory bytes use ByteArrayResource instead
        return new FileSystemResource(file);
    }
}


7.8. HTTP Auth

7.8.1. Client

HttpClient client = new HttpClient();
UsernamePasswordCredentials credentials = new UsernamePasswordCredentials("your_user","your_password");
client.getState().setCredentials(new AuthScope("thehost", 9090, AuthScope.ANY_REALM), credentials);
CommonsClientHttpRequestFactory commons = new CommonsClientHttpRequestFactory(client);

RestTemplate template = new RestTemplate(commons);
Example results = template.getForObject("http://www.netkiller.cn:9090/foo.json", Example.class);


7.9. PKCS12

package example.controller;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;

import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContextBuilder;
import org.apache.http.ssl.SSLContexts;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.security.oauth2.client.OAuth2RestOperations;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.client.RestTemplate;

@Controller
public class TestController {
    @Autowired
    private OAuth2RestOperations restTemplate;

    @GetMapping("/")
    @ResponseBody
    public String index() {
        OAuth2AccessToken token = restTemplate.getAccessToken();
        System.out.println(token.getValue());
        String tmp = restTemplate.getForObject("http://api.alpha.netkiller.cn/", String.class);
        System.out.println(tmp);
        return tmp;
    }

    @GetMapping("/ssl")
    @ResponseBody
    public String ssl() throws KeyManagementException, NoSuchAlgorithmException, KeyStoreException {
        String url = "https://api.alpha.netkiller.cn/";

        SSLContext sslcontext = SSLContexts.custom().loadTrustMaterial(null, (chain, authType) -> true).build();
        SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext, new String[] { "TLSv1" }, null, new NoopHostnameVerifier());
        CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(sslsf).build();
        HttpComponentsClientHttpRequestFactory httpComponentsClientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
        httpComponentsClientHttpRequestFactory.setConnectTimeout(60000);
        httpComponentsClientHttpRequestFactory.setReadTimeout(180000);

        final RestTemplate restTemplate = new RestTemplate(httpComponentsClientHttpRequestFactory);

        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        headers.set("Authorization", "Bearer " + this.restTemplate.getAccessToken().getValue());
        HttpEntity<String> entity = new HttpEntity<String>(headers);

        ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.GET, entity, String.class);
        String str = response.getBody();
        return str;
    }

    @GetMapping("/pkcs12")
    @ResponseBody
    public String PKCS12(String url, String data) throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException, KeyManagementException, UnrecoverableKeyException {
        KeyStore keyStore = KeyStore.getInstance("PKCS12");
        FileInputStream instream = new FileInputStream(new File("/opt/xxx.p12"));
        keyStore.load(instream, "netkiller".toCharArray());
        // Trust own CA and all self-signed certs
        SSLContext sslcontext = SSLContextBuilder.create().loadKeyMaterial(keyStore, "netkiller".toCharArray()).build();
        // Allow TLSv1 protocol only
        HostnameVerifier hostnameVerifier = NoopHostnameVerifier.INSTANCE;
        SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext, new String[] { "TLSv1" }, null, hostnameVerifier);
        CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(sslsf).build();

        HttpComponentsClientHttpRequestFactory clientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory(httpclient);

        RestTemplate restTemplate = new RestTemplate(clientHttpRequestFactory);

        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.add("Connection", "keep-alive");
        httpHeaders.add("Accept", "*/*");
        httpHeaders.add("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8");
        httpHeaders.add("Host", "api.netkiller.cn");
        httpHeaders.add("X-Requested-With", "XMLHttpRequest");
        httpHeaders.add("Cache-Control", "max-age=0");
        httpHeaders.add("User-Agent", "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0) ");

        HttpEntity<String> httpEntity = new HttpEntity<String>(httpHeaders);

        ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.POST, httpEntity, String.class);
        return response.getBody();

    }

}


第 8 章 Spring Data

目录

8.1. Spring Data Redis
8.1.1. 集成 Redis
8.1.1.1. pom.xml
8.1.1.2. springframework-servlet.xml
8.1.1.3. Controller
8.1.1.4. index.jsp
8.1.1.5. 测试
8.1.2. RedisTemplate
8.1.2.1. stringRedisTemplate
8.1.2.2. 删除 key
8.1.2.3. ZSET 数据类型
8.1.2.4. Hash
8.1.2.5. List
8.1.2.6. 过期时间未执行
8.1.2.7. 存储 Json 对象
8.1.3. Spring Data Redis - Repository Examples
8.1.3.1. @EnableRedisRepositories 启动 Redis 仓库
8.1.3.2. 定义 Domain 类
8.1.3.3. Repository 接口
8.1.3.4. 测试代码
8.2. Spring Data MongoDB
8.2.1. Example Spring Data MongoDB
8.2.1.1. pom.xml
8.2.1.2. springframework-servlet.xml
8.2.1.3. POJO
8.2.1.4. Controller
8.2.1.5. 查看测试结果
8.2.1.6. 条件查询
8.2.2. @Document
8.2.2.1. 指定表名
8.2.2.2. @Id
8.2.2.3. @Version
8.2.2.4. @Field 定义字段名
8.2.2.5. @Indexed
8.2.2.6. @CompoundIndex 复合索引
8.2.2.7. @TextIndexed
8.2.2.8. @GeoSpatialIndex 地理位置索引
8.2.2.9. @Transient 丢弃数据，不存到 mongodb
8.2.2.10. @DBRef 做外外键引用
8.2.2.11. @DateTimeFormat
8.2.2.12. @JsonFormat 格式化 json 时间格式
8.2.2.13. @NumberFormat
8.2.2.14. 在 @Document 中使用 Enum 类型
8.2.2.15. 在 @Document 中定义数据结构 List/Map
8.2.2.16. GeoJson 数据类型
8.2.3. MongoRepository
8.2.3.1. 扫描仓库接口
8.2.3.2. findAll()
8.2.3.3. deleteAll()
8.2.3.4. save()
8.2.3.5. count()
8.2.3.6. exists() 判断是否存在
8.2.3.7. existsById()
8.2.3.8. findByXXXX
8.2.3.9. findAll with OrderBy
8.2.3.10. findAll with Sort
8.2.3.11. FindAll with Pageable
8.2.3.12. StartingWith 和 EndingWith
8.2.3.13. Between
8.2.3.14. Before / After
8.2.3.15. @Query
8.2.4. mongoTemplate
8.2.4.1. Save 保存
8.2.4.2. Insert
8.2.4.3. updateFirst 修改符合条件第一条记录
8.2.4.4. updateMulti 修改符合条件的所有
8.2.4.5. 查找并保存
8.2.4.6. upsert - 修改符合条件时如果不存在则添加
8.2.4.7. 删除
8.2.4.8. 查找一条数据
8.2.4.9. 查找所有数据
8.2.4.10. Query
8.2.4.11. Criteria
8.2.4.12. Update
8.2.4.13. BasicUpdate
8.2.4.14. Sort
8.2.4.15. Query + PageRequest
8.2.4.16. newAggregation
8.2.4.17. 创建索引
8.2.4.18. 子对象操作
8.2.5. GeoJson 反序列化
8.2.6. FAQ
8.2.6.1. location object expected, location array not in correct format; nested exception is com.mongodb.MongoWriteException: location object expected, location array not in correct format
8.3. Spring Data MySQL
8.3.1. 选择数据库表引擎
8.3.2. @Entity
8.3.2.1. @Table
8.3.2.2. @Id
8.3.2.3. @Column
8.3.2.4. @Lob 注解属性将被持久化为 Blog 或 Clob 类型
8.3.2.5. @NotNull 不能为空声明
8.3.2.6. @JsonIgnore 返回json是不含有该字段
8.3.2.7. @Temporal 日期定义
8.3.2.8. @DateTimeFormat 处理日期时间格式
8.3.2.9. 默认时间规则
8.3.2.10. 索引
8.3.2.11. Enum 枚举数据类型
8.3.2.12. SET 数据结构
8.3.2.13. 整形数据类型
8.3.2.14. JSON 数据类型
8.3.2.15. @JoinColumn
8.3.2.16. @OneToOne
8.3.2.17. OneToMany 一对多
8.3.2.18. ManyToMany 多对多
8.3.2.19. 外键级联删除
8.3.2.20. 其他
8.3.3. 创建复合主键
8.3.4. @EnableJpaAuditing 开启 JPA 审计功能
8.3.5. 实体集成
8.3.6. Repository
8.3.6.1. CrudRepository
8.3.6.2. JpaRepository
8.3.6.3. findByXXX
8.3.6.4. count 操作
8.3.6.5. OrderBy
8.3.6.6. GreaterThan
8.3.6.7. PageRequest 翻页操作
8.3.6.8. Sort 排序操作操作
8.3.6.9. Query
8.3.6.10. @Transactional
8.3.6.11. 锁 @Lock
8.4. EntityManager
8.5. JdbcTemplate
8.5.1. execute
8.5.2. queryForInt
8.5.3. queryForInt
8.5.4. queryForObject
8.5.4.1. 返回整形与字符型
8.5.4.2. 查询 Double 类型数据库
8.5.4.3. 返回日期
8.5.4.4. 返回结果集
8.5.4.5. 通过 "?" 向SQL传递参数
8.5.4.6. RowMapper 记录映射
8.5.5. queryForList
8.5.6. queryForMap
8.5.7. query
8.5.7.1. ResultSet
8.5.7.2. ResultSetExtractor
8.5.7.3. RowMapper
8.5.8. queryForRowSet
8.5.9. update
8.5.10.
8.5.11. 实例参考
8.5.11.1. 参数传递技巧
8.6. Elasticsearch
8.6.1. 内嵌 Elasticsearch
8.6.1.1. Maven
8.6.1.2. src/main/resources/application.properties
8.6.1.3. Domain Class
8.6.1.4. ElasticsearchRepository
8.6.1.5. SearchRestController
8.6.1.6. 测试
8.6.2. 集群模式
8.6.3. Document
8.6.4. Elasticsearch 删除操作
8.6.5. FAQ
8.6.5.1. java.lang.IllegalStateException: Received message from unsupported version: [2.0.0] minimal compatible version is: [5.0.0]
8.7. Spring Data FAQ
8.7.1. No identifier specified for entity
8.7.2. Oracle Date 类型显示日期和时间
8.7.3. java.lang.ClassCastException: java.lang.Long cannot be cast to java.lang.Integer
8.7.4. Executing an update/delete query; nested exception is javax.persistence.TransactionRequiredException: Executing an update/delete query

8.1. Spring Data Redis

8.1.1. 集成 Redis

8.1.1.1. pom.xml

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

8.1.1.2. springframework-servlet.xml

    <!-- Redis Connection Factory -->
    <bean id="jedisConnFactory"
        class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"
        p:host-name="192.168.2.1" p:port="6379" p:use-pool="true" />

    <!-- redis template definition -->
    <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate"
        p:connection-factory-ref="jedisConnFactory" />

例 8.1. Spring Data Redis Example

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <mvc:resources location="/images/" mapping="/images/**" />
    <mvc:resources location="/css/" mapping="/css/**" />

    <context:component-scan base-package="cn.netkiller.controller" />

    <mvc:annotation-driven />

    <bean
        class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="viewClass"
            value="org.springframework.web.servlet.view.JstlView" />
        <property name="prefix" value="/WEB-INF/jsp/" />
        <property name="suffix" value=".jsp" />
        <!-- <property name="viewNames" value="*.jsp" /> -->
    </bean>

    <bean id="configuracion"
        class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="location" value="classpath:resources/development.properties" />
    </bean>

    <bean id="dataSource"
        class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="${jdbc.driverClassName}" />
        <property name="url" value="${jdbc.url}" />
        <property name="username" value="${jdbc.username}" />
        <property name="password" value="${jdbc.password}" />
    </bean>

    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
    </bean>
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="cn.netkiller.mapper" />
    </bean>

    <bean id="userService" class="cn.netkiller.service.UserService">
    </bean>

    <!-- Redis Connection Factory -->
    <bean id="jedisConnFactory"
        class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"
        p:host-name="192.168.2.1" p:port="6379" p:use-pool="true" />

    <!-- redis template definition -->
    <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate"
        p:connection-factory-ref="jedisConnFactory" />
</beans>

8.1.1.3. Controller

package cn.netkiller.controller;

import javax.annotation.Resource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.ListOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

import cn.netkiller.model.User;

@Controller
public class CacheController {

    // inject the actual template
    @Autowired
    private RedisTemplate<String, String> template;

    // inject the template as ListOperations
    @Resource(name = "redisTemplate")
    private ListOperations<String, String> listOps;

    @RequestMapping("/cache")
    public ModelAndView cache() {

        String message = "";

        User user = new User();
        user.setId("1");
        user.setName("Neo");
        user.setAge(30);

        String key = "user";
        listOps.leftPush(key, user.toString());
        message = listOps.leftPop(key);

        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new StringRedisSerializer());
        template.opsForValue().set("key", user.toString());

        return new ModelAndView("index/index", "variable", message);
    }
}

8.1.1.4. index.jsp

<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
    pageEncoding="ISO-8859-1"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Insert title here</title>
</head>
<body>
<br>
    <div style="text-align:center">
        <h2>
            ${variable}
        </h2>
    </div>
</body>
</html>

8.1.1.5. 测试

请求URL http://your.domain.com/your.html

[root@master ~]# redis-cli
redis 127.0.0.1:6379> keys *
1) "\xac\xed\x00\x05t\x00\x04user"
2) "key"

redis 127.0.0.1:6379> get key
"\xac\xed\x00\x05t\x00\x1dUser [id=1, name=Neo, age=30]"

提示

Spring Redis 默认使用 Byte数据类型存储Key，在redis-cli中会看到 "\xac\xed\x00\x05t\x00\x04" 前缀不方便get操作，所以我们会设置使用字符串，通过 template.setKeySerializer(new StringRedisSerializer()); 实现

8.1.2. RedisTemplate

8.1.2.1. stringRedisTemplate

stringRedisTemplate.opsForValue().set("test", "100",60*10,TimeUnit.SECONDS);	//向redis里存入数据和设置缓存时间
stringRedisTemplate.boundValueOps("test").increment(-1);						//val做-1操作
stringRedisTemplate.opsForValue().get("test")									//根据key获取缓存中的val
stringRedisTemplate.boundValueOps("test").increment(1);							//val +1
stringRedisTemplate.getExpire("test")											//根据key获取过期时间
stringRedisTemplate.getExpire("test",TimeUnit.SECONDS)							//根据key获取过期时间并换算成指定单位
stringRedisTemplate.delete("test");												//根据key删除缓存
stringRedisTemplate.hasKey("546545");											//检查key是否存在，返回boolean值
stringRedisTemplate.opsForSet().add("test", "1","2","3");						//向指定key中存放set集合
stringRedisTemplate.expire("test",1000 , TimeUnit.MILLISECONDS);				//设置过期时间
stringRedisTemplate.opsForSet().isMember("test", "1")							//根据key查看集合中是否存在指定数据
stringRedisTemplate.opsForSet().members("test");								//根据key获取set集合

8.1.2.2. 删除 key

    private void cleanNewToday() {
        long begin = System.currentTimeMillis();

        redisTemplate.delete("news:today");

        long end = System.currentTimeMillis();
        logger.info("Schedule clean redis {} 耗时 {} 秒", "cleanNewFlash()", (end-begin) / 1000 );
    }

8.1.2.3. ZSET 数据类型

//添加 一个 set 集合
SetOperations<String, Object> set = redisTemplate.opsForSet();
set.add("Member", "neo");
set.add("Member", "36");
set.add("Member", "178cm");
//输出 set 集合
System.out.println(set.members("Member"));

//添加有序的 set 集合
ZSetOperations<String, Object> zset = redisTemplate.opsForZSet();
zset.add("zMember", "neo", 0);
zset.add("zMember", "36", 1);
zset.add("zMember", "178cm", 2);
//输出有序 set 集合
System.out.println(zset.rangeByScore("zMember", 0, 2));

package cn.netkiller.api.restful;

import java.util.Set;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import common.pojo.ResponseRestful;

@RestController
@RequestMapping("/news")
public class NewsRestController {

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @RequestMapping(value = "/flash/{count}")
    public ResponseRestful flash(@PathVariable("count") long count) {
        if(count == 0L) {
            count=10L;
        }
        Set<String> news = this.redisTemplate.opsForZSet().reverseRange("news:flash", 0, count);
        if (news == null) {
            return new ResponseRestful(false, 10, "没有查询到结果", news);
        }
        return new ResponseRestful(true, 0, "返回数据: " + news.size() + " 条", news);
    }

    public void addRecentUser(long userId, String name) {
        String key = RedisKeyGenerator.genRecentBrowsingPositionsKey(String.valueOf(userId));
        // 获取已缓存的最近浏览的职位
        ZSetOperations<String, String> zSetOperations = redisTempalte.opsForZSet();
        //zset内部是按分数来排序的，这里用当前时间做分数
        zSetOperations.add(key, name, System.currentTimeMillis());
        zSetOperations.removeRange(key, 0, -6);
    }

}

8.1.2.4. Hash

HashOperations<String, Object, Object>  hash = redisTemplate.opsForHash();
Map<String,Object> map = new HashMap<String,Object>();
map.put("name", "neo");
map.put("age", "36");
hash.putAll("member", map);

System.out.println(hash.entries("member"));

8.1.2.5. List

ListOperations<String, Object> list = redisTemplate.opsForList();
list.rightPush("books", "Linux");
list.rightPush("books", "Java");
System.out.println(list.range("books", 0, 1));

8.1.2.6. 过期时间未执行

Spring Redis 中设置过期时间方法如下

设置 key
redisTemplate.opsForValue().setIfAbsent("key", "value");
设置过期时间
redisTemplate.expire("key", 30000, TimeUnit.MILLISECONDS);
释放 key
redisTemplate.delete("key");

这样存在一个问题，当程序运行一半被强行终止，可能导致setIfAbsent运行完成，但是expire未被执行，这样 key 便永远不会释放。解决方案如下，使用RedisCallback执行原生 Redis 命令。

String result = redisTemplate.execute(new RedisCallback<String>() {
    @Override
    public String doInRedis(RedisConnection connection) throws DataAccessException {
        JedisCommands commands = (JedisCommands) connection.getNativeConnection();
        return commands.set(key, value, "NX", "PX", expire);
    }
});

8.1.2.7. 存储 Json 对象

8.1.2.7.1. 集成 RedisTemplate 定义新类 JsonRedisTemplate

package cn.netkiller.wallet.redis;

import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import com.fasterxml.jackson.databind.ObjectMapper;

public class JsonRedisTemplate extends RedisTemplate<String, Object> {

    public JsonRedisTemplate(RedisConnectionFactory connectionFactory, ObjectMapper objectMapper, Class<?> valueType) {
        RedisSerializer<String> stringSerializer = new StringRedisSerializer();
        super.setKeySerializer(stringSerializer);
        super.setHashKeySerializer(stringSerializer);
        super.setHashValueSerializer(stringSerializer);
        Jackson2JsonRedisSerializer<?> jsonRedisSerializer = new Jackson2JsonRedisSerializer<>(valueType);
        jsonRedisSerializer.setObjectMapper(objectMapper);
        super.setValueSerializer(jsonRedisSerializer);
        super.setConnectionFactory(connectionFactory);
        super.afterPropertiesSet();
    }
}
8.1.2.7.2. 配置 Redis

package cn.netkiller.wallet.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.listener.ChannelTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.listener.adapter.MessageListenerAdapter;

import com.fasterxml.jackson.databind.ObjectMapper;

import cn.netkiller.wallet.redis.JsonRedisTemplate;
import cn.netkiller.wallet.redis.RedisMessageSubscriber;

@Configuration
public class RedisConfig {

    public RedisConfig() {
    }

    @Bean
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory connectionFactory) {
        StringRedisTemplate redisTemplate = new StringRedisTemplate();
        redisTemplate.setConnectionFactory(connectionFactory);
        return redisTemplate;
    }

    @Bean
    public MessageListenerAdapter messageListener() {
        return new MessageListenerAdapter(new RedisMessageSubscriber());
    }

    @Bean
    public ChannelTopic topic() {
        return new ChannelTopic("demo");
    }

    @Bean
    public RedisMessageListenerContainer redisContainer(RedisConnectionFactory connectionFactory, MessageListenerAdapter messageListener) {
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();

        container.setConnectionFactory(connectionFactory);
        container.addMessageListener(messageListener(), topic());
        container.addMessageListener(messageListener(), new ChannelTopic("test"));
        return container;
    }

    @Bean
    public ObjectMapper objectMapper() {
        return new ObjectMapper();
    }

    @Bean
    public JsonRedisTemplate jsonRedisTemplate(RedisConnectionFactory connectionFactory, ObjectMapper objectMapper) {
        return new JsonRedisTemplate(connectionFactory, objectMapper, Object.class);
    }

}
8.1.2.7.3. 测试

package cn.netkiller.wallet.restful;

import java.io.IOException;
import java.util.UUID;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.listener.ChannelTopic;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import cn.netkiller.wallet.pojo.RestfulResponse;
import cn.netkiller.wallet.redis.JsonRedisTemplate;
import cn.netkiller.wallet.redis.RedisMessagePublisher;

@RestController
public class TestRestController {
    private static final Logger logger = LoggerFactory.getLogger(TestRestController.class);

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private JsonRedisTemplate jsonRedisTemplate;

    public TestRestController() {

    }

    @GetMapping("/version")
    public String version() throws IOException {
        Web3ClientVersion web3ClientVersion = web3j.web3ClientVersion().send();
        String clientVersion = web3ClientVersion.getWeb3ClientVersion();
        logger.info(clientVersion);
        return clientVersion;
    }

    @GetMapping("/pub/demo")
    public String pub() {

        RedisMessagePublisher publisher = new RedisMessagePublisher(stringRedisTemplate, new ChannelTopic("demo"));
        String message = "Message " + UUID.randomUUID();
        publisher.publish(message);
        return message;
    }

    @GetMapping("/pub/test")
    public String pub(@RequestParam String message) {

        RedisMessagePublisher publisher = new RedisMessagePublisher(stringRedisTemplate, new ChannelTopic("test"));
        publisher.publish(message);
        return message;
    }

    @GetMapping("/pub/json")
    public RestfulResponse pubJson() {
        RestfulResponse restfulResponse = new RestfulResponse(true, 0, null, null);
        jsonRedisTemplate.opsForValue().set("test", restfulResponse);
        jsonRedisTemplate.convertAndSend("test", restfulResponse);
        return restfulResponse;
    }
}

8.1.3. Spring Data Redis - Repository Examples

8.1.3.1. @EnableRedisRepositories 启动 Redis 仓库

package api.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.repository.configuration.EnableRedisRepositories;

@Configuration
@EnableRedisRepositories
public class CachingConfigurer {

}

8.1.3.2. 定义 Domain 类

package api.domain;

import java.util.List;

import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.Reference;
import org.springframework.data.redis.core.RedisHash;
import org.springframework.data.redis.core.index.Indexed;

@RedisHash("persons")
public class Person {

    public enum Gender {
        FEMALE, MALE
    }

    @Id
    private String id;

    @Indexed
    private String firstname;
    @Indexed
    private String lastname;

    private Gender gender;
    private Address address;

    @Reference
    private List<Person> children;

    public Person() {
        // TODO Auto-generated constructor stub
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getFirstname() {
        return firstname;
    }

    public void setFirstname(String firstname) {
        this.firstname = firstname;
    }

    public String getLastname() {
        return lastname;
    }

    public void setLastname(String lastname) {
        this.lastname = lastname;
    }

    public Gender getGender() {
        return gender;
    }

    public void setGender(Gender gender) {
        this.gender = gender;
    }

    public Address getAddress() {
        return address;
    }

    public void setAddress(Address address) {
        this.address = address;
    }

    public List<Person> getChildren() {
        return children;
    }

    public void setChildren(List<Person> children) {
        this.children = children;
    }

    @Override
    public String toString() {
        return "Person [id=" + id + ", firstname=" + firstname + ", lastname=" + lastname + ", gender=" + gender + ", address=" + address + ", children=" + children + "]";
    }

}

package api.domain;

import org.springframework.data.geo.Point;
import org.springframework.data.redis.core.index.GeoIndexed;
import org.springframework.data.redis.core.index.Indexed;

public class Address {

    private @Indexed String city;
    private String country;
    private @GeoIndexed Point location;

    public Address(String city, String country, Point location) {
        this.city = city;
        this.country = country;
        this.location = location;
    }

    public String getCity() {
        return city;
    }

    public void setCity(String city) {
        this.city = city;
    }

    public String getCountry() {
        return country;
    }

    public void setCountry(String country) {
        this.country = country;
    }

    public Point getLocation() {
        return location;
    }

    public void setLocation(Point location) {
        this.location = location;
    }

}

8.1.3.3. Repository 接口

package api.repository;

import java.util.List;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.geo.Circle;
import org.springframework.data.repository.CrudRepository;

import api.domain.Person;

public interface PersonRepository extends CrudRepository<Person, String> {
    List<Person> findByLastname(String lastname);

    Page<Person> findPersonByLastname(String lastname, Pageable page);

    List<Person> findByFirstnameAndLastname(String firstname, String lastname);

    List<Person> findByFirstnameOrLastname(String firstname, String lastname);

    List<Person> findByAddress_City(String city);

    List<Person> findByAddress_LocationWithin(Circle circle);
}

8.1.3.4. 测试代码

package api.restful;

import java.util.Arrays;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.geo.Point;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import api.domain.Person;
import api.domain.Address;

import api.repository.PersonRepository;

@RestController
@RequestMapping("/test")
public class TestRestController {
    private static final Logger logger = LoggerFactory.getLogger(TestRestController.class);

    @Autowired
    private PersonRepository personRepository;

    public TestRestController() {

    }

    @GetMapping("/redis")
    public Person redis() {

        Person children = new Person();
        children.setFirstname("Lisa");
        children.setLastname("Chen");
        children.setGender(Person.Gender.FEMALE);

        Person person = new Person();
        person.setFirstname("Neo");
        person.setLastname("Chen");
        person.setGender(Person.Gender.MALE);

        // List<Person> childrens = new ArrayList<Person>();

        person.setChildren(Arrays.asList(children));

        Point point = new Point(Double.valueOf("28.352734"), Double.valueOf("32.807382"));
        Address address = new Address("Shenzhen", "China", point);
        person.setAddress(address);
        personRepository.save(person);
        return person;
    }

}


8.2. Spring Data MongoDB

https://docs.spring.io/spring-data/mongodb/docs/current/reference/html/

8.2.1. Example Spring Data MongoDB

8.2.1.1. pom.xml

注意Spring4 与 1.9.1.RELEASE有兼容性问题，日志提示 Error creating bean with name 'mongoTemplate' defined in ServletContext resource

        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-mongodb</artifactId>
            <version>1.8.1.RELEASE</version>
        </dependency>

8.2.1.2. springframework-servlet.xml

    <mongo:db-factory id="mongoDbFactory" host="${mongo.host}" port="${mongo.port}" dbname="${mongo.database}" />
    <!-- username="${mongo.username}" password="${mongo.password}" -->

    <bean id="mongoTemplate" class="org.springframework.data.mongodb.core.MongoTemplate">
        <constructor-arg name="mongoDbFactory" ref="mongoDbFactory"/>
    </bean>

    <mongo:mapping-converter id="converter" db-factory-ref="mongoDbFactory"/>
    <bean id="gridFsTemplate" class="org.springframework.data.mongodb.gridfs.GridFsTemplate">
      <constructor-arg ref="mongoDbFactory"/>
      <constructor-arg ref="converter"/>
    </bean>

例 8.2. Spring Data MongoDB - springframework-servlet.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:mongo="http://www.springframework.org/schema/data/mongo"
    xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/data/mongo
        http://www.springframework.org/schema/data/mongo/spring-mongo-1.5.xsd
        ">

    <mvc:resources location="/images/" mapping="/images/**" />
    <mvc:resources location="/css/" mapping="/css/**" />
    <mvc:resources location="/js/" mapping="/js/**" />
    <mvc:resources location="/zt/" mapping="/zt/**" />
    <mvc:resources location="/sm/" mapping="/sm/**" />
    <mvc:resources location="/module/" mapping="/module/**" />

    <context:component-scan base-package="cn.netkiller.controller" />
    <!-- <context:property-placeholder location="classpath:resources/development.properties" /> -->
    <mvc:annotation-driven />

    <bean id="viewResolver" class="org.springframework.web.servlet.view.UrlBasedViewResolver">
        <property name="viewClass" value="org.springframework.web.servlet.view.JstlView" />
        <property name="prefix" value="/WEB-INF/jsp/" />
        <property name="suffix" value=".jsp" />
    </bean>

    <bean id="configuracion" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="location" value="classpath:resources/development.properties" />
    </bean>

    <!-- MongoDB Connection Factory -->
    <mongo:db-factory id="mongoDbFactory" host="${mongo.host}" port="${mongo.port}" dbname="${mongo.database}" />
    <!-- username="${mongo.username}" password="${mongo.password}" -->

    <!-- MongoDB template definition -->
    <bean id="mongoTemplate" class="org.springframework.data.mongodb.core.MongoTemplate">
        <constructor-arg name="mongoDbFactory" ref="mongoDbFactory"/>
    </bean>

    <!-- MongoDB GridFS template definition -->
    <mongo:mapping-converter id="converter" db-factory-ref="mongoDbFactory"/>
    <bean id="gridFsTemplate" class="org.springframework.data.mongodb.gridfs.GridFsTemplate">
      <constructor-arg ref="mongoDbFactory"/>
      <constructor-arg ref="converter"/>
    </bean>

    <!-- Redis Connection Factory -->
    <bean id="jedisConnFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" p:host-name="192.168.2.1" p:port="6379" p:use-pool="true" />

    <!-- redis template definition -->
    <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate" p:connection-factory-ref="jedisConnFactory" />

</beans>

development.properties 配置内容

mongo.host=192.168.4.1
mongo.port=27017
mongo.username=test
mongo.password=passw0rd
mongo.database=website

8.2.1.3. POJO

package cn.netkiller.pojo;

import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;

@Document(collection = "tracker")
public class Tracker {
    @Id
    private String id;
    private String name;
    private String unique;
    private String hostname;
    private String referrer;
    private String href;

    public Tracker() {
        // TODO Auto-generated constructor stub
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getUnique() {
        return unique;
    }

    public void setUnique(String unique) {
        this.unique = unique;
    }

    public String getHostname() {
        return hostname;
    }

    public void setHostname(String hostname) {
        this.hostname = hostname;
    }

    public String getReferrer() {
        return referrer;
    }

    public void setReferrer(String referrer) {
        this.referrer = referrer;
    }

    public String getHref() {
        return href;
    }

    public void setHref(String href) {
        this.href = href;
    }

    @Override
    public String toString() {
        return "Tracker [id=" + id + ", name=" + name + ", unique=" + unique + ", hostname=" + hostname + ", referrer=" + referrer + ", href=" + href + "]";
    }

}

8.2.1.4. Controller

package cn.netkiller.controller;

import cn.netkiller.pojo.Tracker;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.stereotype.*;
import org.springframework.web.bind.annotation.*;

@Controller
public class TrackerController {

    @Autowired
    private MongoTemplate mongoTemplate;

    public TrackerController() {

    }

    @RequestMapping("/tracker/test")
    @ResponseBody
    String hello() {
        return "Hello World!";
    }

    @RequestMapping("/tracker")
    @ResponseBody
    String execute() {
        Tracker tracker = new Tracker();
        tracker.setName("test");
        tracker.setUnique("111223456");
        tracker.setHostname("www.example.com");
        tracker.setHref("http://example.com/test.html");
        tracker.setReferrer("http://example.com/");
        this.mongoTemplate.insert(tracker);

        return tracker.toString();
    }

}

8.2.1.5. 查看测试结果

> db.tracker.find();
{ "_id" : ObjectId("5757c0b92c526a6bda5eea3a"), "_class" : "cn.netkiller.repositories.Tracker", "name" : "test", "unique" : "111223456", "hostname" : "www.example.com", "referrer" : "http://example.com/", "href" : "http://example.com/test.html" }

8.2.1.6. 条件查询

    @RequestMapping("/read/name/{name}")
    public ArrayList<Tracker> sort(@PathVariable String name) {

        Query query = new Query(Criteria.where("name").is(name));

        ArrayList<Tracker> trackers =  (ArrayList<Tracker>) mongoTemplate.find(query, Tracker.class);
        return trackers;
    }

8.2.2. @Document

复杂的 @Document 数据类型定义

package cn.netkiller.domain;

import java.util.Date;
import java.util.List;
import java.util.Map;

import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;

@Document
public class MultilevelDirectSellingTradingRebate {

    public enum Type {
        POINT, CASH, GIFT
    }

    public enum Rebate {
        DIRECT, INDIRECT
    }

    public enum Status {
        New, Rejected, Approved
    }

    @Id
    private String id;
    public String name;
    public Date beginDate;
    public Date endDate;
    public double lowAmount;
    public double highAmount;
    public Type type;
    public Status status = Status.New;
    public List<Map<String, Map<?, ?>>> product;

    @Override
    public String toString() {
        return "MultilevelDirectSellingTradingRebate [id=" + id + ", name=" + name + ", beginDate=" + beginDate
                + ", endDate=" + endDate + ", lowAmount=" + lowAmount + ", highAmount=" + highAmount + ", type=" + type
                + ", status=" + status + ", product=" + product + "]";
    }

}

8.2.2.1. 指定表名

默认使用 class 作为表名

@Document
public class Multilevel {
    ...
    ...
}

指定特别表名

@Document(collection = "author")

8.2.2.2. @Id

    @Id
    private String id;

8.2.2.3. @Version

    @Version
    private Long version;

8.2.2.4. @Field 定义字段名

@Field("url")
private String link;

8.2.2.5. @Indexed

https://docs.spring.io/spring-data/mongodb/docs/current/api/org/springframework/data/mongodb/core/index/Indexed.html

索引

8.2.2.5.1. 普通索引

@Indexed
8.2.2.5.2. 唯一索引

@Indexed(unique=true)
8.2.2.5.3. 索引排序方式

@Indexed(name = "first_name_index", direction = IndexDirection.DESCENDING)
8.2.2.5.4. 稀疏索引

稀疏索引允许唯一索引存在多个 null 值

    @Indexed(unique = true, sparse = true)
    private String uuid;

    @Indexed(unique = true, sparse = true)
    private String transactionId = null;
8.2.2.5.5. 索引过期时间设置

@Indexed(name = "expire_after_seconds_index", expireAfterSeconds = 10)
private LocalDateTime updateDate;

8.2.2.6. @CompoundIndex 复合索引

8.2.2.6.1. 普通复合索引

@Document
@CompoundIndexes({
    @CompoundIndex(name = "email_age", def = "{'email.id' : 1, 'age': 1}")
})
public class User {
    //
}

@Document
@CompoundIndexes({
  @CompoundIndex(def = "{'firstName':1, 'salary':-1}", name = "compound_index_1"),
  @CompoundIndex(def = "{'secondName':1, 'profession':1}", name = "compound_index_2")
})
public class Person {
  @Id private String id;
  private String firstName;
  private String secondName;
  private LocalDateTime dateOfBirth;
  private Address address;
  private String profession;
  private int salary;
  // constructor
  // getters and setters
}
8.2.2.6.2. 唯一复合索引

唯一复合索引：楼层和房号不能相同，不然就是同一个房间了

@CompoundIndexes({
    @CompoundIndex(name = "floor_num", def = "{'floor' : 1, 'num': 1}",unique=true)
})

不允许同名

@CompoundIndexes({ @CompoundIndex(name = "username", def = "{'firstname' : 1, 'lastname': 1}", unique = true) })

8.2.2.7. @TextIndexed

@Document(language = "spanish")
class SomeEntity {

    @TextIndexed String foo;

    @Language String lang;

    Nested nested;
}

class Nested {

    @TextIndexed(weight=5) String bar;
    String roo;
}

8.2.2.8. @GeoSpatialIndex 地理位置索引

点数据索引

    @GeoSpatialIndexed
    private GeoJsonPoint location; // GPS 定位信息

2D 数据索引

@GeoSpatialIndexed(type = GeoSpatialIndexType.GEO_2DSPHERE)

8.2.2.9. @Transient 丢弃数据，不存到 mongodb

public class User {

    @Transient
    private Integer age;

    // standard getter and setter

}

8.2.2.10. @DBRef 做外外键引用

8.2.2.10.1. Article 类

package cn.netkiller.api.domain;

import java.util.List;

import org.springframework.data.mongodb.core.mapping.DBRef;
import org.springframework.data.mongodb.core.mapping.Document;

@Document
public class Article {

    private String title; // 名称
    private String description; // 描述
    private String tag; // 类型
    @DBRef
    private List<Hypermedia> hypermedia; // 图片，视频

    public Article() {
        // TODO Auto-generated constructor stub
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public String getTag() {
        return tag;
    }

    public void setTag(String tag) {
        this.tag = tag;
    }

    public List<Hypermedia> getHypermedia() {
        return hypermedia;
    }

    public void setHypermedia(List<Hypermedia> hypermedia) {
        this.hypermedia = hypermedia;
    }

    @Override
    public String toString() {
        return "Article [title=" + title + ", description=" + description + ", tag=" + tag + ", hypermedia=" + hypermedia + "]";
    }

}
8.2.2.10.2. Hypermedia 类

package api.domain;

import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;

@Document
public class Hypermedia {

    @Id
    private String id;
    private String hash;
    private String name;
    private String size;

    public Hypermedia() {
        // TODO Auto-generated constructor stub
    }

    public Hypermedia(String hash, String name, String size) {
        this.hash = hash;
        this.name = name;
        this.size = size;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getHash() {
        return hash;
    }

    public void setHash(String hash) {
        this.hash = hash;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSize() {
        return size;
    }

    public void setSize(String size) {
        this.size = size;
    }

    @Override
    public String toString() {
        return "Hypermedia [id=" + id + ", hash=" + hash + ", name=" + name + ", size=" + size + "]";
    }

}

如果你只查询 Article 表，不会单独查询 Hypermedia，返回结果可以掩藏 Id ，不写 get/set 方法即可。

package cn.netkiller.api.domain;

import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;

@Document
public class Hypermedia {

    @Id
    private String id;
    private String hash;
    private String name;
    private String size;

    public Hypermedia() {
        // TODO Auto-generated constructor stub
    }

    public Hypermedia(String hash, String name, String size) {
        this.hash = hash;
        this.name = name;
        this.size = size;
    }

    public String getHash() {
        return hash;
    }

    public void setHash(String hash) {
        this.hash = hash;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSize() {
        return size;
    }

    public void setSize(String size) {
        this.size = size;
    }

    @Override
    public String toString() {
        return "Hypermedia [hash=" + hash + ", name=" + name + ", size=" + size + "]";
    }

}
8.2.2.10.3. MongoRepository

package cn.netkiller.api.repository;

import org.springframework.data.mongodb.repository.MongoRepository;

import api.domain.Article;

public interface ArticleRepository extends MongoRepository<Article, String> {

}

package cn.netkiller.api.repository;

import org.springframework.data.mongodb.repository.MongoRepository;

import api.domain.Hypermedia;

public interface HypermediaRepository extends MongoRepository<Hypermedia, String> {

}
8.2.2.10.4. RestController

package cn.netkiller.api.restful;

import java.util.ArrayList;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import api.domain.Article;
import api.domain.Hypermedia;
import api.repository.ArticleRepository;
import api.repository.HypermediaRepository;

@RestController
@RequestMapping("/article")
public class ArticleRestController {

    @Autowired
    private ArticleRepository articleRepository;

    @Autowired
    private HypermediaRepository hypermediaRepository;

    public ArticleRestController() {
        // TODO Auto-generated constructor stub
    }

    @GetMapping("/save")
    public Article save() {

        Article article = new Article();
        article.setTitle("标题");
        article.setDescription("摘要");
        article.setTag("标签");

        Hypermedia hypermedia = new Hypermedia("AAA", "BBB", "CCC");
        hypermediaRepository.save(hypermedia);

        List<Hypermedia> hypermedias = new ArrayList<Hypermedia>();
        hypermedias.add(hypermedia);

        article.setHypermedia(hypermedias);

        articleRepository.save(article);

        System.out.println(article);

        return article;
    }

}
8.2.2.10.5. 运行结果

neo@MacBook-Pro ~ % curl -s -H "Accept: application/json" -H "Content-Type: application/json" -H "Authorization: Bearer ${TOKEN}" -X GET ${URL}/article/save | jq
{
  "title": "标题",
  "description": "摘要",
  "tag": "标签",
  "hypermedia": [
    {
      "hash": "AAA",
      "name": "BBB",
      "size": "CCC"
    }
  ]
}

MongoDB 结果

db.getCollection('article').find({})

/* 1 */
{
    "_id" : ObjectId("5bab66f8c92782395817cb05"),
    "title" : "标题",
    "description" : "摘要",
    "tag" : "标签",
    "hypermedia" : [
        {
            "$ref" : "hypermedia",
            "$id" : ObjectId("5bab66f8c92782395817cb04")
        }
    ],
    "_class" : "cn.netkiller.api.domain.Article"
}

db.getCollection('hypermedia').find({})

/* 1 */
{
    "_id" : ObjectId("5bab66b9c927823951f4f5fe"),
    "hash" : "AAA",
    "name" : "BBB",
    "size" : "CCC",
    "_class" : "api.domain.Hypermedia"
}

8.2.2.11. @DateTimeFormat

@DateTimeFormat( pattern = "yyyy-MM-dd" )
private Date birthday

@DateTimeFormat(iso = DateTimeFormat.ISO.NONE)
private final Calendar datetime;

@DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
private Date date;

@DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
private Date createdDate = new Date();

8.2.2.12. @JsonFormat 格式化 json 时间格式

默认 json 中的时间格式是这样的

"createDate":"2018-09-11T07:34:20.106+0000","updateDate":"2018-09-11T07:34:20.106+0000"

@JsonFormat 可以格式化 json 返回的时间格式。

@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")

格式化后

"createDate":"2018-09-11 07:42:44","updateDate":"2018-09-11 07:42:44"

解决时区问题，MongoDb 默认使用UTC,显示时间相差8小时

@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
private Date createdDate = new Date();

8.2.2.13. @NumberFormat

                @NumberFormat(style=Style.CURRENCY)
                private double money;

8.2.2.14. 在 @Document 中使用 Enum 类型

    public enum Type {
        POINT, CASH, GIFT
    }

    public enum Rebate {
        DIRECT, INDIRECT
    }

    public enum Status {
        New, Rejected, Approved
    }

枚举类型的赋值方法

        MultilevelDirectSellingTradingRebate multilevelDirectSellingTradingRebate = new MultilevelDirectSellingTradingRebate();
        multilevelDirectSellingTradingRebate.name = "TEST";
        multilevelDirectSellingTradingRebate.beginDate = new Date();
        multilevelDirectSellingTradingRebate.endDate = new Date();
        multilevelDirectSellingTradingRebate.lowAmount = 1.5d;
        multilevelDirectSellingTradingRebate.highAmount = 100d;
        multilevelDirectSellingTradingRebate.type = Type.CASH;

8.2.2.15. 在 @Document 中定义数据结构 List/Map

    public List<Map<String, Map<?, ?>>> product;

下面是数据集结构的赋值例子

    Map<Enum<Rebate>, Double> rebate = new HashMap<Enum<Rebate>, Double>();

    rebate.put(Rebate.DIRECT, 10.05d);
    rebate.put(Rebate.INDIRECT, 6.05d);

    Map<String, Map<?, ?>> prod1 = new HashMap<String, Map<?, ?>>();
    prod1.put("USDRMB", rebate);

    List<Map<String, Map<?, ?>>> products = new ArrayList<Map<String, Map<?, ?>>>();
    products.add(prod1);
    multilevelDirectSellingTradingRebate.product = products;

8.2.2.16. GeoJson 数据类型

    @GeoSpatialIndexed
    private GeoJsonPoint location; // GPS 地址位置

location = new GeoJsonPoint(Double.valueOf(longitude), Double.valueOf(latitude));

8.2.3. MongoRepository

8.2.3.1. 扫描仓库接口

默认不需要设置，除非你的包不在当前包下，或者命令不是 repository。

@EnableMongoRepositories(basePackages = "cn.netkiller.repository")

8.2.3.2. findAll()

    @RequestMapping(value = "read", method = RequestMethod.GET, produces = { "application/xml", "application/json" })
    @ResponseStatus(HttpStatus.OK)
    public List<Withdraw> read() {
        return repository.findAll();
    }

8.2.3.3. deleteAll()

repository.deleteAll();

8.2.3.4. save()

repository.save(new City("Shenzhen", "China"));

8.2.3.5. count()

    @RequestMapping("count")
    public long count() {
        return repository.count();
    }

8.2.3.6. exists() 判断是否存在

boolean isExists = userRepository.exists(user.getId());

8.2.3.7. existsById()

memberRepository.existsById(id);

8.2.3.8. findByXXXX

List<User> findByName(String name);

List<User> users = userRepository.findByName("Eric");

8.2.3.9. findAll with OrderBy

8.2.3.9.1. order by boolean 布尔型数据排序

因为 boolean 数据 true = 1, false = 0 所以 ASC false 会排列在前面。所有很多时候而我们需要 DESC 排序

List<ShippingAddress> shippingAddress = shippingAddressRepository.findAllByMemberIdOrderByDefaultsDesc(memberId);

8.2.3.10. findAll with Sort

List<User> users = userRepository.findAll(new Sort(Sort.Direction.ASC, "name"));

8.2.3.11. FindAll with Pageable

Pageable pageable = PageRequest.of(0, 1);
Page<User> page = userRepository.findAll(pageable);
List<User> users = pages.getContent();
8.2.3.11.1. PageRequest - springboot 1.x 旧版本

Page<User> findByLastname(String lastname, Pageable pageable);

    @RequestMapping(value = "read/{size}/{page}", method = RequestMethod.GET, produces = { "application/xml", "application/json" })
    @ResponseStatus(HttpStatus.OK)
    public List<Withdraw> readPage(@PathVariable int size, @PathVariable int page){
        PageRequest pageRequest = new PageRequest(page-1,size);
        return repository.findAll(pageRequest).getContent();
    }

URL翻页参数，每次返回10条记录

                    第一页 http://localhost:8080/v1/withdraw/read/10/1.json
                    第二页 http://localhost:8080/v1/withdraw/read/10/2.json
                    ...
                    第五页 http://localhost:8080/v1/withdraw/read/10/5.json

8.2.3.12. StartingWith 和 EndingWith

List<User> findByNameStartingWith(String regexp);
List<User> findByNameEndingWith(String regexp);

List<User> users = userRepository.findByNameStartingWith("N");
List<User> users = userRepository.findByNameEndingWith("o");

8.2.3.13. Between

数值范围

List<User> findByAgeBetween(int ageGT, int ageLT);

List<User> users = userRepository.findByAgeBetween(20, 50);

日期范围，取值 e.g. 2018-07-04 00:00:00 and 2018-07-04 23:59:59

List<Member> findByCreatedDateBetween(DateTime start, DateTime end);

List<Member> findByCreatedDate(@Temporal(TemporalType.DATE) Date date);

8.2.3.14. Before / After

List<Assets> findAllByUpdateDateBefore(Date yesterday);
List<Assets> findAllByUpdateDateBeforeAndStatus(Date yesterday, String status);

List<Assets> findAllByUpdateDateAfter(Date yesterday);

8.2.3.15. @Query

public interface PersonRepository extends MongoRepository<Person, String> {
    @Query("{ 'name' : ?0 }")
    List<Person> findWithQuery(String userId);
}

    @Query(value = "{'statusHistories':{$elemMatch:{'status':{$in:['PROCESSABLE']}}},'created' : { '$gt' : { '$date' : ':?0' } , '$lt' : { '$date' : ':?1'}}}", count = true)
    Long countMe(@Param("dateFrom") Date datefrom, @Param("dateTo") Date dateTo);

8.2.4. mongoTemplate

导入与模板相关的包

import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;

注入 MongoTemplate 对象

    @Autowired
    private MongoTemplate mongoTemplate;

8.2.4.1. Save 保存

User user = new User();
user.setName("Netkiller");
mongoTemplate.save(user, "user");

更新数据

user = mongoTemplate.findOne(Query.query(Criteria.where("name").is("Jam")), User.class);
user.setName("Neo");
mongoTemplate.save(user, "user");

8.2.4.2. Insert

User user = new User();
user.setName("Neo");
mongoTemplate.insert(user, "user");

BSONObject personBsonObj = BasicDBObjectBuilder.start()
                .add("name","Neo Chen")
                .add("age",27)
                .add("address",null).get();

mongoTemplate.insert(personBsonObj,"personCollection");

document in the db:

db.personCollection.findOne().pretty();
{"age":21,"name":"John Doe";"address":null}*

8.2.4.3. updateFirst 修改符合条件第一条记录

updateFirst 修改符合条件第一条记录

Query query = new Query();
query.addCriteria(Criteria.where("name").is("Neo"));
Update update = new Update();
update.set("name", "Netkiller");
mongoTemplate.updateFirst(query, update, User.class);

8.2.4.4. updateMulti 修改符合条件的所有

更新所有数据

Query query = new Query();
query.addCriteria(Criteria.where("name").is("Neo"));
Update update = new Update();
update.set("name", "Jerry");
mongoTemplate.updateMulti(query, update, User.class);

8.2.4.5. 查找并保存

Query query = new Query();
query.addCriteria(Criteria.where("name").is("Luck"));
Update update = new Update();
update.set("name", "Lisa");
User user = mongoTemplate.findAndModify(query, update, User.class);

8.2.4.6. upsert - 修改符合条件时如果不存在则添加

Query query = new Query();
query.addCriteria(Criteria.where("name").is("Green"));
Update update = new Update();
update.set("name", "Tom");
mongoTemplate.upsert(query, update, User.class);

mongoTemplate.upsert(new Query(Criteria.where("age").is("18")), new Update().set("name", "neo"), collectionName);

8.2.4.7. 删除

User user = new User();
user.setId("5bbf091efd9557069c4a25c5")
mongoTemplate.remove(user, "user");

8.2.4.8. 查找一条数据

public Person findOneByName(String name) {
   Query query = new Query();
   query.addCriteria(Criteria.where("name").is(name));
   return mongoTemplate.findOne(query, Person.class);
}

8.2.4.9. 查找所有数据

public List<Person> findByName(String name) {
   Query query = new Query();
   query.addCriteria(Criteria.where("name").is(name));
   return mongoTemplate.find(query, Person.class);
}

8.2.4.10. Query

8.2.4.10.1. 翻页

public List<Person> getAllPersonPaginated(int pageNumber, int pageSize) {
   Query query = new Query();
   query.skip(pageNumber * pageSize);
   query.limit(pageSize);
   return mongoTemplate.find(query, Person.class);
}
8.2.4.10.2. between

实现一个区间条件 new Criteria("createdDate").gte(beginDate).lte(endDate)

    public boolean AccountDeposit(Date beginDate, Date endDate) {

        MatchOperation matchOperation = match(new Criteria("createdDate").gte(beginDate).lte(endDate));
        GroupOperation groupOperation = group("loginname").sum("amount").as("amount");
        SortOperation sortOperation = sort(new Sort(Direction.ASC, "loginname"));

        Aggregation aggregation = newAggregation(matchOperation, groupOperation, sortOperation);
        AggregationResults<AccountSettlementDetails> results = mongoTemplate.aggregate(aggregation, AccountSettlementDetails.class, AccountSettlementDetails.class);

        if (results.getMappedResults() != null) {
            log.info(results.getRawResults().get("result").toString());
            for (AccountSettlementDetails settlementDetails : results.getMappedResults()) {

                log.info("{}", settlementDetails.toString());

            }
        }
        return true;
    }

8.2.4.11. Criteria

8.2.4.11.1. is

Query query = new Query();
query.addCriteria(Criteria.where("name").is("Neo"));
List<User> users = mongoTemplate.find(query, User.class);
8.2.4.11.2. Regex 正则表达式搜索

查询以N开头的名字

Query query = new Query();
query.addCriteria(Criteria.where("name").regex("^N"));
List<User> users = mongoTemplate.find(query,User.class);

查询以o结尾的名字

Query query = new Query();
query.addCriteria(Criteria.where("name").regex("o$"));
List<User> users = mongoTemplate.find(query, User.class);
8.2.4.11.3. lt 和 gt

查询年龄小于 < 30 并 > 20 的用户

Query query = new Query();
query.addCriteria(Criteria.where("age").lt(30).gt(20));
List<User> users = mongoTemplate.find(query,User.class);

查找日期范围

Date start = DateUtil.convertStringToDateTime("2014-02-10 20:38:44");
Date end = DateUtil.convertStringToDateTime("2014-02-10 20:38:50");

Query query = new Query();
Criteria criteria = Criteria.where("delflag").is(false);
criteria.and("modifyDate").gte(start).lte(end);
query.addCriteria(criteria);
query.limit(10);
8.2.4.11.4. exists()

Query query = new Query();
query.addCriteria(
    new Criteria().andOperator(
        Criteria.where("field1").exists(true),
        Criteria.where("field1").ne(false)
    )
);

List<Foo> result = mongoTemplate.find(query, Foo.class);
System.out.println("query - " + query.toString());

for (Foo foo : result) {
    System.out.println("result - " + foo);
}
8.2.4.11.5. 包含

public List<Person> findByFavoriteBooks(String favoriteBook) {
   Query query = new Query();
   query.addCriteria(Criteria.where("favoriteBooks").in(favoriteBook));
   return mongoTemplate.find(query, Person.class);
}

8.2.4.12. Update

8.2.4.12.1. set

Update update = new Update();
update.set("name", "Netkiller");
8.2.4.12.2. 追加数据

    Query query = Query.query(Criteria.where("id").is("5bbf091efd9557069c4a25c5"));
    Update update = new Update().push("author", new Author("neo", "chen"));
    mongoTemplate.updateFirst(query, update, Article.class);
8.2.4.12.3. 更新数据

    Query query = Query.query(Criteria.where("classId").is("1").and("Students.studentId").is("1"));
    Update update = Update.update("Students.$.name", "lisa");
    mongoTemplate.upsert(query, update, "class");
8.2.4.12.4. 删除数据

    Query query = Query.query(Criteria.where("classId").is("1").and("Students.studentId").is("3"));
    Update update = new Update();
    update.unset("Students.$");
    mongoTemplate.updateFirst(query, update, "class");
8.2.4.12.5. inc

public void updateMultiplePersonAge() {
   Query query = new Query();
   Update update = new Update().inc("age", 1);
   mongoTemplate.findAndModify(query, update, Person.class);;
}
8.2.4.12.6. update.addToSet

Query query = Query.query(Criteria.where("classId").is("1"));
Student student = new Student("1", "lisa", 3, "girl");
Update update = new Update();
update.addToSet("Students", student);
mongoTemplate.upsert(query, update, "class");

8.2.4.13. BasicUpdate

BasicUpdate 是底层更新可操作，需要手动实现$set等语句

BasicDBObject basicDBObject = new BasicDBObject();
basicDBObject.put("$set", new BasicDBObject("date","2018-09-09"));
Update update = new BasicUpdate(basicDBObject);
mongoTemplate.updateFirst(new Query(Criteria.where("nickname").is("netkiller")), update,collectionName);

8.2.4.14. Sort

按照年龄排序

Query query = new Query();
query.with(new Sort(Sort.Direction.ASC, "age"));
List<User> users = mongoTemplate.find(query,User.class);

8.2.4.15. Query + PageRequest

final Pageable pageableRequest = new PageRequest(0, 2);
Query query = new Query();
query.with(pageableRequest);

8.2.4.16. newAggregation

        MultilevelDirectSellingAccountRewardsSettlementDetails multilevelDirectSellingAccountRewardsSettlementDetails = new MultilevelDirectSellingAccountRewardsSettlementDetails();
        multilevelDirectSellingAccountRewardsSettlementDetails.setLoginname("111");
        multilevelDirectSellingAccountRewardsSettlementDetails.setPhone("111");
        multilevelDirectSellingAccountRewardsSettlementDetails.setRecommenderLoginname("111");
        multilevelDirectSellingAccountRewardsSettlementDetails.setRecommenderPhone("111");
        multilevelDirectSellingAccountRewardsSettlementDetails.setRecommenderName("Neo");
        multilevelDirectSellingAccountRewardsSettlementDetails.setRecommenderType("客户");
        multilevelDirectSellingAccountRewardsSettlementDetails.setAmount(5.02);
        multilevelDirectSellingAccountRewardsSettlementDetails.setCreatedDate(new Date());
        multilevelDirectSellingAccountRewardsSettlementDetailsRepository.save(multilevelDirectSellingAccountRewardsSettlementDetails);

        Date beginDate = this.getToday("00:00:00");
        Date endDate = this.getToday("23:59:59");
        log.info(beginDate.toString() + " ~ " + endDate.toString());

        GroupOperation groupOperation = group("loginname").sum("amount").as("amount");
        MatchOperation matchOperation = match(new Criteria("createdDate").gte(beginDate).lte(endDate));
        SortOperation sortOperation = sort(new Sort(Direction.ASC, "loginname"));

        Aggregation aggregation = newAggregation(matchOperation, groupOperation, sortOperation);
        AggregationResults<MultilevelDirectSellingAccountRewardsSettlementDetails> results = mongoTemplate.aggregate(aggregation, MultilevelDirectSellingAccountRewardsSettlementDetails.class, MultilevelDirectSellingAccountRewardsSettlementDetails.class);
        System.out.println(results.getRawResults().get("result").toString());

8.2.4.17. 创建索引

mongoOps.indexOps(User.class).ensureIndex(new Index().on("name", Direction.ASC));

8.2.4.18. 子对象操作

8.2.4.18.1. List 类型

package cn.netkiller.api.domain;

import java.util.List;

import javax.persistence.Id;
import org.springframework.data.mongodb.core.mapping.Document;

@Document
public class Article {

    @Id
    private String id;
    private String title;
    private String description;

    List<Author> author;
    public static class Author {
        private String id;
        private String firstname;
        private String lastname;

        public Author(String firstname, String lastname) {
            this.firstname = firstname;
            this.lastname = lastname;
        }
    }
}

更新

db.getCollection('foo').update({"author.firstname":"neo"},{"$set":{"author.$.firstname":"netkiller"}})

更新数据

    Query query = Query.query(Criteria.where("author.firstname").is("neo"));
    Update update = new Update().set("author.$.firstname", "netkiller");
    mongoTemplate.updateFirst(query, update, Article.class);

追加数据

    Query query = Query.query(Criteria.where("id").is("5bbf091efd9557069c4a25c5"));
    Update update = new Update().push("author", new Author("neo", "chen"));
    mongoTemplate.updateFirst(query, update, Article.class);

删除数据

        Query query = Query.query(Criteria.where("id").is("5bbf091efd9557069c4a25c5"));
        Update update = new Update().pull("author", new Author("jerry", "lee"));
        mongoTemplate.updateFirst(query, update, Article.class);

8.2.5. GeoJson 反序列化

正常情况下是不需要做反序列化操作的。如花你想测试，打印一些信息可以这样做。

package cn.netkiller.api.config;

import java.io.IOException;

import org.springframework.data.mongodb.core.geo.GeoJsonPoint;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;

public class GeoJsonDeserializer extends JsonDeserializer<GeoJsonPoint> {

    @Override
    public GeoJsonPoint deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {

        final JsonNode tree = jsonParser.getCodec().readTree(jsonParser);
        final String type = tree.get("type").asText();
        final JsonNode coordsNode = tree.get("coordinates");

        System.out.println(tree.toString());
        System.out.println(type);
        System.out.println(coordsNode.toString());

        double x = 0;
        double y = 0;
        if ("Point".equalsIgnoreCase(type)) {
            x = coordsNode.get(0).asDouble();
            y = coordsNode.get(1).asDouble();
        } else {
            System.out.println(String.format("No logic present to deserialize %s ", tree.asText()));
        }

        final GeoJsonPoint point = new GeoJsonPoint(x, y);

        return point;
    }
}

使用 @JsonDeserialize 指定反序列化 Class

@Document
public class Address {
    @Id
    private String id;
    // @GeoSpatialIndexed
    @JsonDeserialize(using = GeoJsonDeserializer.class)
    private GeoJsonPoint location; // GPS 定位信息
}

8.2.6. FAQ

8.2.6.1. location object expected, location array not in correct format; nested exception is com.mongodb.MongoWriteException: location object expected, location array not in correct format

GeoJsonPoint 可能设置了索引，且有些数据部正确。


8.3. Spring Data MySQL

8.3.1. 选择数据库表引擎

正常创建表会使用数据库默认引擎，有时数据库默认引擎并不是我们需要的，通过下面配置可以指定表引擎

# Spring boot 1.x.x
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQLInnoDBDialect

# Spring boot 2.0.2
spring.jpa.hibernate.use-new-id-generator-mappings=true
spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect

8.3.2. @Entity

8.3.2.1. @Table

8.3.2.1.1. catalog

@Table(name="CUSTOMERS",catalog="hibernate")
8.3.2.1.2. schema

配置Schema

@Table(name="tabname", schema="public")
8.3.2.1.3. uniqueConstraints

唯一索引

@Table(name="CUSTOMERS",uniqueConstraints={@UniqueConstraint(columnNames={"name","email"})})

定义多组唯一索引

uniqueConstraints={@UniqueConstraint(columnNames={"name","email"}),@UniqueConstraint(columnNames={"name","age"})}

8.3.2.2. @Id

ID 字段，数据库中的主键。

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", unique = true, nullable = false, insertable = true, updatable = false)
private int id;

字符串做主键

package api.domain;

import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table()
public class TransactionsPostion {

    @Id
    private String address;
    private String startblock;
    private String endblock;

    public TransactionsPostion() {
        // TODO Auto-generated constructor stub
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public String getStartblock() {
        return startblock;
    }

    public void setStartblock(String startblock) {
        this.startblock = startblock;
    }

    public String getEndblock() {
        return endblock;
    }

    public void setEndblock(String endblock) {
        this.endblock = endblock;
    }

}

对应数据库表

CREATE TABLE "transactions_postion" (
  "address" varchar(255) NOT NULL,
  "endblock" varchar(255) DEFAULT NULL,
  "startblock" varchar(255) DEFAULT NULL,
  PRIMARY KEY ("address")
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4

8.3.2.3. @Column

                unique 属性表示该字段是否为唯一标识，默认为false。如果表中有一个字段需要唯一标识，则既可以使用该标记，也可以使用@Table标记中的@UniqueConstraint。
                nullable 属性表示该字段是否可以为null值，默认为true。
                insertable 属性表示在使用“INSERT”脚本插入数据时，是否需要插入该字段的值。
                updatable 属性表示在使用“UPDATE”脚本插入数据时，是否需要更新该字段的值。insertable和updatable属性一般多用于只读的属性，例如主键和外键等。这些字段的值通常是自动生成的。
                columnDefinition属性表示创建表时，该字段创建的SQL语句，一般用于通过Entity生成表定义时使用。
                table 属性表示当映射多个表时，指定表的表中的字段。默认值为主表的表名。
                length 属性表示字段的长度，当字段的类型为varchar时，该属性才有效，默认为255个字符。
                precision 属性和scale属性表示精度，当字段类型为double时，precision表示数值的总长度，scale表示小数点所占的位数。
8.3.2.3.1. 字段长度

字段长度定义

@Column(name="name", length=80, nullable=true)
8.3.2.3.2. 浮点型

    @Column(precision=18, scale=5)
    private BigDecimal principal;

    @Column(name="Price", columnDefinition="Decimal(10,2) default '100.00'")
8.3.2.3.3. 创建于更新控制

    @Column(name = "ctime", nullable = false, insertable = false, updatable = false)
8.3.2.3.4. TEXT 类型

    private String subject;
    @Column(columnDefinition = "TEXT")
    private String content;

8.3.2.4. @Lob 注解属性将被持久化为 Blog 或 Clob 类型

Clob（Character Large Ojects）类型是长字符串类型，具体的java.sql.Clob, Character[], char[] 和 java.lang.String 将被持久化为 Clob 类型。
Blob（Binary Large Objects）类型是字节类型，具体的java.sql.Blob, Byte[], byte[] 和 serializable type 将被持久化为 Blob 类型。
@Lob 持久化为Blob或者Clob类型,根据get方法的返回值不同,自动进行Clob和Blob的转换。
因为这两种类型的数据一般占用的内存空间比较大，所以通常使用延迟加载的方式，与@Basic标记同时使用，设置加载方式为FetchType.LAZY。

@Lob
@Basic(fetch = FetchType.LAZY)
@Column(name=" content", columnDefinition="CLOB", nullable=true)
public String getContent() {
    return content;
}

8.3.2.5. @NotNull 不能为空声明

    @NotNull
    public String username;

8.3.2.6. @JsonIgnore 返回json是不含有该字段

    @JsonIgnore
    private String entityName = this.getClass().getSimpleName();

8.3.2.7. @Temporal 日期定义

@Entity
public class Article {

    @Id
    @GeneratedValue
    Integer id;

    @Temporal(TemporalType.DATE)
    Date publicationDate;

    @Temporal(TemporalType.TIME)
    Date publicationTime;

    @Temporal(TemporalType.TIMESTAMP)
    Date creationDateTime;
}

8.3.2.8. @DateTimeFormat 处理日期时间格式

    public java.sql.Date createdate; 创建日期 YYYY-MM-DD
    public java.util.Date finisheddate; 创建日期时间 YYYY-MM-DD HH:MM:SS

Json默认为 yyyy-MM-ddTHH:mm:ss 注意日期与时间中间的T，修改日期格式将T去掉

    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date createDate;

8.3.2.9. 默认时间规则

8.3.2.9.1. CreatedDate

Spring 提供了 import org.springframework.data.annotation.CreatedDate;

但是这些只能作用于实体类。

    @CreatedDate
    private Date createdDateTime;
8.3.2.9.2. 与时间日期有关的 hibernate 注解
8.3.2.9.2.1. 设置默认时间

@Column(insertable = false)
@org.hibernate.annotations.ColumnDefault("1.00")
@org.hibernate.annotations.Generated(
org.hibernate.annotations.GenerationTime.INSERT
)
protected Date lastModified;
8.3.2.9.2.2. 创建时间

@Temporal(TemporalType.TIMESTAMP)
@Column(updatable = false)
@org.hibernate.annotations.CreationTimestamp
protected Date createdDate;
8.3.2.9.2.3. 更新时间

@Column(name="update_time")
@org.hibernate.annotations.UpdateTimestamp
@Temporal(TemporalType.TIMESTAMP)
private Date updateTime;

@Temporal(TemporalType.TIMESTAMP)
@Column(insertable = false, updatable = false)
@org.hibernate.annotations.Generated(
org.hibernate.annotations.GenerationTime.ALWAYS
)
8.3.2.9.3. 数据库级别的默认创建日期时间定义

package cn.netkiller.api.domain.elasticsearch;

import java.util.Date;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table
public class ElasticsearchTrash {
    @Id
    private int id;

    @Column(columnDefinition = "TIMESTAMP DEFAULT CURRENT_TIMESTAMP")
    private Date ctime;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public Date getCtime() {
        return ctime;
    }

    public void setCtime(Date ctime) {
        this.ctime = ctime;
    }

}

对应数据库DDL

CREATE TABLE `elasticsearch_trash` (
  `id` int(11) NOT NULL,
  `ctime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
8.3.2.9.4. 数据库级别的默认创建日期与更新时间定义

需求是这样的：

1. 创建时间与更新时间只能由数据库产生，不允许在实体类中产生，因为每个节点的时间/时区不一定一直。另外防止人为插入自定义时间时间。

2. 插入记录的时候创建默认时间，创建时间不能为空，时间一旦插入不允许日后在实体类中修改。

3. 记录创建后更新日志字段为默认为 null 表示该记录没有被修改过。一旦数据被修改，修改日期字段将记录下最后的修改时间。

4. 甚至你可以通过触发器实现一个history 表，用来记录数据的历史修改，详细请参考作者另一部电子书《Netkiller Architect 手札》数据库设计相关章节。

package cn.netkiller.api.domain.elasticsearch;

import java.util.Date;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.validation.constraints.Null;

@Entity
@Table
public class ElasticsearchTrash {
    @Id
    private int id;

    // 创建时间
    @Column(insertable = false, updatable = false, columnDefinition = "TIMESTAMP DEFAULT CURRENT_TIMESTAMP")
    private Date ctime;

    // 修改时间
    @Column(nullable = true, insertable = false, updatable = false, columnDefinition = "TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP")
    private Date mtime;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public Date getCtime() {
        return ctime;
    }

    public void setCtime(Date ctime) {
        this.ctime = ctime;
    }

    public Date getMtime() {
        return mtime;
    }

    public void setMtime(Date mtime) {
        this.mtime = mtime;
    }

}

对应数据库DDL

CREATE TABLE `elasticsearch_trash` (
  `id` int(11) NOT NULL,
  `ctime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `mtime` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
8.3.2.9.5. 最后修改时间

需求：记录最后一次修改时间

package cn.netkiller.api.domain.elasticsearch;

import java.util.Date;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table
public class ElasticsearchTrash {
    @Id
    private int id;

    @Column(columnDefinition = "TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP")
    private Date lastModified;

}

产生DDL语句如下

CREATE TABLE `elasticsearch_trash` (
  `id` int(11) NOT NULL,
  `last_modified` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

8.3.2.10. 索引

8.3.2.10.1. 普通索引

@Table(indexes = { @Index(name = "name", columnList = "name DESC"), @Index(name = "path", columnList = "path") })

package common.domain;

import java.util.Date;
import java.util.Set;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Index;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Table;

import org.springframework.format.annotation.DateTimeFormat;

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnore;

@Entity
@Table(indexes = { @Index(name = "name", columnList = "name DESC"), @Index(name = "path", columnList = "path") })
public class Category {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id", unique = true, nullable = false, insertable = true, updatable = false)
    public int id;
    public String name;
    public String description;
    public String path;

    @Column(columnDefinition = "enum('Enabled','Disabled') DEFAULT 'Enabled' COMMENT '状态'")
    public String status;

    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    @Column(columnDefinition = "TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间'")
    public Date ctime;

    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    @Column(columnDefinition = "TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更改时间'")
    public Date mtime;

    @ManyToOne(cascade = { CascadeType.PERSIST, CascadeType.REMOVE })
    @JoinColumn(name = "pid", referencedColumnName = "id")
    private Category categorys;

    @JsonIgnore
    @OneToMany(cascade = CascadeType.ALL, mappedBy = "category", fetch = FetchType.EAGER)
    private Set<Category> category;

    public int getId() {
        return id;
    }

    public void setId(int 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 String getPath() {
        return path;
    }

    public void setPath(String path) {
        this.path = path;
    }

    public String getStatus() {
        return status;
    }

    public void setStatus(String status) {
        this.status = status;
    }

    public Date getCtime() {
        return ctime;
    }

    public void setCtime(Date ctime) {
        this.ctime = ctime;
    }

    public Date getMtime() {
        return mtime;
    }

    public void setMtime(Date mtime) {
        this.mtime = mtime;
    }

    public Category getCategorys() {
        return categorys;
    }

    public void setCategorys(Category categorys) {
        this.categorys = categorys;
    }

    public Set<Category> getCategory() {
        return category;
    }

    public void setCategory(Set<Category> category) {
        this.category = category;
    }

    @Override
    public String toString() {
        return "Category [id=" + id + ", name=" + name + ", description=" + description + ", path=" + path + ", status="
                + status + ", ctime=" + ctime + ", mtime=" + mtime + ", categorys=" + categorys + ", category="
                + category + "]";
    }

}
8.3.2.10.2. 唯一索引

针对字段做唯一索引

@Column(unique = true)
8.3.2.10.3. 复合索引

创建由多个字段组成的复合索引

package cn.netkiller.api.model;

import java.io.Serializable;
import java.util.Date;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.persistence.UniqueConstraint;

import com.fasterxml.jackson.annotation.JsonFormat;

@Entity
@Table(name = "comment", uniqueConstraints = { @UniqueConstraint(columnNames = { "member_id", "articleId" }) })
public class Comment implements Serializable {
    /**
     *
     */
    private static final long serialVersionUID = -1484408775034277681L;
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id", unique = true, nullable = false, insertable = true, updatable = false)
    private int id;

    @ManyToOne(cascade = { CascadeType.ALL })
    @JoinColumn(name = "member_id")
    private Member member;

    private int articleId;

    private String message;

    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @Temporal(TemporalType.TIMESTAMP)
    @Column(updatable = false)
    @org.hibernate.annotations.CreationTimestamp
    protected Date createDate;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public Member getMember() {
        return member;
    }

    public void setMember(Member member) {
        this.member = member;
    }

    public int getArticleId() {
        return articleId;
    }

    public void setArticleId(int articleId) {
        this.articleId = articleId;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public Date getCreateDate() {
        return createDate;
    }

    public void setCreateDate(Date createDate) {
        this.createDate = createDate;
    }
}

CREATE TABLE `comment` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `article_id` int(11) NOT NULL,
  `create_date` datetime DEFAULT NULL,
  `message` varchar(255) DEFAULT NULL,
  `member_id` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `UK5qxfiu92nwlvgli7bl3evl11m` (`member_id`,`article_id`),
  CONSTRAINT `FKmrrrpi513ssu63i2783jyiv9m` FOREIGN KEY (`member_id`) REFERENCES `member` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

8.3.2.11. Enum 枚举数据类型

8.3.2.11.1. 实体中处理 enum 类型

@Enumerated(value = EnumType.ORDINAL) //ORDINAL序数

在实体中处理枚举类型适用于所有数据库，Spring data 将枚举视为 String 类型。

                    package cn.netkiller.api.domain;

                    import java.io.Serializable;

                    import javax.persistence.Column;
                    import javax.persistence.Entity;
                    import javax.persistence.GeneratedValue;
                    import javax.persistence.GenerationType;
                    import javax.persistence.Id;
                    import javax.persistence.Table;

                    @Entity
                    @Table(name = "statistics_history")
                    public class StatisticsHistory implements Serializable {

                    /**
                    *
                    */
                    private static final long serialVersionUID = 1L;
                    @Id
                    @GeneratedValue(strategy = GenerationType.IDENTITY)
                    @Column(name = "id",
                    unique = true, nullable = false, insertable = true, updatable = false)
                    private long id;
                    private long memberId;
                    private long statisticsId;

                    public enum StatisticsType {
                    LIKE, COMMENT, BROWSE;
                    }

                    private StatisticsType type;

                    public Long getId() {
                    return id;
                    }

                    public void setId(Long id) {
                    this.id = id;
                    }

                    public long getMemberId() {
                    return memberId;
                    }

                    public void setMemberId(long memberId) {
                    this.memberId = memberId;
                    }

                    public long getStatisticsId() {
                    return statisticsId;
                    }

                    public void setStatisticsId(long statisticsId) {
                    this.statisticsId = statisticsId;
                    }

                    public StatisticsType getType() {
                    return type;
                    }

                    public void setType(StatisticsType type) {
                    this.type = type;
                    }

                    }

默认 enum 类型创建数据库等效 int(11)

CREATE TABLE `statistics_history` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `member_id` bigint(20) NOT NULL,
  `statistics_id` bigint(20) NOT NULL,
  `type` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
SELECT * FROM test.statistics;

@Enumerated(EnumType.STRING) 注解可以使其成功字符串类型。

                    public enum StatisticsType {
                    LIKE, COMMENT, BROWSE;
                    }

                    @Enumerated(EnumType.STRING)
                    private StatisticsType type;

SQL

CREATE TABLE `statistics_history` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `member_id` bigint(20) NOT NULL,
  `statistics_id` bigint(20) NOT NULL,
  `type` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
8.3.2.11.2. 数据库枚举类型

在枚举中处理类型虽然可以适用于所有数据库，但有时我们希望适用数据库的枚举类型（例如MySQL），数据库中得枚举类型要比字符串效率更高

package cn.netkiller.api.domain.elasticsearch;

import java.util.Date;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table
public class NetkillerTrash {
    @Id
    private int id;

    @Column(columnDefinition = "enum('Y','N') DEFAULT 'N'")
    private boolean status;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public boolean isStatus() {
        return status;
    }

    public void setStatus(boolean status) {
        this.status = status;
    }

}

实际对应的数据库DLL

CREATE TABLE `netkiller_trash` (
  `id` int(11) NOT NULL,
  `status` enum('Y','N') DEFAULT 'N',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

8.3.2.12. SET 数据结构

package common.domain;

import java.util.Date;
import java.util.Map;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Convert;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;

import org.springframework.format.annotation.DateTimeFormat;
import com.fasterxml.jackson.annotation.JsonFormat;

import common.type.OptionConverter;

@Entity
public class ItemPool {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id", unique = true, nullable = false, insertable = false, updatable = false)
    public int id;

    @ManyToOne(cascade = { CascadeType.PERSIST, CascadeType.REMOVE })
    @JoinColumn(name = "site_id", referencedColumnName = "id")
    private Site site;

    public String question;

    @Column(columnDefinition = "json DEFAULT NULL")
    @Convert(converter = OptionConverter.class)
    public Map<String, String> options;

    @Column(columnDefinition = "SET('A','B','C','D','E','F','G') DEFAULT NULL COMMENT '答案'")
    public String answer;

    @ManyToOne(cascade = { CascadeType.PERSIST, CascadeType.REMOVE })
    @JoinColumn(name = "category_id", referencedColumnName = "id")
    private Category category;

    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    @Column(columnDefinition = "TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间'")
    public Date ctime;

    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    @Column(columnDefinition = "TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更改时间'")
    public Date mtime;
}

定义 SET 如下，在JAVA中 SET被映射为逗号分隔的字符串（String），所以操作起来并无不同。使用字符串"A,B,C"存储即可，取出也同样是字符串。

@Column(columnDefinition = "SET('A','B','C','D','E','F','G') DEFAULT NULL COMMENT '答案'")

接入后查看

mysql> select answer from item_pool;
+--------+
| answer |
+--------+
| A,B,C  |
+--------+
1 row in set (0.00 sec)

完美实现

8.3.2.13. 整形数据类型

无符号整形

package com.example.api.domain.elasticsearch;

import java.util.Date;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table
public class Member {
    @Id
    private int id;

    @Column(columnDefinition = "INT(10) UNSIGNED NOT NULL")
    private int age;

    @Column(insertable = false, updatable = false, columnDefinition = "TIMESTAMP DEFAULT CURRENT_TIMESTAMP")
    private Date ctime;

    @Column(nullable = true, insertable = false, updatable = false, columnDefinition = "TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP")
    private Date mtime;

    @Column(columnDefinition = "enum('Y','N') DEFAULT 'N'")
    private boolean status;
}

CREATE TABLE `member` (
  `id` int(11) NOT NULL,
  `age` int(10) unsigned NOT NULL,
  `ctime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `mtime` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
  `status` enum('Y','N') DEFAULT 'N',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

8.3.2.14. JSON 数据类型

MySQL 5.7 中增加了 json 数据类型，下面是一个例子：

CREATE TABLE `test` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `your` json DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8

我们需要在 Java 实体中定义 json 数据库结构，我搜索遍了整个互联网（Google,Bing,Baidu......），没有找到解决方案，功夫不负有心人，反复尝试后终于成功。记住我是第一个这样用的 ：） 。

package common.domain;

import java.util.Date;
import java.util.Map;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Convert;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;

import org.springframework.format.annotation.DateTimeFormat;
import com.fasterxml.jackson.annotation.JsonFormat;

import common.type.OptionConverter;

@Entity
public class ItemPool {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id", unique = true, nullable = false, insertable = false, updatable = false)
    public int id;

    @ManyToOne(cascade = { CascadeType.PERSIST, CascadeType.REMOVE })
    @JoinColumn(name = "site_id", referencedColumnName = "id")
    private Site site;

    public String name;

    @Column(columnDefinition = "json DEFAULT NULL")
    @Convert(converter = OptionConverter.class)
    public Map<String, String> options;

    @ManyToOne(cascade = { CascadeType.PERSIST, CascadeType.REMOVE })
    @JoinColumn(name = "category_id", referencedColumnName = "id")
    private Category category;

    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    @Column(columnDefinition = "TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间'")
    public Date ctime;

    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    @Column(columnDefinition = "TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更改时间'")
    public Date mtime;
}

类型转换 Class

package common.type;

import java.util.Map;
import javax.persistence.AttributeConverter;

import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;

public class OptionConverter implements AttributeConverter<Map<String, String>, String> {

    Gson json = new Gson();

    @Override
    public String convertToDatabaseColumn(Map<String, String> items) {
        return json.toJson(items, new TypeToken<Map<String, String>>() {
        }.getType());
    }

    @Override
    public Map<String, String> convertToEntityAttribute(String str) {
        return json.fromJson(str, new TypeToken<Map<String, String>>() {
        }.getType());
    }
}

通过 @Column(columnDefinition = "json DEFAULT NULL") 定义数据库为 JSON 数据类型

数据存储与取出通过 @Convert(converter = OptionConverter.class) 做转换

这里我需要使用 Map 数据结构 public Map<String, String> options;， 你可以根据你的实际需要定义数据类型 Class

启动 Spring 项目后创建 Schema 如下：

CREATE TABLE `item_pool` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `ctime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '????',
  `mtime` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '????',
  `name` varchar(255) DEFAULT NULL,
  `category_id` int(11) DEFAULT NULL,
  `site_id` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `FKgwuxedi20fxclobkk2po053hj` (`category_id`),
  KEY `FKiujumwssofow95st51ukklpgv` (`site_id`),
  CONSTRAINT `FKgwuxedi20fxclobkk2po053hj` FOREIGN KEY (`category_id`) REFERENCES `category` (`id`),
  CONSTRAINT `FKiujumwssofow95st51ukklpgv` FOREIGN KEY (`site_id`) REFERENCES `site` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8

我们做个简单的测试, 创建仓库。

package common.repository;

import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;

import common.domain.ItemPool;

@Repository
public interface ItemPoolRepository extends CrudRepository<ItemPool, Integer> {

}

package cn.netkiller.api.restful;

import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import common.domain.ItemPool;
import common.repository.ItemPoolRepository;

@RestController
public class TestRestController {

    private static final Logger logger = LoggerFactory.getLogger(TestRestController.class);
    @Autowired
    private ItemPoolRepository itemPoolRepository;

    @GetMapping("/test/json/data/type")
    public void jsonType() {

        ItemPool itemPool = new ItemPool();
        itemPool.name = "Which is Operstion System?";
        Map<String, String> opt = new LinkedHashMap<String, String>();
        opt.put("A", "Linux");
        opt.put("B", "Java");
        itemPool.options = opt;
        itemPoolRepository.save(itemPool);

        itemPool = null;
        itemPool = itemPoolRepository.findOne(1);
        System.out.println(itemPool.toString());
    }

}

只能用完美来形容

mysql> select options from item_pool;
+-----------------------------+
| options                     |
+-----------------------------+
| {"A": "Linux", "B": "Java"} |
+-----------------------------+
1 row in set (0.00 sec)

8.3.2.15. @JoinColumn

@JoinColumn与@Column注释类似，它的定义如下代码所示。

@Target({METHOD, FIELD}) @Retention(RUNTIME)

public @interface JoinColumn {

String name() default "";

String referencedColumnName() default "";

boolean unique() default false;

boolean nullable() default true;

boolean insertable() default true;

boolean updatable() default true;

String columnDefinition() default "";

String table() default "";

}

8.3.2.16. @OneToOne

一对一表结构，如下面ER图所示，users表是用户表里面有登陆信息，profile 保存的时死人信息，这样的目的是我们尽量减少users表的字段，在频繁操作该表的时候性能比较好，另外一个目的是为了横向水平扩展。

    +----------+             +------------+
    | users    |             | profile    |
    +----------+             +------------+
    | id       | <---1:1---o | id         |
    | name     |             | sex        |
    | password |             | email      |
    +----------+             +------------+

package cn.netkiller.api.domain.test;

import java.io.Serializable;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name = "users")
public class Users implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private int id;
    private String name;
    private String password;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    @Override
    public String toString() {
        return "Users [id=" + id + ", name=" + name + ", password=" + password + "]";
    }
}

package cn.netkiller.api.domain.test;

import java.io.Serializable;

import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToOne;
import javax.persistence.Table;

@Entity
@Table(name = "profile")
public class Profile implements Serializable {
    /**
     *
     */
    private static final long serialVersionUID = -2500499458196257167L;
    @Id
    @OneToOne
    @JoinColumn(name = "id")
    private Users users;

    private int age;
    private String sex;
    private String email;

    public Users getUsers() {
        return users;
    }

    public void setUsers(Users users) {
        this.users = users;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    @Override
    public String toString() {
        return "Profile [users=" + users + ", age=" + age + ", sex=" + sex + ", email=" + email + "]";
    }

}

CREATE TABLE `users` (
    `id` INT(11) NOT NULL AUTO_INCREMENT,
    `name` VARCHAR(255) NULL DEFAULT NULL,
    `password` VARCHAR(255) NULL DEFAULT NULL,
    PRIMARY KEY (`id`)
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB;

CREATE TABLE `profile` (
    `age` INT(11) NOT NULL,
    `email` VARCHAR(255) NULL DEFAULT NULL,
    `sex` VARCHAR(255) NULL DEFAULT NULL,
    `id` INT(11) NOT NULL,
    PRIMARY KEY (`id`),
    CONSTRAINT `FK6x079ilawxjrfsljwyyi5ujjq` FOREIGN KEY (`id`) REFERENCES `users` (`id`)
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB;

如果第二张表关联的并非主表的PK（主键）需要使用 referencedColumnName 指定。

@JoinColumn(name = "member_id",referencedColumnName="member_id")

8.3.2.17. OneToMany 一对多

我们要实现一个一对多实体关系，ER 图如下

    +----------+          +------------+
    | Classes  |          | Student    |
    +----------+          +------------+
    | id       | <---+    | id         |
    | name     |     |    | name       |
    +----------+     +--o | classes_id |
                          +------------+

classes 表需要 OneToMany 注解，Student 表需要 ManyToOne 注解，这样就建立起了表与表之间的关系

package cn.netkiller.api.domain.test;

import java.io.Serializable;
import java.util.Set;

import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Table;

@Entity
@Table(name="classes")
public class Classes implements Serializable{
    /**
     *
     */
    private static final long serialVersionUID = -5422905745519948312L;
    @Id
    @GeneratedValue(strategy=GenerationType.AUTO)
    private int id;
    private String name;

    @OneToMany(cascade=CascadeType.ALL,mappedBy="classes")
    private Set<Student> students;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Set<Student> getStudents() {
        return students;
    }

    public void setStudents(Set<Student> students) {
        this.students = students;
    }

    @Override
    public String toString() {
        return "classes [id=" + id + ", name=" + name + ", students=" + students + "]";
    }
}

package cn.netkiller.api.domain.test;

import java.io.Serializable;

import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;

@Entity
@Table(name = "student")
public class Student implements Serializable{
    /**
     *
     */
    private static final long serialVersionUID = 6737037465677800326L;
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private int id;
    private String name;

    // 若有多个cascade，可以是：{CascadeType.PERSIST,CascadeType.MERGE}
    @ManyToOne(cascade = { CascadeType.ALL })
    @JoinColumn(name = "classes_id")
    private Classes classes;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Classes getClasses() {
        return classes;
    }

    public void setClasses(Classes classes) {
        this.classes = classes;
    }

    @Override
    public String toString() {
        return "Student [id=" + id + ", name=" + name + ", classes=" + classes + "]";
    }

}

最终 SQL 表如下

CREATE TABLE `classes` (
    `id` INT(11) NOT NULL AUTO_INCREMENT,
    `name` VARCHAR(255) NULL DEFAULT NULL,
    PRIMARY KEY (`id`)
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB;

CREATE TABLE `student` (
    `id` INT(11) NOT NULL AUTO_INCREMENT,
    `name` VARCHAR(255) NULL DEFAULT NULL,
    `class_id` INT(11) NULL DEFAULT NULL,
    PRIMARY KEY (`id`),
    INDEX `FKnsl7w2nw6o6eq53hqlxfcijpm` (`class_id`),
    CONSTRAINT `FKnsl7w2nw6o6eq53hqlxfcijpm` FOREIGN KEY (`class_id`) REFERENCES `classes` (`id`)
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB;

      Classes classes=new Classes();
      classes.setName("One");

      Student st1=new Student();
      st1.setSname("jason");
      st1.setClasses(classes);
      studentRepostitory.save(st1);

      Student st2=new Student();
      st2.setSname("neo");
      st2.setClasses(classes);
      studentRepostitory.save(st2);

8.3.2.18. ManyToMany 多对多

用户与角色就是一个多对多的关系，多对多是需要中间表做关联的。所以我方需要一个 user_has_role 表。

    +----------+          +---------------+            +--------+
    | users    |          | user_has_role |            | role   |
    +----------+          +---------------+            +--------+
    | id       | <------o | user_id       |      /---> | id     |
    | name     |          | role_id       | o---+      | name   |
    | password |          |               |            |        |
    +----------+          +---------------+            +--------+

创建 User 表

package cn.netkiller.api.domain.test;

import java.io.Serializable;
import java.util.Set;

import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.Table;
import javax.persistence.JoinColumn;

@Entity
@Table(name = "users")
public class Users implements Serializable {
    /**
     *
     */
    private static final long serialVersionUID = -2480194112597046349L;
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private int id;
    private String name;
    private String password;

    @ManyToMany(fetch = FetchType.EAGER)
    @JoinTable(name = "user_has_role", joinColumns = { @JoinColumn(name = "user_id", referencedColumnName = "id") }, inverseJoinColumns = { @JoinColumn(name = "role_id", referencedColumnName = "id") })
    private Set<Roles> roles;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public Set<Roles> getRoles() {
        return roles;
    }

    public void setRoles(Set<Roles> roles) {
        this.roles = roles;
    }

    @Override
    public String toString() {
        return "Users [id=" + id + ", name=" + name + ", password=" + password + ", roles=" + roles + "]";
    }

}

创建 Role 表

package cn.netkiller.api.domain.test;

import java.io.Serializable;
import java.util.Set;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import javax.persistence.Table;

@Entity
@Table(name = "roles")
public class Roles implements Serializable {
    private static final long serialVersionUID = 6737037465677800326L;
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private int id;
    private String name;
    @ManyToMany(mappedBy = "roles")
    private Set<Users> users;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Set<Users> getUsers() {
        return users;
    }

    public void setUsers(Set<Users> users) {
        this.users = users;
    }

    @Override
    public String toString() {
        return "Roles [id=" + id + ", name=" + name + ", users=" + users + "]";
    }

}

最终产生数据库表如下

CREATE TABLE `users` (
    `id` INT(11) NOT NULL AUTO_INCREMENT,
    `name` VARCHAR(255) NULL DEFAULT NULL,
    `password` VARCHAR(255) NULL DEFAULT NULL,
    PRIMARY KEY (`id`)
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB;

CREATE TABLE `roles` (
    `id` INT(11) NOT NULL AUTO_INCREMENT,
    `name` VARCHAR(255) NULL DEFAULT NULL,
    PRIMARY KEY (`id`)
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB;

CREATE TABLE `user_has_role` (
    `user_id` INT(11) NOT NULL,
    `role_id` INT(11) NOT NULL,
    PRIMARY KEY (`user_id`, `role_id`),
    INDEX `FKsvvq61v3koh04fycopbjx72hj` (`role_id`),
    CONSTRAINT `FK2dl1ftxlkldulcp934i3125qo` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`),
    CONSTRAINT `FKsvvq61v3koh04fycopbjx72hj` FOREIGN KEY (`role_id`) REFERENCES `roles` (`id`)
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB;

8.3.2.19. 外键级联删除

orphanRemoval = true 可以实现数据级联删除

package cn.netkiller.api.domain;

import java.io.Serializable;
import java.util.Set;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Table;

import com.fasterxml.jackson.annotation.JsonIgnore;

@Entity
@Table(name = "member")
public class Member implements Serializable {
    /**
     *
     */
    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id", unique = true, nullable = false, insertable = true, updatable = false)
    private int id;

    private String name;
    private String sex;
    private int age;
    private String wechat;

    @Column(unique = true)
    private String mobile;
    private String picture;
    private String ipAddress;

    @JsonIgnore
    @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "member")
    private Set<Comment> comment;
    @JsonIgnore
    @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "member")
    private Set<StatisticsHistory> statisticsHistory;

    public Member() {
    }

    public Member(int id) {
        this.id = id;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getWechat() {
        return wechat;
    }

    public void setWechat(String wechat) {
        this.wechat = wechat;
    }

    public String getMobile() {
        return mobile;
    }

    public void setMobile(String mobile) {
        this.mobile = mobile;
    }

    public String getPicture() {
        return picture;
    }

    public void setPicture(String picture) {
        this.picture = picture;
    }

    public String getIpAddress() {
        return ipAddress;
    }

    public void setIpAddress(String ipAddress) {
        this.ipAddress = ipAddress;
    }

    @Override
    public String toString() {
        return "Member [id=" + id + ", name=" + name + ", sex=" + sex + ", age=" + age + ", wechat=" + wechat + ", mobile=" + mobile + ", picture=" + picture + ", ipAddress=" + ipAddress + "]";
    }

}

8.3.2.20. 其他

8.3.2.20.1. Cascade
                    CascadeType.PERSIST （级联新建）
                    CascadeType.REMOVE （级联删除）
                    CascadeType.REFRESH （级联刷新）
                    CascadeType.MERGE （级联更新）中选择一个或多个。
                    CascadeType.ALL
8.3.2.20.2. @JsonIgnore

当尸体返回 Json 数据结构是，将不包含 @JsonIgnore 定义变量。

    @JsonIgnore
    @OneToMany(mappedBy = "owner")
    private List<Pet> pets;

8.3.3. 创建复合主键

定义实体

package cn.netkiller.wallet.domain;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Embeddable;
import javax.persistence.EmbeddedId;
import javax.persistence.Entity;

@Entity
public class UserToken {
    @EmbeddedId
    @Column(unique = true, nullable = false, insertable = true, updatable = false)
    private UserTokenPrimaryKey primaryKey;

    private String name;
    private String symbol;
    private int decimals;

    public UserToken() {
        // TODO Auto-generated constructor stub
    }

    public UserTokenPrimaryKey getPrimaryKey() {
        return primaryKey;
    }

    public void setPrimaryKey(UserTokenPrimaryKey primaryKey) {
        this.primaryKey = primaryKey;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSymbol() {
        return symbol;
    }

    public void setSymbol(String symbol) {
        this.symbol = symbol;
    }

    public int getDecimals() {
        return decimals;
    }

    public void setDecimals(int decimals) {
        this.decimals = decimals;
    }

    @Override
    public String toString() {
        return "UserToken [primaryKey=" + primaryKey + ", name=" + name + ", symbol=" + symbol + ", decimals=" + decimals + "]";
    }

    @Embeddable
    public static class UserTokenPrimaryKey implements Serializable {

        private static final long serialVersionUID = 1242827922377178368L;
        private String address;
        private String contractAddress;

        public UserTokenPrimaryKey() {
        }

        public UserTokenPrimaryKey(String address, String contractAddress) {
            this.address = address;
            this.contractAddress = contractAddress;
        }

        public String getAddress() {
            return address;
        }

        public void setAddress(String address) {
            this.address = address;
        }

        public String getContractAddress() {
            return contractAddress;
        }

        public void setContractAddress(String contractAddress) {
            this.contractAddress = contractAddress;
        }

        @Override
        public String toString() {
            return "UserTokenPrimaryKey [address=" + address + ", contractAddress=" + contractAddress + "]";
        }

    }

}

实际效果

CREATE TABLE "user_has_token" (
  "address" varchar(255) NOT NULL,
  "contract_address" varchar(255) NOT NULL,
  "decimals" int(11) NOT NULL,
  "name" varchar(255) DEFAULT NULL,
  "symbol" varchar(255) DEFAULT NULL,
  PRIMARY KEY ("address","contract_address")
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4

package cn.netkiller.wallet.repository;

import java.util.List;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

import cn.netkiller.wallet.domain.UserToken;
import cn.netkiller.wallet.domain.UserToken.UserTokenPrimaryKey;;

public interface UserTokenRepository extends JpaRepository<UserToken, UserTokenPrimaryKey> {

    UserToken findOneByPrimaryKey(UserTokenPrimaryKey primaryKey);

    @Query("select ut from UserToken ut where ut.primaryKey.address=:address")
    List<UserToken> getByAddress(@Param("address") String address);

    @Query("select ut from UserToken ut where ut.primaryKey.address=:address and ut.primaryKey.contractAddress=:contractAddress")
    List<UserToken> findByPrimaryKey(@Param("address") String address, @Param("contractAddress") String contractAddress);
}

8.3.4. @EnableJpaAuditing 开启 JPA 审计功能

@SpringBootApplication
@EnableJpaAuditing
public class Application {

    public static void main(String[] args) throws Exception {
        SpringApplication.run(Application .class, args);
    }
}

在需要审计实体中加入 @EntityListeners(AuditingEntityListener.class)

@EntityListeners(AuditingEntityListener.class)
public class Member  implements Serializable {

    private static final long serialVersionUID = -6163675075289529459L;

    @JsonIgnore
    String entityName = this.getClass().getSimpleName();

    @CreatedBy
    String createdBy;

    @LastModifiedBy
    String modifiedBy;
    /**
     * 实体创建时间
     */
    @Temporal(TemporalType.TIMESTAMP)
    @CreatedDate
    protected Date dateCreated = new Date();

    /**
     * 实体修改时间
     */
    @Temporal(TemporalType.TIMESTAMP)
    @LastModifiedDate
    protected Date dateModified = new Date();

     #省略getter setter
}

8.3.5. 实体集成

B、C 类继承 A 所有属性，并且主键均为数据库（auto_increment）

@MappedSuperclass
@(strategy = InheritanceType.TABLE_PER_CLASS)
public class A{
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private int id;
}

@Entity
@Table(name="b")
public class B extends A{

}

@Entity
@Table(name="c")
public class C extends A{

}

8.3.6. Repository

Repository： 仅仅是一个标识，没有任何方法，方便Spring自动扫描识别
CrudRepository： 继承Repository，实现了一组CRUD相关的方法
PagingAndSortingRepository： 继承CrudRepository，实现了一组分页排序相关的方法
JpaRepository： 继承PagingAndSortingRepository，实现一组JPA规范相关的方法

Spring Data JPA 为此提供了一些表达条件查询的关键字：

Keyword	Sample	JPQL snippet
And			findByLastnameAndFirstname	… where x.lastname = ?1 and x.firstname = ?2
Or			findByLastnameOrFirstname	… where x.lastname = ?1 or x.firstname = ?2
Is,Equals	findByFirstnameIs,findByFirstnameEquals	… where x.firstname = ?1
Between		findByStartDateBetween	… where x.startDate between ?1 and ?2
LessThan	findByAgeLessThan	… where x.age < ?1
LessThanEqual	findByAgeLessThanEqual	… where x.age ⇐ ?1
GreaterThan	findByAgeGreaterThan	… where x.age > ?1
GreaterThanEqual	findByAgeGreaterThanEqual	… where x.age >= ?1
After		findByStartDateAfter	… where x.startDate > ?1
Before		findByStartDateBefore	… where x.startDate < ?1
IsNull		findByAgeIsNull	… where x.age is null
IsNotNull,NotNull	findByAge(Is)NotNull	… where x.age not null
Like		findByFirstnameLike	… where x.firstname like ?1
NotLike		findByFirstnameNotLike	… where x.firstname not like ?1
StartingWith	findByFirstnameStartingWith	… where x.firstname like ?1 (parameter bound with appended %)
EndingWith	findByFirstnameEndingWith	… where x.firstname like ?1 (parameter bound with prepended %)
Containing	findByFirstnameContaining	… where x.firstname like ?1 (parameter bound wrapped in %)
OrderBy		findByAgeOrderByLastnameDesc	… where x.age = ?1 order by x.lastname desc
Not			findByLastnameNot	… where x.lastname <> ?1
In			findByAgeIn(Collection ages)	… where x.age in ?1
NotIn		findByAgeNotIn(Collection age)	… where x.age not in ?1
TRUE		findByActiveTrue()	… where x.active = true
FALSE		findByActiveFalse()	… where x.active = false
IgnoreCase	findByFirstnameIgnoreCase	… where UPPER(x.firstame) = UPPER(?1)

常用如下：
And --- 等价于 SQL 中的 and 关键字，比如 findByUsernameAndPassword(String user, Striang pwd)
Or --- 等价于 SQL 中的 or 关键字，比如 findByUsernameOrAddress(String user, String addr)
Between --- 等价于 SQL 中的 between 关键字，比如 findBySalaryBetween(int max, int min)
LessThan --- 等价于 SQL 中的 "<"，比如 findBySalaryLessThan(int max)
GreaterThan --- 等价于 SQL 中的">"，比如 findBySalaryGreaterThan(int min)
IsNull --- 等价于 SQL 中的 "is null"，比如 findByUsernameIsNull()
IsNotNull --- 等价于 SQL 中的 "is not null"，比如 findByUsernameIsNotNull()
NotNull --- 与 IsNotNull 等价
Like --- 等价于 SQL 中的 "like"，比如 findByUsernameLike(String user)
NotLike --- 等价于 SQL 中的 "not like"，比如 findByUsernameNotLike(String user)
OrderBy ---等价于 SQL 中的 "order by"，比如 findByUsernameOrderBySalaryAsc(String user)
Not --- 等价于 SQL 中的 "！ ="，比如 findByUsernameNot(String user)
In --- 等价于 SQL 中的 "in"，比如 findByUsernameIn(Collection<String> userList) ，方法的参数可以是 Collection 类型，也可以是数组或者不定长参数
NotIn --- 等价于 SQL 中的 "not in"，比如 findByUsernameNotIn(Collection<String> userList) ，方法的参数可以是 Collection 类型，也可以是数组或者不定长

8.3.6.1. CrudRepository

CrudRepository 接口提供了最基本的对实体类的添删改查操作

T save(T entity);								//保存单个实体
Iterable<T> save(Iterable<? extends T> entities);//保存集合
T findOne(ID id);								//根据id查找实体
boolean exists(ID id);							//根据id判断实体是否存在
Iterable<T> findAll();							//查询所有实体,不用或慎用!
long count();									//查询实体数量
void delete(ID id);								//根据Id删除实体
void delete(T entity);							//删除一个实体
void delete(Iterable<? extends T> entities);		//删除一个实体的集合
void deleteAll();								//删除所有实体,不用或慎用!

8.3.6.2. JpaRepository

https://docs.spring.io/spring-data/jpa/docs/current/api/org/springframework/data/jpa/repository/JpaRepository.html

Modifier and Type	Method and Description
void	deleteAllInBatch()
Deletes all entities in a batch call.
void	deleteInBatch(Iterable<T> entities)
Deletes the given entities in a batch which means it will create a single Query.
List<T>	findAll()
<S extends T>
List<S>	findAll(Example<S> example)
<S extends T>
List<S>	findAll(Example<S> example, Sort sort)
List<T>	findAll(Sort sort)
List<T>	findAllById(Iterable<ID> ids)
void	flush()
Flushes all pending changes to the database.
T	getOne(ID id)
Returns a reference to the entity with the given identifier.
<S extends T>
List<S>	saveAll(Iterable<S> entities)
<S extends T>
S	saveAndFlush(S entity)
Saves an entity and flushes changes instantly.

8.3.6.3. findByXXX

    @Autowired
    private ArticleRepository articleRepository;

    @RequestMapping("/mysql")
    @ResponseBody
    public String mysql() {
        articleRepository.save(new Article("Neo", "Chen"));
        for (Article article : articleRepository.findAll()) {
            System.out.println(article);
        }
        Article tmp = articleRepository.findByTitle("Neo");
        return tmp.getTitle();
    }

    @RequestMapping("/search")
    @ResponseBody
    public String search() {

        for (Article article : articleRepository.findBySearch(1)) { System.out.println(article); }

        List<Article> tmp = articleRepository.findBySearch(1L);

        tmp.forEach((temp) -> {
            System.out.println(temp.toString());
        });

        return tmp.get(0).getTitle();
    }
8.3.6.3.1. 传 Boolean 参数

package cn.netkiller.wallet.repository.fcoin;

import java.util.List;

import org.springframework.data.domain.Pageable;
import org.springframework.data.repository.CrudRepository;

import cn.netkiller.wallet.domain.fcoin.Fcoin;;

public interface FcoinRepository extends CrudRepository<Fcoin, String> {

    Fcoin findOneByAddress(String address);

    int countByAirdropFalse();

    List<Fcoin> findByAirdrop(boolean airdrop, Pageable pageable);

}
8.3.6.3.2. Eunm 传递枚举参数

package cn.netkiller.api.repository;

import org.springframework.data.repository.CrudRepository;

import cn.netkiller.api.domain.StatisticsHistory;

public interface StatisticsHistoryRepostitory extends CrudRepository<StatisticsHistory, Long> {

    public StatisticsHistory findByMemberIdAndStatisticsIdAndType(long member_id, long statistics_id,
            StatisticsHistory.StatisticsType type);

}

    @Autowired
    private StatisticsHistoryRepostitory statisticsHistoryRepostitory;

    statisticsHistoryRepostitory.findByMemberIdAndStatisticsIdAndType(uid, id, type);

8.3.6.4. count 操作

public interface UserRepository extends CrudRepository<User, Long> {

    Long countByFirstName(String firstName);

    @Transactional
    Long deleteByFirstName(String firstName);

    @Transactional
    List<User> removeByFirstName(String firstName);

}

8.3.6.5. OrderBy

public List<StudentEntity> findAllByOrderByIdAsc();
public List<StudentEntity> findAllByOrderByIdDesc();
List<RecentRead> findByMemberIdOrderByIdDesc(int memberId, Pageable pageable);

8.3.6.6. GreaterThan

package schedule.repository;

import java.util.Date;

import org.springframework.data.repository.CrudRepository;

import common.domain.CmsTrash;

public interface CmsTrashRepository extends CrudRepository<CmsTrash, Integer> {

    Iterable<CmsTrash> findBySiteIdAndTypeOrderByCtimeASC(int siteId, String string);

    Iterable<CmsTrash> findBySiteIdAndTypeAndCtimeGreaterThanOrderByCtimeASC(int siteId, String string, Date date);

}

8.3.6.7. PageRequest 翻页操作

翻页返回数据可以选择 Iterable/List 或者 Page。

Iterable/List 只返回数据，不含页码等数据

Page 返回数据和页码等数据

PageRequest(int page, int size, Sort sort)  Deprecated.
use PageRequest.of(int, int, Sort) instead.
8.3.6.7.1. PageRequest.of

package cn.netkiller.api.repository;

import java.util.List;

import org.springframework.data.domain.Pageable;
import org.springframework.data.repository.CrudRepository;

import cn.netkiller.api.domain.RecentRead;

public interface RecentReadRepostitory extends CrudRepository<RecentRead, Long> {

    List<RecentRead> findByMemberId(long id, Pageable pageable);

}

Top 10 实例

    @RequestMapping("/recent/read/list/{id}")
    public List<RecentRead> recentList(@PathVariable long id) {
        int page = 0;
        int limit = 10;
        List<RecentRead> recentRead = recentReadRepostitory.findByMemberId(id, new PageRequest(page, limit));
        return recentRead;
    }

注意 PageRequest(int page, int size) 在新版 Spring boot 2.x 中已经废弃请使用 PageRequest.of(page, size) 替代

List<Fcoin> fcoins = fcoinRepository.findByAirdrop(false, PageRequest.of(0, size));
8.3.6.7.2. Pageable

接口实现 PagingAndSortingRepository

package api.repository.h5;

import org.springframework.data.repository.PagingAndSortingRepository;

import api.domain.User;

public interface GatherRepository extends PagingAndSortingRepository<User, Integer> {

}

控制器添加 Pageable pageable 参数

    @RequestMapping("/browse")
    public ModelAndView browse(Pageable pageable) {
        Page<User> users = userRepository.findAll(pageable);

        System.out.println(users.toString());
        ModelAndView mv = new ModelAndView();
        mv.addObject("users", users.getContent());
        mv.addObject("number", users.getNumber());
        mv.addObject("size", users.getSize());
        mv.addObject("totalPages", users.getTotalPages());
        mv.setViewName("table");

        return mv;
    }

排序 /browse?sort=id,desc
每页返回数量 /browse?size=10
返回第二页5条数据 /browse?size=5&page=1
返回第二页5条数据，ID倒序排序 /browse?size=5&page=1&sort=id,desc

8.3.6.8. Sort 排序操作操作

List<UserModel> findByName(String name, Sort sort);

Sort sort = new Sort(Direction.DESC, "id");
repostitory.findByName("Neo", sort);

8.3.6.9. Query

8.3.6.9.1. 参数传递

package api.repository.oracle;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;

import api.domain.oracle.Member;

@Repository
public interface MemberRepository extends CrudRepository<Member, Long> {
    public Page<Member> findAll(Pageable pageable);

    // public Member findByBillno(String billno);

    public Member findById(String id);

    @Query("SELECT m FROM Member m WHERE m.status = 'Y' AND m.id = :id")
    public Member findFinishById(@Param("id") String id);

}

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

public interface PersonRepository extends JpaRepository<Person, Long> {
    @Query("SELECT p FROM Person p WHERE LOWER(p.lastName) = LOWER(:lastName)")
    public List<Person> find(@Param("lastName") String lastName);
}
8.3.6.9.2. 原生 SQL

public interface UserRepository extends JpaRepository<User, Long> {

    @Query(value = "SELECT * FROM USERS WHERE EMAIL_ADDRESS = ?0", nativeQuery = true)
    User findByEmailAddress(String emailAddress);
}

insert ignore

    @Modifying
    @Query(value = "insert ignore into emp(create, modified, user_id, user_name, user_nickname, user_mail) values(?1, ?2, ?3, ?4, ?5, ?6)", nativeQuery = true)
    void insertIgnoreEmployee(Timestamp create, Timestamp modified, String userId, String name, String nickname, String mail);
8.3.6.9.3. @Query 与 Pageagble

https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#_native_queries

package api.domain;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Index;
import javax.persistence.Table;

@Entity
@Table(indexes = { @Index(name = "address", columnList = "from_address,to_address"), @Index(name = "contractAddress", columnList = "contractAddress") })

public class TransactionHistory implements Serializable {
    private static final long serialVersionUID = 6710992220657056861L;
    @Id
    @Column(name = "blockNumber", unique = true, nullable = false, insertable = true, updatable = false)
    private int blockNumber;
    private String timeStamp;
    private String hash;
    @Column(name = "from_address")
    private String from;
    @Column(name = "to_address")
    private String to;
    private String value;
    private String gas;
    private String gasPrice;
    private String isError;
    private String contractAddress;
    private String gasUsed;
    private String symbol;

    public TransactionHistory() {
        // TODO Auto-generated constructor stub
    }

    public int getBlockNumber() {
        return blockNumber;
    }

    public void setBlockNumber(int blockNumber) {
        this.blockNumber = blockNumber;
    }

    public String getTimeStamp() {
        return timeStamp;
    }

    public void setTimeStamp(String timeStamp) {
        this.timeStamp = timeStamp;
    }

    public String getHash() {
        return hash;
    }

    public void setHash(String hash) {
        this.hash = hash;
    }

    public String getFrom() {
        return from;
    }

    public void setFrom(String from) {
        this.from = from;
    }

    public String getTo() {
        return to;
    }

    public void setTo(String to) {
        this.to = to;
    }

    public String getValue() {
        return value;
    }

    public void setValue(String value) {
        this.value = value;
    }

    public String getGas() {
        return gas;
    }

    public void setGas(String gas) {
        this.gas = gas;
    }

    public String getGasPrice() {
        return gasPrice;
    }

    public void setGasPrice(String gasPrice) {
        this.gasPrice = gasPrice;
    }

    public String getIsError() {
        return isError;
    }

    public void setIsError(String isError) {
        this.isError = isError;
    }

    public String getContractAddress() {
        return contractAddress;
    }

    public void setContractAddress(String contractAddress) {
        this.contractAddress = contractAddress;
    }

    public String getGasUsed() {
        return gasUsed;
    }

    public void setGasUsed(String gasUsed) {
        this.gasUsed = gasUsed;
    }

    public static long getSerialversionuid() {
        return serialVersionUID;
    }

    public String getSymbol() {
        return symbol;
    }

    public void setSymbol(String symbol) {
        this.symbol = symbol;
    }

    @Override
    public String toString() {
        return "TransactionHistory [blockNumber=" + blockNumber + ", timeStamp=" + timeStamp + ", hash=" + hash + ", from=" + from + ", to=" + to + ", value=" + value + ", gas=" + gas + ", gasPrice=" + gasPrice + ", isError=" + isError + ", contractAddress=" + contractAddress + ", gasUsed=" + gasUsed + ", symbol=" + symbol + "]";
    }

}

package api.repository;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;

import api.domain.TransactionHistory;

@Repository
public interface TransactionHistoryRepository extends CrudRepository<TransactionHistory, Integer> {

    @Query(value = "SELECT * FROM transaction_history th WHERE (th.from_address = :address or th.to_address = :address) and contract_address is NULL",
            countQuery = "SELEÇT count(*) FROM transaction_history th WHERE (th.from_address = :address or th.to_address = :address) and contract_address is NULL",
            nativeQuery = true)
    public Page<TransactionHistory> findEthByAddress(@Param("address") String address, Pageable pageable);

}
8.3.6.9.4. 返回指定字段

通过实体返回数据有时结果集非常庞大，可能会影响性能，这时我们只需要返回指定字段即可。

@Query(value = "select u.userName, ui.name, ui.gender, ui.description from UserInfo ui, User u where u.id = ui.userId")
public List<Object> getCustomField();
8.3.6.9.5. 返回指定的模型

临时写一个新的模型

public class MyModel implements Serializable {

    private String userName;
    private String name;
    private String gender;
    private String description;

    public MyModel() {};

    public MyModel(String userName, String name, String gender, String description) {
        this.userName = userName;
        this.name = name;
        this.gender = gender;
        this.description = description;
    }
}

使用构造方法赋值

@Query(value = "select new cn.netkiller.model.MyModel(u.userName, ui.name, ui.gender, ui.description) from UserInfo ui, User u where u.id = ui.userId")
public List<MyModel> getAllRecord();

8.3.6.10. @Transactional

下面介绍一下@Transactional注解的参数以及使用：

事物传播行为介绍:

@Transactional(propagation=Propagation.REQUIRED) ：如果有事务, 那么加入事务, 没有的话新建一个(默认情况下)
@Transactional(propagation=Propagation.NOT_SUPPORTED) ：容器不为这个方法开启事务
@Transactional(propagation=Propagation.REQUIRES_NEW) ：不管是否存在事务,都创建一个新的事务,原来的挂起,新的执行完毕,继续执行老的事务
@Transactional(propagation=Propagation.MANDATORY) ：必须在一个已有的事务中执行,否则抛出异常
@Transactional(propagation=Propagation.NEVER) ：必须在一个没有的事务中执行,否则抛出异常(与Propagation.MANDATORY相反)
@Transactional(propagation=Propagation.SUPPORTS) ：如果其他bean调用这个方法,在其他bean中声明事务,那就用事务.如果其他bean没有声明事务,那就不用事务.
事物超时设置:

@Transactional(timeout=30) //默认是30秒
事务隔离级别:

@Transactional(isolation = Isolation.READ_UNCOMMITTED)：读取未提交数据(会出现脏读, 不可重复读) 基本不使用
@Transactional(isolation = Isolation.READ_COMMITTED)：读取已提交数据(会出现不可重复读和幻读)
@Transactional(isolation = Isolation.REPEATABLE_READ)：可重复读(会出现幻读)
@Transactional(isolation = Isolation.SERIALIZABLE)：串行化 　MYSQL: 默认为REPEATABLE_READ级别 　SQLSERVER: 默认为READ_COMMITTED
@Transactional注解中常用参数说明

注意的几点:

@Transactional 只能被应用到public方法上, 对于其它非public的方法,如果标记了@Transactional也不会报错,但方法没有事务功能.
用 spring 事务管理器,由spring来负责数据库的打开,提交,回滚.默认遇到运行期例外(throw new RuntimeException("注释");)会回滚，即遇到不受检查（unchecked）的例外时回滚；而遇到需要捕获的例外(throw new Exception("注释");)不会回滚,即遇到受检查的例外（就是非运行时抛出的异常，编译器会检查到的异常叫受检查例外或说受检查异常）时，需我们指定方式来让事务回滚要想所有异常都回滚,要加上 @Transactional( rollbackFor={Exception.class,其它异常}) .如果让unchecked例外不回滚： @Transactional(notRollbackFor=RunTimeException.class)
@Transactional 注解应该只被应用到 public 可见度的方法上。 如果你在 protected、private 或者 package-visible 的方法上使用 @Transactional 注解，它也不会报错， 但是这个被注解的方法将不会展示已配置的事务设置。
@Transactional 注解可以被应用于接口定义和接口方法、类定义和类的 public 方法上。然而，请注意仅仅 @Transactional 注解的出现不足于开启事务行为，它仅仅 是一种元数据，能够被可以识别 @Transactional 注解和上述的配置适当的具有事务行为的beans所使用。上面的例子中，其实正是 元素的出现 开启 了事务行为。
Spring团队的建议是你在具体的类（或类的方法）上使用 @Transactional 注解，而不要使用在类所要实现的任何接口上。你当然可以在接口上使用 @Transactional 注解，但是这将只能当你设置了基于接口的代理时它才生效。因为注解是不能继承的，这就意味着如果你正在使用基于类的代理时，那么事务的设置将不能被基于类的代理所识别，而且对象也将不会被事务代理所包装（将被确认为严重的）。因此，请接受Spring团队的建议并且在具体的类上使用 @Transactional 注解。

8.3.6.10.1. 删除更新需要 @Transactional 注解

package cn.netkiller.api.repository;

import javax.transaction.Transactional;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;

import cn.netkiller.api.domain.RecentRead;

@Repository
public interface RecentReadRepostitory extends CrudRepository<RecentRead, Integer> {

    Page<RecentRead> findByMemberIdOrderByIdDesc(int memberId, Pageable pageable);

    int countByMemberId(int memberId);

    @Transactional
    @Modifying
    @Query("DELETE FROM RecentRead r WHERE r.memberId = ?1 AND r.articleId = ?2")
    void deleteByMemberIdAndArticleId(int memberId, int articleId);

    @Transactional
    @Modifying
    @Query("delete from RecentRead where member_id = :member_id")
    public void deleteByMemberId(@Param("member_id") int memberId);

    int countByMemberIdAndArticleId(int memberId, int articleId);

}
8.3.6.10.2. 回滚操作

    // 指定Exception回滚
    @Transactional(rollbackFor=Exception.class)
    public void methodName() {
       // 不会回滚
       throw new Exception("...");
    }

    //指定Exception回滚，但其他异常不回滚
    @Transactional(noRollbackFor=Exception.class)
    public ItimDaoImpl getItemDaoImpl() {
        // 会回滚
        throw new RuntimeException("注释");
    }

8.3.6.11. 锁 @Lock

interface UserRepository extends Repository<User, Long> {

  // Plain query method
  @Lock(LockModeType.READ)
  List<User> findByLastname(String lastname);


8.4. EntityManager

@Repository
@Transactional(readOnly = true)
class AccountServiceImpl implements AccountService {

  @PersistenceContext
  private EntityManager em;

  @Override
  @Transactional
  public Account save(Account account) {

    if (account.getId() == null) {
      em.persist(account);
      return account;
    } else {
      return em.merge(account);
    }
  }

  @Override
  public List<Account> findByCustomer(Customer customer) {

    TypedQuery query = em.createQuery("select a from Account a where a.customer = ?1", Account.class);
    query.setParameter(1, customer);

    return query.getResultList();
  }

  @Override
  public List<Customer> findAll(int page, int pageSize) {

    TypedQuery query = em.createQuery("select c from Customer c", Customer.class);

    query.setFirstResult(page * pageSize);
    query.setMaxResults(pageSize);

    return query.getResultList();
  }

}


8.5. JdbcTemplate

8.5.1. execute

jdbcTemplate.execute("CREATE TABLE USER (id integer, name varchar(100))");

8.5.2. queryForInt

int count = jdbcTemplate.queryForInt("SELECT COUNT(*) FROM USER");

8.5.3. queryForInt

lang count = jdbcTemplate.queryForLong("SELECT COUNT(*) FROM USER");

8.5.4. queryForObject

8.5.4.1. 返回整形与字符型

Integer age = queryForObject("select age from emp", Integer.class);
String name = queryForObject("select name from emp",String.class);

8.5.4.2. 查询 Double 类型数据库

    private double getSumByMemberId(int memberId) {
        double result = 0.0d;
        String sql = "SELECT sum(o.price::NUMERIC) as total FROM public.order o group by member_id ="+ memberId;
        try {
            result = jdbcTemplate.queryForObject(sql, Double.class);
        } catch (org.springframework.dao.EmptyResultDataAccessException e) {
            log.info("{} {}", MemberId, e.toString());
        }
        return result;
    }

8.5.4.3. 返回日期

注意 Date 是 java.util 不是 java.sql

    private static final Logger log = LoggerFactory.getLogger(ScheduledTasks.class);
    private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-mm-dd HH:mm:ss");

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Scheduled(initialDelay = 1000, fixedRate = 60000)
    public void currentDate() {
    Date date = jdbcTemplate.queryForObject("select sysdate from dual", Date.class);
    log.info("The oracle sysdate is {}", dateFormat.format(date));
    }

8.5.4.4. 返回结果集

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @RequestMapping(value = "/article")
    public @ResponseBody String dailyStats(@RequestParam Integer id) {
        String query = "SELECT id, title, content from article where id = " + id;

        return jdbcTemplate.queryForObject(query, (resultSet, i) -> {
            System.out.println(resultSet.getLong(1) + "," + resultSet.getString(2) + "," + resultSet.getString(3));
            return (resultSet.getLong(1) + "," + resultSet.getString(2) + "," + resultSet.getString(3));
        });

    }

8.5.4.5. 通过 "?" 向SQL传递参数

package com.example.api.restful;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import com.example.api.pojo.ResponseRestful;

@RestController
@RequestMapping("/restful/cms")
public class CmsRestController {
    @Autowired
    private JdbcTemplate jdbcTemplate;

    @RequestMapping(value = "/article/update/count/{articleId}", method = RequestMethod.GET, produces = { "application/xml", "application/json" })
    public ResponseRestful updateCount(@PathVariable int articleId) {
        String sql = "SELECT count(*) FROM cms.article WHERE id > ?";
        int count = jdbcTemplate.queryForObject(sql, new Object[] { articleId }, Integer.class);
        return new ResponseRestful(true, 1, "文章更新", count);
    }
}

8.5.4.6. RowMapper 记录映射

package cn.netkiller.model;

import java.sql.ResultSet;
import java.sql.SQLException;

import org.springframework.jdbc.core.RowMapper;

public class CustomerRowMapper implements RowMapper
{
    public Object mapRow(ResultSet rs, int rowNum) throws SQLException {
        Customer customer = new Customer();
        customer.setId(rs.getInt("ID"));
        customer.setName(rs.getString("NAME"));
        customer.setAge(rs.getInt("AGE"));
        return customer;
    }

}

public Customer findByCustomerId(int id){

    String sql = "SELECT * FROM CUSTOMER WHERE ID = ?";

    Customer customer = (Customer)getJdbcTemplate().queryForObject(
            sql, new Object[] { id }, new CustomerRowMapper());

    return customer;
}

Member member = this.jdbcTemplate.queryForObject("select first_name, last_name from member where id = ?",new Object[]{112L},
new RowMapper<Member>() {
    public Actor mapRow(ResultSet rs, int rowNum) throws SQLException {
        Member member = new Member();
        member.setFirstName(rs.getString("first_name"));
        member.setLastName(rs.getString("last_name"));
        return member;
    }
});

8.5.5. queryForList

List rows = jdbcTemplate.queryForList("SELECT * FROM USER");
Iterator it = rows.iterator();
while(it.hasNext()) {
    Map userMap = (Map) it.next();
    System.out.print(userMap.get("id") + "\t");
    System.out.print(userMap.get("name") + "\t");
    System.out.print(userMap.get("sex") + "\t");
    System.out.println(userMap.get("age") + "\t");
}

List<Map<String, Object>> rows = jdbcTemplate.queryForList("select * from user_token where address=? and contract_address not in (select contract_address from token)", new Object[] { address });
Iterator<Map<String, Object>> it = rows.iterator();
while (it.hasNext()) {
    Map<String, Object> userMap = (Map<String, Object>) it.next();

    String contractAddress = (String) userMap.get("contract_address");
    String symbol = (String) userMap.get("symbol");
    int decimals = (int) userMap.get("decimals");
}

    @RequestMapping("/article/tag/{siteId}")
    public ResponseRestful tag(@PathVariable int siteId) {
        List<Tag> tags = new ArrayList<Tag>();
        List<Map<String, Object>> rows = new ArrayList<Map<String, Object>>();

        String sql = "SELECT id,name FROM cms.tag WHERE site_id = ?";
        rows = jdbcTemplate.queryForList(sql, new Object[] { siteId });

        for (Map<String, Object> row : rows) {
            Tag tag = new Tag();
            tag.setId((Integer) row.get("id"));
            tag.setName((String) row.get("name"));
            tags.add(tag);
        }
        logger.info("tag {} SQL: {}", siteId, sql);
        return new ResponseRestful(true, tags.size(), "标签", tags);
    }

8.5.6. queryForMap

Map<String, Object> map = this.jdbcTemplate.queryForMap("SELECT * FROM USERS WHERE USERNAME=?", "username");

System.out.println(map.get("USERNAME"));

8.5.7. query

8.5.7.1. ResultSet

HashMap<String,String> member = jdbcTemplate.query("select name,age from member where id=1", (ResultSet rs) -> {
    HashMap<String,String> results = new HashMap<>();
    while (rs.next()) {
        results.put(rs.getString("name"), rs.getString("age"));
    }
    return results;
});

8.5.7.2. ResultSetExtractor

ResultSetExtractor

HashMap<String,String> member = jdbcTemplate.query("select name,age from member where id=1", new ResultSetExtractor<Map>(){
    @Override
    public Map extractData(ResultSet rs) throws SQLException,DataAccessException {
        HashMap<String,String> mapResult= new HashMap<String,String>();
        while(rs.next()){
            mapResult.put(rs.getString("name"),rs.getString("age"));
        }
        return mapResult;
    }
});

8.5.7.3. RowMapper

List<Actor> actors = this.jdbcTemplate.query("select first_name, last_name from actor",new RowMapper<Actor>() {
    public Actor mapRow(ResultSet rs, int rowNum) throws SQLException {
        Actor actor = new Actor();
        actor.setFirstName(rs.getString("first_name"));
        actor.setLastName(rs.getString("last_name"));
        return actor;
    }
});

public List<Actor> findAllActors() {
    return this.jdbcTemplate.query( "select first_name, last_name from actor", new ActorMapper());
}

private static final class ActorMapper implements RowMapper<Actor> {

    public Actor mapRow(ResultSet rs, int rowNum) throws SQLException {
        Actor actor = new Actor();
        actor.setFirstName(rs.getString("first_name"));
        actor.setLastName(rs.getString("last_name"));
        return actor;
    }
}

返回第一条数据，事实上只有一条。

    public Token getTokenBySymbol(String symbol) {

        List<Token> response = jdbcTemplate.query("select * from token where symbol ='" + symbol + "'", new RowMapper<Token>() {
            public Token mapRow(ResultSet result, int rowNum) throws SQLException {
                Token Token = new Token();
                Token.setContractAddress(result.getString(""));
                Token.setName(result.getString("name"));
                Token.setSymbol(result.getString("symbol"));
                Token.setDecimals(result.getInt("decimals"));
                return Token;
            }
        });

        if (response.size() == 1) {
            return response.get(0);
        }
        return null;
    }

8.5.8. queryForRowSet

SqlRowSet rs = jdbcTemplate.queryForRowSet("select * from test");

8.5.9. update

    @RequestMapping(value="/comment/add/{siteId}/{articleId}", method = RequestMethod.POST)
    public ResponseRestful commentAdd(@PathVariable("siteId") int siteId, @PathVariable("articleId") int articleId, @RequestBody Comment comment) {
        String sql = "insert into cms.comment("
                + "article_id, "
                + "ctime,"
                + "content,"
                + "member_id,"
                + "nickname,"
                + "picture"
                + ") values(?,?,now(),?,?,?,?,?)";

        int count = jdbcTemplate.update(sql,
                comment.getArticleId(),
                comment.getContent(),
                comment.getMemberId(),
                comment.getNickname(),
                comment.getPicture()
        );

        return new ResponseRestful(true, count, "评论添加成功", comment);
    }

8.5.10.

new MapSqlParameterSource("symbol", symbol)

8.5.11. 实例参考

8.5.11.1. 参数传递技巧

    public List<PendingTransaction> getPendingTransaction(String address, String contractAddress) {
        List<PendingTransaction> pendingTransactions = new ArrayList<PendingTransaction>();
        String sql;
        Object[] param;
        if (contractAddress == null || contractAddress.equals("")) {
            sql = "select * from pending_transaction where from_address = ? and contract_address IS NULL";
            param = new Object[] { address };
        } else {
            sql = "select * from pending_transaction where from_address = ? and contract_address = ?";
            param = new Object[] { address, contractAddress };
        }
        List<Map<String, Object>> rows = jdbcTemplate.queryForList(sql, param);

        for (Map<String, Object> row : rows) {
            PendingTransaction pendingTransaction = new PendingTransaction();
            pendingTransaction.setHash((String) row.get("hash"));
            pendingTransaction.setFrom((String) row.get("from_address"));
            pendingTransaction.setTo((String) row.get("to_address"));
            pendingTransaction.setValue((String) row.get("value"));
            pendingTransaction.setGas((String) row.get("gas"));
            pendingTransaction.setSymbol((String) row.get("symbol"));
            pendingTransaction.setContractAddress((String) row.get("contractAddress"));
            pendingTransactions.add(pendingTransaction);
        }
        logger.info("PendingTransaction:" + pendingTransactions.toString());
        return pendingTransactions;
    }


8.6. Elasticsearch

8.6.1. 内嵌 Elasticsearch

内嵌 Elasticsearch 应用，你不需要一个 Elasticsearch 服务器，启动 Spring boot 即可使用 Elasticsearch 服务。

8.6.1.1. Maven

需要下面两个依赖

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
        </dependency>
        <!-- com.sun.jna for elasticsearch -->
        <dependency>
            <groupId>com.sun.jna</groupId>
            <artifactId>jna</artifactId>
            <version>3.0.9</version>
        </dependency>

<?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>com.example</groupId>
    <artifactId>api</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>api</name>
    <description>Demo project for Spring Boot</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.6.RELEASE</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>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <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>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
        </dependency>
        <!-- https://mvnrepository.com/artifact/javax.persistence/persistence-api -->
        <dependency>
            <groupId>javax.persistence</groupId>
            <artifactId>persistence-api</artifactId>
            <version>1.0.2</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.json/json -->
        <dependency>
            <groupId>org.json</groupId>
            <artifactId>json</artifactId>
        </dependency>
        <!-- com.sun.jna for elasticsearch -->
        <dependency>
            <groupId>com.sun.jna</groupId>
            <artifactId>jna</artifactId>
            <version>3.0.9</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <configuration>
                    <skip>true</skip>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

8.6.1.2. src/main/resources/application.properties

spring.data.elasticsearch.repositories.enabled=true
#spring.data.elasticsearch.cluster-name=elasticsearch
#spring.data.elasticsearch.cluster-nodes=119.29.241.95:9200
spring.data.elasticsearch.local=false
spring.data.elasticsearch.properties.transport.tcp.connect_timeout=60s
spring.data.elasticsearch.properties.host=127.0.0.1
spring.data.elasticsearch.properties.port=9200
spring.data.elasticsearch.properties.path.home=/tmp

8.6.1.3. Domain Class

package com.example.api.domain.elasticsearch;

import java.io.Serializable;
import java.util.Date;

import javax.persistence.Id;

import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.elasticsearch.annotations.DateFormat;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldIndex;
import org.springframework.data.elasticsearch.annotations.FieldType;

import com.fasterxml.jackson.annotation.JsonFormat;

@Document(indexName = "information", type = "article")
public class Article implements Serializable {

    /**
     *
     */
    private static final long serialVersionUID = 8789505663320446079L;
    @Id
    private int id;
    private String title;
    private String description;
    private String author;
    private String source;
    private String content;
    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyyMMdd'T'HHmmss.SSS'Z'")
    @Field(type = FieldType.Date, format = DateFormat.basic_date_time, index = FieldIndex.not_analyzed)
    @CreatedDate
    private Date ctime;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public Date getCtime() {
        return ctime;
    }

    public void setCtime(Date ctime) {
        this.ctime = ctime;
    }

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }

    public String getSource() {
        return source;
    }

    public void setSource(String source) {
        this.source = source;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    @Override
    public String toString() {
        return "Article [id=" + id + ", title=" + title + ", description=" + description + ", author=" + author + ", source=" + source + ", content=" + content + ", ctime=" + ctime + "]";
    }

}

8.6.1.4. ElasticsearchRepository

package com.example.api.repository.elasticsearch;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import org.springframework.stereotype.Repository;

import com.example.api.domain.elasticsearch.Article;

@Repository
public interface ArticleElasticsearchRepository extends ElasticsearchRepository<Article, Integer> {
    Page<Article> findByTitleLike(String title, Pageable page);

    Page<Article> findByDescription(String description, Pageable pageable);

    Page<Article> findByDescriptionNot(String description, Pageable pageable);

    Page<Article> findByDescriptionLike(String description, Pageable pageable);
}

8.6.1.5. SearchRestController

package com.example.api.restful;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.web.PageableDefault;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.example.api.domain.elasticsearch.Article;
import com.example.api.repository.elasticsearch.ArticleElasticsearchRepository;

@RestController
@RequestMapping("/restful/search")
public class SearchRestController {
    @Autowired
    private ArticleElasticsearchRepository articleElasticsearchRepository;

    @RequestMapping(value = "/article/create")
    public  Article create() {
        Article article = new Article();
        article.setId(1);
        article.setTitle("sssss");
        article.setContent("test");
        return articleElasticsearchRepository.save(article);
    }
    @RequestMapping(value = "/article/{articleId}")
    public  Article get(@PathVariable int articleId) {
        return articleElasticsearchRepository.findOne(articleId);
    }
}

8.6.1.6. 测试

MacBook-Pro:~ neo$ curl http://test:test@localhost:8443/restful/search/article/create.json
{"id":1,"title":"sssss","description":null,"author":null,"source":null,"content":"test","ctime":null}

MacBook-Pro:~ neo$ curl http://test:test@localhost:8443/restful/search/article/1.json
{"id":1,"title":"sssss","description":null,"author":null,"source":null,"content":"test","ctime":null}

8.6.2. 集群模式

查看 cluster.name 配置项

root@netkiller ~ % grep ^cluster.name /etc/elasticsearch/elasticsearch.yml
cluster.name: elasticsearch

src/main/resources/application.properties

spring.data.elasticsearch.cluster-name=elasticsearch
spring.data.elasticsearch.cluster-nodes=172.16.0.100:9200
spring.data.elasticsearch.local=false
spring.data.elasticsearch.repositories.enabled=true

8.6.3. Document

@Document(indexName = "customer", type = "external", shards = 1, replicas = 0, refreshInterval = "-1")

8.6.4. Elasticsearch 删除操作

package com.example.api.schedule;

import org.elasticsearch.action.delete.DeleteResponse;
import org.elasticsearch.client.transport.TransportClient;
import org.elasticsearch.rest.RestStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import com.example.api.domain.elasticsearch.ElasticsearchTrash;
import com.example.api.repository.elasticsearch.ElasticsearchTrashRepository;

@Component
public class ScheduledTasks {
    private static final Logger logger = LoggerFactory.getLogger(ScheduledTasks.class);

    @Autowired
    private TransportClient client;

    @Autowired
    private ElasticsearchTrashRepository alasticsearchTrashRepository;

    public ScheduledTasks() {
    }

    @Scheduled(fixedRate = 1000 * 60) // 60秒运行一次调度任务
    public void cleanTrash() {
        for (ElasticsearchTrash elasticsearchTrash : alasticsearchTrashRepository.findAll()) {
            DeleteResponse response = client.prepareDelete("information", "article", elasticsearchTrash.getId() + "").get();
            RestStatus status = response.status();
            logger.info("delete {} {}", elasticsearchTrash.getId(), status.toString());
            if (status == RestStatus.OK || status == RestStatus.NOT_FOUND) {
                alasticsearchTrashRepository.delete(elasticsearchTrash);
            }
        }
    }
}

8.6.5. FAQ

8.6.5.1. java.lang.IllegalStateException: Received message from unsupported version: [2.0.0] minimal compatible version is: [5.0.0]

spring-boot-starter-data-elasticsearch 目前还不支持 5.0.0 版本


8.7. Spring Data FAQ

8.7.1. No identifier specified for entity

8.7.2. Oracle Date 类型显示日期和时间

package cn.netkiller.api.domain.oracle;

import java.util.Date;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import javax.validation.constraints.NotNull;

import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.format.annotation.DateTimeFormat.ISO;

import com.fasterxml.jackson.annotation.JsonFormat;

@Entity
@Table(name = "test")
public class Test {

    @Id
    @Column(name = "ID")
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "test_id_Sequence")
    @SequenceGenerator(name = "test_id_Sequence", sequenceName = "test")
    private Long id;

    @NotNull
    @DateTimeFormat(iso = ISO.DATE_TIME)
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    public Date createdate;

    public Member() {

    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public Date getCreatedate() {
        return createdate;
    }

    public void setCreatedate(Date createdate) {
        this.createdate = createdate;
    }

}

8.7.3. java.lang.ClassCastException: java.lang.Long cannot be cast to java.lang.Integer

问题描述，Restful 请求返回错误，检查数据库 BigInt 修改为 无符号整形，错误依旧存在

ALTER TABLE `cms`.`comment`
CHANGE COLUMN `user_id` `user_id` INT(10) UNSIGNED NULL DEFAULT NULL ;

去掉 UNSIGNED 后，错误消失

ALTER TABLE `cms`.`comment`
CHANGE COLUMN `user_id` `user_id` INT NULL DEFAULT NULL ;

Java 认为 INT(10) UNSIGNED 是 Long 型。

8.7.4. Executing an update/delete query; nested exception is javax.persistence.TransactionRequiredException: Executing an update/delete query

Internal Server Error","exception":"org.springframework.dao.InvalidDataAccessApiUsageException","message":"Executing an update/delete query; nested exception is javax.persistence.TransactionRequiredException: Executing an update/delete query"


第 9 章 Spring Security

目录

9.1. 配置 Spring Security
9.1.1. 默认配置
9.1.2. 设置用户名和密码
9.1.3. 禁用 Security
9.2. EnableWebSecurity
9.3. 访问控制列表（Access Control List，ACL）
9.3.1. antMatchers
9.3.2. HTTP Auth
9.3.3. Rest
9.3.4. hasRole
9.3.5. hasAnyRole()
9.3.6. X-Frame-Options 安全
9.4. WebSecurity
9.4.1. 正则匹配
9.5. AuthenticationManagerBuilder
9.5.1. 添加用户
9.5.2. 添加多个用户，并指定角色
9.6. corsConfigurationSource()
9.7. 获取当前用户

9.1. 配置 Spring Security

9.1.1. 默认配置

如果在 maven 中引入了 spring security当你启动 springboot 的时候会提示

Using generated security password: 1cd27b90-1208-4be2-ae8e-0f564ee427b8

默认用户名是 user 可以这样访问

neo@MacBook-Pro ~ % curl -s http://user:1cd27b90-1208-4be2-ae8e-0f564ee427b8@localhost:8080/member/json
{"status":false,"reason":"","code":0,"data":{}}

9.1.2. 设置用户名和密码

spring.security.user.name=test
spring.security.user.password=test
spring.security.user.role=USER

注意 Springboot 1.x

security.user.name=test
security.user.password=passw0rdf
security.user.role=USER

9.1.3. 禁用 Security

方法一

@SpringBootApplication(exclude = { SecurityAutoConfiguration.class })
public class Application {
    public static void main(String[] args) {
        System.out.println("Web Starting...");
        SpringApplication.run(Application.class, args);
    }
}

Springboot 1.x 可以在 appliction.properties 中加入

security.basic.enabled=false


9.2. EnableWebSecurity

package cn.netkiller.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    public WebSecurityConfig() {
        // TODO Auto-generated constructor stub
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http
            .authorizeRequests()
                .antMatchers("/", "/about.html", "/doc/**").permitAll()
                .anyRequest().authenticated()
                .and()
            .formLogin()
                .loginPage("/login.html")
                .permitAll()
                .and()
            .logout()
                .permitAll();

    }

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth
            .inMemoryAuthentication()
                .withUser("user").password("password").roles("USER")
                .and()
                .withUser("admin").password("admin").roles("ADMIN");

    }
}


9.3. 访问控制列表（Access Control List，ACL）

9.3.1. antMatchers

/** 表示放行所有请求URL

http.authorizeRequests().antMatchers("/**" ).permitAll();

匹配精确的URL地址 "/","/products","/product/show/*","/css/**"

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
    httpSecurity
            .authorizeRequests().antMatchers("/","/products","/product/show/*","/css/**").permitAll()
            .anyRequest().authenticated()
            .and()
            .formLogin().loginPage("/login").permitAll()
            .and()
            .logout().permitAll();

   httpSecurity.csrf().disable();
   httpSecurity.headers().frameOptions().disable();
}

9.3.2. HTTP Auth

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().antMatchers("/ping","/v1/*/ping","/v1/public/**" ).permitAll()
        .anyRequest().authenticated()
        .and().rememberMe().and().httpBasic()
        .and().csrf().disable();
    }

9.3.3. Rest

protected void configure(HttpSecurity http) throws Exception {
    http
      .csrf().disable()
      .authorizeRequests()
        .antMatchers(HttpMethod.POST, "/api/**").authenticated()
        .antMatchers(HttpMethod.PUT, "/api/**").authenticated()
        .antMatchers(HttpMethod.DELETE, "/api/**").authenticated()
        .anyRequest().permitAll()
        .and()
      .httpBasic().and()
      .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}

9.3.4. hasRole

    @Override
    protected void configure(HttpSecurity http) throws Exception {

      http.authorizeRequests()
        .antMatchers("/", "/member").access("hasRole('USER') or hasRole('ADMIN') or hasRole('DBA')")
        .and().formLogin().loginPage("/login")
        .usernameParameter("sso").passwordParameter("password")
        .and().exceptionHandling().accessDeniedPage("/403");
    }

9.3.5. hasAnyRole()

     @Autowired
    private AccessDeniedHandler accessDeniedHandler;

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http.csrf().disable()
                .authorizeRequests()
                    .antMatchers("/", "/home", "/about").permitAll()
                    .antMatchers("/admin/**").hasAnyRole("ADMIN")
                    .antMatchers("/user/**").hasAnyRole("USER")
                    .anyRequest().authenticated()
                .and()
                .formLogin()
                    .loginPage("/login")
                    .permitAll()
                    .and()
                .logout()
                    .permitAll()
                    .and()
                .exceptionHandling().accessDeniedHandler(accessDeniedHandler);
    }

9.3.6. X-Frame-Options 安全

X-Frame-Options: SAMEORIGIN

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            // ...
            .headers()
                .frameOptions().sameOrigin()
                .httpStrictTransportSecurity().disable();
    }
}

安全配置 X-FRAME-OPTIONS 指定允许iframe访问的域名

package cn.netkiller.api.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.header.writers.StaticHeadersWriter;

@Configuration
@EnableWebSecurity
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http.headers().frameOptions().disable().addHeaderWriter(new StaticHeadersWriter("X-FRAME-OPTIONS", "ALLOW-FROM netkiller.cn")).and().
            csrf().disable()
            .authorizeRequests()
            .antMatchers("/","/ping","/v1/*/ping","/public/**","/your/**" ).permitAll()
            .antMatchers("/v1/**").authenticated().
            anyRequest().permitAll().and().
            httpBasic();
    }

}


9.4. WebSecurity

用于Web静态资源的权限控制

package com.example.api.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
@EnableWebSecurity
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {

    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/static/**", "/**/*.jsp");
    }

    protected void registerAuthentication(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication().withUser("user1").password("secret1").roles("USER").and().withUser("user2").password("secret2").roles("USER").and().withUser("admin").password("secret").roles("ADMIN");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().anyRequest().fullyAuthenticated();
        http.httpBasic();
        http.csrf().disable();
    }

}

启动 Springboot 可以看到类似日志

2018-10-12 18:01:40.692  INFO 4722 --- [           main] o.s.s.web.DefaultSecurityFilterChain     : Creating filter chain: Ant [pattern='/**/json'], []
2018-10-12 18:01:40.692  INFO 4722 --- [           main] o.s.s.web.DefaultSecurityFilterChain     : Creating filter chain: Ant [pattern='/about'], []
2018-10-12 18:01:40.692  INFO 4722 --- [           main] o.s.s.web.DefaultSecurityFilterChain     : Creating filter chain: Ant [pattern='/test/hello'], []
2018-10-12 18:01:40.693  INFO 4722 --- [           main] o.s.s.web.DefaultSecurityFilterChain     : Creating filter chain: Ant [pattern='/web/**'], []

9.4.1. 正则匹配

@Override
public void configure(WebSecurity web) throws Exception {
    web.ignoring().regexMatchers(XXXXX);
}


9.5. AuthenticationManagerBuilder

9.5.1. 添加用户

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth
            .inMemoryAuthentication()
                .withUser("user").password("password").roles("USER");

    }

9.5.2. 添加多个用户，并指定角色

添加多个用户

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {

        auth.inMemoryAuthentication()
                .withUser("user").password("password").roles("USER")
                .and()
                .withUser("admin").password("password").roles("ADMIN");
    }

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth
            .inMemoryAuthentication()
                .withUser("user").password("password").roles("USER")
                .and()
                .withUser("admin").password("admin").roles("ADMIN")
                .and()
                .withUser("admin").password("super").roles("ADMIN","SYS","DBA")
                ;
    }


9.6. corsConfigurationSource()

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.cors().and()
            //other config
    }

    @Bean
    CorsConfigurationSource corsConfigurationSource()
    {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(Arrays.asList("https://example.com"));
        configuration.setAllowedMethods(Arrays.asList("GET","POST"));
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }
}


9.7. 获取当前用户

Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String currentPrincipalName = authentication.getName();


第 10 章 Interceptor

目录

10.1. WebMvcConfigurerAdapter
10.2. HandlerInterceptor

10.1. WebMvcConfigurerAdapter

package mis.config;

import mis.interceptor.SpringMVCInterceptor;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

@Configuration
public class WebAppConfig extends WebMvcConfigurerAdapter {
    /**
     * 配置拦截器
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new SpringMVCInterceptor()).addPathPatterns("/**");
    }
}


10.2. HandlerInterceptor

package mis.interceptor;

import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class SpringMVCInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object o) throws Exception {

        if(request.getServletPath().startsWith("/index") || request.getServletPath().startsWith("/login")) {
            return true;
        }

        String username = (String)request.getSession().getAttribute("userName");
        if (StringUtils.isEmpty(username)){
            response.sendRedirect(request.getContextPath() + "/index");
            return false;
        }
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {

    }
}


第 11 章 Spring Cloud

目录

11.1. Spring Cloud Config
11.1.1. Git 仓库
11.1.1.1. 分支
11.1.1.2. HTTP Auth
11.1.1.3. basedir
11.1.1.4. 本地git仓库
11.1.2. Server
11.1.2.1. Maven
11.1.2.2. Application
11.1.2.3. application.properties
11.1.2.4. 测试服务器
11.1.3. Client
11.1.3.1. Maven pom.xml
11.1.3.2. Application
11.1.3.3. bootstrap.properties
11.1.3.4. 测试 client
11.1.4. Config server 用户认证
11.1.4.1. Server 配置
11.1.4.2. Client 配置
11.1.5. 加密敏感数据
11.1.6. Spring Cloud Config JDBC Backend
11.1.6.1.
11.1.6.2.
11.1.6.3.
11.1.6.4. application.properties
11.1.7. Old
11.1.7.1. Server (Camden.SR5)
11.1.7.2. Client (Camden.SR5)
11.2. Spring Cloud Netflix
11.2.1. Eureka Server
11.2.1.1. Maven
11.2.1.2. Application
11.2.1.3. application.properties
11.2.1.4. 检查注册服务器
11.2.1.5. 为 Eureka Server 增加用户认证
11.2.2. Eureka Client
11.2.2.1. Maven
11.2.2.2. Application
11.2.2.3. RestController
11.2.2.4. application.properties
11.2.2.5. 测试
11.2.3. Feign client
11.2.3.1. Maven
11.2.3.2. Application
11.2.3.3. interface
11.2.3.4. application.properties
11.2.3.5. 测试
11.2.3.6. fallback
11.2.4. Zuul
11.2.4.1. Maven
11.2.4.2. EnableZuulProxy
11.2.4.3. application.yml
11.2.4.4. 负载均衡配置
11.3. Spring Cloud Finchley
11.4. Spring Cloud 相关的 application.properties 配置
11.4.1. 启用或禁用 bootstrap
11.5. FAQ
11.5.1. Feign请求超时
11.5.2. 已停止的微服务节点注销慢或不注销
11.5.3. Eureka Client (Dalston.SR1)
11.5.3.1. Maven
11.5.3.2. Application
11.5.3.3. RestController
11.5.3.4. application.properties
11.5.3.5. 测试
11.5.4. Feign 启动出错 PathVariable annotation was empty on param 0.
11.5.5. Feign 提示 Consider defining a bean of type 'common.feign.Cms' in your configuration.

11.1. Spring Cloud Config

11.1.1. Git 仓库

克隆仓库

            git clone https://github.com/netkiller/config.git

创建配置文件 server-development.properties

            vim server-development.properties

            test.a=KKOOKK
            message=Hello world

提交配置文件

            git commit -a
            git push

配置文件格式

/{application}/{profile}[/{label}]
/{application}-{profile}.yml
/{label}/{application}-{profile}.yml
/{application}-{profile}.properties
/{label}/{application}-{profile}.properties

label 是指 git 的分支

11.1.1.1. 分支

                spring.cloud.config.label=mybranch

11.1.1.2. HTTP Auth

                spring.application.name=config-server
                spring.cloud.config.server.git.uri=https://netkiller:xxxxxx@github.com/xyz/microservices-configs.git

                spring.application.name=config-server
                spring.cloud.config.server.git.uri=https://github.com/xyz/microservices-configs.git
                spring.cloud.config.server.git.username=netkiller
                spring.cloud.config.server.git.password=password

11.1.1.3. basedir

                spring.cloud.config.server.git.basedir=api/configs

11.1.1.4. 本地git仓库

创建本地仓库

mkdir ~/config
cd config
git init
git config --global user.email "neo.chen@live.com"
git config --global user.name "Neo Chen"

创建测试配置文件

# cat app-test.properties
name=neo
age=10

提交配置文件

git add app-test.properties
git commit -a

检查文件是否提交成功

[root@netkiller config]# git log
commit aee6c35bacf1740004e02f8ecdcf2fd322422405
Author: Neo Chen <neo.chen@live.com>
Date:   Thu Nov 2 14:18:48 2017 +0800

        new file:   app-test.properties

配置 Spring cloud config 服务器，修改 application.properties 文件

server.port=8888
#spring.cloud.config.server.git.uri=/opt/config
spring.cloud.config.server.git.uri= file://${user.home}/config
security.user.name=cfg
security.user.password=s3cr3t

## Spring cloud GIT Repository file
${user.home}/config/root-server.properties

检验配置中心

[root@netkiller config]# curl http://cfg:test@localhost:8888/app-test.properties
age: 10
name: neo

11.1.2. Server

11.1.2.1. Maven

<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>cn.netkiller</groupId>
    <artifactId>config</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>config</name>
    <url>http://maven.apache.org</url>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.6.RELEASE</version>
        <relativePath />
    </parent>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-config</artifactId>
                <version>1.3.1.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-config-server</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

11.1.2.2. Application

Application

package cn.netkiller.cloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.config.server.EnableConfigServer;
import org.springframework.context.annotation.Configuration;

@Configuration
@EnableAutoConfiguration
@EnableDiscoveryClient
@EnableConfigServer
@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}

11.1.2.3. application.properties

                server.port=8888
                spring.cloud.config.server.git.uri=https://github.com/netkiller/config.git

11.1.2.4. 测试服务器

                neo@netkiller $ curl http://localhost:8888/server-development.json
                {"message":"Hello world","test":{"a":"KKOOKK"}}

11.1.3. Client

11.1.3.1. Maven pom.xml

<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>netkiller.cn</groupId>
    <artifactId>cloud</artifactId>
    <version>0.0.1-SNAPSHOT</version>

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

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-config</artifactId>
                <version>1.3.1.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-config</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <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>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

11.1.3.2. Application

package cn.netkiller.cloud.client;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

@RefreshScope
@RestController
class MessageRestController {

    @Value("${message:Hello default}")
    private String message;

    @RequestMapping("/message")
    String getMessage() {
        return this.message;
    }
}

注意 @RefreshScope 注解

11.1.3.3. bootstrap.properties

spring.application.name=server-development
spring.cloud.config.uri=http://localhost:8888
management.security.enabled=false

11.1.3.4. 测试 client

                neo@netkiller $ curl http://localhost:8080/message.json
                Hello world

11.1.4. Config server 用户认证

11.1.4.1. Server 配置

11.1.4.1.1. application.properties
                    server.port=8888
                    spring.cloud.config.server.git.uri=ssh://localhost/config-repo
                    spring.cloud.config.server.git.clone-on-start=true
                    security.user.name=cfg
                    security.user.password=s3cr3t
11.1.4.1.2. Maven

<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>cn.netkiller</groupId>
    <artifactId>config</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>config</name>
    <url>http://maven.apache.org</url>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.7.RELEASE</version>
        <relativePath />
    </parent>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-config</artifactId>
                <version>1.3.1.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-config-server</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>
11.1.4.1.3. 测试是否生效

neo@MacBook-Pro ~/deployment % curl http://cfg:s3cr3t@localhost:8888/neo-development.json
{"message":"Hello world","test":{"name":"neo"}}

11.1.4.2. Client 配置

bootstrap.properties:

                spring.application.name=project
                spring.profiles.active=development
                spring.cloud.config.uri=http://localhost:8888
                spring.cloud.config.username=cfg
                spring.cloud.config.password=s3cr3t

11.1.5. 加密敏感数据

Config server 创建证书

keytool -genkeypair -alias config-server-key \
       -keyalg RSA -keysize 4096 -sigalg SHA512withRSA \
       -dname 'CN=Config Server,OU=Spring Cloud,O=Netkiller' \
       -keypass s3cr3t -keystore config-server.jks \
       -storepass passw0rd

application.properties 中配置证书

# spring.cloud.config.server.encrypt.enabled=true
encrypt.key-store.location=classpath:/config-server.jks
encrypt.key-store.alias=config-server-key
encrypt.key-store.secret=s3cr3t
encrypt.key-store.password=passw0rd

测试加密

curl -X POST --data-urlencode mypassword http://localhost:8888/encrypt

$ PASSWORD=$(curl -X POST --data-urlencode passw0rd http://cfg:s3cr3t@localhost:8888/encrypt)
$ echo "user.password=$PASSWORD" >> api-interface-development.properties
$ git commit -am 'Added encrypted password'

# 刷新配置
$ curl -X POST http://cfg:s3cr3t@localhost:8888/refresh

11.1.6. Spring Cloud Config JDBC Backend

11.1.6.1.

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

<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-config-server</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jdbc</artifactId>
    </dependency>
    <dependency>
        <groupId>org.flywaydb</groupId>
        <artifactId>flyway-core</artifactId>
        <version>5.0.3</version>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.21</version>
    </dependency>
</dependencies>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>Edgware.SR3</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

11.1.6.2.

CREATE TABLE `properties` (
  `key` varchar(50) NOT NULL,
  `value` varchar(500) NOT NULL,
  `application` varchar(50) NOT NULL,
  `profile` varchar(50) NOT NULL,
  `label` varchar(50) NOT NULL,
  PRIMARY KEY (`KEY`, `APPLICATION`, `PROFILE`, `LABEL`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

11.1.6.3.

@EnableConfigServer
@SpringBootApplication
public class Application {

    //@Autowired
    //JdbcTemplate jdbcTemplate;

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
        // 测试用数据，仅用于本文测试使用
        JdbcTemplate jdbcTemplate = context.getBean(JdbcTemplate.class);
        jdbcTemplate.execute("delete from properties");
        jdbcTemplate.execute("INSERT INTO properties VALUES('neo.message', 'helloworld', 'api', 'stage', 'master')");
        jdbcTemplate.execute("INSERT INTO properties VALUES('neo.message', 'helloworld', 'new', 'online', 'master')");
        jdbcTemplate.execute("INSERT INTO properties VALUES('neo.message', 'helloworld', 'test', 'online', 'develop')");
        jdbcTemplate.execute("INSERT INTO properties VALUES('neo.message', 'helloworld', 'cms', 'online', 'master')");
    }

}

11.1.6.4. application.properties

spring.profiles.active=jdbc 将配置中心的存储实现切换到jdbc的方式, 必须设置。

server.port=8888
spring.profiles.active=jdbc

spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/config-server-db
spring.datasource.username=root
spring.datasource.password=xxxx

# or

spring.datasource.driver-class-name=org.postgresql.Driver
spring.datasource.url= jdbc:postgresql://localhost:5432/configdb
spring.datasource.username=xxxxxx
spring.datasource.password=xxxxxx

11.1.7. Old

11.1.7.1. Server (Camden.SR5)

Maven pom.xml 请使用最新版本 1.3.1, 下面的 maven 是早期 Camden.SR5 版本的配置

<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>netkiller.cn</groupId>
    <artifactId>cloud</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>Neo</name>
    <description>http://www.netkiller.cn</description>
    <packaging>jar</packaging>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.3.RELEASE</version>
        <relativePath />
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-config-server</artifactId>
        </dependency>

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

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Camden.SR5</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

11.1.7.2. Client (Camden.SR5)

Maven pom.xml Camden.SR5 为早期版本，尽可以使用新版

<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>netkiller.cn</groupId>
    <artifactId>cloud</artifactId>
    <version>0.0.1-SNAPSHOT</version>

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

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-config</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <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>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Camden.SR5</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>


11.2. Spring Cloud Netflix

11.2.1. Eureka Server

11.2.1.1. Maven

<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>cn.netkiller.spring.cloud</groupId>
    <artifactId>netflix.eureka.server</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>eureka.server</name>
    <url>http://maven.apache.org</url>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

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

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-netflix</artifactId>
                <version>1.3.5.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka-server</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <configuration>
                    <skip>true</skip>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

11.2.1.2. Application

package cn.netkiller.spring.cloud.netflix.eureka.server;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@SpringBootApplication
@EnableEurekaServer
public class Application {
    public static void main(String[] args) {
        System.out.println("Hello World!");
        // new SpringApplicationBuilder(Application.class).web(true).run(args);
        SpringApplication.run(Application.class, args);
    }
}

11.2.1.3. application.properties

server.port=8761
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false
eureka.client.serviceUrl.defaultZone=http://localhost:${server.port}/eureka/

logging.level.com.netflix.eureka=OFF
logging.level.com.netflix.discovery=OFF

11.2.1.4. 检查注册服务器

http://localhost:8761

11.2.1.5. 为 Eureka Server 增加用户认证

11.2.1.5.1. Maven

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
11.2.1.5.2. application.properties
security.user.name=eureka
security.user.password=s3cr3t
11.2.1.5.3. Eureka Client
spring.application.name=restful-api-service
eureka.client.serviceUrl.defaultZone=http://eureka:s3cr3t@localhost:8761/eureka/
11.2.1.5.4. Feign Client
eureka.client.serviceUrl.defaultZone=http://eureka:s3cr3t@localhost:8761/eureka/

11.2.2. Eureka Client

11.2.2.1. Maven

<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>cn.netkiller.spring.cloud</groupId>
    <artifactId>eureka.client</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>eureka.client</name>
    <url>http://maven.apache.org</url>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

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

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-netflix</artifactId>
                <version>1.3.5.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-config</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <configuration>
                    <skip>true</skip>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

11.2.2.2. Application

package cn.netkiller.spring.cloud.eureka.client;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Configuration
@EnableAutoConfiguration
@EnableEurekaClient
@RestController

public class Application {

    @RequestMapping("/")
    public String home() {
        return "Hello World";
    }

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

11.2.2.3. RestController

package cn.netkiller.spring.cloud.eureka.client;

import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class TestRestController {
    private static final Logger logger = LoggerFactory.getLogger(TestRestController.class);

    @RequestMapping("/")
    public String version() {
        logger.info("Hello!!!");
        return "Version: v1.0.0";
    }

    @RequestMapping(value = "/add", method = RequestMethod.GET)
    public Integer add(@RequestParam Integer a, @RequestParam Integer b) {
        Integer r = a + b;
        return r;
    }

    @RequestMapping("/greeting")
    public String greeting() {
        return "GREETING";
    }
}

11.2.2.4. application.properties

spring.application.name=test-service
server.port=8080
eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka/

11.2.2.5. 测试

首先确认客户端已经注册到 http://localhost:8761/

你可以启动很多 Eureka 客户端，相同的 spring.application.name 会归为一组，为用户提供负载均衡。

neo@MacBook-Pro ~ % curl http://localhost:8080/
Hello World

add 接口测试

curl http://localhost:8080/add.json?a=5&b=3

8

11.2.3. Feign client

11.2.3.1. Maven

<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>cn.netkiller.spring.cloud.netflix</groupId>
    <artifactId>feign.client</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>feign.client</name>
    <url>http://maven.apache.org</url>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

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

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
            <version>1.3.1.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-feign</artifactId>
            <version>1.3.1.RELEASE</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <configuration>
                    <skip>true</skip>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

11.2.3.2. Application

package cn.netkiller.spring.cloud.netflix.feign.client;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.feign.EnableFeignClients;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
@RestController
public class Application {
    @Autowired
    private GreetingClient greetingClient;

    @RequestMapping("/get-greeting")
    public String greeting() {
        return greetingClient.greeting();
    }

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
        System.out.println("Hello World!");
    }
}

11.2.3.3. interface

package cn.netkiller.spring.cloud.netflix.feign.client;

import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;

@FeignClient("test-service")
public interface GreetingClient {
    @RequestMapping("/greeting")
    String greeting();
}

@FeignClient("test-service") 是 Eureka Client application.properties 中的 spring.application.name 配置项

@RequestMapping("/greeting") 是 Eureka Client RestController 中的 @RequestMapping

11.2.3.4. application.properties

spring.application.name=spring-cloud-eureka-feign-client
server.port=8088
#eureka.client.register-with-eureka=false
#eureka.client.fetch-registry=false
eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka/
feign.compression.response.enabled=true
feign.compression.request.enabled=true
feign.compression.request.mime-types=text/xml,application/xml,application/json
feign.compression.request.min-request-size=2048

11.2.3.5. 测试

$ curl -s http://localhost:8088/get-greeting.json

GREETING

11.2.3.6. fallback

@FeignClient(value = "restful-api-service", fallback = UserServiceFeignClientFallback.class)
public interface UserServiceFeignClient {
@RequestMapping(value = "/api/user/{id}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE)
    User getUser(@PathVariable("id") int id);

    @RequestMapping(value = "/api/user/search/findByName?name={name}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE)
    User findUserByName(@PathVariable("name") String name);

    @RequestMapping(value = "/api/user/search/findByAddress?address={address}", method = RequestMethod.GET)
    String findUserByAddress(@PathVariable("address") String address);
}

@Component
public class UserServiceFeignClientFallback implements UserServiceFeignClient {

    @Override
    public User getUser(int id) {
        return new User("getUser.Fallback", "feignClient return");
    }

    @Override
    public User findUserByName(String name) {
        return new User("findUserByName.Fallback", "feignClient return");
    }

    @Override
    public String findUserByAddress(String address) {
        return "fallback";
    }
}

11.2.4. Zuul

11.2.4.1. Maven

<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>cn.netkiller</groupId>
    <artifactId>zuul</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>zuul</name>
    <url>http://maven.apache.org</url>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.6.RELEASE</version>
        <relativePath /> <!-- lookup parent from repository -->
    </parent>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Dalston.SR2</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-zuul</artifactId>
        </dependency>
    </dependencies>
</project>

11.2.4.2. EnableZuulProxy

package cn.netkiller.zuul;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;

@SpringBootApplication
@EnableZuulProxy
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

11.2.4.3. application.yml

server:
  port: 8765

logging:
  level:
    ROOT: INFO
    org.springframework.web: DEBUG

zuul:
  routes:
    restful:
      path: /restful/**
      url: http://api:password@api.netkiller.com:8080/restful

11.2.4.4. 负载均衡配置

zuul:
  routes:
    httpbin:
      path: /**
      serviceId: httpslb

httpslb:
  ribbon:
    listOfServers: api1.netkiller.org, api2.netkiller.cn


11.3. Spring Cloud Finchley

Spring Cloud Finchley 是 Zuul 的替代品


11.4. Spring Cloud 相关的 application.properties 配置

11.4.1. 启用或禁用 bootstrap

spring.cloud.bootstrap.enabled=false
spring.cloud.bootstrap.location=classpath:bootstrap.properties


11.5. FAQ

11.5.1. Feign请求超时

方法一，修改配置是让Hystrix的超时时间改为5秒
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 5000

方法二，修改配置，禁用Hystrix的超时时间
hystrix.command.default.execution.timeout.enabled: false

方法三，修改配置，用于索性禁用feign的hystrix。
feign.hystrix.enabled: false

11.5.2. 已停止的微服务节点注销慢或不注销

由于 Eureka Server 清理无效节点周期长默认为90秒，可能会遇到微服务注销慢甚至不注销的问题。

Eureka Server 配置，注意仅适合开发环境。

# 设为false, 关闭自我保护， 从而保证会注销微服务
eureka.server.enable-self-preservation=false

# 清理间隔（单位毫秒，默认是60 * 1000）
eureka.server.eviction-interval-timer-in-ms=30000

Eureka Client

配置开启健康检查，续约更新时间和到期时间。

# 设为true，开启健康检查（需要spring-boot-starter-actuator 依赖）
eureka.client.healthcheck.enabled=ture

# 续约更新时间间隔（默认是30秒）
eureka.instance.lease-renewal-interval-in-seconds=20000

# 续约到期时间（默认90秒)
eureka.instance.lease-expiration-duration-in-seconds=30000

11.5.3. Eureka Client (Dalston.SR1)

11.5.3.1. Maven

<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>cn.netkiller.spring.cloud</groupId>
    <artifactId>eureka.client</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>eureka.client</name>
    <url>http://maven.apache.org</url>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

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

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Dalston.SR1</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-config</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <configuration>
                    <skip>true</skip>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

11.5.3.2. Application

package cn.netkiller.spring.cloud.eureka.client;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication
@EnableDiscoveryClient
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

11.5.3.3. RestController

package cn.netkiller.spring.cloud.eureka.client;

import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class TestRestController {
    private static final Logger logger = LoggerFactory.getLogger(TestRestController.class);

    @RequestMapping("/")
    public String home() {
        logger.info("Hello!!!");
        return "Hello World";
    }

    @Autowired
    private DiscoveryClient discoveryClient;

    @RequestMapping("/service-instances/{applicationName}")
    public List<ServiceInstance> serviceInstancesByApplicationName(@PathVariable String applicationName) {
        return this.discoveryClient.getInstances(applicationName);
    }

    @RequestMapping(value = "/add", method = RequestMethod.GET)
    public Integer add(@RequestParam Integer a, @RequestParam Integer b) {
        @SuppressWarnings("deprecation")
        ServiceInstance instance = discoveryClient.getLocalServiceInstance();
        Integer r = a + b;
        logger.info("/add, host:" + instance.getHost() + ", service_id:" + instance.getServiceId() + ", result:" + r);
        return r;
    }

    @RequestMapping("/greeting")
    public String greeting() {
        return "GREETING";
    }
}

11.5.3.4. application.properties

spring.application.name=test-service
server.port=8080
eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka/

11.5.3.5. 测试

首先确认客户端已经注册到 http://localhost:8761/

$ curl http://localhost:8080/service-instances/test-service

[
    {
        "host": "Neo-Desktop",
        "port": 8080,
        "secure": false,
        "uri": "http://Neo-Desktop:8080",
        "serviceId": "TEST-SERVICE",
        "metadata": {},
        "instanceInfo": {
            "instanceId": "Neo-Desktop:test-service:8080",
            "app": "TEST-SERVICE",
            "appGroupName": null,
            "ipAddr": "172.25.10.150",
            "sid": "na",
            "homePageUrl": "http://Neo-Desktop:8080/",
            "statusPageUrl": "http://Neo-Desktop:8080/info",
            "healthCheckUrl": "http://Neo-Desktop:8080/health",
            "secureHealthCheckUrl": null,
            "vipAddress": "test-service",
            "secureVipAddress": "test-service",
            "countryId": 1,
            "dataCenterInfo": {
            "@class": "com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo",
            "name": "MyOwn"
        },
        "hostName": "Neo-Desktop",
        "status": "UP",
        "leaseInfo": {
            "renewalIntervalInSecs": 30,
            "durationInSecs": 90,
            "registrationTimestamp": 1497922681680,
            "lastRenewalTimestamp": 1497922681680,
            "evictionTimestamp": 0,
            "serviceUpTimestamp": 1497922003783
        },
        "isCoordinatingDiscoveryServer": false,
        "metadata": {},
        "lastUpdatedTimestamp": 1497922681680,
        "lastDirtyTimestamp": 1497922681025,
        "actionType": "ADDED",
        "asgName": null,
        "overriddenStatus": "UNKNOWN"
        }
    }
]

add 接口测试

curl http://localhost:8080/add.json?a=5&b=3

8

11.5.4. Feign 启动出错 PathVariable annotation was empty on param 0.

问题分析，@PathVariable 找不到对应的参数

package api.feign;

import java.util.List;
import java.util.Map;

import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;

@FeignClient("restful-api-service")
public interface Search {

    @RequestMapping("/search/article/list")
    public List<Map<String, Object>> list();

    @RequestMapping("/search/article/{articleId}")
    public Object read(@PathVariable String articleId);
}

解决方案

    @RequestMapping("/search/article/{articleId}")
    public Object read(@PathVariable("articleId") String articleId);

11.5.5. Feign 提示 Consider defining a bean of type 'common.feign.Cms' in your configuration.

背景：我们需要共用 Feign 接口，故将 Feign 放到共用的 common-version.jar 包中，供其他项目使用。

启动提示：Consider defining a bean of type 'common.feign.Cms' in your configuration.

注解加入包位置后解决

@EnableFeignClients("common.feign")

例 11.1. Share feign interface.

package cn.netkiller.feign;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.feign.EnableFeignClients;

@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients("common.feign")
public class Application {

    public static void main(String[] args) {
        System.out.println("Feign Starting...");
        SpringApplication.run(Application.class, args);
    }
}


第 12 章 WebFlux

目录

12.1. Getting Started
12.1.1. Maven
12.1.2. Application
12.1.3. RestController
12.1.4. 测试
12.2. WebFlux Router
12.2.1. Component 原件
12.2.2. 路由配置

12.1. Getting Started

12.1.1. Maven

<?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>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.1.RELEASE</version>
        <relativePath /> <!-- lookup parent from repository -->
    </parent>
    <groupId>cn.netkiller</groupId>
    <artifactId>webflux</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>webflux</name>
    <description>Demo webflux project for Spring Boot</description>

    <properties>
        <java.version>11</java.version>
    </properties>

    <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>
        <dependency>
            <groupId>org.springframework.restdocs</groupId>
            <artifactId>spring-restdocs-mockmvc</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

12.1.2. Application

package cn.netkiller.webflux;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class WebfluxApplication {

    public static void main(String[] args) {
        SpringApplication.run(WebfluxApplication.class, args);
    }

}

12.1.3. RestController

package cn.netkiller.webflux;

import org.reactivestreams.Publisher;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

import reactor.core.publisher.Mono;

@RestController
public class TestController {

    public TestController() {

    }

    @GetMapping("/")
    @ResponseBody
    public Publisher<String> index() {
        return Mono.just("Hello world!");
    }

}

12.1.4. 测试

neo@MacBook-Pro ~/webflux % mvn spring-boot:run

neo@MacBook-Pro ~ % curl http://localhost:8080
Hello world!%


12.2. WebFlux Router

12.2.1. Component 原件

package cn.netkiller.webflux.component;

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 HelloWorldHandler {

    public HelloWorldHandler() {
    }

    public Mono<ServerResponse> helloWorld(ServerRequest request) {
        return ServerResponse.ok().contentType(MediaType.TEXT_PLAIN).body(BodyInserters.fromObject("Hello World!!!"));
    }
}

12.2.2. 路由配置

package cn.netkiller.webflux.config;

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;

import cn.netkiller.webflux.component.HelloWorldHandler;

@Configuration
public class WebFluxRouter {

    public WebFluxRouter() {
    }

    @Bean
    public RouterFunction<ServerResponse> routeHelloWorld(HelloWorldHandler helloWorldHandler) {

        return RouterFunctions.route(RequestPredicates.GET("/hello").and(RequestPredicates.accept(MediaType.TEXT_PLAIN)), helloWorldHandler::helloWorld);
    }
}


第 13 章 SpringBootTest

目录

13.1. Maven 依赖
13.2. 测试类
13.2.1. Junit基本注解介绍
13.3.
13.3.1. Assert.assertEquals 判断相等
13.3.2. Assert.assertTrue
13.4. JPA 测试
13.5. Controller单元测试
13.6. WebTestClient

13.1. Maven 依赖

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


13.2. 测试类

创建测试类，在测试类的类头部添加：@RunWith(SpringRunner.class)和@SpringBootTest注解，在测试方法的前添加@Test，最后选择方法右键run运行。

@RunWith(SpringRunner.class)
@SpringBootTest
public class WalletTest {

    @Autowired
    WalletService walletService;

    public WalletTest() {
        // TODO Auto-generated constructor stub
    }

    @Test
    public void test() throws Exception {

        Assert.assertEquals(5,5);

    }
}

13.2.1. Junit基本注解介绍

@RunWith
在JUnit中有很多个Runner，他们负责调用你的测试代码，每一个Runner都有各自的特殊功能，你要根据需要选择不同的Runner来运行你的测试代码。
如果我们只是简单的做普通Java测试，不涉及Spring Web项目，你可以省略@RunWith注解，这样系统会自动使用默认Runner来运行你的代码。

//在所有测试方法前执行一次，一般在其中写上整体初始化的代码
@BeforeClass

//在所有测试方法后执行一次，一般在其中写上销毁和释放资源的代码
@AfterClass

//在每个测试方法前执行，一般用来初始化方法（比如我们在测试别的方法时，类中与其他测试方法共享的值已经被改变，为了保证测试结果的有效性，我们会在@Before注解的方法中重置数据）
@Before

//在每个测试方法后执行，在方法执行完成后要做的事情
@After

// 测试方法执行超过1000毫秒后算超时，测试将失败
@Test(timeout = 1000)

// 测试方法期望得到的异常类，如果方法执行没有抛出指定的异常，则测试失败
@Test(expected = Exception.class)

// 执行测试时将忽略掉此方法，如果用于修饰类，则忽略整个类
@Ignore("not ready yet")
@Test


13.3.

13.3.1. Assert.assertEquals 判断相等

13.3.2. Assert.assertTrue


13.4. JPA 测试

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(Application.class)
public class ApplicationTests {

    @Autowired
    private UserRepository userRepository;
    @Autowired
    private MessageRepository messageRepository;

    @Test
    public void test() throws Exception {

        userRepository.save(new User("Neo", 10));
        userRepository.save(new User("Jam", 20));
        userRepository.save(new User("Tom", 30));
        userRepository.save(new User("Sam", 40));
        userRepository.save(new User("Leo", 50));

        Assert.assertEquals(5, userRepository.findAll().size());

        messageRepository.save(new Message("Neo", "How are you?"));
        messageRepository.save(new Message("Jam", "Hi!"));
        messageRepository.save(new Message("Sam", "What's going on?"));

        Assert.assertEquals(3, messageRepository.findAll().size());

    }
}


13.5. Controller单元测试

创建测试类，在测试类的类头部添加：@RunWith(SpringRunner.class)、@SpringBootTest、@ AutoConfigureMockMvc注解，在测试方法的前添加@Test，最后选择方法右键run运行。

使用@Autowired 注入MockMvc，在方法中使用 mvc测试功能。示例：

@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class StudentControllerTest {
    @Autowired
    private MockMvc mvc;

    @Test
    public void getAll() throws Exception {

              mvc.perform(MockMvcRequestBuilders.get("/student/getAll")).andExpect(MockMvcResultMatchers.model().attributeExists("students"));

    }

     @Test
     public void save() throws Exception {

               Student student = new Student();
               student.setAge(12);
               student.setId("1003");
               student.setName("Neo");
               mvc.perform(MockMvcRequestBuilders.post("/student/save", student));

     }

     @Test
     public void delete() throws Exception {

               mvc.perform(MockMvcRequestBuilders.delete("/student/delete?id=1002"));

     }

     @Test
     public void index() throws Exception {

               mvc.perform(MockMvcRequestBuilders.get("/student/index")).andReturn();

     }

}


13.6. WebTestClient

package cn.netkiller.webflux;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.reactive.server.WebTestClient;

@RunWith(SpringRunner.class)
@SpringBootTest
public class WebfluxApplicationTests {

    @Test
    public void contextLoads() {
    }

    private WebTestClient webTestClient;

    @Before
    public void setUp() {
        this.webTestClient = WebTestClient.bindToServer().baseUrl("http://localhost:8080").build();
    }

    @Test
    public void sample() throws Exception {
        this.webTestClient.get().uri("/").exchange().expectStatus().isOk().expectBody(String.class).isEqualTo("Hello world!");
    }

    @Test
    public void client() {

    }
}


第 14 章 Tomcat Spring 运行环境

目录

14.1. Maven
14.2. Spring Boot Quick start
14.2.1. pom.xml
14.2.2. Controller
14.3. Spring MVC configuration
14.4. Tomcat
14.5. 集成 Mybatis
14.5.1. pom.xml
14.5.2. properties
14.5.3. dataSource
14.5.4. SqlSessionFactory
14.5.5. Mapper 扫描
14.5.6. Mapper 单一class映射
14.5.7. Service
14.5.8. 测试实例

14.1. Maven

<?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>com.example</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>demo</name>
    <description>Demo project for Spring Boot</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.3.0.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <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>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>


14.2. Spring Boot Quick start

14.2.1. pom.xml

<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>api.netkiller.cn</groupId>
    <artifactId>api.netkiller.cn</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>Skyline</name>
    <description>skylinechencf@gmail.com</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.4.0.RELEASE</version>
    </parent>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>

    <build>
        <sourceDirectory>src</sourceDirectory>
        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.3</version>
                <configuration>
                    <source />
                    <target />
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

14.2.2. Controller

package hello;

import org.springframework.boot.*;
import org.springframework.boot.autoconfigure.*;
import org.springframework.stereotype.*;
import org.springframework.web.bind.annotation.*;

@Controller
@EnableAutoConfiguration
public class SampleController {

    @RequestMapping("/")
    @ResponseBody
    String home() {
        return "Hello World!";
    }

    public static void main(String[] args) throws Exception {
        SpringApplication.run(SampleController.class, args);
    }
}

测试

curl http://127.0.0.1:8080/


14.3. Spring MVC configuration

  <!-- **************************************************************** -->
  <!--  RESOURCE FOLDERS CONFIGURATION                                  -->
  <!--  Dispatcher configuration for serving static resources           -->
  <!-- **************************************************************** -->
  <mvc:resources location="/images/" mapping="/images/**" />
  <mvc:resources location="/css/" mapping="/css/**" />


14.4. Tomcat

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://xmlns.jcp.org/xml/ns/javaee"
    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
    id="WebApp_ID" version="3.1">
    <display-name>m.cf88.com</display-name>
    <welcome-file-list>
        <welcome-file>index.html</welcome-file>
        <welcome-file>index.htm</welcome-file>
        <welcome-file>index.jsp</welcome-file>
        <welcome-file>default.html</welcome-file>
        <welcome-file>default.htm</welcome-file>
        <welcome-file>default.jsp</welcome-file>
    </welcome-file-list>

    <servlet>
        <servlet-name>netkiller</servlet-name>
        <servlet-class>
            org.springframework.web.servlet.DispatcherServlet
        </servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>netkiller</servlet-name>
        <url-pattern>/welcome.jsp</url-pattern>
        <url-pattern>/welcome.html</url-pattern>
        <url-pattern>*.html</url-pattern>
    </servlet-mapping>

</web-app>

netkiller-servlet.xml

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="cn.netkiller.controller" />

    <bean id="viewResolver"
        class="org.springframework.web.servlet.view.UrlBasedViewResolver">
        <property name="viewClass"
            value="org.springframework.web.servlet.view.JstlView" />
        <property name="prefix" value="/WEB-INF/jsp/" />
        <property name="suffix" value=".jsp" />
    </bean>

</beans>


14.5. 集成 Mybatis

14.5.1. pom.xml

         <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.3.0</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>1.2.3</version>
        </dependency>

14.5.2. properties

     <bean id="configuracion"
        class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="location" value="classpath:resources/development.properties" />
    </bean>

jdbc.driverClassName=oracle.jdbc.driver.OracleDriver
jdbc.url=jdbc:oracle:thin:@192.168.4.9:1521:orcl
#jdbc.url=jdbc:mysql://127.0.0.1:3306/mybatis
jdbc.username=test
jdbc.password=123456

    <bean id="dataSource"
        class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="${jdbc.driverClassName}" />
        <property name="url" value="${jdbc.url}" />
        <property name="username" value="${jdbc.username}" />
        <property name="password" value="${jdbc.password}" />
    </bean>

14.5.3. dataSource

    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="${driver}" />
        <property name="url" value="${url}" />
        <property name="username" value="${username}" />
        <property name="password" value="${password}" />
    </bean>

14.5.4. SqlSessionFactory

创建SqlSessionFactory，需指定数据源，property名称必须为dataSource

    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
    </bean>

14.5.5. Mapper 扫描

    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="cn.netkiller.mappers" />
        <property name="annotationClass" value="cn.netkiller.mappers.annotation.MybatisMapper"/>
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
    </bean>

14.5.6. Mapper 单一class映射

创建数据映射器Mapper，属性mapperInterface的value必须为接口类

    <bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
        <property name="mapperInterface" value="com.mybatis.demo.UserMapper" />
        <property name="sqlSessionFactory" ref="sqlSessionFactory" />
    </bean>

14.5.7. Service

    <bean id="userService" class="cn.netkiller.service.UserService">
    </bean>

14.5.8. 测试实例

例 14.1. MyBatis

建立映射

package cn.netkiller.mapper;

import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Param;

import cn.netkiller.model.User;

public interface UserMapper {
    @Select("SELECT * FROM `user` WHERE id = #{id}")
    public User findById(@Param("id") int id);
}

建立模型

package cn.netkiller.model;

public class User {
    private String id;
    private String name;
    private int age;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "User [id=" + id + ", name=" + name + ", age=" + age + "]";
    }
}

建立 service

package cn.netkiller.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import cn.netkiller.mapper.UserMapper;
import cn.netkiller.model.User;

@Service
public class UserService {
    @Autowired
    private UserMapper userMapper;

    public UserMapper getUserMapper() {
        return userMapper;
    }

    public void setUserMapper(UserMapper userMapper) {
        this.userMapper = userMapper;
    }

    public User findById(int id) {
        return userMapper.findById(id);
    }
}

建立控制器

package cn.netkiller.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.view.RedirectView;

import cn.netkiller.mapper.UserMapper;
import cn.netkiller.model.User;
import cn.netkiller.service.UserService;

@Controller
public class Index {

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private UserService userService;

    @RequestMapping("/index")
    // @ResponseBody
    public ModelAndView index() {

        String message = "Hello";
        return new ModelAndView("index/index", "variable", message);
    }

    @RequestMapping("/user")
    public ModelAndView user() {

        User user = userService.findById(2);
        String message = user.toString();
        return new ModelAndView("index/index", "variable", message);
    }

    @RequestMapping("/member")
    public ModelAndView member() {
        User user = userMapper.findById(2);
        String message = user.toString();
        return new ModelAndView("index/index", "variable", message);
    }
}


第 15 章 Miscellaneous

目录

15.1. Object to Json
15.2. Json To Object

15.1. Object to Json

ObjectMapper mapper = new ObjectMapper();
User user = new User();

//Object to JSON in file
mapper.writeValue(new File("c:\\user.json"), user);

//Object to JSON in String
String jsonInString = mapper.writeValueAsString(user);

//Convert object to JSON string and pretty print
String jsonInString = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(user);


15.2. Json To Object

ObjectMapper mapper = new ObjectMapper();
String jsonInString = "{'name' : 'mkyong'}";

//JSON from file to Object
User user = mapper.readValue(new File("c:\\user.json"), User.class);

//JSON from String to Object
User user = mapper.readValue(jsonInString, User.class);


第 16 章 FAQ

目录

16.1. org.hibernate.dialect.Oracle10gDialect does not support identity key generation
16.2. No identifier specified for entity
16.3. Could not read document: Invalid UTF-8 middle byte 0xd0
16.4. java.sql.SQLRecoverableException: IO Error: The Network Adapter could not establish the connection
16.5. Field javaMailSender in cn.netkiller.rest.EmailRestController required a bean of type 'org.springframework.mail.javamail.JavaMailSender' that could not be found.
16.6. org.postgresql.util.PSQLException: FATAL: no pg_hba.conf entry for host "172.16.0.3", user "test", database "test ", SSL off
16.7. Spring boot 怎样显示执行的SQL语句
16.8. Cannot determine embedded database driver class for database type NONE
16.9. Spring boot / Spring cloud 时区差8个小时
16.10. @Value 取不到值
16.11. Spring boot 2.1.0

16.1. org.hibernate.dialect.Oracle10gDialect does not support identity key generation

@GeneratedValue(strategy=GenerationType.IDENTITY)
换成
@GeneratedValue(strategy=GenerationType.AUTO)

or

@Id
@Column(name = "ID")
@GeneratedValue(strategy=GenerationType.SEQUENCE, generator = "id_Sequence")
@SequenceGenerator(name = "id_Sequence", sequenceName = "ID_SEQ")
private int id;


16.2. No identifier specified for entity

在实体中使用

import javax.persistence.Id;
替换
import org.springframework.data.annotation.Id;


16.3. Could not read document: Invalid UTF-8 middle byte 0xd0

Spring 默认不支持 UTF-8

2016-08-17 16:04:53.148 WARN 7700 --- [nio-8080-exec-1] .w.s.m.s.DefaultHandlerExceptionResolver : Failed to read HTTP message:	org.springframework.http.converter.HttpMessageNotReadableException:Could not read document: Invalid UTF-8 middle byte 0xd0 at [Source: java.io.PushbackInputStream@33aa54cc; line: 1, column: 38](through reference chain: api.domain.oracle.Withdraw["bankname"]); nested exception is 	com.fasterxml.jackson.databind.JsonMappingException: Invalid UTF-8 middle byte 0xd0 at [Source: java.io.PushbackInputStream@33aa54cc; line: 1, column: 38]
            (through reference chain: api.domain.oracle.Withdraw["bankname"])

解决方案 application.properties 配置文件中加入如下配置：

            spring.messages.encoding=UTF-8
            server.tomcat.uri-encoding=UTF-8
            spring.http.encoding.charset=UTF-8
            spring.http.encoding.enabled=true
            spring.http.encoding.force=true


16.4. java.sql.SQLRecoverableException: IO Error: The Network Adapter could not establish the connection

分析，Oracle 数据库无法连接，确认用户密码正确，日志提示 The Network Adapter could not establish the connection 看上去更像网络故障，同事还有下面两条日志。

            Caused by: oracle.net.ns.NetException: The Network Adapter could not
            establish the connection
            Caused by: java.net.SocketTimeoutException: connect timed out

通过 ss 命令可以看到有tcp操作，可以排除不是网络故障。

            [root@iZ62m7362hwZ ~]# ss -ant | grep 1521
            TIME-WAIT 0 0 47.90.18.24:45780 15.84.21.59:1521

检查你的用户名与密码是否含有特殊字符，特殊字符需要使用转义字符"\".

            spring.datasource.url=jdbc:oracle:thin:neo/\[y7\$ghM\~3b@(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=215.184.211.50)(PORT=1521))(LOAD_BALANCE=YES)(FAILOVER=ON)(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=orcl)(FAILOVER_MODE=(TYPE=SESSION)(METHOD=BASIC))))
            #spring.datasource.username=neo
            #spring.datasource.password=[y7$ghM~3b

将用户名写入spring.datasource.url中，格式jdbc:oracle:thin:用户名/密码@(......)，禁用spring.datasource.username和spring.datasource.password两个配置项。


16.5. Field javaMailSender in cn.netkiller.rest.EmailRestController required a bean of type 'org.springframework.mail.javamail.JavaMailSender' that could not be found.

启动提示 'org.springframework.mail.javamail.JavaMailSender' that could not be found 这句话很误导人。实际上是 (spring.mail.host) did not find property 'host'

            ***************************
            APPLICATION FAILED TO START
            ***************************

            Description:

            Field javaMailSender in cn.netkiller.rest.EmailRestController required a
            bean of type 'org.springframework.mail.javamail.JavaMailSender' that
            could not be found.
            - Bean method 'mailSender' not loaded because AnyNestedCondition 0
            matched 2 did not; NestedCondition on
            MailSenderAutoConfiguration.MailSenderCondition.JndiNameProperty
            @ConditionalOnProperty (spring.mail.jndi-name) did not find property
            'jndi-name'; NestedCondition on
            MailSenderAutoConfiguration.MailSenderCondition.HostProperty
            @ConditionalOnProperty (spring.mail.host) did not find property
            'host'

            Action:

            Consider revisiting the conditions above or defining a bean of type
            'org.springframework.mail.javamail.JavaMailSender' in your
            configuration.

解决方案，application.properties 增加 spring.mail.host=localhost


16.6. org.postgresql.util.PSQLException: FATAL: no pg_hba.conf entry for host "172.16.0.3", user "test", database "test ", SSL off

确认 pg_hba.conf 配置正确，并且 psql 可以正常链接，spring仍然报错

            spring.datasource.url=jdbc:postgresql://47.90.18.244:5432/test
            spring.datasource.username=test
            spring.datasource.password=test
            spring.jpa.show-sql=true
            spring.jpa.hibernate.ddl-auto=create-drop
            spring.jpa.generate-ddl=true

请检查 jdbc:postgresql://47.90.18.244:5432/test 后面test是否多了一个空格或者有特殊字符。删除test后面的空格可以解决


16.7. Spring boot 怎样显示执行的SQL语句

spring.jpa.show-sql=true


16.8. Cannot determine embedded database driver class for database type NONE

错误如下

***************************
APPLICATION FAILED TO START
***************************

Description:

Cannot determine embedded database driver class for database type NONE

Action:

If you want an embedded database please put a supported one on the classpath. If you have database settings to be loaded from a particular profile you may need to active it (no profiles are currently active).

背景：Maven 项目中并不包含任何与数据库有关的依赖。问题出在另一个公共包中如： common-version.jar

解决方案：排除不需要的包

        <dependency>
            <groupId>cn.netkiller</groupId>
            <artifactId>common</artifactId>
            <version>0.0.1-SNAPSHOT</version>
            <exclusions>
                <exclusion>
                    <groupId>mysql</groupId>
                    <artifactId>mysql-connector-java</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-data-jpa</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-jdbc</artifactId>
                </exclusion>
            </exclusions>
        </dependency>


16.9. Spring boot / Spring cloud 时区差8个小时

经过检查：操作系统时区 CST，数据库是 SYSTEM，Spring boot 获取时间相差8个小时。

分析：认为是 @JsonFormat 格式化造成的。

解决方案：在 @JsonFormat 中增加时区设置。

    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai")
    public Date ctime;

期间尝试多种方式无效:

# 下面例子无效
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.mvc.date-format=yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone=GMT+8

# 下面方法无效
spring.datasource.url=jdbc:mysql://119.29.241.95:3306/5kwords?useSSL=false&serverTimezone=UTC

# 下面配置仍然无效
spring.jpa.properties.jadira.usertype.autoRegisterUserTypes = true
spring.jpa.properties.jadira.usertype.javaZone=Asia/Shanghai
spring.jpa.properties.jadira.usertype.databaseZone=Asia/Shanghai

根源在 Json 转化。

完成的例子

package common.domain;

import java.io.Serializable;
import java.util.Date;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

import org.springframework.format.annotation.DateTimeFormat;

import com.fasterxml.jackson.annotation.JsonFormat;

@Entity
@Table(name = "article", catalog = "cms")
public class Article implements Serializable {
    private static final long serialVersionUID = 7603772682950271321L;

    @Id
    public int id;
    public String title;
    @Column(name = "short")
    public String shortTitle;
    public String description;
    public String author;
    public String star;
    public String tags;
    public boolean status;
    public String content;
    public int typeId;
    public int siteId;

    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai")
    public Date ctime;

    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "America/Phoenix")
    public Date mtime;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public Date getCtime() {
        return ctime;
    }

    public void setCtime(Date ctime) {
        this.ctime = ctime;
    }

    public String getShortTitle() {
        return shortTitle;
    }

    public void setShortTitle(String shortTitle) {
        this.shortTitle = shortTitle;
    }

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }

    public String getStar() {
        return star;
    }

    public void setStar(String star) {
        this.star = star;
    }

    public String getTags() {
        return tags;
    }

    public void setTags(String tags) {
        this.tags = tags;
    }

    public boolean isStatus() {
        return status;
    }

    public void setStatus(boolean status) {
        this.status = status;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public int getTypeId() {
        return typeId;
    }

    public void setTypeId(int typeId) {
        this.typeId = typeId;
    }

    public int getSiteId() {
        return siteId;
    }

    public void setSiteId(int siteId) {
        this.siteId = siteId;
    }

    public Date getMtime() {
        return mtime;
    }

    public void setMtime(Date mtime) {
        this.mtime = mtime;
    }

    @Override
    public String toString() {
        return "Article [id=" + id + ", title=" + title + ", shortTitle=" + shortTitle + ", description=" + description + ", author=" + author + ", star=" + star + ", tags=" + tags + ", status=" + status + ", content=" + content + ", typeId=" + typeId + ", siteId=" + siteId + ", ctime=" + ctime + ", mtime=" + mtime + "]";
    }

}


16.10. @Value 取不到值

在构造方法中引用@value为null，由于spring实例化顺序为先执行构造方法，再注入成员变量，所以取值为null。

调用spring组件时使用new对象，而不是@Autowired。


16.11. Spring boot 2.1.0

application.properties 需要加入下面参数，否则 @Bean 不允许。

spring.main.allow-bean-definition-overriding=true

另外 MySQL驱动必须使用最新的 com.mysql.cj.jdbc.Driver


第 17 章 MyBatis

目录

17.1. Mybatis 入门
17.2. 接口注解

http://blog.mybatis.org/

17.1. Mybatis 入门

创建数据库与表并插入测试数据

CREATE DATABASE `mybatis` /*!40100 COLLATE 'utf8_general_ci' */;

CREATE USER 'mybatis'@'192.168.%' IDENTIFIED BY 'mybatis';
GRANT USAGE ON *.* TO 'mybatis'@'192.168.%';
GRANT SELECT, EXECUTE, SHOW VIEW, ALTER, ALTER ROUTINE, CREATE, CREATE ROUTINE, CREATE TEMPORARY TABLES, CREATE VIEW, DELETE, DROP, EVENT, INDEX, INSERT, REFERENCES, TRIGGER, UPDATE, LOCK TABLES  ON `mybatis`.* TO 'mybatis'@'192.168.%' WITH GRANT OPTION;
FLUSH PRIVILEGES;
SHOW GRANTS FOR 'mybatis'@'192.168.%';

CREATE TABLE IF NOT EXISTS `user` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(50) DEFAULT NULL,
  `age` int(10) unsigned DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;

INSERT INTO `user` (`id`, `name`, `age`) VALUES
    (1, 'Neo', 35),
    (2, 'Jerry', 36);

Maven pom.xml 中加入依赖包

    <dependencies>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.3.0</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.37</version>
        </dependency>
    </dependencies>

pom.xml

<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>MyBatis</groupId>
    <artifactId>MyBatis</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>3.8.1</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.3.0</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.37</version>
        </dependency>
    </dependencies>
    <build>
        <sourceDirectory>src</sourceDirectory>
        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.3</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

src/mybatis.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC" />
            <!-- 配置数据库连接信息 -->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver" />
                <property name="url" value="jdbc:mysql://192.168.6.1:3306/mybatis" />
                <property name="username" value="mybatis" />
                <property name="password" value="mybatis" />
            </dataSource>
        </environment>
    </environments>
    <mappers>

        <mapper resource="cn/netkiller/mapping/userMapping.xml" />
    </mappers>
</configuration>

src/cn/netkiller/mapping/userMapping.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="cn.netkiller.mapping.UserMapping">
    <select id="getUser" parameterType="String" resultType="cn.netkiller.model.User">
        select * from user where id=#{id}
    </select>
</mapper>

resultType 文件

package cn.netkiller.model;

public class User {
    private String id;
    private String name;
    private int age;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "User [id=" + id + ", name=" + name + ", age=" + age + "]";
    }
}

测试代码

package cn.netkiller.test;

import java.io.InputStream;

import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import cn.netkiller.model.*;

public class Tests {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        String resource = "mybatis.xml";

        InputStream is = Tests.class.getClassLoader().getResourceAsStream(resource);
        SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(is);
        SqlSession session = sessionFactory.openSession();

        String statement = "cn.netkiller.mapping.UserMapping.getUser";// 映射sql的标识字符串

        User user = session.selectOne(statement, "2");
        System.out.println(user.toString());
    }

}


17.2. 接口注解

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC" />
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver" />
                <property name="url" value="jdbc:mysql://192.168.6.1:3306/mybatis" />
                <property name="username" value="mybatis" />
                <property name="password" value="mybatis" />
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper class="cn.netkiller.mapper.UserMapper" />
    </mappers>
</configuration>

package cn.netkiller.mapper;

import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Param;

import cn.netkiller.model.User;

public interface UserMapper {
    @Select("SELECT * FROM `user` WHERE id = #{id}")
    public User findById(@Param("id") int id);
}

package cn.netkiller.test;

import java.io.InputStream;

import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import cn.netkiller.mapper.UserMapper;
import cn.netkiller.model.User;

public class AnnotationsTest {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        String resource = "mybatis.xml";

        InputStream is = XmlTest.class.getClassLoader().getResourceAsStream(resource);
        SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(is);
        SqlSession session = sessionFactory.openSession();

        UserMapper mapper = session.getMapper(UserMapper.class);
        User user = mapper.findById(2);

        System.out.println(user.toString());

    }
}


第 18 章 Apache Struts

目录

18.1. struts.xml
18.1.1. include
18.2. Struts Tags
18.2.1. property
18.2.2. set
18.2.3. url
18.2.4. s:include
18.2.5. s:action
18.2.6. HTML Form
18.2.6.1. form
18.2.6.2. textfield
18.2.6.3. s:hidden
18.2.6.4. select
18.2.7. iterator
18.2.8. if elseif else
18.3. Action
18.3.1. redirect
18.3.2. redirectAction
18.3.3. JSON
18.3.3.1. enableGZIP 压缩传输
18.3.3.2. excludeProperties 排除 Properties
18.3.3.3.
18.3.4. 传递 Timestamp 变量
18.4. Ajax + JSON
18.4.1. GET/POST JSON
18.5. Json 内容展示
18.5.1. 禁止方法
18.5.2. 格式化日期
18.5.3. 重命名变量名
18.5.4. org.apache.struts2.json
18.6. Interceptor
18.6.1. Session
18.7. Action 中使用线程
18.8. 日志
18.9. FAQ
18.9.1. Struts 怎样判断用户来自电脑还是移动设备

http://struts.apache.org/

You can checkout all the example applications from the Struts 2 GitHub repository at https://github.com/apache/struts-examples.

18.1. struts.xml

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://java.sun.com/xml/ns/javaee"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
    id="WebApp_ID" version="3.0">
    <display-name>helloworld</display-name>
    <welcome-file-list>
        <welcome-file>index.html</welcome-file>
        <welcome-file>index.htm</welcome-file>
        <welcome-file>index.jsp</welcome-file>
        <welcome-file>default.html</welcome-file>
        <welcome-file>default.htm</welcome-file>
        <welcome-file>default.jsp</welcome-file>
    </welcome-file-list>

    <filter>
        <filter-name>struts2</filter-name>
        <filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
    </filter>

    <filter-mapping>
        <filter-name>struts2</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

</web-app>

18.1.1. include

    <include file="/cn/netkiller/struts/ajax.xml" />
    <include file="/cn/netkiller/struts/admin.xml" />
    <include file="/cn/netkiller/struts/logs.xml" />


18.2. Struts Tags

使用Struts Tags 需要在jsp页面中加入下面一行。

<%@ taglib prefix="s" uri="/struts-tags" %>

18.2.1. property

<%@ taglib prefix="s" uri="/struts-tags" %>

<html>
<head>
    <title>Hello</title>
</head>
<body>

Hello, <s:property value="name"/>

</body>
</html>

<s:property value="messageStore.message" />
<s:property value="#session.user.username" />

<s:bean name="cn.netkiller.Person" var="personBean" />
<s:property value="#personBean.name" />

18.2.2. set

<s:set var="personName" value="person.name"/>
Hello, <s:property value="#personName"/>

<s:set var="janesName">Jane Doe</s:set>
<s:property value="#janesName"/>

禁止HTML转义，如果你的字符串中含有&, <, > 等字符输出就会出现 &amp;, &lt;, &gt; escapeHtml="false" 可以禁止这样的转义，原样输出。

<s:property value="url" escapeHtml="false"/>

https://struts.apache.org/docs/property.html

Name	Required	Default	Evaluated	Type	Description
default	false		false	String	The default value to be used if value attribute is null
escapeCsv	false	false	false	Boolean	Whether to escape CSV (useful to escape a value for a column)
escapeHtml	false	true	false	Boolean	Whether to escape HTML
escapeJavaScript	false	false	false	Boolean	Whether to escape Javascript
escapeXml	false	false	false	Boolean	Whether to escape XML

18.2.3. url

<p><a href="<s:url action='hello'/>">Hello World</a></p>

<s:url action="hello" var="helloLink">
  <s:param name="userName">Bruce Phillips</s:param>
</s:url>

<p><a href="${helloLink}">Hello Bruce Phillips</a></p>

18.2.4. s:include

<s:include value="/pages/example.jsp"></s:include>

18.2.5. s:action

<%@ taglib prefix="s" uri="/struts-tags" %>
<s:action name="index" namespace="/news" executeResult="true" />

<s:action name="index" namespace="/member" executeResult="true">
    <s:param name="name">Neo</s:param>
</s:action>

18.2.6. HTML Form

18.2.6.1. form

<p>Get your own personal hello by filling out and submitting this form.</p>

<s:form action="hello">

  <s:textfield name="userName" label="Your name" />

   <s:submit value="Submit" />

</s:form>

18.2.6.2. textfield

<s:textfield name="variable"/>

18.2.6.3. s:hidden

隐藏表单

<s:hidden id="unique" name="form.unique" value=""/>

18.2.6.4. select

<s:select name="city" list="{'Beijing','Shanghai','Guangdong','Shenzhen'}" theme="simple" headerKey="Shenzhen" headerValue="Shenzhen"></s:select>

<select name="city" id="searchCriteriaForm_city">
    <option value="Shenzhen">Shenzhen</option>
    <option value="Beijing">Beijing</option>
    <option value="Shanghai">Shanghai</option>
    <option value="Guangdong">Guangdong</option>
    <option value="Shenzhen">Shenzhen</option>
</select>

<s:select name="city" id="city" list="#{1:'Beijing',2:'Shanghai',3:'Guangdong',4:'Shenzhen'}"  label="city" listKey="key" listValue="value"  headerKey="4" headerValue="Shenzhen" />

<select name="city" id="city">
    <option value="4">Shenzhen</option>
    <option value="1">Beijing</option>
    <option value="2">Shanghai</option>
    <option value="3">Guangdong</option>
    <option value="4">Shenzhen</option>
</select>

18.2.7. iterator

<s:iterator value="people">
    <s:property value="lastName"/>, <s:property value="firstName"/>
</s:iterator>

18.2.8. if elseif else

<s:if test="%{false}">
    <div>Will Not Be Executed</div>
</s:if>
<s:elseif test="%{true}">
    <div>Will Be Executed</div>
</s:elseif>
<s:else>
    <div>Will Not Be Executed</div>
</s:else>


18.3. Action

struts.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts PUBLIC
    "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
    "http://struts.apache.org/dtds/struts-2.0.dtd">

<struts>

    <constant name="struts.devMode" value="true" />

    <package name="basicstruts2" extends="struts-default">
        <action name="index">
            <result>/index.jsp</result>
        </action>

        <action name="hello"
            class="org.apache.struts.helloworld.action.HelloWorldAction" method="execute">
            <result name="success">/HelloWorld.jsp</result>
        </action>

        <action name="register" class="org.apache.struts.register.action.Register"
            method="execute">
            <result name="success">/thankyou.jsp</result>
            <result name="input">/register.jsp</result>
        </action>

    </package>
</struts>

18.3.1. redirect

<action name="hello"
   class="com.tutorialspoint.struts2.HelloWorldAction"
   method="execute">
   <result name="success" type="redirect">
       <param name="location">
         /NewWorld.jsp
      </param >
   </result>
</action>

18.3.2. redirectAction

<package name="public" extends="struts-default">
    <action name="login" class="...">
        <!-- Redirect to another namespace -->
        <result type="redirectAction">
            <param name="actionName">dashboard</param>
            <param name="namespace">/secure</param>
        </result>
    </action>
</package>

<package name="secure" extends="struts-default" namespace="/secure">
    <-- Redirect to an action in the same namespace -->
    <action name="dashboard" class="...">
        <result>dashboard.jsp</result>
        <result name="error" type="redirectAction">error</result>
    </action>

    <action name="error" class="...">
        <result>error.jsp</result>
    </action>
</package>

<package name="passingRequestParameters" extends="struts-default" namespace="/passingRequestParameters">
   <!-- Pass parameters (reportType, width and height) -->
   <!--
   The redirectAction url generated will be :
   /genReport/generateReport.action?reportType=pie&amp;width=100&amp;height=100#summary
   -->
   <action name="gatherReportInfo" class="...">
      <result name="showReportResult" type="redirectAction">
         <param name="actionName">generateReport</param>
         <param name="namespace">/genReport</param>
         <param name="reportType">pie</param>
         <param name="width">100</param>
         <param name="height">100</param>
         <param name="empty"></param>
         <param name="suppressEmptyParameters">true</param>
         <param name="anchor">summary</param>
      </result>
   </action>

   <action name="Auth" class="cn.netkiller.api.action.Auth">
        <result name="credit" type="redirectAction">
            <param name="actionName">history</param>
            <param name="namespace">/api/report</param>
            <param name="LoginName">${LoginName}</param>
            <param name="Direction">${Direction}</param>
            <param name="StartDate">${StartDate}</param>
            <param name="EndDate">${EndDate}</param>
        </result>
    </action>

</package>

18.3.3. JSON

JSON 拦截器

    <action name="withdraw" class="cn.netkiller.api.action.Report" method="getHistory">
        <interceptor-ref name="defaultStack" />
        <interceptor-ref name="json">
            <param name="enableSMD">true</param>
        </interceptor-ref>
        <result name="success" type="json">
            <param name="enableGZIP">true</param>
            <param name="excludeProperties">.*direction</param>
        </result>
    </action>

18.3.3.1. enableGZIP 压缩传输

        <result name="success" type="json">
            <param name="enableGZIP">true</param>
        </result>

18.3.3.2. excludeProperties 排除 Properties

        <result name="success" type="json">
            <param name="excludeProperties">.*direction</param>
        </result>

18.3.3.3.

18.3.4. 传递 Timestamp 变量

Struts 从URL传递 Timestamp 类型的变量 StartDate

http://www.netkiller.cn/api/report/history.do?LoginName=888666&StartDate=2016-01-01&EndDate=2016-02-29

解决方法 参考 setStartDate / setEndDate 两个方法，以字符串方式传入，然后转换为Timestamp类型

package cn.netkiller.api.action;

import java.sql.Timestamp;
import java.util.Calendar;
import org.apache.struts2.json.annotations.JSON;
import com.opensymphony.xwork2.Action;
import com.opensymphony.xwork2.ActionSupport;

public class Report extends ActionSupport {
    private static final long serialVersionUID = 6484202866632836225L;

    private String loginName;
    private Timestamp startDate = null;
    private Timestamp endDate = null;

    public Report() {

    }

    public String execute() {
        return Action.SUCCESS;
    }

    public String getLoginName() {
        return loginName;
    }

    public void setLoginName(String loginName) {
        this.loginName = loginName;
    }

    public Timestamp getStartDate() {
        return startDate;
    }

    public void setStartDate(String startDate) {
        this.startDate = Timestamp.valueOf(startDate + " 00:00:00");
    }

    public Timestamp getEndDate() {
        return endDate;
    }

    public void setEndDate(String endDate) {
        this.endDate = Timestamp.valueOf(endDate + " 00:00:00");
    }

}


18.4. Ajax + JSON

struts.xml 中加入

        <action name="Captcha" class="com.example.action.ajax.Captcha">
            <result name="success" type="json"></result>
        </action>

Java 文件

package com.example.action.ajax;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.opensymphony.xwork2.Action;
import com.opensymphony.xwork2.ActionSupport;

public class Captcha extends ActionSupport {
    private static final long serialVersionUID = 7284583098398030297L;

    private String string1 = "A";
    private String[] stringarray1 = {"A1","B1"};
    private int number1 = 123456789;
    private int[] numberarray1 = {1,2,3,4,5,6,7,8,9};
    private List<String> lists = new ArrayList<String>();
    private Map<String, String> maps = new HashMap<String, String>();

    //no getter method, will not include in the JSON

    public Captcha(){
        lists.add("list1");
        lists.add("list2");
        lists.add("list3");
        lists.add("list4");
        lists.add("list5");

        maps.put("key1", "value1");
        maps.put("key2", "value2");
        maps.put("key3", "value3");
        maps.put("key4", "value4");
        maps.put("key5", "value5");
    }

    public String execute() {
               return Action.SUCCESS;
        }

    public String getString1() {
        return string1;
    }

    public void setString1(String string1) {
        this.string1 = string1;
    }

    public String[] getStringarray1() {
        return stringarray1;
    }

    public void setStringarray1(String[] stringarray1) {
        this.stringarray1 = stringarray1;
    }

    public int getNumber1() {
        return number1;
    }

    public void setNumber1(int number1) {
        this.number1 = number1;
    }

    public int[] getNumberarray1() {
        return numberarray1;
    }

    public void setNumberarray1(int[] numberarray1) {
        this.numberarray1 = numberarray1;
    }

    public List<String> getLists() {
        return lists;
    }

    public void setLists(List<String> lists) {
        this.lists = lists;
    }

    public Map<String, String> getMaps() {
        return maps;
    }

    public void setMaps(Map<String, String> maps) {
        this.maps = maps;
    }

}

测试URL http://localhost:8080/ajax/Captcha.action

18.4.1. GET/POST JSON

struts.xml 文件加入 enableSMD

        <action name="Captcha" class="com.example.action.ajax.Captcha">
            <interceptor-ref name="defaultStack" />
            <interceptor-ref name="json">
                <param name="enableSMD">true</param>
            </interceptor-ref>
            <result name="success" type="json"></result>
        </action>

Java 代码

package com.example.action.ajax;

import com.opensymphony.xwork2.Action;
import com.opensymphony.xwork2.ActionSupport;

public class Captcha extends ActionSupport {
    private static final long serialVersionUID = 7284583098398030297L;

    private String phone = "13113668890";
    private String email = "netkiller@msn.com";
    private Boolean status = false;

    //no getter method, will not include in the JSON

    public Captcha() {

    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String execute() {
        this.status = true;
        System.out.printf("%s, %s, %s, %b\n", this.getClass().getName(), this.getPhone(), this.getEmail(), this.getStatus());
        return Action.SUCCESS;
    }

    public String getPhone() {
        return this.phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }

    public void setStatus(Boolean status) {
        this.status = status;
    }
    public boolean getStatus() {
        return this.status;
    }
}

使用 curl 模拟 post 测试

# curl http://192.168.4.34:8080/ajax/Captcha.do
{"email":"netkiller@msn.com","phone":"13113668890","status":true}

# curl -X POST -H "Content-Type:application/json" -d '{"phone":"13066884444", "email":"neo.chan@live.com"}' http://192.168.4.34:8080/ajax/Captcha.do
{"email":"neo.chan@live.com","phone":"13066884444","status":true}

GET 例子

# curl "http://192.168.4.34:8080/ajax/Captcha.do?phone=13322993040&email=netkiller@mac.com"
{"email":"netkiller@mac.com","phone":"13322993040","status":true}


18.5. Json 内容展示

Struts 配置文件

    <package name="information" extends="main" namespace="/inf">
        <action name="Information" class="com.example.action.Infomation">
            <result type="tiles">information</result>
        </action>
    </package>

Action 文件

package cn.netkiller.action;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.net.URL;
import java.net.URLConnection;
import java.nio.charset.Charset;

import javax.json.Json;
import javax.json.JsonObject;
import javax.json.JsonReader;

import com.opensymphony.xwork2.Action;
import com.opensymphony.xwork2.ActionSupport;

public class Infomation extends ActionSupport{
    /**
     *
     */
    private static final long serialVersionUID = 1L;

    private JsonObject jsonObject = null;
    private String jsonString = "";

    @Override
    public String execute() throws IOException {

        String URL = "http://inf.example.com/list/json/93/20/0.html";
        System.out.printf("%s Requeted URL is %s", this.getClass().getName(), URL);

        StringBuilder sb = new StringBuilder();
        URLConnection urlConn = null;
        InputStreamReader in = null;
        try {
            URL url = new URL(URL);
            urlConn = url.openConnection();
            if (urlConn != null)
                urlConn.setReadTimeout(60 * 1000);
            if (urlConn != null && urlConn.getInputStream() != null) {
                in = new InputStreamReader(urlConn.getInputStream(), Charset.defaultCharset());
                BufferedReader bufferedReader = new BufferedReader(in);
                if (bufferedReader != null) {
                    int cp;
                    while ((cp = bufferedReader.read()) != -1) {
                        sb.append((char) cp);
                    }
                    bufferedReader.close();
                }
            }
            in.close();

            jsonString = sb.toString();

            System.out.println(jsonString);

            JsonReader reader = Json.createReader(new StringReader(jsonString));

            JsonObject jsonObject = reader.readObject();
            this.setJsonObject(jsonObject);

            reader.close();

            // System.out.println(jsonObject.size());

            /*for (int i = 0; i < jsonObject.size() - 2; i++) {
                JsonObject rowObject = jsonObject.getJsonObject(Integer.toString(i));
                // System.out.println(rowObject.toString());
                System.out.printf("%s\t%s\t%s\n", rowObject.getJsonString("id"), rowObject.getJsonString("title"),
                        rowObject.getJsonString("ctime"));
            }*/

        } catch (Exception e) {
            throw new RuntimeException("Exception while calling URL:" + URL, e);
        }

        return Action.SUCCESS;
    }

    public JsonObject getJsonObject() {
        return jsonObject;
    }

    public void setJsonObject(JsonObject jsonObject) {
        this.jsonObject = jsonObject;
    }

    public String getJsonString() {
        return jsonString;
    }

    public void setJsonString(String jsonString) {
        this.jsonString = jsonString;
    }
}

JSP 文件

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>

<table>
    <c:forEach items="${jsonObject.entrySet()}" var="json"
        varStatus="status">
        <c:if test="${not status.last}">
            <tr>
                <td>${json.key+1}</td>
                <td>${json.value.getJsonString("id")}</td>
                <td>${json.value.getJsonString("title")}</td>
                <td>${json.value.getJsonString("ctime")}</td>
            </tr>
        </c:if>
    </c:forEach>
</table>
===================

<c:forEach items="${jsonObject.entrySet()}" var="json">
      ${json.value.toString()}
</c:forEach>

===========

<s:property value="jsonString" />

解决双引号问题

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>

<%@ taglib prefix="s" uri="/struts-tags"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn"%>

<table>
    <c:forEach items="${jsonObject.entrySet()}" var="json"
        varStatus="status">
        <c:if test="${not status.last}">

            <c:set var="id"
                value="${fn:replace(json.value.getJsonString('id'),'\"', '')}" />
            <c:set var="title" value="${fn:replace(json.value.getJsonString('title'),'\"', '')}" />
            <c:set var="ctime"
                value="${fn:replace(json.value.getJsonString('ctime'),'\"', '')}" />
            <tr>
                <td><c:out value="${status.count}" /></td>
                <td><a href="/inf/Detail.do?id=<c:out value="${id}"/>"><c:out
                            value="${title}" /></a></td>
                <td><c:out value="${ctime}" /></td>
            </tr>
        </c:if>
    </c:forEach>
</table>

<c:set var="pages" value="${jsonObject.getJsonObject('pages')}" />

<a href="${pages.first}">首页</a>
<a href="${pages.before}">上一页</a>
<a href="${pages.next}">下一页</a>
<a href="${pages.last}">尾页</a>

<span>Count: ${pages.count}, Total: ${pages.total}</span>

18.5.1. 禁止方法

    @JSON(serialize = false)
    public String getDatas() {
        return datas;
    }

使用 excludeProperties 在 Action 中排除

    <action name="withdraw" class="cn.netkiller.api.action.Report" method="getHistory">
        <interceptor-ref name="defaultStack" />
        <interceptor-ref name="json">
            <param name="enableSMD">true</param>
        </interceptor-ref>
        <result name="success" type="json">
            <param name="enableGZIP">true</param>
            <param name="excludeProperties">.*direction</param>
        </result>
    </action>

18.5.2. 格式化日期

@JSON(format="yyyy-MM-dd")
public Date getDate(){
    return date;
}

18.5.3. 重命名变量名

@JSON(name="mypassword")
public String getPassword() {
    return password;
}

18.5.4. org.apache.struts2.json

JSONUtil.serialize(sb.toString());
JSONUtil.deserialize(sb.toString());


18.6. Interceptor

18.6.1. Session

在 web.xml 文件中定义 Session 超时时间

<session-config>
    <session-timeout>30</session-timeout>
</session-config>

创建拦截器程序

package cn.netkiller.interceptor;

import java.util.Map;
import java.lang.Override;

import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.interceptor.AbstractInterceptor;

public class SessionInterceptor extends AbstractInterceptor {

    private static final long serialVersionUID = 8347994918002285514L;

    @Override
    public String intercept(ActionInvocation invocation) throws Exception {
        Map<String, Object> session = invocation.getInvocationContext().getSession();
        if (session.isEmpty())
            return "nosession"; // session is empty/expired
        return invocation.invoke();
    }
}

配置拦截器

    <package name="mobile" extends="main" namespace="/mobile">
        <global-results>
            <result name="nosession" type="redirectAction">
                <param name="actionName">Index</param>
                <param name="namespace">/mobile</param>
            </result>
        </global-results>
        <interceptor name="session" class="cn.netkiller.SessionInterceptor" />
        <interceptor-stack name="sessionExpirayStack">
            <interceptor-ref name="defaultStack"/>
            <interceptor-ref name="session"/>
           </interceptor-stack>
           <default-interceptor-ref name="sessionExpirayStack" />

        <action name="testAction" class="TestClass">
            <interceptor-ref name="sessionExpirayStack" />
            <result name="success">success.jsp</result>
            <result name="error">error.jsp</result>
          </action>
      </package>


18.7. Action 中使用线程

背景，在Action中发送邮件，阻塞程序继续执行并返回500，使用 Thread 实现异步发送，因为我们并不关心邮件是否到达,只需正常发送即可。

    public String execute(){

        ...
        ...

            try {
                // Send email

                Thread sendmail = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            log.info("sendEmail Begin");
                            sender.sendEmail(form.getEmail(), form.getText());
                            log.info("sendEmail End");
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                });
                sendmail.setName("sendmail" + sendmail.getId() + "logingName:" + form.getLoginname());
                sendmail.start();

            } catch (Exception e) {
                e.printStackTrace();
                log.info("sendEmail Error");
            }

            ...
            ...

            log.info("CreateTrialAccount:" + form.toString());
            return Action.SUCCESS;
    }


18.8. 日志

WEB-INF/classes/log4j.properties

## # Struts2
log4j.logger.freemarker=INFO
log4j.logger.org.apache.struts2=DEBUG
log4j.logger.org.apache.struts2.components=INFO
log4j.logger.org.apache.struts2.dispatcher=INFO
log4j.logger.org.apache.struts2.convention=INFO
log4j.logger.org.apache.struts2.util=DEBUG
log4j.logger.org.apache.tiles.access.TilesAccess=DEBUG
log4j.logger.org.apache.struts2.interceptor=DEBUG

## # OpenSymphony Stuff
log4j.logger.com.opensymphony=INFO
log4j.logger.com.opensymphony.xwork2.ognl=INFO
log4j.logger.com.opensymphony.xwork2.util=INFO


18.9. FAQ

18.9.1. Struts 怎样判断用户来自电脑还是移动设备

package cn.netkiller.action;

import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionSupport;
import javax.servlet.http.HttpServletRequest;

public class Index extends ActionSupport {

    private static final long serialVersionUID = 7782483098148890753L;

    public String execute() {
        HttpServletRequest request = (HttpServletRequest) ActionContext.getContext().get(org.apache.struts2.StrutsStatics.HTTP_REQUEST);
        String userAgent = request.getHeader("User-Agent").toLowerCase();
        System.out.println(this.getClass().getName() + ": " + userAgent);
        String[] mobileAgents = { "iphone", "android", "phone", "mobile", "wap", "netfront", "java", "opera mobi", "opera mini", "ucweb", "windows ce", "symbian", "series", "webos", "sony", "blackberry", "dopod", "nokia", "samsung", "palmsource", "xda", "pieplus", "meizu", "midp", "cldc", "motorola", "foma", "docomo", "up.browser", "up.link", "blazer", "helio", "hosin", "huawei", "novarra", "coolpad", "webos", "techfaith", "palmsource", "alcatel", "amoi", "ktouch", "nexian", "ericsson", "philips", "sagem", "wellcom", "bunjalloo", "maui", "smartphone", "iemobile", "spice", "bird", "zte-", "longcos", "pantech", "gionee", "portalmmm", "jig browser", "hiptop", "benq", "haier", "^lct", "320x320", "240x320", "176x220", "w3c ", "acs-", "alav", "alca", "amoi", "audi", "avan", "benq", "bird", "blac", "blaz", "brew", "cell", "cldc", "cmd-", "dang", "doco", "eric", "hipt", "inno", "ipaq", "java", "jigs", "kddi", "keji", "leno", "lg-c", "lg-d", "lg-g", "lge-", "maui", "maxo", "midp", "mits", "mmef", "mobi", "mot-",
                "moto", "mwbp", "nec-", "newt", "noki", "oper", "palm", "pana", "pant", "phil", "play", "port", "prox", "qwap", "sage", "sams", "sany", "sch-", "sec-", "send", "seri", "sgh-", "shar", "sie-", "siem", "smal", "smar", "sony", "sph-", "symb", "t-mo", "teli", "tim-", "tsm-", "upg1", "upsi", "vk-v", "voda", "wap-", "wapa", "wapi", "wapp", "wapr", "webc", "winw", "winw", "xda", "xda-", "Googlebot-Mobile" };
        if (userAgent != null) {
            for (String mobileAgent : mobileAgents) {
                if (userAgent.indexOf(mobileAgent) >= 0) {
                    return "mobile";
                }
            }
        }

        return "computer";
    }
}

    <action name="Index" class="cn.netkiller.action.Index">
        <result name="computer" type="redirectAction">
            <param name="actionName">index</param>
            <param name="namespace">/computer</param>
        </result>
        <result name="mobile" type="redirect">
            <param name="location">http://m.netkiller.cn/index.html</param>
        </result>
    </action>


第 19 章 Apache Tiles

目录

19.1. 配置 Tiles
19.1.1. Maven
19.1.2. web.xml
19.2. Template 配置模板
19.3. Struts tiles

19.1. 配置 Tiles

19.1.1. Maven

    <dependency>
      <groupId>org.apache.struts</groupId>
      <artifactId>struts-tiles</artifactId>
      <version>1.3.10</version>
    </dependency>

19.1.2. web.xml

Tiles 有两种配置方法，选择一种最适合你的方式添加到 web.xml 文件即可

第一种方式 Tiles servlet

<servlet>
    <servlet-name>tiles</servlet-name>
    <servlet-class>org.apache.tiles.web.startup.TilesServlet</servlet-class>
    ...
    <load-on-startup>2</load-on-startup>
</servlet>

第二种方式 Tiles listener

<listener>
    <listener-class>org.apache.tiles.web.startup.TilesListener</listener-class>
</listener>


19.2. Template 配置模板

WEB-INF/tiles.xml

<tiles-definitions>
    <definition name="index" path="/WEB-INF/jsp/index.jsp">
        <put name="title"  value="Tiles Example" />
        <put name="header" value="/WEB-INF/jsp/header.jsp" />
        <put name="menu"   value="/WEB-INF/jsp/menu.jsp" />
        <put name="body"   value="/WEB-INF/jsp/body.jsp" />
        <put name="footer" value="/WEB-INF/jsp/footer.jsp" />
    </definition>
    <definition name="template_mobile" template="/WEB-INF/jsp/mobile/template.jsp">
        <put-attribute name="header" value="/WEB-INF/jsp/mobile/header.jsp" />
        <put-attribute name="content" value="" />
        <put-attribute name="footer" value="/WEB-INF/jsp/mobile/footer.jsp" />
    </definition>
</tiles-definitions>

/WEB-INF/jsp/mobile/template.jsp

<%@page pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<%@taglib prefix="tiles" uri="http://tiles.apache.org/tags-tiles" %>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>www.netkiller.cn</title>
</head>
<body>

<tiles:insertAttribute name="header" ignore="true" />

<tiles:insertAttribute name="content" ignore="true" />

<tiles:insertAttribute name="footer" ignore="true" />

</body>
</html>


19.3. Struts tiles

/WEB-INF/tiles.xml

    <definition name="Index" extends="template_mobile">
        <put-attribute name="content" value="/WEB-INF/jsp/mobile/Index.jsp" />
    </definition>

src/struts.xml

<struts>
    <package name="mobile" extends="main" namespace="/mobile">
        <action name="Index" class="cn.netkiller.mobile.action.Index" method="execute">
            <result name="success" type="tiles">Index</result>
        </action>
    </package>
</struts>


第 20 章 Play

https://www.playframework.com/

安装Play

curl -s https://raw.githubusercontent.com/oscm/shell/master/lang/java/framework/play.sh | bash

或者

cd /usr/local/src/

wget http://downloads.typesafe.com/typesafe-activator/1.3.2/typesafe-activator-1.3.2-minimal.zip

unzip typesafe-activator-1.3.2-minimal.zip

mv activator-1.3.2-minimal /srv/
ln -s activator-1.3.2-minimal /srv/activator

cat >> /etc/profile.d/activator.sh <<'EOF'
export PATH=$PATH:/srv/activator
EOF

首次运行会下载所需的包

# activator
Getting com.typesafe.activator activator-launcher 1.3.2 ...
downloading https://repo.typesafe.com/typesafe/ivy-releases/com.typesafe.activator/activator-launcher/1.3.2/jars/activator-launcher.jar ...
    [SUCCESSFUL ] com.typesafe.activator#activator-launcher;1.3.2!activator-launcher.jar (3004ms)
downloading https://repo1.maven.org/maven2/org/scala-lang/scala-library/2.11.5/scala-library-2.11.5.jar ...
    [SUCCESSFUL ] org.scala-lang#scala-library;2.11.5!scala-library.jar (49602ms)
downloading https://repo.typesafe.com/typesafe/ivy-releases/com.typesafe.activator/activator-props/1.3.2/jars/activator-props.jar ...
    [SUCCESSFUL ] com.typesafe.activator#activator-props;1.3.2!activator-props.jar (2091ms)
downloading https://repo.typesafe.com/typesafe/ivy-releases/com.typesafe.activator/activator-ui-common/1.3.2/jars/activator-ui-common.jar ...
    [SUCCESSFUL ] com.typesafe.activator#activator-ui-common;1.3.2!activator-ui-common.jar (2480ms)
downloading https://repo.typesafe.com/typesafe/ivy-releases/org.scala-sbt/launcher-interface/0.13.8-M5/jars/launcher-interface.jar ...
    [SUCCESSFUL ] org.scala-sbt#launcher-interface;0.13.8-M5!launcher-interface.jar (2489ms)
downloading https://repo.typesafe.com/typesafe/ivy-releases/org.scala-sbt/completion_2.11/0.13.8-M5/jars/completion_2.11.jar ...
    [SUCCESSFUL ] org.scala-sbt#completion_2.11;0.13.8-M5!completion_2.11.jar (6234ms)
downloading https://repo.typesafe.com/typesafe/ivy-releases/com.typesafe.activator/activator-templates-cache/1.0-a0afb008ea619bf9d87dc010156cddffa8a6f880/jars/activator-templates-cache.jar ...
    [SUCCESSFUL ] com.typesafe.activator#activator-templates-cache;1.0-a0afb008ea619bf9d87dc010156cddffa8a6f880!activator-templates-cache.jar (8632ms)
downloading https://repo.typesafe.com/typesafe/ivy-releases/com.typesafe.activator/activator-common/1.0-a0afb008ea619bf9d87dc010156cddffa8a6f880/jars/activator-common.jar ...
    [SUCCESSFUL ] com.typesafe.activator#activator-common;1.0-a0afb008ea619bf9d87dc010156cddffa8a6f880!activator-common.jar (23184ms)
downloading https://repo1.maven.org/maven2/org/scala-lang/modules/scala-xml_2.11/1.0.1/scala-xml_2.11-1.0.1.jar ...
    [SUCCESSFUL ] org.scala-lang.modules#scala-xml_2.11;1.0.1!scala-xml_2.11.jar(bundle) (8692ms)
downloading https://repo1.maven.org/maven2/org/scala-lang/modules/scala-parser-combinators_2.11/1.0.1/scala-parser-combinators_2.11-1.0.1.jar ...
    [SUCCESSFUL ] org.scala-lang.modules#scala-parser-combinators_2.11;1.0.1!scala-parser-combinators_2.11.jar(bundle) (5927ms)
downloading https://repo1.maven.org/maven2/org/apache/lucene/lucene-core/4.3.0/lucene-core-4.3.0.jar ...
    [SUCCESSFUL ] org.apache.lucene#lucene-core;4.3.0!lucene-core.jar (22313ms)
downloading https://repo1.maven.org/maven2/org/apache/lucene/lucene-analyzers-common/4.3.0/lucene-analyzers-common-4.3.0.jar ...
    [SUCCESSFUL ] org.apache.lucene#lucene-analyzers-common;4.3.0!lucene-analyzers-common.jar (12576ms)
downloading https://repo1.maven.org/maven2/org/apache/lucene/lucene-queryparser/4.3.0/lucene-queryparser-4.3.0.jar ...
    [SUCCESSFUL ] org.apache.lucene#lucene-queryparser;4.3.0!lucene-queryparser.jar (6739ms)
downloading https://repo1.maven.org/maven2/com/typesafe/akka/akka-actor_2.11/2.3.9/akka-actor_2.11-2.3.9.jar ...
    [SUCCESSFUL ] com.typesafe.akka#akka-actor_2.11;2.3.9!akka-actor_2.11.jar (14051ms)
downloading https://repo1.maven.org/maven2/com/amazonaws/aws-java-sdk/1.3.29/aws-java-sdk-1.3.29.jar ...


第 21 章 Log

目录

21.1. Logback
21.1.1. Maven 包
21.1.2. Example
21.2. slf4j
21.3. log4j
21.3.1. 安装 Log4j
21.3.1.1. 手工安装
21.3.1.2. Maven
21.3.2. log4j 环境变量
21.3.3. Log4j Example
21.3.4. log4j.properties

21.1. Logback

http://logback.qos.ch/index.html

Logback 是 log4j 作者开发，目前的趋势Log4j逐步被Logback取代。

21.1.1. Maven 包

        <!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-api -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.25</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/ch.qos.logback/logback-core -->
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-core</artifactId>
            <version>1.2.3</version>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-access</artifactId>
            <version>1.2.3</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/ch.qos.logback/logback-classic -->
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.3</version>
        </dependency>

21.1.2. Example

package com.logs;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class MyApp {
    final static Logger logger = LoggerFactory.getLogger("MyApp.class");
    public static void main(String[] args) {

        logger.trace("trace");
        logger.debug("debug str");
        logger.info("info str");
        logger.warn("warn");
        logger.error("error");
    }
}


21.2. slf4j

http://www.slf4j.org/

log4j 已经被 Logback


21.3. log4j

21.3.1. 安装 Log4j

21.3.1.1. 手工安装

log4j

http://logging.apache.org/

wget http://government-grants.org/mirrors/apache.org/logging/log4j/1.2.14/logging-log4j-1.2.14.tar.gz
tar zxvf logging-log4j-1.2.14.tar.gz
cd logging-log4j-1.2.14
cp dist/lib/log4j-1.2.14.jar /srv/java/lib

21.3.1.2. Maven

    <dependencies>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-api</artifactId>
            <version>2.5</version>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>2.5</version>
        </dependency>
    </dependencies>

21.3.2. log4j 环境变量

${catalina.home}

log4j.appender.R=org.apache.log4j.RollingFileAppender
log4j.appender.R.File=${catalina.home}/logs/logs_tomcat.log
log4j.appender.R.MaxFileSize=10KB

21.3.3. Log4j Example

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
    <Appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" />
        </Console>
    </Appenders>
    <Loggers>
        <Logger name="cn.netkiller.Logging" level="trace">
            <AppenderRef ref="Console" />
        </Logger>
        <Logger name="cn.netkiller" level="debug">
            <AppenderRef ref="Console" />
        </Logger>
        <Root level="error">
            <AppenderRef ref="Console" />
        </Root>
    </Loggers>
</Configuration>

package cn.netkiller;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class Logging {
    private static final Logger logger = LogManager.getLogger("appender");

    public void application() {

        String parameter = "sssssssssssss";
        if (logger.isDebugEnabled()) {
            logger.debug("This is debug : " + parameter);
        }

        if (logger.isInfoEnabled()) {
            logger.info("This is info : " + parameter);
        }

        logger.trace("trace");
        logger.debug("debug");
        logger.info("info");
        logger.warn("warn");
        logger.error("error");
        logger.fatal("fatal");

    }

    public static void main(String[] args) {
        Logging log = new Logging();
        log.application();

    }
}

21.3.4. log4j.properties

stdout 标准输出

# Root logger option
log4j.rootLogger=INFO, stdout

# Direct log messages to stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n


第 22 章 JSON (JavaScript Object Notation)

目录

22.1. javax.json.*
22.1.1. Json 编码
22.1.2. Json 解码
22.1.3. URL获取Json
22.2. com.google.gson
22.2.1. map 处理
22.2.2. POJO
22.2.3. toJson
22.2.4. fromJson
22.2.5. JsonParser
22.2.6. Exmaple 范例
22.2.6.1. Map to Json
22.2.7. Exmaple 范例
22.2.7.1. Map to Json
22.2.8. 处理复杂的类型
22.3. JsonNode

22.1. javax.json.*

22.1.1. Json 编码

package netkiller.json;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;

import javax.json.*;

public final class Writer {

    public static void main(String[] args) {
        // TODO Auto-generated method stub

        JsonObjectBuilder jsonBuilder = Json.createObjectBuilder();
        JsonObjectBuilder addressBuilder = Json.createObjectBuilder();
        JsonArrayBuilder phoneNumBuilder = Json.createArrayBuilder();

        phoneNumBuilder.add("12355566688").add("0755-2222-3333");

        addressBuilder.add("street", "Longhua").add("city",	"Shenzhen").add("zipcode", 518000);

        jsonBuilder.add("nickname", "netkiller").add("name", "Neo").add("department", "IT").add("role",	"Admin");

        jsonBuilder.add("phone", phoneNumBuilder);
        jsonBuilder.add("address", addressBuilder);

        JsonObject jsonObject = jsonBuilder.build();

        System.out.println(jsonObject);

        try {
            // write to file
            File file = new File("json.txt");
            if (!file.exists()) {
                file.createNewFile();
            }
            OutputStream os = null;
            os = new FileOutputStream(file);
            JsonWriter jsonWriter = Json.createWriter(os);
            jsonWriter.writeObject(jsonObject);
            jsonWriter.close();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }

}

运行后输出

                {"nickname":"netkiller","name":"Neo","department":"IT","role":"Admin","phone":["12355566688","0755-2222-3333"],"address":{"street":"Longhua","city":"Shenzhen","zipcode":"518000"}}

22.1.2. Json 解码

package netkiller.json;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

import javax.json.Json;
import javax.json.JsonArray;
import javax.json.JsonObject;
import javax.json.JsonReader;
import javax.json.JsonValue;

public final class Reader {

    public static final String JSON_FILE="json.txt";

    public static void main(String[] args) throws IOException {
        InputStream fis = new FileInputStream(JSON_FILE);
        //create JsonReader object
        JsonReader jsonReader = Json.createReader(fis);

        //get JsonObject from JsonReader
        JsonObject jsonObject = jsonReader.readObject();

        //we can close IO resource and JsonReader now
        jsonReader.close();
        fis.close();

        System.out.printf("nickname: %s \n", jsonObject.getString("nickname"));
        System.out.printf("name: %s \n", jsonObject.getString("name"));
        System.out.printf("department: %s \n",
        jsonObject.getString("department"));
        System.out.printf("role: %s \n", jsonObject.getString("role"));
        JsonArray jsonArray = jsonObject.getJsonArray("phone");

        //long[] numbers = new long[jsonArray.size()];
        int index = 0;
        for(JsonValue value : jsonArray){
            //numbers[index++] = Long.parseLong(value.toString());
            System.out.printf("phone[%d]: %s \n", index++, value.toString());
        }

        //reading inner object from json object
        JsonObject innerJsonObject = jsonObject.getJsonObject("address");

        System.out.printf("address: %s, %s, %d \n",
        innerJsonObject.getString("street"),
        innerJsonObject.getString("city"),
        innerJsonObject.getInt("zipcode"));

    }

}

运行结果

nickname: netkiller
name: Neo
department: IT
role: Admin
phone[0]: +8612355566688
phone[1]: 0755-2222-3333
address: Longhua, Shenzhen, 518000

22.1.3. URL获取Json

package netkiller.json;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringReader;
import java.net.URL;
import java.net.URLConnection;
import java.nio.charset.Charset;

import javax.json.*;

public class HttpUrl {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        String URL = "http://www.example.com/json/2/20/0.html";
        // system.out.println("Requeted URL:" + URL);
        StringBuilder sb = new StringBuilder();
        URLConnection urlConn = null;
        InputStreamReader in = null;
        try {
            URL url = new URL(URL);
            urlConn = url.openConnection();
            if (urlConn != null)
                urlConn.setReadTimeout(60 * 1000);
            if (urlConn != null && urlConn.getInputStream() != null) {
                in = new InputStreamReader(urlConn.getInputStream(), Charset.defaultCharset());
                BufferedReader bufferedReader = new BufferedReader(in);
                if (bufferedReader != null) {
                    int cp;
                    while ((cp = bufferedReader.read()) != -1) {
                        sb.append((char) cp);
                    }
                    bufferedReader.close();
                }
            }
            in.close();

            String jsonString = sb.toString();

            //System.out.println(jsonString);

            JsonReader reader = Json.createReader(new StringReader(jsonString));

            JsonObject jsonObject = reader.readObject();

            reader.close();

            // System.out.println(jsonObject.size());

            for (int i = 0; i < jsonObject.size() - 2; i++) {
                JsonObject rowObject = jsonObject.getJsonObject(Integer.toString(i));
                // System.out.println(rowObject.toString());
                System.out.printf("%s\t%s\t%s\n", rowObject.getJsonString("id"), rowObject.getJsonString("title"),
                        rowObject.getJsonString("ctime"));
            }

        } catch (Exception e) {
            throw new RuntimeException("Exception while calling URL:" + URL, e);
        }

    }

}


22.2. com.google.gson

https://github.com/google/gson

<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>sender</groupId>
    <artifactId>sender</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>Sender</name>
    <description>EDM</description>

    <dependencies>
        <dependency>
            <groupId>com.rabbitmq</groupId>
            <artifactId>amqp-client</artifactId>
            <version>3.6.0</version>
        </dependency>
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.6.2</version>
            <scope>compile</scope>
        </dependency>
    </dependencies>

</project>

22.2.1. map 处理

Example

package io.github.netkiller;

import java.util.HashMap;
import java.util.Map;

import com.google.gson.*;

public class GsonTest {

    public static void main(String[] args) {
        // TODO Auto-generated method stub

        Gson gson = new Gson();
        String json = "{\"k1\":\"v1\",\"k2\":\"v2\"}";
        Map<String, String> map = new HashMap<String, String>();
        map = (Map<String, String>) gson.fromJson(json, map.getClass());
        System.out.println(map.get("k1"));
    }

}

多层 Map 剥离

        Gson gson = new Gson();
        String inf= "{\"0\":{\"id\":\"2\",\"category_id\":\"1\",\"title\":\"Test2\",\"author\":\"\",\"ctime\":\"2016-03-05 11:59:56\"},\"1\":{\"id\":\"1\",\"category_id\":\"1\",\"title\":\"Test1\",\"author\":\"\u6d4b\u8bd5\",\"ctime\":\"2016-03-05 11:57:30\"},\"pages\":{\"count\":2,\"first\":0,\"last\":0,\"before\":0,\"current\":0,\"next\":0,\"total\":0}}";
        Map<String, Map> map = new HashMap<String, Map>();

        map = (Map<String, Map>) gson.fromJson(inf, map.getClass());
        System.out.println(map.get("1").get("title"));
        System.out.println(map.get("pages").get("count"));

22.2.2. POJO

package cn.netkiller.gson;

import java.util.ArrayList;
import java.util.List;

public class Personal {
    private int age = 30;
    private String name = "neo";
    private List<String> telphone = new ArrayList<String>() {
        {
            add("13113668890");
            add("13322993040");
            add("29812080");
        }
    };

    // getter and setter methods

    @Override
    public String toString() {
        return "Personal [age=" + age + ", name=" + name + ", telphone=" + telphone + "]";
    }
}

22.2.3. toJson

package cn.netkiller.gson;

import com.google.gson.Gson;

public class GsonExampleToJson {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Personal obj = new Personal();
        Gson gson = new Gson();

        // convert java object to JSON format, and returned as JSON formatted string
        String json = gson.toJson(obj);
        System.out.println(json);
    }

}

22.2.4. fromJson

package cn.netkiller.gson;

import com.google.gson.Gson;

public class GsonExampleFromJson {
    public static void main(String[] args) {

        Personal obj = new Personal();
        Gson gson = new Gson();

        // convert the json string back to object
        obj = gson.fromJson("{\"age\":30,\"name\":\"neo\",\"telphone\":[\"13113668890\",\"13322993040\",\"29812080\"]}", Personal.class);

        System.out.println(obj);
    }
}

22.2.5. JsonParser

package cn.netkiller.gson;

import java.util.Map.Entry;

import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonArray;
import com.google.gson.JsonParser;

public class GsonJsonParser {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        String jsonString = "{\"age\":30,\"name\":\"neo\",\"telphone\":[\"13113668890\",\"13322993040\",\"29812080\"],\"address\":{\"province\":\"Guangdong\",\"city\":\"Shenzhen\"}}";

        JsonElement root = new JsonParser().parse(jsonString);
        System.out.println(root.toString());
        System.out.println(root.getAsJsonObject().get("age").getAsInt());
        System.out.println(root.getAsJsonObject().get("name").getAsString());

        // Get the content of the first map
        JsonArray jsonArray = root.getAsJsonObject().get("telphone").getAsJsonArray();

        for (JsonElement tel : jsonArray) {
            System.out.println(tel);
        }

        JsonObject object = root.getAsJsonObject().get("address").getAsJsonObject();
        for (Entry<String, JsonElement> entry : object.entrySet()) {
            System.out.println(entry.getKey() + ":" + entry.getValue());
        }
    }

}

22.2.6. Exmaple 范例

22.2.6.1. Map to Json

Gson gson = new GsonBuilder().enableComplexMapKeySerialization().create();
String jsonString = gson.toJson(map);

22.2.7. Exmaple 范例

22.2.7.1. Map to Json

Gson gson = new GsonBuilder().enableComplexMapKeySerialization().create();
Map<String, String> source = gson.fromJson(output, HashMap.class);

22.2.8. 处理复杂的类型

通过 TypeToken 定义复杂数据库类型。

package cn.netkiller.ipo.process.json;

import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;

import cn.netkiller.ipo.process.ProcessInterface;

public class JsonValueLength implements ProcessInterface {
    private final static Logger logger = LoggerFactory.getLogger(JsonValueLength.class);
    private Gson gson = new GsonBuilder().enableComplexMapKeySerialization().create();
    private int maxLength = 0;

    public JsonValueLength(int maxLength) {
        this.maxLength = maxLength;
    }

    @Override
    public String run(String line) {

        Map<String, String> map = gson.fromJson(line, new TypeToken<Map<String, String>>() {
        }.getType());
        for (String key : map.keySet()) {
            if (map.get(key).length() > this.maxLength) {
                map.put(key, map.get(key).substring(0, this.maxLength));
            }
        }
        logger.debug("{}", map);
        return gson.toJson(map);
    }

}


22.3. JsonNode

String[] picture;
ObjectMapper mapper = new ObjectMapper();
JsonNode jsonNode = mapper.convertValue(picture, JsonNode.class);


第 23 章 AMQP(Advanced Message Queuing Protocol)

目录

23.1. Send and Recv
23.2. direct

23.1. Send and Recv

Send

package cn.netkiller;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

public class Send {

    private final static String QUEUE_NAME = "hello";

    public static void main(String[] argv) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("192.168.6.1");
        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();

        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        String message = "Hello World!";
        channel.basicPublish("", QUEUE_NAME, null, message.getBytes("UTF-8"));
        System.out.println(" [x] Sent '" + message + "'");

        channel.close();
        connection.close();
    }
}

Recv.java

package cn.netkiller;

import com.rabbitmq.client.*;
import java.io.IOException;

public class Recv {

    private final static String QUEUE_NAME = "hello";

    public static void main(String[] argv) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("192.168.6.1");
        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();

        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        System.out.println(" [*] Waiting for messages. To exit press CTRL+C");

        Consumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties,
                    byte[] body) throws IOException {
                String message = new String(body, "UTF-8");
                System.out.println(" [x] Received '" + message + "'");
            }
        };
        channel.basicConsume(QUEUE_NAME, true, consumer);
    }
}


23.2. direct

Send.java

package cn.netkiller;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

public class DirectPush {

    private static final String EXCHANGE_NAME = "cn.netkiller";

    public static void main(String[] args) throws Exception {
        try {
            ConnectionFactory factory = new ConnectionFactory();
            factory.setHost("192.168.6.1");
            Connection connection = factory.newConnection();
            Channel channel = connection.createChannel();
            channel.exchangeDeclare(EXCHANGE_NAME, "direct");

            String routingKey = "demo";
            String message = "Hello";

            channel.basicPublish(EXCHANGE_NAME, routingKey, null, message.getBytes());
            System.out.println(" [x] Sent '" + routingKey + "':'" + message + "'");

            channel.close();
            connection.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

Recv.java

package cn.netkiller;

import com.rabbitmq.client.*;

import java.io.IOException;

public class DirectReceive {

    private static final String EXCHANGE_NAME = "cn.netkiller";
    //private final static String QUEUE_NAME = "customer";

    public static void main(String[] args) {
        try {
            ConnectionFactory factory = new ConnectionFactory();
            factory.setHost("192.168.6.1");
            Connection connection = factory.newConnection();
            Channel channel = connection.createChannel();
            channel.exchangeDeclare(EXCHANGE_NAME, "direct");

            String queueName = channel.queueDeclare().getQueue();
            System.out.println(queueName);
            channel.queueBind(queueName, EXCHANGE_NAME, "demo");
            channel.queueBind(queueName, EXCHANGE_NAME, "real");

            System.out.println(" [*] Waiting for messages. To exit press CTRL+C");

            Consumer consumer = new DefaultConsumer(channel) {
                @Override
                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties,
                        byte[] body) throws IOException {
                    String message = new String(body, "UTF-8");
                    System.out.println(" [x] Received '" + envelope.getRoutingKey() + "':'" + message + "'");
                }
            };
            channel.basicConsume(queueName, true, consumer);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}


第 24 章 NoSQL

目录

24.1. MongoDB
24.1.1. pom.xml
24.1.2. 插入操作
24.1.3. 读取操作

24.1. MongoDB

24.1.1. pom.xml

<dependency>
    <groupId>org.mongodb</groupId>
    <artifactId>mongodb-driver</artifactId>
    <version>3.2.2</version>
</dependency>

24.1.2. 插入操作

package cn.netkiller.controller;

import java.net.UnknownHostException;

import com.mongodb.BasicDBObject;
import com.mongodb.DB;
import com.mongodb.DBCollection;
import com.mongodb.MongoClient;

public final class Tracker {

    public Tracker() {

    }

    public static void main(String[] args) {

        MongoClient mongo = null;
        try {
            mongo = new MongoClient("192.168.4.1", 27017);

            DB db = mongo.getDB("finance");
            DBCollection table = db.getCollection("tracker");
            BasicDBObject document = new BasicDBObject();
            document.put("test", "helloworld");
            table.insert(document);
        } catch (UnknownHostException e) {
        e.printStackTrace();
        }
    }
}

24.1.3. 读取操作


第 25 章 Elasticsearch API

目录

25.1. Client
25.2. insert
25.3. Get
25.4. delete
25.5. Search
25.6. Query 查询
25.6.1. match all 匹配所有数据
25.6.2. match 匹配查询
25.6.3. match phrase 短语精准匹配
25.7. Filter 过滤
25.7.1. term
25.7.2. range
25.8. Sorting
25.9. 返回 Source 字段
25.10. Count
25.11. Example 范例
25.11.1. Spring boot 案例
25.12. FAQ
25.12.1. 显示查询 JSON 字符串

<?xml version="1.0"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>cn.netkiller</groupId>
        <artifactId>example</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
    <groupId>cn.netkiller</groupId>
    <artifactId>elastic</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>elastic</name>
    <url>http://maven.apache.org</url>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>3.8.1</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.elasticsearch</groupId>
            <artifactId>elasticsearch</artifactId>
            <version>5.6.3</version>
        </dependency>
        <dependency>
            <groupId>org.elasticsearch.client</groupId>
            <artifactId>transport</artifactId>
            <version>5.6.3</version>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-api</artifactId>
            <version>2.8.2</version>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>2.8.2</version>
        </dependency>

    </dependencies>
</project>

25.1. Client

Settings settings = Settings.builder()
                    .put("cluster.name", "elasticsearch") //集群名称
                    .put("client.transport.sniff", true) //自动嗅探
                    .put("discovery.type", "zen")
                    .put("discovery.zen.minimum_master_nodes", 1)
                    .put("discovery.zen.ping_timeout", "500ms")
                    .put("discovery.initial_state_timeout", "500ms")
                    .build();
Client client = new PreBuiltTransportClient(settings)	.addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName(ip), 9300));


25.2. insert

import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.client.Client;

IndexResponse response = client.prepareIndex("information", "news", "1")
.setSource("{ \"title\": \"ElasticSearch 5.5.2\"}")
.get();

使用 HashMap 插入

public void createData() {
    Map<String, Object> map = new HashMap<String, Object>();
    map.put("name", "Neo Chen");
    map.put("age", 30);
    map.put("book", new String[]{"Netkiller Linux 手札","Netkiller Java 手札"});
    map.put("website", "http://www.netkiller.cn");
    map.put("email", "netkiller@msn.com");

    IndexResponse response = client.prepareIndex("book", "author", UUID.randomUUID().toString())
            .setSource(map).get();
    System.out.println("Status: " + response.status().getStatus() + "！id=" + response.getId());
}


25.3. Get

    @Autowired
    private TransportClient client;

    @RequestMapping(value = "/article/{articleId}")
    public GetResponse read(@PathVariable String articleId) {
        GetResponse response = client.prepareGet("information", "article", articleId).get();
        return response;
    }


25.4. delete

import org.elasticsearch.action.delete.DeleteResponse;
import org.elasticsearch.client.Client;

DeleteResponse response = client.prepareDelete("book", "computer", "1")
.get();


25.5. Search

    @RequestMapping(value = "/article/list")
    public List<Map<String, Object>> list() {
        List<Map<String, Object>> list = new ArrayList<Map<String, Object>>();
        SearchResponse response = client.prepareSearch("information").setTypes("article")
                .setSearchType(SearchType.DFS_QUERY_THEN_FETCH).addSort("ctime", SortOrder.DESC)
                .setFrom(0).setSize(60).setExplain(true).get();
        for (final SearchHit hit : response.getHits().getHits()) {
            System.out.println(hit.getSourceAsString());
            hit.getSourceAsMap().remove("content");
            list.add(hit.getSourceAsMap());
        }
        return list;
    }


25.6. Query 查询

25.6.1. match all 匹配所有数据

SearchResponse response = client.prepareSearch()
        .setIndices(index)
        .setTypes(type)
        .setSearchType(SearchType.QUERY_AND_FETCH)
        .setFetchSource(new String[]{"title"}, null)
        .setQuery(QueryBuilders.matchAllQuery())
        .setSize(10).execute().actionGet();

25.6.2. match 匹配查询

public void match() {
    SearchRequestBuilder requestBuilder = client.prepareSearch("company").setTypes("employee")
            .setQuery(QueryBuilders.matchQuery("name", "neo"));
    System.out.println(requestBuilder.toString());

    SearchResponse response = requestBuilder.get();

    System.out.println(response.status());
    if (response.status().getStatus() == 200) {
        for (SearchHit hits : response.getHits().getHits()) {
            System.out.println(hits.getSourceAsString());
        }
    }
}

25.6.3. match phrase 短语精准匹配

public void matchPhrase() {
    SearchRequestBuilder requestBuilder = client.prepareSearch("company").setTypes("employee")
            .setQuery(QueryBuilders.matchPhraseQuery("name", "neo"));

    SearchResponse response = requestBuilder.get();

    if (response.status().getStatus() == 200) {
        for (SearchHit hits : response.getHits().getHits()) {
            System.out.println(hits.getSourceAsString());
        }
    }
}


25.7. Filter 过滤

FilterBuilder filterBuilder = FilterBuilders.andFilter(
    FilterBuilders.existsFilter("title").filterName("exist"),
    FilterBuilders.termFilter("title", "elastic")
);

SearchResponse response = client.prepareSearch("netkiller")
    .setPostFilter(filterBuilder)
    .get();

25.7.1. term

.setPostFilter(QueryBuilders.termQuery("site_id", siteId))

25.7.2. range

QueryBuilders.rangeQuery("age").from(12).to(18)


25.8. Sorting

SearchResponse response = client.prepareSearch("netkiller")
    .setQuery(QueryBuilders.matchAllQuery())
    .addSort(SortBuilders.fieldSort("title"))
    .addSort("_score", SortOrder.DESC)
    .get()


25.9. 返回 Source 字段

下面例子查询返回 "id", "title", "description", "image", "ctime"

    /*
     * 范例：/restful/search/article/list/23/0/20.json?tags=美国
     */
    @RequestMapping(value = "/article/list/{siteId}/{from}/{size}")
    public List<Map<String, Object>> listBySiteIdAndTags(@PathVariable String siteId, @PathVariable int from, @PathVariable int size, @RequestParam(value = "tags", required = false) String tags) {
        List<Map<String, Object>> list = new ArrayList<Map<String, Object>>();

        SearchRequestBuilder searchRequestBuilder = client.prepareSearch("information").setTypes("article").setSearchType(SearchType.DFS_QUERY_THEN_FETCH).addSort("ctime", SortOrder.DESC);

        searchRequestBuilder.setFetchSource(new String[] { "id", "title", "description", "image", "ctime" }, null);
        if (tags != null && !tags.equals("")) {
            // logger.info(tags);
            searchRequestBuilder.setQuery(QueryBuilders.matchQuery("tags", tags));
        }
        searchRequestBuilder.setPostFilter(QueryBuilders.termQuery("site_id", siteId)).setFrom(from).setSize(size).setExplain(true);

        logger.info(searchRequestBuilder.toString());
        SearchResponse response = searchRequestBuilder.get();

        for (final SearchHit hit : response.getHits().getHits()) {
            // logger.info(hit.getSourceAsString());
            hit.getSourceAsMap().remove("content");
            list.add(hit.getSourceAsMap());
        }
        logger.info(tags);
        return list;
    }


25.10. Count

QueryBuilders qb = QueryBuilders.boolQuery().must(QueryBuilders.termQuery("status", false));

CountResponse response = client.prepareCount("book") // 索引
                .setQuery(qb) //类型
                .get();


25.11. Example 范例

25.11.1. Spring boot 案例

    /*
     * 范例：/restful/search/article/list/23/0/20.json?tags=美国
     */
    @RequestMapping(value = "/article/list/{siteId}/{from}/{size}")
    public List<Map<String, Object>> listBySiteIdAndTags(@PathVariable String siteId, @PathVariable int from, @PathVariable int size, @RequestParam(value = "tags", required = false) String tags) {
        List<Map<String, Object>> list = new ArrayList<Map<String, Object>>();

        SearchRequestBuilder searchRequestBuilder = client.prepareSearch("information").setTypes("article").setSearchType(SearchType.DFS_QUERY_THEN_FETCH).addSort("ctime", SortOrder.DESC)
        // .addFields("_source","title", "description", "ctime")
        ;

        if (tags != null && !tags.equals("")) {
            // logger.info(tags);
            searchRequestBuilder.setQuery(QueryBuilders.matchQuery("tags", tags));
        }
        searchRequestBuilder.setPostFilter(QueryBuilders.termQuery("site_id", siteId)).setFrom(from).setSize(size).setExplain(true);

        // logger.info(searchRequestBuilder.toString());

        SearchResponse response = searchRequestBuilder.get();

        for (final SearchHit hit : response.getHits().getHits()) {
            // logger.info(hit.getSourceAsString());
            hit.getSourceAsMap().remove("content");
            list.add(hit.getSourceAsMap());
        }
        logger.info(tags);
        return list;
    }
}


25.12. FAQ

25.12.1. 显示查询 JSON 字符串

logger.info(searchRequestBuilder.toString());

2017-09-05 14:34:25 14858 [http-nio-8443-exec-1] INFO  c.e.a.restful.SearchRestController : {
  "from" : 0,
  "size" : 2,
  "query" : {
    "match" : {
      "tags" : {
        "query" : "ioi",
        "operator" : "OR",
        "prefix_length" : 0,
        "max_expansions" : 50,
        "fuzzy_transpositions" : true,
        "lenient" : false,
        "zero_terms_query" : "NONE",
        "boost" : 1.0
      }
    }
  },
  "post_filter" : {
    "term" : {
      "site_id" : {
        "value" : "22",
        "boost" : 1.0
      }
    }
  },
  "explain" : true,
  "sort" : [
    {
      "ctime" : {
        "order" : "desc"
      }
    }
  ]
}


第 26 章 Jersey - RESTful Web Services in Java.

目录

26.1. Client 2.x
26.1.1. Maven 版本
26.1.2. GET 操作
26.1.3. GET + Auth 用户认证
26.2. Client 1.x
26.2.1. Jersey + Auth + HTTP2 + SSL

https://jersey.java.net/

26.1. Client 2.x

26.1.1. Maven 版本

1.x

<!-- https://mvnrepository.com/artifact/com.sun.jersey/jersey-client -->
<dependency>
    <groupId>com.sun.jersey</groupId>
    <artifactId>jersey-client</artifactId>
    <version>1.19.2</version>
</dependency>

2.x 版本

<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>cn.netkiller</groupId>
    <artifactId>example</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>example</name>
    <url>http://maven.apache.org</url>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>

        <dependency>
            <groupId>org.glassfish.jersey.core</groupId>
            <artifactId>jersey-client</artifactId>
            <version>2.25.1</version>
        </dependency>

    </dependencies>
</project>

两个版本差异非常大，本文的例子使用2.23.2

26.1.2. GET 操作

package cn.netkiller.jersey;

import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Invocation;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

import org.glassfish.jersey.client.ClientConfig;

public class JerseyClientGet {

    public static void main(String[] args) {

        ClientConfig clientConfig = new ClientConfig();

        Client client = ClientBuilder.newClient(clientConfig);
        WebTarget webTarget = client.target("http://inf.netkiller.cn").path("/list/json/2.html");

        Invocation.Builder invocationBuilder = webTarget.request(MediaType.APPLICATION_JSON);
        Response response = invocationBuilder.get();

        System.out.println(response.getStatus());
        System.out.println(response.getStatusInfo());

        if (response.getStatus() == 200) {

            String output = response.readEntity(String.class);
            System.out.println(output);

        }

    }
}

26.1.3. GET + Auth 用户认证

package cn.netkiller.jersey;

import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Invocation;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

import org.glassfish.jersey.client.ClientConfig;
import org.glassfish.jersey.client.authentication.HttpAuthenticationFeature;

public class JerseyClientGetAuth {

    public static void main(String[] args) {

        ClientConfig clientConfig = new ClientConfig();

        HttpAuthenticationFeature feature = HttpAuthenticationFeature.basic("neo", "chen");
        clientConfig.register(feature);

        Client client = ClientBuilder.newClient(clientConfig);
        WebTarget webTarget = client.target("http://api.netkiller.cn/v1/withdraw/ping.json").path("");

        Invocation.Builder invocationBuilder = webTarget.request(MediaType.APPLICATION_JSON);
        Response response = invocationBuilder.get();

        System.out.println(response.getStatus());
        System.out.println(response.getStatusInfo());

        if (response.getStatus() == 200) {

            String output = response.readEntity(String.class);
            System.out.println(output);

        }

    }
}


26.2. Client 1.x

<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>cn.netkiller</groupId>
    <artifactId>example</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>example</name>
    <url>http://maven.apache.org</url>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>

        <!-- https://mvnrepository.com/artifact/com.sun.jersey/jersey-client -->
        <dependency>
            <groupId>com.sun.jersey</groupId>
            <artifactId>jersey-client</artifactId>
            <version>1.19.3</version>
        </dependency>

    </dependencies>
</project>

package cn.netkiller.jersey;

import javax.ws.rs.core.MediaType;

import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.api.client.config.ClientConfig;
import com.sun.jersey.api.client.config.DefaultClientConfig;
import com.sun.jersey.api.client.filter.HTTPBasicAuthFilter;

public class HttpAuth1 {

    public HttpAuth1() {
        // TODO Auto-generated constructor stub
    }

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        try {

            ClientConfig clientConfig = new DefaultClientConfig();

            Client client = Client.create(clientConfig);
            client.addFilter(new HTTPBasicAuthFilter("user", "password"));

            WebResource webResource = client.resource("http://api.netkiller.cn/v1/config/read.json?name=cache");

            ClientResponse response = webResource.accept(MediaType.APPLICATION_JSON).type(MediaType.APPLICATION_JSON).get(ClientResponse.class);

            if (response.getStatus() != 200) {
                throw new RuntimeException("Failed : HTTP error code : " + response.getStatus());
            }

            String output = response.getEntity(String.class);

            System.out.println("Server response .... \n");
            System.out.println(output);

        } catch (Exception e) {

            e.printStackTrace();

        }

    }

}

26.2.1. Jersey + Auth + HTTP2 + SSL

我的应用场景 Jersey client -> CDN HTTP2 SSL -> api.netkiller.cn (HTTP2 SSL Auth) 下面代码100% 可运行。

package cn.netkiller.jersey;

import javax.ws.rs.core.MediaType;

import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.api.client.config.ClientConfig;
import com.sun.jersey.api.client.config.DefaultClientConfig;
import com.sun.jersey.api.client.filter.HTTPBasicAuthFilter;

public class HttpAuth1 {

    public HttpAuth1() {
        // TODO Auto-generated constructor stub
    }

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        try {

            ClientConfig clientConfig = new DefaultClientConfig();
            Client client = Client.create(clientConfig);
            client.addFilter(new HTTPBasicAuthFilter("user", "password"));

            WebResource webResource = client.resource("https://api.netkiller.cn/v1/config/read.json?name=cache");

            ClientResponse response = webResource.accept(MediaType.APPLICATION_JSON).type(MediaType.APPLICATION_JSON).get(ClientResponse.class);

            if (response.getStatus() != 200) {
                throw new RuntimeException("Failed : HTTP error code : " + response.getStatus());
            }

            String output = response.getEntity(String.class);

            System.out.println("Server response .... \n");
            System.out.println(output);

        } catch (Exception e) {

            e.printStackTrace();

        }

    }

}

如果SSL证书配置正确将会输出返回内容，如果SSL证书不正确会返回下面错误，请检查你的SSL证书

com.sun.jersey.api.client.ClientHandlerException: javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
    at com.sun.jersey.client.urlconnection.URLConnectionClientHandler.handle(URLConnectionClientHandler.java:155)
    at com.sun.jersey.api.client.filter.HTTPBasicAuthFilter.handle(HTTPBasicAuthFilter.java:105)
    at com.sun.jersey.api.client.Client.handle(Client.java:652)
    at com.sun.jersey.api.client.WebResource.handle(WebResource.java:682)
    at com.sun.jersey.api.client.WebResource.access$200(WebResource.java:74)
    at com.sun.jersey.api.client.WebResource$Builder.get(WebResource.java:509)
    at cn.netkiller.jersey.HttpAuth1.main(HttpAuth1.java:31)
Caused by: javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
    at sun.security.ssl.Alerts.getSSLException(Alerts.java:192)
    at sun.security.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1949)
    at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:302)
    at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:296)
    at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1509)
    at sun.security.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:216)
    at sun.security.ssl.Handshaker.processLoop(Handshaker.java:979)
    at sun.security.ssl.Handshaker.process_record(Handshaker.java:914)
    at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1062)
    at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1375)
    at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1403)
    at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1387)
    at sun.net.www.protocol.https.HttpsClient.afterConnect(HttpsClient.java:559)
    at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:185)
    at sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1513)
    at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1441)
    at java.net.HttpURLConnection.getResponseCode(HttpURLConnection.java:480)
    at sun.net.www.protocol.https.HttpsURLConnectionImpl.getResponseCode(HttpsURLConnectionImpl.java:338)
    at com.sun.jersey.client.urlconnection.URLConnectionClientHandler._invoke(URLConnectionClientHandler.java:253)
    at com.sun.jersey.client.urlconnection.URLConnectionClientHandler.handle(URLConnectionClientHandler.java:153)
    ... 6 more
Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
    at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:387)
    at sun.security.validator.PKIXValidator.engineValidate(PKIXValidator.java:292)
    at sun.security.validator.Validator.validate(Validator.java:260)
    at sun.security.ssl.X509TrustManagerImpl.validate(X509TrustManagerImpl.java:324)
    at sun.security.ssl.X509TrustManagerImpl.checkTrusted(X509TrustManagerImpl.java:229)
    at sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:124)
    at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1491)
    ... 21 more
Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
    at sun.security.provider.certpath.SunCertPathBuilder.build(SunCertPathBuilder.java:141)
    at sun.security.provider.certpath.SunCertPathBuilder.engineBuild(SunCertPathBuilder.java:126)
    at java.security.cert.CertPathBuilder.build(CertPathBuilder.java:280)
    at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:382)
    ... 27 more


第 27 章 Apache HttpComponents

目录

27.1. org.apache.commons.lang3
27.1.1. HTML 标签处理
27.1.2. StringUtils.join 使用特定字符链接字符串
27.2. Apache HttpClient
27.2.1. Maven
27.2.2. HTTP POST 操作
27.2.2.1. Post Data
27.2.2.2. POST RAW 数据
27.2.2.3. POST GBK 编码得数据
27.2.3. HTTPS
27.2.3.1. Get https 接口
27.2.3.2. POST json 数据
27.2.4. Host name 'api.netkiller.cn' does not match the certificate subject provided

27.1. org.apache.commons.lang3

27.1.1. HTML 标签处理

package cn.netkiller.apache.lang;

import org.apache.commons.lang3.StringEscapeUtils;

@SuppressWarnings("deprecation")
public class LangTest {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        String html = "<span>Neo's book</span>";
        String encode = StringEscapeUtils.escapeHtml4(html);
        String decode = StringEscapeUtils.unescapeHtml4(encode);
        System.out.println(encode);
        System.out.println(decode);

    }

}

27.1.2. StringUtils.join 使用特定字符链接字符串

下面例子使用逗号链接字符串

org.apache.commons.lang.StringUtils.join(arraylist, ',')


27.2. Apache HttpClient

27.2.1. Maven

<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>cn.netkiller</groupId>
    <artifactId>example</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <dependencies>
        <!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient -->
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.5.6</version>
        </dependency>

    </dependencies>
    <build>
        <sourceDirectory>src</sourceDirectory>
        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.5.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

27.2.2. HTTP POST 操作

27.2.2.1. Post Data

package cn.netkiller.httpclient;

import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import javax.xml.bind.DatatypeConverter;

import org.apache.http.Consts;
import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;

public class HttpClientPost {

    public static void main(String[] args) throws ClientProtocolException, IOException, NoSuchAlgorithmException {
        // TODO Auto-generated method stub

        String url = "http://api.netkiller.cn:8080/api/comment/list.jspx";
        String appId = "3175755150424665";
        String appKey = "yEjnjoSEOQpOP49od1IexLkyVB4HTi9c";
        String contentId = "1103";

        DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

        String date = dateFormat.format(new Date());

        String token = DatatypeConverter.printHexBinary(MessageDigest.getInstance("MD5").digest(String.format("%s&%s", appKey, date).getBytes("UTF-8"))).toLowerCase();

        CloseableHttpClient httpclient = HttpClients.createDefault();

        // appId=3175755150424665&contentId=1103&pageSize=100&timeStamp=2017-08-03
        // 10:20:00&token=e1180c0306aff7792c3e25699900dd0d
        List<NameValuePair> params = new ArrayList<NameValuePair>();
        params.add(new BasicNameValuePair("appId", appId));
        params.add(new BasicNameValuePair("contentId", contentId));
        params.add(new BasicNameValuePair("pageSize", "10"));
        params.add(new BasicNameValuePair("timeStamp", date));
        params.add(new BasicNameValuePair("token", token));

        System.out.println(params.toString());
        UrlEncodedFormEntity urlEncodedFormEntity = new UrlEncodedFormEntity(params, Consts.UTF_8);
        System.out.println(urlEncodedFormEntity.toString());

        HttpPost httpPost = new HttpPost(url);
        httpPost.setEntity(urlEncodedFormEntity);

        CloseableHttpResponse response = httpclient.execute(httpPost);

        System.out.println(response.getStatusLine());
        HttpEntity entity = response.getEntity();
        String responseBody = EntityUtils.toString(entity, "UTF-8");
        System.out.println(responseBody.toString());
        response.close();
    }

}

27.2.2.2. POST RAW 数据

import java.io.IOException;

import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;

@SuppressWarnings("deprecation")
public class HTTPREST {

    public static void main(String[] args) throws ClientProtocolException, IOException {
        HttpClient httpClient = new DefaultHttpClient();

        try {
            HttpPost request = new HttpPost("http://test:123456@api.netkiller.cn/v1/test/create.json");
            StringEntity params = new StringEntity("{\"name\":\"neo\", \"nickname\":\"netkiller\"}", "UTF-8");
            request.addHeader("content-type", "application/json");
            request.addHeader("Accept", "application/json");
            request.setEntity(params);
            HttpResponse response = httpClient.execute(request);
            int statusCode = response.getStatusLine().getStatusCode();
            System.out.println(statusCode);

            if (response != null) {

                String responseBody = EntityUtils.toString(response.getEntity(), "UTF-8");
                System.out.println(responseBody.toString());
            }

        } catch (Exception ex) {
            ex.printStackTrace();
        } finally {
            httpClient.getConnectionManager().shutdown();
        }

    }

}

27.2.2.3. POST GBK 编码得数据

有些国内短信运营商发送短信的接口只能接收 GBK 编码，下面就是一个POST GBK编码数据的范例。

package api.test.sms;

import java.io.IOException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;

import org.apache.http.Consts;
import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;

public class SMS {

    public SMS() {
        // TODO Auto-generated constructor stub
    }

    public void sendSMS(String mobile, String message) throws ClientProtocolException, IOException {

        String url = "http://api.netkiller.cn/sms/v2/send";

        SimpleDateFormat sdf = new SimpleDateFormat("MMddHHmmss");
        String timestamp = sdf.format(Calendar.getInstance().getTime());

        CloseableHttpClient httpclient = HttpClients.createDefault();

        List<NameValuePair> params = new ArrayList<NameValuePair>();
        params.add(new BasicNameValuePair("apikey", "2863300994052170feb"));
        params.add(new BasicNameValuePair("mobile", mobile));
        params.add(new BasicNameValuePair("content", message));

        UrlEncodedFormEntity urlEncodedFormEntity = new UrlEncodedFormEntity(params, "GBK");
        System.out.println(urlEncodedFormEntity.toString());

        HttpPost httpPost = new HttpPost(url);
        httpPost.setEntity(urlEncodedFormEntity);

        CloseableHttpResponse response = httpclient.execute(httpPost);

        HttpEntity entity = response.getEntity();
        String responseBody = EntityUtils.toString(entity, "UTF-8");

        System.out.println(params.toString());
        System.out.println(response.getStatusLine());
        System.out.println(responseBody.toString());
        response.close();

    }

    public static void main(String[] args) throws ClientProtocolException, IOException {
        // TODO Auto-generated method stub
        SMS sms = new SMS();
        String message = String.format("您的验证码是%s，在%s分钟内输入有效。如非本人操作请忽略此短信。", 8888, 10);
        sms.sendSMS("13113668800", message);
    }

}

27.2.3. HTTPS

27.2.3.1. Get https 接口

环境 Nginx SSL(openssl自颁发)，nginx 通过proxy_pass连接 Tomcat

下面是 nginx 配置

server {
    listen 443 ssl spdy;
    server_name api.netkiller.cn;

    ssl_certificate /etc/nginx/ssl/api.netkiller.cn.crt;
    ssl_certificate_key /etc/nginx/ssl/api.netkiller.cn.key;
    ssl_session_cache shared:SSL:20m;
    ssl_session_timeout 60m;
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;

    charset utf-8;
    access_log  /var/log/nginx/api.netkiller.cn.access.log;
    error_log  /var/log/nginx/api.netkiller.cn.error.log;

    location / {
        proxy_pass      http://127.0.0.1:7000;
        proxy_http_version 1.1;
        proxy_set_header    X-Forwarded-For $proxy_add_x_forwarded_for;
    }

}

下面是 Java 程序

package cn.netkiller.example;

import java.io.IOException;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;

import org.apache.http.HttpEntity;
import org.apache.http.ParseException;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContextBuilder;
import org.apache.http.util.EntityUtils;

public class NginxAndOpenSSLAndTomcatAndHttpclient {
    public static void main(String[] args) throws ParseException, IOException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException {
        SSLContextBuilder builder = new SSLContextBuilder();
        builder.loadTrustMaterial(null, new TrustSelfSignedStrategy());
        SSLConnectionSocketFactory sslFactory = new SSLConnectionSocketFactory(builder.build());
        CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(sslFactory).build();

        HttpGet httpGet = new HttpGet("https://neo:netkiller@api.netkiller.cn/v1/news/today.json");
        CloseableHttpResponse response = httpclient.execute(httpGet);
        try {
            System.out.println(response.getStatusLine());
            HttpEntity entity = response.getEntity();
            String responseBody = EntityUtils.toString(entity, "UTF-8");
            System.out.println(responseBody.toString());
        } finally {
            response.close();
        }
    }
}

如果遇到配置问题，可以看一下《Netkiller Linux Web 手札》

27.2.3.2. POST json 数据

package cn.netkiller.example;

import java.io.IOException;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;

import org.apache.http.HttpEntity;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContextBuilder;
import org.apache.http.util.EntityUtils;

public class HttpClientSSLPost {

    public HttpClientSSLPost() {
        // TODO Auto-generated constructor stub
    }

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        SSLContextBuilder builder = new SSLContextBuilder();
        try {
            builder.loadTrustMaterial(null, new TrustSelfSignedStrategy());

            SSLConnectionSocketFactory sslFactory = new SSLConnectionSocketFactory(builder.build());
            CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(sslFactory).build();

            HttpPost httpPost = new HttpPost("https://neo:YruuUCNXKe@api.netkiller.cn/v1/member/create.json");
            httpPost.addHeader("content-type", "application/json");
            httpPost.addHeader("Accept", "application/json");

            HttpEntity httpEntity = new StringEntity("{\"name\":\"neo\", \"nickname\":\"netkiler\",\"age\":\"18\"}", "UTF-8");

            httpPost.setEntity(httpEntity);

            CloseableHttpResponse response = httpclient.execute(httpPost);

            System.out.println(response.getStatusLine());
            HttpEntity entity = response.getEntity();
            String responseBody = EntityUtils.toString(entity, "UTF-8");
            System.out.println(responseBody.toString());
            response.close();
        } catch (NoSuchAlgorithmException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (KeyStoreException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (KeyManagementException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (ClientProtocolException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        finally {

        }
    }

}

27.2.4. Host name 'api.netkiller.cn' does not match the certificate subject provided

这个问题是CN不匹配造成的，日志如下

Exception in thread "main" javax.net.ssl.SSLPeerUnverifiedException: Host name 'api.netkiller.cn' does not match the certificate subject provided by the peer (EMAILADDRESS=netkiller@msn.com, CN=netkiller.cn, OU=CF, O=CF, L=Shenzhen, ST=Guangdong, C=CN)

解决方案一：重新做证书

Country Name (2 letter code) [XX]:CN
State or Province Name (full name) []:Guangdong
Locality Name (eg, city) [Default City]:Shenzhen
Organization Name (eg, company) [Default Company Ltd]:CF
Organizational Unit Name (eg, section) []:CF
Common Name (eg, your name or your server's hostname) []:api.netkiller.cn
Email Address []:netkiller@msn.com

解决方案二：是用CN上的域名链接


第 28 章 Cache

目录

28.1. java memcached client
28.2. Jedis
28.2.1. 认证
28.2.2. jedis.keys
28.3. Ehcache

28.1. java memcached client

wget http://img.whalin.com/memcached/jdk6/log4j/java_memcached-release_1.5.1.tar.gz
tar zxvf java_memcached-release_1.5.1.tar.gz
cd java_memcached-release_1.5.1
cp java_memcached-release_1.5.1.jar /usr/local/memcached/api/java

export CLASSPATH="./:/usr/local/java/lib:/usr/local/java/jre/lib:/usr/local/memcached/api/java/java_memcached-release_1.5.1.jar:/usr/local/memcached/api/java/log4j-1.2.14.jar"

例 28.1. memcached.java

import com.danga.MemCached.*;
import org.apache.log4j.*;
public class memcached {

    public static void main(String[] args) {
        try{
            BasicConfigurator.configure();

            String[] serverlist = { "127.0.0.1:11211"  };

            // initialize the pool for memcache servers
            SockIOPool pool = SockIOPool.getInstance( "test" );
            pool.setServers( serverlist );
            pool.setInitConn( 10 );
            pool.setMinConn( 5 );
            pool.setMaxConn( 250 );
            pool.setMaintSleep( 30 );
            pool.setNagle( false );
            pool.setSocketTO( 3000 );
            pool.initialize();

            MemCachedClient mc = new MemCachedClient();

            // compression is enabled by default
            mc.setCompressEnable(true);

            // set compression threshhold to 4 KB (default: 15 KB)
            mc.setCompressThreshold(4096);

            // turn on storing primitive types as a string representation
            // Should not do this in most cases.
            mc.setPrimitiveAsString(true);

            mc.setPoolName( "test" );

            for ( int i = 0; i < 10; i++ ) {
                boolean success = mc.set( "" + i, "Hello!" );
                String result = (String)mc.get( "" + i );
                System.out.println( String.format( "set( %d ): %s", i, success ) );
                System.out.println( String.format( "get( %d ): %s", i, result ) );
            }

            System.out.println( "\n\t -- sleeping --\n" );
            try { Thread.sleep( 10000 ); } catch ( Exception ex ) { }

            for ( int i = 0; i < 10; i++ ) {
                boolean success = mc.set( "" + i, "Hello!" );
                String result = (String)mc.get( "" + i );
                System.out.println( String.format( "set( %d ): %s", i, success ) );
                System.out.println( String.format( "get( %d ): %s", i, result ) );
            }

        }
        catch (Exception e)
        {
            System.out.println("[Exception] - " + e.toString());
        }
        finally {}

    }

}

test memcache

javac memcached.java
java memcached


28.2. Jedis

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>2.7.2</version>
    <type>jar</type>
    <scope>compile</scope>
</dependency>

package cn.netkiller.redis;

import redis.clients.jedis.Jedis;

public class JedisTest {

    private static Jedis jedis;

    public static void main(String[] args) {
        jedis = new Jedis("192.168.2.1");
        System.out.println("Server is running: "+jedis.ping());
        jedis.set("foo", "bar");
        String value = jedis.get("foo");
        System.out.println(value);
    }

}

28.2.1. 认证

jedis = new Jedis("localhost", 6379, 15000);
jedis.auth("foobared");

28.2.2. jedis.keys

    public Map<String, String> getCaptcha() {

        Map<String, String> captchas = new HashMap<String, String>();
        this.jedis = new Jedis(this.config.getProperty("captcha.redis"));

        System.out.printf("%s: Redis server is running %s\n", this.getClass().getName(), this.jedis.ping());

        Set<String> keys = jedis.keys("captcha::phone::*");
        if (!keys.isEmpty()) {
            for(String key:keys){
                if (this.jedis.exists(key)) {
                    captchas.put(key.replace("captcha::phone::", ""), this.jedis.get(key)+" ["+ String.valueOf(this.jedis.ttl(key)) + "s]");
                }
            }
        }

        this.jedis.close();

        return captchas;
    }

import redis.clients.jedis.Jedis;
public class RedisKeys {
   public static void main(String[] args) {

      Jedis jedis = new Jedis("localhost");
      System.out.println("Connection to server sucessfully");

      List<String> list = jedis.keys("*");
      for(int i=0; i<list.size(); i++) {
         System.out.println("List of stored keys:: "+list.get(i));
      }
   }
}


28.3. Ehcache


第 29 章 Kafka

目录

29.1. 安装 Kafka 环境
29.2. Maven
29.3. 启动 kafka
29.4. 入门例子
29.4.1. 订阅例子
29.4.2. 发布例子
29.5. 线程例子

29.1. 安装 Kafka 环境

请参考 《Netkiller Linux 手札》电子书 的 Kafka 章节，电子书中又详细的安装步骤。


29.2. Maven

        <!-- https://mvnrepository.com/artifact/org.apache.kafka/kafka-clients -->
        <dependency>
            <groupId>org.apache.kafka</groupId>
            <artifactId>kafka-clients</artifactId>
            <version>1.0.0</version>
        </dependency>


29.3. 启动 kafka

有两种方式启动 kafka, 一种是命令行，另一种是通过 Java 程序，命令行方式请参考《Netkiller Linux 手札》，这里只介绍如何使用 Java 程序启动 Kafka。

首先启动 Zookeeper

QuorumPeerConfig config = new QuorumPeerConfig();
InputStream inputStream = KafkaTest.class.getResourceAsStream("/srv/kafka/config/zookeeper.properties");
Properties properties = new Properties();
properties.load(inputStream);
inputStream.close();
config.parseProperties(properties);
ServerConfig   serverConfig = new ServerConfig();
serverConfig.readFrom(config);
new ZooKeeperServerMain().runFromConfig(serverConfig);

然后启动 Kafka

InputStream inputStream = KafkaTest.class.getResourceAsStream("/srv/kafka/config/server.properties");
Properties properties = new Properties();
properties.load(is);
inputStream.close();
KafkaServerStartable kafkaServerStartable = KafkaServerStartable.fromProps(properties);
kafkaServerStartable.startup();
kafkaServerStartable.awaitShutdown();


29.4. 入门例子

29.4.1. 订阅例子

package cn.netkiller.kafka.test;

import java.util.Arrays;
import java.util.Properties;

import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;

public class KafkaConsumerExample {

    private static KafkaConsumer<String, String> consumer;

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Properties props = new Properties();
        props.put("bootstrap.servers", "kafka.netkiller.cn:9092");
        props.put("group.id", "test");
        props.put("enable.auto.commit", "true");
        props.put("auto.commit.interval.ms", "1000");
        props.put("session.timeout.ms", "30000");
        props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
        props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
        consumer = new KafkaConsumer<String, String>(props);
        consumer.subscribe(Arrays.asList("test"));
        while (true) {
            ConsumerRecords<String, String> records = consumer.poll(100);
            for (ConsumerRecord<String, String> record : records)
                System.out.printf("offset = %d, key = %s, value = %s\n", record.offset(), record.key(), record.value());
        }
    }

}

测试方法

root@netkiller ~ % /srv/kafka/bin/kafka-console-producer.sh --broker-list localhost:9092 --topic test
>Helloworld

下面详细讲解上面的程序。首先我们通过Properties文件来配置消费属性。

        Properties props = new Properties();
        props.put("bootstrap.servers", "kafka.netkiller.cn:9092");
        props.put("group.id", "test");
        props.put("enable.auto.commit", "true");
        props.put("auto.commit.interval.ms", "1000");
        props.put("session.timeout.ms", "30000");
        props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
        props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");

然后订阅TOPIC，从那个TOPIC 读取消息，Kafka 可以同时订阅多个 TOPIC。下面的例子中，同时订阅了foo和bar两个topic：

consumer.subscribe(Arrays.asList("foo", "bar"));

取出消息（消费消息），通过循环调用poll方法，从队列中取出消息。

        while (true) {
            ConsumerRecords<String, String> records = consumer.poll(100);
            for (ConsumerRecord<String, String> record : records)
                System.out.printf("offset = %d, key = %s, value = %s\n", record.offset(), record.key(), record.value());
        }

poll方法里面的参数是等待消息的时间(Long类型)，如果队列里面有消息，会立马返回，如果没有，会等待指定的时间然后返回。

29.4.2. 发布例子

package cn.netkiller.kafka.test;

import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.Producer;
import org.apache.kafka.clients.producer.ProducerRecord;

import java.util.Properties;

public class KafkaProducerExample {
    public static void main(String[] args) {
        Properties props = new Properties();
        props.put("bootstrap.servers", "kafka.netkiller.cn:9092");
        props.put("acks", "all");
        props.put("retries", 0);
        props.put("batch.size", 16384);
        props.put("linger.ms", 1);
        props.put("buffer.memory", 33554432);
        props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
        props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");

        Producer<String, String> producer = new KafkaProducer<>(props);
        for(int i = 0; i < 100; i++)
            producer.send(new ProducerRecord<>("test", Integer.toString(i), Integer.toString(i)));

        producer.close();
    }
}


29.5. 线程例子

package cn.netkiller.ipo.test;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;
import org.apache.kafka.common.errors.WakeupException;
import org.apache.kafka.common.serialization.StringDeserializer;

public class KafkaConsumerThread implements Runnable {
    private final KafkaConsumer<String, String> consumer;
    private final List<String> topics;

    public KafkaConsumerThread(String groupId, List<String> topics) {
        this.topics = topics;
        Properties props = new Properties();
        props.put("bootstrap.servers", "kafka.netkiller.cn:9092");
        props.put("group.id", groupId);
        props.put("key.deserializer", StringDeserializer.class.getName());
        props.put("value.deserializer", StringDeserializer.class.getName());
        this.consumer = new KafkaConsumer<String, String>(props);
    }

    public void run() {
        try {
            consumer.subscribe(this.topics);

            while (true) {
                ConsumerRecords<String, String> records = consumer.poll(Long.MAX_VALUE);
                for (ConsumerRecord<String, String> record : records) {
                    Map<String, Object> data = new HashMap<>();
                    data.put("partition", record.partition());
                    data.put("offset", record.offset());
                    data.put("value", record.value());
                    System.out.println(data);
                }
            }
        } catch (WakeupException e) {
            // ignore for shutdown
        } finally {
            consumer.close();
        }
    }

    public void shutdown() {
        consumer.wakeup();
    }

    public static void main(String[] args) {
        int numConsumers = 3;
        String groupId = "consumer-tutorial-group";
        List<String> topics = Arrays.asList("test");
        ExecutorService executor = Executors.newFixedThreadPool(numConsumers);

        final List<KafkaConsumerThread> consumers = new ArrayList<>();
        for (int i = 0; i < numConsumers; i++) {
            KafkaConsumerThread consumer = new KafkaConsumerThread(groupId, topics);
            consumers.add(consumer);
            executor.submit(consumer);
        }

        Runtime.getRuntime().addShutdownHook(new Thread() {
            @Override
            public void run() {
                for (KafkaConsumerThread consumer : consumers) {
                    consumer.shutdown();
                }
                executor.shutdown();
                try {
                    executor.awaitTermination(5000, TimeUnit.MILLISECONDS);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
    }
}


部分 II. Java UnitTest

目录

30. Junit5
30.1. JUnit 5 注解
30.1.1. @Disabled
30.1.2. @Tag
30.1.3. @Nested
30.1.4. @TestFactory
30.2. JUnit 5 断言
30.2.1. assertArrayEquals
30.2.2. assertAll
30.2.3.
30.2.4. fail
30.2.5. JUnit 5 前置条件
30.3. 依赖注入
30.3.1. TestInfo
30.3.2. TestReporter
30.4. Junit4
30.4.1. 生成 HTML 报告
30.4.2. Junit4 输出格式定义
31. TestNG
32. JaCoCo - Java code coverage tool
32.1. Maven
32.2. Gradle
33. Rest Assured
33.1. 打印出 response 的 body


第 30 章 Junit5

目录

30.1. JUnit 5 注解
30.1.1. @Disabled
30.1.2. @Tag
30.1.3. @Nested
30.1.4. @TestFactory
30.2. JUnit 5 断言
30.2.1. assertArrayEquals
30.2.2. assertAll
30.2.3.
30.2.4. fail
30.2.5. JUnit 5 前置条件
30.3. 依赖注入
30.3.1. TestInfo
30.3.2. TestReporter
30.4. Junit4
30.4.1. 生成 HTML 报告
30.4.2. Junit4 输出格式定义

JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage

30.1. JUnit 5 注解

JUnit 5 常用注解

名称				说明
@Test			表明一个测试方法
@DisplayName	测试类或方法的显示名称
@BeforeEach		表明在单个测试方法运行之前执行的方法
@AfterEach		表明在单个测试方法运行之后执行的方法
@BeforeAll		表明在所有测试方法运行之前执行的方法
@AfterAll		表明在所有测试方法运行之后执行的方法
@Disabled		禁用测试类或方法
@Tag			为测试类或方法添加标签

30.1.1. @Disabled

    @Test
    @Disabled("这条用例暂时跑不过，忽略!")
    void myFailTest(){
        assertEquals(1,2);
    }

30.1.2. @Tag

元注解标签

@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Tag("remote")
public @interface Remote {
}

@DisplayName("Remote test")
public class RemoteTest {
    @Test
    @Remote
    public void testGetUser() {
        System.out.println("Get user");
    }
}

30.1.3. @Nested

package junit5;

import static org.junit.jupiter.api.Assertions.*;

import java.util.HashMap;
import java.util.Map;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

@DisplayName("Nested tests for HashMap")
class Junit5NestedTest {

    Map<String, Object> map;

    @Nested

    @DisplayName("when a new")
    class WhenNew {

        @BeforeEach
        void create() {
            map = new HashMap<>();
        }

        @Test

        @DisplayName("is empty")
        void isEmpty() {
            assertTrue(map.isEmpty());
        }

        @Nested

        @DisplayName("after adding a new entry")
        class AfterAdd {

            String key = "key";
            Object value = "value";

            @BeforeEach
            void add() {
                map.put(key, value);
            }

            @Test

            @DisplayName("is not empty")
            void isNotEmpty() {
                assertFalse(map.isEmpty());
            }

            @Test

            @DisplayName("returns value when getting by key")
            void returnValueWhenGettingByKey() {
                assertEquals(value, map.get(key));
            }

            @Nested

            @DisplayName("after removing the entry")
            class AfterRemove {

                @BeforeEach
                void remove() {
                    map.remove(key);
                }

                @Test

                @DisplayName("is empty now")
                void isEmpty() {
                    assertTrue(map.isEmpty());
                }

                @Test

                @DisplayName("returns null when getting by key")
                void returnNullForKey() {
                    assertNull(map.get(key));
                }
            }
        }
    }

}

30.1.4. @TestFactory

    @TestFactory
    public Collection<DynamicTest> simpleDynamicTest() {
        return Collections.singleton(dynamicTest("simple dynamic test", () -> assertTrue(2 > 1)));
    }

DynamicTest 提供了一个静态方法 stream 来根据输入生成动态测试

    @TestFactory
    public Stream<DynamicTest> streamDynamicTest() {
     return stream(
           Stream.of("Hello", "World").iterator(),
           (word) -> String.format("Test - %s", word),
           (word) -> assertTrue(word.length() > 4)
     );
    }


30.2. JUnit 5 断言

常用的断言方法
方法				说明
assertEquals	判断两个对象或两个原始类型是否相等
assertNotEquals	判断两个对象或两个原始类型是否不相等
assertSame	判断两个对象引用是否指向同一个对象
assertNotSame	判断两个对象引用是否指向不同的对象
assertTrue	判断给定的布尔值是否为 true
assertFalse	判断给定的布尔值是否为 false
assertNull	判断给定的对象引用是否为 null
assertNotNull	判断给定的对象引用是否不为 null

30.2.1. assertArrayEquals

assertArrayEquals 方法的示例

    @Test
    @DisplayName("array assertion")
    public void array() {
        assertArrayEquals(new int[]{1, 2}, new int[] {1, 2});
    }

30.2.2. assertAll

    @Test
    @DisplayName("运行一组断言")
    public void assertAllCase() {
        assertAll("groupAssert",
                () -> assertEquals(2, 1 + 1),
                () -> assertTrue(1 > 0)
        );
    }

assertThrows

通过 assertThrows 或 expectThrows 来判断是否抛出期望的异常类型

assertThrows 和 expectThrows 方法的示例

    @Test
    @DisplayName("throws exception")
    public void exception() {
        assertThrows(ArithmeticException.class, () -> System.out.println(1 / 0));
    }

30.2.4. fail

通过 fail 方法直接使得测试失败

    @Test
    @DisplayName("fail")
    public void shouldFail() {
        fail("This should fail");
    }

30.2.5. JUnit 5 前置条件

前置条件（assumptions）类似于断言，不同之处在于不满足的断言会使得测试方法失败，而不满足的前置条件只会使得测试方法的执行终止。前置条件可以看成是测试方法执行的前提，当该前提不满足时，就没有继续执行的必要。

    @DisplayName("Assumptions")
    public class AssumptionsTest {
        private final String environment = "DEV";

         @Test
         @DisplayName("simple")
         public void simpleAssume() {
            assumeTrue(Objects.equals(this.environment, "DEV"));
            assumeFalse(() -> Objects.equals(this.environment, "PROD"));
         }

         @Test
         @DisplayName("assume then do")
         public void assumeThenDo() {
            assumingThat(
               Objects.equals(this.environment, "DEV"),
               () -> System.out.println("In DEV")
            );
         }
    }

assumeTrue 和 assumFalse 确保给定的条件为 true 或 false，不满足条件会使得测试执行终止。assumingThat 的参数是表示条件的布尔值和对应的 Executable 接口的实现对象。只有条件满足时，Executable 对象才会被执行；当条件不满足时，测试执行并不会终止。


30.3. 依赖注入

30.3.1. TestInfo

    @Test
    @DisplayName("test info")
    public void testInfo(final TestInfo testInfo) {
        System.out.println(testInfo.getDisplayName());
    }

30.3.2. TestReporter

    @Test
    @DisplayName("test reporter")
    public void testReporter(final TestReporter testReporter) {
        testReporter.publishEntry("name", "Alex");
    }


30.4. Junit4

30.4.1. 生成 HTML 报告

<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>demo</groupId>
    <artifactId>junit4</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>junit4</name>
    <url>http://maven.apache.org</url>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.22.1</version>
                <configuration>
                    <useSystemClassLoader>false</useSystemClassLoader>
                </configuration>
            </plugin>
        </plugins>
    </build>
    <reporting>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-report-plugin</artifactId>
                <version>2.22.1</version>
            </plugin>
        </plugins>
    </reporting>
</project>

生成报告

neo@MacBook-Pro ~/workspace/junit4 % mvn surefire-report:report-only

30.4.2. Junit4 输出格式定义

package cn.netkiller.api.test;

import java.util.ArrayList;
import java.util.List;

import org.junit.experimental.categories.Category;
import org.junit.runner.Description;
import org.junit.runners.BlockJUnit4ClassRunner;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.InitializationError;

public class ApiTestRunner extends BlockJUnit4ClassRunner {

    public ApiTestRunner(Class<?> class) throws InitializationError {
        super(class);
    }

    @Override
    protected Description describeChild(FrameworkMethod method) {
        Api api = method.getAnnotation(Api.class);
        if (api == null) {
            return super.describeChild(method);
        }

        List<String> list = new ArrayList<String>();
        Class<?>[] categorys;

        if (method.getAnnotation(Category.class) != null) {
            categorys = method.getAnnotation(Category.class).value();
        } else {
            categorys = this.getTestClass().getJavaClass().getAnnotation(Category.class).value();
        }

        for (Class<?> category : categorys) {
            if (category.getSimpleName().equals("EnterpriseCategory")) {
                list.add("Enterprise");
            } else if (category.getSimpleName().equals("ProfessionalCategory")) {
                list.add("Professional");
            }
        }

        String tag = "";
        if (list != null) {
            tag = String.join(",", list);
        }
        String name = String.format("%s - %s %s - %s", method.getName(), api.method(), api.uri(), tag);
        return Description.createTestDescription(getTestClass().getJavaClass(), name, method.getAnnotations());
    }
}

package cn.netkiller.api.test;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(value = RetentionPolicy.RUNTIME)
@Target(value = { ElementType.METHOD })
public @interface Api {
    public ApiMethod method() default ApiMethod.GET;

    public String uri() default "/";
}

public interface ProfessionalCategory {
    String name = "Professional";
}

public interface EnterpriseCategory {
    String name = "Enterprise";
}


第 31 章 TestNG


第 32 章 JaCoCo - Java code coverage tool

目录

32.1. Maven
32.2. Gradle

32.1. Maven

mvn help:describe -Dplugin=org.jacoco:jacoco-maven-plugin -Ddetail

<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>demo</groupId>
    <artifactId>junit4</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>junit4</name>
    <url>http://maven.apache.org</url>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.22.1</version>
                <configuration>
                    <useSystemClassLoader>false</useSystemClassLoader>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.jacoco</groupId>
                <artifactId>jacoco-maven-plugin</artifactId>
                <version>0.8.2</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>prepare-agent</goal>
                        </goals>
                    </execution>
                    <execution>
                        <id>report</id>
                        <phase>test</phase>
                        <goals>
                            <goal>report</goal>
                        </goals>
                    </execution>
                </executions>

            </plugin>
        </plugins>
    </build>
    <reporting>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-report-plugin</artifactId>
                <version>2.22.1</version>
            </plugin>
        </plugins>
    </reporting>
</project>

运行 mvn test 便会生成 jacoco 报告

neo@MacBook-Pro ~/workspace/junit4 % find target/site/jacoco
target/site/jacoco
target/site/jacoco/jacoco.csv
target/site/jacoco/demo.junit4
target/site/jacoco/demo.junit4/index.source.html
target/site/jacoco/demo.junit4/index.html
target/site/jacoco/demo.junit4/App.java.html
target/site/jacoco/demo.junit4/App.html
target/site/jacoco/index.html
target/site/jacoco/jacoco-sessions.html
target/site/jacoco/jacoco-resources
target/site/jacoco/jacoco-resources/branchnc.gif
target/site/jacoco/jacoco-resources/sort.js
target/site/jacoco/jacoco-resources/greenbar.gif
target/site/jacoco/jacoco-resources/sort.gif
target/site/jacoco/jacoco-resources/source.gif
target/site/jacoco/jacoco-resources/report.gif
target/site/jacoco/jacoco-resources/prettify.js
target/site/jacoco/jacoco-resources/branchpc.gif
target/site/jacoco/jacoco-resources/branchfc.gif
target/site/jacoco/jacoco-resources/class.gif
target/site/jacoco/jacoco-resources/prettify.css
target/site/jacoco/jacoco-resources/report.css
target/site/jacoco/jacoco-resources/group.gif
target/site/jacoco/jacoco-resources/package.gif
target/site/jacoco/jacoco-resources/redbar.gif
target/site/jacoco/jacoco-resources/up.gif
target/site/jacoco/jacoco-resources/session.gif
target/site/jacoco/jacoco-resources/down.gif
target/site/jacoco/jacoco-resources/method.gif
target/site/jacoco/jacoco-resources/bundle.gif
target/site/jacoco/jacoco.xml


32.2. Gradle

https://docs.gradle.org/current/userguide/jacoco_plugin.html

plugins 中加入 id 'jacoco'

/*
 * This file was generated by the Gradle 'init' task.
 *
 * This generated file contains a sample Java Library project to get you started.
 * For more details take a look at the Java Libraries chapter in the Gradle
 * user guide available at https://docs.gradle.org/5.0/userguide/java_library_plugin.html
 */

plugins {
    // Apply the java-library plugin to add support for Java Library
    id 'java-library'
    // id 'java'
    id 'eclipse' // optional (to generate Eclipse project files)
    id 'idea' // optional (to generate IntelliJ IDEA project files)
    id 'jacoco'
}

repositories {
    // Use jcenter for resolving your dependencies.
    // You can declare any Maven/Ivy/file repository here.
    jcenter()
    //mavenCentral()
}

dependencies {

    testCompile('org.junit.jupiter:junit-jupiter-api:5.3.2')
    testCompile('org.junit.jupiter:junit-jupiter-params:5.3.2')
    testRuntime('org.junit.jupiter:junit-jupiter-engine:5.3.2')

    testCompile 'io.rest-assured:rest-assured:3.2.0'
    testCompile 'io.rest-assured:json-schema-validator:3.2.0'
    compile 'io.rest-assured:rest-assured:3.2.0'
    compile 'io.rest-assured:json-path:3.2.0'
    compile 'io.rest-assured:xml-path:3.2.0'

}

test {
    useJUnitPlatform()
    testLogging {
        events "passed", "skipped", "failed"
    }
}

运行 gradlew build jacocoTestReport 生成测试报告

neo@MacBook-Pro ~/workspace/junit5 % ./gradlew build jacocoTestReport

BUILD SUCCESSFUL in 5s
6 actionable tasks: 2 executed, 4 up-to-date
neo@MacBook-Pro ~/workspace/junit5 % find build/reports/jacoco
build/reports/jacoco
build/reports/jacoco/test
build/reports/jacoco/test/html
build/reports/jacoco/test/html/index.html
build/reports/jacoco/test/html/jacoco-sessions.html
build/reports/jacoco/test/html/jacoco-resources
build/reports/jacoco/test/html/jacoco-resources/branchnc.gif
build/reports/jacoco/test/html/jacoco-resources/sort.js
build/reports/jacoco/test/html/jacoco-resources/greenbar.gif
build/reports/jacoco/test/html/jacoco-resources/sort.gif
build/reports/jacoco/test/html/jacoco-resources/source.gif
build/reports/jacoco/test/html/jacoco-resources/report.gif
build/reports/jacoco/test/html/jacoco-resources/prettify.js
build/reports/jacoco/test/html/jacoco-resources/branchpc.gif
build/reports/jacoco/test/html/jacoco-resources/branchfc.gif
build/reports/jacoco/test/html/jacoco-resources/class.gif
build/reports/jacoco/test/html/jacoco-resources/prettify.css
build/reports/jacoco/test/html/jacoco-resources/report.css
build/reports/jacoco/test/html/jacoco-resources/group.gif
build/reports/jacoco/test/html/jacoco-resources/package.gif
build/reports/jacoco/test/html/jacoco-resources/redbar.gif
build/reports/jacoco/test/html/jacoco-resources/up.gif
build/reports/jacoco/test/html/jacoco-resources/session.gif
build/reports/jacoco/test/html/jacoco-resources/down.gif
build/reports/jacoco/test/html/jacoco-resources/method.gif
build/reports/jacoco/test/html/jacoco-resources/bundle.gif
build/reports/jacoco/test/html/net.coding.api.task
build/reports/jacoco/test/html/net.coding.api.task/Task.java.html
build/reports/jacoco/test/html/net.coding.api.task/index.source.html
build/reports/jacoco/test/html/net.coding.api.task/index.html
build/reports/jacoco/test/html/net.coding.api.task/Task.html
build/reports/jacoco/test/html/junit5
build/reports/jacoco/test/html/junit5/Library.java.html
build/reports/jacoco/test/html/junit5/index.source.html
build/reports/jacoco/test/html/junit5/index.html
build/reports/jacoco/test/html/junit5/UnitTest.java.html
build/reports/jacoco/test/html/junit5/Library.html
build/reports/jacoco/test/html/junit5/UnitTest.html
build/reports/jacoco/test/html/junit5/Config.html
build/reports/jacoco/test/html/junit5/Config.java.html
build/reports/jacoco/test/html/com.example.project
build/reports/jacoco/test/html/com.example.project/index.source.html
build/reports/jacoco/test/html/com.example.project/index.html
build/reports/jacoco/test/html/com.example.project/Calculator.java.html
build/reports/jacoco/test/html/com.example.project/Calculator.html

如果你希望自动执行，不需要携带 jacocoTestReport 任务，添加下面代码到 build.gradle 文件最下面

test.finalizedBy(project.tasks.jacocoTestReport)

排除 class

jacocoTestReport {
  afterEvaluate {
    classDirectories = files(classDirectories.files.collect {
      fileTree(dir: it, exclude: [
        'io/reflectoring/coverage/ignored/**',
        'io/reflectoring/coverage/part/**'
      ])
    })
  }
}


第 33 章 Rest Assured

目录

33.1. 打印出 response 的 body

33.1. 打印出 response 的 body

    @Test()
    public void getHttpTest() {
        Response response = given()
                .get("http://www.netkiller.cn");
        response.print();
    }


第 34 章 Software Development Kit

目录

34.1. Hessian
34.2. quartz-scheduler
34.3. Redisson

34.1. Hessian

基于Binary-RPC协议实现


34.2. quartz-scheduler

http://quartz-scheduler.org/


34.3. Redisson

http://redisson.org/


部分 III. Android 9 Pie

目录

35. Android Studio
35.1. 卸载 Android Studio
35.2. 代码格式化
35.3. 设置兼容最低SDK版本
36. AndroidManifest.xml
36.1.
36.2. 开启网络
36.3. 文件存储权限
36.4. 相机权限
36.5. GPS 定位权限
37. 配置文件
37.1. *.properties 文件
37.2. 再 AndroidManifest.xml 使用 meta-data element 定义
37.3. 再 build.gradle 文件中配置 productFlavors
38. UI Layout
38.1. 切换UI
38.1.1.
38.1.2. startActivity()
38.1.3. Activity 间数据传递
38.2. Button
38.2.1. 启用禁用
38.2.2. 实现 OnClickListener 接口
38.3. ListView
38.3.1. Array
38.3.2. List
38.3.3. setOnItemClickListener()
38.3.4. 用接口方法实现
38.4. Switch
38.5. GardView
38.6. GridView
38.7. ProgressBar
38.8. ImageView
38.9. Fragment
38.10. Dialog
38.11. Menu
39. Toast
39.1. 默认样式
39.2. 自定义样式
39.3. 带有图片的样式
40. Environment
40.1.
40.2.
40.3.
41. Schedule 计划任务
41.1. Time 和 TimerTask 定时刷新
41.2. 使用 Runnable 和 Handler 实现定时执行
42. Internationalization i18n with Android (国际化)
42.1. 创建国际化文件
42.2. strings.xml 文件
42.3. 翻译语言
42.4. 引用国际化文件
42.5. 切换语言
43. 存储
43.1. SharedPreferences
43.1.1. 操作模式
43.1.2. 保存数据
43.1.3. 读取数据
43.1.4. 通过 key 查询数据是否存在
43.1.5. 删除数据
43.1.6. 清空数据
43.1.7. 对象存储
43.1.8. SharedPreferences 读取物理存储文件
43.2. SD Card
43.2.1. SD Card 状态
44. 相机与相册
44.1. manifest 文件
44.2. layout
44.3. Activity
44.4. LED flash 做手电筒
45. 麦克风与录音
45.1. 开启麦克风和SD卡权限
45.2. layout
45.3. Activity
46. 多媒体开发
46.1. MediaPlayer
46.2. VideoView 开发
46.2.1. 播放网络视频
46.2.2. MediaController 添加翻页事件
46.2.3. 静音播放视频
46.2.4. 更新进度条
46.2.5. 完整的例子
46.3. SurfaceView
46.4. Vitamio
47. 定位
47.1. manifest 权限配置
47.2. layout
47.3. Activity
48. 电话
48.1. SIM 卡状态
48.2. 通信录与拨打电话
48.3.
49. 消息广播
49.1. 动态注册
49.2. 静态注册
49.3. 自定义用户消息广播
49.4. 本地广播
50. Service
50.1. Service的基本用法
50.1.1. manifest 文件
50.1.2. 创建 Service
50.1.3. Layout 代码
50.1.4. Activity 代码
50.2. Service 中启动线程
50.3. Service和Activity通信
50.3.1. Layout
50.3.2. Service
50.3.3. Activity
51. NFC (Near field communication)
51.1. AndroidManifest.xml 文件配置
51.2. Loyout 文件
51.3. Activity 文件
52. OkHttp - An HTTP & HTTP/2 client for Android and Java applications
52.1. Gradle
52.2. AndroidManifest.xml 开启网络访问权限
52.3. okhttp 默认是 HTTPS 开启 HTTP
52.4. GET
52.5. POST
52.5.1. POST Form Data
52.5.2. POST RAW JSON
52.5.3. 数据流提交
52.6. http header 相关设置
52.6.1. 设置 HTTP 头
52.6.2. Cookie 管理
52.6.3. 禁用缓存
52.6.4. 设置缓存 max-age
52.6.5. 强制缓存
52.7. HTTP Base Auth
52.8. HttpUrl.Builder 组装 URL 地址参数
52.9. Android Activity Example
52.10. Android Oauth2 + Jwt example
52.11. HTTP/2
53. EventBus
53.1. 添加 EventBus 依赖到项目Gradle文件
53.2. 快速开始一个演示例子
53.2.1. 创建 MessageEvent 类
53.2.2. Layout
53.2.3. Activity
53.3. Sticky Events
53.3.1. MainActivity
53.3.2. StickyActivity
53.3.3. MessageEvent
53.3.4. 删除粘性事件
53.4. 线程模型
53.5. 配置 EventBus
53.6. 事件优先级
53.7. 捕获异常事件
54. 设计模式
54.1. 单例模式
55.
55.1. java.net.UnknownServiceException: CLEARTEXT communication to 192.168.0.185 not permitted by network security policy
55.2. Caused by: android.os.NetworkOnMainThreadException


第 35 章 Android Studio

目录

35.1. 卸载 Android Studio
35.2. 代码格式化
35.3. 设置兼容最低SDK版本

35.1. 卸载 Android Studio

卸载 Mac Android Studio

rm -Rf /Applications/Android\ Studio.app
rm -Rf ~/Library/Preferences/AndroidStudio*
rm ~/Library/Preferences/com.google.android.studio.plist
rm -Rf ~/Library/Application\ Support/AndroidStudio*
rm -Rf ~/Library/Logs/AndroidStudio*
rm -Rf ~/Library/Caches/AndroidStudio*
rm -Rf ~/Library/Android*

删除 Projects

rm -Rf ~/AndroidStudioProjects

删除 gradle

rm -rf ~/.gradle

卸载Android Virtual Devices(AVDs) and *.keystore.

rm -Rf ~/.android


35.2. 代码格式化

Option + Command + L


35.3. 设置兼容最低SDK版本

Android 9 Pie 的设置方法

打开 build.gradle 修改 minSdkVersion 项，这里的 26 表示 Android 8.0

apply plugin: 'com.android.application'

android {
    compileSdkVersion 28
    defaultConfig {
        applicationId "cn.netkiller.video"
        minSdkVersion 26
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:28.0.0'
    implementation 'com.android.support.constraint:constraint-layout:1.1.3'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}


第 36 章 AndroidManifest.xml

目录

36.1.
36.2. 开启网络
36.3. 文件存储权限
36.4. 相机权限
36.5. GPS 定位权限

36.1.

<uses-sdk
        android:minSdkVersion="26"
        android:targetSdkVersion="28" />


36.2. 开启网络

<uses-permission android:name="android.permission.INTERNET" />

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="cn.netkiller.android.myapplication">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity
            android:name=".MainActivity"
            android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />

            </intent-filter>
        </activity>

    </application>
    <uses-permission android:name="android.permission.INTERNET" />
</manifest>


36.3. 文件存储权限

    <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />


36.4. 相机权限

    <uses-permission android:name="android.permission.CAMERA" />
    <uses-feature android:name="android.hardware.camera" />


36.5. GPS 定位权限

<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.INTERNET" />


第 37 章 配置文件

目录

37.1. *.properties 文件
37.2. 再 AndroidManifest.xml 使用 meta-data element 定义
37.3. 再 build.gradle 文件中配置 productFlavors

37.1. *.properties 文件

创建 properties 文件 res/config/development.properties

api_url=https://api.netkiller.cn/v1/
api_key=123456

package cn.netkiller.app;

import android.content.Context;
import android.content.res.Resources;
import android.util.Log;

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

public final class Config {
    private static final String TAG = "Config";

    public static String getKey(Context context, String name) {
        Resources resources = context.getResources();

        try {
            InputStream rawResource = resources.openRawResource(R.config.development);
            Properties properties = new Properties();
            properties.load(rawResource);
            return properties.getProperty(name);
        } catch (Resources.NotFoundException e) {
            Log.e(TAG, "Unable to find the config file: " + e.getMessage());
        } catch (IOException e) {
            Log.e(TAG, "Failed to open config file.");
        }

        return null;
    }
}

String apiUrl = Config.getKey(this, "api_url");
String apiKey = Config.getKey(this, "api_key");


37.2. 再 AndroidManifest.xml 使用 meta-data element 定义

...
<application ...>
    ...
    ...
    <meta-data android:name="api_url" android:value="https://api.netkiller.cn/v1/"/>
    <meta-data android:name="api_key" android:value="123456"/>
</application>

public static String getMetaData(Context context, String name) {
    try {
        ApplicationInfo ai = context.getPackageManager().getApplicationInfo(context.getPackageName(), PackageManager.GET_META_DATA);
        Bundle bundle = ai.metaData;
        return bundle.getString(name);
    } catch (PackageManager.NameNotFoundException e) {
        Log.e(TAG, "Unable to load meta-data: " + e.getMessage());
    }
    return null;
}

String apiUrl = getMetaData(this, "api_url");
String apiKey = getMetaData(this, "api_key");


37.3. 再 build.gradle 文件中配置 productFlavors

productFlavors {
    prod {
        buildConfigField 'String', 'API_URL', '"https://api.netkiller.cn/v1/"'
        buildConfigField 'String', 'API_KEY', '"123456"'
    }
}

引用 config 方法

String apiUrl = BuildConfig.API_URL;
String apiKey = BuildConfig.API_KEY;


第 38 章 UI Layout

目录

38.1. 切换UI
38.1.1.
38.1.2. startActivity()
38.1.3. Activity 间数据传递
38.2. Button
38.2.1. 启用禁用
38.2.2. 实现 OnClickListener 接口
38.3. ListView
38.3.1. Array
38.3.2. List
38.3.3. setOnItemClickListener()
38.3.4. 用接口方法实现
38.4. Switch
38.5. GardView
38.6. GridView
38.7. ProgressBar
38.8. ImageView
38.9. Fragment
38.10. Dialog
38.11. Menu

38.1. 切换UI

38.1.1.

setContentView(R.layout.view);

38.1.2. startActivity()

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
     <application android:label="Test">

        ...
        ...
        <activity android:name=".WriteActivity"></activity>

    </application>

</manifest>

        Button button = (Button) findViewById(R.id.writeButton);

        button.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                setContentView(R.layout.activity_write);
                Intent intent = new Intent(MainActivity.this,WriteActivity.class);
                startActivity(intent);
            }
        });

38.1.3. Activity 间数据传递

Intent it = new Intent(Activity.Main.this, Activity2.class);
Bundle bundle=new Bundle();
bundle.putString("name", "This is from MainActivity!");
it.putExtras(bundle);
startActivity(it);

获取数据

Bundle bundle=getIntent().getExtras();
String name=bundle.getString("name");


38.2. Button

public class MainActivity extends AppCompatActivity {

    //我们需要自己写一个常量作为requestCode，在请求result时传递进去
    public static final int REQUEST_CODE_NORMAL = 100;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button button = (Button) findViewById(R.id.Button);

        button.setOnClickListener(new View.OnClickListener() {
            public void onClick(View view) {
                startActivityForResult(new Intent(this,SecondActivity.class),REQUEST_CODE_NORMAL);
            }
        });
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == REQUEST_CODE_NORMAL) {
            //获得Result数据并处理
            ...
            ...
        }
    }
}

public class SecondActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.save);

         Button button = (Button) findViewById(R.id.SaveButton);

        button.setOnClickListener(new View.OnClickListener() {
            public void onClick(View view) {
                Intent intent = new Intent(this,MainResultActivity.class);
                intent.putExtra("content",etContent.getText().toString());
                setResult(1,intent);
                //发送Result数据给请求方，然后finish（）
                finish();
            }
        });
    }
}

38.2.1. 启用禁用

myButton.setEnabled(false);

38.2.2. 实现 OnClickListener 接口

package cn.netkiller.video;

import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private Button buttonVideoView;
    private Button buttonSurfaceView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        buttonVideoView = (Button) findViewById(R.id.buttonVideoView);
        buttonVideoView.setOnClickListener(this);

        buttonSurfaceView = (Button) findViewById(R.id.buttonSurfaceView);
        buttonSurfaceView.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        Intent intent;
        switch (v.getId()) {
            case R.id.buttonVideoView:
                startActivity(new Intent(MainActivity.this, VideoViewActivity.class));
                break;
            case R.id.buttonSurfaceView:

                break;
            default:
                break;
        }
    }
}


38.3. ListView

38.3.1. Array

        String[] list = Arrays.asList("Apple", "Banana", "Orange", "Watermelon",
                "Pear", "Grape", "Pineapple", "Strawberry", "Cherry", "Mango";
        ArrayAdapter<String> adapter = new ArrayAdapter<String>(MainActivity.this, android.R.layout.simple_list_item_1,data);
        ListView listView = (ListView) findViewById(R.id.history);
        listView.setAdapter(adapter);

    <ListView
        android:id="@+id/history"
        android:layout_width="368dp"
        android:layout_height="444dp"
        android:scrollbars="horizontal"
        tools:layout_editor_absoluteX="8dp"
        tools:layout_editor_absoluteY="59dp" />

38.3.2. List

        List<String> list = Arrays.asList("Apple", "Banana", "Orange", "Watermelon","Pear", "Grape", "Pineapple", "Strawberry", "Cherry", "Mango");
        ArrayAdapter<String> adapter = new ArrayAdapter<String>(MainActivity.this, android.R.layout.simple_list_item_1,list);
        ListView listView = (ListView) findViewById(R.id.history);
        listView.setAdapter(adapter);

38.3.3. setOnItemClickListener()

        List<String> list = Arrays.asList("Text 文本", "URL 网址", "电话号码", "短信","开启应用", "地址", "日历", "图片", "邮箱", "GPS 坐标");
        ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1,list);
        final ListView listView = (ListView) findViewById(R.id.schemaList);
        listView.setAdapter(adapter);

        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {

                String text = listView.getItemAtPosition(position)+"";
                Log.e("WRITE","position="+position+", text="+text);
            }
        });

38.3.4. 用接口方法实现

public class MainActivity extends Activity implements OnItemClickListener, OnScrollListener

        List<String> list = Arrays.asList("Text 文本", "URL 网址", "电话号码", "短信","开启应用", "地址", "日历", "图片", "邮箱", "GPS 坐标");
        ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1,list);
        final ListView listView = (ListView) findViewById(R.id.schemaList);
        listView.setAdapter(adapter);

        listView.setOnItemClickListener(this);
        listView.setOnScrollListener(this);

    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        String text = listView.getItemAtPosition(position)+"";
        Log.e("WRITE","position="+position+", text="+text);
    }

    @Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {

        switch (scrollState) {
        case SCROLL_STATE_FLING:
            Log.i("tag", "用户手指离开屏幕后，因惯性继续滑动");
            Map<String,Object>map = new HashMap<String,Object>();
            map.put("icon", R.drawable.ic_launcher);
            map.put("text", "新增加项目");
            dataList.add(map);
            adapter.notifyDataSetChanged();
            break;
        case SCROLL_STATE_IDLE:
            Log.i("tag","已经停止滑动");
            break;
        case SCROLL_STATE_TOUCH_SCROLL:
            Log.i("tag", "手指未离开屏幕，屏幕继续滑动");
            break;
        }
    }


38.4. Switch

    <Switch
        android:id="@+id/switchWrite"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="8dp"
        android:layout_marginBottom="8dp"
        android:text="NDEF Message write"
        android:textOff="NDEF Message write Off"
        android:textOn="NDEF Message write On"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toStartOf="@+id/writeButton"
        app:layout_constraintHorizontal_bias="0.076"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/ndefWrite"
        app:layout_constraintVertical_bias="0.087" />

        final Switch switchWrite = (Switch) findViewById(R.id.switchWrite);

        switchWrite.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {

                if(isChecked) {
                    status.setText(switchWrite.getTextOn().toString());
                } else {
                    status.setText(switchWrite.getTextOff().toString());
                }
            }
        });

    if(switchWrite.isChecked()){

    }


38.5. GardView


38.6. GridView


38.7. ProgressBar


38.8. ImageView


38.9. Fragment


38.10. Dialog


38.11. Menu


第 39 章 Toast

目录

39.1. 默认样式
39.2. 自定义样式
39.3. 带有图片的样式

39.1. 默认样式

Toast.makeText(getApplicationContext(), "默认Toast样式", Toast.LENGTH_SHORT).show();


39.2. 自定义样式

toast = Toast.makeText(getApplicationContext(),"自定义位置Toast", Toast.LENGTH_LONG);
   toast.setGravity(Gravity.CENTER, 0, 0);
   toast.show();


39.3. 带有图片的样式

toast = Toast.makeText(getApplicationContext(),"带图片的Toast", Toast.LENGTH_LONG);
   toast.setGravity(Gravity.CENTER, 0, 0);
   LinearLayout toastView = (LinearLayout) toast.getView();
   ImageView imageView = new ImageView(getApplicationContext());
   imageView.setImageResource(R.drawable.icon);
   toastView.addView(imageView, 0);
   toast.show();


第 40 章 Environment

目录

40.1.
40.2.
40.3.

40.1.

Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED);


40.2.

Environment.getExternalStorageDirectory().getAbsolutePath();


40.3.

        Log.i("Environment", "getRootDirectory(): "
                + Environment.getRootDirectory().toString());
        Log.i("Environment", "getDataDirectory(): "
                + Environment.getDataDirectory().toString());
        Log.i("Environment", "getDownloadCacheDirectory(): "
                + Environment.getDownloadCacheDirectory().toString());
        Log.i("Environment", "getExternalStorageDirectory(): "
                + Environment.getExternalStorageDirectory().toString());

        Log.i("Environment","getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES): "
                        + Environment.getExternalStoragePublicDirectory(
                        Environment.DIRECTORY_PICTURES).toString());


第 41 章 Schedule 计划任务

目录

41.1. Time 和 TimerTask 定时刷新
41.2. 使用 Runnable 和 Handler 实现定时执行

41.1. Time 和 TimerTask 定时刷新

package cn.netkiller.okhttp;

import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;

public class ScheduleActivity extends AppCompatActivity {

    private TextView clock;

    final Handler handler = new Handler() {
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case 1:
                    update(msg.obj.toString());
                    break;
            }
        }

        void update(String c) {

            clock.setText(c);
        }
    };

    Timer timer = new Timer();
    TimerTask task = new TimerTask() {
        DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");

        public void run() {
            Message message = new Message();
            message.what = 1;
            message.obj = dateFormat.format(new Date());
            handler.sendMessage(message);
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_schedule);

        clock = (TextView) findViewById(R.id.clock);
        clock.setText("Today is ...");
        timer.schedule(task, 1000 * 5, 1000); //启动timer

    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (timer != null) {
            timer.cancel();
            timer = null;
        }

    }

}


41.2. 使用 Runnable 和 Handler 实现定时执行

原理是使用 handler.postDelayed 延迟 Runnable 的运行时间

package cn.netkiller.okhttp;

import android.os.Handler;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;

public class RunnableActivity extends AppCompatActivity {

    private Handler handler = new Handler();
    private Runnable runnable = new Runnable() {
        public void run() {
            this.update();
            handler.postDelayed(this, 1000);// 1000 ms = 1s 间隔1秒
        }

        void update() {
            DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            time.setText(dateFormat.format(new Date()));
        }
    };
    private TextView time;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_runnable);

        time = (TextView) findViewById(R.id.time);
        time.setText("Start...");

        handler.postDelayed(runnable, 1000 * 5); // 5 秒后开始
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        handler.removeCallbacks(runnable);
    }

}


第 42 章 Internationalization i18n with Android (国际化)

目录

42.1. 创建国际化文件
42.2. strings.xml 文件
42.3. 翻译语言
42.4. 引用国际化文件
42.5. 切换语言

42.1. 创建国际化文件

进入 Android Studio 文件菜单 File -> New -> New Resource File

在左侧列表中找到 Locale 点击 “>>” 按钮

选择国家后，点击 OK 按钮即可。

资源文件夹中已经显示出国际化文件，上面并有对应的国旗。

查看项目文件夹

neo@MacBook-Pro ~/AndroidStudioProjects/locale % find app/src/main/res | grep values
app/src/main/res/values-zh-rCN
app/src/main/res/values-zh-rCN/strings.xml
app/src/main/res/values
app/src/main/res/values/colors.xml
app/src/main/res/values/dimens.xml
app/src/main/res/values/styles.xml
app/src/main/res/values/strings.xml


42.2. strings.xml 文件

<resources>
    <string name="app_name">Netkiller</string>
    <string name="title_home">Home</string>
    <string name="title_dashboard">Dashboard</string>
    <string name="title_notifications">Notifications</string>
</resources>


42.3. 翻译语言

再 res/values/strings 目录上面单击鼠标右键，打开 Open Translations Editor 翻译编辑器。

单击地球图标，添加 zh-cn 语言

现在就可以对照翻译语言包文件了。


42.4. 引用国际化文件

String test = "Sign Up";

String test = getResources().getString(R.string.sign_up);

R.string.browserSentence = "You are using $1%s to browse the Internet.";

String browser = getString(R.string.browserSentence, browser.getBrowser());

TextView textView = new TextView(this);
TextView.setText(“Sign Up”);

TextView textView = new TextView(this);
textView.setText(R.string.sign_up);

<TextView
 android:layout_width="wrap_content"
 android:layout_height="wrap_content"
 android:text="Hello World!" />

<TextView
 android:layout_width="wrap_content"
 android:layout_height="wrap_content"
 android:text="@string/hello_world" />


42.5. 切换语言

        DisplayMetrics metrics = getResources().getDisplayMetrics();
        Configuration configuration = getResources().getConfiguration();
        configuration.setLocale(locale);
        getResources().updateConfiguration(configuration, metrics);

        //重新启动 Activity
        Intent intent = new Intent(this, MainActivity.class);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
        startActivity(intent);


第 43 章 存储

目录

43.1. SharedPreferences
43.1.1. 操作模式
43.1.2. 保存数据
43.1.3. 读取数据
43.1.4. 通过 key 查询数据是否存在
43.1.5. 删除数据
43.1.6. 清空数据
43.1.7. 对象存储
43.1.8. SharedPreferences 读取物理存储文件
43.2. SD Card
43.2.1. SD Card 状态

43.1. SharedPreferences

SharedPreferences是Android中的数据存储技术，常用来存储一些轻量级的数据。

实际上SharedPreferences是 NoSQL 数据库，用于处理的key-value键值对存储，类似Windows 系统的注册表，Linux 系统里的 Berkeley DB (bdb) 以及衍生出的 dba,mdb 这类 hash 表数据库。

43.1.1. 操作模式

Context.MODE_PRIVATE：为默认操作模式,代表该文件是私有数据,只能被应用本身访问,在该模式下,写入的内容会覆盖原文件的内容
Context.MODE_APPEND：模式会检查文件是否存在,存在就往文件追加内容,否则就创建新文件.
Context.MODE_WORLD_READABLE和Context.MODE_WORLD_WRITEABLE用来控制其他应用是否有权限读写该文件.
MODE_WORLD_READABLE：表示当前文件可以被其他应用读取.
MODE_WORLD_WRITEABLE：表示当前文件可以被其他应用写入.

43.1.2. 保存数据

        Button buttonPut = (Button) findViewById(R.id.buttonPut);

        buttonPut.setOnClickListener(new View.OnClickListener() {
            public void onClick(View view) {

                //实例化SharedPreferences对象
                SharedPreferences sharedPreferences = getSharedPreferences("test", Activity.MODE_PRIVATE);

                //实例化SharedPreferences.Editor对象
                SharedPreferences.Editor editor = sharedPreferences.edit();

                //用putString的方法保存数据
                editor.putString("name", "Neo");
                editor.putString("nickname", "netkiller");
                editor.putBoolean("sex", true);
                editor.putInt("age", 30);
                editor.putFloat("tall", 180.23f);
                Set<String> books = new HashSet<String>();
                books.add("Netkiller Linux 手札");
                books.add("Netkiller Java 手札");
                books.add("Netkiller Android 手札");
                editor.putStringSet("books", books);

                //提交当前数据
                editor.commit();

            }
        });

43.1.3. 读取数据

        Button buttonGet = (Button) findViewById(R.id.buttonGet);

        buttonGet.setOnClickListener(new View.OnClickListener() {
            public void onClick(View view) {

                //实例化SharedPreferences对象
                SharedPreferences sharedPreferences = getSharedPreferences("test", Activity.MODE_PRIVATE);

                //使用getString方法获得value，
                String name = sharedPreferences.getString("name", "");
                String nickname = sharedPreferences.getString("nickname", "");
                boolean sex = sharedPreferences.getBoolean("sex", false);
                int age = sharedPreferences.getInt("age", 0);
                float tall = sharedPreferences.getFloat("tall", 0f);
                Set<String> books = sharedPreferences.getStringSet("books", null);

                Log.i("SharedPreferences", String.format("%s,%s,%s,%s,%s,%s", name, nickname, sex, age, tall, books.toString()));

            }
        });

43.1.4. 通过 key 查询数据是否存在

        SharedPreferences sharedPreferences = getSharedPreferences("test", Activity.MODE_PRIVATE);
        if (sharedPreferences.contains("nickname")) {
            Log.i("SharedPreferences", sharedPreferences.getString("nickname", ""));
        }else{
            Log.i("SharedPreferences", "key: nickname 不存在");
        }

43.1.5. 删除数据

    SharedPreferences sharedPreferences = getSharedPreferences("test", Activity.MODE_PRIVATE);
    SharedPreferences.Editor editor = sharedPreferences.edit();

    editor.remove("nickname");
    editor.commit();

    Log.i("SharedPreferences", sharedPreferences.getAll().toString());

43.1.6. 清空数据

    SharedPreferences sharedPreferences = getSharedPreferences("test", Activity.MODE_PRIVATE);
    SharedPreferences.Editor editor = sharedPreferences.edit();
    editor.clear();
    editor.commit();

    Log.i("SharedPreferences", sharedPreferences.getAll().toString());

43.1.7. 对象存储

对象存储，需要将对象序列化，然后反序列化

43.1.8. SharedPreferences 读取物理存储文件

SharedPreferences 的数据存储再一个 xml 文件中，他的地址是：

//<package name>应替换成应用的包名, <name>
File xmlFile = new File("/data/data/<package name>/shared_prefs/<name>.xml");

<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
   <string name="name">陈景峰</string>
   <string name="nickname">netkiller</string>
   <int name="age" value="30" />
</map>


43.2. SD Card

43.2.1. SD Card 状态

    if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
                    TextView textView = (TextView) findViewById(R.id.textView);
                    textView.setText("SD 卡不存在，请插入 SD 卡！");
                }


第 44 章 相机与相册

目录

44.1. manifest 文件
44.2. layout
44.3. Activity
44.4. LED flash 做手电筒

44.1. manifest 文件

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="cn.netkiller.camera">

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <provider
            android:name="android.support.v4.content.FileProvider"
            android:authorities="cn.netkiller.camera.provider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/provider_paths" />
        </provider>
    </application>

</manifest>

provider_paths.xml 文件

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-path name="external_files" path="."/>
</paths>


44.2. layout

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/buttonOpenCamera"
        android:layout_width="160dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="8dp"
        android:layout_marginBottom="8dp"
        android:text="拍照立即显示"
        app:layout_constraintBottom_toTopOf="@+id/buttonAlbum"
        app:layout_constraintEnd_toStartOf="@+id/buttonSavePhoto"
        app:layout_constraintHorizontal_bias="0.283"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/imageViewPicture"
        app:layout_constraintVertical_bias="0.0" />

    <Button
        android:id="@+id/buttonSavePhoto"
        android:layout_width="160dp"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="20dp"
        android:layout_marginBottom="8dp"
        android:text="拍照存储后显示"
        app:layout_constraintBottom_toTopOf="@+id/buttonAlbum"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/imageViewPicture"
        app:layout_constraintVertical_bias="0.0" />

    <ImageView
        android:id="@+id/imageViewPicture"
        android:layout_width="338dp"
        android:layout_height="366dp"
        android:layout_centerHorizontal="true"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="8dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textView" />

    <Button
        android:id="@+id/buttonAlbum"
        android:layout_width="352dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginEnd="8dp"
        android:layout_marginBottom="8dp"
        android:text="Album"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" />

</android.support.constraint.ConstraintLayout>


44.3. Activity

package cn.netkiller.camera;

import android.Manifest;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Environment;
import android.os.StrictMode;
import android.provider.MediaStore;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.FileProvider;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private ImageView imageViewPicture;
    private Button buttonOpenCamera;
    private Button buttonSavePhoto;
    private Button buttonAlbum;

    private static int REQUEST_CAMERA = 1;
    private static int REQUEST_PHOTO = 2;
    private static int REQUEST_ALBUM = 3;
    private File imageFile;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        imageViewPicture = (ImageView) findViewById(R.id.imageViewPicture);
        buttonOpenCamera = (Button) findViewById(R.id.buttonOpenCamera);
        buttonSavePhoto = (Button) findViewById(R.id.buttonSavePhoto);
        buttonAlbum = (Button) findViewById(R.id.buttonAlbum);

        buttonOpenCamera.setOnClickListener(this);
        buttonSavePhoto.setOnClickListener(this);
        buttonAlbum.setOnClickListener(this);

        StrictMode.VmPolicy.Builder newbuilder = new StrictMode.VmPolicy.Builder();
        StrictMode.setVmPolicy(newbuilder.build());

    }

    @Override
    public void onClick(View view) {
        Intent intent;
        switch (view.getId()) {
            case R.id.buttonOpenCamera:
                // 拍照并显示图片
                intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);// 启动系统相机
                startActivityForResult(intent, REQUEST_CAMERA);
                break;
            case R.id.buttonSavePhoto:

                if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
                    TextView textView = (TextView) findViewById(R.id.textView);
                    textView.setText("SD 卡不存在，请插入 SD 卡！");
                }

                if (ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
                    ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
                    return;
                } else {

                    String dir = Environment.getExternalStorageDirectory().getPath();
                    new File(dir).mkdirs();

                    imageFile = new File(dir, "tmp.png");

                    Uri imageUri = FileProvider.getUriForFile(MainActivity.this, "cn.netkiller.camera.provider", imageFile);
                    Log.d("album", imageFile.getPath());

                    intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);// 启动系统相机
                    intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
                    startActivityForResult(intent, REQUEST_PHOTO);

                }

                break;
            case R.id.buttonAlbum:

                intent = new Intent(Intent.ACTION_GET_CONTENT);
                intent.setType("image/*");
                startActivityForResult(Intent.createChooser(intent, "Select Picture"), 3);

                break;
            default:
                break;
        }
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (resultCode == RESULT_OK) { // 如果返回数据
            if (requestCode == REQUEST_CAMERA) { // 判断请求码是否为REQUEST_CAMERA,如果是代表是这个页面传过去的，需要进行获取
                Bundle bundle = data.getExtras(); // 从data中取出传递回来缩略图的信息，图片质量差，适合传递小图片
                Bitmap bitmap = (Bitmap) bundle.get("data"); // 将data中的信息流解析为Bitmap类型
                imageViewPicture.setImageBitmap(bitmap);// 显示图片
            } else if (requestCode == REQUEST_PHOTO) {
                Log.i("photo", imageFile.getPath());
                try {
//                    InputStream inputStream = getContentResolver().openInputStream(imageUri);
                    String dir = Environment.getExternalStorageDirectory().getPath();
                    FileInputStream fileInputStream = new FileInputStream(imageFile);
                    Bitmap bitmap = BitmapFactory.decodeStream(fileInputStream);
                    fileInputStream.close();
                    imageViewPicture.setImageBitmap(bitmap);// 显示图片
                } catch (Exception e) {
                    e.printStackTrace();
                }
            } else if (requestCode == REQUEST_ALBUM) {

                Bitmap bitmap = null;

                //外界的程序访问ContentProvider所提供数据 可以通过ContentResolver接口
                ContentResolver resolver = getContentResolver();
                Uri originalUri = data.getData();        //获得图片的uri

                try {
                    bitmap = MediaStore.Images.Media.getBitmap(resolver, originalUri);        //显得到bitmap图片
                } catch (IOException e) {
                    e.printStackTrace();
                }

                //获取图片的路径：
                Log.i("album", String.valueOf(originalUri));

                imageViewPicture.setImageBitmap(bitmap);
            }
        }
    }

}


44.4. LED flash 做手电筒

<uses-permission android:name="android.permission.FLASHLIGHT" />

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".FlashLightActivity">

    <Button
        android:id="@+id/buttonLight"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="8dp"
        android:layout_marginBottom="8dp"
        android:text="On"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>

package cn.netkiller.camera;

import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.pm.PackageManager;
import android.hardware.Camera;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraManager;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

import java.security.Policy;

public class FlashLightActivity extends AppCompatActivity {

    private Button buttonLight;
    private CameraManager cameraManager;
    private String cameraId;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_flash_light);

        buttonLight = (Button) findViewById(R.id.buttonLight);

        Boolean isFlashAvailable = getApplicationContext().getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH);

        if (!isFlashAvailable) {

            AlertDialog alert = new AlertDialog.Builder(FlashLightActivity.this).create();
            alert.setTitle("Error");
            alert.setMessage("Your device doesn't support flash light!");
            alert.setButton(DialogInterface.BUTTON_POSITIVE, "OK", new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int which) {
                    // closing the application
                    finish();
                    System.exit(0);
                }
            });
            alert.show();
            return;
        }

        cameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
        try {
            cameraId = cameraManager.getCameraIdList()[0];
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }

        buttonLight.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                try {
                    light();
                } catch (CameraAccessException e) {
                    e.printStackTrace();
                }

            }
        });
    }

    public void light() throws CameraAccessException {

        if (buttonLight.getText().equals("On")) {
            Toast.makeText(getApplicationContext(), "打开手电筒", Toast.LENGTH_SHORT).show();
            cameraManager.setTorchMode(cameraId, true);
            buttonLight.setText("Off");
        } else {
            Toast.makeText(getApplicationContext(), "关闭手电筒", Toast.LENGTH_SHORT).show();
            cameraManager.setTorchMode(cameraId, false);
            buttonLight.setText("On");
        }
    }
}


第 45 章 麦克风与录音

目录

45.1. 开启麦克风和SD卡权限
45.2. layout
45.3. Activity

45.1. 开启麦克风和SD卡权限

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="cn.netkiller.voice">

    <uses-permission android:name="android.permission.RECORD_AUDIO" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>


45.2. layout

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/status"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="http://www.netkiller.cn"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/record"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginBottom="1dp"
        android:onClick="onClick"
        android:text="Record"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent" />

    <Button
        android:id="@+id/play"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginEnd="8dp"
        android:layout_marginBottom="7dp"
        android:text="Play"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent" />

</android.support.constraint.ConstraintLayout>


45.3. Activity

package cn.netkiller.voice;

import android.Manifest;
import android.content.pm.PackageManager;
import android.media.MediaPlayer;
import android.media.MediaRecorder;
import android.os.Environment;
import android.support.v4.app.ActivityCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private static final int RECORD_AUDIO = 10;
    private Button record;
    private Button play;
    private MediaRecorder mediaRecorder;
    private TextView status;

    private MediaPlayer mediaPlayer;
    private String filename;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        status = (TextView) findViewById(R.id.status);
        record = (Button) findViewById(R.id.record);
        play = (Button) findViewById(R.id.play);

        record.setOnClickListener(this);
        play.setOnClickListener(this);

        if (ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED || ActivityCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.RECORD_AUDIO}, RECORD_AUDIO);
        }

    }

    @Override
    public void onClick(View v) {

        switch (v.getId()) {
            case R.id.record:
                if (record.getText().equals("Record")) {
                    this.start();
                } else {
                    this.stop();
                }
                break;
            case R.id.play:
                play();
                status.setText("播放录音");
                break;
        }
    }

    private void start() {

        record.setText("Stop");
        status.setText("开启录音，请对准话筒讲话");

        mediaRecorder = new MediaRecorder();
        mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
        mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
        mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);

        String dir = Environment.getExternalStorageDirectory().getPath();
        String date = new SimpleDateFormat("yyyy-MM-ddhhmmss").format(new Date());

        filename = String.format("%s/%s.3gp", dir, date);

        Log.e("Voice", "voice path " + filename);

        mediaRecorder.setOutputFile(filename);

        try {
            mediaRecorder.prepare();
        } catch (IOException e) {
            e.printStackTrace();
        }
        mediaRecorder.start();

    }

    private void stop() {
        record.setText("Record");
        status.setText("录音停止");

        if (mediaRecorder != null) {
            mediaRecorder.stop();
            mediaRecorder.release();
            mediaRecorder = null;
        }
    }

    private void play() {
        this.stop();
        if (filename == null) {
            Toast.makeText(getApplicationContext(), "请先录音", Toast.LENGTH_SHORT).show();
            return;
        }
        try {

            if (mediaPlayer != null && mediaPlayer.isPlaying()) {
                mediaPlayer.reset();
            } else {
                mediaPlayer = new MediaPlayer();
                mediaPlayer.setDataSource(filename);
                mediaPlayer.prepare();
                mediaPlayer.start();
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (mediaPlayer != null) {
            mediaPlayer.stop();
            mediaPlayer.release();
        }
    }

}


第 46 章 多媒体开发

目录

46.1. MediaPlayer
46.2. VideoView 开发
46.2.1. 播放网络视频
46.2.2. MediaController 添加翻页事件
46.2.3. 静音播放视频
46.2.4. 更新进度条
46.2.5. 完整的例子
46.3. SurfaceView
46.4. Vitamio

46.1. MediaPlayer

播放一段特效声音，例如铃音，可以在点击按钮德时候调用 playSound()

    private void playSound(){

        MediaPlayer mediaPlayer = MediaPlayer.create(FlashLightActivity.this, R.raw.alert);
        mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {

            @Override
            public void onCompletion(MediaPlayer mp) {
                // TODO Auto-generated method stub
                mediaPlayer.release();
            }
        });
        mediaPlayer.start();
    }


46.2. VideoView 开发

VideoView，用于播放一段视频媒体，它继承了SurfaceView，位于"android.widget.VideoView"，是一个视频控件。

VideoView也为开发人员提供了对应的方法，这里简单介绍一些常用的：

int getCurrentPosition()：获取当前播放的位置。
int getDuration()：获取当前播放视频的总长度。
isPlaying()：当前VideoView是否在播放视频。
void pause()：暂停
void seekTo(int msec)：从第几毫秒开始播放。
void resume()：重新播放。
void setVideoPath(String path)：以文件路径的方式设置VideoView播放的视频源。
void setVideoURI(Uri uri)：以Uri的方式设置VideoView播放的视频源，可以是网络Uri或本地Uri。
void start()：开始播放。
void stopPlayback()：停止播放。
setMediaController(MediaController controller)：设置MediaController控制器。
setOnCompletionListener(MediaPlayer.onCompletionListener l)：监听播放完成的事件。
setOnErrorListener(MediaPlayer.OnErrorListener l)：监听播放发生错误时候的事件。
setOnPreparedListener(MediaPlayer.OnPreparedListener l)：：监听视频装载完成的事件。

上面的一些方法通过方法名就可以了解用途。和MediaPlayer配合SurfaceView播放视频不同，VideoView播放之前无需编码装载视频，它会在start()开始播放的时候自动装载视频。并且VideoView在使用完之后，无需编码回收资源。

46.2.1. 播放网络视频

加入 android.permission.INTERNET 允许访问网络

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="cn.netkiller.video">

    <uses-permission android:name="android.permission.INTERNET" />
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name=".VideoViewActivity"></activity>
    </application>

</manifest>

最简洁的布局，只有一个 VideoView

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".VideoViewActivity">

    <VideoView
        android:id="@+id/videoView"
        android:layout_width="match_parent"
        android:layout_height="236dp"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="8dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</android.support.constraint.ConstraintLayout>

播放的文件来自 IPFS 星际文件系统

package cn.netkiller.video;

import android.net.Uri;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.MediaController;
import android.widget.VideoView;

public class VideoViewActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_video_view);

        Uri uri = Uri.parse("http://ipfs.netkiller.cn/ipfs/QmcA1Fsrt6jGTVqAUNZBqaprMEdFaFkmkzA5s2M6mF85UC");
        VideoView videoView = (VideoView) this.findViewById(R.id.videoView);
        videoView.setMediaController(new MediaController(this));
        videoView.setVideoURI(uri);
        videoView.start();
        videoView.requestFocus();

    }
}

运行程序开始播放视频，点击视频会在屏幕下方弹出 MediaController 控制条

46.2.2. MediaController 添加翻页事件

package cn.netkiller.video;

import android.net.Uri;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.MediaController;
import android.widget.Toast;
import android.widget.VideoView;

public class VideoViewActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_video_view);

        final Uri uri = Uri.parse("http://ipfs.netkiller.cn/ipfs/QmcA1Fsrt6jGTVqAUNZBqaprMEdFaFkmkzA5s2M6mF85UC");
        final VideoView videoView = (VideoView) this.findViewById(R.id.videoView);
        MediaController mediaController = new MediaController(this);
        mediaController.setMediaPlayer(videoView);

        mediaController.setPrevNextListeners(
                new View.OnClickListener() {

                    @Override
                    public void onClick(View v) {
                        Toast.makeText(VideoViewActivity.this, "下一曲", Toast.LENGTH_SHORT).show();
                        videoView.setVideoURI(Uri.parse("http://ipfs.netkiller.cn/ipfs/QmUaDftnPB7zCTwTASnSAWLiXWd1L5vNGEeU585rddfVTh"));
                    }
                },
                new View.OnClickListener() {

                    @Override
                    public void onClick(View v) {
                        Toast.makeText(VideoViewActivity.this, "上一曲", Toast.LENGTH_SHORT).show();
                        videoView.setVideoURI(Uri.parse("http://ipfs.netkiller.cn/ipfs/QmbvKvj9X368WMtmkLYFuf59gSwLXYDLcdJuSiSHKPhTG4"));
                    }
                });

        videoView.setMediaController(mediaController);
        videoView.setVideoURI(uri);
        videoView.start();
        videoView.requestFocus();

    }
}

46.2.3. 静音播放视频

        videoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {

            @Override
            public void onPrepared(MediaPlayer mediaPlayer) {
                mediaPlayer.setVolume(0f, 0f);
            }
        });

46.2.4. 更新进度条

            new Thread() {
                @Override
                public void run() {
                    try {
                        while (videoView.isPlaying()) {
                            // 如果正在播放，没0.5.毫秒更新一次进度条
                            int current = videoView.getCurrentPosition();
                            seekBar.setProgress(current);
                            sleep(500);
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }.start();

46.2.5. 完整的例子

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".VideoViewActivity">

    <VideoView
        android:id="@+id/videoView"
        android:layout_width="match_parent"
        android:layout_height="236dp"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="8dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <LinearLayout
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginEnd="8dp"
        android:layout_marginBottom="8dp"
        android:orientation="vertical"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal">

            <TextView
                android:id="@+id/textViewTime"
                android:layout_width="wrap_content"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:text="00:00" />

            <SeekBar
                android:id="@+id/seekBar"
                android:layout_width="270dp"
                android:layout_height="wrap_content" />

            <TextView
                android:id="@+id/textViewCurrentPosition"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:text="00:00" />
        </LinearLayout>

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal">

            <Button
                android:id="@+id/buttonPlay"
                android:layout_width="183dp"
                android:layout_height="wrap_content"
                android:text="播放" />

            <Button
                android:id="@+id/buttonStop"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:text="停止" />

        </LinearLayout>

    </LinearLayout>

    <TextView
        android:id="@+id/textViewStatus"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="8dp"
        android:text="        "
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/videoView" />
</android.support.constraint.ConstraintLayout>

package cn.netkiller.video;

import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Handler;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.SeekBar;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.VideoView;

import java.text.SimpleDateFormat;
import java.util.Calendar;

public class VideoViewActivity extends AppCompatActivity implements View.OnClickListener {

    private VideoView videoView;
    private SeekBar seekBar;
    private Button buttonPlay;
    private TextView textViewTime;
    private TextView textViewCurrentPosition;
    private TextView textViewStatus;

    private Handler handler = new Handler();
    private Runnable runnable = new Runnable() {
        public void run() {
            if (videoView.isPlaying()) {
                int current = videoView.getCurrentPosition();
                seekBar.setProgress(current);
                textViewCurrentPosition.setText(time(videoView.getCurrentPosition()));
            }
            handler.postDelayed(runnable, 500);
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_video_view);

        final Uri uri = Uri.parse("http://ipfs.netkiller.cn/ipfs/QmcA1Fsrt6jGTVqAUNZBqaprMEdFaFkmkzA5s2M6mF85UC");
        videoView = (VideoView) this.findViewById(R.id.videoView);
        videoView.setVideoURI(uri);
        videoView.requestFocus();

        videoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {

            @Override
            public void onPrepared(MediaPlayer mediaPlayer) {
                textViewTime.setText(time(videoView.getDuration()));
                textViewStatus.setText("视频加载完毕");
                buttonPlay.setEnabled(true);

            }
        });

        // 在播放完毕被回调
        videoView.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
            @Override
            public void onCompletion(MediaPlayer mp) {
                Toast.makeText(VideoViewActivity.this, "播放完成", Toast.LENGTH_SHORT).show();
            }
        });

        videoView.setOnErrorListener(new MediaPlayer.OnErrorListener() {

            @Override
            public boolean onError(MediaPlayer mp, int what, int extra) {
                // 发生错误重新播放
                play();
                Toast.makeText(VideoViewActivity.this, "播放出错", Toast.LENGTH_SHORT).show();
                return false;
            }
        });

        textViewStatus = (TextView) findViewById(R.id.textViewStatus);
        textViewStatus.setText("玩命加载中");

        textViewTime = (TextView) findViewById(R.id.textViewTime);

        seekBar = (SeekBar) findViewById(R.id.seekBar);
        // 为进度条添加进度更改事件
        seekBar.setOnSeekBarChangeListener(onSeekBarChangeListener);

        textViewCurrentPosition = (TextView) findViewById(R.id.textViewCurrentPosition);

        buttonPlay = (Button) findViewById(R.id.buttonPlay);
        buttonPlay.setEnabled(false);
        final Button buttonStop = (Button) findViewById(R.id.buttonStop);

        buttonPlay.setOnClickListener(this);
        buttonStop.setOnClickListener(this);

    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.buttonPlay:
                play();
                break;
            case R.id.buttonStop:
                stop();
                break;
            default:
                break;
        }
    }

    private SeekBar.OnSeekBarChangeListener onSeekBarChangeListener = new SeekBar.OnSeekBarChangeListener() {
        // 当进度条停止修改的时候触发
        @Override
        public void onStopTrackingTouch(SeekBar seekBar) {
            // 取得当前进度条的刻度
            int progress = seekBar.getProgress();
            if (videoView.isPlaying()) {
                // 设置当前播放的位置
                videoView.seekTo(progress);
            }
        }

        @Override
        public void onStartTrackingTouch(SeekBar seekBar) {

        }

        @Override
        public void onProgressChanged(SeekBar seekBar, int progress,
                                      boolean fromUser) {

        }
    };

    protected void play() {

        if (buttonPlay.getText().equals("播放")) {
            buttonPlay.setText("暂停");
            textViewStatus.setText("请您欣赏");
            // 开始线程，更新进度条的刻度
            handler.postDelayed(runnable, 0);
            videoView.start();
            seekBar.setMax(videoView.getDuration());

        } else

        {
            buttonPlay.setText("播放");
            if (videoView.isPlaying()) {
                videoView.pause();
            }
        }

    }

    protected void stop() {
        if (videoView.isPlaying()) {
            videoView.stopPlayback();
        }
    }

    protected String time(long millionSeconds) {

        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("mm:ss");
        Calendar c = Calendar.getInstance();
        c.setTimeInMillis(millionSeconds);
        return simpleDateFormat.format(c.getTime());
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        handler.removeCallbacks(runnable);
    }

}


46.3. SurfaceView


46.4. Vitamio


第 47 章 定位

目录

47.1. manifest 权限配置
47.2. layout
47.3. Activity

47.1. manifest 权限配置

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="cn.netkiller.location">

    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
    <uses-permission android:name="android.permission.INTERNET" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>


47.2. layout

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TableLayout
        android:id="@+id/tableLayout"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="8dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <TableRow
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="状态: " />

            <TextView
                android:id="@+id/status"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="TextView" />
        </TableRow>

        <TableRow
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="经度: " />

            <TextView
                android:id="@+id/textViewLatitude"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="TextView" />
        </TableRow>

        <TableRow
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="纬度: " />

            <TextView
                android:id="@+id/textViewLongitude"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="TextView" />
        </TableRow>

        <TableRow
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="海拔: " />

            <TextView
                android:id="@+id/textViewAltitude"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="TextView" />
        </TableRow>

        <TableRow
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <TextView
                android:id="@+id/textView4"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="速度" />

            <TextView
                android:id="@+id/textViewSpeed"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="TextView" />
        </TableRow>

        <TableRow
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="时间: " />

            <TextView
                android:id="@+id/textViewTime"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="TextView" />
        </TableRow>
    </TableLayout>

    <ListView
        android:id="@+id/listViewAddress"
        android:layout_width="368dp"
        android:layout_height="358dp"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="8dp"
        android:layout_marginBottom="8dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/tableLayout" />
</android.support.constraint.ConstraintLayout>


47.3. Activity

package cn.netkiller.location;

import android.Manifest;
import android.content.pm.PackageManager;
import android.location.Address;
import android.location.Criteria;
import android.location.Geocoder;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.location.LocationProvider;
import android.support.v4.app.ActivityCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;

import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;

public class MainActivity extends AppCompatActivity {

    private static final int REQUEST_PERMISSION_CODE = 12;
    private TextView textViewLatitude;
    private TextView textViewLongitude;
    private TextView textViewAltitude;
    private TextView textViewSpeed;
    private TextView textViewTime;
    private TextView status;

    private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    private ListView listViewAddress;
    private ArrayAdapter<String> adapter;
    private ArrayList<String> list;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        textViewLatitude = (TextView) findViewById(R.id.textViewLatitude);
        textViewLongitude = (TextView) findViewById(R.id.textViewLongitude);
        textViewAltitude = (TextView) findViewById(R.id.textViewAltitude);
        textViewSpeed = (TextView) findViewById(R.id.textViewSpeed);
        textViewTime = (TextView) findViewById(R.id.textViewTime);
        status = (TextView) findViewById(R.id.status);

        list = new ArrayList<String>();
        adapter = new ArrayAdapter<String>(MainActivity.this, android.R.layout.simple_list_item_1, list);
        listViewAddress = (ListView) findViewById(R.id.listViewAddress);
        listViewAddress.setAdapter(adapter);

        this.location();
    }

    private void loop() {

    }

    public void location() {

        //获取LocationManager对象
        LocationManager locationManager = (LocationManager) getSystemService(LOCATION_SERVICE);

        boolean gpsStatus = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
        Log.d("Location", "GPS Status: " + gpsStatus);

        boolean networkStatus = locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER);
        Log.d("Location", "Network Status: " + networkStatus);

        //创建一个Criteria对象
        Criteria criteria = new Criteria();
        //设置粗略精确度
        criteria.setAccuracy(Criteria.ACCURACY_COARSE);
        //设置是否需要返回海拔信息
        criteria.setAltitudeRequired(true);
        //设置是否需要返回方位信息
        criteria.setBearingRequired(true);
        //设置是否允许付费服务
        criteria.setCostAllowed(false);
        //设置电量消耗等级
        criteria.setPowerRequirement(Criteria.POWER_HIGH);
        //设置是否需要返回速度信息
        criteria.setSpeedRequired(true);

        Log.d("Location", "Criteria: " + criteria.toString());

        //获取最符合此标准的provider对象
//        String currentProvider = locationManager.getProvider(LocationManager.GPS_PROVIDER).getName();

        //根据设置的Criteria对象，获取最符合此标准的provider对象
        String currentProvider = locationManager.getBestProvider(criteria, true);

        Log.d("Location", "currentProvider: " + currentProvider);
        status.setText(currentProvider);

        if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION}, REQUEST_PERMISSION_CODE);
            return;
        } else {
            status.setText("正在获取GPS坐标请稍候...");
        }

        locationManager.requestLocationUpdates(currentProvider, 0, 0, locationListener);
        //根据当前provider对象获取最后一次位置信息
        Location location = locationManager.getLastKnownLocation(currentProvider);

        //如果位置信息不为null，则请求更新位置信息
        if (location != null) {

            textViewLatitude.setText(location.getLatitude() + "");
            textViewLongitude.setText(location.getLongitude() + "");
            textViewAltitude.setText(location.getAltitude() + "");
            textViewSpeed.setText(location.getSpeed() + "");
            textViewTime.setText(location.getTime() + "");

            Log.d("Location", "Latitude: " + location.getLatitude());
            Log.d("Location", "location: " + location.getLongitude());

        } else {

            Log.d("Location", "Latitude: " + 0);
            Log.d("Location", "location: " + 0);

        }

    }

    //创建位置监听器
    private LocationListener locationListener = new LocationListener() {

        //位置发生改变时调用
        @Override
        public void onLocationChanged(Location location) {
            status.setText("onLocationChanged");

            //位置信息变化时触发
            Log.e("Location", "定位方式：" + location.getProvider());
            Log.e("Location", "纬度：" + location.getLatitude());
            Log.e("Location", "经度：" + location.getLongitude());
            Log.e("Location", "海拔：" + location.getAltitude());
            Log.e("Location", "时间：" + location.getTime());

            textViewLatitude.setText(location.getLatitude() + "");
            textViewLongitude.setText(location.getLongitude() + "");
            textViewAltitude.setText(location.getAltitude() + "");
            textViewSpeed.setText(location.getSpeed() + "");
            textViewTime.setText(dateFormat.format(new Date(location.getTime())) + "");

            //解析地址
            Geocoder geoCoder = new Geocoder(MainActivity.this, Locale.getDefault());

            double latitude = location.getLatitude();
            double longitude = location.getLongitude();

            List<Address> locationList = null;
            try {
                locationList = geoCoder.getFromLocation(latitude, longitude, 5);
            } catch (IOException e) {
                e.printStackTrace();
            }

//            Address address = locationList.get(0);//得到Address实例第一个地址
//            status.setText(address.toString());
//            String countryName = address.getCountryName();//得到国家名称，比如：中国
//            String locality = address.getLocality();//得到城市名称，比如：北京市

            list.clear();

            for (Address address : locationList) {

                for (int n = 0; address.getAddressLine(n) != null; n++) {
                    String addressLine = address.getAddressLine(n);//得到周边信息，包括街道等，i=0，得到街道名称
                    list.add(addressLine);
                    Log.i("Location", "addressLine = " + addressLine);
                    Log.d("Location", address.getCountryName() + address.getAdminArea() + address.getFeatureName());
                }
            }

            adapter.notifyDataSetChanged();

        }

        //provider失效时调用
        @Override
        public void onProviderDisabled(String provider) {

            Log.d("Location", "onProviderDisabled");
            status.setText("onProviderDisabled");

        }

        //provider启用时调用
        @Override
        public void onProviderEnabled(String provider) {

            Log.d("Location", "onProviderEnabled");
            status.setText("onProviderEnabled");

        }

        //状态改变时调用
        @Override
        public void onStatusChanged(String provider, int status, Bundle extras) {

            Log.d("Location", "onStatusChanged");
            //GPS状态变化时触发
            switch (status) {
                case LocationProvider.AVAILABLE:
                    Log.e("Location", "当前GPS状态为可见状态");
                    break;
                case LocationProvider.OUT_OF_SERVICE:
                    Log.e("Location", "当前GPS状态为服务区外状态");
                    break;
                case LocationProvider.TEMPORARILY_UNAVAILABLE:
                    Log.e("Location", "当前GPS状态为暂停服务状态");
                    break;
            }

        }

    };

    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);

        switch (requestCode) {
            case REQUEST_PERMISSION_CODE: {
                // If request is cancelled, the result arrays are empty.
                if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {

                } else {
                    // permission denied, boo! Disable the
                    // functionality that depends on this permission.
                }
                return;
            }

        }
    }

}


第 48 章 电话

目录

48.1. SIM 卡状态
48.2. 通信录与拨打电话
48.3.

48.1. SIM 卡状态

        TelephonyManager telephonyManager = (TelephonyManager)context.getSystemService(context.TELEPHONY_SERVICE);

        if(telephonyManager.getSimState() == TelephonyManager.SIM_STATE_READY){
            return true;
        }else{
            return false;
        }


48.2. 通信录与拨打电话

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="cn.netkiller.contacts">

    <uses-permission android:name="android.permission.READ_CONTACTS"/>
    <uses-permission android:name="android.permission.CALL_PHONE"/>

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/textView2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="通信录与拨打电话"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/contact"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginTop="60dp"
        android:layout_marginEnd="8dp"
        android:text="联系人"
        app:layout_constraintEnd_toStartOf="@+id/call"
        app:layout_constraintHorizontal_bias="0.478"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/phone" />

    <EditText
        android:id="@+id/phone"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginTop="64dp"
        android:layout_marginEnd="8dp"
        android:ems="10"
        android:inputType="phone"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.475"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textView2" />

    <Button
        android:id="@+id/call"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="60dp"
        android:layout_marginEnd="52dp"
        android:text="Call"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/phone" />

</android.support.constraint.ConstraintLayout>

package cn.netkiller.contacts;

import android.app.Activity;
import android.content.ContentResolver;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.net.Uri;
import android.provider.ContactsContract;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private EditText phone;
    private Button contact;
    private Button call;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        contact = (Button) findViewById(R.id.contact);
        contact.setOnClickListener(this);

        call = (Button) findViewById(R.id.call);
        call.setOnClickListener(this);

        phone = (EditText) findViewById(R.id.phone);
        phone.setText("", TextView.BufferType.EDITABLE);

        if (ActivityCompat.checkSelfPermission(this, android.Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions((Activity) this, new String[]{android.Manifest.permission.READ_CONTACTS}, 1);
        }
        if (ActivityCompat.checkSelfPermission(this, android.Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions((Activity) this, new String[]{android.Manifest.permission.CALL_PHONE}, 1);
        }

    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.contact:
                Toast.makeText(MainActivity.this, "获取联系人手机号码", Toast.LENGTH_SHORT).show();
                startActivityForResult(new Intent(Intent.ACTION_PICK, ContactsContract.Contacts.CONTENT_URI), 0);
                break;
            case R.id.call:
                Toast.makeText(MainActivity.this, "打电话", Toast.LENGTH_SHORT).show();

                Intent intent = new Intent();
                intent.setAction(Intent.ACTION_CALL);//ACTION_DIAL
                intent.setData(Uri.parse("tel:" + phone.getText()));
                startActivity(intent);

                break;
        }

    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (resultCode == Activity.RESULT_OK) {
            Uri contactData = data.getData();
            Cursor cursor = getContentResolver().query(contactData, null, null, null, null);
            cursor.moveToFirst();

            //条件为联系人ID
            String contactId = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts._ID));
            //获得DATA表中的电话号码，条件为联系人ID,因为手机号码可能会有多个
            Cursor contact = getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, ContactsContract.CommonDataKinds.Phone.CONTACT_ID + "=" + contactId, null, null);
            while (contact.moveToNext()) {
                String contactNumber = contact.getString(contact.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));

                phone.setText(contactNumber);
            }
            cursor.close();

        }
    }

}


48.3.

<uses-permission android:name="android.permission.SEND_SMS" />

    private void sendSMS(String phoneNumber,String message){
        if(PhoneNumberUtils.isGlobalPhoneNumber(phoneNumber)){
            Intent intent = new Intent(Intent.ACTION_SENDTO, Uri.parse("smsto:"+phoneNumber));
            intent.putExtra("sms_body", message);
            startActivity(intent);
        }
    }

    public void sendSMS(String phoneNumber,String message){
        android.telephony.SmsManager smsManager = android.telephony.SmsManager.getDefault();
        //拆分短信内容，手机短信长度有限制
        List<String> divideContents = smsManager.divideMessage(message);
        for (String text : divideContents) {
            smsManager.sendTextMessage(phoneNumber, null, text, sentPI, deliverPI);
        }
    }


第 49 章 消息广播

目录

49.1. 动态注册
49.2. 静态注册
49.3. 自定义用户消息广播
49.4. 本地广播

安卓中有两种广播，一种是系统发出的广播信息，例如网络改变，电池的电量低等等，另一种是用户发出的广播信息。

Android 中的广播类型可以分为两种类型，标准广播和有序广播。

标准广播（Normal broadcasts）：标准广播是一种完全异步执行的广播，在广播发出之后，所有的广播接收器几乎会在同一时刻接收到这条广播消息。这种广播效率比较高，但同时也意味着它是无法被截断的。

有序广播（Ordered broadcasts）：有序广播则是一种同步执行的广播，在广播发出之后，同一时刻只会有一个广播接收器能够收到这条广播消息，当这个广播接收器中的逻辑执行完毕之后，广播才会继续传递。

android.intent.action.BATTERY_CHANGED	持久广播含充电状态，级别，以及其他相关的电池信息。
android.intent.action.BATTERY_LOW		显示设备的电池电量低。
android.intent.action.BATTERY_OKAY		指示电池正在低点后但没有问题。
android.intent.action.BOOT_COMPLETED	一次播出后，系统已完成启动。
android.intent.action.BUG_REPORT		显示活动报告的错误。
android.intent.action.CALL				执行呼叫由数据指定某人。
android.intent.action.CALL_BUTTON		用户按下“呼叫”按钮进入拨号器或其他适当的用户界面发出呼叫。
android.intent.action.DATE_CHANGED		日期改变。
android.intent.action.REBOOT			有设备重启。

49.1. 动态注册

package cn.netkiller.broadcast;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {

    private IntentFilter intentFilter;
    private MyBroadcastReceiver myBroadcastReceiver;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        intentFilter = new IntentFilter();
        //为过滤器添加处理规则
        intentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE");
        myBroadcastReceiver = new MyBroadcastReceiver();
        //注册广播接收器
        registerReceiver(myBroadcastReceiver, intentFilter);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        //注销动态的广播接收器
        unregisterReceiver(myBroadcastReceiver);
    }

    //自定义内部类，继承 BroadcastReceiver
    public class MyBroadcastReceiver extends BroadcastReceiver {

        @Override
        public void onReceive(Context context, Intent intent) {
            Toast.makeText(context, "网络状态已改变", Toast.LENGTH_SHORT).show();
        }
    }
}

现在尝试改变网络状态，例如开启或关闭飞行模式，程序会弹出 "网络状态已改变"。

我的测试环境是 Android 9 Pie 没有加入下面的权限仍然能工作

<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

优化程序

package cn.netkiller.broadcast;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.BatteryManager;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {

    private IntentFilter intentFilter;
    private MyBroadcastReceiver myBroadcastReceiver;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        intentFilter = new IntentFilter();
        //为过滤器添加处理规则
        intentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE");
        intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
        intentFilter.addAction(Intent.ACTION_BATTERY_CHANGED);
        intentFilter.addAction(Intent.ACTION_BATTERY_LOW);

        myBroadcastReceiver = new MyBroadcastReceiver();
        //注册广播接收器
        registerReceiver(myBroadcastReceiver, intentFilter);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        //注销动态的广播接收器
        unregisterReceiver(myBroadcastReceiver);
    }

    //自定义内部类，继承 BroadcastReceiver
    public class MyBroadcastReceiver extends BroadcastReceiver {

        @Override
        public void onReceive(Context context, Intent intent) {
            ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
            NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
            //判断是否联网
            if (networkInfo != null && networkInfo.isConnected()) {
                Toast.makeText(context, "网络已连接", Toast.LENGTH_SHORT).show();
            } else {
                Toast.makeText(context, "网络不可用", Toast.LENGTH_SHORT).show();
            }

            int status = intent.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
            boolean isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING ||
                    status == BatteryManager.BATTERY_STATUS_FULL;

            if (isCharging) {
                Toast.makeText(context, "正在充电", Toast.LENGTH_SHORT).show();
            } else {
                Toast.makeText(context, "电池已经充满", Toast.LENGTH_SHORT).show();
            }

            int chargePlug = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1);
            boolean usbCharge = chargePlug == BatteryManager.BATTERY_PLUGGED_USB;
            boolean acCharge = chargePlug == BatteryManager.BATTERY_PLUGGED_AC;
            if (usbCharge) {
                Toast.makeText(context, "USB 充电", Toast.LENGTH_SHORT).show();
            }

        }
    }
}


49.2. 静态注册

Android Studio 选择 File - New - Other - Broadcast Receiver

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="cn.netkiller.broadcast">

    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <receiver
            android:name=".MyReceiver"
            android:enabled="true"
            android:exported="true">

            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED" />
            </intent-filter>

        </receiver>
    </application>

</manifest>

MyReceiver 集成 BroadcastReceiver 在 onReceive 中写入你的业务逻辑。

package cn.netkiller.broadcast;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.widget.Toast;

public class MyReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        Toast.makeText(context, "程序已启动，接收到系统启动广播", Toast.LENGTH_SHORT).show();
    }
}

现在重启 Android 模拟器，启动后虽然 App 并没有进入，但是屏幕底部会看到 "程序已启动，接收到系统启动广播"


49.3. 自定义用户消息广播

package cn.netkiller.broadcast;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {

    private IntentFilter intentFilter;
    private TextView textView;
    private MyBroadcastReceiver myBroadcastReceiver;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        intentFilter = new IntentFilter();
        intentFilter.addAction("cn.netkiller.broadcast.MESSAGE");
        myBroadcastReceiver = new MyBroadcastReceiver();
        //注册广播接收器
        registerReceiver(myBroadcastReceiver, intentFilter);

        textView = (TextView) findViewById(R.id.textView);

        Button button = (Button) findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //把要发送的广播值传入Intent对象
                Intent intent = new Intent("cn.netkiller.broadcast.MESSAGE");
                intent.putExtra("msg", "Helloworld");
                //调用Context的 sendBroadcast()方法发送广播
                sendBroadcast(intent);
                textView.setText("Send");

            }
        });
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        //注销动态的广播接收器
        unregisterReceiver(myBroadcastReceiver);
    }

    //自定义内部类，继承 BroadcastReceiver
    public class MyBroadcastReceiver extends BroadcastReceiver {

        @Override
        public void onReceive(Context context, Intent intent) {

            String data = intent.getStringExtra("msg");
            Toast.makeText(context, data, Toast.LENGTH_SHORT).show();

        }
    }
}


49.4. 本地广播

上面讲的系统广播是全局的，任何APP都能接收到你的广播，这样就很容易引起APP的安全性问题。很多时候我们只想接收来自本应用程序发出的广播。

package cn.netkiller.broadcast;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {

    private TextView textView;
    private IntentFilter intentFilter;
    private LocalBroadcastManager localBroadcastManager;
    private MyBroadcastReceiver myBroadcastReceiver;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //获取LocalBroadcastManger 单li实例
        localBroadcastManager = localBroadcastManager.getInstance(this);

        intentFilter = new IntentFilter();
        intentFilter.addAction("cn.netkiller.broadcast.MESSAGE");
        myBroadcastReceiver = new MyBroadcastReceiver();
        //注册本地广播接收器
        localBroadcastManager.registerReceiver(myBroadcastReceiver, intentFilter);

        textView = (TextView) findViewById(R.id.textView);

        Button button = (Button) findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                textView.setText("Send");

                Intent intent = new Intent();
                intent.setAction("cn.netkiller.broadcast.MESSAGE");
                intent.putExtra("msg", "http://www.netkiller.cn");
                //发送本地广播
                localBroadcastManager.sendBroadcast(intent);
            }
        });
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        //注销本地广播接收器
        localBroadcastManager.unregisterReceiver(myBroadcastReceiver);
    }

    //自定义内部类，继承 BroadcastReceiver
    public class MyBroadcastReceiver extends BroadcastReceiver {

        @Override
        public void onReceive(Context context, Intent intent) {

            String data = intent.getStringExtra("msg");
            Toast.makeText(context, data, Toast.LENGTH_SHORT).show();

        }
    }
}


第 50 章 Service

目录

50.1. Service的基本用法
50.1.1. manifest 文件
50.1.2. 创建 Service
50.1.3. Layout 代码
50.1.4. Activity 代码
50.2. Service 中启动线程
50.3. Service和Activity通信
50.3.1. Layout
50.3.2. Service
50.3.3. Activity

50.1. Service的基本用法

50.1.1. manifest 文件

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="cn.netkiller.service">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <service
            android:name=".MyService"
            android:enabled="true"
            android:exported="true"></service>
    </application>

</manifest>

这段代码不是手工加入的，只需在 Android Studio 中选择 File - New - Service - Service 创建 Service 会自动加入下面代码

<service
            android:name=".MyService"
            android:enabled="true"
            android:exported="true"></service>

50.1.2. 创建 Service

在 Android Studio 中选择 File - New - Service - Service 创建 Service

MyService继承自Service，并重写父类的onCreate()、onStartCommand()和onDestroy()方法

package cn.netkiller.service;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;

public class MyService extends Service {
    public MyService() {
    }

    @Override
    public void onCreate() {
        super.onCreate();
        Log.d("Service", "onCreate() executed");
        Log.d("Service", "MyService thread id is " + Thread.currentThread().getId());
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d("Service", "onStartCommand() executed");
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.d("Service", "onDestroy() executed");
    }

    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        throw new UnsupportedOperationException("Not yet implemented");
    }
}

onCreate() Service 创建的时候执行，已经创建的Service不会再执行

onStartCommand() 任何时候，只要执行 startService(intent); 便会执行

onDestroy() 停止的时候执行

50.1.3. Layout 代码

在布局文件中加入了两个按钮，一个用于启动Service，一个用于停止Service

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <LinearLayout
        android:layout_width="368dp"
        android:layout_height="229dp"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="8dp"
        android:layout_marginBottom="8dp"
        android:orientation="vertical"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.0">

        <Button
            android:id="@+id/startService"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Start service" />

        <Button
            android:id="@+id/stopService"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Stop service" />
    </LinearLayout>

</android.support.constraint.ConstraintLayout>

50.1.4. Activity 代码

package cn.netkiller.service;

import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private Button startService;
    private Button stopService;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        startService = (Button) findViewById(R.id.startService);
        stopService = (Button) findViewById(R.id.stopService);
        startService.setOnClickListener(this);
        stopService.setOnClickListener(this);

        Log.d("Service", "MainActivity thread id is " + Thread.currentThread().getId());

    }

    @Override
    public void onClick(View v) {
        Intent intent;
        switch (v.getId()) {
            case R.id.startService:
                intent = new Intent(this, MyService.class);
                startService(intent);
                break;
            case R.id.stopService:
                intent = new Intent(this, MyService.class);
                stopService(intent);
                break;
            default:
                break;
        }
    }
}


50.2. Service 中启动线程

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {

        Log.d("Service", "onStartCommand() begin");
        new Thread(new Runnable() {
            @Override
            public void run() {
                // 开始执行后台任务
                Log.d("Service", "onStartCommand() executed");
            }
        }).start();

        Log.d("Service", "onStartCommand() end");

        return super.onStartCommand(intent, flags, startId);
    }


50.3. Service和Activity通信

50.3.1. Layout

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <LinearLayout
        android:layout_width="368dp"
        android:layout_height="229dp"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="8dp"
        android:layout_marginBottom="8dp"
        android:orientation="vertical"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.0">

        <Button
            android:id="@+id/startService"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Start service" />

        <Button
            android:id="@+id/stopService"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Stop service" />

        <Button
            android:id="@+id/bindService"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Bind Service" />

        <Button
            android:id="@+id/unbindService"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Unbind Service" />

    </LinearLayout>

</android.support.constraint.ConstraintLayout>

50.3.2. Service

package cn.netkiller.service;

import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;

public class MyService extends Service {

    private MyBinder myBinder = new MyBinder();

    public MyService() {
    }

    @Override
    public void onCreate() {
        super.onCreate();
        Log.d("Service", "onCreate() executed");
        Log.d("Service", "MyService thread id is " + Thread.currentThread().getId());
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {

        Log.d("Service", "onStartCommand() begin");
        new Thread(new Runnable() {
            @Override
            public void run() {
                // 开始执行后台任务
                Log.d("Service", "onStartCommand() executed");
            }
        }).start();

        Log.d("Service", "onStartCommand() end");

        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.d("Service", "onDestroy() executed");
    }

    @Override
    public IBinder onBind(Intent intent) {
        return myBinder;
    }

    class MyBinder extends Binder {

        public void startTask() {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    // 执行具体的任务
                    Log.d("Service", "startTask()");
                }
            }).start();
        }

    }
}

50.3.3. Activity

package cn.netkiller.service;

import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private Button startService;
    private Button stopService;
    private Button bindService;
    private Button unbindService;

    private MyService.MyBinder myBinder;

    private ServiceConnection connection = new ServiceConnection() {

        @Override
        public void onServiceDisconnected(ComponentName name) {
        }

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            myBinder = (MyService.MyBinder) service;
            myBinder.startTask();
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        startService = (Button) findViewById(R.id.startService);
        stopService = (Button) findViewById(R.id.stopService);
        startService.setOnClickListener(this);
        stopService.setOnClickListener(this);

        bindService = (Button) findViewById(R.id.bindService);
        unbindService = (Button) findViewById(R.id.unbindService);
        bindService.setOnClickListener(this);
        unbindService.setOnClickListener(this);

        Log.d("Service", "MainActivity thread id is " + Thread.currentThread().getId());

    }

    @Override
    public void onClick(View v) {
        Intent intent;
        switch (v.getId()) {
            case R.id.startService:
                intent = new Intent(this, MyService.class);
                startService(intent);
                break;
            case R.id.stopService:
                intent = new Intent(this, MyService.class);
                stopService(intent);
                break;
            case R.id.bindService:
                Intent bindIntent = new Intent(this, MyService.class);
                bindService(bindIntent, connection, BIND_AUTO_CREATE);
                break;
            case R.id.unbindService:
                unbindService(connection);
                break;
            default:
                break;
        }
    }
}


第 51 章 NFC (Near field communication)

目录

51.1. AndroidManifest.xml 文件配置
51.2. Loyout 文件
51.3. Activity 文件

51.1. AndroidManifest.xml 文件配置

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="cn.netkiller.nfc">
    <!--<uses-sdk android:minSdkVersion="14"/>-->
    <uses-permission android:name="android.permission.NFC" />
    <!-- 要求当前设备必须要有NFC芯片 -->
    <uses-feature
        android:name="android.hardware.nfc"
        android:required="true" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="NFC 初始化工具"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity
            android:name=".MainActivity"
            android:launchMode="singleTop">

            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>

            <intent-filter>
                <action android:name="android.nfc.action.NDEF_DISCOVERED" />
                <category android:name="android.intent.category.DEFAULT" />
                <data android:mimeType="text/plain" />

                <!--<data android:mimeType="text/plain" />-->
                <!--<data android:mimeType="*/*" />-->
            </intent-filter>
            <intent-filter>
                <action android:name="android.nfc.action.TECH_DISCOVERED" />
            </intent-filter>

            <intent-filter>
                <action android:name="android.nfc.action.TAG_DISCOVERED" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>

        </activity>

    </application>

</manifest>


51.2. Loyout 文件

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/ndefMessage"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginTop="20dp"
        android:layout_marginEnd="8dp"
        android:text="message"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="1.0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textView4" />

    <TextView
        android:id="@+id/textView4"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginTop="16dp"
        android:layout_marginEnd="8dp"
        android:text="NDEF Message : "
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.03"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TableLayout
        android:id="@+id/tableLayout"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="10dp"
        android:layout_marginTop="24dp"
        android:layout_marginEnd="8dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/ndefMessage">

        <TableRow
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <TextView
                android:id="@+id/textView"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="NFC UID" />

            <TextView
                android:id="@+id/uid"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="UID"
                tools:layout_editor_absoluteX="146dp"
                tools:layout_editor_absoluteY="71dp" />
        </TableRow>

        <TableRow
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <TextView
                android:id="@+id/textView2"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="NFC Tag" />

            <TextView
                android:id="@+id/nfcTag"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="tag"
                tools:layout_editor_absoluteX="179dp"
                tools:layout_editor_absoluteY="83dp" />
        </TableRow>

        <TableRow
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <TextView
                android:id="@+id/textView3"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="NFC Size" />

            <TextView
                android:id="@+id/size"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="size"
                tools:layout_editor_absoluteX="179dp"
                tools:layout_editor_absoluteY="150dp" />
        </TableRow>

        <TableRow
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <TextView
                android:id="@+id/textView5"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="NDEF Type" />

            <TextView
                android:id="@+id/schema"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="schema"
                tools:layout_editor_absoluteX="168dp"
                tools:layout_editor_absoluteY="257dp" />
        </TableRow>

        <TableRow
            android:layout_width="match_parent"
            android:layout_height="match_parent">

        </TableRow>

        <TableRow
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <TextView
                android:id="@+id/textView7"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="NDEF charset" />

            <TextView
                android:id="@+id/charset"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="charset"
                tools:layout_editor_absoluteX="163dp"
                tools:layout_editor_absoluteY="304dp" />

        </TableRow>

        <TableRow
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <TextView
                android:id="@+id/textView8"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="NDEF Lang" />

            <TextView
                android:id="@+id/language"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="lang"
                tools:layout_editor_absoluteX="163dp"
                tools:layout_editor_absoluteY="331dp" />
        </TableRow>

        <TableRow
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <TextView
                android:id="@+id/textView6"
                android:layout_width="79dp"
                android:layout_height="wrap_content"
                android:text="Status" />

            <TextView
                android:id="@+id/status"
                android:layout_width="289dp"
                android:layout_height="wrap_content"
                android:text="status"
                tools:layout_editor_absoluteX="90dp"
                tools:layout_editor_absoluteY="404dp" />

        </TableRow>
    </TableLayout>

    <TextView
        android:id="@+id/textView9"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginTop="48dp"
        android:layout_marginEnd="8dp"
        android:text="NDEF Message write :"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/tableLayout" />

    <TextView
        android:id="@+id/ndefWrite"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="8dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="1.0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textView9" />

    <Switch
        android:id="@+id/switchWrite"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginTop="28dp"
        android:layout_marginEnd="8dp"
        android:text="NDEF Message write"
        android:textOff="NDEF Message write Off"
        android:textOn="NDEF Message write On"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.497"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/ndefWrite" />

</android.support.constraint.ConstraintLayout>


51.3. Activity 文件

package cn.netkiller.nfc;

import android.nfc.FormatException;
import android.nfc.NdefRecord;
import android.support.annotation.NonNull;
import android.support.design.widget.BottomNavigationView;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

import android.app.PendingIntent;
import android.content.Intent;
import android.nfc.NdefMessage;
import android.nfc.NfcAdapter;

import android.nfc.Tag;
import android.nfc.tech.Ndef;
import android.os.Parcelable;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.CompoundButton;
import android.widget.Switch;
import android.widget.TextView;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.util.UUID;

public class MainActivity extends AppCompatActivity {

    private NfcAdapter nfcAdapter;
    private PendingIntent pendingIntent;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        final TextView status = (TextView) findViewById(R.id.status);

        nfcAdapter = NfcAdapter.getDefaultAdapter(this);

        if (nfcAdapter == null) {
            System.out.println("**** NFC ERROR ****");
            status.setText("NFC is not available.");
            return;
        } else if (!nfcAdapter.isEnabled()) {
            status.setText("请开启系统NFC功能");
        }

        status.setText("Start...");

        pendingIntent = PendingIntent.getActivity(this, 0, new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);

        final Switch switchWrite = (Switch) findViewById(R.id.switchWrite);

        switchWrite.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {

                if (isChecked) {
                    status.setText(switchWrite.getTextOn().toString());
                } else {
                    status.setText(switchWrite.getTextOff().toString());
                }
            }
        });
    }

    @Override
    protected void onResume() {
        super.onResume();
        nfcAdapter.enableForegroundDispatch(this, pendingIntent, null, null);
    }

    @Override
    protected void onPause() {
        super.onPause();
        nfcAdapter.disableForegroundDispatch(this);
    }

    //当窗口的创建模式是singleTop或singleTask时调用，用于取代onCreate方法
    //当NFC标签靠近手机，建立连接后调用
    @Override
    public void onNewIntent(Intent intent) {
        super.onNewIntent(intent);

        TextView status = (TextView) findViewById(R.id.status);
        TextView type = (TextView) findViewById(R.id.nfcTag);
        TextView size = (TextView) findViewById(R.id.size);
        TextView uidTextView = (TextView) findViewById(R.id.uid);
        TextView ndefMessage = (TextView) findViewById(R.id.ndefMessage);
        TextView schema = (TextView) findViewById(R.id.schema);
        TextView charset = (TextView) findViewById(R.id.charset);
        TextView language = (TextView) findViewById(R.id.language);
        TextView ndefWrite = (TextView) findViewById(R.id.ndefWrite);
        Switch switchWrite = (Switch) findViewById(R.id.switchWrite);

        Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);

        if (switchWrite.isChecked()) {

            UUID uuid = UUID.randomUUID();
            try {
                write(uuid.toString(), tag);
                ndefWrite.setText(uuid.toString());
            } catch (IOException e) {
                e.printStackTrace();
            } catch (FormatException e) {
                e.printStackTrace();
            }

        }

        if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(intent.getAction())) {
            status.setText("Read NDEF Message...");

            byte[] uid = tag.getId();
            BigInteger n = new BigInteger(uid);
            String hex = n.toString(16);
            uidTextView.setText(hex);

            Ndef ndef = Ndef.get(tag);
            String log = ndef.getType() + "\n最大数据容量：" + ndef.getMaxSize() + " bytes\n\n";
            System.out.println(log);
            type.setText(ndef.getType());
            size.setText(ndef.getMaxSize() + " bytes");

            Parcelable[] rawMessages = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES);
            if (rawMessages != null) {
                NdefMessage[] messages = new NdefMessage[rawMessages.length];
                for (int i = 0; i < rawMessages.length; i++) {
                    messages[i] = (NdefMessage) rawMessages[i];
                }
                byte[] payload = messages[0].getRecords()[0].getPayload();

                try {

                    String tagId = new String(messages[0].getRecords()[0].getType());
                    schema.setText(tagId);

                    String encoding = ((payload[0] & 128) == 0) ? "UTF-8" : "UTF-16";
                    charset.setText(encoding);

                    int languageCodeLength = payload[0] & 0x3f;
                    String languageCode = new String(payload, 1, languageCodeLength, "US-ASCII");

//                    String lang = new String(payload, 1, payload[0] & 0063, "US-ASCII");
                    language.setText(languageCode);

//                    String text = new String(messages[0].getRecords()[0].getPayload());
                    String text = new String(payload, languageCodeLength + 1, payload.length - languageCodeLength - 1, encoding);
                    ndefMessage.setText(text);

                } catch (UnsupportedEncodingException e) {
                    e.printStackTrace();
                }

            }

        } else {
            status.setText("NOT NDEF Messages tag");
        }
    }

    private void write(String text, Tag tag) throws IOException, FormatException {
        NdefRecord[] records = {createRecord(text)};
        NdefMessage message = new NdefMessage(records);
        // Get an instance of Ndef for the tag.
        Ndef ndef = Ndef.get(tag);
        // Enable I/O
        ndef.connect();
        // Write the message
        ndef.writeNdefMessage(message);
        // Close the connection
        ndef.close();
    }

    private NdefRecord createRecord(String text) throws UnsupportedEncodingException {
        String lang = "en";
        byte[] textBytes = text.getBytes();
        byte[] langBytes = lang.getBytes("US-ASCII");
        int langLength = langBytes.length;
        int textLength = textBytes.length;
        byte[] payload = new byte[1 + langLength + textLength];

        // set status byte (see NDEF spec for actual bits)
        payload[0] = (byte) langLength;

        // copy langbytes and textbytes into payload
        System.arraycopy(langBytes, 0, payload, 1, langLength);
        System.arraycopy(textBytes, 0, payload, 1 + langLength, textLength);

        NdefRecord recordNFC = new NdefRecord(NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_TEXT, new byte[0], payload);

        return recordNFC;
    }

}


第 52 章 OkHttp - An HTTP & HTTP/2 client for Android and Java applications

目录

52.1. Gradle
52.2. AndroidManifest.xml 开启网络访问权限
52.3. okhttp 默认是 HTTPS 开启 HTTP
52.4. GET
52.5. POST
52.5.1. POST Form Data
52.5.2. POST RAW JSON
52.5.3. 数据流提交
52.6. http header 相关设置
52.6.1. 设置 HTTP 头
52.6.2. Cookie 管理
52.6.3. 禁用缓存
52.6.4. 设置缓存 max-age
52.6.5. 强制缓存
52.7. HTTP Base Auth
52.8. HttpUrl.Builder 组装 URL 地址参数
52.9. Android Activity Example
52.10. Android Oauth2 + Jwt example
52.11. HTTP/2

http://square.github.io/okhttp/

52.1. Gradle

再 app/build.gradle 文件中增加依赖包

implementation 'com.squareup.okhttp3:okhttp:3.11.0'

app/build.gradle

neo@MacBook-Pro ~/AndroidStudioProjects/okhttp % cat app/build.gradle

apply plugin: 'com.android.application'

android {
    compileSdkVersion 28
    defaultConfig {
        applicationId "cn.netkiller.okhttp"
        minSdkVersion 28
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:28.0.0'
    implementation 'com.android.support:design:28.0.0'
    implementation 'com.android.support.constraint:constraint-layout:1.1.3'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
    implementation 'com.squareup.okhttp3:okhttp:3.11.0'
}


52.2. AndroidManifest.xml 开启网络访问权限

在 app/src/main/AndroidManifest.xml 文件中增加

<uses-permission android:name="android.permission.INTERNET" />

否则 okhttp 无法访问网络，添加后的效果如下。

neo@MacBook-Pro ~/AndroidStudioProjects/okhttp % cat app/src/main/AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="cn.netkiller.okhttp">
    <uses-permission android:name="android.permission.INTERNET" />
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity
            android:name=".MainActivity"
            android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>


52.3. okhttp 默认是 HTTPS 开启 HTTP

如果你尝试使用 http 链接 OkHttp3 就抛出异常: CLEARTEXT communication to " + host + " not permitted by network security policy

开发过程中由于 https ssl 需要购买证书，费用较高，通常测试环境我们仍然使用 http 下面方法是开启 http 模式，

创建文件 res/xml/network_security_config.xml 内容如下

neo@MacBook-Pro ~/AndroidStudioProjects/okhttp % cat app/src/main/res/xml/network_security_config.xml
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <domain-config cleartextTrafficPermitted="true">
        <domain includeSubdomains="true">localhost</domain>
    </domain-config>
</network-security-config>

再 app/src/main/AndroidManifest.xml 文件中增加 android:networkSecurityConfig="@xml/network_security_config"

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="cn.netkiller.okhttp">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity
            android:name=".MainActivity"
            android:label="@string/app_name"
            android:networkSecurityConfig="@xml/network_security_config">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

    <uses-permission android:name="android.permission.INTERNET" />
</manifest>


52.4. GET

    OkHttpClient client = new OkHttpClient();

    Request request = new Request.Builder()
            .url("http://www.netkiller.cn")
            .build();

    Response response = client.newCall(request).execute();
    if (!response.isSuccessful()) {
        throw new IOException("服务器端错误: " + response);
    }

    Headers responseHeaders = response.headers();
    for (int i = 0; i < responseHeaders.size(); i++) {
        System.out.println(responseHeaders.name(i) + ": " + responseHeaders.value(i));
    }

    System.out.println(response.body().string());


52.5. POST

52.5.1. POST Form Data

from 数据如下

key=value&other=data

        String url = "https://api.netkiller.cn/oauth/token";

        OkHttpClient client = new OkHttpClient();

        RequestBody formBody = new FormBody.Builder()
                .add("grant_type", "password")
                .add("username", "netkiller")
                .add("password", "123456")
                .build();

        Request request = new Request.Builder()
                .url(url)
                .post(formBody)
                .build();

        Response response = client.newCall(request).execute();
        if (!response.isSuccessful()) {
            throw new IOException("服务器端错误: " + response);
        }

52.5.2. POST RAW JSON

POST RAW Json 数据，数据的形态这样的

{"key":"value"}

public static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");

OkHttpClient client = new OkHttpClient();

String post(String url, String json) throws IOException {
  RequestBody body = RequestBody.create(JSON, json);
  Request request = new Request.Builder()
      .url(url)
      .post(body)
      .build();
  Response response = client.newCall(request).execute();
  return response.body().string();
}

52.5.3. 数据流提交

OkHttpClient client = new OkHttpClient();
final MediaType MEDIA_TYPE_TEXT = MediaType.parse("text/plain");
final String postBody = "Hello World";

RequestBody requestBody = new RequestBody() {
    @Override
    public MediaType contentType() {
        return MEDIA_TYPE_TEXT;
    }
    @Override
    public void writeTo(BufferedSink sink) throws IOException {
        sink.writeUtf8(postBody);
    }
    @Override
    public long contentLength() throws IOException {
        return postBody.length();
    }
};

Request request = new Request.Builder()
        .url("https://www.google.com")
        .post(requestBody)
        .build();
Response response = client.newCall(request).execute();
if (!response.isSuccessful()) {
    throw new IOException("服务器端错误: " + response);
}
System.out.println(response.body().string());


52.6. http header 相关设置

52.6.1. 设置 HTTP 头

添加Http头

Request request = new Request.Builder()
        .url(url)
        .addHeader("CLIENT", "AD")
        .addHeader("USERID", "343")
        .build();

覆盖 HTTP 头

        Request request = new Request.Builder()
                .header("Authorization", "replace this text with your token")
                .url(url)
                .build();

public class Headers {
   public static void main(String[] args) throws IOException {
    OkHttpClient client = new OkHttpClient();

    Request request = new Request.Builder()
            .url("http://www.netkiller.cn")
            .header("User-Agent", "Apple Mac")
            .addHeader("Accept", "text/html")
            .build();

    Response response = client.newCall(request).execute();
    if (!response.isSuccessful()) {
        throw new IOException("服务器端错误: " + response);
    }

    System.out.println(response.header("Server"));
    System.out.println(response.headers("Set-Cookie"));
   }
}

52.6.2. Cookie 管理

OkHttpClient mHttpClient = new OkHttpClient.Builder().cookieJar(new CookieJar() {
    private final HashMap<String, List<Cookie>> cookieStore = new HashMap<>();
    @Override
    public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {
        cookieStore.put(url.host(), cookies);
    }
    @Override
    public List<Cookie> loadForRequest(HttpUrl url) {
        List<Cookie> cookies = cookieStore.get(url.host());
        return cookies != null ? cookies : new ArrayList<Cookie>();
    }
}).build();

52.6.3. 禁用缓存

Request request = new Request.Builder()
    .cacheControl(new CacheControl.Builder().noCache().build())
    .url(url)
    .build();

52.6.4. 设置缓存 max-age

// 等同于 nocache
Request request = new Request.Builder()
    .cacheControl(new CacheControl.Builder()
        .maxAge(0, TimeUnit.SECONDS)
        .build())
    .url("https://www.netkiller.cn")
    .build();

// 设置缓存 365 天
Request request = new Request.Builder()
    .cacheControl(new CacheControl.Builder()
        .maxStale(365, TimeUnit.DAYS)
        .build())
    .url("https://www.netkiller.cn")
    .build();

52.6.5. 强制缓存

Request request = new Request.Builder()
    .cacheControl(new CacheControl.Builder()
        .onlyIfCached()
        .build())
    .url("https://www.netkiller.cn/helloworld.txt")
    .build();
Response forceCacheResponse = client.newCall(request).execute();
if (forceCacheResponse.code() != 504) {
  // The resource was cached! Show it.
} else {
  // The resource was not cached.
}


52.7. HTTP Base Auth

        OkHttpClient client = new OkHttpClient.Builder().authenticator(
                new Authenticator(){
                    public Request authenticate(Route route, Response response) {
                        String credential = Credentials.basic("api","secret");
                        return response.request().newBuilder().header("Authorization", credential).build();
                    }
                }
        ).build();


52.8. HttpUrl.Builder 组装 URL 地址参数

使用字符串拼接 URL地址特别容易出错

String url = "https://www.netkiller.cn/article?username="+ username + "&category="+ category;

较好的处理方式是使用 HttpUrl.Builder

        HttpUrl.Builder builder = HttpUrl.parse("https://www.netkiller.cn/article").newBuilder();
        builder.addQueryParameter("username", "netkiller");
        builder.addQueryParameter("category", "android");
        String url = builder.build().toString();

        Log.d("okhttp", url);

输出结果

https://www.netkiller.cn/article?username=netkiller&category=android


52.9. Android Activity Example

package cn.netkiller.okhttp;

import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;

import java.io.IOException;

import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;

public class HttpActivity extends AppCompatActivity {

    TextView textView;
    private Handler mHandler;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_http);

        textView = (TextView) findViewById(R.id.textView);

        mHandler = new Handler(Looper.getMainLooper()) {
            @Override
            public void handleMessage(Message msg) {
                textView.setText((String) msg.obj);
            }
        };

        OkHttpClient client = new OkHttpClient();
        Request request = new Request.Builder().url("https://api.netkiller.cn/member/json").build();
        client.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                Log.d("okhttp", "Connect timeout. " + e.getMessage());
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                String text = response.body().string();
                Log.d("okhttp", "HTTP Code " + response.code() + " TEXT " + text);

                Message msg = new Message();
                msg.what = 0;
                msg.obj = text;
                mHandler.sendMessage(msg);

            }
        });

    }
}


52.10. Android Oauth2 + Jwt example

Oauth 返回数据，通过 Gson 的 fromJson 存储到下面类中。

package cn.netkiller.okhttp.pojo;

public class Oauth {
    private String access_token;
    private String token_type;
    private String refresh_token;
    private String expires_in;
    private String scope;
    private String jti;

    public String getAccess_token() {
        return access_token;
    }

    public void setAccess_token(String access_token) {
        this.access_token = access_token;
    }

    public String getToken_type() {
        return token_type;
    }

    public void setToken_type(String token_type) {
        this.token_type = token_type;
    }

    public String getRefresh_token() {
        return refresh_token;
    }

    public void setRefresh_token(String refresh_token) {
        this.refresh_token = refresh_token;
    }

    public String getExpires_in() {
        return expires_in;
    }

    public void setExpires_in(String expires_in) {
        this.expires_in = expires_in;
    }

    public String getScope() {
        return scope;
    }

    public void setScope(String scope) {
        this.scope = scope;
    }

    public String getJti() {
        return jti;
    }

    public void setJti(String jti) {
        this.jti = jti;
    }

    @Override
    public String toString() {
        return "Oauth{" +
                "access_token='" + access_token + '\'' +
                ", token_type='" + token_type + '\'' +
                ", refresh_token='" + refresh_token + '\'' +
                ", expires_in='" + expires_in + '\'' +
                ", scope='" + scope + '\'' +
                ", jti='" + jti + '\'' +
                '}';
    }
}

Activity 文件

package cn.netkiller.okhttp;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;

import com.google.gson.Gson;

import java.io.IOException;

import cn.netkiller.okhttp.pojo.Oauth;
import okhttp3.Authenticator;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.Credentials;
import okhttp3.FormBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.Route;

public class Oauth2jwtActivity extends AppCompatActivity {

    private TextView token;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_oauth2jwt);

        token = (TextView) findViewById(R.id.token);

        try {
            get();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    public static Oauth accessToken() throws IOException {

        OkHttpClient client = new OkHttpClient.Builder().authenticator(
                new Authenticator() {
                    public Request authenticate(Route route, Response response) {
                        String credential = Credentials.basic("api", "secret");
                        return response.request().newBuilder().header("Authorization", credential).build();
                    }
                }
        ).build();

        String url = "https://api.netkiller.cn/oauth/token";

        RequestBody formBody = new FormBody.Builder()
                .add("grant_type", "password")
                .add("username", "blockchain")
                .add("password", "123456")
                .build();

        Request request = new Request.Builder()
                .url(url)
                .post(formBody)
                .build();

        Response response = client.newCall(request).execute();
        if (!response.isSuccessful()) {
            throw new IOException("服务器端错误: " + response);
        }

        Gson gson = new Gson();
        Oauth oauth = gson.fromJson(response.body().string(), Oauth.class);
        Log.i("oauth", oauth.toString());
        return oauth;
    }

    public void get() throws IOException {

        OkHttpClient client = new OkHttpClient.Builder().authenticator(
                new Authenticator() {
                    public Request authenticate(Route route, Response response) throws IOException {
                        return response.request().newBuilder().header("Authorization", "Bearer " + accessToken().getAccess_token()).build();
                    }
                }
        ).build();

        String url = "https://api.netkiller.cn/misc/compatibility";

        Request request = new Request.Builder()
                .url(url)
                .build();

        client.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                call.cancel();
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {

                final String myResponse = response.body().string();

                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        Log.i("oauth", myResponse);
                        token.setText(myResponse);
                    }
                });

            }
        });
    }

    public void post() throws IOException {

        OkHttpClient client = new OkHttpClient.Builder().authenticator(
                new Authenticator() {
                    public Request authenticate(Route route, Response response) throws IOException {
                        return response.request().newBuilder().header("Authorization", "Bearer " + accessToken().getAccess_token()).build();
                    }
                }
        ).build();

        String url = "https://api.netkiller.cn/history/create";

        String json = "{\n" +
                "  \"assetsId\": \"5bced71c432c001c6ea31924\",\n" +
                "  \"title\": \"添加信息\",\n" +
                "  \"message\": \"信息内容\",\n" +
                "  \"status\": \"录入\"\n" +
                "}";

        final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
        RequestBody body = RequestBody.create(JSON, json);

        Request request = new Request.Builder()
                .url(url)
                .post(body)
                .build();

        client.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                call.cancel();
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {

                final String myResponse = response.body().string();

                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {

//                        Gson gson = new Gson();
//                        Oauth oauth = gson.fromJson(myResponse, Oauth.class);
//                        Log.i("oauth", oauth.toString());

                        token.setText(myResponse);
                    }
                });

            }
        });
    }

}

上面的例子演示了，怎样获取 access token 以及 HTTP 基本操作 GET 和 POST，再Restful接口中还可能会用到 PUT, DELETE 等等，原来相同，这里就不在演示。


52.11. HTTP/2

首先确认你的服务器是支持 HTTP2，HTTP2配置方法可以参考 《Netkiller Web 手札》

下面是我的例子仅供参考，Nginx 开启 http2 代理后面的 Spring Cloud 接口。

server {
    listen       80;
    listen 443 ssl http2;
    server_name  api.netkiller.cn;

    ssl_certificate ssl/netkiller.cn.crt;
    ssl_certificate_key ssl/netkiller.cn.key;
    ssl_session_cache shared:SSL:20m;
    ssl_session_timeout 60m;

    charset utf-8;
    access_log  /var/log/nginx/api.netkiller.cn.access.log;

    error_page  497              https://$host$uri?$args;

    if ($scheme = http) {
        return 301 https://$server_name$request_uri;
    }

    location / {
        proxy_pass   http://127.0.0.1:8000;
        proxy_http_version 1.1;
        proxy_set_header    Host    $host;
        proxy_set_header    X-Forwarded-For $proxy_add_x_forwarded_for;
    }

    #error_page  404              /404.html;

    # redirect server error pages to the static page /50x.html
    #
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }

    # deny access to .htaccess files, if Apache's document root
    # concurs with nginx's one
    #
    #location ~ /\.ht {
    #    deny  all;
    #}
}

安卓程序如下

        String url = "https://api.netkiller.cn/member/json";

        OkHttpClient client = new OkHttpClient();
        Request request = new Request.Builder().url(url).build();
        client.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                Log.d("okhttp", "Connect timeout. " + e.getMessage());
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                String text = response.body().string();
                Log.d("okhttp", "HTTP Code " + response.code() + " TEXT " + text);
                Log.d("okhttp", "Protocol: " + response.protocol());
            }
        });

运行结果

D/okhttp: HTTP Code 200 TEXT {"status":false,"reason":"","code":0,"data":{"id":null,"username":"12322228888","mobile":"12322228888","password":"123456","wechat":"微信ID","role":"Organization","captcha":null,"createDate":"2018-10-25 09:25:23","updateDate":null}}
    Protocol: h2

输出 h2 表示当前接口与服务器连接是通过 http2 完成。


第 53 章 EventBus

目录

53.1. 添加 EventBus 依赖到项目Gradle文件
53.2. 快速开始一个演示例子
53.2.1. 创建 MessageEvent 类
53.2.2. Layout
53.2.3. Activity
53.3. Sticky Events
53.3.1. MainActivity
53.3.2. StickyActivity
53.3.3. MessageEvent
53.3.4. 删除粘性事件
53.4. 线程模型
53.5. 配置 EventBus
53.6. 事件优先级
53.7. 捕获异常事件

http://greenrobot.org/eventbus

在EventBus中主要有以下三个成员：

Event：事件，可以自定义为任意对象，类似Message类的作用；
Publisher：事件发布者，可以在任意线程、任意位置发布Event，已发布的Evnet则由EventBus进行分发；
Subscriber：事件订阅者，接收并处理事件，需要通过register(this)进行注册，而在类销毁时要使用unregister(this)方法解注册。每个Subscriber可以定义一个或多个事件处理方法，其方法名可以自定义，但需要添加@Subscribe的注解，并指明ThreadMode（不写默认为Posting）。

53.1. 添加 EventBus 依赖到项目Gradle文件

Gradle:

implementation 'org.greenrobot:eventbus:3.1.1'

完整的例子

apply plugin: 'com.android.application'

android {
    compileSdkVersion 28
    defaultConfig {
        applicationId "cn.netkiller.eventbus"
        minSdkVersion 26
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:28.0.0'
    implementation 'com.android.support.constraint:constraint-layout:1.1.3'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
    implementation 'org.greenrobot:eventbus:3.1.1'
}


53.2. 快速开始一个演示例子

操作 EventBus 只需四个步骤

1. 注册事件

EventBus.getDefault().register( this );

2. 取消注册

EventBus.getDefault().unregister( this );

3. 订阅事件

    @Subscribe(threadMode = ThreadMode.MAIN)
    public void onMessageEvent(MessageEvent event) {
        Toast.makeText(this, event.message, Toast.LENGTH_SHORT).show();
    }

4. 发送数据

EventBus.getDefault().post(new MessageEvent("Helloworld"));

53.2.1. 创建 MessageEvent 类

package cn.netkiller.eventbus.pojo;

public class MessageEvent {
    public final String message;

    public MessageEvent(String message) {
        this.message = message;
    }
}

53.2.2. Layout

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="8dp"
        android:text="Button"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textView" />

</android.support.constraint.ConstraintLayout>

53.2.3. Activity

package cn.netkiller.eventbus;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;

import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;

import cn.netkiller.eventbus.pojo.MessageEvent;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        EventBus.getDefault().register(this);

        findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                EventBus.getDefault().post(new MessageEvent("Hello everyone!"));
            }
        });

    }

    @Override
    protected void onDestroy() {
        super.onDestroy();

        //取消注册 , 防止Activity内存泄漏
        EventBus.getDefault().unregister(this);
    }

    @Subscribe(threadMode = ThreadMode.MAIN)
    public void onMessageEvent(MessageEvent event) {
        Toast.makeText(this, event.message, Toast.LENGTH_SHORT).show();
    }
}


53.3. Sticky Events

Sticky Events 粘性事件可以理解为Message做了持久化，直到Message被消费为止。无需注册即可发送Message。

下面的例子：在MainActivity发送事件，在StickyActivity里注册并且接收事件

A. MainActivity 发送事件：

EventBus.getDefault().postSticky(new MessageEvent("http://www.netkiller.cn"));

B. StickyActivity 接收事件

1. 注册

EventBus.getDefault().register( this );

2. 事件接收

    @Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
    public void onMessageEvent(MessageEvent event) {
        Toast.makeText(this, event.message, Toast.LENGTH_SHORT).show();
    }

3. 取消注册

EventBus.getDefault().unregister( this ) ;

53.3.1. MainActivity

Layout

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="8dp"
        android:text="Button"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textView" />

</android.support.constraint.ConstraintLayout>

MainActivity

package cn.netkiller.eventbus;

import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;

import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;

import cn.netkiller.eventbus.pojo.MessageEvent;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                EventBus.getDefault().postSticky(new MessageEvent("Hello everyone!"));
                startActivity(new Intent(MainActivity.this, StickyActivity.class));
            }
        });

    }

}

53.3.2. StickyActivity

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".StickyActivity">

</android.support.constraint.ConstraintLayout>

StickyActivity

package cn.netkiller.eventbus;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.Toast;

import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;

import cn.netkiller.eventbus.pojo.MessageEvent;

public class StickyActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_sticky);

        EventBus.getDefault().register(this);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        EventBus.getDefault().unregister(this);
    }

    @Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
    public void onMessageEvent(MessageEvent event) {
        Toast.makeText(this, event.message, Toast.LENGTH_SHORT).show();
    }

}

53.3.3. MessageEvent

package cn.netkiller.eventbus.pojo;

public class MessageEvent {
    public final String message;

    public MessageEvent(String message) {
        this.message = message;
    }
}

53.3.4. 删除粘性事件

MessageEvent stickyEvent = EventBus.getDefault().getStickyEvent(MessageEvent.class);

// Better check that an event was actually posted before
if(stickyEvent != null) {
    // "Consume" the sticky event
    EventBus.getDefault().removeStickyEvent(stickyEvent);
    // Now do something with it
}


53.4. 线程模型

EventBus 有五种线程模型（ThreadMode）

Posting：直接在事件发布者所在线程执行事件处理方法；
Main：直接在主线程中执行事件处理方法（即UI线程），如果发布事件的线程也是主线程，那么事件处理方法会直接被调用，并且未避免ANR，该方法应避免进行耗时操作；
MainOrdered：也是直接在主线程中执行事件处理方法，但与Main方式不同的是，不论发布者所在线程是不是主线程，发布的事件都会进入队列按事件串行顺序依次执行；
BACKGROUND：事件处理方法将在后台线程中被调用。如果发布事件的线程不是主线程，那么事件处理方法将直接在该线程中被调用。如果发布事件的线程是主线程，那么将使用一个单独的后台线程，该线程将按顺序发送所有的事件。
Async：不管发布者的线程是不是主线程，都会开启一个新的线程来执行事件处理方法。如果事件处理方法的执行需要一些时间，例如网络访问，那么就应该使用该模式。为避免触发大量的长时间运行的事件处理方法，EventBus使用了一个线程池来有效地重用已经完成调用订阅者方法的线程以限制并发线程的数量。  后面会通过代码展示五种ThreadMode的工作方式。

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".ThreadModeActivity">

    <Button
        android:id="@+id/buttonSend"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="8dp"
        android:text="Send"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/buttonThread"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="8dp"
        android:text="Send Thread"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/buttonSend" />
</android.support.constraint.ConstraintLayout>

package cn.netkiller.eventbus;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;

import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;

public class ThreadModeActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_thread_mode);

        EventBus.getDefault().register(this);

        findViewById(R.id.buttonSend).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.d("EventBus Thread : ", Thread.currentThread().getName());
                EventBus.getDefault().post("http://www.netkiller.cn");
            }
        });

        findViewById(R.id.buttonThread).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        Log.d("EventBus Thread : ", Thread.currentThread().getName());
                        EventBus.getDefault().post("http://www.netkiller.cn");

                    }
                }).start();

            }
        });

    }

    @Subscribe(threadMode = ThreadMode.POSTING)
    public void onMessageEventPostThread(String event) {
        Log.d("EventBus PostThread", "Message： " + event + "  thread: " + Thread.currentThread().getName());
    }

    @Subscribe(threadMode = ThreadMode.MAIN)
    public void onMessageEventMainThread(String event) {
        Log.d("EventBus MainThread", "Message： " + event + "  thread: " + Thread.currentThread().getName());
    }

    @Subscribe(threadMode = ThreadMode.MAIN_ORDERED)
    public void onEventMainOrdered(String event) {
        Log.d("EventBus MainOrdered", "Message: " + event + " thread:" + Thread.currentThread().getName());
    }

    @Subscribe(threadMode = ThreadMode.BACKGROUND)
    public void onMessageEventBackgroundThread(String event) {
        Log.d("EventBus BackgroundThread", "Message： " + event + "  thread: " + Thread.currentThread().getName());
    }

    @Subscribe(threadMode = ThreadMode.ASYNC)
    public void onMessageEventAsync(String event) {
        Log.d("EventBus Async", "Message： " + event + "  thread: " + Thread.currentThread().getName());
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        EventBus.getDefault().unregister(this);
    }
}

在 main 线程中发布消息

D/EventBus Thread :: main
D/EventBus MainThread: Message： http://www.netkiller.cn  thread: main
D/EventBus PostThread: Message： http://www.netkiller.cn  thread: main
D/EventBus Async: Message： http://www.netkiller.cn  thread: pool-1-thread-1
D/EventBus BackgroundThread: Message： http://www.netkiller.cn  thread: pool-1-thread-2
D/EventBus MainOrdered: Message: http://www.netkiller.cn thread:main

在线程中发布消息

D/EventBus Thread :: Thread-2
D/EventBus BackgroundThread: Message： http://www.netkiller.cn  thread: Thread-2
D/EventBus PostThread: Message： http://www.netkiller.cn  thread: Thread-2
D/EventBus Async: Message： http://www.netkiller.cn  thread: pool-1-thread-2
D/EventBus MainOrdered: Message: http://www.netkiller.cn thread:main
D/EventBus MainThread: Message： http://www.netkiller.cn  thread: main


53.5. 配置 EventBus

上面章节中的例子EventBus实例中采用默认方式

EventBus.getDefault().register(this);

这种方式的获取到的EventBus的都是默认属性，有时候并不能满足我们的要求，这时候我们可以通过EventBusBuilder来个性化配置EventBus的属性。

// 创建默认的EventBus对象，相当于EventBus.getDefault()。

EventBus installDefaultEventBus()：
// 添加由EventBus“注释预处理器生成的索引
EventBuilder addIndex(SubscriberInfoIndex index)：
// 默认情况下，EventBus认为事件类有层次结构（订户超类将被通知）
EventBuilder eventInheritance(boolean eventInheritance)：
// 定义一个线程池用于处理后台线程和异步线程分发事件
EventBuilder executorService(java.util.concurrent.ExecutorService executorService)：
// 设置忽略订阅索引，即使事件已被设置索引，默认为false
EventBuilder ignoreGeneratedIndex(boolean ignoreGeneratedIndex)：
// 打印没有订阅消息，默认为true
EventBuilder logNoSubscriberMessages(boolean logNoSubscriberMessages)：
// 打印订阅异常，默认true
EventBuilder logSubscriberExceptions(boolean logSubscriberExceptions)：
// 设置发送的的事件在没有订阅者的情况时，EventBus是否保持静默，默认true
EventBuilder sendNoSubscriberEvent(boolean sendNoSubscriberEvent)：
// 发送分发事件的异常，默认true
EventBuilder sendSubscriberExceptionEvent(boolean sendSubscriberExceptionEvent)：
// 在3.0以前，接收处理事件的方法名以onEvent开头，方法名称验证避免不是以此开头，启用严格的方法验证（默认：false）
EventBuilder strictMethodVerification(java.lang.Class<?> clazz)
// 如果onEvent***方法出现异常，是否将此异常分发给订阅者（默认：false）
EventBuilder throwSubscriberException(boolean throwSubscriberException)

我的实例参考

EventBus eventBus = EventBus.builder().eventInheritance(true)
    .ignoreGeneratedIndex(false)
    .logNoSubscriberMessages(true)
    .logSubscriberExceptions(false)
    .sendNoSubscriberEvent(true)
    .sendSubscriberExceptionEvent(true)
    .throwSubscriberException(false)
    .strictMethodVerification(true)
    .build();
eventBus.register(this);


53.6. 事件优先级

priority 数值越大优先级又高

    // MainActivity
    @Subscribe(priority = 2)
    public void onMessageEvent(MessageEvent event) {
        Toast.makeText(this, event.message, Toast.LENGTH_SHORT).show();
    }

    // SecondActivity
    @Subscribe(priority = 1)
    public void onMessageSecondEvent(MessageEvent event) {
        Toast.makeText(this, event.message, Toast.LENGTH_SHORT).show();
    }

时间拦截，MainActivity 收到信息后调用 EventBus.getDefault().cancelEventDelivery(event); 之后所有订阅将收不到信息。

    // MainActivity
    @Subscribe(priority = 2)
    public void onMessageEvent(MessageEvent event) {
        Toast.makeText(this, event.message, Toast.LENGTH_SHORT).show();
        EventBus.getDefault().cancelEventDelivery(event);
    }

    // SecondActivity
    @Subscribe(priority = 1)
    public void onMessageSecondEvent(MessageEvent event) {
        Toast.makeText(this, event.message, Toast.LENGTH_SHORT).show();
    }


53.7. 捕获异常事件

在 init() 中加入你的业务逻辑，根据需要，在特定的情况下使用 throw new Exception("异常信息"); 抛出异常。异常会被 hrowableFailureEvent(ThrowableFailureEvent event) 捕获到。

package cn.netkiller.eventbus;

import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Toast;

import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
import org.greenrobot.eventbus.util.AsyncExecutor;
import org.greenrobot.eventbus.util.ThrowableFailureEvent;

import cn.netkiller.eventbus.pojo.MessageEvent;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        EventBus.getDefault().register(this);

        findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                AsyncExecutor.create().execute(
                        new AsyncExecutor.RunnableEx() {
                            @Override
                            public void run() throws Exception {
                                init();
                                EventBus.getDefault().post(new MessageEvent("Hello everyone!"));
                            }
                        }
                );
            }
        });
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        EventBus.getDefault().unregister(this);
    }

    @Subscribe(threadMode = ThreadMode.MAIN)
    public void onMessageEvent(MessageEvent event) {
        Toast.makeText(this, event.message, Toast.LENGTH_SHORT).show();
    }

    public void init() throws Exception {
        // ...
        throw new Exception("实际发送异常");
    }

    @Subscribe(threadMode = ThreadMode.MAIN)
    public void hrowableFailureEvent(ThrowableFailureEvent event) {
        Log.d("EventBus", "hrowableFailureEvent: " + event.getThrowable().getMessage());
        Toast.makeText(this, event.getThrowable().getMessage(), Toast.LENGTH_SHORT).show();
    }

}


第 54 章 设计模式

目录

54.1. 单例模式

54.1. 单例模式

package cn.netkiller.voice;

import android.media.MediaRecorder;
import android.os.Environment;
import android.util.Log;

import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class Audio {

    private boolean isRecord = false;

    private MediaRecorder mediaRecorder;
    private String filename;

    private Audio() {

    }

    private static Audio instance;

    public synchronized static Audio getInstance() {
        if (instance == null)
            instance = new Audio();
        return instance;
    }

    public String getFilename() {
        return filename;
    }

    public void start() {

        if (mediaRecorder == null) {

            String path = Environment.getExternalStorageDirectory().getPath();
            String folder = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
            String name = new SimpleDateFormat("hhmmss").format(new Date());
            new File(path, folder).mkdirs();

            filename = String.format("%s/%s/%s.3gp", path, folder, name);
            Log.e("Voice", "voice path " + filename);

            try {

                mediaRecorder = new MediaRecorder();
                mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
                mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT);
                mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT);
                mediaRecorder.setOutputFile(filename);
                mediaRecorder.prepare();
                mediaRecorder.start();

                isRecord = true;

            } catch (IOException ex) {
                ex.printStackTrace();

            }
        }

    }

    public void stop() {
        if (mediaRecorder != null && isRecord) {
            System.out.println("stopRecord");
            isRecord = false;
            mediaRecorder.stop();
            mediaRecorder.release();
            mediaRecorder = null;
        }
    }

}


第 55 章

目录

55.1. java.net.UnknownServiceException: CLEARTEXT communication to 192.168.0.185 not permitted by network security policy
55.2. Caused by: android.os.NetworkOnMainThreadException

55.1. java.net.UnknownServiceException: CLEARTEXT communication to 192.168.0.185 not permitted by network security policy

okhttp 默认使用 https 链接服务器，如果使用 http 会抛出现上面的异常

if (!Platform.get().isCleartextTrafficPermitted(host)) {
      throw new RouteException(new UnknownServiceException(
          "CLEARTEXT communication to " + host + " not permitted by network security policy"));
}

创建文件 res/xml/network_security_config.xml 内容如下

neo@MacBook-Pro ~/AndroidStudioProjects/okhttp % cat app/src/main/res/xml/network_security_config.xml
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <base-config cleartextTrafficPermitted="true" />
</network-security-config>

再 app/src/main/AndroidManifest.xml 文件中增加 android:networkSecurityConfig="@xml/network_security_config"

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="cn.netkiller.okhttp">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity
            android:name=".MainActivity"
            android:label="@string/app_name"
            android:networkSecurityConfig="@xml/network_security_config">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

    <uses-permission android:name="android.permission.INTERNET" />
</manifest>


55.2. Caused by: android.os.NetworkOnMainThreadException

主线程不能访问网络，在访问网络的代码前面添加如下代码即可：

StrictMode.ThreadPolicy policy= new StrictMode.ThreadPolicy.Builder().permitAll().build();
StrictMode.setThreadPolicy(policy);

或者写在 setContentView(R.layout.activity_main); 后面

另一种方式是在线程中执行

       new Thread(new Runnable() {
            @Override
            public void run() {

                try {
                    String json = get("http://192.168.0.185:8080/member/json");
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }
        }).start();