天天看點

Springboot 自定義注解+AOP簡單執行個體介紹

前言:

該篇以記錄接口調用的傳入參數日志為場景,來介紹下使用自定義注解作為切點,AOP切面方式去記錄每個接口的傳入參數以及可擴充的業務處理。

正文:

項目目錄:

Springboot 自定義注解+AOP簡單執行個體介紹

先是建立自定義注解, LogTrack:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @Author : JCccc
 * @CreateTime : 2020/4/13
 * @Description :
 **/

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface LogTrack {
    String value() default "logTracking";
}      

上面的自定義注解,我隻用了一個預設參,是以不用命名,寫value就可以。

然後寫切點對應的代碼,LogTrackAspect:

用到了fastjson,導入依賴:

<!--fastjson-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.58</version>
        </dependency>      
/**
 * @Author : JCccc
 * @CreateTime : 2020/4/13
 * @Description :
 **/

@Component
@Aspect
public class LogTrackAspect  {
    private static final Logger log = LoggerFactory.getLogger(LogTrackAspect.class);



    //這裡需要注意了,這個是将自己自定義注解作為切點的根據,路徑一定要寫正确了
    @Pointcut(value = "@annotation(com.jc.mytest.aop.logRecord.LogTrack)")
    public void access() {

    }

    //進來切點世界,先經過的第一個站
    @Before("access()")
    public void doBefore(JoinPoint joinPoint) throws Throwable {


        System.out.println("-aop 日志記錄啟動-" + new Date());


    }


    //環繞增強,是在before前就會觸發
    @Around("@annotation(logTrack)")
    public Object around(ProceedingJoinPoint pjp, LogTrack logTrack) throws Throwable {
        System.out.println("-aop 日志環繞階段-" + new Date());
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();

//        GET 請求其實可以從request裡擷取出參數
//       Map<String,String[]> map=request.getParameterMap();
//        System.out.println("擷取參數:"+map.get("username")[0])

        String url = request.getRequestURL().toString();
        String ip = IpUtil.getIpAddr(request);
        String logTrackValue = logTrack.value();
        Object[] pipArrary = pjp.getArgs();
        if (pipArrary.length>1){ //多參,不是Map/JsonObject方式
            List<Object> argList = new ArrayList<>();
            for (Object arg : pjp.getArgs()) {
          // request/response無法使用toJSON
            if (arg instanceof HttpServletRequest) {
                       argList.add("request");
                 } else if (arg instanceof HttpServletResponse) {
                  argList.add("response");
                 } else {
                    argList.add(JSON.toJSON(arg));
            }
            }
         Signature signature = pjp.getSignature();
         MethodSignature methodSignature = (MethodSignature) signature;
        // 參數名數組
        String[] parameterNames = ((MethodSignature) signature).getParameterNames();
            System.out.println("參數名數組:"+new ArrayList(Arrays.asList(parameterNames)));
            System.out.println("參數是:"+argList.toString());
            System.out.println("logTrackValue:"+logTrackValue);
            System.out.println("url:"+url);
            System.out.println("ip:"+ip);
            return pjp.proceed();
            
        }

        Object param =  pipArrary[0];
        System.out.println("logTrackValue:"+logTrackValue);
        System.out.println("url:"+url);
        System.out.println("ip:"+ip);
        System.out.println("param:"+param.toString());
        return pjp.proceed();

    }


    //進來切點這,最後經過的一個站,也是方法正常運作結束後
    @After("access()")
    public void after(JoinPoint joinPoint) {
        System.out.println("-aop 日志記錄結束-" + new Date());
    }

}      

ps:return pjp.proceed(); 這個是從切點的環繞增強裡面脫離出來,接下來會進入before階段 ,然後回到接口,再回來after階段。

是以擴充業務邏輯處理的話,可以放在return pjp.proceed();這行代碼之前,例如判斷使用者密碼是否正确;判斷使用者權限等等。

接下來是在Controller編寫接口,并用上自定義注解,MyTestController:

/**
 * @Author : JCccc
 * @CreateTime : 2020/3/27
 * @Description :
 **/

@Controller
@RequestMapping("/test")
public class MyTestController {

    @ResponseBody
    @GetMapping("/testGet1")
    @LogTrack("testGet1 接口")
    public void testGet1(@RequestParam("userId") String userId, @RequestParam("toUserId") String toUserId) {

        System.out.println("已經進入GET測試接口,參數userId:" + userId+  "參數toUserId:"+toUserId);
    }


    @ResponseBody
    @GetMapping("/testGet2")
    @LogTrack("testGet2 接口")
    public void testGet2(@RequestParam Map map) {

        System.out.println("已經進入GET測試接口,參數:" + map.get("userId"));
    }

    @ResponseBody
    @PostMapping("/testPost1")
    @LogTrack("testPost1 接口")
    public void testPost1(@RequestBody Map map) {

        System.out.println("已經進入POST測試接口,參數:" + map.toString());
    }

    @ResponseBody
    @PostMapping("/testPost2")
    @LogTrack("testPost2 接口")
    public void testPost2(@RequestBody JSONObject jsonObject) {

        System.out.println("已經進入POST測試接口,參數:" + jsonObject.toString());
    }

}      

然後我們來運作一下,看看控制台,就知道整個切點以及環繞的流程了: 

首先是測試GET方式的接口,通過@RequestParam單個參數擷取的情況:

Springboot 自定義注解+AOP簡單執行個體介紹

調用接口:

Springboot 自定義注解+AOP簡單執行個體介紹

運作結果:

Springboot 自定義注解+AOP簡單執行個體介紹

接下來還是GET方式 ,通過Map去接收多參:

Springboot 自定義注解+AOP簡單執行個體介紹

調用接口:

Springboot 自定義注解+AOP簡單執行個體介紹

運作結果:

Springboot 自定義注解+AOP簡單執行個體介紹

然後是調用Post請求:

Springboot 自定義注解+AOP簡單執行個體介紹
Springboot 自定義注解+AOP簡單執行個體介紹

ps: 如果發現按照上面配置了,但是aop切點的方法好像沒觸發,

那麼可以試試

1.檢查jar包是否有導入正确

<dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.4</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>1.8.9</version>
        </dependency>      

2.在啟動類上加上注解@EnableAspectJAutoProxy  這個其實springboot是預設幫我們已經開啟為true狀态的。

本篇使用到的一些工具類:

IpUtil:

import javax.servlet.http.HttpServletRequest;
import java.net.InetAddress;
import java.net.UnknownHostException;

/**
 * @Author : JCccc
 * @CreateTime : 2018-11-23
 * @Description :
 * @Point: Keep a good mood
 **/
public class IpUtil {
    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")) {
                    // 根據網卡取本機配置的IP
                    InetAddress inet = null;
                    try {
                        inet = InetAddress.getLocalHost();
                    } catch (UnknownHostException e) {
                        e.printStackTrace();
                    }
                    ipAddress = inet.getHostAddress();
                }
            }
            // 對于通過多個代理的情況,第一個IP為用戶端真實IP,多個IP按照','分割
            if (ipAddress != null && ipAddress.length() > 15) { // "***.***.***.***".length()
                // = 15
                if (ipAddress.indexOf(",") > 0) {
                    ipAddress = ipAddress.substring(0, ipAddress.indexOf(","));
                }
            }
        } catch (Exception e) {
            ipAddress="";
        }
        // ipAddress = this.getRequest().getRemoteAddr();

        return ipAddress;
    }
}      

繼續閱讀