8. Handle exceptions uniformly in the SpringMVC framework
The SpringMVC framework provides a unified exception handling mechanism (which can also be used directly in the SpringBoot framework), so that each exception only needs to be handled once, even if an exception occurs in multiple requests, there is no need to handle it repeatedly! The core is that when a developer calls a method that might throw an exception, the exception is directly thrown again in the controller. Then SpringMVC will catch the corresponding exception object when it calls the method of the controller. Moreover, if the developer defines a unified method for handling exceptions, The SpringMVC framework automatically calls this method to handle exceptions!
On the unified approach to handling exceptions:
-
By default, this method only applies to requests that are related to the current controller class. For example, if this method is written to UserController, it only applies to requests that are processed in UserController. If an exception occurs during the execution of a method on another controller, it will not be handled! There are two possible solutions to this problem:
- Write the method to handle the exception in the base class of the controller class, and all the controller classes inherit from the base class.
- Define methods to handle exceptions in any class and add them before the declaration of that class
@ControllerAdvice
or@RestControllerAdvice
Note that controller classes do not need to inherit from this class;
-
The @ExceptionHandler annotation must be added to the unified exception handling method;
-
You should use public permissions;
-
The type of return value, referring to the return value design principles for methods that handle requests;
-
Method names can be customized;
-
The parameter list of the method should add at least an exception type parameter to represent the exception object caught by the framework. The exception type of the parameter should be able to represent any exception to be handled. Other parameters can be added to the argument list of a method, but only those allowed by the SpringMVC framework, such as HttpServletRequest, HttpServletResponse, etc., cannot be added as arbitrary as the methods used to handle requests!
Can be in the project of cn. Tedu. Straw. Portal. The controller in the package to create GlobalExceptionHandler class, used for unified handling exceptions, add @ RestControllerAdvice before declaration of a class, Make the methods that handle exceptions in this class apply to the entire project, and add methods to this class to handle exceptions:
package cn.tedu.straw.portal.controller;
import cn.tedu.straw.portal.service.ex.ClassDisabledException;
import cn.tedu.straw.portal.service.ex.InsertException;
import cn.tedu.straw.portal.service.ex.InviteCodeException;
import cn.tedu.straw.portal.service.ex.PhoneDuplicateException;
import cn.tedu.straw.portal.vo.R;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler
public R handleException(Throwable e) {
if (e instanceof InviteCodeException) {
return R.failure(12, e);
} else if (e instanceof ClassDisabledException) {
return R.failure(13, e);
} else if (e instanceof PhoneDuplicateException) {
return R.failure(14, e);
} else if (e instanceof InsertException) {
return R.failure(15, e);
} else {
return R.failure(9999, e); }}}Copy the code
Error codes should be declared as static constants for uniform management and readability of code. Static constants can be declared using static internal interfaces in class R for easy declaration and management:
package cn.tedu.straw.portal.vo;
import lombok.Data;
import lombok.experimental.Accessors;
@Data
@Accessors(chain=true)
public class R {
private Integer state;
private String message;
public static R ok(a) {
return new R().setState(State.OK);
}
public static R failure(Integer state, String message) {
return new R().setState(state).setMessage(message);
}
public static R failure(Integer state, Throwable e) {
return failure(state, e.getMessage());
}
public static interface State {
int OK = 0;
int ERR_INVITE_CODE = 4001;
int ERR_CLASS_DISABLED = 4002;
int ERR_PHONE_DUPLICATE = 4003;
int ERR_INSERT = 4004;
int ERR_UNKNOWN = 9999; }}Copy the code
These static constants are then used for error codes when handling exceptions:
package cn.tedu.straw.portal.controller;
import cn.tedu.straw.portal.service.ex.ClassDisabledException;
import cn.tedu.straw.portal.service.ex.InsertException;
import cn.tedu.straw.portal.service.ex.InviteCodeException;
import cn.tedu.straw.portal.service.ex.PhoneDuplicateException;
import cn.tedu.straw.portal.vo.R;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler
public R handleException(Throwable e) {
if (e instanceof InviteCodeException) {
return R.failure(R.State.ERR_INVITE_CODE, e);
} else if (e instanceof ClassDisabledException) {
return R.failure(R.State.ERR_CLASS_DISABLED, e);
} else if (e instanceof PhoneDuplicateException) {
return R.failure(R.State.ERR_PHONE_DUPLICATE, e);
} else if (e instanceof InsertException) {
return R.failure(R.State.ERR_INSERT, e);
} else {
returnR.failure(R.State.ERR_UNKNOWN, e); }}}Copy the code
9. Verify the request parameters
For server-side development, all request parameters should be submitted by the client as it is not reliable, such as “user name” may be a letter, or other basic format is not correct (length, composition characters), but even if the client is checking mechanism is also unreliable, after all, the client is the possibility of been tampered with, Or the non-browser client may have a user using the version has not been updated, resulting in the request parameter format is wrong! Therefore, the server should check the validity of the request parameters as soon as it receives them!
Note: Even if the server side checks all parameters, the client side checks must exist! The main point is to intercept most of the wrong requests to reduce the pressure on the server side!
Serverside validation can be implemented using hibernate-validation, which is already integrated into spring-boot-starter-validation, so add this dependency to your project first:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
Copy the code
For this validation mechanism, the method is to verify the attributes of an object. Some annotations can be added before the attributes to be verified to indicate the validation rules. Common annotations are:
@NotNull
: No value is allowed, that is, yes is not allowednull
;@NotEmpty
: cannot be an empty string value, that is, the string length must be greater than 0.@NotBlank
: cannot be whitespace, that is, the string must contain characters other than whitespace, for example""
It is also wrong;@Pattern
: Regular expressions for validation can be defined in annotation parameters;@Size
: verifies that the length of a string value is within a certain range;- Other……
For example, you can add validation-related annotations before attributes of the User class. For example, you can add validation-related annotations before attributes of the Password class:
/** * Password */
@TableField("password")
@notblank (message = "Password cannot be blank!" )
@size (min = 4, Max = 16, message = "Password must be 4~16 characters!" )
private String password;
Copy the code
Then, in the controller class, in the parameter list of the method handling the request, add the @valid or @validated annotation before the verified object, and then add the BindingResult parameter. In the method body handling the request, judge the BindingResult parameter to obtain the verification result:
// http://localhost:8080/portal/user/student/register? inviteCode=JSD2003-111111&nickname=Hello&phone=13800138002&password=1234
@RequestMapping("/student/register")
public R studentRegister(String inviteCode,
@Validated User user, BindingResult bindingResult) {
if (inviteCode == null || inviteCode.length() < 4 || inviteCode.length() > 20) {
throw new ParameterValidationException("The invitation code must be 4-20 characters long!");
}
if (bindingResult.hasErrors()) {
String errorMessage = bindingResult
.getFieldError().getDefaultMessage();
log.debug("validation has error : {}", errorMessage);
throw new ParameterValidationException(errorMessage);
}
userService.registerStudent(user, inviteCode);
return R.ok();
}
Copy the code
About the above verification:
- The object being validated must be 1;
- Encapsulate the validation result
BindingResult
Must be declared after the parameter being validated; - The verification framework can not complete all the verification requirements, if the verification framework can not do some verification rules, you can write your own verification rules;
- If something goes wrong during validation and is not used in the controller
BindingResult
An error message is received and thrownBindException
It is also possible to handle the exception directly in the code that handles the exception; - The demo code above also covers this
R
andGlobalExceptionHandler
These two classes and other related content.
10. Sign up for front-end page tests
In order to avoid Spring Security interceptor asynchronous request, need to customize the configuration class, inherited from WebSecurityConfigurerAdapter, rewrite the protected void the configure (HttpSecurity HTTP), Call the CSRF ().disable() method of the parameter object so that AJAX requests can be submitted in the web page!
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
}
Copy the code
The front end will use Vue to access page elements and AJAX to submit asynchronous requests and process the results. The basic use of these two front end frameworks is illustrated below:
Test page code:
<! DOCTYPEhtml>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Student registration</title>
<script src="jquery.min.js"></script>
<script src="vue.js"></script>
<style>
.err { color: red; }
</style>
</head>
<body>
<h1>Student registration</h1>
<div id="register">
<form id="form-register" v-on:submit.prevent="register" action="/portal/user/student/register">
<table>
<tr>
<td>Invite code</td>
<td><input name="inviteCode"><span class="err" v-text="inviteCodeMessage"></span></td>
</tr>
<tr>
<td>Mobile phone number</td>
<td><input name="phone"><span class="err" v-text="phoneMessage"></span></td>
</tr>
<tr>
<td>nickname</td>
<td><input name="nickname"></td>
</tr>
<tr>
<td>password</td>
<td><input name="password"></td>
</tr>
<tr>
<td>Confirm password</td>
<td><input name="confirmPassword"></td>
</tr>
<tr>
<td> </td>
<td><input value="User Registration" type="submit"></td>
</tr>
</table>
</form>
</div>
<script>
// Create Vue data model
let app = new Vue({
el: '#register'.data: {inviteCodeMessage: null.phoneMessage: null
},
methods: {
register: function () {
// alert(" Ready to register!" );
app.inviteCodeMessage = null;
app.phoneMessage = null;
$.ajax({
url: '/portal/user/student/register'.data: $('#form-register').serialize(),
type: 'post'.dataType: 'json'.success: function(json) {
console.log(json);
if (json.state == 2000) {
alert("Registration successful!");
} else if (json.state == 4001 || json.state == 4002) {
app.inviteCodeMessage = json.message;
} else if (json.state == 4003) {
app.phoneMessage = json.message;
} else {
alert("Registration failed!"+ json.message); }}}); }}});</script>
</body>
</html>
Copy the code
Pages about the actual use, can be downloaded at https://robinliu.3322.org:8888/download/demo/straw_v1.4.zip, Will download page and related files are copied to the project of SRC/main/resources/static folder can (if the static folder does not exist, can create).
In terms of the actual use of the registration page, the locations that need to be adjusted are:
-
Line 30: Add v-bind:class=”{‘d-block’: HasError}”, using the hasError attribute of Vue to control the value named d-block in the CSS style. This D-block stands for display block, that is, the current label is displayed as a block. It is defined by Bootstrap and its value is a Boolean value.
-
Line 31: Add v-text=”errorMessage” to the tag to indicate that the text displayed by the tag is bound to the errorMessage attribute of the Vue;
-
Line 34: Add id=”form-register” to the
-
Line 37: Remove v-model=”inviteCode” from tag, otherwise an error may be reported;
-
Add your own
-
<script> let app = new Vue({ el: '#app'.data: { hasError: false.errorMessage: null }, methods: { register: function () { app.hasError = false; app.errorMessage = null; $.ajax({ url: '/portal/user/student/register'.data: $('#form-register').serialize(), type: 'post'.dataType: 'json'.success: function (json) { console.log(json); if (json.state == 2000) { alert("Registration successful!"); // location.href = 'some page '; } else { app.hasError = true; app.errorMessage = json.message; }}}); }}});</script> Copy the code
-
11. Finishing work after successful registration
In the current design of the user data table, the password field is char(68), but the actual password length is only 60 characters. In fact, before storing the password, we should add {bcrpyt} prefix before encrypting the result. This prefix is used to state that the current ciphertext encryption method is implemented by bCRPyT algorithm. Spring Security can automatically invoke the matching algorithm for password authentication based on the algorithm type!
So, in the UserServiceImpl class, add the completion prefix to the encryption method:
/** * Perform password encryption **@paramRawPassword Old password *@returnThe ciphertext is encrypted based on the original password */
private String encode(String rawPassword) {
String encodePassword = passwordEncoder.encode(rawPassword);
encodePassword = "{bcrypt}" + encodePassword;
return encodePassword;
}
Copy the code
Because of the change in password rules, the original password is not appropriate, and there are even more incorrect data, which should be deleted completely:
delete from user;
Copy the code
Or:
truncate user;
Copy the code
12. Stage summary
About the techniques used:
- SpringBoot;
- Lombok: Makes it easier for developers to use it without explicitly declaring Setters, Getters, toString(), etc
@Slf4j
Annotations to output logs; - Slf4j: Output logs with custom log levels, using placeholders to avoid repeated concatenation of strings;
- MyBatis Plus: MyBatis Plus has completed a lot of routine add, delete, change and check, so that developers do not need to write relevant code, simplify the development of persistence layer, of course, MyBatis Plus has completed the function can not meet all the needs, and even some methods may not be easy to use, if the developer needs to customize other data access functions, Can also refer to the use of MyBatis to develop new data access functions;
- MyBatis Plus Generator: Used to automatically generate some files in the project, including: entity class, persistence layer interface, persistence layer XML, business layer interface, business layer implementation class, controller class, it is automatically generated based on the field design of the data table;
- Spring Security: To be continued;
- Spring Validation: Validates the request parameters;
- Custom exceptions and unified exception handling.