SpringBoot AOP導(dǎo)致service注入后是null的問題
SpringBoot AOP導(dǎo)致service注入后是null
1.由于業(yè)務(wù)需求需要
記錄用戶操作日志,無疑需要使用到SpringAOP。
2.先引入SpringBoot的AOP maven 依賴
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
3.實(shí)現(xiàn)這個(gè)日志操作可以有很多種方法
比如寫攔截器,或者基于注解形式,在或者在原來寫好的代碼中添加也行,但是最后的一種無疑會污染原來的代碼,同時(shí)會對原來的邏輯有一定污染,而現(xiàn)在的業(yè)務(wù)場景,所有的邏輯代碼都已經(jīng)編寫完成,自測完成,而且查詢接口的數(shù)量明顯大于(增刪改),而且用戶的操作日志我們最關(guān)心的無疑是對數(shù)據(jù)的操作。所以選擇基于注解的形式實(shí)現(xiàn)。
4.注解編寫
上代碼
@Retention(RetentionPolicy.RUNTIME)//元注解,定義注解被保留策略,一般有三種策略 //1、RetentionPolicy.SOURCE 注解只保留在源文件中,在編譯成class文件的時(shí)候被遺棄 //2、RetentionPolicy.CLASS 注解被保留在class中,但是在jvm加載的時(shí)候北歐拋棄,這個(gè)是默認(rèn)的聲明周期 //3、RetentionPolicy.RUNTIME 注解在jvm加載的時(shí)候仍被保留 @Target({ElementType.METHOD}) //定義了注解聲明在哪些元素之前 @Documented public @interface SystemOperaLog { //定義成員 String descrption() default "" ;//描述 String actionType() default "添加" ;//操作的類型,1、添加 2、修改 3、刪除 }
5.下面需要對SpringBoot AOP 中一些注解
了解一下
- @Aspect:描述一個(gè)切面類,定義切面類的時(shí)候需要打上這個(gè)注解
- @Configuration:spring-boot配置類
- @Pointcut:聲明一個(gè)切入點(diǎn),切入點(diǎn)決定了連接點(diǎn)關(guān)注的內(nèi)容,使得我們可以控制通知什么時(shí)候執(zhí)行。Spring AOP只支持Spring bean的方法執(zhí)行連接點(diǎn)。所以你可以把切入點(diǎn)看做是Spring bean上方法執(zhí)行的匹配。一個(gè)切入點(diǎn)聲明有兩個(gè)部分:一個(gè)包含名字和任意參數(shù)的簽名,還有一個(gè)切入點(diǎn)表達(dá)式,該表達(dá)式?jīng)Q定了我們關(guān)注那個(gè)方法的執(zhí)行。
注:作為切入點(diǎn)簽名的方法必須返回void 類型
Spring AOP支持在切入點(diǎn)表達(dá)式中使用如下的切入點(diǎn)指示符:
- execution - 匹配方法執(zhí)行的連接點(diǎn),這是你將會用到的Spring的最主要的切入點(diǎn)指示符。
- within - 限定匹配特定類型的連接點(diǎn)(在使用Spring AOP的時(shí)候,在匹配的類型中定義的方法的執(zhí)行)。
- this - 限定匹配特定的連接點(diǎn)(使用Spring AOP的時(shí)候方法的執(zhí)行),其中bean reference(Spring AOP 代理)是指定類型的實(shí)例。
- target - 限定匹配特定的連接點(diǎn)(使用Spring AOP的時(shí)候方法的執(zhí)行),其中目標(biāo)對象(被代理的應(yīng)用對象)是指定類型的實(shí)例。
- args - 限定匹配特定的連接點(diǎn)(使用Spring AOP的時(shí)候方法的執(zhí)行),其中參數(shù)是指定類型的實(shí)例。
- @target - 限定匹配特定的連接點(diǎn)(使用Spring AOP的時(shí)候方法的執(zhí)行),其中正執(zhí)行對象的類持有指定類型的注解。
- @args - 限定匹配特定的連接點(diǎn)(使用Spring AOP的時(shí)候方法的執(zhí)行),其中實(shí)際傳入?yún)?shù)的運(yùn)行時(shí)類型持有指定類型的注解。
- @within - 限定匹配特定的連接點(diǎn),其中連接點(diǎn)所在類型已指定注解(在使用Spring AOP的時(shí)候,所執(zhí)行的方法所在類型已指定注解)。
- @annotation - 限定匹配特定的連接點(diǎn)(使用Spring AOP的時(shí)候方法的執(zhí)行),其中連接點(diǎn)的主題持有指定的注解。
其中execution使用最頻繁,即某方法執(zhí)行時(shí)進(jìn)行切入。定義切入點(diǎn)中有一個(gè)重要的知識,即切入點(diǎn)表達(dá)式,我們一會在解釋怎么寫切入點(diǎn)表達(dá)式。
切入點(diǎn)意思就是在什么時(shí)候切入什么方法,定義一個(gè)切入點(diǎn)就相當(dāng)于定義了一個(gè)“變量”,具體什么時(shí)間使用這個(gè)變量就需要一個(gè)通知。
即將切面與目標(biāo)對象連接起來。
如例子中所示,通知均可以通過注解進(jìn)行定義,注解中的參數(shù)為切入點(diǎn)。
spring aop支持的通知:
- @Before:前置通知:在某連接點(diǎn)之前執(zhí)行的通知,但這個(gè)通知不能阻止連接點(diǎn)之前的執(zhí)行流程(除非它拋出一個(gè)異常)。
- @AfterReturning :后置通知:在某連接點(diǎn)正常完成后執(zhí)行的通知,通常在一個(gè)匹配的方法返回的時(shí)候執(zhí)行。
6.了解完以上步驟
需要做的是記錄用戶正常請求,以及異常請求需要記錄的信息,對應(yīng)一下實(shí)體操作
@Data @EqualsAndHashCode(callSuper = false) @Accessors(chain = true) @ApiModel(value="SysOperaLog對象", description="") public class SysOperaLog implements Serializable { private static final long serialVersionUID = 1L; @TableId(value = "log_id", type = IdType.AUTO) private Integer logId; @ApiModelProperty(value = "操作類型") private String type; @ApiModelProperty(value = "訪問資源路徑") private String url; @ApiModelProperty(value = "操作人員") private String operaUser; @ApiModelProperty(value = "方法名稱") private String methodName; @ApiModelProperty(value = "訪問攜帶參數(shù)") private String params; @ApiModelProperty(value = "遠(yuǎn)程ip地址") private String ipAddress; @ApiModelProperty(value = "訪問時(shí)間") @TableField("operaTime") @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss") private LocalDateTime operaTime; @ApiModelProperty(value = "訪問總時(shí)長單位毫秒") @TableField("timeLong") private Long timeLong; @ApiModelProperty(value = "描述") private String des; @ApiModelProperty(value = "訪問狀態(tài) 0 訪問成功 -1 訪問失敗") private Integer visitState; @ApiModelProperty(value = "異常信息") private String exceptionDetail; }
7.用戶正常請求
在用戶正常請求頭部加上自定義的注解
@PostMapping("/save") @SystemOperaLog(descrption = "新增車位信息") public Result saveParkLot(@ApiParam(name = "parkLotsDTO", value = "車位信息") @RequestBody ParkLotsDTO parkLotsDTO, BindingResult bindingResult) { // 現(xiàn)在表示執(zhí)行的驗(yàn)證出現(xiàn)錯誤 if (bindingResult.hasErrors()) { // 獲取全部錯誤信息 List<ObjectError> allErrors = bindingResult.getAllErrors(); String errorMsg = ""; if (!CollectionUtils.isEmpty(allErrors)) { errorMsg = allErrors.get(0).getDefaultMessage(); } return ResultSupport.fail(errorMsg); } else { parkLotsService.save(parkLotsDTO); return ResultSupport.saveSuccess(); } }
8.在AOP中攔截進(jìn)入該方法的前置操作以及異常操作
@Aspect @Configuration @Slf4j public class SystemOperaLogAop { @Autowired private SysOperaLogMapper sysOperaLogMapper; /*** * 定義controller切入點(diǎn)攔截規(guī)則,攔截SystemControllerLog注解的方法 */ @Pointcut("@annotation(cn.hayll.parking.local.common.annotation.SystemOperaLog)") public void controllerAspect(){} /*** * 攔截控制層的操作日志 * @param joinPoint * @return * @throws Throwable */ @Around("controllerAspect()") public Object recordLog(ProceedingJoinPoint joinPoint) throws Throwable { //1、開始時(shí)間 long beginTime = System.currentTimeMillis(); //利用RequestContextHolder獲取requst對象 ServletRequestAttributes requestAttr = (ServletRequestAttributes)RequestContextHolder.currentRequestAttributes(); String uri = requestAttr.getRequest().getServletPath(); //訪問目標(biāo)方法的參數(shù) 可動態(tài)改變參數(shù)值 Object[] args = joinPoint.getArgs(); //方法名獲取 String methodName = joinPoint.getSignature().getName(); //可能在反向代理請求進(jìn)來時(shí),獲取的IP存在不正確行 這里直接摘抄一段來自網(wǎng)上獲取ip的代碼 Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); SysUserDetails sysUserDetails = (SysUserDetails) authentication.getPrincipal(); Signature signature = joinPoint.getSignature(); if(!(signature instanceof MethodSignature)) { throw new IllegalArgumentException("暫不支持非方法注解"); } //調(diào)用實(shí)際方法 Object object = joinPoint.proceed(); //獲取執(zhí)行的方法 MethodSignature methodSign = (MethodSignature) signature; Method method = methodSign.getMethod(); //判斷是否包含了 無需記錄日志的方法 long endTime = System.currentTimeMillis(); //模擬異常 //System.out.println(1/0); SysOperaLog systemLogDTO = new SysOperaLog(); systemLogDTO.setType(getAnnontationMethodDescription(joinPoint,1)); systemLogDTO.setUrl(uri); systemLogDTO.setOperaUser(sysUserDetails.getUsername()); systemLogDTO.setMethodName(methodName); systemLogDTO.setIpAddress(getIpAddr(requestAttr.getRequest())); List list = CollectionUtils.arrayToList(args); List arrayList = new ArrayList(list); //移除操作需要將數(shù)組轉(zhuǎn)換的集合類型在此轉(zhuǎn)換為集合類型 Iterator iterator = arrayList.iterator(); while (iterator.hasNext()){ if(iterator.next().toString().contains("BeanPropertyBindingResult")){ iterator.remove(); break; } } systemLogDTO.setParams(arrayList.toString()); systemLogDTO.setOperaTime(LocalDateTime.now()); systemLogDTO.setDes(getAnnontationMethodDescription(joinPoint,0)); systemLogDTO.setTimeLong(endTime - beginTime); sysOperaLogMapper.insert(systemLogDTO); return object; } //異常處理 @AfterThrowing(pointcut = "controllerAspect()",throwing="e") public void doAfterThrowing(JoinPoint joinPoint, Throwable e) throws Throwable{ //1、開始時(shí)間 long beginTime = System.currentTimeMillis(); //利用RequestContextHolder獲取requst對象 ServletRequestAttributes requestAttr = (ServletRequestAttributes)RequestContextHolder.currentRequestAttributes(); String uri = requestAttr.getRequest().getServletPath(); //訪問目標(biāo)方法的參數(shù) 可動態(tài)改變參數(shù)值 Object[] args = joinPoint.getArgs(); //方法名獲取 String methodName = joinPoint.getSignature().getName(); //可能在反向代理請求進(jìn)來時(shí),獲取的IP存在不正確行 這里直接摘抄一段來自網(wǎng)上獲取ip的代碼 Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); SysUserDetails sysUserDetails = (SysUserDetails) authentication.getPrincipal(); Signature signature = joinPoint.getSignature(); if(!(signature instanceof MethodSignature)) { throw new IllegalArgumentException("暫不支持非方法注解"); } //獲取執(zhí)行的方法 MethodSignature methodSign = (MethodSignature) signature; Method method = methodSign.getMethod(); //判斷是否包含了 無需記錄日志的方法 long endTime = System.currentTimeMillis(); //模擬異常 SysOperaLog systemLogDTO = new SysOperaLog(); systemLogDTO.setUrl(uri); systemLogDTO.setType(getAnnontationMethodDescription(joinPoint,1)); systemLogDTO.setOperaUser(sysUserDetails.getUsername()); systemLogDTO.setMethodName(methodName); systemLogDTO.setIpAddress(getIpAddr(requestAttr.getRequest())); List list = CollectionUtils.arrayToList(args); List arrayList = new ArrayList(list); //移除操作需要將數(shù)組轉(zhuǎn)換的集合類型在此轉(zhuǎn)換為集合類型 Iterator iterator = arrayList.iterator(); while (iterator.hasNext()){ if(iterator.next().toString().contains("BeanPropertyBindingResult")){ iterator.remove(); break; } } systemLogDTO.setParams(arrayList.toString()); systemLogDTO.setOperaTime(LocalDateTime.now()); systemLogDTO.setTimeLong(endTime - beginTime); systemLogDTO.setDes(getAnnontationMethodDescription(joinPoint,0)); systemLogDTO.setVisitState(-1); systemLogDTO.setExceptionDetail(e.getMessage()); sysOperaLogMapper.insert(systemLogDTO); } public static String getIpAddr(HttpServletRequest request) { String ipAddress = null; try { ipAddress = request.getHeader("x-forwarded-for"); if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) { ipAddress = request.getHeader("Proxy-Client-IP"); } if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) { ipAddress = request.getHeader("WL-Proxy-Client-IP"); } if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) { ipAddress = request.getRemoteAddr(); if (ipAddress.equals("127.0.0.1")) { // 根據(jù)網(wǎng)卡取本機(jī)配置的IP InetAddress inet = null; try { inet = InetAddress.getLocalHost(); } catch (UnknownHostException e) { log.error("獲取ip異常:{}" ,e.getMessage()); e.printStackTrace(); } ipAddress = inet.getHostAddress(); } } // 對于通過多個(gè)代理的情況,第一個(gè)IP為客戶端真實(shí)IP,多個(gè)IP按照','分割 if (ipAddress != null && ipAddress.length() > 15) { // = 15 if (ipAddress.indexOf(",") > 0) { ipAddress = ipAddress.substring(0, ipAddress.indexOf(",")); } } } catch (Exception e) { ipAddress = ""; } return ipAddress; } /*** * 獲取controller的操作信息 * @param point * @return */ public String getAnnontationMethodDescription(JoinPoint point,Integer type) throws Exception{ //獲取連接點(diǎn)目標(biāo)類名 String targetName = point.getTarget().getClass().getName() ; //獲取連接點(diǎn)簽名的方法名 String methodName = point.getSignature().getName() ; //獲取連接點(diǎn)參數(shù) Object[] args = point.getArgs() ; //根據(jù)連接點(diǎn)類的名字獲取指定類 Class targetClass = Class.forName(targetName); //獲取類里面的方法 Method[] methods = targetClass.getMethods() ; String description="" ; for (Method method : methods) { if (method.getName().equals(methodName)){ Class[] clazzs = method.getParameterTypes(); if (clazzs.length == args.length){ if(type==0){ description = method.getAnnotation(SystemOperaLog.class).descrption(); break; }else{ description = method.getAnnotation(SystemOperaLog.class).actionType(); break; } } } } return description ; }
9.說一下踩的一個(gè)坑
現(xiàn)在日志已經(jīng)可以正常工作了,但是業(yè)務(wù)代碼卻失效了,service注入的時(shí)候是空的(部分代碼),
一頓百度以后發(fā)現(xiàn),原來AOP只能對public 和provide 生效,如果你的方法限制是private,那么service注入就為空,在springboot 中默認(rèn)使用的是cglib來代理操作對象,首先,私有方法是不會出現(xiàn)在代理類中,這也就是為什么代理對象無法對private操作的根本原因
- jdk是代理接口,私有方法必然不會存在在接口里,所以就不會被攔截到;
- cglib是子類,private的方法照樣不會出現(xiàn)在子類里,也不能被攔截。
10.解決的根本辦法
不是強(qiáng)制使用cglib來代理,而是要將你的controller中的方法不設(shè)置私有屬性,以上僅僅代表個(gè)人觀點(diǎn)喲。
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Java利用Redis實(shí)現(xiàn)高并發(fā)計(jì)數(shù)器的示例代碼
這篇文章主要介紹了Java利用Redis實(shí)現(xiàn)高并發(fā)計(jì)數(shù)器的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-02-02Springboot整合quartz實(shí)現(xiàn)多個(gè)定時(shí)任務(wù)實(shí)例
這篇文章主要介紹了Springboot整合quartz實(shí)現(xiàn)多個(gè)定時(shí)任務(wù)代碼實(shí)例,Quartz?是一款功能強(qiáng)大的開源任務(wù)調(diào)度框架,幾乎可以集成到任何?Java?應(yīng)用程序中,Quartz?可用于創(chuàng)建簡單或復(fù)雜的任務(wù)調(diào)度,用以執(zhí)行數(shù)以萬計(jì)的任務(wù),需要的朋友可以參考下2023-08-08Java中基于Shiro,JWT實(shí)現(xiàn)微信小程序登錄完整例子及實(shí)現(xiàn)過程
這篇文章主要介紹了Java中基于Shiro,JWT實(shí)現(xiàn)微信小程序登錄完整例子 ,實(shí)現(xiàn)了小程序的自定義登陸,將自定義登陸態(tài)token返回給小程序作為登陸憑證。需要的朋友可以參考下2018-11-11Spring Boot 排除某個(gè)類加載注入IOC的操作
這篇文章主要介紹了Spring Boot 排除某個(gè)類加載注入IOC的操作,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-08-08