注解的妙用

一、注解是什么?

在Java中,注解(Annotation)是一种用于在代码中插入元数据的方式。注解不直接影响程序代码的行为,而是提供了一种形式化的方法来说明代码的某些方面,供编译器、开发工具或运行时环境等使用。简单来说,注解是用来提供信息给编译器和JVM(Java虚拟机)的标签,这些信息可以被编译器、开发工具、框架等在编译时或运行时读取并处理。

Java注解有以下几个关键特性:

元注解:用来定义注解的注解,如@Retention, @Target, @Documented, @Inherited等。例如,@Retention(RetentionPolicy.RUNTIME)指定了这个注解在运行时仍然有效,因此可以通过反射机制读取。

目标:注解可以应用在类、方法、构造函数、字段、局部变量声明、包或参数等元素上。@Target元注解指定了一个注解可以被应用于哪些程序元素。

保留策略:通过@Retention元注解指定,决定了注解的生命周期。
有三种保留策略:

  • SOURCE:注解只保留在源码中,编译时会被忽略。
  • CLASS:注解保留在编译后的字节码文件中,但运行时不会被JVM保留。
  • RUNTIME:注解不仅保留在源码和字节码中,JVM在运行时也能访问到,这使得我们可以在程序运行时通过反射读取注解信息。
    默认值:注解的成员可以有默认值,如果在使用注解时没有为成员赋值,则使用默认值。

自定义注解:用户可以定义自己的注解类型,通过@interface关键字进行定义,类似于定义接口。

注解的常见用途包括但不限于:

编译检查:如@Override确保子类正确重写了父类方法。
自动生成文档:如Javadoc使用的@param, @return, @throws等。
测试框架:JUnit的@Test标注测试方法。
配置框架:Spring框架中的@Component, @Service, @Autowired等用于依赖注入和管理Bean。
其他工具处理:如Lombok的@Data自动生成getter/setter等。

二、注解的原理

注解的生效是一个从定义到最终在编译或运行时被识别并采取相应行动的过程,它依赖于注解的保留策略、读取方式以及具体的应用逻辑。

Java注解的生效原理涉及几个核心环节:定义、编译、存储、读取与处理。

以下是注解生效的具体步骤:

1、定义:注解通过@interface关键字定义,类似于接口,其中可以包含方法声明,这些方法实际上定义了注解的属性(也称为成员变量)。方法没有方法体,其返回类型定义了属性的类型。

2、编译时处理:

  • 编译器识别:编译器会检查注解的使用是否合法,比如是否按照@Target指定的目标使用,同时也会处理一些编译时生效的注解,如@Override、@Deprecated等。
  • 注解处理器:除了标准处理外,还可以通过自定义注解处理器(Annotation Processor)在编译期间读取并处理注解,生成额外的源代码、编译时警告或错误,甚至修改现有类。

3、存储:
根据@Retention元注解的设置,决定注解信息如何存储。

  • SOURCE:仅在源码中,编译后丢失。
  • CLASS:编译进.class文件,但JVM运行时不保留。
  • RUNTIME:存储在.class文件中,并能在运行时通过反射访问。

4、运行时读取与处理:

  • 反射:对于RUNTIME保留的注解,程序可以通过反射API(如Class.getAnnotation(), Method.getAnnotations()等)在运行时读取注解信息。
  • 注解处理器/框架:如Spring框架会扫描带有特定注解(如@Component, @Service)的类,并根据这些注解配置应用程序上下文,实现依赖注入等功能。
  • 动态代理:对于需要动态处理注解的场景,可能会利用Java的动态代理机制,如通过java.lang.reflect.Proxy或第三方库如CGLIB,创建代理对象并在调用方法前后处理注解逻辑。

5、 实际应用:一旦注解被读取,应用(框架、库或自定义逻辑)会根据注解内容做出相应处理,比如初始化组件、执行验证逻辑、改变执行流程等。

三、注解的一般用法

定义一个注解

定义一个非常简单的,打印方法传参的注解

@Target(ElementType.METHOD) // 作用在方法上
@Retention(RetentionPolicy.RUNTIME)  // 运行时可见(默认编译时可见)
public @interface Introduce {String name() default "";int age() default 0;String address() default "";
}

定义注解处理器

通过反射获取对应注解并解析其中的参数信息。

public class IntroduceHandler {public void handle() {Class<Person> myClass = Person.class;Method[] declaredMethods = myClass.getDeclaredMethods();for (Method method : declaredMethods) {if (method.isAnnotationPresent(Introduce.class)) {Introduce introduce = method.getAnnotation(Introduce.class);String name = introduce.name();int age = introduce.age();String address = introduce.address();System.out.println("name:" + name + ",age:" + age + ",address:" + address);}}}}

使用注解

public class Person {public static void main(String[] args) {new IntroduceHandler().handle();new Person().sayHello("xiaoming");}@Introduce(name = "Tan", age = 18, address = "shanghai")public void sayHello(String name) {System.out.println("Hello " + name);}
}

如上所示,通过代码解析获取对应的注解解析器,当调用 sayHello方法时就可以获取对应参数的并解析出来。这种方式需要手动调用解析处理器,侵入性强。有没有好用的呢?那就是 Spring Aop提供的注解了,框架内自动完成注解解析动作。

注解的高级用法(分布式锁)

目标:在springboot中使用注解完成分布式锁的定义和使用。

springboot前置依赖

基于 Springboot - 2.7.14版本,Java8 环境,基于 redisson框架

  1. 引入核心依赖
 		<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency><dependency><groupId>org.redisson</groupId><artifactId>redisson-spring-boot-starter</artifactId><version>3.21.0</version></dependency>
  1. redis 参数配置
server:port: 8080
#logging:
#  level:
#    root: debug
spring:redis:# redisson配置redisson:# 如果该值为false,系统将不会创建RedissionClient的bean。enabled: true# mode的可用值为,single/cluster/sentinel/master-slavemode: single# single: 单机模式#   address: redis://localhost:6379# cluster: 集群模式#   每个节点逗号分隔,同时每个节点前必须以redis://开头。#   address: redis://localhost:6379,redis://localhost:6378,...# sentinel:#   每个节点逗号分隔,同时每个节点前必须以redis://开头。#   address: redis://localhost:6379,redis://localhost:6378,...# master-slave:#   每个节点逗号分隔,第一个为主节点,其余为从节点。同时每个节点前必须以redis://开头。#   address: redis://localhost:6379,redis://localhost:6378,...address: redis://127.0.0.1:6379# redis 密码,空可以不填。password:database: 0
  1. redis configuration bean配置
package com.tan.training.config;import org.apache.commons.lang3.StringUtils;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;/*** Redisson配置类。*/
@Configuration
@ConditionalOnProperty(name = "spring.redis.redisson.enabled", havingValue = "true")
public class RedissonConfig {@Value("${spring.redis.redisson.mode}")private String mode;/*** 仅仅用于sentinel模式。*/@Value("${spring.redis.redisson.masterName:}")private String masterName;@Value("${spring.redis.redisson.address}")private String address;@Value("${spring.redis.redisson.password:}")private String password;/*** 数据库默认0*/@Value("${spring.redis.redisson.database:0}")private Integer database;@Beanpublic RedissonClient redissonClient() {if (StringUtils.isBlank(password)) {password = null;}Config config = new Config();if ("single".equals(mode)) {config.useSingleServer().setDatabase(database).setPassword(password).setAddress(address);} else if ("cluster".equals(mode)) {String[] clusterAddresses = address.split(",");config.useClusterServers()//集群模式不支持多个数据库概念,默认db 0.setPassword(password).addNodeAddress(clusterAddresses);} else if ("sentinel".equals(mode)) {String[] sentinelAddresses = address.split(",");config.useSentinelServers().setDatabase(database).setPassword(password).setMasterName(masterName).addSentinelAddress(sentinelAddresses);} else if ("master-slave".equals(mode)) {String[] masterSlaveAddresses = address.split(",");if (masterSlaveAddresses.length == 1) {throw new IllegalArgumentException("redis.redisson.address MUST have multiple redis addresses for master-slave mode.");}String[] slaveAddresses = new String[masterSlaveAddresses.length - 1];System.arraycopy(masterSlaveAddresses, 1, slaveAddresses, 0, slaveAddresses.length);config.useMasterSlaveServers().setDatabase(database).setPassword(password).setMasterAddress(masterSlaveAddresses[0]).addSlaveAddress(slaveAddresses);} else {throw new IllegalArgumentException(mode);}return Redisson.create(config);}
}

定义注解

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DistributeLock {int THIRTY_SECOND = 30000;int ONE_MINUTE = 60000;int TEN_MINUTE = 600000;String id();int milliSeconds() default 60000;boolean forceUnlock() default false;
}

定义注解AOP 处理器

@Aspect
@Component
@Lazy(false)
public class DistributeLockAspect {private static final Logger log = LoggerFactory.getLogger(DistributeLockAspect.class);@Autowiredprivate RedissonClient redisson;public DistributeLockAspect() {}@PostConstructpublic void init() {log.info("DistributeLockAspect start!");}@Around("@annotation(com.tan.training.aop.DistributeLock)")public void doHandler(ProceedingJoinPoint pjp) throws Throwable {MethodSignature signature = (MethodSignature)pjp.getSignature();Method method = signature.getMethod();DistributeLock lockAnno = (DistributeLock)method.getAnnotation(DistributeLock.class);if (lockAnno == null) {throw new IllegalAccessException("no @DistributeScheduleLock");} else {String methodFullNameWithId = method.getDeclaringClass().getName() + "." + method.getName() + ":" + lockAnno.id();String lockName = "rLock:" + methodFullNameWithId;synchronized(this) {RLock lock = this.redisson.getLock(lockName);if (lock.isLocked()) {log.debug("{} not obtained the lock at {}", methodFullNameWithId, System.currentTimeMillis());} else {// 确保在异常情况下也能正确地处理锁的释放boolean releaseLockFlag = false;try {releaseLockFlag = true;long b = System.currentTimeMillis();boolean flag = lock.tryLock(10L, (long)lockAnno.milliSeconds(), TimeUnit.MILLISECONDS);log.debug("{} tryLock flag: {}, cost {} ms", new Object[]{methodFullNameWithId, flag, System.currentTimeMillis() - b});if (flag) {long beginTime = System.currentTimeMillis();log.debug("{} obtained the lock at {}", methodFullNameWithId, beginTime);pjp.proceed();log.debug("{} execution cost {} ms", methodFullNameWithId, System.currentTimeMillis() - beginTime);releaseLockFlag = false;} else {releaseLockFlag = false;}} finally {if (releaseLockFlag) {boolean var15 = lockAnno.forceUnlock();if (var15) {lock.unlock();log.debug("{} force to release the lock at {}", methodFullNameWithId, System.currentTimeMillis());}}}boolean forceUnlock = lockAnno.forceUnlock();if (forceUnlock) {lock.unlock();log.debug("{} force to release the lock at {}", methodFullNameWithId, System.currentTimeMillis());}}}}}
}

定时获取锁测试

@Component
public class TaskJob {@Scheduled(cron = "0/5 * * * * ?")// 等待锁过期释放@DistributeLock(id = "changeState", milliSeconds = 10 * 1000,forceUnlock = true)// 使用完,强制释放锁//@DistributeLock(id = "changeState", milliSeconds = 10 * 1000,forceUnlock = true)public void changeStatus() {System.out.println("changeStatus start ....");}
}

如下:非强制释放锁的情况下,锁过期依赖于时间到期。
在这里插入图片描述

总结

注解是一种丰富代码的元数据形式,可以用于标记和控制代码行为。合理使用注解可以提高代码的可读性、可维护性和可测试性。同时,使用注解的过程中需要注意保持注解的简洁、明确和易于理解,避免过度使用注解导致代码臃肿和复杂。本文展示了两种场景下的注解使用小案例,方便大家在定义的时候直接取用,同时也方便加深理解。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/338756.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Vue+AntDesignVue实现a-tree树形组件的层级选中功能

文章目录 一、构建树形组件二、js代码实现 最近碰到了一个新需求&#xff0c;使用树形选择器实现角色管理功能&#xff0c;当用户选中一个节点时&#xff0c;其所有子节点都会被自动选中&#xff1b;同样&#xff0c;当用户取消选中一个节点时&#xff0c;其所有子节点也会被取…

LNMP部署及应用

目录 1.LNMP概述 Nginx 特点 Nginx 作用 2.分布式部署LNMP操练 Nginx主机&#xff1a;CentOS 7-1 PHP主机: CentOS 7-2 1.LNMP概述 Nginx 是开源、高性能、高可靠的 Web 和反向代理服务器&#xff0c;而且支持热部署&#xff0c;几乎可以做到 7 * 24 小时不间断运行&…

Java实战:文本文件复制

任务目标 本实战任务的目标是创建一个Java程序&#xff0c;用于复制指定的文本文件到另一个位置&#xff0c;并在控制台中显示复制结果。 任务步骤 创建源文件&#xff1a;在指定的路径D:\love.txt创建源文件。创建文件复制类&#xff1a;在net.huawei.student.test包中创建…

绿联 安装memcached容器 - 一个开源的高性能分布式内存对象缓存系统

绿联 安装memcached容器 - 一个开源的高性能分布式内存对象缓存系统 1、镜像 memcached:latest 2、安装 2.1、基础设置 重启策略&#xff1a;容器退出时总是重启容器。 2.2、网络 网络选择桥接(bridge)。 2.3、端口设置 容器端口11211固定不变&#xff0c;本地端口若未被…

即时通讯视频会议平台,WorkPlus本地化部署解决方案

随着现代科技的快速发展&#xff0c;传统的会议方式已经不再满足企业和组织的需求。即时通讯视频会议以其便利性和高效性&#xff0c;成为了现代企业沟通和协作的重要工具。通过即时通讯视频会议&#xff0c;企业可以实现无时差的交流和远程协作&#xff0c;增强团队合作和提高…

计算机系统结构之虚拟存储器

虚拟存储器通过增设地址映像表机构来实现程序在主存中的定位。 一、段式管理 (1)段式管理的基本思想是把程序按内容或过程函数关系分成段&#xff0c;每段有自己的名字。一个用户作业或进程所包含的段对应一个二维线性虚拟空间&#xff08;二维虚拟存储器&#xff09;。段式管…

springboot实现文件上传功能,整合云服务

文章目录 这是springboot案例的,文件上传功能的拆分,本篇将带大家彻底了解文件上传功能,先从本地存储再到云存储,全网最详细版本,保证可以学会,可以了解文件上传功能的开发文件上传功能剖析进行书写一个小的文件上传文件上传的文件三要素首先表单提交的方式要是 post方式,第二个…

如何从Windows的硬盘中恢复丢失或删除的照片

你有没有不小心删除了一张你再也找不回来的重要照片&#xff1f;如果是您的公司或家庭照片、婚礼或童年回忆&#xff0c;或亲人的照片怎么办&#xff1f; 根据我们的经验&#xff0c;用户通常会在清理计算机的存储/速度时遇到这样的事故&#xff0c;并最终删除包含重要图片的文…

pytorch学习笔记5

transform 本质上作用是将图片通过transform这个这个工具箱获取想要的结果 tensor就是一个包含神经网络需要的一些理论基础的参数 from torch.utils.tensorboard import SummaryWriter from torchvision import transforms from PIL import Image #tensor数据类型 #通过tra…

最新h5st(4.7.2)参数分析与纯算法还原(含算法源码)

文章目录 1. 写在前面2. 加密分析3. 算法还原 【&#x1f3e0;作者主页】&#xff1a;吴秋霖 【&#x1f4bc;作者介绍】&#xff1a;擅长爬虫与JS加密逆向分析&#xff01;Python领域优质创作者、CSDN博客专家、阿里云博客专家、华为云享专家。一路走来长期坚守并致力于Python…

22 、系统安全

新的服务器到手&#xff0c;部署服务器初始化。 1、配置ip地址 网关dns解析&#xff08;static&#xff09;内网和外网。 2、安装源&#xff0c;外网&#xff08;在线即可&#xff09;&#xff0c;内网&#xff08;只能用源码包编译安装&#xff09;。 3、磁盘分区&#xff…

k8s 1.28.x 配置nfs

1.安装nfs&#xff0c;在每个节点上安装 yum install -y nfs-utils 2.创建共享目录(主节点上操作) mkdir -p /opt/nfs/k8s 3.编写NFS的共享配置 /opt/nfs/k8s *(rw,no_root_squash) #*代表对所有IP都开放此目录&#xff0c;rw是读写 4.启动nfs systemctl enable nfs-ser…

十_信号11 - 函数sigsetjmp() 和 siglongjmp()

也就是说&#xff0c;正常情况下&#xff0c;当捕捉到一个信号&#xff0c;并调用该信号的信号处理程序时&#xff0c;被捕捉的信号会被加入到当前进程的信号屏蔽字中&#xff0c;以防止在本次信号处理程序还没有完成的时候&#xff0c;再次触发该信号&#xff0c; 发生重入。 …

Py列表(list)

目录 正向索引&#xff1a; 反向索引&#xff1a; 嵌套列表&#xff1a; 修改列表中的值 列表常用的方法 实例 练习&#xff1a; 正向索引&#xff1a; 从0开始&#xff0c;依次递增。第一个元素的索引为0&#xff0c;第二个元素的索引为1&#xff0c;依此类推。 列表的下标…

JS-09-es6常用知识1

目录 1 模板字符串 1.1 模板字符串基本用法 1.2 模板字符串解决了一些痛点 2 解构赋值 2.1 对象的解构赋值 2.2 函数参数的解构赋值 2.3 补写&#xff1a;属性的简写 3 rest参数 3.1 arguments 3.2 rest参数 3.3 补充&#xff1a;判断数据类型 4 箭头函数 4.1 …

传输中的串扰(八)

串扰指的是有害信号从一个线网传递到相邻线网上。通常把噪声源所在的线网称为动态线或攻击线网&#xff0c;而把有噪声形成的线网称为静态线或受害线网。 静态线上的噪声电压的表现与信号电压完全一样。一旦在静态线上产生噪声电压&#xff0c;它们就会传播并在阻抗突变处出现反…

服务器数据恢复—服务器raid常见故障表现原因解决方案

RAID&#xff08;磁盘阵列&#xff09;是一种将多块物理硬盘整合成一个虚拟存储的技术&#xff0c;raid模块相当于一个存储管理的中间层&#xff0c;上层接收并执行操作系统及文件系统的数据读写指令&#xff0c;下层管理数据在各个物理硬盘上的存储及读写。相对于单独的物理硬…

外卖点餐系统 springboot+vue+element-ui

免费获取方式↓↓↓ 项目介绍038&#xff1a; http://localhost:8080/ 账号&#xff1a;weiguanke 123 系统登陆后展示 用户可视界面 – 登录页面 – 首页&#xff1a; – 店铺查找页面&#xff1a; 店铺查找 – 店铺页面 店铺管理者可视页面 – 店铺页面 店铺管理员…

Redis之持久化、集群

1. Redis持久化 Redis为什么需要持久化?因为Redis的数据我们都知道是存放在内存中的&#xff0c;那么每次关闭或者机器断电&#xff0c;我们的数据旧丢失了。 因此&#xff0c;Redis如果想要被别人使用&#xff0c;这个问题就需要解决&#xff0c;怎么解决呢?就是说我们的数…

Windows通过cmd运行快速启动应用

Windows如何通过cmd运行快速启动应用&#xff1f; 在Windows操作系统中&#xff0c;可以通过配置环境变量的方式将文件的路径配置到环境变量的path中&#xff0c;配置完成后可以在cmd中输入对应的应用名称即可启动应用&#xff0c;具体操作如下&#xff1a; 1. 添加应用程序路径…