开发规范拟定--初版
来源:互联网 发布:寻求网络黑客高手帮忙 编辑:程序博客网 时间:2024/05/01 02:05
介绍
好的开发规范不仅能够使得项目变得易维护,易升级。一些通用的规范可以参考《阿里巴巴java开发手册》
本文档主要针对我们现在使用的框架提出一些开发规范,欢迎补充
包结构规范
以短信邮件项目(mail-sms)为例,介绍包结构命名规范。
短信邮件项目主要包含短信,邮件两个子模块
【强制】 包分层–通用
一般每个项目都包含下面六个模块,还有一些各自扩展的模块
1. api #api接口定义,用于暴露服务
2. api-impl #api接口实现
3. app #应用
4. admin #后台页面
5. web #前台页面
6. model #实体
关于项目结构的介绍可以参考《项目结构说明.md》,他们的包分层应当统一
api:sinosoftgz.message.apiapi-impl:sinosoftgz.message.apiapp:sinosoftgz.message.appadmin:sinosoftgz.message.adminmodel:sinosoftgz.message.model
格式如下:公司名.模块名.层次名
包名应当尽量使用能够概括模块总体含义,单词义,单数,不包含特殊字符的单词
【正例】: sinosoftgz.message.admin
【反例】: sinosoftgz.mailsms.admin
sinosoftgz.mail.sms.admin
【推荐】包分层–业务
当项目模块的职责较为复杂,且考虑到以后拓展的情况下,单个模块依旧包含着很多小的业务模块时,应当优先按照业务区分包名
【正例】:
sinosoftgz.message.admin config 模块公用Config.java service 模块公用Service.java web 模块公用Controller.java IndexController.java mail service Mail私有Service.java MailTemplateService.java MailMessageService.java web Mail私有Controller.java MailTemplateController.java MailMessageController.java sms service Sms私有Service.java SmsTemplateService.java SmsMessageService.java web Sms私有Controller.java SmsTemplateController.java SmsMessageController.java MailSmsAdminApp.java
【反例】:
sinosoftgz.message.admin config 模块公用Config.java service 模块公用Service.java mail Mail私有Service.java MailTemplateService.java MailMessageService.java sms Sms私有Service.java SmsTemplateService.java SmsMessageService.java web 模块公用Controller.java IndexController.java mail Mail私有Controller.java MailTemplateController.java MailMessageController.java sms Sms私有Controller.java SmsTemplateController.java SmsMessageController.java MailSmsAdminApp.java
service和controller以及其他业务模块相关的包相隔太远,或者干脆全部丢到一个包内,单纯用前缀区分,会形成臃肿,充血的包结构。如果是项目结构较为单一,可以仅仅使用前缀区分;如果是项目中业务模块有明显的区分条件,应当单独作为一个包,用包名代表业务模块的含义。
数据库规范
【强制】必要的地方必须添加索引,如唯一索引,以及作为条件查询的列
【强制】生产环境,uat环境,不允许使用jpa.hibernate.ddl-auto: create
自动建表,每次ddl的修改需要保留脚本,统一管理
【强制】业务数据不能使用deleteBy…而要使用逻辑删除setDelete(true),查询时,findByxxxAndisDelete(xxx,false)
ORM规范
【强制】条件查询超过三个参数的,使用criteriaQuery
,predicates
而不能使用springdata的findBy
【正例】
public Page<MailTemplateConfig> findAll(MailTemplateConfig mailTemplateConfig, Pageable pageable) { Specification querySpecification = (Specification<MailTemplateConfig>) (root, criteriaQuery, criteriaBuilder) -> { List<Predicate> predicates = new ArrayList<>(); predicates.add(criteriaBuilder.isFalse(root.get("isDelete"))); //级联查询mailTemplate if (!Lang.isEmpty(mailTemplateConfig.getMailTemplate())) { //短信模板名称 if (!Lang.isEmpty(mailTemplateConfig.getMailTemplate().getTemplateName())) { predicates.add(criteriaBuilder.like(root.join("mailTemplate").get("templateName"), String.format("%%%s%%", mailTemplateConfig.getMailTemplate().getTemplateName()))); } //短信模板类型 if (!Lang.isEmpty(mailTemplateConfig.getMailTemplate().getTemplateType())) { predicates.add(criteriaBuilder.equal(root.join("mailTemplate").get("templateType"), mailTemplateConfig.getMailTemplate().getTemplateType())); } } //产品分类 if (!Lang.isEmpty(mailTemplateConfig.getProductType())) { predicates.add(criteriaBuilder.equal(root.get("productType"), mailTemplateConfig.getProductType())); } //客户类型 if (!Lang.isEmpty(mailTemplateConfig.getConsumerType())) { predicates.add(criteriaBuilder.equal(root.get("consumerType"), mailTemplateConfig.getConsumerType())); } return criteriaBuilder.and(predicates.toArray(new Predicate[predicates.size()])); }; return mailTemplateConfigRepos.findAll(querySpecification, pageable); }
【说明】条件查询是admin模块不可避免的一个业务功能,使用criteriaQuery
可以轻松的添加条件,使得代码容易维护,他也可以进行分页,排序,连表操作,充分发挥jpa面向对象的特性,使得业务开发变得快捷。
【反例】
public Page<GatewayApiDefine> findAll(GatewayApiDefine gatewayApiDefine,Pageable pageable){ if(Lang.isEmpty(gatewayApiDefine.getRole())){ gatewayApiDefine.setRole(""); } if(Lang.isEmpty(gatewayApiDefine.getApiName())){ gatewayApiDefine.setApiName(""); } if(Lang.isEmpty(gatewayApiDefine.getEnabled())){ return gatewayApiDefineDao.findByRoleLikeAndApiNameLikeOrderByLastUpdatedDesc("%"+gatewayApiDefine.getRole()+"%","%"+gatewayApiDefine.getApiName()+"%",pageable); }else{ return gatewayApiDefineDao.findByRoleLikeAndApiNameLikeAndEnabledOrderByLastUpdatedDesc("%"+gatewayApiDefine.getRole()+"%","%"+gatewayApiDefine.getApiName()+"%",gatewayApiDefine.getEnabled(),pageable); } }
【说明】在Dao层定义了大量的findBy方法,在Service写了过多的if else判断,导致业务逻辑不清晰
禁止使用魔鬼数字
【模型层与业务层】
一些固定业务含义的代码可以使用枚举类型,或者final static常量表示,在设值时,不能直接使用不具备业务含义的数值。
【正例】:使用final static常量:
//实体类定义 /** * 发送设置标志 * * @see sendFlag */ public final static String SEND_FLAG_NOW = "1"; //立即发送 public final static String SEND_FLAG_DELAY = "2"; //预设时间发送 /** * 发送成功标志 * * @see sendSuccessFlag */ public final static Map<String, String> SEND_SUCCESS_FLAG_MAP = new LinkedHashMap<>(); public final static String SEND_WAIT = "0"; public final static String SEND_SUCCESS = "1"; public final static String SEND_FAIL = "2"; static { SEND_SUCCESS_FLAG_MAP.put(SEND_WAIT, "未发送"); SEND_SUCCESS_FLAG_MAP.put(SEND_SUCCESS, "发送成功"); SEND_SUCCESS_FLAG_MAP.put(SEND_FAIL, "发送失败"); } /** * 发送设置标志 (1:立即发送 2:预设时间发送 ) */ @Column(columnDefinition = "varchar(1) comment '发送设置标志'") protected String sendFlag;//业务代码赋值使用MailMessage mailMessage = new MailMessage();mailMessage.setSendSuccessFlag(MailMessage.SEND_WAIT);mailMessage.setValidStatus(MailMessage.VALID_WAIT);mailMessage.setCustom(true);
【反例】
//实体类定义 /** * 发送设置标志 (1:立即发送 2:预设时间发送 ) */ @Column(columnDefinition = "varchar(1) comment '发送设置标志'") protected String sendFlag;//业务代码赋值使用MailMessage mailMessage = new MailMessage();mailMessage.setSendSuccessFlag("1");mailMessage.setValidStatus("0");mailMessage.setCustom(true);
【说明】魔鬼数字不能使代码一眼能够看明白到底赋的是什么值,并且,实体类发生变化后,可能会导致赋值错误,与预期赋值不符合且错误不容易被发现。
【正例】:也可以使用枚举类型避免魔鬼数字
protected String productType; protected String productName; @Enumerated(EnumType.STRING) protected ConsumerTypeEnum consumerType; @Enumerated(EnumType.STRING) protected PolicyTypeEnum policyType; @Enumerated(EnumType.STRING) protected ReceiverEnum receiver;
public enum ConsumerTypeEnum { PERSONAL, ORGANIZATION; public String getLabel() { switch (this) { case PERSONAL: return "个人"; case ORGANIZATION: return "团体"; default: return ""; } }}
【视图层】
例如,页面迭代select的option,不应该在view层判断,而应该在后台传入map在前台迭代
【正例】:
model.put("typeMap",typeMap);模板类型:<select type="text" name="templateType"> <option value="">全部</option> <#list typeMap?keys as key> <option <#if ((mailTemplate.templateType!"")==key)>selected="selected"</#if>value="${key}">${typeMap[key]}</option> </#list></select>
【反例】:
模板类型:<select type="text" name="templateType"> <option value="">全部</option> <option <#if ${xxx.templateType!}=="1" selected="selected"</#if> value="1">承保通知</option> ... <option <#if ${xxx.templateType!}=="5" selected="selected"</#if> value="5">核保通知</option></select>
【说明】:否则修改后台代码后,前端页面也要修改,设计模式的原则,应当是修改一处,其他全部变化。且 1,2…,5的含义可能会变化,不能从页面得知value和option的含义是否对应。
并发注意事项
项目中会出现很多并发问题,要做到根据业务选择合适的并发解决方案,避免线程安全问题
【强制】simpleDateFormat有并发问题,不能作为static类变量
【反例】:
这是我在某个项目模块中,发现的一段代码
Class XxxController{ public final static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); @RequestMapping("/xxxx") public String xxxx(String dateStr){ XxxEntity xxxEntity = new XxxEntity(); xxxEntity.setDate(simpleDateFormat.parse(dateStr)); xxxDao.save(xxxEntity); return "xxx"; }}
【说明】SimpleDateFormat 是线程不安全的类,不能作为静态类变量给多线程并发访问。如果不了解多线程,可以将其作为实例变量,每次使用时都new一个出来使用。不过更推荐使用ThreadLocal来维护,减少new的开销。
【正例】一个使用ThreadLocal维护SimpleDateFormat的线程安全的日期转换类:
public class ConcurrentDateUtil { private static ThreadLocal<DateFormat> threadLocal = new ThreadLocal<DateFormat>() { @Override protected DateFormat initialValue() { return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); } }; public static Date parse(String dateStr) throws ParseException { return threadLocal.get().parse(dateStr); } public static String format(Date date) { return threadLocal.get().format(date); }}
【推荐】名称唯一性校验出现的线程安全问题
各个项目的admin模块在需求中经常会出现要求名称不能重复,即唯一性问题。通常在前台做ajax校验,后台使用select count(1) from table_name where name=?
的方式查询数据库。这么做无可厚非,但是在极端的情况下,会出现并发问题。两个线程同时插入一条相同的name,如果没有做并发控制,会导致出现脏数据。如果仅仅是后台系统,那么没有必要加锁去避免,只需要对数据库加上唯一索引,并且再web层或者service层捕获数据异常即可。
【正例】:
//实体类添加唯一索引@Entity@Table(name = "mns_mail_template", uniqueConstraints = {@UniqueConstraint(columnNames = {"templateName"})})public class MailTemplate extends AbstractTemplate { /** * 模板名称 */ @Column(columnDefinition = "varchar(160) comment '模板名称'") private String templateName;}//业务代码捕获异常@RequestMapping(value = {"/saveOrUpdate"}, method = RequestMethod.POST) @ResponseBody public AjaxResponseVo saveOrUpdate(MailTemplate mailTemplate) { AjaxResponseVo ajaxResponseVo = new AjaxResponseVo(AjaxResponseVo.STATUS_CODE_SUCCESS, "操作成功", "邮件模板定义", AjaxResponseVo.CALLBACK_TYPE_CLOSE_CURRENT); try { //管理端新增时初始化一些数据 if (Lang.isEmpty(mailTemplate.getId())) { mailTemplate.setValidStatus(MailTemplate.VALID_WAIT); } mailTemplateService.save(mailTemplate); } catch (DataIntegrityViolationException ce) { ajaxResponseVo.setStatusCode(AjaxResponseVo.STATUS_CODE_ERROR); ajaxResponseVo.setMessage("模板名称已经存在"); ajaxResponseVo.setCallbackType(null); logger.error(ce.getMessage()); } catch (Exception e) { ajaxResponseVo.setStatusCode(AjaxResponseVo.STATUS_CODE_ERROR); ajaxResponseVo.setMessage("操作失败!"); ajaxResponseVo.setCallbackType(null); logger.error(e.getMessage(), e); } return ajaxResponseVo; }
【说明】关于其他一些并发问题,不仅仅是一篇文档能够讲解清楚的,需要对开发有很深的理解,我还记录了一些并发问题,仅供参考:http://blog.csdn.net/u013815546/article/details/56481842
moton使用注意事项
【注意】包的扫描
每个模块都要扫描自身的项目结构
mail-sms-admin:application.ymlmotan: client-group: sinosoftrpc client-access-log: false server-group: sinosoftrpc server-access-log: false export-port: ${random.int[9001,9999]} zookeeper-host: 127.0.0.1:2181 annotaiong-package: sinosoftgz.message.admin
app模块由于将api-impl脱离出了自身的模块,通常还需要扫描api-impl的模块
//配置pom依赖 pom.xml<dependency> <groupId>sinosoftgz</groupId> <artifactId>mail-sms-api-impl</artifactId></dependency>//配置spring ioc扫描 AutoImportConfig.java@ComponentScans({ @ComponentScan(basePackages = {"sinosoftgz.message.app", "sinosoftgz.message.api"})})//配置motan扫描 mail-sms-app:application.ymlmotan: annotaiong-package: sinosoftgz.message.app,sinosoftgz.message.api client-group: sinosoftrpc client-access-log: true server-group: sinosoftrpc server-access-log: true export-port: ${random.int[9001,9999]} zookeeper-host: localhost:2181
【注意】motan跨模块传输实体类时懒加载失效
遇到的时候注意一下,由于jpa,hibernate懒加载的问题,因为其内部使用动态代理去实现的懒加载,导致懒加载对象无法被正确的跨模块传输,此时需要进行深拷贝。
【正例】:
/** * 深拷贝OrderMain对象,主要用于防止Hibernate序列化懒加载Session关闭问题 * <p/> * // * @param order * * @return */ public OrderMain cpyOrder(OrderMain from, OrderMain to) { OrderMain orderMainNew = to == null ? new OrderMain() : to; Copys copys = Copys.create(); List<OrderItem> orderItemList = new ArrayList<>(); List<SubOrder> subOrders = new ArrayList<>(); List<OrderGift> orderGifts = new ArrayList<>(); List<OrderMainAttr> orderMainAttrs = new ArrayList<>(); OrderItem orderItemTmp; SubOrder subOrderTmp; OrderGift orderGiftTmp; OrderMainAttr orderMainAttrTmp; copys.from(from).excludes("orderItems", "subOrders", "orderGifts", "orderAttrs").to(orderMainNew).clear(); if (!Lang.isEmpty(from.getOrderItems())) { for (OrderItem i : from.getOrderItems()) { orderItemTmp = new OrderItem(); copys.from(i).excludes("order").to(orderItemTmp).clear(); orderItemTmp.setOrder(orderMainNew); orderItemList.add(orderItemTmp); } orderMainNew.setOrderItems(orderItemList); } SubOrderItem subOrderItem; List<SubOrderItem> subOrderItemList = new ArrayList<>(); if (from.getSubOrders() != null) { for (SubOrder s : from.getSubOrders()) { subOrderTmp = new SubOrder(); copys.from(s).excludes("order", "subOrderItems").to(subOrderTmp).clear(); subOrderTmp.setOrder(from); for (SubOrderItem soi : s.getSubOrderItems()) { subOrderItem = new SubOrderItem(); copys.from(soi).excludes("order", "subOrder", "orderItem").to(subOrderItem).clear(); subOrderItem.setOrder(orderMainNew); subOrderItem.setSubOrder(subOrderTmp); subOrderItemList.add(subOrderItem); if (!Lang.isEmpty(soi.getOrderItem())) { for (OrderItem i : orderMainNew.getOrderItems()) { if (i.getId().equals(soi.getOrderItem().getId())) { subOrderItem.setOrderItem(soi.getOrderItem()); } else { subOrderItem.setOrderItem(soi.getOrderItem()); } } } } subOrderTmp.setSubOrderItems(subOrderItemList); subOrders.add(subOrderTmp); } orderMainNew.setSubOrders(subOrders); } if (from.getOrderGifts() != null) { for (OrderGift og : from.getOrderGifts()) { orderGiftTmp = new OrderGift(); copys.from(og).excludes("order").to(orderGiftTmp).clear(); orderGiftTmp.setOrder(orderMainNew); orderGifts.add(orderGiftTmp); } orderMainNew.setOrderGifts(orderGifts); } if (from.getOrderAttrs() != null) { for (OrderMainAttr attr : from.getOrderAttrs()) { orderMainAttrTmp = new OrderMainAttr(); copys.from(attr).excludes("order").to(orderMainAttrTmp).clear(); orderMainAttrTmp.setOrder(orderMainNew); orderMainAttrs.add(orderMainAttrTmp); } orderMainNew.setOrderAttrs(orderMainAttrs); } return orderMainNew; }
公用常量规范
【强制】模块常量
模块自身公用的常量放置于模块的Constants 类中,以final static的方式声明
public class Constants { public static final String BUSINESS_PERFIX_PATH = "/mail-sms-app";}
【强制】项目常量
项目公用的常量放置于util模块的GlobalContants类中,以静态内部类和final static的方式声明
public abstract class GlobalContants { /** * 返回的状态 */ public class ResponseStatus{ public static final String SUCCESS = "success";//成功 public static final String ERROR = "error";//错误 } /** * 响应状态 */ public class ResponseString{ public static final String STATUS = "status";//状态 public static final String ERROR_CODE = "error";// 错误代码 public static final String MESSAGE = "message";//消息 public static final String DATA = "data";//数据 } ...}
- 开发规范拟定--初版
- Android文件命名规范初版
- Android文件命名规范初版
- 暂时拟定的代码审核规范
- 开发规范、命名规范
- 开发规范:HTML规范
- 开发规范: 架构规范
- 开发规范
- 开发规范
- 规范开发
- 开发规范
- 开发规范
- 开发规范
- 开发规范
- 开发规范
- 开发规范
- 怎样拟定创业计划书
- 创业计划书的拟定
- NEUQ 1768 一道简单的递推题
- CentOS用yum安装、配置、升级、初始化5.5升级10.0MariaDB
- 6.6解题报告
- 顺序表 查找 插入 删除 操作
- 四十岁是科学家的黄金年龄 我还有机会
- 开发规范拟定--初版
- Writing Aliases in csh and tcsh Simple aliases The csh and tcsh shells provide an alias command th
- SpringMVC坏境搭建
- easyui_datagrid1
- Python
- WARN No appenders could be found for logger (org.springframework.web.context.ContextLoader).
- 人工智能基础复习2——问题求解
- ColorArcProgressBar圆形进度条
- 2017算法课.16(Predict the Winner)