SpringBoot AOP causes service injection to be null
1. Due to business needs
To record user operation logs, SpringAOP is undoubtedly required.
2. First introduce SpringBoot's AOP maven dependency
<dependency> <groupId></groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
3. There are many ways to implement this log operation
For example, writing an interceptor, or adding it to the original code based on annotation form, is OK, but the last one will undoubtedly pollute the original code and at the same time will pollute the original logic. In today's business scenarios, all the logic code has been written and self-tested, and the number of query interfaces is significantly greater than (addition, deletion and modification), and what we care most about in the user's operation log is undoubtedly the operation of data. Therefore, annotation-based implementation is chosen.
4. Annotation writing
On the code
@Retention()//Meta-annotation, defining the strategy of the annotation being retained, generally there are three strategies//1. Annotations are only retained in the source file and are abandoned when compiled into class file//2. The annotation is retained in the class, but it is abandoned by Nordic when jvm is loaded. This is the default declaration cycle//3. The annotation is still retained when jvm is loaded@Target({}) //Defines which elements the annotation declaration is before@Documented public @interface SystemOperaLog { //Define members String descrption() default "" ;//describe String actionType() default "Add to" ;//The type of operation, 1. Add 2. Modify 3. Delete}
5. The following is some annotations in SpringBoot AOP
Learn about it
- @Aspect: Describe a section class. When defining a section class, you need to put this annotation on it.
- @Configuration: spring-boot configuration class
- @Pointcut: Declare an entry point, which determines the content of the connection point's attention, allowing us to control when the notification is executed. Spring AOP only supports Spring bean method execution connection points. So you can think of point-input as a match for the method execution on the Spring bean. A point-cut declaration has two parts: a signature containing a name and arbitrary parameters, and a point-cut expression that determines the execution of that method we focus on.
Note: The method as a point-cut signature must return the void type
Spring AOP supports the use of the following point-cut indicators in point-cut expressions:
- execution - Match the connection point that the method executes, which is the main point-cut indicator for Spring you will use.
- within - Qualify connection points that match a specific type (execution of methods defined in the matching type when using Spring AOP).
- this - Qualify matching specific connection points (execution of methods when using Spring AOP), where bean reference (Spring AOP proxy) is an instance of the specified type.
- target - Qualify matching specific connection points (execution of methods when using Spring AOP), where the target object (proxy application object) is an instance of the specified type.
- args - Qualify matching specific connection points (execution of methods when using Spring AOP), where the parameters are instances of the specified type.
- @target - Qualify matching specific connection points (execution of methods when using Spring AOP), where the class that is executing the object holds annotations of the specified type.
- @args - Qualify matching specific connection points (execution of methods when using Spring AOP), where the runtime type of the actual passed parameter holds annotations of the specified type.
- @within - Qualify matching specific connection points, where the type of connection points is located has annotated (when using Spring AOP, the type of method executed has annotated specified).
- @annotation - Qualify matching specific connection points (execution of methods when using Spring AOP), where the topic of the connection points holds the specified annotation.
Among them, execution is used most frequently, that is, it is cut into the process when a certain method is executed. There is an important knowledge in defining point-cutting, namely point-cutting expressions. We will explain how to write point-cutting expressions later.
The entry point means when to enter what method. Defining an entry point is equivalent to defining a "variable". A notice is required when to use this variable.
Connect the face to the target object.
As shown in the example, notifications can be defined by annotations, and the parameters in the annotations are entry points.
Spring AOP supports notifications:
- @Before: Pre-notification: Notification executed before a connection point, but this notification cannot prevent the execution flow before the connection point (unless it throws an exception).
- @AfterReturning: Post-notification: Notifications executed after a connection point is completed normally, usually executed when a matching method returns.
6. Understand the above steps
What needs to be done is to record the user's normal request and the information that needs to be recorded in exception requests, corresponding to the entity operation
@Data @EqualsAndHashCode(callSuper = false) @Accessors(chain = true) @ApiModel(value="SysOperaLog Object", description="") public class SysOperaLog implements Serializable { private static final long serialVersionUID = 1L; @TableId(value = "log_id", type = ) private Integer logId; @ApiModelProperty(value = "Operation Type") private String type; @ApiModelProperty(value = "Access Resource Path") private String url; @ApiModelProperty(value = "Operator") private String operaUser; @ApiModelProperty(value = "Method Name") private String methodName; @ApiModelProperty(value = "Access Carrying Parameters") private String params; @ApiModelProperty(value = "Remote IP Address") private String ipAddress; @ApiModelProperty(value = "Access Time") @TableField("operaTime") @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss") private LocalDateTime operaTime; @ApiModelProperty(value = "Total access time unit milliseconds") @TableField("timeLong") private Long timeLong; @ApiModelProperty(value = "describe") private String des; @ApiModelProperty(value = "Access Status 0 Access Successful -1 Access Failed") private Integer visitState; @ApiModelProperty(value = "Exception information") private String exceptionDetail; }
7. User requests normally
Add custom annotations to the user's normal request header
@PostMapping("/save") @SystemOperaLog(descrption = "New parking space information") public Result saveParkLot(@ApiParam(name = "parkLotsDTO", value = "Parking space information") @RequestBody ParkLotsDTO parkLotsDTO, BindingResult bindingResult) { // Now indicates that there is an error in the execution of verification if (()) { // Get all error messages List<ObjectError> allErrors = (); String errorMsg = ""; if (!(allErrors)) { errorMsg = (0).getDefaultMessage(); } return (errorMsg); } else { (parkLotsDTO); return (); } }
8. Intercept the pre-operation and exceptional operations entering this method in AOP
@Aspect @Configuration @Slf4j public class SystemOperaLogAop { @Autowired private SysOperaLogMapper sysOperaLogMapper; /*** * Define controller entry point interception rules and intercept SystemControllerLog annotations */ @Pointcut("@annotation()") public void controllerAspect(){} /*** * Intercept the operation log of the control layer * @param joinPoint * @return * @throws Throwable */ @Around("controllerAspect()") public Object recordLog(ProceedingJoinPoint joinPoint) throws Throwable { //1, start time long beginTime = (); //Use RequestContextHolder to get the request object ServletRequestAttributes requestAttr = (ServletRequestAttributes)(); String uri = ().getServletPath(); //Access the parameters of the target method and change the parameter value dynamically Object[] args = (); //Get method name String methodName = ().getName(); //It may be that when the reverse proxy request comes in, the obtained IP has an incorrect line. Here is a copy of a code from the Internet to obtain IP. Authentication authentication = ().getAuthentication(); SysUserDetails sysUserDetails = (SysUserDetails) (); Signature signature = (); if(!(signature instanceof MethodSignature)) { throw new IllegalArgumentException("Not method annotation is not supported yet"); } //Calling the actual method Object object = (); //Get the execution method MethodSignature methodSign = (MethodSignature) signature; Method method = (); //Determine whether it contains method that does not require logging long endTime = (); //Simulate exception //(1/0); SysOperaLog systemLogDTO = new SysOperaLog(); (getAnnontationMethodDescription(joinPoint,1)); (uri); (()); (methodName); (getIpAddr(())); List list = (args); List arrayList = new ArrayList(list); //Remove operation requires converting the array converted collection type to the collection type here Iterator iterator = (); while (()){ if(().toString().contains("BeanPropertyBindingResult")){ (); break; } } (()); (()); (getAnnontationMethodDescription(joinPoint,0)); (endTime - beginTime); (systemLogDTO); return object; } //Exception handling @AfterThrowing(pointcut = "controllerAspect()",throwing="e") public void doAfterThrowing(JoinPoint joinPoint, Throwable e) throws Throwable{ //1, start time long beginTime = (); //Use RequestContextHolder to get the request object ServletRequestAttributes requestAttr = (ServletRequestAttributes)(); String uri = ().getServletPath(); //Access the parameters of the target method and change the parameter value dynamically Object[] args = (); //Get method name String methodName = ().getName(); //It may be that when the reverse proxy request comes in, the obtained IP has an incorrect line. Here is a copy of a code from the Internet to obtain IP. Authentication authentication = ().getAuthentication(); SysUserDetails sysUserDetails = (SysUserDetails) (); Signature signature = (); if(!(signature instanceof MethodSignature)) { throw new IllegalArgumentException("Not method annotation is not supported yet"); } //Get the execution method MethodSignature methodSign = (MethodSignature) signature; Method method = (); //Determine whether it contains method that does not require logging long endTime = (); //Simulate exception SysOperaLog systemLogDTO = new SysOperaLog(); (uri); (getAnnontationMethodDescription(joinPoint,1)); (()); (methodName); (getIpAddr(())); List list = (args); List arrayList = new ArrayList(list); //Remove operation requires converting the array converted collection type to the collection type here Iterator iterator = (); while (()){ if(().toString().contains("BeanPropertyBindingResult")){ (); break; } } (()); (()); (endTime - beginTime); (getAnnontationMethodDescription(joinPoint,0)); (-1); (()); (systemLogDTO); } public static String getIpAddr(HttpServletRequest request) { String ipAddress = null; try { ipAddress = ("x-forwarded-for"); if (ipAddress == null || () == 0 || "unknown".equalsIgnoreCase(ipAddress)) { ipAddress = ("Proxy-Client-IP"); } if (ipAddress == null || () == 0 || "unknown".equalsIgnoreCase(ipAddress)) { ipAddress = ("WL-Proxy-Client-IP"); } if (ipAddress == null || () == 0 || "unknown".equalsIgnoreCase(ipAddress)) { ipAddress = (); if (("127.0.0.1")) { // Get the IP configured by the network card InetAddress inet = null; try { inet = (); } catch (UnknownHostException e) { ("Getipabnormal:{}" ,()); (); } ipAddress = (); } } // For the case of multiple proxying, the first IP is the real IP of the client, and multiple IPs are divided according to ',' if (ipAddress != null && () > 15) { // = 15 if ((",") > 0) { ipAddress = (0, (",")); } } } catch (Exception e) { ipAddress = ""; } return ipAddress; } /*** * Get the controller's operation information * @param point * @return */ public String getAnnontationMethodDescription(JoinPoint point,Integer type) throws Exception{ //Get the connection point target class name String targetName = ().getClass().getName() ; //Get the method name of the connection point signature String methodName = ().getName() ; //Get connection point parameters Object[] args = () ; //Get the specified class based on the name of the connection point class Class targetClass = (targetName); //Get the method in the class Method[] methods = () ; String description="" ; for (Method method : methods) { if (().equals(methodName)){ Class[] clazzs = (); if ( == ){ if(type==0){ description = ().descrption(); break; }else{ description = ().actionType(); break; } } } } return description ; }
9. Tell me about a pitfall I stepped on
Now the logs can work normally, but the business code has failed. The service is empty (partial code).
After a meal at Baidu, I found that AOP can only be effective for public and provide. If your method limit is private, then service injection is empty. In springboot, cglib is used by default to proxy operation objects. First of all, private methods will not appear in the proxy class, which is the fundamental reason why proxy objects cannot operate private
- jdk is a proxy interface, and private methods will inevitably not exist in the interface, so they will not be intercepted;
- cglib is a subclass, and the private method will not appear in the subclass and cannot be intercepted.
10. The fundamental solution
It is not to force cglib to proxy, but to not set private attributes in your controller. The above only represents personal opinions.
Summarize
The above is personal experience. I hope you can give you a reference and I hope you can support me more.