[关闭]
@andrewwang 2017-03-20T22:56:15.000000Z 字数 8438 阅读 1054

开发&Java


技术 Java


开发规范

架构

系统架构(船架)
业务隔离机制(船的防水隔离)
系统profile(体检)

系统设计

  1. 开放封闭原则(Open Closed Principle)
  2. 约定大于配置(惯例优先原则)

  3. 所有操作都要有反馈。特别是API,一定要有返回码。

  4. 所有重要/资金业务(注册,下单,退款,发货等)在数据改变时都必须log业务结果(成功info,失败error)。
  5. 使用分布式锁确保数据一致
    a. 锁定内的运行时间越短越好(只锁定小段代码或者MQ执行),因为锁定默认超时时间是5秒,其他锁如果超过时间会抛出异常。一个业务的不同代码片段可以分部用不同的资源来锁定。支持一个线程内多次锁定同一个资源,即可以嵌套锁。
    b. 锁定的是业务(service),不是数据调整(dao和CRUD)。一个业务对应多个数据调整,锁在业务上,而不是每个数据调整都锁定。
    c. 锁定的是一套业务,而不是每个业务独立。锁定的资源数量等于调试的表数量。如优惠券领取业务,有两个接口(1. 领券,2. 用户是否已领券)。1的资源有券(保护券数量)和用户优惠券(保护用户的最大领券数量),2的资源有用户优惠券。
  1. Order o = getOrder(id);
  2. if (o is unpay) { // 提高性能的判断,只适用于单向状态图
  3. try (Lock lock = lock(getLockName("Order", id))) {
  4. o = getOrder(id);
  5. if (o is unpay)
  6. payOrder(id);
  7. }
  8. }

数据

  1. 不能直接修改数据库结构,必须在代码层修改
  2. 对外开放的接口定义成public,其他用private。
  3. 所有表名要有前缀,如"ly_finance_"
  4. 数据库枚举字段(如状态)的使用
    数量明确的用byte,预定义成类(和实体同层)
    无限的用英文字符串(大写),放到业务系统的常量定义类(多层子类)。
  5. 表结构要有说明,字段说明写在类上,枚举字段的值写在常量定义类。

代码

  1. 数据要有缓存
  2. 所有定义都需要有值,不管其是否有默认值
  3. 编写测试用例文档和单元测试代码,保证质量
  4. 所有入口要有日志(@Log),特别是Controller层。重要业务要有业务流程日志,特别是对外接口。
  5. Controller层的函数需要确保向后兼容。通过版本号控制。如函数有输入输出调整,则版本号+1。新增新的逻辑,以前版本的逻辑不能修改。
    service层的函数不能有版本号。
  6. 数据源类型的数据(DataSource)和标准数据不能混用。区别:前者数量少量且恒定,后者数量可以很多。前者用下拉框,后者用搜索。
  7. 接口数据的规范(不允许对象空)。允许:null,[]。不允许:{}
  8. 尽量少用static,除非是常量定义(现在用的也很少了)。
  9. 类的组包解包要成对出现,特别是类和json文本之间的转换。
  10. null和empty的区别:null是不存在,empty是有但没有内容。
  11. 代码采用需求加载(类和变量的定义上加@Lazy(true))。
    框架层和通用业务层:使用Lazy
    业务层:所有使用层都需要(如user)则用Lazy
    使用层(api,boss,schedule,serviceX):不需要
    加载时间长的功能要在使用层显式初始化(初始加载)
  12. 数据获取尽量用函数封装,可以处理异常情况。如map.get("xyz").toString()改用MapUtils.getString(map, "xyz");

注意事项

工具

  1. 代码编辑器开启窗口Problems(问题)和Outline(类结构)
  2. 可以暂时关闭build automatically,但要尽快开启。尽量少用Clean。

Java

  1. 值类型(long)和引用类型(Long)的区别。值类型比较用==,引用类型比较必须用equal。
  2. Boolean的变量定义不要有is,如isOn用on。表结构字段还是要有is.参考
  3. BigDecimal的相等比较。
    scale(小数点后数量)的不同,compareTo忽略scale,equals要比较scale
  1. BigDecimal bd1 = new BigDecimal("1.00");
  2. Assert.assertFalse(BigDecimal.ONE.equals(bd1));
  3. Assert.assertTrue(BigDecimal.ONE.compareTo(bd1) == 0);

Spring

启动时创建表结构

属性文件的属性spring.jpa.hibernate.ddl-auto
auto是创建,none是不创建
建议多用none,可以节省启动时间

service初始化

  1. @PostConstruct
  2. private void init() {
  3. }

属性值设置

  1. @Value("${prop}") // 必须在属性文件设置
  2. @Value("${prop:}") // 有默认值,如字符空
  3. @Value("${prop:hello}") // 设置默认值是hello,属性文件优先
  4. String prop = "hi"; // 赋值是无效的

entity注意事项

日期显示处理

  1. @JsonSerialize(using = CustomDateTimeSerializer.class)
  2. public Date getCreatetime() {}

RestController对象设置

POST

@RequestBody User user
@RequestBody接收的对象定义规则,否则函数会异常(无法响应)
1. 必须要有默认构造函数
2. 如有子类,其在接收类的变量定义必须是static
也可以直接接收String,如@RequestBody String data

GET

url路径参数:@PathVariable String entity
querystring参数:@RequestParam("data") String data

创建分页Page

  1. , @RequestParam int pageIndex, @RequestParam(defaultValue = "10") int pageSize
  2. new PageImpl<Order>(pl, new PageRequest(pageIndex, pageSize), total)

jpa分页的返回对象转换

  1. Page<Source> sourcePage = null;
  2. Page<Target> = sourcePage.map(new Converter<Source, Target>() {
  3. @Override
  4. public Target convert(Source source) {
  5. return new Target(source);
  6. }
  7. });

JPA多表查询

JPA多表查询

  1. @Query("SELECT t FROM CmsArticle t, CmsCategory u WHERE t.categoryId=u.id AND t.status=:status AND u.code=:code AND u.parentId is null AND u.status=:status")
  2. public Page<CmsArticle> searchByCategory(@Param("code") String code, @Param("status") int status, Pageable pageable);

JPA单表/多表搜索,对象要有关联(不推荐本方案)

缓存

不需要定义key,key是有规则生成的。函数参数必须是Object类型
@Cacheable(value = "ProductListByCategory")
如果返回值是列表,则必须定义具体列表,如ArrayList,绝对不能使用List。
同一个类的函数调用是不能启用缓存的(即缓存无效)

Spring boot

security

SpringBoot配置属性之Security

mq

SpringBoot配置属性之MQ

Actuator

SpringBoot四大神器之Actuator

第三方库

guava

字符串处理:分割,连接,填充
数组操作,含集合生成新集合
CharMatcher

Thymeleaf

介绍

  1. URL的处理是通过语法@{...}来处理:
  2. <a th:href="@{http://www.thymeleaf.org}">Thymeleaf</a>
  3. 字符串替换:
  4. 局部:<span th:text="|Welcome to our application, ${user.name}!|"> th:href="|mailto:${text_email}|"
  5. 全部:th:href="${jsFile}"
  6. 自定义属性:
  7. <div th:attr="selfdefineAttr=${xxx}"></div>
  8. 等号不能括号起来
  9. <div th:attr="style='background-image:url('+${banner.pic}+')'"></div>
  10. 等同于上面<div th:style="'background-image:url('+${banner.pic}+')'"></div>
  11. 直接显示html
  12. 方式1:<div th:remove="tag" th:utext="${content}"></div>
  13. 方式2:<%==content%>,单个=是值,两个=是html显示

正则表达式

\w 匹配字母或数字或下划线或汉字 [a-zA-Z_0-9]
\s 匹配任意的空白符(空格、TAB\t、回车\r \n)
\d 匹配数字 [0-9]

^ 匹配字符串的开始
$ 匹配字符串的结束

quartz

http://blog.csdn.net/guolong1983811/article/details/51501346

maven

库类型的jar包(非运行包),不要用spring-boot-maven-plugin

代码片段

常用

异常

UnsupportedOperationException

日志

  1. log.info(LogUtils.gen("message", "key1", value1, "key2", value2));
  2. log.info(LogUtils.gen("getUser", ImmutableMap.<String, String> builder().put("token", token).put("userId", userId).build()));

对象克隆

一般是从parent克隆出child

  1. BeanUtils.copyProperties(parent, child);

注解类获取

  1. this.getClass().isAnnotationPresent(BizApi.class); // 获取所有引用的判断:BizApi是annotation

bean获取

  1. this.service = this.context.getBean(IUserAccountService.class);
  1. for (Map.Entry<String, Object> entry : this.context.getBeansWithAnnotation(BizApi.class).entrySet()) {
  2. this.apiMap.put(entry.getKey(), (IBizApi) entry.getValue());
  3. }

数组类

字符串[]到数组

  1. List<String> stooges = Arrays.asList("Larry", "Moe", "Curly");
  2. Arrays.asList(new String[] { "13589878678", "13690378756" })

List到字符串

  1. Joiner.on(",").join(list);
  1. map到字符串:
  1. Map<String, String> map = this.getRequestParams(request);
  2. String body = Joiner.on("&").withKeyValueSeparator("=").join(map);

字符串到map

  1. final Map<String, String> join =Splitter.on("&").withKeyValueSeparator("=").split("id=123&name=green”);

字符串到List

  1. Splitter. on("," ).omitEmptyStrings().splitToList("sd,dsf");

初始化map

  1. ImmutableMap.<String, String>builder().put("uri", uri).put("body", body).build();
  2. ####从一个list生成另外一个listAdvertise转换成ClientBanner
  3. Lists.transform(listAdv, new Function<Src, Target>() {
  4. @Override
  5. public Target apply(Src src) {
  6. return new Target(src);
  7. }
  8. });

列表到Map

  1. Map<String, Company> objMap = companyList.stream().collect(Collectors.toMap(Company::getCode, (obj) -> obj));

循环处理(java8)

  1. List<User> userList = Lists.newArrayList();
  2. this.partyDao.findAll().forEach((party)->userList.add(new User(party)));

Map也可以同样循环处理

json

转换成json格式文本(JsonObject)

  1. User obj;
  2. // String jsonText = new Gson().toJson(obj);
  3. String jsonText = JSONUtils.toJson(obj, User.class, false);

转换成类对象(JsonObject, json格式文本)

  1. User user = JSONUtils.fromJsonObject(jsonObject, User.class, null);
  2. // User user = new GsonBuilder().create().fromJson(jsonText, User.class);
  3. User user = JSONUtils.fromJson(jsonText, User.class);

转换成数组对象(json格式文本)

  1. // 获取特定对象列表
  2. List<Person> rtn =JSONUtils.fromJson(jsonText, new TypeToken<List<Person>>(){}.getType());
  3. ArrayList<String> list = JSONUtils.fromJson(jsonText, ArrayList.class);
  4. // 默认序列化有问题(map里的number会转成double)
  5. // 获取对象列表,对象是个map,和json的对象值一一对应。
  6. jsonText = "[{id:123,name:'第三方',price:8.88}]";
  7. ArrayList<Map<String, Object>> list = JSONUtils.fromJson(jsonText, ArrayList.class, ArrayList.class, new ListMapJsonDeserializer());

转换成Map对象(json格式文本)

  1. Map<Integer,Person> map =JSONUtils.fromJson(jsonText, new TypeToken<Map<Integer,Person>>(){}.getType());
  2. jsonText = "{id:123,name:'第三方',price:8.88}";
  3. Map<String, Object> map = JSONUtils.fromJson(jsonText, HashMap.class, HashMap.class, new MapJsonDeserializer());
  4. // com.fasterxml.jackson.databind
  5. ObjectMapper objectMapper = new ObjectMapper();
  6. Map<String, Object> map = objectMapper.readValue(jsonText, Map.class);

转换成JsonObject(json格式文本)

JsonObject jo = JSONUtils.parse(jsonText);
JsonArray ja = JSONUtils.parseArray(jsonText);

创建json对象

  1. JsonObject jo = new JsonObject();
  2. jo.add("list", JSONUtils.toJsonElement(list));
  3. jo.add("obj", JSONUtils.toJsonElement(obj));
  4. jo.add("value", new JsonPrimitive(val));

线程

  1. new Thread(new Runnable() {
  2. @Override
  3. public void run() {
  4. // code here
  5. }
  6. }
  7. }).start();

模板&回调

模版函数定义方法

  1. public <T> void test(T t1) {} // 函数定义
  2. test(new ClassA()); // 调用

回调函数(Java8的功能)

  1. public void callbackUsage(Function<String, String> fn) {
  2. fn.apply(“123”);
  3. }
  4. this.callbackUsage(new Function<String, String>() {
  5. @Override
  6. public String apply(String t) {
  7. return null;
  8. }
  9. });

测试

单元测试类的定义代码

@RunWith(SpringRunner.class)
@SpringBootTest

增强spring的UT

注解注解2

分析

命令

查看内存分配命令(可以看到大量占用内存的对象):jmap –histo:live [pid]
dump命令:jmap -dump:live,format=b,file=p.dump [pid]

visualvm

外部工具
visualvm
Jstatd方式远程监控Linux下 JVM运行情况TODO

JavaMelody

内置到项目
JavaMelody
Spring Boot集成TODO

功能方案

框架层调用业务层信息

业务层需将业务信息注入框架层。实现示例如下(框架层获取业务层的用户信息):

层级 说明
框架层 实体类UserAccount 框架层使用的用户信息结构
框架层 服务类UserAccountService 框架层其他类获取用户信息的入口,实例化IUserAccountService就是注入
框架层 接口类IUserAccountService
业务层 服务类UserService,实现接口IUserAccountService 继承接口就是定义注入类

第三方二维码图片生成和使用

使用说明
图片地址示例:http://qr.liantu.com/api.php?text=http://www.xyz.com/shop/1111

参考

他山之石

快速开发框架

Dubbo:RPC+ZooKeeper
spring-wind:可参考代码和代码结构。
iBase4J:采用了ZooKeeper,分布式服务理念
Hasor-RSF:分布式服务框架
Redkale:有点大。第三方接入采用插件模式
eova:老了,可参考思想。

其他

sharding-jdbc
告别手写 API文档生成工具推荐
阿里完整自动化测试解决方案 macaca

资料

后台上手指南

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注