GitHub 18K Star Java engineer into god’s path, not to learn about it!

GitHub 18K Star Java engineer into god’s road, really not to learn about it!

GitHub 18K Star Java engineer into god’s road, really really don’t come to know it!

Many of you are familiar with annotations in Java, such as @Override, @AutoWired, @Service, etc., which are provided by the JDK or frameworks such as Spring.

In previous interviews, I found that the knowledge of annotations was mostly limited to the level of use, and few programmers knew how annotations were implemented, let alone how to use custom annotations to solve practical problems.

But in fact, I think the standard of a good programmer is to know how to optimize their own code, so in code optimization, how to simplify the code and get rid of duplicate code is a crucial topic, in this topic area, custom annotations can definitely be a great contribution.

So, in my opinion, programmers who use custom annotations are good.

So, in this article, I’ll show you some examples of how the author actually uses annotations to improve your code.

Basic knowledge of

There are two types of annotations in Java, meta-annotations and custom annotations.

Many people make the mistake of thinking that custom annotations are defined by the developer and not by other frameworks, but the annotations mentioned above are actually custom annotations.

There are many descriptions of “meta” in the programming world, such as “meta annotation”, “metadata”, “metaclass”, “mettable”, etc. The “meta” here is actually translated from the meta.

Generally we understand meta-annotations as annotations that describe annotations, metadata as data that describe data, and metaclasses as classes that describe classes…

So, in Java, all annotations are custom annotations, except for a limited number of fixed “annotations that describe annotations”.

There are four standard annotation classes (meta-annotation classes) available in the JDK for annotating annotation types:

@Target
@Retention
@Documented
@Inherited
Copy the code

Except for these four, all other annotations are custom annotations.

I am not going to go into the functions of the above four meta-annotations, you can learn by yourself.

The examples I will mention in this article are real scenarios that the author uses in his daily work. One thing they have in common is that they all use Spring AOP technology.

What is AOP and its usage believe that many people know, here will not expand the introduction.

Use custom annotations for logging

I don’t know if you have encountered a similar appeal, is the hope of a method at the entrance or exit of the unified log processing, such as recording input parameters, input parameters, record method execution time, etc..

If you write your own code for each method, there will be a lot of code duplication on the one hand, and it will be easy to miss.

In this scenario, you can use custom annotations + facets to achieve this functionality.

Suppose we want to record what was done, such as adding a record or deleting a record, on some Web request method.

First we define a custom annotation:

@target (ElementType.METHOD) @Retention(retentionPolicy.runtime) public @interface OpLog { /** * Service type, such as new, delete, modify ** @return */ public OpType OpType (); /** * business object name, such as order, inventory, price ** @return */ public String opItem(); /** * public String opItemIdExpression(); /** * public String opItemIdExpression(); }Copy the code

Not only do we need to record what was done in the log, but we also need to know the specific unique identity of the object being operated on, such as the order number information.

However, the parameter type of each interface method is certainly different, and it is difficult to have a unified standard, so we can use the Spel expression, that is, in the expression to specify how to obtain the unique identity of the corresponding object.

With the above notes, you can now write the section. The main code is as follows:

/** * OpLog section processing class, used to get log information through annotations, * * @author Hollis */ @aspect @Component public class OpLogAspect {private static final Logger Logger = LoggerFactory.getLogger(OpLogAspect.class); @Autowired HttpServletRequest request; @Around("@annotation(com.hollis.annotation.OpLog)") public Object log(ProceedingJoinPoint pjp) throws Exception { Method  method = ((MethodSignature)pjp.getSignature()).getMethod(); OpLog opLog = method.getAnnotation(OpLog.class); Object response = null; Try {// The target method executes response = ppj.proceed (); } catch (Throwable throwable) { throw new Exception(throwable); } if (StringUtils.isNotEmpty(opLog.opItemIdExpression())) { SpelExpressionParser parser = new SpelExpressionParser(); Expression expression = parser.parseExpression(opLog.opItemIdExpression()); EvaluationContext context = new StandardEvaluationContext(); Object[] args = pjp.getargs (); / / get the name of the runtime parameters LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer (); String[] parameterNames = discoverer.getParameterNames(method); // Bind parameters to context if (parameterNames! = null) { for (int i = 0; i < parameterNames.length; i++) { context.setVariable(parameterNames[i], args[i]); }} // Put the method's resp into context as a variable. The variable name is the class name. Convert this to a hump form beginning with a lowercase letter if (response! = null) { context.setVariable( CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_CAMEL, response.getClass().getSimpleName()), response); String itemId = String.valueof (expression.getValue(context)); // Handle (oplog.optype (), oplog.opitem (), itemId); } return response; } private void handle(OpType opType, String opItem, String opItemId) {// Print logger.info ("opType = "+ opType.name() +",opItem =" +opItem +", opItemId =" +opItemId); }}Copy the code

In the above sections, there are several points that need your attention:

1. Use the @around annotation to specify the set aspect of the OpLog labeled method. 2. Use the relevant method of Spel to obtain the unique identification of the target object from the corresponding parameters through the specified representation. 3. After the method is successfully executed, generate logs.

With the above sections and annotations, we just need to add annotations to the corresponding method, such as:

 @RequestMapping(method = {RequestMethod.GET, RequestMethod.POST})
@OpLog(opType = OpType.QUERY, opItem = "order", opItemIdExpression = "#id")
public @ResponseBody
HashMap view(@RequestParam(name = "id") String id)
    throws Exception {
}
Copy the code

The parameter list of the input parameter already has a unique identifier for the object being operated on.

If the unique identifier of the object being operated on is not in the input parameter list, it may be one of the attributes of the input object, as follows:

 @RequestMapping(method = {RequestMethod.GET, RequestMethod.POST})
@OpLog(opType = OpType.QUERY, opItem = "order", opItemIdExpression = "#orderVo.id")
public @ResponseBody
HashMap update(OrderVO orderVo)
    throws Exception {
}
Copy the code

This can be obtained from the value of the ID attribute of the OrderVO object as an input parameter.

What if the unique identifier we want to record does not exist in the input parameter? The most typical method is the insert method, and you don’t know what the primary key ID is until the insert succeeds.

One of the things we did in the above section is that we parse the return value of a method using an expression, if we can parse it to a specific value, yes. As follows:

 @RequestMapping(method = {RequestMethod.GET, RequestMethod.POST})
@OpLog(opType = OpType.QUERY, opItem = "order", opItemIdExpression = "#insertResult.id")
public @ResponseBody
InsertResult insert(OrderVO orderVo)
    throws Exception {

    return orderDao.insert(orderVo);
}
Copy the code

Above, this is a simple logging scenario using custom annotations + sections. Let’s look at another example of how to use annotations to validate method parameters.

Use custom annotations for pre-checking

When we provide interfaces to the outside world, we will have certain requirements on some parameters, for example, some parameter values cannot be empty. In most cases, we need to take the initiative to verify that the incoming value is reasonable.

Here is a recommended way to implement parameter validation using HibernateValidator + custom annotations + AOP.

First we will have a concrete input parameter class, defined as follows:

public class User {
    private String idempotentNo;
    @NotNull(
        message = "userName can't be null"
    )
    private String userName;
}
Copy the code

The value of userName cannot be null.

Then use Hibernate Validator to define a utility class for parameter validation.

** @author Hollis */ public class BeanValidator {private static Validator Validator = Validation.byProvider(HibernateValidator.class).configure().failFast(true) .buildValidatorFactory().getValidator(); /** * @param object object * @param groups groups */ public static void validateObject(Object object, Class<? >... groups) throws ValidationException { Set<ConstraintViolation<Object>> constraintViolations = validator.validate(object, groups); if (constraintViolations.stream().findFirst().isPresent()) { throw new ValidationException(constraintViolations.stream().findFirst().get().getMessage()); }}}Copy the code

The above code validates a bean and throws a ValidationException if it fails.

Next, define an annotation:

/** * facade interface annotation, used to unify the facade parameter verification and exception capture * <pre> * Note, note that using this annotation, The return value of this METHOD must be a subclass of BaseResponse * </pre> */ @target (elementType.method) @Retention(retentionPolicy.runtime) public @interface Facade { }Copy the code

This annotation does not contain any parameters and is only used to indicate which methods need parameter validation.

Next, define the section:

/** * Facade section class, * * @author Hollis */ @aspect @Component public class FacadeAspect {private static final Logger Logger = LoggerFactory.getLogger(FacadeAspect.class); @Autowired HttpServletRequest request; @Around("@annotation(com.hollis.annotation.Facade)") public Object facade(ProceedingJoinPoint pjp) throws Exception { Method method = ((MethodSignature)pjp.getSignature()).getMethod(); Object[] args = pjp.getArgs(); Class returnType = ((MethodSignature)pjp.getSignature()).getMethod().getReturnType(); / / to iterate over all the parameters, parameter calibration for (Object parameter: the args) {try {BeanValidator. ValidateObject (parameter); } catch (ValidationException e) { return getFailedResponse(returnType, e); }} try {// The target method executes Object response = ppp.proceed (); return response; } catch (Throwable throwable) { return getFailedResponse(returnType, throwable); Private Object getFailedResponse(Class returnType, Throwable throwable) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {// If the return value is a subclass of BaseResponse, Failed to create a common response to the if (returnType. GetDeclaredConstructor (). The newInstance () instanceof BaseResponse) {BaseResponse response = (BaseResponse)returnType.getDeclaredConstructor().newInstance(); response.setSuccess(false); response.setResponseMessage(throwable.toString()); response.setResponseCode(GlobalConstant.BIZ_ERROR); return response; } LOGGER.error( "failed to getFailedResponse , returnType (" + returnType + ") is not instanceof BaseResponse"); return null; }}Copy the code

The above code, similar to the previous section, mainly defines a section that will uniformly treat all methods labeled @facade. That is, the parameter verification is performed before the method call starts. If the verification fails, a fixed failed Response is returned. The reason we can return a fixed BaseResponse is because we require that all of our interface responses must inherit from the BaseResponse class, which defines default parameters such as error codes.

After that, just add annotations to the methods that require parameter verification:

@Facade
public TestResponse query(User user) {

}
Copy the code

This way, with the above annotations and facets, we can have unified control over all external methods.

As a matter of fact, I left out a lot of things in the aspect above. The section we actually use does more than just checking parameters. For example, unified handling of exceptions, unified conversion of error codes, recording method execution time, recording method input and output parameters, and so on.

All in all, there are a lot of things we can do in unison using aspects + custom annotations. In addition to the above scenarios, we have many similar uses, such as:

Unified cache handling. For example, you need to check the cache before and update the cache after some operations. This can be done through custom annotations + sections.

In fact, the code is similar, the idea is relatively simple, is to use custom annotations to mark the fatigue or method to be processed in the section, and then in the section to intervene in the method execution process, such as before or after the execution of some special operations.

Using this approach can greatly reduce code duplication, greatly improve the code elegance, easy to use.

But at the same time, don’t overuse them, because annotations look simple, but there’s a lot of logic inside that’s easy to ignore. For example, I wrote about @Transactional transactions, which Spring officially recommends. Why don’t I recommend them? Mindless use of sections and annotations may introduce unnecessary problems.

However, custom annotations are a nice invention that can reduce a lot of code duplication. Use it in your projects.

About the author: Hollis, a person with a unique pursuit of Coding, is a technical expert of Alibaba, co-author of “Three Courses for Programmers”, and author of a series of articles “Java Engineers becoming Gods”.

If you have any comments, suggestions, or want to communicate with the author, you can follow the public account [Hollis] and directly leave a message to me in the background.