欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

SpringBoot AOP導(dǎo)致service注入后是null的問題

 更新時(shí)間:2024年10月31日 14:14:01   作者:Xiao_zuo_ya  
本文主要講述了如何利用SpringAOP實(shí)現(xiàn)用戶操作日志的記錄,首先,引入SpringBoot的AOP依賴,然后,選擇基于注解的形式來實(shí)現(xiàn)日志操作,以避免污染原有代碼和邏輯,在理解了SpringBootAOP的一些注解后,需要記錄用戶的正常請求以及異常請求的信息

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)文章

  • springboot使用注解獲取yml配置的兩種方法

    springboot使用注解獲取yml配置的兩種方法

    本文主要介紹了springboot使用注解獲取yml配置的兩種方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-09-09
  • Java中return的用法(兩種)

    Java中return的用法(兩種)

    這篇文章主要介紹了Java中return的用法(兩種)的相關(guān)資料,需要的朋友可以參考下
    2016-01-01
  • Java中的main函數(shù)的詳細(xì)介紹

    Java中的main函數(shù)的詳細(xì)介紹

    這篇文章主要介紹了Java中的main函數(shù)的詳細(xì)介紹的相關(guān)資料,main()函數(shù)在java程序中必出現(xiàn)的函數(shù),這里就講解下使用方法,需要的朋友可以參考下
    2017-09-09
  • Java利用Redis實(shí)現(xiàn)高并發(fā)計(jì)數(shù)器的示例代碼

    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-02
  • java實(shí)現(xiàn)時(shí)鐘效果

    java實(shí)現(xiàn)時(shí)鐘效果

    這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)時(shí)鐘效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2020-03-03
  • Java8?Stream?流常用方法合集

    Java8?Stream?流常用方法合集

    這篇文章主要介紹了?Java8?Stream?流常用方法合集,Stream?是?Java8?中處理集合的關(guān)鍵抽象概念,它可以指定你希望對集合進(jìn)行的操作,可以執(zhí)行非常復(fù)雜的查找、過濾和映射數(shù)據(jù)等操作,下文相關(guān)資料,需要的朋友可以參考一下
    2022-04-04
  • Springboot整合quartz實(shí)現(xiàn)多個(gè)定時(shí)任務(wù)實(shí)例

    Springboot整合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-08
  • win10 64位 jdk1.8的方法教程詳解

    win10 64位 jdk1.8的方法教程詳解

    這篇文章主要介紹了win10 64位 jdk1.8的方法教程詳解,本文給大家介紹的非常詳細(xì),對大家的工作或?qū)W習(xí)具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-03-03
  • Java中基于Shiro,JWT實(shí)現(xiàn)微信小程序登錄完整例子及實(shí)現(xiàn)過程

    Java中基于Shiro,JWT實(shí)現(xiàn)微信小程序登錄完整例子及實(shí)現(xiàn)過程

    這篇文章主要介紹了Java中基于Shiro,JWT實(shí)現(xiàn)微信小程序登錄完整例子 ,實(shí)現(xiàn)了小程序的自定義登陸,將自定義登陸態(tài)token返回給小程序作為登陸憑證。需要的朋友可以參考下
    2018-11-11
  • Spring Boot 排除某個(gè)類加載注入IOC的操作

    Spring Boot 排除某個(gè)類加載注入IOC的操作

    這篇文章主要介紹了Spring Boot 排除某個(gè)類加載注入IOC的操作,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-08-08

最新評論