Common module encapsulation

Starting with this article, we’ll learn about framing. Due to the large amount of code, it is impossible to post all of it in this book, so only some core code will be shown. All the source code can be viewed from the book source code.

After studying the previous chapters, the reader should have a general understanding of the project and have established the basic modules. To ensure the reuse and extensibility of applications, we need to encapsulate some common basic methods so that individual modules can call them.

In a complete microservice architecture, strings and dates tend to be handled the most. In some security applications, encryption algorithms are also used. Interfaces should also be versioned to improve application scalability. Therefore, we need to encapsulate these scenarios so that developers can use them easily. In this chapter, we first set up a complete set of microservice architecture from the public module.

Encapsulation of common engineering common class libraries

The Common project is a common module for the entire application, so it should contain common libraries such as date-time processing, string processing, encryption/decryption encapsulation, message queue encapsulation, and so on.

Date and time processing

The handling of date and time is one of the more widely used operations in an application, such as blog post time and comment time. The time is stored in the database as a timestamp, which requires a lot of processing to be returned to the client.

Therefore, we can create Dateutils under the Common project with the following code:

import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.calendar; import java.util.Date; public final class DateUtils { public static boolean isLegalDate(String str, String pattern){ try { SimpleDateFormat format = new SimpleDateFormat(pattern); format.parse(str); return true; } catch (Exception e){ return false; } } public static Date parseString2Date(String str,String pattern){ try { SimpleDateFormat format = new SimpleDateFormat(pattern); return format.parse( str); }catch (ParseException e){ e.printstackTrace(); return null; } } public static calendar parseString2calendar(String str,String pattern){ return parseDate2Calendar(parsestring2Date(str, pattern)); } public static String parseLong2DateString(long date,String pattern){ SimpleDateFormat sdf = new SimpleDateFormat(pattern); String sd = sdf.format(new Date(date)); return sd; } public static Calendar parseDate2Calendar(Date date){ Calendar calendar = Calendar.getInstance(); calendar.setTime(date); return calendar; } public static Date parseCalendar2Date(calendar calendar){ return calendar.getTime(); } public static String parseCalendar2String(calendar calendar,String pattern){ return parseDate2String(parsecalendar2Date(calendar), pattern); } public static String parseDate2String(Date date,String pattern) { SimpleDateFormat format = new SimpleDateFormat(pattern); return format.format(date); } public static String formatTime( long time){ long nowTime = System.currentTimeMillis(); long interval = nowTime - time; long hours = 3600 * 1000; long days = hours * 24; long fiveDays = days *5; if (interval < hours){ long minute = interval / 1008/ 60; If (minute == 0) {return "just "; } return minute (); }else if (interval < days){return interval / 1000/360 days +" hours ago "; }else if (interval< fiveDays) {return interval / 1000/3600/24 +" days ago "; }else i Date date = new Date(time); return parseDate2String(date,"MM-dd"); }}}Copy the code

When dealing with date formats, we can call methods provided by the code above, such as isLegalDate, which determines whether a date is valid. When we convert dates, we can call these methods that begin with parse. The name gives us a sense of what they mean. For example, parseCalendar2String means to convert a Calendar object to a String. ParseDate2String Converts a Date object to a String type. ParseString2Date converts a String object to a Date type.

Of course, the above code does not cover all processing of dates. If you have new processing requirements during development, you can add methods to DateUtils.

In addition, we should follow the principle of “don’t reinvent the wheel” when doing project development, introducing mature third party libraries whenever possible. At present, joda-time is a mature framework for date processing in the market, and its introduction method is also relatively simple. It only needs to add its dependency in POm. XML, for example:

<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</ artifactId><version>2.10.1</version>
</dependency>
Copy the code

Using joda-time is also relatively simple. You only need to build DateTime objects to perform date-time operations. If you get a date 90 days after the current date, you can write the following code:

DateTime dateTime = new DateTime();

System.out.println(dateTime.plusDays(90).toString(“yyyy-MM-dd HH:mm:ss”));

Joda-time is an efficient date-handling tool that is increasingly being used as an alternative to the JDK’s native date-time classes. You can give it priority when doing date and time processing.

String handling

In application development, string is arguably the most common data type, and its handling is also the most common, such as the need to determine the non-nullity of strings, random string generation, and so on. Next, let’s look at the string handling utility class stringUtils:

public final class StringUtils{ private static final char[] CHARS ={ '0','1','2','3', '4', '5','6', '7',' 8','9'}; private static int char_length =CHARS.length; public static boolean isEmpty( string str){return null == str ll str.length()== 0; } public static boolean isNotEmpty(string str){ return ! isEmpty(str); } public static boolean isBlank(String str){ int strLen; if (null == str ll(strLen = str.length())== 0){ return true; } for (int i= e; i< strLen; i++){ if ( ! Character.iswhitespace(str.charAt(i))){ return false; } } return true; } public static boolean isNotBlank(String str){ return ! isBlank(str); } public static String randomString(int length){ StringBuilder builder = new StringBuilder(length); Random random = new Random(); for (int i = 0; i< length; i++){ builder.append(random.nextInt(char_length)); } return builder.toString(); } public static string uuid()i return UUID.randomUUID().toString().replace("-",""); } private StringUtils(){ throw new AssertionError(); }}Copy the code

Strings are also known as universal types. Any basic type (such as integer, floating point, Boolean, etc.) can be replaced by strings. Therefore, it is necessary to encapsulate basic string operations.

The above code encapsulates common string operations, such as isEmpty and isBlank, which are used to determine whether or not a string isEmpty. The difference is that isEmpty simply compares the length of a string. If isEmpty is 0, it returns true; otherwise, it returns false. IsBlank checks if there really is content, such as “” (where the space is) returns true. In the same way, isNotEmpty and isNotBlank determine whether they are not null. RandomString is a randomString of six digits, often used in short message verification code generation. Uuid is used to generate a unique identifier. It is often used to generate the primary key and file name of a database.

Encryption/decryption encapsulation

For some sensitive data, such as payment data, order data and passwords, we often need to encrypt them in the HTTP transmission process or data storage to ensure the relative security of the data, so we need to use encryption and decryption algorithms.

At present, the commonly used encryption algorithm is divided into symmetric encryption algorithm, asymmetric encryption algorithm and information summary algorithm.

Symmetric encryption algorithm: an encryption algorithm that uses the same key for encryption and decryption. Common encryption algorithms include AES, DES, and XXTEA. Asymmetric encryption algorithm: generates a pair of public and private keys, uses the public key to encrypt, and decrypts the private key. Common encryption algorithms include RSA. Information digest algorithm: an irreversible encryption algorithm. As the name implies, it can only be encrypted but not decrypted, common examples are MD5. sha-1 and SHA-256.

AES, RSA, MD5 and SHA-1 algorithms are used in the actual project of this book, so they are encapsulated separately under the Common Project.

(1) Add dependency to pom.xml:

<dependency> <groupId>commons-codec</groupId> <artifactId>commons-codec</artifactId></dependency> <dependency> < the groupId > Commons - IO < / groupId > < artifactId > Commons - IO < / artifactId > < version > 2.6 < / version > < / dependency >Copy the code

In the above dependencies, Commons-Codec is a package provided by the Apache Foundation for information summarization and Base64 encoding and decoding. In common symmetric and asymmetric encryption algorithms, ciphertext is Base64 encoded. Commons-io is a package provided by the Apache Foundation for manipulating input and output streams. In the RSA encryption/decryption algorithm, byte stream operations are required, so you need to add this dependency package.

(2) Write AES algorithm:

import javax.crypto.spec. SecretKeySpec; public class AesEncryptUtils { private static final String ALGORITHMSTR = "AES/ECB/PKCSSPadding"; public static String base64Encode(byte[] bytes) i return Base64.encodeBase64String( bytes); } public static byte[] base64Decode(String base64Code) throws Exception { return Base64.decodeBase64(base64Code); } public static byte[] aesEncryptToBytes(String content,String encryptKey) throws Exception { KeyGenerator kgen = KeyGenerator.getInstance("AES"); kgen.init(128); Cipher cipher = Cipher.getInstance(ALGORITHMSTR); cipher.init(Cipher.ENCRYPT_MODE,new SecretKeySpec(encryptKey.getBytes(),"AES")); return cipher.doFinal(content.getBytes("utf-8")); } public static String aesEncrypt(String content, String encryptKey) throwS Exception { return base64Encode(aesEncryptToBytes(content,encryptKey)); } public static string aesDecryptByBytes(byte[] encryptBytes, String decryptKey)throws Exception { KeyGenerator kgen = KeyGenerator.getInstance("AES"); kgen.init(128); Cipher cipher = Cipher.getInstance(ALGORITHMSTR); cipher.init(Cipher.DECRYPT_MODE,new SecretKeySpec(decryptKey.getBytes(),"AES")); byte[] decryptBytes = cipher.doFinal(encryptBytes); return new String(decryptBytes); } public static String aesDecrypt(String encryptStr, String decryptKey) throws Exception i return aesDecryptByBytes(base64Decode(encryptStr),decryptKey); }}Copy the code

The preceding code is the common AES encryption algorithm. Encryption and decryption require a unified key. The key is a customized string of 16, 24, or 32 bits. The aesEncrypt method is called for encryption, where the first parameter is plaintext and the second parameter is the key. Call aesDecrypt for decryption, where the first parameter is ciphertext and the second parameter is the key.

ALGORITHMSTR (AES/ECB/PKCS5Padding) a string constant ALGORITHMSTR (AES/ECB/PKCS5Padding) is defined in the code, which defines the specific encryption and decryption implementation of the symmetric encryption algorithm, where AES is the AES algorithm and ECB is the encryption mode. PKCS5Padding is the specific padding method. Common padding methods include PKCS7Padding and NoPadding. Encrypting the same string in different ways will result in different results. Therefore, the encryption algorithm must be consistent with that of the client; otherwise, the client cannot decrypt the ciphertext returned by the server.

(3) Writing RSA algorithm:

public class RSAUtils { public static final String CHARSET ="UTF-8"; public static final String RSA_ALGORITHM="RSA"; public static Map<String,String>createKeys(int keySize){ KeyPairGenerator kpg; try{ kpg =KeyPairGenerator.getInstance(RSA_ALGORITHM); Security.addProvider(new com.sun.crypto.provider. SunJCE()); }catch(NoSuchAlgorithmException e){ throw new IllegalArgumentException("No such algorithm-->[" + RSA_ALGORITHM +"]"); } kpg.initialize(keySize); KeyPair keyPair = kpg.generateKeyPair(); Key publicKey = keyPair.getPublic(); string publicKeyStr = Base64.encodeBase64String(publicKey.getEncoded()); Key privateKey = keyPair.getPrivate(); String privateKeyStr = Base64.encodeBase64String(privateKey.getEncoded()); Map<String,String> keyPairMap = new HashMap<>(2); keyPairMap.put("publicKey", publicKeyStr); keyPairMap.put( "privateKey", privateKeyStr); return keyPairMap; } public static RSAPublicKey getPublicKey(String publicKey) throws NoSuchAlgorithmException, InvalidKeySpecException { KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM); x509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(Base64.decodeBase64(publicKey)) ; RSAPublicKey key = (RSAPublicKey) keyFactory.generatePublic( x509KeySpec); return key; } public static RSAPrivateKey getPrivateKey(String privateKey) throws NoSuchAlgorithmException,InvalidKeySpecException {  KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM); PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(Base64.decodeBase64 (privateKey)); RSAPrivateKey key = (RSAPrivateKey) keyFactory.generatePrivate(pkcs8KeySpec); return key; } public static String publicEncrypt(String data,RSAPublicKey publicKey){ try{ Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); cipher.init(Cipher. ENCRYPT_MODE,publicKey); return Base64.encodeBase64String(rsaSplitCodec(cipher,Cipher. ENCRYPT_MODE, data.getBytes(CHARSET),publicKey.getModulus().bitLength())); }catch(Exception e){throw new RuntimeException(" +data +"); } } public static String privateDecrypt(String data,RSAPrivateKey privateKey){ try{ Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); cipher.init(Cipher. DECRYPT_MODE, privateKey); Return new String(rsaSplitCodec(cipher, cipher.decrypt_mode, base64. decodeBase64(data), privateKey.getModulus().bitLength()),CHARSET); }catch(Exception e){ e.printStackTrace(); Throw new RuntimeException(" exception encountered while decrypting string ["+data+"] ",e); } } private static byte[] rsaSplitCodec(Cipher cipher, int opmode, byte[] datas,int keySize){ int maxBlock = 0; if(opmode == Cipher. DECRYPT_MODE){ maxBlock = keysize / 8; }else{ maxBlock =keysize / 8 -11; } ByteArrayOutputStream out = new ByteArrayoutputStream(); int offSet = 0; byte[] buff; int i = 0; try{ while(datas. length > offSet)f if(datas.length-offSet > maxBlock){ buff = cipher.doFinal(datas,offSet,maxBlock); }else{ buff = cipher.doFinal(datas,offSet, datas.length-offSet); } out.write(buff, 0, buffe.length); i++; offSet = i * maxBlock; } }catch(Exception e){ e.printStackTrace(); Throw new RuntimeException(" ["+maxBlock+"]); throw new RuntimeException(" ["+maxBlock+"]); } byte[] resultDatas = out.toByteArray(); IOUtils.closeQuietly(out); return resultDatas; }}Copy the code

As mentioned above, RSA is an asymmetric encryption algorithm. Asymmetric encryption means that different keys are used for encryption and decryption. The basic idea of RSA is to generate a pair of keys, namely a public key and a private key, based on certain rules. The public key is provided to the client, that is, anyone can obtain it, while the private key is stored on the server, and no one can obtain it through normal channels.

Generally, asymmetric encryption algorithms use public keys to encrypt data on the client and decrypt data on the server using the private key. For example, the above code provides encryption and decryption methods, namely publicEncrypt and privateDecrypt, but these two methods cannot pass public and private key strings directly. Instead, the getPublicKey and getPrivateKey methods return the RSAPublicKey and RSAPrivateKey objects to the encryption and decryption methods.

Public and private keys can be generated in many ways, such as OpenSSL tools, third-party online tools, and encoding implementations. Since asymmetric encryption algorithm maintains public and private keys respectively, its efficiency is lower than symmetric encryption algorithm, but its security level is higher than symmetric encryption algorithm. Readers should take comprehensive consideration when selecting encryption algorithm, and adopt the encryption algorithm suitable for the project.

(4) Information summary algorithm:

import java.security.MessageDigest; public class MessageDigestutils { public static string encrypt(String password,string algorithm){ try { MessageDigest md  =MessageDigest.getInstance(algorithm); byte[] b = md.digest(password.getBytes("UTF-8")); return ByteUtils.byte2HexString(b); }catch (Exception e){ e.printStackTrace(); return null; }}}Copy the code

The JDK comes with the information digest algorithm, but returns a byte array type, in the actual need to convert the byte array into a hexadecimal string, so the above code does a brief encapsulation of the information digest algorithm. By calling the MessageDigestutils. Encrypt method can return to the encrypted string ciphertext, one of the first parameter is clear, the second parameter based algorithm for specific information, optional values such as MD5, SHA1 and SHA256.

Abstract encryption is an irreversible algorithm, that is, it can only be encrypted, but not decrypted. In today’s highly developed technology, although information summarization algorithm cannot be directly decrypted, it can be cracked by collision algorithm curve. Ms. Wang Xiaoyun, a famous mathematician and cryptographer in China, has already cracked MD5 and SHA1 algorithms through collision algorithms. Therefore, in order to improve the security of encryption technology, we generally use “multiple encryption +salt” encryption, such as ND5(MD5(plaintext +salt)), readers can understand salt as a key, but it cannot be decrypted through salt.

Encapsulation of message queues

Message queue is generally used in asynchronous processing, high-concurrency message processing and delay processing, etc. It is also widely used in the current Internet environment, so it is also encapsulated for subsequent message queue use.

In this example, message queues are demonstrated using RabbitMQ. First, install RabbitMQ on a Windows system. Due to the RabbitMQ Erlang, should install Erlang, first download address for http:/www.rabbitmq.com/which-erlan… Http:/www.rabbitmq.com/install-windows.html, double-click the downloaded exe files, according to the steps to complete the installation.

After the installation is complete, press Win+R, Enter services. MSC in the displayed window, and press Enter to open the service list, as shown in Figure 6-1.

As you can see, RabbitMQ has started. By default, only port 5672 is enabled after RabbitMQ is installed, so you can only view and manage RabbitMQ by command. For convenience, you can install a plug-in to enable RabbitMQ Web management. Open the CMD command console, go to the sbin directory of the RabbitMQ installation directory, and type rabbitmq-plugins enablerabbitMQ_management, as shown in Figure 6-2.

The default startup port of the Web management interface is 15672. Enter localhost:15672 in the address box of the browser. The default account and password are guest. The Web management page is displayed, as shown in Figure 6-3.

Next, we encapsulate the message queue. Add RabbitMQ dependencies:

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</ artifactId>
</dependency>
Copy the code

Message queues are integrated through the Spring Cloud component Spring Cloud Bus, and RabbitMQ can be easily used by adding dependencies on Spring-Cloud-starter-bus-AMQP.

(2) Create the RabbitMQ configuration class RabbitConfiguration to define the RabbitMQ properties:

import org.springframework.amqp.core.Queue;
import org.springframework.boot.SpringBootConfiguration;import org.springframework.context.annotation. Bean;
@SpringBootConfiguration
public class Rabbitconfiguration {
@Bean
public Queue queue(){
return new Queue( "someQueue");
}
}
Copy the code

As mentioned earlier, SpringBoot can configure applications using the @SpringBootConfiguration annotation. Once we have integrated the RabbitMQ dependencies, we need to configure them as well. In the above code, we defined a Bean whose purpose is to automatically create message queue names. If you do not create a queue using code, manually add a queue on the RabbitMQ Web UI each time. Otherwise, an error message is displayed, as shown in Figure 6-4.

However, it is not desirable to manually create the queue each time through the Web management interface, so we can define the queue beforehand in the above configuration class.

(3) RabbitMQ is an asynchronous request. The client sends a message, and the RabbitMQ server receives the message and sends it back to the client. The sender of the message is called a producer and the receiver is called a consumer, so you also need to encapsulate the sending and receiving of the message.

Create a class named MyBean for sending and receiving message queues:

@Component public class MyBean { private final AmqpAdmin amqpAdmin; private final AmqpTemplate amqpTemplate; @Autowired public MyBean(AmqpAdmin amqpAdmin,AmqpTemplate amqpTemplate){ this.amqpAdmin = amqpAdmin; this.amqpTemplate = amqpTemplate; } @rabbithandler @rabbitListener (queues = "queue ") public void processMessage(String content){} @rabbitListener (queues =" queue ") public void processMessage(String content){} @rabbitListener (queues = "queue ") public void processMessage(String content) system.out.println( content); {} public void the send (string content). / / the message queue producers amqpTemplate convertAndSend (" someQueue ", the content); }}Copy the code

ProcessNessage is the message consumer. @rabbithandler and @RabbitListener are annotations to indicate that this method is the message consumer. And specify which queue to consume.

Interface Version Management

Generally, after the first version of the product is released and put online, iterations and optimization are constantly carried out. We cannot guarantee that the original interface will not be changed in the subsequent upgrade process, and some changes may affect online business. Therefore, if you want to modify the interface without affecting the online business, you need to introduce the concept of versioning. As the name implies, a version number is added when the interface is requested, and the back end performs business logic in different version periods based on the version number. So, even if we upgrade the interface, it will not affect the original online interface, so as to ensure the normal operation of the system.

There are many ideas for version definition, such as:

Request a header version number, such as header(“version”, “1.0”); The URL is followed by the version number, for example, API? Version = 1.0; RESTful version number definitions, such as/API /v1.

This section describes the third way to define a version number. The easiest way to define a version number is to directly write a fixed version number in RequestMapping, for example:

@RequestMapping("/v1/index")
Copy the code

The downside of this approach is that it does not scale well and the interface will report a 404 error once another version number is passed in. For example, if the client interface address is requested as /v2/index, and our project only defines v1, the index interface cannot be requested.

The desired effect is that if the version number passed in is not found in the project, the highest version of the interface is automatically found. How to do this? Please refer to the following code implementation.

(1) Define annotation class:

@Target(ElementType. TYPE)
@Retention(RetentionPolicy.RUNTIME)@Mapping
@Documented
public @interface ApiVersion {
int value();
}
Copy the code

In the above code, we first define an annotation that specifies the version number of the controller, such as @apiVersion (1), and then access the methods defined by the controller at the address v1/**.

(2) Customize RequestMappingHandler:

public class CustomRequestMappingHandlerMapping extends RequestMappingHandlerMapping i @override protected RequestCondition<ApiVersionCondition> getCustomTypeCondition(Class<? > handlerType) { ApiVersion apiVersion = Annotationutils.findAnnotation(handlerType, Apiversion.class); return createCondition( apiversion); } @override protected RequestCondition<ApiVersionConditionz getCustomMethodCondition(Nethod method){ ApiVersion apiversion = AnnotationUtils.findAnnotation(method,ApiVersion.class); return createCondition(apiversion) ; } private RequestCondition<ApiVersionCondition> createCondition(ApiVersion apiVersion)f return apiversion == null ? null : new ApiVersionCondition(apiVersion.value()); }}Copy the code

Spring MVC defined through RequestMapping request path, so if we want to automatically by v1 such address to request the specified controller, should inherit the RequestMappingHandlerMapping class to rewrite its methods.

Spring MVC automatically maps all controller classes when the application is started and loads methods annotated with @RequestMapping into memory. Since we inherited the RequestMappingHandlerMapping class, so in the mapping performed when rewriting getCustomTypeCondition and getCustomMethodCondition method, the method can know the content of the body, We create a custom RequestCondition and pass the version information to the RequestCondition.

(3) CustomRequestMappingHandlerMapping only inherited the RequestMappingHandlerMapping, Spring Boot did not know, so also need to define it in the configuration class, So that the Spring Boot at startup perform custom RequestMappingHandlerMapping classes.

In public projects to create webConfig class and inheritance webNvcConfigurationSupport class, then rewrite the requestMappingHandlerMapping methods, such as:

@Override public RequestMappingHandlerMapping requestMappingHandlerMapping(){ RequestMappingHandlerMapping handlerMapping = new CustomRequestMappingHandlerMapping(); handlerMapping.set0rder(0); return handlerMapping; }Copy the code

In the above code, we rewrite the requestMappingHandlerMapping method and instantiate the RequestMapping – HandlerMapping object, Returns the custom CustomRequestMappingHandlerMapping in front of the class.

(4) Add annotation @APIVersion (1) to the controller class to achieve version control, where the number 1 represents the version number v1. To request an interface, enter an address like/API /v1/index as follows:

@RequestMapping("{version}") @RestController @ApiVersion(1) public class TestV1controller{ @GetMapping("index ") public String index(){ return ""; }}Copy the code

Verify the validity of input parameters

When we define the interface, we need to verify the input parameters to prevent the intrusion of illegal parameters. For example, when the login interface is implemented, the mobile phone number and password cannot be empty and must contain 11 digits. The client also does validation, but it only applies to normal user requests, and if the user bypasses the client and requests the interface directly, some exception characters may be passed in. Therefore, it is necessary to verify the validity of input parameters at the same time.

The simplest way to do validation is to do if-else on each interface, but this is not elegant. Spring provides a validator class that you can work with.

Add the following methods to the common controller class:

protected void validate(BindingResult result){ if(result.hasFieldErrors()){ List<FieldError> errorList = result.getFieldErrors(); errorList.stream().forEach(item -> Assert.isTrue(false,item.getDefaultMessage())); }}Copy the code

The results of the Validator are stored in the BindingResult class, so the above method passes in the BindingResult class. In the above code, the program uses hasFieldErrors to determine if there is a validation failure. If there is, it uses getFieldErrors to retrieve all error messages and loop through the list of errors. Once an error is found, it throws an exception using Assert assertions. Section 6.4 describes how to handle exceptions and returns a message indicating verification failure.

The benefit of using an assertion is that it throws a runtime exception, which means we don’t need to explicitly add a throwsException to the end of the method, which allows us to scale well and simplify the code.

Add the @VALID annotation to the controller interface parameter, followed by the BindingResult class, and call the validate(result) method in the method body.

@GetMapping( "index")
public String index(@valid TestRequest request, BindingResult result){
validate(result);
return "Hello " +request.getName();
}
Copy the code

To implement interface validation, add a validation rule annotation to each attribute in the class that defines the @VALID annotation, for example:

@Data
public class TestRequest {
@NotEmpty
private String name;
}
Copy the code

Common annotations are listed below for readers’ reference.

  • @notnull: Cannot be null.
  • @notempty: cannot be empty or an empty string. @Max: indicates the maximum value.
  • @min: minimum value.
  • @pattern: indicates a regular match.
  • @length: maximum Length and minimum Length.

Unified handling of exceptions

Exceptions are common in product development, such as program running or database connection. Exceptions may be thrown during the process. If no processing is performed, the client will receive the information shown in Figure 6-5.

So you can see that it just returned 500 on the screen, which is not what we expected. Normally, a uniform JSON format should be returned even if an error occurs, such as:

{"code" :0, "message" :" cannot be null ", "data" :null}Copy the code

It’s as simple as taking advantage of Spring’s AOP features and adding the following methods to the common controller:

@ExceptionHandler
public SingleResult doError(Exception exception){
if(Stringutils.isBlank(exception.getMessage())){
return SingleResult.buildFailure();
}
return SingleResult.buildFailure(exception.getMessage());
}
Copy the code

The @ExceptionHandler annotation is added to the doError method to indicate that when an Exception occurs, the annotated method is executed, and the method receives the Exception class. As we know, the Exception class is the parent of all Exception classes, so when an Exception occurs, SpringMVC finds a method annotated with the @ExceptionHandler annotation, calls it and passes it to the specific Exception object.

To return the JSON format above, we simply return the SingleResult object. Note that SingleResult is a custom data Result class that inherits from the Result class to return a single data object. The corresponding is the MultiResult class, which returns multiple Result sets, and all interfaces should return Result. About the class, the reader can form a complete set of reference book source code, in the common engineering com.lynn.blog.com mon. The result under the package.

Replacing the JSON converter

Spring MVC uses Jackson framework as the JSON format conversion engine for data output by default. However, there are many JSON parsing frameworks on the market, such as FastJson and Gson, etc. Jackson, as an old framework, is no longer comparable to these frameworks.

The power of Spring is also in its extensibility, providing a number of interfaces that developers can use to change their default engines, including JSON conversions. Let’s look at how to change Jackson to FastJson.

(1) Add FastJson dependency

< the dependency > < groupId > com. Alibaba < / groupId > < artifactId > fastjson < / artifactId > < version > 1.2.47 < / version > < / dependency >Copy the code

FastJson is a class library for generating and parsing JSON data produced by Alibaba. Its execution efficiency is also outstanding among similar frameworks, so this book adopts FastJson as the parsing engine of JSON.

(2) in webConfig rewrite configureMessageConverters method in class:

@override public void configureMessageConverters(List<HttpMessageConverter< ? >> converters){ super.configureMessageConverters(converters); FastJsonHttpMessageConverter fastConverter=new Fast]sonHttpMessageConverter(); FastJsonConfig fastJsonconfig=new FastsonConfig(); fastJsonconfig.setSerializerFeatures( SerializerFeature.PrettyFormat ); List<MediaType> mediaTypeList = new ArrayList<>(); mediaTypeList.add(MediaType.APPLICATION_JSON_UTF8); fastConverter.setSupportedMediaTypes(mediaTypeList); fastConverter.setFastsonConfig(fastsonConfig); converters.add(fastConverter); }Copy the code

When the program starts, executes configureMessageConverters method, if you don’t rewrite the method, the method of body is empty, we view the source code. The code is as follows:

/** * Override this method to add custom {@link HttpMessageConverter}s to use* with the {@link RequestMappingHandlerAdapter} and the * {@link ExceptionHandlerExceptionResolver}. Adding converters to the * list turns  off the default converters that would otherwise be registered* by default. Also see {@link #addDefaultHttpNessageConverters(List)} that* can be used to add default message converters. * @param converters a list to add message converters to; * initially an empty list. */ protected void configureMessageConverters(List<HttpNessageConverter<? >> converters) {}Copy the code

At this point, Spring MVC uses Jackson as its default JSON parsing engine, so once we override the Configuremessage-Converters method, it overrides Jackson, using our custom JSON parser as the JSON parsing engine.

Thanks to Spring the scalability of the design, we can be a JSON parsing engine replacement for FastJson, it provides AbstractHttp – MessageConverter abstract classes and GenericHttpMessageConverter interface. By implementing their methods, you can customize the JSON parsing.

In the code above is FastJsonHttpMessageConverter FastJson in order to integrate Spring and implementation of a converter. When we rewrite configureMessageConverters method, therefore, first of all to instantiate FastJsonHttpMessage – Converter object, and carry on Fast] sonConfig basic configuration. PrettyFormat indicates whether the returned result is formatted. MediaType sets rules encoded as UTF-8. In the end, will add to the list of conterters Fast3sonHttpMessageConverter object.

So when we request the interface to return data, Spring MVC will use FastJson to transform the data.

Redis encapsulation

As an in-memory database, Redis is widely used. We can cache some data to improve the query performance of the application, such as saving login data (verification code and token, etc.) and realizing distributed locks.

Redis is also used in this paper’s actual combat project, and Spring Boot is very convenient to operate Redis. SpringBoot integrates Redis and implements a number of methods, some of which can be shared. We can encapsulate our own Redis operation code according to project requirements.

(1) Add Redis dependency:

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId></dependency>
Copy the code

Spring-boot-starter-data includes data-related packages such as JPA, mongodb, and ElasticSearch. Therefore, Redis is also placed under spring-boot-starter-data.

(2) Create Redis class, this class contains Redis general operations, its code is as follows:

@Component public class Redis i @Autowired private StringRedisTemplate template; public void set(String key, String value,long expire){ template.opsForValue().set(key, value,expire,TimeUnit.SECONDS); } public void set(String key,string value){ template.opsForValue().set(key, value); } public Object get(String key) i return template.opsForValue().get(key); } public void delete(String key) { template.delete(key); }}Copy the code

In the code above, we first inject the StringRedisTemplate class, which is the Redis operation template class provided by Spring Boot and, as you can tell by its name, is dedicated to string access operations and inherits from the RedisTemplate class. Only the basic Redis operations are implemented in the code, including key value save, read, and delete operations. The set method overloads two methods that can accept the expiration date of the data preservation. Timeunit. SECONDS specifies the expiration date in SECONDS. Readers who find that these operations do not meet their requirements during project development can add methods to this class to meet their requirements.

summary

This paper mainly encapsulates the common modules of the blog site, that is, the methods and class libraries that each module may use to ensure the reuse of the code. Readers can also according to their own understanding and specific project requirements to encapsulate some methods, provided to each module call.

Three things to watch ❤️

If you find this article helpful, I’d like to invite you to do three small favors for me:

  1. Like, forward, have your “like and comment”, is the motivation of my creation.

  2. Follow the public account “Java rotten pigskin” and share original knowledge from time to time.

  3. Also look forward to the follow-up article ing🚀

  4. [666] Scan the code to obtain the learning materials package

Article is not original, and all of us feel while reading the article good remember thumb up forward + attention oh ~ this article source: www.toutiao.com/i6920459942…