java基础--注解

参考 https://juejin.cn/post/7122293903789096973#comment

1、注解基础知识

  • Annotation 其实就是代码里的特殊标记, 这些标记可以在编译, 类加载, 运行时被读取, 并执行相应的处理。通过使用Annotation, 程序员可以在不改变原有逻辑的情况下, 在源文件中嵌入一些补充信息。代码分析工具、开发工具和部署工具可以通过这些补充信息进行验证或者进行部署。
  • Annotion(注解)是一个接口,程序可以通过反射来获取指定程序元素的Annotion对象,然后通过Annotion对象来获取注解里面的元数据。
  • Annotation 可以像修饰符一样被使用, 可用于修饰包,类, 构造器, 方法, 成员变量, 参数, 局部变量的声明, 这些信息被保存在Annotation 的“name=value” 对中。

2、注解作用

  • 生成文档。这是最常见的,也是 Java 最早提供的注解。如@param、@return等等
  • **跟踪代码依赖性,实现替代配置文件功能。**作用就是减少配置,如 SpringBean的装载注入,而且现在的框架基本上都是使用注解来减少配置文件的数量,同时这样也使得编程更加简洁,代码更加清晰。
  • **在编译时进行格式检查。**如@Override放在方法前,如果你这个方法并不是覆盖了超类方法,则编译时就能检查出;

其实注解就是一张便利贴,它贴在那里,你看到的那一刻,就明白该做什么事啦。

  • 如出门前,门上贴着一张便利贴📌,上面写着"出门记得带钥匙",当你看到的那一刻,你就会去检查一下自己是否带钥匙啦。

  • 在Java中也是一样的,你定义了一个注解,注解上可以写一些东西,然后你再将它贴在某个上面,说明白执行规则,当编译到这里的时候需要干嘛干嘛,又或者是当运行到这里的时候需要干嘛干嘛。

  • 因为注解写的东西的不同,或者是处理注解的规则不同,而产生了不同的注解及作用。

3、元注解

元注解就是注解的注解,就是给你自己定义的注解添加注解,你自己定义了一个注解,但你想要你的注解有什么样的功能,此时就需要用元注解对你的注解进行说明了。

注解有很多,门上贴一个,冰箱上贴一个,书桌上贴一个等等
元注解就是把他们整合起来称呼的,像上面这些可以统称为生活类注解啊。所以也就是注解的注解。

3.1 @Target

在 @Target 注解中指定的每一个 ElementType 就是一个约束,它告诉编译器,这个自定义的注解只能用于指定的类型。
说明了注解所修饰的对象范围:注解可被用于 packages、types(类、接口、枚举、Annotation类型)、类型成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catch参数)。

3.2 @Retention

@Retention只能用于修饰一个Annotation定义, 用于指定该Annotation 的生命周期, @Rentention包含一个RetentionPolicy类型的成员变量, 使用@Rentention时必须为该value 成员变量指定值。

  • RetentionPolicy.SOURCE:在源文件中有效(即源文件保留),编译器直接丢弃这种策略的注释
  • RetentionPolicy.CLASS:在class文件中有效(即class保留),当运行Java 程序时, JVM 不会保留注解。这是默认值
  • RetentionPolicy.RUNTIME:在运行时有效(即运行时保留),当运行Java 程序时, JVM 会保留注释。程序可以通过反射获取该注释。

3.3、@Documented

用于指定被该元Annotation 修饰的Annotation 类将被javadoc工具提取成文档。默认情况下,javadoc是不包括注解的。
定义为Documented的注解必须设置Retention值为RUNTIME。

3.4、@Inherited

是一个标记注解阐述了某个被标注的类型是被继承的。使用了@Inherited修饰的注解类型被用于一个class时该class的子类也有了该注解。

3.5、@Repeatable

允许一个注解可以被使用一次或者多次(Java 8)。

4、自定义注解

自定义注解实际上就是一种类型而已,也就是引用类型(Java中除了8种基本类型之外,我们见到的任何类型都是引用类型)

4.1、定义注解

自定义注解过程:

  • 声明一个类MyAnnotation
  • 把class关键字改为@interface

这样我们就声明了一个自定义的注解,当我们用@interface声明一个注解的时候,实际上是声明了一个接口,这个接口自动的继承了java.lang.annotation.Annotation,但是我们只需要@interface这个关键字来声明注解,编译器会自动的完成相关的操作,不需要我们手动的指明继承Annotation接口。另外在定义注解时,不能再继承其他的注解或接口。

下面举了四个例子,这四个注解分别是放在 类(接口、枚举类上)、构造函数、方法级别、成员属性上的。

4.2、注解的成员变量

  • 成员以无参数无异常的方法来声明 String constructorName() default “”;
  • 可以使用default为成员指定一个默认值 public String name() default “defaultName”;
  • 成员类型是受限的,合法的类型包括原始类型以及String、Class、Annotation、Enumeration (JAVA的基本数据类型有8种:byte(字节)、short(短整型)、int(整数型)、long(长整型)、float(单精度浮点数类型)、double(双精度浮点数类型)、char(字符类型)、boolean(布尔类型)
  • 注解类可以没有成员,没有成员的注解称为标识注解,例如JDK注解中的@Override、@Deprecation
  • 如果注解只有一个成员,并且把成员取名为value(),则在使用时可以忽略成员名和赋值号“=”
    • 例如JDK注解的@SuppviseWarnings ;如果成员名 不为value,则使用时需指明成员名和赋值号"="
public @interface MyAnnotation {

    String value();
}

4.3、使用注解

因为我们在注解中声明了属性,所以在使用注解的时候必须要指明属性值(如果有默认值可以不指定) ,多个属性之间没有顺序,多个属性之间通过逗号分隔。

@Retention(RetentionPolicy.RUNTIME)
// type注解修饰范围为类、接口、枚举
@Target({ElementType.METHOD, ElementType.TYPE})
@Documented
public @interface RequestLimit {

    /**
     * 允许访问的次数,默认值120
     */
    int count() default 120;

    /**
     * 间隔的时间段,单位秒,默认值60
     */
    int time() default 60;

    /**
     * 访问达到限制后需要等待的时间,单位秒,默认值120
     */
    int waits() default 120;
    
	// 没有默认值,使用时必须指定属性值
    int test1();
}

@RequestLimit(test1 = 1)
public class TestAnno {

}

5、自定义注解实战

注解大多时候与反射或者 AOP 切面结合使用,它的作用有很多,比如标记和检查,最重要的一点就是简化代码,降低耦合性,提高执行效率。

5.1、自定义注解 + SpringMVC 拦截器实现权限控制功能

还有一种应用场景,权限判断或者说是登录校验。

这个是我当时还没有学习市面上的权限框架,就是使用了这种自定义注解+拦截器的方式来实现简单的权限控制。

定义注解:

@Target({ElementType.METHOD,ElementType.TYPE}) // 这个注解可以放在也可以放在方法上的。
@Retention(RetentionPolicy.RUNTIME)
public @interface Authority {
    Role[] roles() ;
}
public enum Role {
    SADMIN,
    ADMIN,
    TEACHER,
    STUDENT
}

使用注解:

@Authority(roles = {Role.ADMIN, Role.SADMIN}) // 放在类上 说明这个类下所有的方法都需要有这个权限才可以进行访问
@RestController
@RequestMapping("/admin")
public class AdminController {
    
    @GetMapping("/hello")
    public String Hello(){
        return "hello 你最近还好吗";
    }
}
@Controller
@RequestMapping("/student")
public class StudentController {
    @Authority(roles = {Role.STUDENT}) // 放在方法上则说明此方法需要注解上的权限才能进行访问
    @GetMapping("/test")
    public String test(){
        return "你好,我已经不是一名学生啦";
    }
}

编写 SpringMVC 拦截器及处理注解的Handler
在其中进行 Token 的判断,和访问方法的权限判断,看方法上是否有注解,有的话,
就和当前用户对比,成功就可以访问,失败就直接拒绝。

当时用的是SSM框架,所以才会看到有 response.sendRedirect(contextPath + “/login”);这样的。

public class LoginInterceptor extends HandlerInterceptorAdapter {

    private static final Logger log = LoggerFactory.getLogger(WebExceptionHandler.class);

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        String url = request.getRequestURI();
//        log.info(request.getMethod()+" 请求URL:"+url);

        //从Token中解析User信息
        User user = TokenUtil.verifyToken(request);

        String contextPath = request.getContextPath();
        //user 为空则 表示 Token 不存在
        if (user != null) {
            if (user.getRole().equals("sadmin")) {
                //检查方法上 是否有注解的 Role.SADMIN 或者 Role.ADMIN 权限 , 没有则检查类上有没有 如果符合要求则放行
                if (HandlerUitl.checkAuthority(handler, new Role[]{Role.SADMIN, Role.ADMIN})) {
                    request.setAttribute("user", user);
                    return true;
                }
            }
            if (user.getRole().equals("admin")) {
                if (HandlerUitl.checkAuthority(handler, new Role[]{Role.ADMIN})) {
                    request.setAttribute("user", user);
                    return true;
                }else {
                    response.sendRedirect(contextPath + "/login");
                }
            }

            if (user.getRole().equals("teacher")) {
                if (HandlerUitl.checkAuthority(handler, new Role[]{Role.TEACHER})) {

                    return true;
                } else {
                    response.sendRedirect(contextPath + "/login");
                }
            }
            if (user.getRole().equals("student")) {
                if (HandlerUitl.checkAuthority(handler, new Role[]{Role.STUDENT})) {

                    return true;
                } else {

                    response.sendRedirect(contextPath + "/student/login");
                }
            }
        }else {
            response.sendRedirect(contextPath + "/login");
        }


        return false;
    }
}
  • 用于检查 方法 或者 类 是否需要权限
  • 并和 拥有的权限做对比
  • 如果方法上有 ,则以方法的 优先
public class HandlerUitl {

    public static boolean checkAuthority(Object handler, Role[] roles1){
            if (handler instanceof HandlerMethod) {
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            // 获取方法上的注解
            Authority authority = handlerMethod.getMethod().getAnnotation(Authority.class);
            // 如果方法上的注解为空 则获取类的注解
            if (authority == null) {
                authority = handlerMethod.getMethod().getDeclaringClass().getAnnotation(Authority.class);
            }
            // 如果标记了注解,则判断权限
            if (authority != null) {
                Role[] roles = authority.roles();
                //如果 方法权限为 0 则通过
                if(roles.length==0){
                    return true;
                }
                //判断 拥有的权限 是否 符合 方法所需权限
                for(int i = 0; i < roles.length; i++){
                    for(int j = 0; j < roles1.length; j++){
                        if(roles[i]==roles1[j]){
//                            System.out.println("可以访问");
                            return true;
                        }
                    }
                }

            }
            return false;
        }
        return true;

    }

}

5.2、自定义注解+AOP+Redis 防止重复提交

先简单说一下防止重复提交注解的逻辑:

  • 在需要防止重复提交的接口的方法,加上注解。
  • 发送请求写接口携带 Token
  • 请求的路径+ Token 拼接程 key,value 值为生成的 UUID 码
  • 然后 set Redis 分布式锁,能获取到就顺利提交(分布式锁默认 5 秒过期),不能获取就是重复提交了,报错。

定义注解

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

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface NoRepeatSubmit {

    /**
     * 设置请求锁定时间
     * @return
     */
    int lockTime() default 5;
}

定义处理注解的切面类

import com.eshop.api.ApiResult;
import com.eshop.common.aop.NoRepeatSubmit;
import com.eshop.common.util.RedisLock;
import com.eshop.common.util.RequestUtils;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;

import javax.servlet.http.HttpServletRequest;
import java.util.UUID;

/**
 * 重复提交aop
 */
@Aspect
@Component
@Slf4j
public class RepeatSubmitAspect {

    @Autowired
    private RedisLock redisLock;

    @Pointcut("@annotation(noRepeatSubmit)")
    public void pointCut(NoRepeatSubmit noRepeatSubmit) {
    }

    @Around("pointCut(noRepeatSubmit)")
    public Object around(ProceedingJoinPoint pjp, NoRepeatSubmit noRepeatSubmit) throws Throwable {
        int lockSeconds = noRepeatSubmit.lockTime();

        HttpServletRequest request = RequestUtils.getRequest();
        Assert.notNull(request, "request can not null");

        String bearerToken = request.getHeader("Authorization");
        String[] tokens = bearerToken.split(" ");
        String token = tokens[1];
        String path = request.getServletPath();
        String key = getKey(token, path);
        String clientId = getClientId();

        boolean isSuccess = redisLock.tryLock(key, clientId, lockSeconds);
        log.info("tryLock key = [{}], clientId = [{}]", key, clientId);

        if (isSuccess) {
            log.info("tryLock success, key = [{}], clientId = [{}]", key, clientId);
            // 获取锁成功
            Object result;

            try {
                // 执行进程
                result = pjp.proceed();
            } finally {
                // 解锁
                redisLock.releaseLock(key, clientId);
                log.info("releaseLock success, key = [{}], clientId = [{}]", key, clientId);
            }
            return result;
        } else {
            // 获取锁失败,认为是重复提交的请求
            log.info("tryLock fail, key = [{}]", key);
            return  ApiResult.fail("重复请求,请稍后再试");
        }
    }

    private String getKey(String token, String path) {
        return token + path;
    }

    private String getClientId() {
        return UUID.randomUUID().toString();
    }

}
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.params.SetParams;

import java.util.Collections;

/**
 * Redis 分布式锁实现
 */
@Service
public class RedisLock {

    private static final Long RELEASE_SUCCESS = 1L;
    private static final String LOCK_SUCCESS = "OK";
    private static final String SET_IF_NOT_EXIST = "NX";
    // 当前设置 过期时间单位, EX = seconds; PX = milliseconds
    private static final String SET_WITH_EXPIRE_TIME = "EX";
    // if get(key) == value return del(key)
    private static final String RELEASE_LOCK_SCRIPT = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";

    @Autowired
    private StringRedisTemplate redisTemplate;

    /**
     * 该加锁方法仅针对单实例 Redis 可实现分布式加锁
     * 对于 Redis 集群则无法使用
     *
     * 支持重复,线程安全
     *
     * @param lockKey   加锁键
     * @param clientId  加锁客户端唯一标识(采用UUID)
     * @param seconds   锁过期时间
     * @return
     */
    public boolean tryLock(String lockKey, String clientId, long seconds) {
        return redisTemplate.execute((RedisCallback<Boolean>) redisConnection -> {
            Jedis jedis = (Jedis) redisConnection.getNativeConnection();
            SetParams setParams = new SetParams();
            String result = jedis.set(lockKey, clientId, setParams.nx().px(seconds));
            if (LOCK_SUCCESS.equals(result)) {
                return true;
            }
            return false;
        });
    }

    /**
     * 与 tryLock 相对应,用作释放锁
     *
     * @param lockKey
     * @param clientId
     * @return
     */
    public boolean releaseLock(String lockKey, String clientId) {
        return redisTemplate.execute((RedisCallback<Boolean>) redisConnection -> {
            Jedis jedis = (Jedis) redisConnection.getNativeConnection();
            Object result = jedis.eval(RELEASE_LOCK_SCRIPT, Collections.singletonList(lockKey),
                    Collections.singletonList(clientId));
            if (RELEASE_SUCCESS.equals(result)) {
                return true;
            }
            return false;
        });
    }
}

使用注解

/**
     * 添加收藏
     */
@NoRepeatSubmit
@PostMapping("/collect/add")
@ApiOperation(value = "添加收藏",notes = "添加收藏")
public ApiResult<Boolean> collectAdd(@Validated @RequestBody StoreProductRelationQueryParam param){
    // 处理业务逻辑
    return ApiResult.ok();
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值