📚博客主页:代码探秘者
✨专栏:《JavaSe》 其他更新ing…
❤️感谢大家点赞👍🏻收藏⭐评论✍🏻,您的三连就是我持续更新的动力❤️
🙏作者水平有限,欢迎各位大佬指点,相互学习进步!
文章目录
- 一、Java 注解基础
- 1.1 什么是注解(Annotation)?
- 1.2 注解的分类
- 二、常用内置注解
- 2.1 编译器相关注解(来自 `java.lang`)
- 2.2 元注解(用于定义注解)
- 三、自定义注解基础
- 3.1 自定义注解的基本结构
- 3.2 使用自定义注解
- 3.3 注解解析(通过反射读取)
- 四、注解的常见应用场景(了解)
- 4.1 Spring 框架注解
- 4.2 JUnit 单元测试注解
- 4.3 Lombok 注解(简化开发)
- 五、实战:自定义注解
- 5.1 判断手机号格式-@IsMobile
- 5.2 正则表达式:校验
- 5.3 校验类实现
- 5.4 使用示例
- 六、实战:结合 AOP 使用注解实现功能(了解)
- 6. 1 日志切面-@LogExecutionTime
- 6.2 自定义注解 @AutoFill+AOP
- 6.2.1 步骤1
- 6.2.3 步骤2
- 6.2.3 步骤3
本文章是对 Java 注解(Annotation)相关内容的系统总结,包括基础知识、常见内置注解、自定义注解、注解的运行机制,以及在实际开发中的常见应用场景。
一、Java 注解基础
1.1 什么是注解(Annotation)?
注解是 Java 5 引入的一种元数据机制,用于修饰代码(类、方法、字段、参数等),不会直接影响代码运行逻辑,但可以被编译器或运行时工具读取,并执行相关处理。
1.2 注解的分类
按生命周期可分为:
- SOURCE:只在源码中保留,编译后丢弃,如
@Override
- CLASS:保留到 class 文件中,运行时不可见(默认)
- RUNTIME:运行时可通过反射获取,常用于框架开发(如 Spring)
二、常用内置注解
2.1 编译器相关注解(来自 java.lang
)
注解 | 作用说明 |
---|---|
@Override | 标明方法重写父类方法 |
@Deprecated | 表示方法/类已过时 |
@SuppressWarnings | 抑制编译器警告,例如 unchecked |
2.2 元注解(用于定义注解)
注解 | 含义 |
---|---|
@Target | 指定注解可修饰的目标(类、方法、字段等) |
@Retention | 指定注解的保留策略(SOURCE、CLASS、RUNTIME) |
@Documented | 指定注解是否包含在 Javadoc 中 |
@Inherited | 指定子类是否能继承父类的注解 |
三、自定义注解基础
3.1 自定义注解的基本结构
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyLog {
String value() default "default";
}
3.2 使用自定义注解
public class Service {
@MyLog("执行业务方法")
public void doSomething() {
System.out.println("正在处理...");
}
}
3.3 注解解析(通过反射读取)
Method method = Service.class.getMethod("doSomething");
MyLog log = method.getAnnotation(MyLog.class);
if (log != null) {
System.out.println("注解值:" + log.value());
}
四、注解的常见应用场景(了解)
4.1 Spring 框架注解
注解 | 用途 |
---|---|
@Component | 注册为 Spring Bean |
@Service | 标识服务层组件 |
@Autowired | 自动注入依赖 |
@RequestMapping | 映射 Web 请求 |
@RestController | 标记为 REST 控制器 |
@Transactional | 事务管理 |
4.2 JUnit 单元测试注解
注解 | 用途 |
---|---|
@Test | 表示一个测试方法 |
@Before | 每个测试前执行 |
@After | 每个测试后执行 |
@BeforeClass | 所有测试前执行一次 |
4.3 Lombok 注解(简化开发)
注解 | 功能 |
---|---|
@Getter | 自动生成 getter 方法 |
@Setter | 自动生成 setter 方法 |
@Data | 包括 getter、setter、toString 等 |
@Builder | 生成建造者模式代码 |
五、实战:自定义注解
5.1 判断手机号格式-@IsMobile
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Documented //注解规则
@Constraint(validatedBy = {IsMobileValidator.class})
public @interface IsMobile {
String message() default "手机号码格式错误";
boolean required() default true;
Class<?>[] groups() default { };//默认参数
Class<? extends Payload>[] payload() default { };//默认参数
}
✅ 解析
内容 | 含义 |
---|---|
@IsMobile | 自定义注解名称,用于手机号验证 |
@Target(...) | 注解可以放的位置 |
@Retention(RUNTIME) | 保留到运行时 |
@Constraint(...) | 指定由哪个类执行校验逻辑 |
message() | 提示信息 |
required() | 是否必须有值(用于支持“非必填但格式正确”) |
groups() /payload() | 扩展字段,兼容 Bean Validation 框架 |
5.2 正则表达式:校验
* ValidatorUtil: 完成一些校验工作,比如手机号码格式是否正确..
* 提醒:java基础时,我们讲过正则表达式的使用,可以回顾.
*/
public class ValidatorUtil {
//校验手机号码的正则表达式
//13300000000 合格
//11000000000 不合格
private static final Pattern mobile_pattern = Pattern.compile("^[1][3-9][0-9]{9}$");
//编写方法, 如果满足规则,返回T, 否则返回F
public static boolean isMobile(String mobile) {
if(!StringUtils.hasText(mobile)) {
return false;
}
//进行正则表达式校验-java基础讲过
Matcher matcher = mobile_pattern.matcher(mobile);
return matcher.matches();
}
//测试一下校验方法
@Test
public void t1() {
String mobile = "13300000009";
System.out.println(isMobile(mobile));//
}
}
5.3 校验类实现
//我们自拟定注解 IsMobile 的校验规则, 可以自己根据业务需求来编写..
public class IsMobileValidator //注解
implements ConstraintValidator<IsMobile, String> {
private boolean required = false;
//初始化方法
@Override
public void initialize(IsMobile constraintAnnotation) {
//初始化
required = constraintAnnotation.required();
}
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
//必填
if (required) {
return ValidatorUtil.isMobile(value); //这里是调用正则表达式
} else {//非必填
if (!StringUtils.hasText(value)) {
return true;
} else {
return ValidatorUtil.isMobile(value);
}
}
}
}
5.4 使用示例
public class UserDTO {
@IsMobile(message = "手机号格式不正确", required = true)
private String phone;
// 其他字段...
}
六、实战:结合 AOP 使用注解实现功能(了解)
6. 1 日志切面-@LogExecutionTime
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogExecutionTime {}
@Aspect
@Component
public class LoggingAspect {
@Around("@annotation(LogExecutionTime)")
public Object logTime(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
Object proceed = joinPoint.proceed();
long duration = System.currentTimeMillis() - start;
System.out.println("执行耗时:" + duration + "ms");
return proceed;
}
}
6.2 自定义注解 @AutoFill+AOP
@AutoFill 主要是进行公共字段填充
- 下面案例来自苍穹外卖项目,提供学习了解
6.2.1 步骤1
/**
* 自定义注解,用于标识某个方法需要进行功能字段自动填充处理
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoFill {
//数据库操作类型:UPDATE INSERT
OperationType value();
}
其中OperationType已在sky-common模块中定义
/**
* 数据库操作类型
*/
public enum OperationType {
/**
* 更新操作
*/
UPDATE,
/**
* 插入操作
*/
INSERT
}
6.2.3 步骤2
自定义切面 AutoFillAspect
在sky-server模块,创建com.sky.aspect包。
package com.sky.aspect;
/**
* 自定义切面,实现公共字段自动填充处理逻辑
*/
@Aspect
@Component
@Slf4j
public class AutoFillAspect {
/**
* 切入点
*/
@Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill)")
public void autoFillPointCut(){}
/**
* 前置通知,在通知中进行公共字段的赋值
*/
@Before("autoFillPointCut()")
public void autoFill(JoinPoint joinPoint){
/重要
//可先进行调试,是否能进入该方法 提前在mapper方法添加AutoFill注解
log.info("开始进行公共字段自动填充...");
}
}
完善自定义切面 AutoFillAspect 的 autoFill 方法
/**
* 自定义切面,实现公共字段自动填充处理逻辑
*/
@Aspect
@Component
@Slf4j
public class AutoFillAspect {
/**
* 切入点
*/
@Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill)")
public void autoFillPointCut(){}
/**
* 前置通知,在通知中进行公共字段的赋值
*/
@Before("autoFillPointCut()")
public void autoFill(JoinPoint joinPoint){
log.info("开始进行公共字段自动填充...");
//获取到当前被拦截的方法上的数据库操作类型
MethodSignature signature = (MethodSignature) joinPoint.getSignature();//方法签名对象
AutoFill autoFill = signature.getMethod().getAnnotation(AutoFill.class);//获得方法上的注解对象
OperationType operationType = autoFill.value();//获得数据库操作类型
//获取到当前被拦截的方法的参数--实体对象
Object[] args = joinPoint.getArgs();
if(args == null || args.length == 0){
return;
}
Object entity = args[0];
//准备赋值的数据
LocalDateTime now = LocalDateTime.now();
Long currentId = BaseContext.getCurrentId();
//根据当前不同的操作类型,为对应的属性通过反射来赋值
if(operationType == OperationType.INSERT){
//为4个公共字段赋值
try {
Method setCreateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_TIME, LocalDateTime.class);
Method setCreateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_USER, Long.class);
Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);
//通过反射为对象属性赋值
setCreateTime.invoke(entity,now);
setCreateUser.invoke(entity,currentId);
setUpdateTime.invoke(entity,now);
setUpdateUser.invoke(entity,currentId);
} catch (Exception e) {
e.printStackTrace();
}
}else if(operationType == OperationType.UPDATE){
//为2个公共字段赋值
try {
Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);
//通过反射为对象属性赋值
setUpdateTime.invoke(entity,now);
setUpdateUser.invoke(entity,currentId);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
6.2.3 步骤3
在Mapper接口的方法上加入 AutoFill 注解
以CategoryMapper为例,分别在新增和修改方法添加@AutoFill()注解,也需要EmployeeMapper做相同操作
package com.sky.mapper;
@Mapper
public interface CategoryMapper {
/**
* 插入数据
* @param category
*/
@Insert("insert into category(type, name, sort, status, create_time, update_time, create_user, update_user)" +
" VALUES" +
" (#{type}, #{name}, #{sort}, #{status}, #{createTime}, #{updateTime}, #{createUser}, #{updateUser})")
@AutoFill(value = OperationType.INSERT)
void insert(Category category);
/**
* 根据id修改分类
* @param category
*/
@AutoFill(value = OperationType.UPDATE)
void update(Category category);
}
同时,将业务层为公共字段赋值的代码注释掉。
1). 将员工管理的新增和编辑方法中的公共字段赋值的代码注释。
2). 将菜品分类管理的新增和修改方法中的公共字段赋值的代码注释。