文章目录
做Java开发这么久,我们每天都在跟注解打交道。
写SpringBoot接口要加@RestController,做配置要加@Configuration,注入对象用@Autowired,简单点说,现在的Java开发,离开注解根本写不动代码。
很多小伙伴天天用别人写好的注解,却一直搞不懂自定义注解怎么玩,总觉得这东西高深、难理解,好像只有框架源码里才会用到。
其实完全不是这样。
自定义注解就是咱们程序员的代码小工具 ,核心作用就一个:给类、方法、参数打标签,然后通过程序识别这个标签,自动帮我们干活。
今天由浅入深从零讲起,最后带大家在SpringBoot里手写一个真实项目能用的自定义操作日志注解,看完就能直接用到自己项目里。
一、先搞懂:注解到底是个啥?
不用记官方定义,就记住一句大白话:
注解就是贴在代码上的特殊标签,本身不干活,只做标记,等着别人来识别、执行对应的逻辑。
举个日常开发的例子:
你在Controller的方法上加个@GetMapping\(\&\#34;/list\&\#34;\),这个注解本身不会处理任何请求,它就只是一个标记。
真正干活的是SpringMVC框架:启动时扫描所有带这个注解的方法,记录好请求路径和对应方法的关系,前端发起请求后,SpringMVC根据这个标记匹配接口,执行对应的业务代码。
所有注解的底层逻辑都是这三步:
-
定义注解:造出来一个专属标签
-
使用注解:把标签贴在类、方法、字段上
-
解析注解:写代码识别这个标签,执行具体业务逻辑
原生自带的注解是框架帮我们定义和解析好了,自定义注解就是咱们自己动手,造标签、自己写解析逻辑而已。
二、自定义注解核心基础:4个元注解必须懂
想要自己定义注解,不用写复杂逻辑,只需要用@interface关键字就行。
但写自定义注解的时候,必须搭配元注解 使用。元注解就是修饰注解的注解,用来规定咱们自定义的标签能用在哪、能活多久。
一共就4个,日常开发只用记最常用的2个就够,简单易懂不绕弯。
1、@Target:注解能贴在哪?
限定咱们自定义的标签,只能标记在哪些代码位置。
常用取值就这几个:
-
ElementType.METHOD:只能贴在方法上(最常用,比如日志、权限注解)
-
ElementType.TYPE:只能贴在类、接口、枚举上(比如全局配置注解)
-
ElementType.FIELD:只能贴在成员变量上(比如字段校验注解)
-
ElementType.PARAMETER:只能贴在方法参数上
2、@Retention:注解能活多久?
限定注解的生命周期,核心就看这一个:
-
RetentionPolicy.RUNTIME :运行时一直有效,程序跑起来也能通过反射获取到注解信息(自定义业务注解必选)
-
另外两个SOURCE、CLASS基本不用,不用额外记忆
3、@Documented、@Inherited(了解即可)
-
@Documented:生成接口文档时,带上这个注解信息
-
@Inherited:子类能继承父类的注解
日常开发自定义注解,默认加上前两个核心元注解就行,简单不出错。
三、手写第一个简单自定义注解(零基础入门)
先不搞复杂业务,咱们先写一个最简单的自定义注解,看懂结构,摸清套路。
语法格式记住:注解里可以定义属性,写法跟接口方法一样,还能给默认值。
java
import java.lang.annotation.*;
// 自定义注解:标记接口操作类型
@Target(ElementType.METHOD) // 仅作用在方法上
@Retention(RetentionPolicy.RUNTIME) // 运行时保留,允许反射获取
public @interface MyOperationLog {
// 注解属性:操作名称,默认值为空
String operationName() default "";
}
就这么几行,一个自定义注解就造好了。
使用起来也超级简单,直接贴在方法上,给属性赋值就行:
java
// 给新增用户接口打上标记,标注操作类型是"新增用户"
@MyOperationLog(operationName = "新增系统用户")
public void addUser(User user){
// 核心新增业务逻辑
userService.save(user);
}
到这里,完成了定义注解、使用注解两步。
现在这个注解只是个摆设,贴上去没任何效果,因为还差最关键的一步:解析注解,执行具体逻辑。
四、SpringBoot实战核心:自定义注解+AOP实现自动日志
实际开发中,自定义注解90%的场景都是搭配AOP切面使用。
为什么用AOP?不用写重复代码,不用改原有业务逻辑,通过切面拦截加了注解的方法,自动执行前置、后置逻辑,完美契合解耦思想。
咱们做一个真实项目刚需功能:接口调用自动记录操作日志,无需每个接口手动写日志代码。
实战整体步骤(四步走完)
-
SpringBoot项目引入AOP依赖
-
创建自定义操作日志注解
-
创建AOP切面类,拦截注解、解析日志信息
-
Controller接口使用注解,测试效果
第一步:pom.xml引入AOP依赖
SpringBoot项目必须先引入切面依赖,否则AOP功能不生效:
xml
<!-- Spring AOP 核心依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
第二步:创建自定义注解@SysOperationLog
专门用于标记需要记录操作日志的接口方法:
java
import java.lang.annotation.*;
/**
* 自定义接口操作日志注解
* 标记需要自动记录操作日志的业务方法
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SysOperationLog {
/**
* 业务操作类型(比如新增、修改、删除、查询)
*/
String businessType() default "";
/**
* 接口功能描述
*/
String description() default "";
}
第三步:核心!创建AOP切面解析注解
这一步是关键:切面拦截所有加了@SysOperationLog注解的方法,方法执行前后,自动打印操作信息、请求参数、耗时、操作人等日志。
java
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.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
/**
* 操作日志切面处理类
* 拦截自定义注解,自动记录操作日志
*/
@Aspect // 标识为切面类
@Component // 交给Spring容器管理
public class OperationLogAspect {
// 切入点:所有加了@SysOperationLog注解的方法
@Pointcut("@annotation(com.example.demo.annotation.SysOperationLog)")
public void logPointCut() {
}
// 环绕通知:方法执行前后都能拦截处理
@Around("logPointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
// 1、获取方法执行开始时间
long startTime = System.currentTimeMillis();
// 2、获取目标方法和注解上的自定义信息
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
SysOperationLog operationLog = method.getAnnotation(SysOperationLog.class);
// 3、执行目标接口的业务方法
Object result = point.proceed();
// 4、计算方法执行耗时
long costTime = System.currentTimeMillis() - startTime;
// 5、打印操作日志(实际项目可存入数据库)
System.out.println("===== 接口操作日志开始 =====");
System.out.println("操作业务类型:" + operationLog.businessType());
System.out.println("接口功能描述:" + operationLog.description());
System.out.println("请求方法名称:" + method.getDeclaringClass().getName() + "." + method.getName());
System.out.println("方法执行耗时:" + costTime + "ms");
System.out.println("===== 接口操作日志结束 =====\n");
return result;
}
}
第四步:Controller接口使用注解测试
咱们写两个简单的测试接口,直接在方法上加上自定义注解,不用手动写任何日志打印代码:
java
import com.example.demo.annotation.SysOperationLog;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class UserController {
// 查询用户列表接口,加自定义日志注解
@GetMapping("/user/list")
@SysOperationLog(businessType = "查询操作", description = "查询系统所有用户列表")
public String getUserList(){
// 模拟业务执行
return "用户列表查询成功,数据返回完毕";
}
// 新增用户接口,加自定义日志注解
@GetMapping("/user/add")
@SysOperationLog(businessType = "新增操作", description = "新增后台系统管理用户")
public String addUser(){
// 模拟业务执行
return "新增用户成功";
}
}
五、实际开发中,自定义注解常用场景
学会这个套路,工作中很多重复繁杂的通用功能,都可以用自定义注解+AOP搞定,除了操作日志,这些场景高频常用:
-
接口权限校验注解:@RequiresPermission,加在接口上,自动校验用户是否有访问权限,无权限直接拦截报错
-
接口限流防刷注解:@RequestLimit,限制同一个用户几秒内只能访问一次接口,防止接口被恶意刷爆
-
字段数据脱敏注解:@DataMask,标记手机号、身份证字段,返回前端自动脱敏隐藏中间数字
-
缓存自动刷新注解:@CacheRefresh,操作数据后自动清空对应缓存,不用手动写缓存代码
所有通用、重复、跟核心业务无关的代码,全都可以抽成自定义注解+AOP,让业务代码只专注做业务。
六、最后总结(记住这几句就够了)
-
注解本质就是代码标签,本身不干活,只做标记,靠反射或AOP解析执行逻辑
-
自定义注解核心就两步:元注解定义标签 + AOP切面写解析逻辑
-
SpringBoot开发自定义注解,必配
@Target、@Retention\(RetentionPolicy\.RUNTIME\) -
核心价值:解耦通用逻辑、消灭重复代码、让代码更简洁优雅