首页 > 基础资料 博客日记
【Java用法】Java中如何自定义注解并使用
2025-01-15 14:00:08基础资料围观49次
博主介绍:✌全网粉丝21W+,CSDN博客专家、Java领域优质创作者,掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域✌
技术范围:SpringBoot、SpringCloud、Vue、SSM、HTML、Nodejs、Python、MySQL、PostgreSQL、大数据、物联网、机器学习等设计与开发。
感兴趣的可以先关注收藏起来,在工作中、生活上等遇到相关问题都可以给我留言咨询,希望帮助更多的人。
Java中如何自定义注解并使用
一、背景知识
1.1 @interface 关键字
我们想定义一个自己的注解 需要使用 @interface 关键字来定义。如定义一个叫 MyAnnotation 的注解:
public @interface MyAnnotation {
}
1.2 元注解
只有 @interface 关键字 还不够,我们还需要了解5大元注解
- @Retention
- @Target
- @Documented
- @Inherited(JDK8 引入)
- @Repeatable(JDK8 引入)
- @Retention 指定注解的生命周期
其中Retention是一个枚举类:
RetentionPolicy.SOURCE
:注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃(.java文件)
RetentionPolicy.CLASS
:注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期(.class文件)
RetentionPolicy.RUNTIME
: 注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在(内存中的字节码)
- @Target指定注解可以修饰的元素类型
ElementType.ANNOTATION_TYPE
- 标记的注解可以应用于注解类型。
ElementType.CONSTRUCTO
R - 标记的注解可以应用于构造函数。
ElementType.FIELD
- 标记的注解可以应用于字段或属性。
ElementType.LOCAL_VARIABLE
- 标记的注解可以应用于局部变量。
ElementType.METHOD
- 标记的注解可以应用于方法。
ElementType.PACKAGE
- 标记的注解可以应用于包声明。
ElementType.PARAMETER
- 标记的注解可以应用于方法的参数。
ElementType.TYPE
- 标记的注解可以应用于类的任何元素。
- @Documented
指定注解会被JavaDoc工具提取成文档。默认情况下,JavaDoc是不包括文档的
- @Inherited
表示该注解会被子类继承,注意,仅针对类,成员属性、方法并不受此注释的影响。
- @Repeatable
表示注解可以重复使用,为了解决同一个注解不能重复在同一类/方法/属性上使用的问题。
二、注解实现
2.1 步骤一:定义注解
例如实现一个简单的在标记注解的地方打印输出方法耗时时间的日志。
定义一个 MyLogTimeAnnotation 注解,可以不定义任何属性。
package com.xkcoding.websocket.annotation;
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyLogTimeAnnotation {
}
2.2 步骤二:使用切面执行自定义注解逻辑
如果项目中没有依赖,则需要引入依赖,方式一,单独引入切面依赖
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.13</version>
</dependency>
或者方式二,通过springboot-aop间接引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
我项目中使用的springboot版本是 3.2.7,以下是项目中使用的父依赖,直接在pom.xml中引入方式二中的依赖即可。
<parent>
<artifactId>spring-boot-starter-parent</artifactId>
<groupId>org.springframework.boot</groupId>
<version>3.2.7</version>
</parent>
在开发中一般加上注解之后会自动执行一些逻辑,大部分实现的原理是使用切面来实现注解的逻辑的。
package com.xkcoding.websocket.aspect;
import com.xkcoding.websocket.annotation.LogTimeAnnotation;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
@Slf4j
@Aspect
@Component
public class LogTimeAnnotationAspect {
@Around("@annotation(myLogTimeAnnotation)")
public Object definedLog(ProceedingJoinPoint joinPoint, MyLogTimeAnnotation myLogTimeAnnotation) throws Throwable {
long startMillis = System.currentTimeMillis();
Signature signature = joinPoint.getSignature();
log.info("{} 方法开始时间:{}", signature, startMillis);
Object proceed = joinPoint.proceed();
long end = System.currentTimeMillis();
log.info("{} 方法结束时间:{},总耗时 = {}ms", joinPoint.getSignature(), end, end - startMillis);
return proceed;
}
}
简单说一下各个注解代表什么含义:
- @Aspect:作用是把当前类标识为一个切面供容器读取 ,也就是加上这个注解,spring才知道你这是一个切面类,用于处理切点的逻辑的。
- @Pointcut:切入点,@Pointcut切点表达式非常丰富,可以将 方法(method)、类(class)、接口(interface)、包(package) 等作为切入点,非常灵活,常用的有@annotation、@within、execution等方式,上面的示例使用的是@annotation方式,意思就是说被Spring扫描到方法上带有@annotation中的注解 就会执行切面通知。
- @Before:该注解标注的方法在业务模块代码执行之前执行,其不能阻止业务模块的执行,除非抛出异常;
- @AfterReturning:该注解标注的方法在业务模块代码执行之后执行;
- @AfterThrowing:该注解标注的方法在业务模块抛出指定异常后执行;
@After:该注解标注的方法在所有的 Advice 执行完成后执行,无论业务模块是否抛出异常,类似于 finally 的作用; - @Around:该注解功能最为强大,其所标注的方法用于编写包裹业务模块执行的代码,通知的第一个参数必须是 ProceedingJoinPoint 类型。在通知体内,调用 ProceedingJoinPoint 的 proceed() 方法使得连接点方法执行如果不调用 proceed () 方法,连接点方法则不会执行。无论是调用前逻辑还是调用后逻辑,都可以在该方法中编写,甚至其可以根据一定的条件而阻断业务模块的调用; 如果切面中使用了@Around 注解,如果不调用 ProceedingJoinPoint 的 proceed() 方法的话,那么 @Before 和 @After 直接标注的方法也不会被触发。@Around 注解标注的方法,在 ProceedingJoinPoint 的 proceed() 方法 前的逻辑是比@Before的逻辑还要靠前, 在proceed() 方法之后的逻辑比 @After 的逻辑还要靠后。
- Joint Point:JointPoint是程序运行过程中可识别的点,这个点可以用来作为AOP切入点。JointPoint对象则包含了和切入相关的很多信息。比如切入点的对象,方法,属性等。我们可以通过反射的方式获取这些点的状态和信息,用于追踪tracing和记录logging应用信息。
三、注解使用
与SpringBoot源注解使用一模一样,无任何区别。
代码示例:
package com.xkcoding.websocket.controller;
import com.xkcoding.websocket.annotation.LogTimeAnnotation;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping(value = "/device")
public class DeviceController {
@LogTimeAnnotation
@GetMapping(value = "/page")
public Object queryPage() throws InterruptedException {
// todo 业务逻辑
Thread.sleep(1503);
return null;
}
}
结果如下图所示:
四、可能出现的问题
注解不生效:原因是切面没有被SpringBoot扫描到从而导致切面没有被注入到Spring容器中。
1)对于SpringBoot单体项目
在启动类入口类上添加包扫描,一定要扫描到切面所在的包即可
2)对于微服务项目来说
可以使切面被自动注入到容器中,当然这只是其中一种方式,不仅仅局限于此种方式:
示例:
可以在resources目录下创建 META-INFO.spring 目录,然后在 META-INFO.spring 这个目录下创建自动导包配置项文件,名字是:
org.springframework.boot.autoconfigure.AutoConfiguration.imports
文件
然后在文件中配置
com.xkcoding.common.security.aspect.LogTimeAnnotationAspect
这样在程序启动时就可以注入 LogTimeAnnotationAspect 这个 bean 到容器中了。
好了,今天分享到这里。希望你喜欢这次的探索之旅!不要忘记 “点赞” 和 “关注” 哦,我们下次见!🎈
本文完结!
祝各位大佬和小伙伴身体健康,万事如意,发财暴富,扫下方二维码与我一起交流!!!
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:jacktools123@163.com进行投诉反馈,一经查实,立即删除!
标签: