注解
注解概念
1.什么是注解
注解用来给类声明附加额外信息,可以标注在类、字段、方法等上面,编译器、JVM以及开发人员等都可以通过反射拿到注解信息,进而做一些相关处理
SpringBoot 全部都是采用注解化
2.常用注解
@Override
只能标注在子类覆盖父类的方法上面,有提示的作用
@Deprecated
标注在过时的方法或类上面,有提示的作用
@SuppressWarnings("unchecked")
标注在编译器认为有问题的类、方法等上面,用来取消编译器的警告提示,警告类型有serial
、unchecked
、unused
、all
3.元注解
元注解(Meta-Annotation),它们被用来对其他注解进行注释。元注解可以在编写自定义注解时使用,以表明注解的作用范围、生命周期等
元注解用来在声明新注解时指定新注解的一些特性
@Target
指定新注解标注的位置,比如类、字段、方法等,取值有ElementType.Method等
ElementType.TYPE:用于类、接口、枚举、注解类型。
ElementType.FIELD:用于字段(包括枚举常量)。
ElementType.METHOD:用于方法。
ElementType.PARAMETER:用于方法参数。
ElementType.CONSTRUCTOR:用于构造函数。
ElementType.LOCAL_VARIABLE:用于局部变量。
ElementType.ANNOTATION_TYPE:用于注解类型。
ElementType.PACKAGE:用于包声明。
ElementType.TYPE_PARAMETER:用于Java 8新增的类型参数上。
ElementType.TYPE_USE:Java8新增,注解可以通过该元注解使用于任何用于声明类型的地方。
@Retention
指定新注解的信息保留到什么时候,取值有RetentionPolicy.RUNTIME
等
@Inherited
指定新注解标注在父类上时可被子类继承
4.常用注解
@Target(ElementType.METHOD) // 指定新注解可以标注在方法上
@Retention(RetentionPolicy.RUNTIME) // 指定新注解保留到程序运行时期
@Inherited // 指定新注解标注在父类上时可被子类继承
public @interface MayiktName {
public String name();
}
5.注解的Target
TYPE:类、接口(包括注解类型)和枚举的声明
FIELD:字段声明(包括枚举常量)
METHOD:方法声明
PARAMETER:参数声明
CONSTRUCTOR:构造函数声明
LOCAL_VARIABLE:本地变量声明
ANNOTATION_TYPE:注解类型声明
PACKAGE:包声明
TYPE_PARAMETER:类型参数声明,JavaSE8引进,可以应用于类的泛型声明之处
TYPE_USE:JavaSE8引进,此类型包括类型声明和类型参数声明
获取注解信息
Class<?> aClass = Class.forName("com.mayikt.entity.UserEntity");
获取当前类上的注解
MayiktName declaredAnnotation = aClass.getDeclaredAnnotation(MayiktName.class);
System.out.println(declaredAnnotation);
获取当前方法上的注解
Method userNameMethod = aClass.getDeclaredMethod("getUserName");
MayiktName declaredAnnotation =
userNameMethod.getDeclaredAnnotation(MayiktName.class);
System.out.println(declaredAnnotation);
获取字段上的注解
Field pubUserName = aClass.getDeclaredField("pubUserName");
final MayiktName declaredAnnotation =
pubUserName.getDeclaredAnnotation(MayiktName.class);
System.out.println(declaredAnnotation);
获得构造方法注解
// 先获得构造方法对象
Constructor<TestAnnotation> constructors = clazz.getConstructor(new Class[] {});
// 拿到构造方法上面的注解实例
MyConstructorAnnotation myConstructorAnnotation =constructors.getAnnotation(MyConstructorAnnotation.class);
System.out.println(myConstructorAnnotation.desc() + "+" + myConstructorAnnotation.uri());
注解如何生效
实际项目中 注解想生效通过反射+aop机制
自定义注解运行的原理 :反射+aop
自定义注解的运行原理涉及到Java的反射机制。
在Java中,注解就像修饰器一样,它们可以在运行期被动态地读取和处理。当我们在代码中使用了自定义注解时,编译器会将注解信息保存在类文件的字节码中,并在运行时通过反射机制读取注解的信息。
在程序运行时,Java虚拟机会扫描指定的类,找出其中带有指定注解的部分,然后读取注解的内容,并根据注解的信息进行相应的处理。这个过程中,Java虚拟机通过解析类文件中的常量池,获取注解对象及注解对象的属性的值,从而完成注解的具体处理过程。
自定义注解的运行原理可以用例子说明。例如,我们定义了一个名为"User"的注解,并用它对类进行了修饰:
@User(id=1, name="Tom")
public class MyClass {
// ...
}
编译器会将该注解信息保存在MyClass类的字节码中。然后在运行时,我们可以使用反射的方式读取注解的信息:
Class<MyClass> cls = MyClass.class;
User user = cls.getAnnotation(User.class);
int id = user.id(); // 获取注解的属性值
String name = user.name(); // 获取注解的属性值
在这个例子中,我们使用反射机制获取了MyClass类上的注解信息,并将其属性值赋给变量id和name。这里的User将会是一个Java类,它可以包含多个属性,用于描述注解的信息。
在程序中使用自定义注解时,需要注意注解类型的作用范围及生命周期,例如@Retention(RetentionPolicy.RUNTIME)
和@Target(ElementType.TYPE)
等注解需要指定其作用范围和生命周期,以保证符合预期的效果。
注解实现案例
1.自定义限流注解
对我们接口实现 限流 比如 每s 只能访问1次 或者每s 访问两次。
Maven
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.1.RELEASE</version>
</parent>
<dependencies>
<!-- springboot 整合web组件-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>18.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
</dependencies>
使用谷歌的guava例子
@RestController
public class MemberService {
/**
* 每秒生成 1.0个令牌
*/
private RateLimiter rateLimiter = RateLimiter.create(1.0);
@GetMapping("/get")
public String get() {
boolean result = rateLimiter.tryAcquire();
if (!result) {
return "当前访问人数过多,请稍后重试!";
}
return "my is get";
}
}
2.封装自定义注解限流框架
@RestController
public class MemberService {
@GetMapping("/get")
@MayiktCurrentLimit(name = "get", token = 1)
public String get() {
return "my is get";
}
@GetMapping("/add")
@MayiktCurrentLimit(name = "add", token = 10)
public String add() {
return "my is add";
}
}
整合自定义注解
@Target({ElementType.METHOD})//表示该注解可以应用于方法上
@Retention(RetentionPolicy.RUNTIME)//表示该注解在运行时仍然可以通过反射来访问
public @interface MayiktCurrentLimit {
/**
* 限流的名称
*/
String name() default "";//default 为其中的某个元素设置默认值
double token() default 20;
}
整合Aop实现接口限流
@Aspect//声明一个类是切面,用于定义横切关注点(例如日志、事务管理等)
@Component
public class CurrentLimitAop {
// /**
// * 每秒生成 1.0个令牌
// */
// private RateLimiter rateLimiter = RateLimiter.create(1.0);
private ConcurrentHashMap<String, RateLimiter> rateLimiters = new ConcurrentHashMap<>();
// @Before(value = "@annotation(com.zhaoli.annotation.MayiktCurrentLimit)")
// public void before() {
// System.out.println("-------------前置通知----------------");
// }
//
// @AfterReturning(value = "@annotation(com.zhaoli.annotation.MayiktCurrentLimit)")
// public void afferEeturning() {
// System.out.println("-------------后置通知----------------");
// }
/**
* 使用了 Spring AOP 的环绕通知(Around Advice)。通过 @Around 注解,该通知绑定到了特定的切点,
* 即被 @annotation(com.zhaoli.annotation.MayiktCurrentLimit) 注解标记的方法。
* 这表示环绕通知将会环绕(即在方法执行前后)被 @MayiktCurrentLimit 注解标记的方法
*/
@Around(value = "@annotation(com.zhaoli.annotation.MayiktCurrentLimit)")
public Object around(ProceedingJoinPoint joinPoint) {
try {
Signature signature = joinPoint.getSignature();//获取拦截的方法名
MethodSignature methodSignature = (MethodSignature) signature;
//获取该方法上的注解
MayiktCurrentLimit mayiktCurrentLimit = methodSignature.getMethod()
.getDeclaredAnnotation(MayiktCurrentLimit.class);
//获取到该注解的参数
String name = mayiktCurrentLimit.name();
double token = mayiktCurrentLimit.token();
//获取 name 对应的 rateLimiter
RateLimiter rateLimiter = rateLimiters.get(name);
if (rateLimiter == null) {
//不存在将其存入 ConcurrentHashMap 中
rateLimiter = RateLimiter.create(token);
rateLimiters.put(name, rateLimiter);
}
/**
* 如果被限流
*/
boolean result = rateLimiter.tryAcquire();
if (!result) {
return "当前访问人数过多,请稍后重试!";
}
Object proceedResult = joinPoint.proceed();//执行目标方法
return proceedResult;
} catch (Throwable e) {
return "系统异常";
}
}
}
对反射不太了解推荐学习这篇文章:《探索Java反射机制:深入理解并灵活运用Java反射机制的原理与应用场景》