講解在真正項(xiàng)目開發(fā)中常見的6個(gè)方面熊经,java編程的解決方法

本篇文章從”數(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/

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末剃氧,一起剝皮案震驚了整個(gè)濱河市敏储,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌她我,老刑警劉巖虹曙,帶你破解...
    沈念sama閱讀 222,590評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異番舆,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)矾踱,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,157評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門恨狈,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人呛讲,你說我怎么就攤上這事禾怠》捣睿” “怎么了?”我有些...
    開封第一講書人閱讀 169,301評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵吗氏,是天一觀的道長(zhǎng)芽偏。 經(jīng)常有香客問我,道長(zhǎng)弦讽,這世上最難降的妖魔是什么污尉? 我笑而不...
    開封第一講書人閱讀 60,078評(píng)論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮往产,結(jié)果婚禮上被碗,老公的妹妹穿的比我還像新娘。我一直安慰自己仿村,他們只是感情好锐朴,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,082評(píng)論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著蔼囊,像睡著了一般焚志。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上畏鼓,一...
    開封第一講書人閱讀 52,682評(píng)論 1 312
  • 那天酱酬,我揣著相機(jī)與錄音,去河邊找鬼滴肿。 笑死岳悟,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的泼差。 我是一名探鬼主播贵少,決...
    沈念sama閱讀 41,155評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼堆缘!你這毒婦竟也來了滔灶?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,098評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤吼肥,失蹤者是張志新(化名)和其女友劉穎录平,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體缀皱,經(jīng)...
    沈念sama閱讀 46,638評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡斗这,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,701評(píng)論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了啤斗。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片表箭。...
    茶點(diǎn)故事閱讀 40,852評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖钮莲,靈堂內(nèi)的尸體忽然破棺而出免钻,到底是詐尸還是另有隱情彼水,我是刑警寧澤,帶...
    沈念sama閱讀 36,520評(píng)論 5 351
  • 正文 年R本政府宣布极舔,位于F島的核電站凤覆,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏拆魏。R本人自食惡果不足惜盯桦,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,181評(píng)論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望稽揭。 院中可真熱鬧俺附,春花似錦、人聲如沸溪掀。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,674評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽揪胃。三九已至璃哟,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間喊递,已是汗流浹背随闪。 一陣腳步聲響...
    開封第一講書人閱讀 33,788評(píng)論 1 274
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留骚勘,地道東北人铐伴。 一個(gè)月前我還...
    沈念sama閱讀 49,279評(píng)論 3 379
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像俏讹,于是被迫代替她去往敵國(guó)和親当宴。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,851評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容