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 !
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 !