【JAVA基础】自定义注解

注解是一种能被添加到java源代码中的元数据,方法、类、参数和包都可以用注解来修饰。注解可以看作是一种特殊的标记,可以用在方法、类、参数和包上,程序在编译或者运行时可以检测到这些标记而进行一些特殊的处理。

1.基本使用

java.lang.annotation中提供了元注解,可以使用这些注解来定义自己的注解。

1.1 声明定义一个注解

(1)五要素:
修饰符:访问修饰符必须为public,不写默认为pubic;
关键字:关键字为@interface
注解名:注解名称为自定义注解的名称
注解类型元素:注解类型元素是注解中内容,可以理解成自定义接口的实现部分;
元注解:使用元注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface OAuth
{
    String loginUrl() default WapUrl.LOGINPAGE;
}

1.2 常用元注解

(1)Target
描述了注解修饰的对象范围,取值在java.lang.annotation.ElementType定义,常用的包括:
METHOD:用于描述方法
PACKAGE:用于描述包
PARAMETER:用于描述方法变量
TYPE:用于描述类、接口或enum类型
(2)Retention
表示注解保留时间长短。取值在java.lang.annotation.RetentionPolicy中,取值为:
SOURCE:在源文件中有效,编译过程中会被忽略
CLASS:随源文件一起编译在class文件中,运行时忽略
RUNTIME:在运行时有效
只有定义为RetentionPolicy.RUNTIME时,我们才能通过注解反射获取到注解

1.3 获取注解

可以通过反射获取注解,比如获取@ResponseBody注解

    // 判断是否是AJAX请求,AJAX请求一般使用@ResponseBody
    private boolean isJsonRequest(HandlerMethod handler)
    {
        ResponseBody responseBody = handler.getMethodAnnotation(ResponseBody.class);
        boolean isJsonRequest = false;
        if (responseBody != null)
        {
            isJsonRequest = true;
        }
        return isJsonRequest;
    }

2.应用场景

2.1 自定义注解+拦截器 实现登录校验

使用spring拦截器实现这样一个功能,如果方法上加了@OAuth,则表示用户该接口需要登录才能访问(没有登录直接跳转到登录页面),否则不需要登录。
(1)先定义一个OAuth注解,默认值是登录页面地址

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface OAuth
{
    String loginUrl() default WapUrl.LOGINPAGE;
}

(2)添加登录注解

接口方法添加我们的登录注解@OAuth,要求登录才能访问

    // 添加购物车商品
    @OAuth
    @ResponseBody
    @RequestMapping(value = WebURL.CART_ADD, method = RequestMethod.POST)
    public ResponseJSON actionCartAdd(@RequestBody @Valid CartAddModRequest cartAddModRequest, BindingResult bindingResult)
    {
    }

(3)实现登录拦截逻辑
如果当前用户没有登录,直接跳转到登录页面

public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    // 反射获取方法上的OAuth注解
    OAuth oAuth = handlerMethod.getMethodAnnotation(OAuth.class);
    // 登录用户信息
    OAuthUser oAuthUser = authService.getOAuth(sid);    
    if (oAuth != null) {
        // 不处于登录状态
        if (oAuthUser == null || oAuthUser.getCustId() == null) {
          // 判断是否是AJAX请求
          boolean isJsonRequest = isJsonRequest((HandlerMethod) handler);   
          // redirect到登录界面
          String baseUrl = Constants.BASE_SERVER;
          String returnUrl = baseUrl + request.getRequestURI() + (request.getQueryString() != null ?
              "?" + request.getQueryString() :
              "");
          response.setStatus(403);
          if (Strings.isNullOrEmpty(oAuth.loginUrl())) {
            response.sendRedirect(
                baseUrl + WapUrl.LOGINPAGE + "?returnUrl=" + URLEncoder.encode(returnUrl, "utf-8"));
          } else {
            response.sendRedirect(
                baseUrl + oAuth.loginUrl() + "?returnUrl=" + URLEncoder.encode(returnUrl, "utf-8"));
          }
          return false;
        } 
    }
    return true;
}

2.2 自定义注解+拦截器 实现限制访问

(1)先定义一个Follow注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Follow
{
    // 触屏版(域名配置)是否允许访问
    boolean isWapAccess() default false;
    // APP是否允许访问
    boolean isAppAccess() default false;
    // 只需要openId, 并不强制用户关注
    boolean onlyOpenId() default false;
}

(2)添加触屏版是否允许访问注解

@Follow(isWapAccess = false)
@RequestMapping(value = WapUrl.TODAY_SHOW_LIST_FOLLOW, method = RequestMethod.GET)
public String todayWxList(@ModelAttribute("model") ModelMap model)
{
    return todayList(model);
}

(3)实现登录拦截逻辑

public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    // 反射获取方法上的Follow注解
    Follow follow = handlerMethod.getMethodAnnotation(Follow.class);
    if (follow != null) {
        if (follow.isAppAccess() && WebCookiesUtil.getIsAppLogin()) {
          return true;
        }
        if (!follow.isWapAccess()) {
          redirectWechatPage(request, response);
          return false;
        } else {
          // 有follow标签+非微信访问+允许wap访问
        }
    }
}

2.3 自定义注解+AOP 实现日志保存

此例子使用Spring boot 实现
(1)导入切面需要的依赖包

<dependency>
      <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

(2)定义一个注解@SysLog

 // 系统日志注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SysLog {
    String value() default "";
}

(3)方法上添加日志注解

     // 获取费用信息,汇总后的
    @SysLog("查询费用信息,主页面")
    @PostMapping("getExpense")
    public ResultDto getExpense(@RequestBody Map<String, Object> map)
    {
        return sExpenseService.getExpense(map);
    }

(4)定义一个切面类,实现拦截逻辑

/**
 * 系统日志,切面处理类
 */
@Aspect
@Component
@Slf4j
public class SysLogAspect {
    private final SysLogService sysLogService;
    @Autowired
    public SysLogAspect(SysLogService sysLogService) {
        this.sysLogService = sysLogService;
    }
    //PointCut表示这是一个切点,@annotation表示这个切点切到一个注解上,后面带该注解的全类名,切面最主要的就是切点,所有的故事都围绕切点发生。
    //logPointCut是一个切点名字,@Around注解使用,表示围绕该切点发生
    @Pointcut("@annotation(com.hao24.common.annotation.SysLog)")
    public void logPointCut() { 
    }
    // logPointCut1是另外一个切点名字
    @Pointcut("execution(* com.hao24.modules..*.*Controller.*(..))")
    public void logPointCut1()
    {
    }
    @Around("logPointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        long beginTime = System.currentTimeMillis();
        //执行方法
        Object result = point.proceed();
        //执行时长(毫秒)
        long time = System.currentTimeMillis() - beginTime;
        //保存日志
        saveSysLog(point, time);
        return result;
    }
    //保存到数据库中
    private void saveSysLog(ProceedingJoinPoint joinPoint, long time) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        SysLogEntity sysLog = new SysLogEntity();
        SysLog syslog = method.getAnnotation(SysLog.class);
        if(syslog != null){
            //注解上的描述
            sysLog.setOperation(syslog.value());
        }
        //请求的方法名
        String className = joinPoint.getTarget().getClass().getName();
        String methodName = signature.getName();
        sysLog.setMethod(className + "." + methodName + "()");
        //请求的参数
        Object[] args = joinPoint.getArgs();
        try{
            String params = new Gson().toJson(args);
            sysLog.setParams(params);
        }catch (Exception ignored){
        }
        //获取request
        HttpServletRequest request = HttpContextUtils.getHttpServletRequest();
        //设置IP地址
        sysLog.setIp(IpUtils.getIpAddr(request));
        //用户名
        String username = ((TblSysUserEntity) SecurityUtils.getSubject().getPrincipal()).getUsername();
        sysLog.setUsername(username);
        sysLog.setTime(time);
        sysLog.setCreateDate(new Date());
        //保存系统日志
        sysLogService.save(sysLog);
    }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,372评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,368评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,415评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,157评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,171评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,125评论 1 297
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,028评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,887评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,310评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,533评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,690评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,411评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,004评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,659评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,812评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,693评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,577评论 2 353

推荐阅读更多精彩内容