自定义注解+aop实现-快速上手

本文不介绍spring aop的基础知识,只演示实现一个自定义注解的流程,但是读者应该对注解的基本概念,切面、切点、通知等的基本概念有所了解。

项目基于Spring boot+maven+java8

项目需求

我想要在某些controller方法中记录请求日志,包括ip,方法,请求入参,返回结果,执行耗时等等。

那么我们可以通过自定义一个注解实现,加在需要打印日志的方法上即可。

各个公司内部的实现逻辑可能有些差别,但大同小异。

项目结构

首先展示一下项目的基本结构:

自定义注解+aop实现-快速上手_第1张图片

项目的pom文件:



	4.0.0
	
		org.springframework.boot
		spring-boot-starter-parent
		2.5.3
		 
	
	com.ztt
	aop_demo
	0.0.1-SNAPSHOT
	aop_demo
	Demo project for Spring Boot
	
		1.8
	
	
		
			org.springframework.boot
			spring-boot-starter
		

		
			org.springframework.boot
			spring-boot-starter-web
		

		
			org.springframework.boot
			spring-boot-starter-aop
		

		
			org.projectlombok
			lombok
		

		
			com.alibaba
			fastjson
			1.2.68
		

		
			org.springframework.boot
			spring-boot-starter-test
			test
		
	

	
		
			
				org.springframework.boot
				spring-boot-maven-plugin
			
		
	


第一步:自定义一个注解 

首先,我们需要自定义一个注解,代码如下。 

import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface EagleEye {
    // 注解的属性。属性不是必须的,可以没有,根据公司的实际业务需要去定义
//    String value();
//    String name() default "";

}

@Retention(RetentionPolicy.RUNTIME) :

这种类型的Annotations将被JVM保留,所以他们能在运行时被JVM或其他使用反射机制的代码所读取和使用。

@Target

定义注解的作用目标。@Target(ElementType.METHOD)表示该注解可以作用于方法上。

@Documented

 用来指定自定义注解是否能随着被定义的java文件生成到JavaDoc文档当中。

这个时候,注解就定义完成了,可以加在方法上使用了:

import com.alibaba.fastjson.JSONObject;
import com.ztt.annotation.EagleEye;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Slf4j
@RestController
@RequestMapping(value = "/hello")
public class HelloController {

    @GetMapping(value = "/testEagleEye")
    // 使用自定义注解
    @EagleEye
    public Object testEagleEye(@RequestBody JSONObject params) {
        log.info("params:{}", params);

        return "success";
    }

}

然后浏览器或者postman调用这个方法,会发现ip,返回结果,执行耗时什么的根本没有打印出来。

那肯定是不会打印的,因为我们只是定义了一个注解,并没有任何的代码去解析这个注解(所谓解析,就是说在编译期或运行时检测到注解,并进行特殊操作)。因此,单单定义一个注解是没有任何卵用的,我们必须去实现aop做相关操作才有意义。

那么怎么去解析这个注解呢?当然是需要我们写aop代码了。

第二步 aop实现

上一步我们自定义了一个注解@EagleEye ,并把它加到了controller的方法testEagleEye上,但是发现这个注解并没有啥卵用。

只有实现了aop,注解才能真正的起作用。

这一步,我们就去写代码实现aop,解析这个注解,代码如下:

import com.ztt.annotation.EagleEye;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;

@Aspect
@Component
@Slf4j
public class EagleEyeAop {

    /**
     * 切点表达式。此处的切点表达式会,匹配所有@EagleEye注解修饰的方法
     */
    @Pointcut("@annotation(com.ztt.annotation.EagleEye)")
    public void eagleEye() {

    }


    /**
     * 环绕通知
     * @param joinPoint
     * @return
     * @throws Throwable
     */
    @Around("eagleEye()")
    public Object eagleEyeAround(ProceedingJoinPoint joinPoint) throws Throwable {
        long startTimeMillis = System.currentTimeMillis();
        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest httpServletRequest = servletRequestAttributes.getRequest();

        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();
        EagleEye eagleEyeAnnotation = method.getAnnotation(EagleEye.class);


        log.info(">>>>>> request start >>>>>>");
        log.info("请求链接:{}", httpServletRequest.getRequestURL().toString());
        log.info("请求类型:{}", httpServletRequest.getMethod());
        log.info("请求方法:{}", signature.getDeclaringTypeName(), signature.getName());
        log.info("请求ip:{}", httpServletRequest.getRemoteAddr());
        log.info("请求入参:{}", joinPoint.getArgs());

        Object proceedResult = null;
        try {
            // 执行目标方法
            proceedResult = joinPoint.proceed();
        } catch (Exception e) {
            log.error("目标方法执行异常:", e);
            throw e;
        }

        //
        long endTimeMillis = System.currentTimeMillis();
        log.info("请求耗时:{}ms", endTimeMillis - startTimeMillis);
        //
        log.info("目标方法执行结果:{}", proceedResult);
        log.info(">>>>>> request end >>>>>>");


        return proceedResult;

    }

}

第三不 验证aop

实现aop之后,我们再去调用上面的controller方法,可以看到控制台打印了日志:

自定义注解+aop实现-快速上手_第2张图片

你可能感兴趣的:(Java基础,aop,自定义注解)