主頁 > 軟體設計 > 搞定SpringBoot難題!設計優秀的后端介面?輕松解決

搞定SpringBoot難題!設計優秀的后端介面?輕松解決

2020-11-10 14:30:30 軟體設計

1 概述

本篇文章以Spring Boot為基礎,從以下三個方向講述了如何設計一個優秀的后端介面體系:

  • 引數校驗:涉及Hibernate Validator的各種注解,快速失敗模式,分組,組序列以及自定義注解/Validator
  • 例外處理:涉及ControllerAdvice/@RestControllerAdvice以及@ExceptionHandler
  • 資料回應:涉及如何設計一個回應體以及如何包裝回應體

有了一個優秀的后端介面體系,不僅有了規范,同時擴展新的介面也很容易,本文演示了如何從零一步步構建一個優秀的后端介面體系,

2 新建工程

打開熟悉的IDEA,選擇依賴:

搞定SpringBoot難題!設計優秀的后端介面?輕松解決

首先創建如下檔案:

搞定SpringBoot難題!設計優秀的后端介面?輕松解決

TestController.java:

@RestController
@RequestMapping("/")
@CrossOrigin(value = "http://localhost:3000")
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class TestController {
    private final TestService service;
    @PostMapping("test")
    public String test(@RequestBody User user)
    {
        return service.test(user);
    }

使用了@RequiredArgsConstructor代替@Autowired,由于筆者使用Postwoman測驗,因此需要加上跨域注解@CrossOrigin,默認3000埠(Postwoman埠),

TestService.java:

@Service
public class TestService {
    public String test(User user)
    {
        if(StringUtils.isEmpty(user.getEmail()))
            return "郵箱不能為空";
        if(StringUtils.isEmpty(user.getPassword()))
            return "密碼不能為空";
        if(StringUtils.isEmpty(user.getPhone()))
            return "電話不能為空";
//        持久化操作
        return "success";
    }
}

業務層首先進行了引數校驗,這里省略了持久化操作,

User.java:

@Data
public class User {
    private String phone;
    private String password;
    private String email;
}

3 引數校驗

首先來看一下引數校驗,上面的例子中在業務層完成引數校驗,這是沒有問題的,但是,還沒進行業務操作就需要進行這么多的校驗顯然這不是很好,更好的做法是,使用Hibernate Validator,

3.1 Hibernate Validator

3.1.1 介紹

JSR是Java Specification Requests的縮寫,意思是Java規范提案,是指向JCP(Java Community Process)提出新增一個標準化技術規范的正式請求,JSR-303是Java EE6中的一項子規范,叫作Bean Validation,Hibernate Validator是Bean Validator的參考實作,除了實作所有JSR-303規范中的內置constraint實作,還有附加的constraint,詳細如下:

  • @Null:被注解元素必須為null(為了節省篇幅下面用“元素”代表“被注解元素必須為”)
  • @NotNull:元素不為null
  • @AssertTrue:元素為true
  • @AssertFalse:元素為false
  • @Min(value):元素大于或等于指定值
  • @Max(value):元素小于或等于指定值
  • @DecimalMin(value):元素大于指定值
  • @DecimalMax(value):元素小于指定值
  • @Size(max,min):元素大小在給定范圍內
  • @Digits(integer,fraction):元素字串中的整數位數規定最大integer位,小數位數規定最大fraction位
  • @Past:元素是一個過去日期
  • @Future:元素是將來日期
  • @Pattern:元素需要符合正則運算式

其中Hibernate Validator附加的constraint如下:

  • @Eamil:元素為郵箱
  • @Length:字串大小在指定范圍內
  • @NotEmpty:字串必須非空(目前最新的6.1.5版本已棄用,建議使用標準的@NotEmpty)
  • @Range:數字在指定范圍內

而在Spring中,對Hibernate Validation進行了二次封裝,添加了自動校驗,并且校驗資訊封裝進了特定的BindingResult中,下面看看如何使用,

3.1.2 使用

在各個欄位加上@NotEmpty,并且郵箱加上@Email,電話加上11位限制,并且在各個注解加上message,表示對應的提示資訊:

@Data
public class User {
    @NotEmpty(message = "電話不能為空")
    @Length(min = 11,max = 11,message = "電話號碼必須11位")
    private String phone;
    @NotEmpty(message = "密碼不能為空")
    @Length(min = 6,max = 20,message = "密碼必須為6-20位")
    private String password;
    @NotEmpty(message = "郵箱不能為空")
    @Email(message = "郵箱格式不正確")
    private String email;
}

對于String來說有時候會使用@NotNull或@NotBlank,它們的區別如下:

  • @NotEmpty:不能為null并且長度必須大于0,除了String外,對于Collection/Map/陣列也適用
  • @NotBlank:只用于String,不能為null,并且呼叫trim()后,長度必須大于0,也就是必須有除空格外的實際字符
  • @NotNull:不能為null

接著把業務層的引數校驗操作洗掉,并把控制層修改如下:

@PostMapping("test")
public String test(@RequestBody @Valid User user, BindingResult bindingResult)
{
    if(bindingResult.hasErrors())
    {
        for(ObjectError error:bindingResult.getAllErrors())
            return error.getDefaultMessage();
    }
    return service.test(user);
}

在需要校驗的物件上加上@Valid,并且加上BindingResult引數,可以從中獲取錯誤資訊并回傳,

3.1.3 測驗

全部都使用錯誤的引數設定,回傳”郵箱格式不正確“:

搞定SpringBoot難題!設計優秀的后端介面?輕松解決

第二次測驗中除了密碼都使用正確的引數,回傳”密碼必須為6-20位“:

搞定SpringBoot難題!設計優秀的后端介面?輕松解決

第三次測驗全部使用正確的引數,回傳”success“:

搞定SpringBoot難題!設計優秀的后端介面?輕松解決

3.2 校驗模式設定

Hibernate Validator有兩種校驗模式:

  • 普通模式:默認模式,會校驗所有屬性,然后回傳所有的驗證失敗資訊
  • 快速失敗模式:只要有一個驗證失敗就回傳

使用快速失敗模式需要通過HibernateValidateConfiguration以及ValidateFactory創建Validator,并且使用Validator.validate()進行手動驗證,

首先添加一個生成Validator的類:

@Configuration
public class FailFastValidator<T> {
    private final Validator validator;
    public FailFastValidator()
    {
        validator = Validation
        .byProvider(HibernateValidator.class).configure()
        .failFast(true).buildValidatorFactory()
        .getValidator();
    }

    public Set<ConstraintViolation<T>> validate(T user)
    {
        return validator.validate(user);
    }

修改控制層的代碼,通過@RequiredArgsConstructor注入FailFastValidator<User>,并把原來的在User上的@Valid去掉,在方法體進行手動驗證:

@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class TestController {
    private final TestService service;
    private final FailFastValidator<User> validator;
    @PostMapping("test")
    public String test(@RequestBody User user, BindingResult bindingResult)
    {
        Set<ConstraintViolation<User>> message = validator.validate(user);
        message.forEach(t-> System.out.println(t.getMessage()));
//        if(bindingResult.hasErrors())
//        {
//            bindingResult.getAllErrors().forEach(t->System.out.println(t.getDefaultMessage()));
//            for(ObjectError error:bindingResult.getAllErrors())
//                return error.getDefaultMessage();
//        }
        return service.test(user);
    }
}

測驗(連續三次校驗的結果):

搞定SpringBoot難題!設計優秀的后端介面?輕松解決

如果是普通模式(修改.failFast(false)),一次校驗便會連續輸出三個資訊:

搞定SpringBoot難題!設計優秀的后端介面?輕松解決

3.3 @Valid與@Validated

@Valid是javax.validation包里面的,而@Validated是org.springframework.validation.annotation里面的,是@Valid的一次封裝,相當于是@Valid的增強版,供Spring提供的校驗機制使用,相比起@Valid,@Validated提供了分組以及組序列的功能,下面分別進行介紹,

3.4 分組

當需要在不同的情況下使用不同的校驗方式時,可以使用分組校驗,比如在注冊時不需要校驗id,修改資訊時需要校驗id,但是默認的校驗方式在兩種情況下全部都校驗,這時就需要使用分組校驗,

下面以不同的組別校驗電話號碼長度的不同進行說明,修改User類如下:

@Data
public class User {
    @NotEmpty(message = "電話不能為空")
    @Length(min = 11,max = 11,message = "電話號碼必須11位",groups = {GroupA.class})
    @Length(min = 12,max = 12,message = "電話號碼必須12位",groups = {GroupB.class})
    private String phone;
    @NotEmpty(message = "密碼不能為空")
    @Length(min = 6,max = 20,message = "密碼必須為6-20位")
    private String password;
    @NotEmpty(message = "郵箱不能為空")
    @Email(message = "郵箱格式不正確")
    private String email;

    public interface GroupA{}
    public interface GroupB{}
}

在@Length中加入了組別,GroupA表示電話需要為11位,GroupB表示電話需要為12位,GroupA/GroupB是User中的兩個空介面,然后修改控制層:

public String test(@RequestBody @Validated({User.GroupB.class}) User user, BindingResult bindingResult)
{
    if(bindingResult.hasErrors())
    {
        bindingResult.getAllErrors().forEach(t->System.out.println(t.getDefaultMessage()));
        for(ObjectError error:bindingResult.getAllErrors())
            return error.getDefaultMessage();
    }
    return service.test(user);
}

在@Validated中指定為GroupB,電話需要為12位,測驗如下:

搞定SpringBoot難題!設計優秀的后端介面?輕松解決

3.5 組序列

默認情況下,不同組別的約束驗證的無序的,也就是說,對于下面的User類:

@Data
public class User {
    @NotEmpty(message = "電話不能為空")
    @Length(min = 11,max = 11,message = "電話號碼必須11位")
    private String phone;
    @NotEmpty(message = "密碼不能為空")
    @Length(min = 6,max = 20,message = "密碼必須為6-20位")
    private String password;
    @NotEmpty(message = "郵箱不能為空")
    @Email(message = "郵箱格式不正確")
    private String email;
}

每次進行校驗的順序不同,三次測驗結果如下:

搞定SpringBoot難題!設計優秀的后端介面?輕松解決

搞定SpringBoot難題!設計優秀的后端介面?輕松解決

有些時候順序并不重要,而有些時候順序很重要,比如:

  • 第二個組中的約束驗證依賴于一個穩定狀態運行,而這個穩定狀態由第一個組來進行驗證
  • 某個組的驗證比較耗時,CPU和記憶體的使用率相對較大,最優的選擇是將其放在最后進行驗證

因此在進行組驗證的時候需要提供一種有序的驗證方式,一個組可以定義為其他組的序列,這樣就可以固定每次驗證的順序而不是隨機順序,另外如果驗證組序列中,前面的組驗證失敗,則后面的組不會驗證,

例子如下,首先修改User類并定義組序列:

@Data
public class User {
    @NotEmpty(message = "電話不能為空",groups = {First.class})
    @Length(min = 11,max = 11,message = "電話號碼必須11位",groups = {Second.class})
    private String phone;
    @NotEmpty(message = "密碼不能為空",groups = {First.class})
    @Length(min = 6,max = 20,message = "密碼必須為6-20位",groups = {Second.class})
    private String password;
    @NotEmpty(message = "郵箱不能為空",groups = {First.class})
    @Email(message = "郵箱格式不正確",groups = {Second.class})
    private String email;

    public interface First{}
    public interface Second{}
    @GroupSequence({First.class,Second.class})
    public interface Group{}
}

定義了兩個空介面First和Second表示順序,同時在Group中使用@GroupSequence指定了順序,

接著修改控制層,在@Validated中定義組:

 

這樣就能按照固定的順序進行引數校驗了,

3.6 自定義校驗

盡管Hibernate Validator中的注解適用情況很廣了,但是有時候需要特定的校驗規則,比如密碼強度,人為判定弱密碼還是強密碼,也就是說,此時需要添加自定義校驗的方式,有兩種處理方法:

  • 自定義注解
  • 自定義Validator

首先來看一下自定義注解的方法,

3.6.1 自定義注解

這里添加一個判定弱密碼的注解WeakPassword:

@Documented
@Constraint(validatedBy = WeakPasswordValidator.class)
@Target({ElementType.METHOD,ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface WeakPassword{
    String message() default "請使用更加強壯的密碼";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

同時添加一個實作了ConstraintValidator<A,T>的WeakPasswordValidator,當密碼長度大于10位時才符合條件,否則回傳false表示校驗不通過:

public class WeakPasswordValidator implements ConstraintValidator<WeakPassword,String> {
    @Override
    public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {
        return s.length() > 10;
    }
    @Override
    public void initialize(WeakPassword constraintAnnotation) {}
}

接著可以修改User如下,在對應的欄位加上自定義注解@WeakPassword:

@Data
public class User {
    //...
    @WeakPassword(groups = {Second.class})
    private String password;
    //...
}

測驗如下:

搞定SpringBoot難題!設計優秀的后端介面?輕松解決

3.6.2 自定義Validator

除了自定義注解之外,還可以自定義Validator來實作自定義的引數校驗,需要實作Validator介面:

@Component
public class WeakPasswordValidator implements Validator{
    @Override
    public boolean supports(Class<?> aClass) {
        return User.class.equals(aClass);
    }

    @Override
    public void validate(Object o, Errors errors) {
        ValidationUtils.rejectIfEmpty(errors,"password","password.empty");
        User user = (User)o;
        if(user.getPassword().length() <= 10)
            errors.rejectValue("password","Password is not strong enough!");
    }
}

實作其中的supports以及validate:

  • support:可以驗證該類是否是某個類的實體
  • validate:當supports回傳true后,驗證給定物件o,當出現錯誤時,向errors注冊錯誤

ValidationUtils.rejectIfEmpty校驗當物件o中某個欄位屬性為空時,向其中的errors注冊錯誤,注意并不會中斷陳述句的運行,也就是即使password為空,user.getPassword()還是會運行,這時會拋出空指標例外,下面的errors.rejectValue同樣道理,并不會中斷陳述句的運行,只是注冊了錯誤資訊,中斷的話需要手動拋出例外,

修改控制層中的回傳值,改為getCode():

if(bindingResult.hasErrors())
{
    bindingResult.getAllErrors().forEach(t-> System.out.println(t.getCode()));
    for(ObjectError error:bindingResult.getAllErrors())
        return error.getCode();
}
return service.test(user);

測驗:

搞定SpringBoot難題!設計優秀的后端介面?輕松解決

4 例外處理

到這里引數校驗就完成了,下一步是處理例外,

如果將引數校驗中的BindingResult去掉,就會將整個后端例外回傳給前端:

//public String test(@RequestBody @Validated({User.Group.class}) User user, BindingResult bindingResult)
public String test(@RequestBody @Validated({User.Group.class}) User user)
復制代碼

搞定SpringBoot難題!設計優秀的后端介面?輕松解決

這樣雖然后端是方便了,不需要每一個介面都加上BindingResult,但是前端不好處理,整個例外都回傳了,因此后端需要捕捉這些例外,但是,不能手動去捕捉每一個,這樣還不如之前使用BindingResult,這種情況下就需要用到全域的例外處理,

4.1 基本使用

處理全域例外的步驟如下:

  • 創建全域例外處理的類:加上@ControllerAdvice/@RestControllerAdvice注解(取決于控制層用的是@Controller/@RestController,@Controller可以跳轉到相應頁面,回傳JSON等加上@ResponseBody即可,而@RestController相當于@Controller+@ResponseBody,回傳JSON無需加上@ResponseBody,但是視圖決議器無法決議jsp以及html頁面)
  • 創建例外處理方法:加上@ExceptionHandler指定想要處理的例外型別
  • 處理例外:在對應的處理例外方法中處理例外

這里增加一個全域例外處理類GlobalExceptionHandler:

@RestControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public String methodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e)
    {
        ObjectError error = e.getBindingResult().getAllErrors().get(0);
        return error.getDefaultMessage();
    }
}

首先加上@RestControllerAdvice,并在例外處理方法上加上@ExceptionHandler,

接著修改控制層,去掉其中的BindingResult:

@PostMapping("test")
public String test(@RequestBody @Validated({User.Group.class}) User user)
{
    return service.test(user);
}

然后就可以進行測驗了:

搞定SpringBoot難題!設計優秀的后端介面?輕松解決

全域例外處理相比起原來的每一個介面都加上BindingResult方便很多,而且可以集中處理所有例外,

4.2 自定義例外

很多時候都會用到自定義例外,這里新增一個測驗例外TestException:

@Data
public class TestException extends RuntimeException{
    private int code;
    private String msg;

    public TestException(int code,String msg)
    {
        super(msg);
        this.code = code;
        this.msg = msg;
    }

    public TestException()
    {
        this(111,"測驗例外");
    }

    public TestException(String msg)
    {
        this(111,msg);
    }
}

接著在剛才的全域例外處理類中添加一個處理該例外的方法:

@ExceptionHandler(TestException.class)
public String testExceptionHandler(TestException e)
{
    return e.getMsg();
}

在控制層進行測驗:

@PostMapping("test")
public String test(@RequestBody @Validated({User.Group.class}) User user)
{
    throw new TestException("出現例外");
//        return service.test(user);
}

結果如下:

搞定SpringBoot難題!設計優秀的后端介面?輕松解決

5 資料回應

在處理好了引數校驗以及例外處理之后,下一步就是要設定統一的規范化的回應資料,一般來說無論回應成功還是失敗都會有一個狀態碼,回應成功還會攜帶回應資料,回應失敗則攜帶相應的失敗資訊,因此,第一步是設計一個統一的回應體,

5.1 統一回應體

統一回應體需要創建回應體類,一般來說,回應體需要包含:

  • 狀態碼:String/int
  • 回應資訊:String
  • 回應資料:Object/T(泛型)

這里簡單的定義一個統一回應體Result:

@Data
@AllArgsConstructor
public class Result<T> {
    private String code;
    private String message;
    private T data;
}

接著修改全域例外處理類:

@RestControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Result<String> methodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e)
    {
        ObjectError error = e.getBindingResult().getAllErrors().get(0);
        return new Result<>(error.getCode(),"引數校驗失敗",error.getDefaultMessage());
    }

    @ExceptionHandler(TestException.class)
    public Result<String> testExceptionHandler(TestException e)
    {
        return new Result<>(e.getCode(),"失敗",e.getMsg());
    }

使用Result<String>封裝回傳值,測驗如下:

搞定SpringBoot難題!設計優秀的后端介面?輕松解決

可以看到回傳了一個比較友好的資訊,無論是回應成功還是回應失敗都會回傳同一個回應體,當需要回傳具體的用戶資料時,可以修改控制層介面直接回傳Result<User>:

@PostMapping("test")
public Result<User> test(@RequestBody @Validated({User.Group.class}) User user)
{
    return service.test(user);
}

測驗:

搞定SpringBoot難題!設計優秀的后端介面?輕松解決

5.2 回應碼列舉

通常來說可以把回應碼做成列舉類:

@Getter
public enum ResultCode {
    SUCCESS("111","成功"),FAILED("222","失敗");

    private final String code;
    private final String message;
    ResultCode(String code,String message)
    {
        this.code = code;
        this.message = message;
    }
}

列舉類封裝了狀態碼以及資訊,這樣在回傳結果時,只需要傳入對應的列舉值以及資料即可:

@RestControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Result<String> methodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e)
    {
        ObjectError error = e.getBindingResult().getAllErrors().get(0);
        return new Result<>(ResultCode.FAILED,error.getDefaultMessage());
    }

    @ExceptionHandler(TestException.class)
    public Result<String> testExceptionHandler(TestException e)
    {
        return new Result<>(ResultCode.FAILED,e.getMsg());
    }
}

5.3 全域包裝回應體

統一回應體是個很好的想法,但是還可以再深入一步去優化,因為每次回傳之前都需要對回應體進行包裝,雖然只是一行代碼但是每個介面都需要包裝一下,這是個很麻煩的操作,為了更進一步“偷懶”,可以選擇實作ResponseBodyAdvice<T>來進行全域的回應體包裝,

修改原來的全域例外處理類如下:

@RestControllerAdvice
public class GlobalExceptionHandler implements ResponseBodyAdvice<Object> {
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Result<String> methodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e)
    {
        ObjectError error = e.getBindingResult().getAllErrors().get(0);
        return new Result<>(ResultCode.FAILED,error.getDefaultMessage());
    }

    @ExceptionHandler(TestException.class)
    public Result<String> testExceptionHandler(TestException e)
    {
        return new Result<>(ResultCode.FAILED,e.getMsg());
    }

    @Override
    public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
        return !methodParameter.getParameterType().equals(Result.class);
    }

    @Override
    public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
        return new Result<>(o);
    }
}

實作了ResponseBodyAdvice<Object>:

  • supports方法:判斷是否支持控制器回傳方法型別,可以通過supports判斷哪些型別需要包裝,哪些不需要包裝直接回傳
  • beforeBodyWrite方法:當supports回傳true后,對資料進行包裝,這樣在回傳資料時就無需使用Result<User>手動包裝,而是直接回傳User即可

接著修改控制層,直接回傳物體類User而不是回應體包裝類Result<User>:

@PostMapping("test")
public User test(@RequestBody @Validated({User.Group.class}) User user)
{
    return service.test(user);
}

測驗輸出如下:

搞定SpringBoot難題!設計優秀的后端介面?輕松解決

5.4 繞過全域包裝

雖然按照上面的方式可以使后端的資料全部按照統一的形式回傳給前端,但是有時候并不是回傳給前端而是回傳給其他第三方,這時候不需要code以及msg等資訊,只是需要資料,這樣的話,可以提供一個在方法上的注解來繞過全域的回應體包裝,

比如添加一個@NotResponseBody注解:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface NotResponseBody {
}

接著需要在處理全域包裝的類中,在supports中進行判斷:

@Override
public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
    return !(
        methodParameter.getParameterType().equals(Result.class) 
        ||
        methodParameter.hasMethodAnnotation(NotResponseBody.class)
    );
}

最后修改控制層,在需要繞過的方法上添加自定義注解@NotResponseBody即可:

@PostMapping("test")
@NotResponseBody
public User test(@RequestBody @Validated({User.Group.class}) User user)

6 總結

搞定SpringBoot難題!設計優秀的后端介面?輕松解決

7 原始碼

直接clone下來使用IDEA打開即可,每一次優化都做了一次提交,可以看到優化的程序,喜歡的話歡迎給個star:

  • Github
  • 碼云


作者:氷泠
鏈接:https://juejin.im/post/6860404263143604232
來源:掘金

轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/209338.html

標籤:其他

上一篇:關于c#中的單例模式

下一篇:SpringBoot啟動類的掃描注解的用法及沖突原則

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 面試突擊第一季,第二季,第三季

    第一季必考 https://www.bilibili.com/video/BV1FE411y79Y?from=search&seid=15921726601957489746 第二季分布式 https://www.bilibili.com/video/BV13f4y127ee/?spm_id_fro ......

    uj5u.com 2020-09-10 05:35:24 more
  • 第三單元作業總結

    1.前言 這應該是本學期最后一次寫作業總結了吧。總體來說,對作業的節奏也差不多掌握了,作業做起來的效率也更高了。雖然和之前的作業一樣,作業中都要用到新的知識,但是相比之前,更加懂得了如何利用工具以及資料。雖然之間卡過殼,但總體而言,這幾次作業還算完成的比較好。 2.作業程序總結 相比前兩個單元,此單 ......

    uj5u.com 2020-09-10 05:35:41 more
  • 北航OO(2020)第四單元博客作業暨課程總結博客

    北航OO(2020)第四單元博客作業暨課程總結博客 本單元作業的架構設計 在本單元中,由于UML圖具有比較清晰的樹形結構,因此我對其中需要進行查詢操作的元素進行了包裝,在樹的父節點中存盤所有孩子的參考。考慮到性能問題,我采用了快取機制,一次查詢后盡可能快取已經遍歷過的資訊,以減少遍歷次數。 本單元我 ......

    uj5u.com 2020-09-10 05:35:48 more
  • BUAA_OO_第四單元

    一、UML決議器設計 ? 先看下題目:第四單元實作一個基于JDK 8帶有效性檢查的UML(Unified Modeling Language)類圖,順序圖,狀態圖分析器 MyUmlInteraction,實際上我們要建立一個有向圖模型,UML中的物件(元素)可能與同級元素連接,也可與低級元素相連形成 ......

    uj5u.com 2020-09-10 05:35:54 more
  • 6.1邏輯運算子

    邏輯運算子 1. && 短路與 運算式1 && 運算式2 01.運算式1為true并且運算式2也為true 整體回傳為true 02.運算式1為false,將不會執行運算式2 整體回傳為false 03.只要有一個運算式為false 整體回傳為false 2. || 短路或 運算式1 || 運算式2 ......

    uj5u.com 2020-09-10 05:35:56 more
  • BUAAOO 第四單元 & 課程總結

    1. 第四單元:StarUml檔案決議 本單元采用了圖模型決議UML。 UML檔案可以抽象為圖、子圖、邊的邏輯結構。 在實作中,圖的節點包括類、介面、屬性,子圖包括狀態圖、順序圖等。 采用了三次遍歷UML元素的方法建圖,第一遍遍歷建點,第二、三次遍歷設定屬性、連邊,實作圖物件的初始化。這里借鑒了一些 ......

    uj5u.com 2020-09-10 05:36:06 more
  • 談談我對C# 多型的理解

    面向物件三要素:封裝、繼承、多型。 封裝和繼承,這兩個比較好理解,但要理解多型的話,可就稍微有點難度了。今天,我們就來講講多型的理解。 我們應該經常會看到面試題目:請談談對多型的理解。 其實呢,多型非常簡單,就一句話:呼叫同一種方法產生了不同的結果。 具體實作方式有三種。 一、多載 多載很簡單。 p ......

    uj5u.com 2020-09-10 05:36:09 more
  • Python 資料驅動工具:DDT

    背景 python 的unittest 沒有自帶資料驅動功能。 所以如果使用unittest,同時又想使用資料驅動,那么就可以使用DDT來完成。 DDT是 “Data-Driven Tests”的縮寫。 資料:http://ddt.readthedocs.io/en/latest/ 使用方法 dd. ......

    uj5u.com 2020-09-10 05:36:13 more
  • Python里面的xlrd模塊詳解

    那我就一下面積個問題對xlrd模塊進行學習一下: 1.什么是xlrd模塊? 2.為什么使用xlrd模塊? 3.怎樣使用xlrd模塊? 1.什么是xlrd模塊? ?python操作excel主要用到xlrd和xlwt這兩個庫,即xlrd是讀excel,xlwt是寫excel的庫。 今天就先來說一下xl ......

    uj5u.com 2020-09-10 05:36:28 more
  • 當我們創建HashMap時,底層到底做了什么?

    jdk1.7中的底層實作程序(底層基于陣列+鏈表) 在我們new HashMap()時,底層創建了默認長度為16的一維陣列Entry[ ] table。當我們呼叫map.put(key1,value1)方法向HashMap里添加資料的時候: 首先,呼叫key1所在類的hashCode()計算key1 ......

    uj5u.com 2020-09-10 05:36:38 more
最新发布
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:20:47 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:20:25 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:20:17 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:20:10 more
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:19:44 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:19:07 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:18:57 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:18:49 more
  • 05單件模式

    #經典的單件模式 public class Singleton { private static Singleton uniqueInstance; //一個靜態變數持有Singleton類的唯一實體。 // 其他有用的實體變數寫在這里 //構造器宣告為私有,只有Singleton可以實體化這個類! ......

    uj5u.com 2023-04-19 08:42:51 more
  • 【架構與設計】常見微服務分層架構的區別和落地實踐

    軟體工程的方方面面都遵循一個最基本的道理:沒有銀彈,架構分層模型更是如此,每一種都有各自優缺點,所以請根據不同的業務場景,并遵循簡單、可演進這兩個重要的架構原則選擇合適的架構分層模型即可。 ......

    uj5u.com 2023-04-19 08:42:41 more