如何自定义一个注解?

想要自定义一个注解,就必须了解5大元注解
@Retention
@Target
@Documented
@Inherited(JDK8 引入)
@Repeatable(JDK8 引入)

一.元注解

@Retention 指定注解的生命周期

@Retention(RetentionPolicy.SOURCE)

其中Retention是一个枚举类
RetentionPolicy.SOURCE : 注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃(.java文件)
RetentionPolicy.CLASS :注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期(.class文件)
RetentionPolicy.RUNTIME: 注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在(内存中的字节码)

@Target指定注解可以修饰的元素类型

@Target(ElementType.Field)

  • ElementType.ANNOTATION_TYPE - 标记的注解可以应用于注解类型。
  • ElementType.CONSTRUCTOR - 标记的注解可以应用于构造函数。
  • ElementType.FIELD - 标记的注解可以应用于字段或属性。
  • ElementType.LOCAL_VARIABLE - 标记的注解可以应用于局部变量。
  • ElementType.METHOD - 标记的注解可以应用于方法。
  • ElementType.PACKAGE - 标记的注解可以应用于包声明。
  • ElementType.PARAMETER - 标记的注解可以应用于方法的参数。
  • ElementType.TYPE - 标记的注解可以应用于类的任何元素。

@Documented

指定注解会被JavaDoc工具提取成文档。默认情况下,JavaDoc是不包括文档的

@Inherited

表示该注解会被子类继承,注意,仅针对类,成员属性、方法并不受此注释的影响。

@Repeatable

表示注解可以重复使用,为了解决同一个注解不能重复在同一类/方法/属性上使用的问题。

二.自定义注解

格式

public @interface 注解名称 {
[访问级别修饰符] [数据类型] 属性名() default 默认值;
}

自定义注解使用方法

既然指定了自定义注解,那我们怎么使用自定义注解呢?

  • 运行时处理注解:使用反射
  • 编译时期处理注解:使用注解处理器AbstractProcessor处理注解

1)接口限流注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AccessLimit {
    /**
     * 限制周期(秒)
     */
    int seconds();

    /**
     * 规定周期内限制次数
     */
    int maxCount();

    /**
     * 触发限制时的消息提示
     */
    String msg() default "操作频率过高";

}

@AccessLimit一般用在controller的方法上,就是防止同一个用户多次访问同一个接口。

该注解主要用于拦截相应的请求到达controller,用于限制同一用户限制多次访问同一个接口。
这里我们使用拦截器,在拦截器的preHandler中进行请求的拦截。
拦截器定义

定义一个拦截器需要实现HandlerInterceptor接口

  • preHandle :请求到达controller层前执行
  • postHandle :当controller方法执行完后执行
  • afterCompletion: 整个方法执行完后执行
@Component
public class AccessLimitInterceptor  implements HandlerInterceptor  {
    @Autowired
    RedisService redisService;


    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (handler instanceof HandlerMethod) {
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            AccessLimit accessLimit = handlerMethod.getMethodAnnotation(AccessLimit.class);
            //方法上没有访问控制的注解,直接通过
            if (accessLimit == null) {
                return true;
            }
            int seconds = accessLimit.seconds();
            int maxCount = accessLimit.maxCount();
            String ip = request.getHeader("x-forwarded-for");;  //获得ip地址
            String method = request.getMethod();    //获得方法
            String requestURI = request.getRequestURI(); //获得请求路径
            String redisKey = ip + ":" + method + ":" + requestURI; //根据前文信息获得一个唯一key
            Integer count = (Integer) redisService.getObjectByValue(redisKey);

            if (count == null) {
                //在规定周期内第一次访问,存入redis
                redisService.saveObjectToValue(redisKey, 1);
                redisService.expire(redisKey, seconds);
            } else {
                if (count >= maxCount) {
                    //超出访问限制次数
                    response.setContentType("application/json;charset=utf-8");
                    PrintWriter out = response.getWriter();
                    Result result = Result.fail(403, accessLimit.msg(),null);
                    out.write(String.valueOf(new JSONObject(result)));
                    out.flush();
                    out.close();
                    return false;
                } else {
                    //没超出访问限制次数
                    redisService.incrementByKey(redisKey, 1);
                }
            }
        }
        return true;
    }
}

记得要将拦截器注册到WebMvcConfiguration接口

 @Override
    public void addInterceptors(InterceptorRegistry registry) {

        registry.addInterceptor(accessLimitInterceptor).addPathPatterns("/**");
 
    }

添加拦截路径,对所有的路径进行拦截。

2) 用户操作日志注解

该注解主要用来记录用户来到controller层后的做了什么操作,也记录了用户的相关信息

面向切面和拦截器不同,拦截器主要是在请求进入controller前进行相关的过滤等操作,而日志的记录业界上最适合使用是使用面向切面来记录日志

//注解用于方法
@Target(ElementType.METHOD)
//运行时使用
@Retention(RetentionPolicy.RUNTIME)
public @interface VisitLogger {
    String behavior() default "" ;
    String content() default "";

}

用户日志实体类

@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@AllArgsConstructor
@NoArgsConstructor
public class VisitLog implements Serializable {

    private static final long serialVersionUID = 1L;

    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    private String uuid;  //访客标识码
    private String uri;    //请求接口
    private String method;  //请求方式
    private String param;   //请求参数
    private String behavior;  //访问行为
    private String content;  //访问内容
    private String remark;  //备注
    private String ip;  //ip
    private String ipSource; //ip来源
    private String os;  //操作系统
    private String browser;  //浏览器
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime createTime; //访问时间
    private Integer times;  //访问行为
    private String userAgent;  //user-agent用户代理
    
}

在这里插入图片描述
比如说上面,当需要进行日志记录时,我们需要在对应的controller方法中使用该注解,并在该注解上记录我们需要记录的操作

那么具体的面向切面逻辑该怎么记录呢?



/**
 * AOP记录访问日志
 *
 */
@Component
@Aspect
public class VisitLogAspect {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    @Autowired
    VisitLogService visitLogService;
    @Autowired
    VisitorService visitorService;
    @Autowired
    UserAgentUtils userAgentUtils;
    @Autowired
    RedisService redisService;


    ThreadLocal<Long> currentTime = new ThreadLocal<>();


    /**
     * 配置切入点
     */
    @Pointcut("@annotation(visitLogger)")
    public void log(VisitLogger visitLogger){}

    /**
     * 配置环绕通知
     *
     * @param joinPoint 连接点
	 * @return 返回方法执行后的结果
	 */
    @Around("log(visitLogger)")
    public Object logAround(ProceedingJoinPoint joinPoint,VisitLogger visitLogger) throws Throwable {

        currentTime.set(System.currentTimeMillis());
        //获取请求对象
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        //让目标方法执行 获取返回的结果
        Object result = joinPoint.proceed();
        int times = (int) (System.currentTimeMillis() - currentTime.get());
        currentTime.remove();
        //校验访客标识码
        String identification = checkIdentification(request);
        //异步保存至数据库
        saveVisitLog(joinPoint, visitLogger, request, result, times, identification);

        return result;
    }



    void saveVisitLog(){

    }



    /**
     * 异步设置VisitLogger对象属性并保存到数据库中
     *
     * @param joinPoint
     * @param visitLogger
     * @param result
     * @param times
     * @return
     */
    @Async
    void saveVisitLog(ProceedingJoinPoint joinPoint, VisitLogger visitLogger, HttpServletRequest request, Object result,
                               int times, String identification) {
        String uri = request.getRequestURI();
        String method = request.getMethod();
        String behavior = visitLogger.behavior();
        String content = visitLogger.content();
        String ip = request.getHeader("x-forwarded-for");


        String ipSource = IpAddressUtils.getCityInfo(ip);
        String userAgent = request.getHeader("User-Agent");
        Map<String, String> userAgentMap = userAgentUtils.parseOsAndBrowser(userAgent);
        String os = userAgentMap.get("os");
        String browser = userAgentMap.get("browser");



        //获取参数名和参数值
        Map<String, Object> requestParams = new LinkedHashMap<>();
        String[] parameterNames = ((MethodSignature) joinPoint.getSignature()).getParameterNames();
        Object[] args = joinPoint.getArgs();
        for (int i = 0; i < args.length; i++) {
            if( args[i] instanceof HttpServletRequest || args[i] instanceof HttpServletResponse || args[i] instanceof MultipartFile){
                continue;
            }
            requestParams.put(parameterNames[i], args[i]);
        }
        //根据访问内容和返回的结果判断访问的内容并进行备注
        Map<String, String> map = judgeBehavior(behavior, content, requestParams, result);
        VisitLog log = new VisitLog(null,identification, uri, method, new JSONObject(requestParams).toString(), behavior, map.get("content"),map.get("remark"), ip,ipSource,os,browser,LocalDateTime.now(),times, userAgent);

        visitLogService.saveOrUpdate(log);
    }



    /**
     * 根据访问行为,设置对应的访问内容或备注
     *
     * @param behavior
     * @param content
     * @param requestParams
     * @param result
     * @return 返回内容和备注为主键的map
     */
    private Map<String, String> judgeBehavior(String behavior, String content, Map<String, Object> requestParams, Object result) {
        Map<String, String> map = new HashMap<>();
        String remark = "";
        if (behavior.equals("访问页面") && (content.equals("首页"))) {
            int pageNum = (int) requestParams.get("currentPage");
            remark = "第" + pageNum + "页";
        } else if (behavior.equals("查看博客")) {
            Result res = (Result) result;
            if (res.getCode() == 200) {
                Blog blog = (Blog) res.getData();
                String title = blog.getTitle();
                content = title;
                remark = "文章标题:" + title;
            }
        } else if (behavior.equals("搜索博客")) {
            Result res = (Result) result;
            if (res.getCode() == 200) {
                String query = (String) requestParams.get("queryString");
                content = query;
                remark = "搜索内容:" + query;
            }
        } else if (behavior.equals("查看分类")) {
            String categoryName = (String) requestParams.get("typeName");
            int pageNum = (int) requestParams.get("currentPage");
            content = categoryName;
            remark = "分类名称:" + categoryName + ",第" + pageNum + "页";
        } else if (behavior.equals("点击友链")) {
            String nickname = (String) requestParams.get("nickname");
            content = nickname;
            remark = "友链名称:" + nickname;
        }
        map.put("remark", remark);
        map.put("content", content);
        return map;
    }




    /**
     * 校验访客标识码
     *
     * @param request
     * @return 访客标识码UUID
     */
    private String checkIdentification(HttpServletRequest request) {
        String identification = request.getHeader("identification");
        if (identification == null) {
            //第一次访问,签发uuid并保存到数据库和Redis
            identification =   UUID.randomUUID().toString();
            saveUUID(identification,request);
        } else {
            //校验Redis中是否存在uuid
            boolean redisHas = redisService.hasValueInSet(RedisKeyConfig.IDENTIFICATION_SET, identification);

            //Redis中不存在uuid
            if (!redisHas) {
                //校验数据库中是否存在uuid
                boolean mysqlHas = visitorService.hasUUID(identification);
                if (mysqlHas) {
                    //数据库存在,保存至Redis
                    redisService.saveValueToSet("identificationSet", identification);
                    //更新最后访问时间和pv
                    updateVistor(identification);

                } else {
                    //数据库不存在,签发新的uuid
                    identification =   UUID.randomUUID().toString();
                    //异步保存
                    saveUUID(identification,request);
                }
            }
            else{
                //更新最后时间和pv
                updateVistor(identification);
            }
        }
        return identification;
    }





    @Async
    void updateVistor(String identification) {
        //更新最后访问时间和pv
        Visitor visitor = visitorService.getVisitorByUuid(identification);
        visitor.setPv(visitor.getPv()+1);
        visitor.setLastTime(LocalDateTime.now());
        visitorService.saveOrUpdate(visitor);
    }


    /**
     * 异步保存UUID至数据库和Redis
     *
     * @param request
     * @return UUID
     */
    @Async
    void saveUUID(String uuid,HttpServletRequest request) {

        //保存至Redis
        redisService.saveValueToSet(RedisKeyConfig.IDENTIFICATION_SET, uuid);
        //获取响应对象
        HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();

        //添加访客标识码UUID至响应头
        response.addHeader("identification", uuid);
        //暴露自定义header供页面资源使用
        response.addHeader("Access-Control-Expose-Headers", "identification");

        //获取访问者基本信息
        String ip = request.getHeader("x-forwarded-for");

        String userAgent = request.getHeader("User-Agent");
        String ipSource = IpAddressUtils.getCityInfo(ip);
        Map<String, String> userAgentMap = userAgentUtils.parseOsAndBrowser(userAgent);
        String os = userAgentMap.get("os");
        String browser = userAgentMap.get("browser");
        Visitor visitor = new Visitor(null,uuid, ip,ipSource,os,browser,LocalDateTime.now(),LocalDateTime.now(),1, userAgent);
        //保存至数据库
        visitorService.saveOrUpdate(visitor);
    }
}

  • 8
    点赞
  • 48
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值