本篇文章從”數(shù)據(jù)庫審計(jì)字段”泽艘,”方法級(jí)別數(shù)據(jù)驗(yàn)證”,””返回值約束”镐依,“業(yè)務(wù)邏輯中的門面模式”匹涮,“業(yè)務(wù)異常設(shè)計(jì)”,“枚舉狀態(tài)設(shè)計(jì)”等6個(gè)方面作為出發(fā)點(diǎn)槐壳,講解在真正項(xiàng)目開發(fā)中然低,java編程的最佳實(shí)踐。本文的所有代碼和思想都是筆者自己的實(shí)際經(jīng)驗(yàn)和見解务唐,希望對(duì)讀者有所幫助雳攘。
數(shù)據(jù)庫審計(jì)字段
在做業(yè)務(wù)系統(tǒng)數(shù)據(jù)庫設(shè)計(jì)的時(shí)候,我相信你總會(huì)創(chuàng)建一些相關(guān)的審計(jì)字段枫笛,比如:創(chuàng)建人吨灭,創(chuàng)建時(shí)間,更新人刑巧,更新時(shí)間喧兄。
每次重復(fù)的在會(huì)話中獲取創(chuàng)建人(更新人)和創(chuàng)建時(shí)間(更新時(shí)間),然后從controller層傳入到service層啊楚,在進(jìn)行entity賦值吠冤,然后進(jìn)行插入數(shù)據(jù)庫(reposity層)。
這種對(duì)業(yè)務(wù)無關(guān)的操作特幔,最好可以做成通用的咨演,那么,如何設(shè)計(jì)一個(gè)通用的審計(jì)日志插入呢蚯斯?
舉例:
框架:spring boot + mybatis
數(shù)據(jù)庫:mysql
輔助工具:lombok
定義注解
注解在實(shí)體中的字段薄风,都會(huì)自動(dòng)賦值到實(shí)體字段中:@CreateAt/@CreateBy/@UpdateAt/@UpdateBy
@Retention(RetentionPolicy.RUNTIME)
@Target(value = {FIELD, METHOD, ANNOTATION_TYPE})
public @interface CreateAt {
}
@Retention(RetentionPolicy.RUNTIME)
@Target(value = {FIELD, METHOD, ANNOTATION_TYPE})
public @interface CreateBy {
}
@Retention(RetentionPolicy.RUNTIME)
@Target(value = {FIELD, METHOD, ANNOTATION_TYPE})
public @interface UpdateAt {
}
@Retention(RetentionPolicy.RUNTIME)
@Target(value = {FIELD, METHOD, ANNOTATION_TYPE})
public @interface UpdateBy {
}
AOP 攔截
使用mybatis生成數(shù)據(jù)庫代碼,然后進(jìn)行抽象,其他的生成類進(jìn)行集成:
public interface MybatisBaseRepository {
long countByExample(E example);
int deleteByExample(E example);
int deleteByPrimaryKey(PK id);
int insert(T record);
int insertSelective(T record);
List selectByExample(E example);
T selectByPrimaryKey(PK id);
int updateByExampleSelective(@Param("record") T record, @Param("example") E example);
int updateByExample(@Param("record") T record, @Param("example") E example);
int updateByPrimaryKeySelective(T record);
int updateByPrimaryKey(T record);
}
aop需要攔截insert、update然后對(duì)其中的泛型實(shí)體 T 進(jìn)行賦值(這里邊的T有可能包含以上注解,對(duì)這些注解的字段進(jìn)行賦值)
AOP代碼如下:
@Slf4j
@Aspect
@Component
public class MybatisAuditAOPDefault {
@Pointcut("execution(* com.tasly.chm.repository..*.*Repository.insert*(..))")
private void insertCutMethod() {
}
@Pointcut("execution(* com.tasly.chm.repository..*.*Repository.update*(..))")
private void updateCutMethod() {
}
@Around("insertCutMethod()")
public Object doInsertAround(ProceedingJoinPoint pjp) throws Throwable {
Object[] args = pjp.getArgs();
for (Object arg : args) {
AuditingAction.markCreateAuditing(arg);
}
return pjp.proceed();
}
@Around("updateCutMethod()")
public Object doupdateAround(ProceedingJoinPoint pjp) throws Throwable {
Object[] args = pjp.getArgs();
for (Object arg : args) {
AuditingAction.markUpdateAuditing(arg);
}
return pjp.proceed();
}
}
審計(jì)處理類-AuditingAction
AuditingAction主要有兩個(gè)方法:
markCreateAuditing(Object) : 標(biāo)記插入實(shí)體的數(shù)據(jù)值
markUpdateAuditing(Object) : 標(biāo)記更新實(shí)體的數(shù)據(jù)值
AuditingAction:
@UtilityClass
public class AuditingAction {
public void markCreateAuditing(Object targetEntity) throws IllegalAccessException {
boolean isList = List.class.isAssignableFrom(targetEntity.getClass());
if(isList){
List list = (List) targetEntity;
if(CollectionUtils.isEmpty(list)){
return;
}
for(Object target : list){
doMarkCreateAudditingSingle(target);
}
return ;
}
doMarkCreateAudditingSingle(targetEntity);
}
private static void doMarkCreateAudditingSingle(Object targetEntity) throws IllegalAccessException {
List fieldList = getFields(targetEntity);
doMarkCreateAuditing(targetEntity, fieldList);
}
private static void doMarkCreateAuditing(Object targetEntity, List fieldList) throws IllegalAccessException {
Date currentDate = new Date();
for (Field field : fieldList) {
if (AnnotationUtils.getAnnotation(field, CreateBy.class) != null) {
ReflectionUtils.makeAccessible(field);ReflectionUtils.setField(field,targetEntity,getAuditingProvider().auditingUser());
}
if (AnnotationUtils.getAnnotation(field, CreateAt.class) != null) {
ReflectionUtils.makeAccessible(field);ReflectionUtils.setField(field,targetEntity,currentDate);
}
}
}
public void markUpdateAuditing(Object targetEntity) throws IllegalAccessException {
doMarkUpdateAuditingSingle(targetEntity);
}
private static void doMarkUpdateAuditingSingle(Object targetEntity) throws IllegalAccessException {
List fieldList = getFields(targetEntity);
Date currentDate = new Date();
for (Field field : fieldList) {
if (AnnotationUtils.getAnnotation(field, UpdateBy.class) != null) {
ReflectionUtils.makeAccessible(field); ReflectionUtils.setField(field,targetEntity,getAuditingProvider().auditingUser());
}
if (AnnotationUtils.getAnnotation(field, UpdateAt.class) != null) {
ReflectionUtils.makeAccessible(field);
ReflectionUtils.setField(field,targetEntity,currentDate);
}
}
}
private static List getFields(Object targetEntity) {
List fieldList = new ArrayList<>();
Class tempClass = targetEntity.getClass();
while (tempClass != null) {//當(dāng)父類為null的時(shí)候說明到達(dá)了最上層的父類(Object類).
fieldList.addAll(Arrays.asList(tempClass.getDeclaredFields()));
tempClass = tempClass.getSuperclass(); //得到父類,然后賦給自己
}
return fieldList;
}
private AuditingProvider getAuditingProvider() {
return SpringContexts.getBeansByClass(AuditingProvider.class)
.values()
.stream()
.filter(auditingProvider -> !SystemMetaObject.forObject(auditingProvider).hasGetter("h"))//不是MapperProxy的代理類
.findFirst()
.orElseThrow(NotFoundAuditingProvider::new);
}
}
審計(jì)人提供者 AuditingProvider
public interface AuditingProvider {
String auditingUser();
}
需要實(shí)現(xiàn)提供器類拍嵌,負(fù)責(zé)提供審計(jì)人的操作,默認(rèn)實(shí)現(xiàn)如:
public class MybatisAuditingProvider implements AuditingProvider {
@Override
public String auditingUser() {
return String.valueOf(SecurityUser.get().getUserId());
}
}
總結(jié)
自動(dòng)插入審計(jì)字段信息的設(shè)計(jì)已經(jīng)設(shè)計(jì)好了遭赂。、 當(dāng)然横辆,在此基礎(chǔ)上撇他,我還完成了AutoConfig的注解類@EnableMybatisAudit,你也可以腦洞大開的試一下。
方法級(jí)別數(shù)據(jù)驗(yàn)證
無論寫什么樣的代碼,提供出去的api接口困肩,一定要很健壯划纽。
先不考慮業(yè)務(wù)邏輯的前提下,我們應(yīng)該很明確的指出定義的接口的出參和入?yún)⒌囊?/p>
舉例:
框架: spring boot
驗(yàn)證:jsr 303規(guī)范 (hibernate實(shí)現(xiàn))
方式:spring 提供的方法級(jí)別驗(yàn)證
開啟方法級(jí)別驗(yàn)證方式
直接使用hibernate實(shí)現(xiàn)的國(guó)際化就可以锌畸,不要重復(fù)造輪子
@Bean
public MethodValidationPostProcessor methodValidationPostProcessor(@Autowired LocalValidatorFactoryBean localValidatorFactoryBean) {
MethodValidationPostProcessor methodValidationPostProcessor = new MethodValidationPostProcessor();
methodValidationPostProcessor.setValidator(localValidatorFactoryBean);
return methodValidationPostProcessor;
}
@Bean
public LocaleResolver localeResolver() {
CookieLocaleResolver slr = new CookieLocaleResolver();
slr.setDefaultLocale(Locale.CHINA);
slr.setCookieMaxAge(3600);
return slr;
}
@Bean
public LocalValidatorFactoryBean validator() {
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
validator.setProviderClass(org.hibernate.validator.HibernateValidator.class);
validator.setValidationMessageSource(getMessageSource());
return validator;
}
private ResourceBundleMessageSource getMessageSource(){
ResourceBundleMessageSource rbms = new ResourceBundleMessageSource();
rbms.setDefaultEncoding(StandardCharsets.UTF_8.toString());
rbms.setUseCodeAsDefaultMessage(false);
rbms.setCacheSeconds(60);
rbms.setBasenames("classpath:org/hibernate/validator/ValidationMessages");
return rbms;
}
使用方式
我們要在業(yè)務(wù)接口中定義出參和入?yún)⒌囊笥铝樱@些注解我相信你很熟悉了,對(duì) 潭枣,沒錯(cuò)比默,是jsr 303規(guī)范驗(yàn)證:
public interface PlanService {
@NotNull
CreatedPlanBO addPlanCompleted(@NotNull @Valid CreatedPlanBO createdPlanBO);
@NotNull
Plan addPlan(@NotNull @Valid PlanBO planBO);
Plan addOneYearToPlan(@NotNull Integer id) throws PlanNotFoundException;
void deleteOneYearToPlan(@NotNull Integer id)
throws PlanNotFoundException,PlanAtLeastOneYearException;
void deletePlan(@NotNull Integer id) throws PlanNotFoundException;
}
要在實(shí)現(xiàn)類中一定要標(biāo)識(shí)注解 @Validated, 否則不生效的,看一下實(shí)現(xiàn):
@Validated
@Service
public class PlanServiceImpl implements PlanService {
//省略代碼實(shí)現(xiàn)
}
這樣我們使用其他人寫的service時(shí),直接看到接口定義就可以使用了盆犁,很方便命咐。
尤其這樣的接口定義 :
@NotNull
List listString();
這樣,我們就知道這個(gè)接口返回值谐岁,如果沒有數(shù)據(jù)醋奠,則會(huì)返回空集合,而不是空對(duì)象(null),可以減少空指針異常的發(fā)生翰铡,并且這個(gè)接口的實(shí)現(xiàn)者也需要按照這樣的接口定義來寫钝域。
這里可以稱他為約定編程讽坏。
其他方式
不一定必須使用spring提供的方法驗(yàn)證锭魔,如果項(xiàng)目中不是spring項(xiàng)目,或者使用了早期不支持這樣方法驗(yàn)證的spring呢路呜,我們要怎么辦呢迷捧?
推薦要使用Guava:
Preconditions.checkNotNull();
Preconditions.checkArgument();
但是這樣的話,就要和團(tuán)隊(duì)人員進(jìn)行約束胀葱,比如入?yún)⒈仨毑荒転榭?返回值是集合的話不能為空對(duì)象等漠秋。。
我給大家的建議是抵屿,如果有集合返回值的話庆锦,使用前需要進(jìn)行驗(yàn)證:
CollectionUtils.isEmpty(Collection);
然后才進(jìn)行使用,沒有看到明顯的方法約束的話轧葛,一定要這么做搂抒。
相信墨菲定律一定存在吧!
總結(jié)
方法級(jí)別的驗(yàn)證,可以帶來接口使用上的約束尿扯,對(duì)于調(diào)用者和實(shí)現(xiàn)者來說求晶,都是一個(gè)不錯(cuò)的選擇,如果可以衷笋,一定要這么做芳杏,如果不可以,請(qǐng)約束你自己!
返回值約束
我相信,你嘗嘗會(huì)有這樣的迷惑: 返回值我到底能不能為空呢爵赵?
有的程序員給了自己一個(gè)錯(cuò)誤的判定:”可以為空吝秕,調(diào)用者調(diào)用的時(shí)候判斷一下,如果可以為空空幻,則怎樣怎樣郭膛,不為空,則怎樣怎樣”
如果你腦海中也有以上的答案氛悬,請(qǐng)忘記它则剃,因?yàn)槟闶清e(cuò)誤的,起碼在jdk8之后是錯(cuò)誤的
舉例 :
環(huán)境:jdk8
理念:異常設(shè)計(jì)
關(guān)鍵字:Optional
Optional
java.util.Optional是jdk8給我看到的很棒的東西如捅,他教給了我如何去處理空值的問題.
Optional的語義是:可以為空
看代碼:
Optional get(id);
這段代碼棍现,很漂亮,它告訴我們镜遣,通過id獲取Plan己肮,這個(gè)Plan是有可能為空的,那么可以理解為:”設(shè)計(jì)接口的作者悲关,希望你可以通過Plan是否存在來控制你的業(yè)務(wù)邏輯”
怎么樣谎僻,是不是很棒
在看一個(gè)接口:
Plan get(id);
這個(gè)接口是很迷惑的,你只知道通過id可以獲取Plan,但是其他的你卻不知道寓辱。
這樣的接口很糟糕艘绍,因?yàn)檎{(diào)用者害怕調(diào)用的Plan是空值,這樣秫筏,他們就不得不做一些沒必要的驗(yàn)證了诱鞠。
有沒有什么更好的方式來告訴調(diào)用者,接口一定不能為空的这敬?
異常聲明
異常是個(gè)好東西航夺,比如上邊的接口,我們?cè)O(shè)計(jì)成:
Plan get(id) throws PlanNotFoundException;
這樣調(diào)用者就很清晰的知道崔涂,如果我通過id獲取Plan的時(shí)候阳掐,是有可能因?yàn)檎也坏綊伋霎惓5模@樣的設(shè)計(jì)冷蚂,更起到警戒的作用缭保,調(diào)用者會(huì)很清晰的知道get(id)方法必須有返回值。
方法驗(yàn)證
還有一種方式寫法帝雇,已經(jīng)介紹過了涮俄,不在贅述了。
@Notnull
Plan get(id);
總結(jié)
三種接口定義方法尸闸,由你來選擇:
Optional get(id);
Plan get(id) throws PlanNotFoundException;
@Notnull
Plan get(id);
希望這樣的總結(jié)彻亲,可以給你帶來啟示
業(yè)務(wù)邏輯中的門面模式
我們經(jīng)常碰到這樣的場(chǎng)景孕锄,自己模塊的service通常要調(diào)用其他模塊的service使用,當(dāng)然苞尝,這種情況下畸肆,一般都會(huì)直接調(diào)用,然后完成自己的service.
我想說宙址,如果你是這么想的轴脐,恭喜你,后期你會(huì)遇到無窮無盡的service嵌套service抡砂,并且埋點(diǎn)做起來很難大咱。如果你的service很慢,你會(huì)檢查是你的service慢注益,還是調(diào)用其他人的service慢碴巾。這樣會(huì)給你早晨很大的麻煩.
那如何去做呢,我給大家一個(gè)建議
使用門面模式進(jìn)行封裝丑搔。
創(chuàng)建外部模塊
在自己的模塊創(chuàng)建 external 包厦瓢,用來包裝你調(diào)用的其他service
寫法
public interface PlanFamingFacade {
void checkFarmingTaskItem(@NotNull Plan plan,@NotNull Integer FarmingTaskItemId);
}
他是一個(gè)面向FamingService服務(wù)類的一個(gè)轉(zhuǎn)化層,我相信,好處應(yīng)該是不言而喻的吧
你可以做aop啤月,做埋點(diǎn)煮仇,檢測(cè)各種其他模塊調(diào)用的效率,而且谎仲,其他模塊變化的時(shí)候浙垫,你可以快速做出相應(yīng),比如做自己模塊的cache.
而且强重,你可以將調(diào)用模塊返回的對(duì)象绞呈,轉(zhuǎn)成你自己模塊的對(duì)象(BO 對(duì)象),這樣更加靈活。
總結(jié)
門面的模式间景,其實(shí)在微服務(wù)設(shè)計(jì)中也是常常存在的,比如調(diào)用不同服務(wù)時(shí)的熔斷機(jī)制艺智。
如果可以倘要,一定要使用門面模式,使用后的不久十拣,你會(huì)來感謝我的封拧!
業(yè)務(wù)異常設(shè)計(jì)
我之前詳細(xì)講過關(guān)于異常的理解,如果沒看過夭问,可以去看一下: 如何優(yōu)雅的設(shè)計(jì)java異常
這次要討論的是業(yè)務(wù)模塊中泽西,如何實(shí)際的使用和設(shè)計(jì)異常。以及異常在代碼中的寫法缰趋。
舉例:
工具: lombok
總異常碼設(shè)計(jì)
按照模塊來劃分大的異常碼
public class ErrorCodeBase {
public static final long HERBS = 20000;
public static final long PROCESSING = 30000L;
public static final long PLAN = 40000L;
public static final long SEED = 50000L;
public static final long SOLAR_TERM = 50000L;
public static final long BLOCK = 70000L;
public static final long PRODUCTION = 80000L;
public static final long COMPANY = 90000L;
}
通用異常設(shè)計(jì)
義務(wù)異常需要定為為RuntimeException,這樣寫的好處是不需要讓調(diào)用者通過異常來控制業(yè)務(wù)邏輯
public abstract class ServiceException extends RuntimeException{
private List errors ;
public ServiceException(String description,String errorCode,String errorMsg){
this(description);
this.addError(errorCode,errorMsg);
}
public ServiceException(String description){
super(description);
this.errors = Lists.newArrayList();
}
public ServiceException addError(String errorCode,String errorMsg){
this.errors.add(new ErrorInfo(errorCode,errorMsg));
return this ;
}
public List getErrors() {
return errors;
}
protected String errorCode(long base,long index){
return String.valueOf(base + index);
}
}
其中ErrorInfo比較簡(jiǎn)單:
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ErrorInfo {
private String code ;
private String message ;
}
主要是為了Controller中異常信息的轉(zhuǎn)化捧杉, 可以將異常信息合理的傳給前端陕见,進(jìn)行判斷和顯示
子模塊異常設(shè)計(jì)
子模塊通過ServiceException來定義各個(gè)子模塊的異常.
異常碼設(shè)計(jì),通過偏移量來進(jìn)行累加味抖,這樣統(tǒng)一管理评甜,避免異常碼重復(fù)
class ErrorCodes {
public static final String PLAN_NOT_FOUND = String.valueOf(ErrorCodeBase.PLAN + 1L);
public static final String YEAR_OUT_OF_BUNDS = String.valueOf(ErrorCodeBase.PLAN + 2L);
public static final String PLAN_TASK_NOT_FOUND = String.valueOf(ErrorCodeBase.PLAN + 3L);
public static final String PLAN_TASK_SUPPLY_NOT_FOUND = String.valueOf(ErrorCodeBase.PLAN + 4L);
public static final String PLAN_AT_LEAST_ONE_YEAR = String.valueOf(ErrorCodeBase.PLAN + 5L);
public static final String PLAN_HERBS_NOT_FOUND_EXCEPTION = String.valueOf(ErrorCodeBase.PLAN + 6L);
public static final String PLAN_NOT_FOUND_FARMING_TASK_ITEM_EXCEPTION = String.valueOf(ErrorCodeBase.PLAN + 7L);
public static final String PLAN_SOLAR_TERM_NOT_FOUND_EXCEPTION = String.valueOf(ErrorCodeBase.PLAN + 8L);
public static final String PLAN_COMPANY_ROLE_NOT_FOUND_EXCEPTION = String.valueOf(ErrorCodeBase.PLAN + 9L);
}
class PlanException extends ServiceException {
public PlanException(String errorCode, String errorMsg) {
super("plan_error", errorCode, errorMsg);
}
}
最終的子模塊的某異常類,如下:
public class PlanNotFoundException extends PlanException {
public PlanNotFoundException( String errorMsg) {
super(PLAN_NOT_FOUND, errorMsg);
}
public PlanNotFoundException() {
super(PLAN_NOT_FOUND, "種植計(jì)劃未找到");
}
}
業(yè)務(wù)異常使用
在業(yè)務(wù)層接口定義中,你可以暴露出可能出現(xiàn)的異常,然后由調(diào)用者選擇是否需要處理你的異常(因?yàn)槭欠鞘軝z異常),比如:
@NotNull
PlanTask addEmtpyPlanTask(@NotNull Integer planId, @NotNull String year)
throws PlanNotFoundException, YearIsOutOfBundsException;
當(dāng)然仔涩,對(duì)于中小型項(xiàng)目的異常處理忍坷,做法比較簡(jiǎn)單,可以在Controller層做一個(gè)通用的異常處理類熔脂,然后直接將ServiceException中的ErrorInfo直接轉(zhuǎn)成前端可讀的異常信息,做法比較簡(jiǎn)單佩研,我就不在贅述了,請(qǐng)看代碼:
@ExceptionHandler(ServiceException.class)
@ResponseStatus(HttpStatus.OK)
@ResponseBody
public ErrorEntity handlerServiceException(ServiceException e){
log.warn("ServiceException:"+e.getMessage(),e);
if( log.isDebugEnabled() ){
log.debug("--ServiceException:"+e.getMessage());
for(ErrorInfo errorInfo : e.getErrors()){
log.debug("----code:{},message:{}",errorInfo.getCode(),errorInfo.getMessage());
}
}
return new ErrorEntity()
.setStatus(HttpStatus.FORBIDDEN.value())
.setDescription(e.getMessage())
.setErrors(e.getErrors());
}
當(dāng)然霞揉,除了上述方式以外韧骗,如果調(diào)用者需要進(jìn)行異常的轉(zhuǎn)化,也可以直接try..catch,然后轉(zhuǎn)化成自己的異常零聚,或者做一些其他業(yè)務(wù)邏輯(但是不建議通過異常來控制業(yè)務(wù)流程)
異常設(shè)計(jì)最小化
我之前還設(shè)計(jì)過一種比較簡(jiǎn)化的異常方式袍暴,可以在小型項(xiàng)目中進(jìn)行異常處理(一定記住,不能因?yàn)轫?xiàng)目小隶症,就不進(jìn)行異常處理政模,它可以很簡(jiǎn)單,但不能沒有):
public enum Exceptions {
//權(quán)限角色
NOT_FIND_ROLE(1000001L,"找不到相關(guān)角色!"),
//學(xué)生模塊異常
STUDENT_NO_TEXIST(2000001L,"學(xué)生不存在"),
STUDENT_IS_TEXIST(2000002L,"學(xué)生存在"),
STUDENT_IMPORT_IS_TEXIST(2000003L,"導(dǎo)入的數(shù)據(jù)學(xué)生存在");
Long errCode;
String errMsg;
Exceptions(Long errCode, String errMsg){
this.errCode = errCode;
this.errMsg = errMsg;
}
public ServiceException exception(){
return new ServiceException(this.errCode,this.errMsg);
}
public ServiceException exception(Object errData){
return new ServiceException(this.errCode,errData,this.errMsg);
}
public ServiceException exception(Object errData,Throwable e){
return new ServiceException(this.errCode,this.errMsg,errData,e);
}
}
這個(gè)異常蚂会,在使用起來非常方便:
throw Exceptions.NOT_FIND_ROLE.exception();
這樣的簡(jiǎn)約風(fēng)格淋样,可以給你帶來很好的效果
因?yàn)檫@樣的設(shè)計(jì)是對(duì)異常碼和異常信息編程,而不是對(duì)異常類型進(jìn)行編程胁住。
所以缺點(diǎn)就是趁猴,在service接口定義中,不能指定異常類型彪见。
一種簡(jiǎn)單風(fēng)格的舉例
“如果找不到儡司,需要拋出異常”余指,這是一個(gè)很常見的設(shè)計(jì)方式捕犬,接口定義如下:
void deletePlan(@NotNull Integer id) throws PlanNotFoundException;
實(shí)現(xiàn)起來,建議配合jdk8一起使用:
Plan plan = Optional.ofNullable(planRepository.selectByPrimaryKey(id))
.orElseThrow(PlanNotFoundException::new);
因?yàn)椴挥靡恢眎f..else的判斷酵镜,所以碉碉,你的代碼會(huì)很干凈。
總結(jié)
兩種異常風(fēng)格的設(shè)計(jì)淮韭,和如何使用都在這里了垢粮,建議這么去設(shè)計(jì)你的異常。
無論異常處理怎么復(fù)雜靠粪,都是一個(gè)異常鏈調(diào)用的過程蜡吧。非常簡(jiǎn)單!
枚舉狀態(tài)設(shè)計(jì)
你常常會(huì)在義務(wù)代碼中見到這樣的需求毫蚓,這條消息的狀態(tài)可能是已刪除,或者這條訂單的狀態(tài)是已發(fā)貨,這時(shí)候斩跌,則會(huì)用到枚舉值,我給大家的建議是,枚舉值一定要設(shè)計(jì)標(biāo)志位,這樣绍些,可以將標(biāo)志位存在數(shù)據(jù)庫中,減少存儲(chǔ)大小耀鸦,也可以在代碼中確保標(biāo)志位不會(huì)改變柬批。
設(shè)計(jì)枚舉
public enum NeedGpsEnum {
NEED(0, "需要"),
NO_NEED(1, "不需要");
NeedGpsEnum(int value, String type) {
this.value = value;
this.type = type;
}
private int value;
private String type;
public int getValue() {
return value;
}
}
這樣的設(shè)計(jì),保證了NEED 和NO_NEED的標(biāo)志位(0/1)是不會(huì)因?yàn)槊杜e位置的改變而變動(dòng)的袖订,而且氮帐,從代碼可讀性角度,我們可以知道NEED和NO_NEED是什么意思(需要/不需要)洛姑。
這樣的可讀性和易用性都比較強(qiáng)上沐,建議這么寫!
枚舉應(yīng)用場(chǎng)景
《Effective java》中,建議可以用枚舉來實(shí)現(xiàn)單例楞艾,但是我不建議你這么做参咙,他的語義是很不清晰的。
真正的枚舉用法硫眯,我理解可以分為以下幾種:
狀態(tài)模式/策略模式
業(yè)務(wù)屬性狀態(tài)(如上)
固定的常量屬性(比如 月份這樣固定的枚舉值)
希望大家按照這三種用法去使用蕴侧。這樣的設(shè)計(jì)會(huì)給你帶來很多好處。
總結(jié)
枚舉的使用場(chǎng)景給大家介紹完了两入,希望你再創(chuàng)新之前净宵,可以先按照這樣的思想去思考。等你真正創(chuàng)新裹纳,想到了新的枚舉用法择葡,請(qǐng)給我留言。
原文鏈接:http://lrwinx.github.io/2017/10/13/java%E7%BC%96%E7%A8%8B%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5/