Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

How to use annotations correctly

2025-01-15 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

Shulou(Shulou.com)06/02 Report--

This article introduces the relevant knowledge of "how to use annotations correctly". Many people will encounter such a dilemma in the operation of actual cases, so let the editor lead you to learn how to deal with these situations. I hope you can read it carefully and be able to achieve something!

A brief introduction to the desensitization scenario of log

In the log, our log usually prints the Json string of model, for example, the following model classes

Public class Request {/ * * user name * / private String name; / * * ID card * / private String idcard; / * Mobile number * / private String phone; / * base64 * / private String imgBase64;} of the picture

There are examples of the following classes

Request request = new Request (); request.setName ("Aixinjuro"); request.setIdcard ("450111112222"); request.setPhone ("18611111767"); request.setImgBase64 ("xxx")

We usually use fastJson to print the json string of this Request:

Log.info (JSON.toJSONString (request))

In this way, you can print out all the attribute values of Request, as shown in the log:

{"idcard": "450111112222", "imgBase64": "xxx", "name": "Zhang San", "phone": "17120227942"}

There are two problems with the log here.

Security: personal information such as name,phone and idcard is extremely sensitive and should not be printed in clear text. We hope that this sensitive information is output in desensitized form.

Field redundancy: imgBase64 is the base64 of a picture and a very long string. In production, the image base64 data is not very helpful in troubleshooting problems, but will increase the storage cost. Moreover, this field is the base64 on the front and back of the ID card, which also belongs to sensitive information, so this field needs to be removed from the log. We want to see the following log after desensitization and downsizing (removing the imgBase64 field):

{"idcard": "450,222", "name": "Love * * Luo", "phone": "18699", "imgBase64": ""}

You can see that each field is desensitized at last, but it should be noted that the desensitization rules of these fields are different.

ID card (idcard), keep the first three digits, the last three digits, and code the rest

Keep the name (name) before and after two digits, and code the rest.

The telephone number (phone) keeps the first three digits, the last four digits, and the rest code.

The base64 (imgBase64) of the picture shows the empty string directly.

How to implement it? first of all, we need to know a knowledge point, that is, the JSON.toJSONString method specifies a parameter ValueFilter, and you can customize the properties to be converted. We can use this Filter so that the final JSON string does not display or display the desensitized value. The general logic is as follows

Public class Util {public static String toJSONString (Object object) {try {return JSON.toJSONString (object, getValueFilter ());} catch (Exception e) {return ToStringBuilder.reflectionToString (object) }} private static ValueFilter getValueFilter () {return (obj, key, value)-> {/ / obj- object key- field name value- field value return formatted value};}

As shown above, as long as we desensitize value in the getValueFilter method, we can display the desensitized log in the final log. Now the question is, how to deal with desensitization of fields. We know that some fields need desensitization and some fields do not need desensitization, so some people may determine whether to desensitize by the name of key. The code is as follows:

Private static ValueFilter getValueFilter () {return (obj, key, value)-> {/ / obj- object key- field name value- field value if (Objects.equal (key, "phone")) {return desensitized phone} if (Objects.equal (key) "idcard") {idcard} if after return desensitization (Objects.equal (key, "name")) {name} after return desensitization / / the rest return return value} according to the original value that does not need desensitization }

This does seem to implement the requirements, but is it enough to implement the requirements? there is a serious problem with such an implementation:

Desensitization rules and specific attribute names are closely coupled, need to write a lot of if else judgment logic in valueFilter, scalability is not high, versatility is not strong, to take a simple example, due to business reasons, some fields of the phone in our project are called phone, some are called tel, some are called telephone, their desensitization rules are the same, but you have to write the following ugly code in the above method.

Private static ValueFilter getValueFilter () {return (obj, key, value)-> {/ / obj- object key- field name value- field value if (Objects.equal (key, "phone") | | Objects.equal (key, "tel") | Objects.equal (key) "telephone") | |) {phone} after return desensitization / / return return value} according to the original value for others that do not need desensitization }

So can you use a general-purpose and scalable method to solve it? I believe you already have a good idea of the title of the article. Yes, it is the annotations used. Next, let's take a look at what annotations are and how to customize them.

The definition and implementation principle of annotations

Annotation, also known as Java annotations, is an annotation mechanism introduced by JDK 5.0. if the comments of the code are for the programmer, then the annotations are for the program. after the program sees the annotations, the program can get the annotations at run time, and enhance the ability of the runtime according to the annotations. There are three common applications in the code.

@ Override checks whether the method overrides the parent class method, and reports a compilation error if it is found that the parent class or the implemented interface does not have this method

@ Deprecated tags obsolete classes, methods, properties, etc.

SuppressWarnings-instructs the compiler to ignore the warnings declared in the comments.

So how are these annotations implemented? let's open the @ Override annotation to see.

@ Documented @ Retention (RetentionPolicy.RUNTIME) @ Target (value= {CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE}) public @ interface Deprecated {}

You can see that there are @ Documented, @ Retention, @ Target annotations on Deprecated annotations. These annotations are also called meta-annotations, that is, annotations Deprecated or other custom annotations. The behavior of other annotations is regulated and defined by these annotations. The types and functions of these meta-annotations are as follows.

@ Documented indicates that it will be processed by tools such as javadoc, so that the annotation type information will eventually be included in the generated document.

There are three main strategies for saving @ Retention annotations

RetentionPolicy.SOURCE source code-level annotations, indicating that the specified annotations are only visible at compile time and will not be written into bytecode files. Override and SuppressWarnings belong to this type. Such annotations mainly serve as a reminder at compile time for programmers, but are of little significance at run time, so they will not be compiled into bytecode files in the end.

RetentionPolicy.RUNTIME indicates that annotations will be compiled into the final character code file, and JVM will also read annotations after startup, so that we can get these annotations through reflection at run time, and do relevant operations according to these annotations. This is the preservation strategy used by most custom annotations. Here you may have a question: why is Deprecated marked as RUNTIME? for programmers, in theory, only care about the calling class. Method Deprecated is enough, and what's the point of getting it at run time? consider a scenario where you want to count the frequency of outdated methods being called in production to evaluate the bad smell of your project or as a reference for refactoring.

RetentionPolicy.CLASS annotations are compiled into the final character code file, but are not loaded into JVM (annotations are discarded when the class is loaded). This saving strategy is not commonly used and is mainly used in bytecode file processing.

@ Target indicates where the annotation can be used, and it can be used anywhere by default. The scope of the annotation is mainly specified by value. Here are some common types:

FIELD acts on attributes

METHOD acts on the method

ElementType.TYPE: acts on classes, interfaces (including annotation types), or enum declarations

@ Inherited-Mark which annotation class this annotation is inherited from (the default annotation does not inherit from any subclass)

Let's take a look at @ interface, what is this for? in fact, if you decompile it, you will find that the compiler encodes it as follows in bytecode.

Public interface Override extends Annotation {}

What is Annotation?

We can see that the essence of annotations is to inherit the interface of Annotation, supplemented by meta-annotations of runtime behavior, scope, and so on, annotated by Retention,Target specifications.

There are no attributes defined in Deprecated annotations. In fact, you can define attributes if you need annotations. For example, Deprecated annotations can define an attribute of value, and you can specify the value value of this annotation when declaring annotations.

@ Documented @ Retention (RetentionPolicy.RUNTIME) @ Target (value= {CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE}) public @ interface Deprecated {String value () default ";}

So that when I apply this annotation to properties, etc., I can specify this value value, as shown below

Public class Person {@ Deprecated (value = "xxx") private String tail;}

If the save strategy of the annotation is RetentionPolicy.RUNTIME, we can get the annotation at run time in the following way, and then get the attribute value of the annotation, etc.

Field.getAnnotation (Deprecated.class)

Skillfully using annotations to solve the problem of log desensitization

The above briefly describes the principle and writing of annotations, and then let's take a look at how to use annotations to desensitize our logs.

First of all, we need to define desensitization annotations. Since this annotation needs to be fetched at run time, the save policy should be RetentionPolicy.RUNTIME, and this annotation should be applied to fields such as phone,idcard, so the value of @ Target is ElementType.FIELD. In addition, we notice that although fields such as phone number and ID card need to be desensitized, their desensitization strategies are different, so we need to define an attribute for this annotation. In this way, you can specify which desensitization type its attributes belong to. The desensitization annotations we define are as follows:

/ / sensitive information type public enum SensitiveType {ID_CARD, PHONE, NAME, IMG_BASE64} @ Target ({ElementType.FIELD}) @ Retention (RetentionPolicy.RUNTIME) public @ interface SensitiveInfo {SensitiveType type ();}

Once the annotation is defined, we can now specify the annotation and its sensitive information type for our sensitive field, as follows

Public class Request {@ SensitiveInfo (type = SensitiveType.NAME) private String name; @ SensitiveInfo (type = SensitiveType.ID_CARD) private String idcard; @ SensitiveInfo (type = SensitiveType.PHONE) private String phone; @ SensitiveInfo (type = SensitiveType.IMG_BASE64) private String imgBase64;}

After specifying the annotation for the attribute, how to desensitize the corresponding sensitive field type according to the annotation? you can use reflection to obtain every Field of the class first, and then determine whether there is a corresponding annotation on the Field. If so, determine which sensitive type of annotation this annotation is for, and then do the corresponding desensitization operation for the corresponding field. Put the code directly, and the comment is written very clearly. I believe you should be able to understand it.

Private static ValueFilter getValueFilter () {return (obj, key, value)-> {/ / obj- object key- field name value- field value try {/ / get the property of each class by reflection [] fields = obj.getClass () .getDeclaredFields () For (Field field: fields) {if (! field.getName (). Equals (key)) {continue;} / / determines whether the attribute has a corresponding SensitiveInfo annotation SensitiveInfo annotation = field.getAnnotation (SensitiveInfo.class) / / if so, execute the desensitization method of the corresponding field if (null! = annotation) {switch (annotation.type ()) {case PHONE: return phone desensitization Case ID_CARD: return ID card desensitization; case NAME: return name desensitization; case IMG_BASE64: return "" / / the base64 of the image is not displayed Directly return empty default: / / where you can throw an exception} catch (Exception e) {log.error ("To JSON String fail") E) } return value;};}

Some people may say that the amount of desensitization code achieved by using annotations has more than doubled, which does not seem to be worth it. In fact, in the previous way, desensitization rules are strongly coupled with a certain field name, and the maintainability is not good, while using annotations, such as phone and tel,telephone in the project, all belong to the type of phone desensitization. As long as it is uniformly marked with * * @ SensitiveInfo (type = SensitiveType.PHONE) * *, and if there is a new desensitization type, just add a new SensitiveType type, which will greatly enhance the maintainability and expansibility. So in such scenarios, the use of annotations is highly recommended.

Advanced application of annotations-using annotations to eliminate duplicate code

In the process of docking with the bank, the bank provides some API interfaces, which is a little special for the serialization of the parameters. Instead of using JSON, we need to put the parameters together in turn to form a large string.

According to the order of the API documents provided by the bank, all the parameters are composed of fixed-length data, and then spliced together as the whole string.

Because each parameter has a fixed length, it needs to be filled when the length is not reached:

If the parameter of the string type is less than the length, you need to fill in the right part of the underscore, that is, the content of the string is left.

The parameters of the numeric type are filled with 0 to the left of the length, that is, the actual number is on the right.

The representation of the currency type requires the amount to be rounded down 2 digits to points into units, which is also populated to the left as a number type.

MD5 all parameters as signatures (for ease of understanding, salting is not involved in Demo). A brief look at the interface definitions of the two banks

1. Create a user

Insert a picture description here

2. Payment interface

The general practice is to fill in parameters, stitching and signature verification for each interface according to the previous rules. Take the above two interfaces as an example, let's take a look at the general practice first.

The request to create a user and pay is as follows:

/ / create user POJO @ Data public class CreateUserRequest {private String name; private String identity; private String mobile; private int age;} / / pay POJO @ Data public class PayRequest {private long userId; private BigDecimal amount;} public class BankService {/ / create user method public static String createUser (CreateUserRequest request) throws IOException {StringBuilder stringBuilder = new StringBuilder () / / string to the left, fill in _ stringBuilder.append (String.format ("%-10s", request.getName ()). Replace ('','_'); / / string to the left, fill in _ stringBuilder.append (String.format ("%-18s", request.getIdentity ()). Replace (','_')) / / keep the number to the right, fill stringBuilder.append (String.format ("d", age)) with 0 in excess; / / string to the left, and fill stringBuilder.append with _ in excess (String.format ("%-11s", mobile). Replace ('','_')) / / finally add MD5 as the signature stringBuilder.append (DigestUtils.md2Hex (stringBuilder.toString (); return Request.Post ("http://baseurl/createUser") .bodyString (stringBuilder.toString (), ContentType.APPLICATION_JSON) .execute () .returnContent () .asString ()) } / / payment method public static String pay (PayRequest request) throws IOException {StringBuilder stringBuilder = new StringBuilder (); / / the number is to the right, and the superfluous stringBuilder.append is filled with 0 (String.format ("0d", request.getUserId () / / the amount is rounded down 2 digits to the score in units, as the number is to the right, and the excess is filled with 0 in stringBuilder.append (String.format ("0d", request.getAmount (). SetScale (2) RoundingMode.down). Multiply (new BigDecimal ("100"). LongValue () / / add MD5 as the signature stringBuilder.append (DigestUtils.md2Hex (stringBuilder.toString (); return Request.Post ("http://baseurl//pay") .bodyString (stringBuilder.toString (), ContentType.APPLICATION_JSON). Execute (). ReturnContent (). AsString ();}}

You can see that just by writing these two requests, there is a lot of repetition in the logic:

1. The three types of formatting logic, string, currency and number, are repeated a lot. Take string processing as an example.

As you can see, the processing of formatting strings is just that the length of each field is different, and the rest of the formatting rules are exactly the same, but in the above, we have completed the same set of processing logic for each string. This set of stitching rules can be extracted completely (because only the length is different, the stitching rules are the same).

2. The logic of string concatenation, countersigning and sending requests in the processing process is repeated in all methods.

3. Because the order in which each field participates in splicing is different, we need human flesh hard coding to ensure the order of these fields. The maintenance cost is extremely high, and it is easy to make mistakes. Imagine that if the parameters reach dozens or hundreds, these parameters need to be spliced in a certain order. If human flesh is required to guarantee, it is difficult to guarantee correctness, and the loss outweighs the gain because there is too much repetitive work.

Next, let's take a look at how to greatly simplify our code with annotations.

1. First of all, for each calling interface, they need to request the network at the bottom, but the request method is different. For this, we can make a note for the interface as follows.

@ Retention (RetentionPolicy.RUNTIME) @ Target (ElementType.TYPE) @ Documented @ Inherited public @ interface BankAPI {String url () default "; String desc () default";}

In this way, the method name of the corresponding interface can be obtained uniformly through annotations in the network request layer.

2. For the POJO of each request interface, we notice that each attribute has three attributes: type (string / number / currency), length and order, so we can define an annotation that contains these three attributes, as follows

@ Retention (RetentionPolicy.RUNTIME) @ Target (ElementType.FIELD) @ Documented @ Inherited public @ interface BankAPIField {int order () default-1; int length () default-1; String type () default "; / / M represents currency, S represents string, N represents number}

Next, we apply the annotations defined above to the request POJO above

For creating user requests

@ BankAPI (url = "/ createUser", desc = "create user interface") @ Data public class CreateUserAPI extends AbstractAPI {@ BankAPIField (order = 1, type = "S", length = 10) private String name; @ BankAPIField (order = 2, type = "S", length = 18) private String identity; @ BankAPIField (order = 4, type = "S", length = 11) / / Note that order here needs to be private String mobile in the order shown in the API table. @ BankAPIField (order = 3, type = "N", length = 5) private int age;}

For payment interface

@ BankAPI (url = "/ bank/pay", desc = "payment interface") @ Data public class PayAPI extends AbstractAPI {@ BankAPIField (order = 1, type = "N", length = 20) private long userId; @ BankAPIField (order = 2, type = "M", length = 10) private BigDecimal amount;}

The next process to be invoked using annotations is as follows

Get the Field array of the class based on reflection, and then sort the Field according to the order value in the BankAPIField annotation of Field

The sorted Field is traversed sequentially, first judging its type, and then formatting its value according to the type. If it is judged to be "S", the value is formatted according to the format of the string required by the interface, and these formatted Field values are spliced and signed in turn.

After stitching, the request is sent. At this time, get the annotation of the POJO class, get the url value of the annotation BankAPI, combine it with baseUrl to form a complete url, and add the concatenation string in step 2 to construct a complete request.

The code is as follows:

Private static String remoteCall (AbstractAPI api) throws IOException {/ / get the request address BankAPI bankAPI = api.getClass () .getAnnotation (BankAPI.class) from the BankAPI comment; bankAPI.url (); StringBuilder stringBuilder = new StringBuilder () Arrays.stream (api.getClass (). GetDeclaredFields ()) / / get all fields. Filter (field-> field.isAnnotationPresent (BankAPIField.class)) / / find the fields marked with annotations. Sorted (Comparator.comparingInt (a-> a.getAnnotation (BankAPIField.class). Order ()) / sort the fields according to the order in the annotations. Peek (field-> field) .setAccessible (true) / / can access private fields .forEach (field-> {/ / get annotations BankAPIField bankAPIField = field.getAnnotation (BankAPIField.class)) Object value = ""; try {/ / reflection get field value value = field.get (api);} catch (IllegalAccessException e) {e.printStackTrace () } / / format the string switch (bankAPIField.type ()) {case "S": {stringBuilder.append (String.format ("% -" + bankAPIField.length () + "s", value.toString ()) .replace ('','_') according to the field type Break;} case "N": {stringBuilder.append (String.format ("%" + bankAPIField.length () + "s", value.toString ()). Replace (','0')); break } case "M": {if (! (value instanceof BigDecimal)) throw new RuntimeException (String.format ("{} of {} must be BigDecimal", api, field)) StringBuilder.append (String.format ("% 0" + bankAPIField.length () + "d", ((BigDecimal) value) .setScale (2, RoundingMode.DOWN) .multiply (new BigDecimal ("100s"). LongValue ()); break;} default: break }}); / / signature logic stringBuilder.append (DigestUtils.md2Hex (stringBuilder.toString (); String param = stringBuilder.toString (); long begin = System.currentTimeMillis () / / request String result = Request.Post ("http://localhost:45678/reflection" + bankAPI.url ()) .bodyString (param, ContentType.APPLICATION_JSON) .execute (). ReturnContent (). AsString (); log.info (" calling bank API {} url: {} parameter: {} time: {} ms ", bankAPI.desc (), bankAPI.url (), param, System.currentTimeMillis ()-begin) Return result;}

Now let's take a look at the logic of creating users and paying.

/ / create public static String createUser (CreateUserAPI request) throws IOException {return remoteCall (request);} / / payment method public static String pay (PayAPI request) throws IOException {return remoteCall (request);}

You can see that all requests now only need to call the remoteCall method. RemoteCall method unifies the logic of all requests, omits a large amount of irrelevant code, and greatly enhances the maintainability of the code! The use of annotations and reflections allows us to generalize such structural problems, indeed Cool!

This is the end of "how to use Notes correctly". Thank you for your reading. If you want to know more about the industry, you can follow the website, the editor will output more high-quality practical articles for you!

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Development

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report