Bootstrap

Java注解深度解析:从基础概念到自定义注解的实战应用

注解

注解概念

1.什么是注解

注解用来给类声明附加额外信息,可以标注在类、字段、方法等上面,编译器、JVM以及开发人员等都可以通过反射拿到注解信息,进而做一些相关处理

SpringBoot 全部都是采用注解化

2.常用注解

@Override 只能标注在子类覆盖父类的方法上面,有提示的作用
@Deprecated 标注在过时的方法或类上面,有提示的作用
@SuppressWarnings("unchecked") 标注在编译器认为有问题的类、方法等上面,用来取消编译器的警告提示,警告类型有serialuncheckedunusedall

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反射机制的原理与应用场景》

;