当前位置:网站首页>Custom annotation! Absolutely is the sharp weapon that programmer installs force!!

Custom annotation! Absolutely is the sharp weapon that programmer installs force!!

2020-11-10 10:44:22 HollisChuang.

GitHub 18k Star Of Java The way for engineers to become gods , Don't you come to know about it !

GitHub 18k Star Of Java The way for engineers to become gods , Don't you really come to understand !

GitHub 18k Star Of Java The way for engineers to become gods , I really don't want to know about it !

I believe many people are right Java I'm familiar with the notes in , For example, we often use some, such as @Override、@Autowired、@Service etc. , These are all JDK Or things like Spring This kind of framework provides us with .

In the past interview process , I find , A lot of programmers just stay at the level of using annotations , Few people know how annotations are implemented , Not to mention using custom annotations to solve practical problems .

But in fact , I think a good programmer's standard is to know how to optimize their own code , That's in code optimization , How to simplify code , Getting rid of duplicate code is a crucial topic , In this topic area , Custom annotations can definitely be a great credit .

therefore , in my opinion , Will use custom annotations ≈ Good programmers .

that , this paper , Let me introduce a few , The author has used several examples in the development , Let's show you how to use annotations to enhance the power of your code .

Basic knowledge

stay Java in , There are two kinds of annotations , Meta annotations and custom annotations .

Many people mistakenly think that custom annotations are defined by developers themselves , And other frameworks don't , But in fact, the annotations mentioned above are all custom annotations .

About " element " This description , There's a lot of it in the programming world , such as " Yuan notes "、" Metadata "、" The metaclass "、" Metatable " wait , there " element " Actually, it's all from meta Translated from .

Generally, we put Meta annotations are understood as annotations describing annotations , Metadata is understood as data describing data , Metaclass is understood as a class describing a class ...

therefore , stay Java in , Except for a limited number of fixed " Notes describing annotations " outside , All annotations are custom annotations .

stay JDK Provided in 4 A standard annotation class used to annotate annotation types ( Yuan notes ), They are :

@Target
@Retention
@Documented
@Inherited

Except for the above four , All other annotations are custom annotations .

I'm not going to go into the four meta annotations here , We can learn by ourselves .

Several examples to be mentioned in this article , These are the scenes that the author uses in daily work , This example has one thing in common , That's all used Spring Of AOP technology .

What is? AOP And his usage, I believe many people know , I will not introduce it here .

Use custom annotations for logging

I don't know if you have encountered similar appeals , It is hoped that unified log processing can be done at the entrance or exit of a method , For example, record the input parameters 、 The ginseng 、 Record the execution time of the method, etc .

If you write this code yourself in each method , On the one hand, there will be a lot of code duplication , In addition, it is easy to be missed .

This scenario , You can use custom annotations + Faceted implementation of this function .

Suppose we want to be in some web On the method of request , Record what you did in this operation , Such as adding a record or deleting a record .

First, let's customize an annotation :

/**
 * Operate Log  Custom comments for 
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface OpLog {

    /**
     *  Business types , If new 、 Delete 、 modify 
     *
     * @return
     */
    public OpType opType();

    /**
     *  Business object name , Like an order 、 stock 、 Price 
     *
     * @return
     */
    public String opItem();

    /**
     *  Business object number expression , Describes how to get an expression for the order number 
     *
     * @return
     */
    public String opItemIdExpression();
}

Because we not only need to record in the log what this operation , You also need to know the specific unique identity of the object being manipulated , Such as order number information .

But the parameter type of each interface method is definitely different , It's hard to have a uniform standard , Then we can use Spel expression , In other words, the expression indicates how to obtain the unique identification of the corresponding object .

With the note above , Then we can write the section . The main codes are as follows :

/**
 * OpLog Section processing class of , Used to get log information through annotations , Logging 
 *
 * @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 {
            //  Target method execution 
            response = pjp.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();
            //  Get parameter value 
            Object[] args = pjp.getArgs();

            //  Gets the name of the runtime parameter 
            LocalVariableTableParameterNameDiscoverer discoverer
                = new LocalVariableTableParameterNameDiscoverer();
            String[] parameterNames = discoverer.getParameterNames(method);

            //  Bind parameters to context in 
            if (parameterNames != null) {
                for (int i = 0; i < parameterNames.length; i++) {
                    context.setVariable(parameterNames[i], args[i]);
                }
            }

            //  The method of resp Put as variable context in , The variable name is the hump form that the class name is converted into a lowercase letter 
            if (response != null) {
                context.setVariable(
                    CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_CAMEL, response.getClass().getSimpleName()),
                    response);
            }

            //  Analytic expression , To get the results 
            String itemId = String.valueOf(expression.getValue(context));

            //  Execution logging 
            handle(opLog.opType(), opLog.opItem(), itemId);
        }

        return response;
    }


    private void handle(OpType opType,  String opItem, String opItemId) {
      //  Print out by log 
      LOGGER.info("opType = " + opType.name() +",opItem = " +opItem + ",opItemId = " +opItemId);
    }
}

In the above section , There are a few points that we need to pay attention to :

1、 Use @Around Annotation to specify the annotation OpLog Method to set the section . 2、 Use Spel Related methods , By the specified representation , Get the unique identification of the target object from the corresponding parameters . 3、 After the successful execution of the method , Output log .

With the above sections and annotations , We just need to add annotations on the corresponding methods , 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 {
}

The above is the unique identification of the object to be operated in the parameter list , Use it directly #id Just specify .

If the unique identity of the object being manipulated is not in the input parameter list , Then it may be an attribute of the input object , Usage is 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 {
}

above , From the OrderVO Object's id Property value acquisition .

If we want to record a unique identifier , If it is not included in the reference , What should I do ? The most typical is the insertion method , Before successful insertion , You don't know the primary key at all ID What is it? , What about this ?

In the section above us , Did one thing , Even if we use the expression to parse the return value of the method , If you can parse to get the specific value , Yes, 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);
}

above , It's a simple way to use custom annotations + The scene of logging from different aspects . Let's take a look at how to use annotations to verify method parameters .

Using custom annotations for pre checking

When we provide interfaces to the outside world , There will be certain requirements for some of the parameters , For example, some parameter values cannot be empty . In most cases, we need to take the initiative to verify , Judge whether the value passed in by the other party is reasonable .

Here we recommend one to use HibernateValidator + Custom annotation + AOP How to realize parameter verification .

First of all, we will have a specific reference class , The definition is as follows :

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

above , Yes userName Parameter indication cannot be null.

And then use hibernate validator Define a tool class , It is used for parameter verification .

/**
 *  Parameter verification tool 
 *
 * @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());
        }
    }
}

Above code , Will be right for one bean check , Once it fails , Will throw ValidationException.

Next, define an annotation :

/**
 * facade Interface notes ,  Used to unify pairs facade Parameter verification and exception capture 
 * <pre>
 *       Be careful , Using this annotation requires attention , The return value of this method must be BaseResponse Subclasses of 
 * </pre>
 */

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

}

There are no parameters in this comment , It is only used to mark those methods for parameter verification .

Next, define the facets :

/**
 * Facade Section processing class of , Unified statistics for parameter verification and exception capture 
 *
 * @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();

        // Loop through all the parameters , Check the parameters 
        for (Object parameter : args) {
            try {
                BeanValidator.validateObject(parameter);
            } catch (ValidationException e) {
                return getFailedResponse(returnType, e);
            }
        }

        try {
            //  Target method execution 
            Object response = pjp.proceed();
            return response;
        } catch (Throwable throwable) {
            return getFailedResponse(returnType, throwable);
        }
    }

    /**
     *  Define and return a generic failure response 
     */
    private Object getFailedResponse(Class returnType, Throwable throwable)
        throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {

        // If the return value is of type BaseResponse  Subclasses of , Create a generic failure response 
        if (returnType.getDeclaredConstructor().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;
    }
}

Above code , It's a little bit similar to the front section , It mainly defines a facet , All labels will be marked @Facade The method of unified processing , That is, the parameter verification is performed before the method call is started , Once the verification fails , Then return a fixed failure Response, One thing in particular to note , The reason why we can return a fixed BaseResponse, It is because we will require all our external interfaces response Must inherit BaseResponse class , Some parameters in this class will be defined by default , Such as error code, etc .

after , Just add the corresponding annotation to the method that needs parameter verification :

@Facade
public TestResponse query(User user) {

}

such , With the above comments and sections , We can have unified control over all external methods .

Actually , This one above facadeAspect I omitted a lot of things , The facet we really use , It's not just a parameter check , There are many other things that can be done . For example, the unified handling of exceptions 、 Unified conversion of error codes 、 Record the execution time of the method 、 Record the input and output parameters of methods and so on .

All in all , Using facets + Custom annotation , We can do a lot of things together . In addition to the above scenes , We have many similar usages , such as :

Unified cache processing . For example, some operations need to check the cache before the operation 、 Update cache after operation . This can be done through custom annotations + The way of cut-off is unified .

The code is almost the same , The idea is also simple , It is to mark the accumulation or method that needs to be faceted through custom annotation , Then in the aspect of the implementation process of the method intervention , For example, do some special operations before or after execution .

Using this method can greatly reduce duplicate code , Greatly improve the elegance of the code , It's convenient for us to use .

But it can't be overused at the same time , Because annotations seem simple , But in fact, there are a lot of internal logic that can be easily ignored . It's like I wrote an article before 《Spring It's officially recommended @Transactional Business , Why I don't recommend using !》 The same point of view is mentioned in , The use of facets and annotations without brain , Some unnecessary problems may be introduced .

Anyway? , Custom annotations are a great invention , Can reduce a lot of duplicate code . Use it in your project .

This article by the blog one article many sends the platform OpenWrite Release !

版权声明
本文为[HollisChuang.]所创,转载请带上原文链接,感谢