Java注解
在Java中注解 其实就是写在接口、类、属性、方法上的一个标签,或者说是一个特殊形式的注释,与普通的注释不同的是:普通注释只是一个注释,而注解在代码运行时是可以被反射读取并进行相应的操作,而如果没有使用反射或者其他检查,那么注解是没有任何真实作用的,也不会影响到程序的正常运行结果。
注解的作用
- 作为特定标记,用于告诉编译器一些信息
- 编译时动态处理,如动态生成代码
- 运行时动态处理,作为额外信息的载体,如获取注解信息
注解的分类
注解通常分为三类:
- 元注解:Java内置的注解,标明该注解的使用范围、生命周期等
- 标准注解:Java提供的基础注解,标明过期的元素/标明是复写父类方法的方法/标明抑制警告
- 自定义注解:第三方定义的注解,含义和功能由第三方来定义和实现
元注解
用于定义注解的注解,通常用于注解的定义上,标明该注解的使用范围、生效范围等 。元注解主要包含以下五种: @Retention
、@Target
、@Documented
、@Inherited
、@Repeatable
@Retention
作用:声明注解的保留周期(生命周期)
该注解的不同参数及效果:
@Retention(RetentionPolicy.SOURCE)
:只作用在源码阶段,字节码文件中不存在@Retention(RetentionPolicy.CLASS)
:保留到字节码文件阶段,运行阶段不存在@Retention(RetentionPolicy.RUNTIME)
:一直保留到运行阶段
@Target
作用:声明被修饰的注解只能在哪些位置使用
该注解的不同参数及效果:
@Target(ElementType.TYPE)
:被修饰的注解只能在类、接口上使用@Target(ElementType.FIELD)
:被修饰的注解只能在成员变量上使用@Target(ElementType.METHOD)
:被修饰的注解只能在成员方法上使用@Target(ElementType.PARAMETER)
:被修饰的注解只能在方法参数上使用@Target(ElementType.CONSTRUCTOR)
:被修饰的注解只能在构造器上使用@Target(ElementType.LOCAL_VARIABLE)
:被修饰的注解只能在局部变量上使用
@Documented
作用:是否在生成的Javadoc文档中体现,被标注该注解后,生成的javadoc中,会包含该注解
@Inherited
作用:是否可以被标注类的子类继承。被@Inherited
修饰的注解是具有继承性的,在自定义的注解标注到某个类时,该类的子类会继承这个自定义注解。
@Repeatable
作用:是否可以重复标注
标准注解
Java 中自带且常用的几种标准注解有 @Override
、@Deprecated
、@SuppresWarninngs
、@SafeVarargs
@Override
:是一个标记类型注解,用于提示子类要复写父类中被 @Override
修饰的方法,它说明了被标注的方法重载了父类的方法,起到了断言的作用
@Deprecated
:也是一个标记类型注解,用于标记过时的元素。比如如果开发人员正在调用一个过时的方法、类或成员变量时,可以用该注解进行标注
@SuppressWarnings
:可以阻止警告的提示
@SafeVarargs
:是一个参数安全类型注解,它的目的是提醒开发人员,不要用参数做一些不安全的操作
自定义注解
自定义注解的基本格式(修饰符、默认值可缺失):
java
public @interface 注解名 {
修饰符 返回值 属性名() 默认值;
修饰符 返回值 属性名() 默认值;
}
定义一个简单的注解示例:
java
// 保留至运行时
@Retention(RetentionPolicy.RUNTIME)
// 可以加在方法或者类上
@Target(value = {ElementType.TYPE,ElementType.METHOD})
public @interface RequestMapping {
public String method() default "GET";
String path();
}
注解的本质就是一个接口 ,继承了java.lang.annotation.Annotation
自定义注解在Spring boot项目中的应用实例
在Spring boot框架中,注解给项目开发带来了巨大的方便,使用注解的优势主要有:
- 采用纯Java代码,不在需要配置繁杂的xml文件
- 在配置中也可享受面向对象带来的好处
- 类型安全对重构可以提供良好的支持
- 减少复杂配置文件的同时亦能享受到Spring IoC容器提供的功能
在Spring boot框架中,同样可以自定义注解并用于优化项目开发,实例如下:
问题描述
在项目业务表中存在以下四个公共字段:
字段名 | 含义 | 项目涉及的操作类型 |
---|---|---|
create_time | 创建时间 | insert |
create_user | 创建人id | insert |
update_time | 修改时间 | insert/update |
update_user | 修改人id | insert/update |
当创建或修改相关信息时,这几个字段也会做相应的set方法赋值,同时存在着代码冗余、不便于后期维护的问题,为解决这个问题,可以加一个公共字段自动填充的功能
实现思路
公共字段自动填充功能的实现思路:
- 自定义注解
@AutoFill
,用于标识需要进行公共字段自动填充的方法 - 自定义切面类
AutoFillAspect
,统一拦截加入了@AutoFill
注解的方法,通过反射为公共字段赋值 - 在Mapper的方法上加上
@AutoFill
注解
用到的知识点:枚举 、注解 、AOP 、反射
自定义注解@AutoFill
首先定义一个枚举类,用于作为@AutoFill
的参数即数据库操作类型:
java
public enum OperationType {
// 更新操作
UPDATE,
//插入操作
INSERT
}
然后自定义注解@AutoFill
:
java
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoFill {
//数据库操作类型:UPDATE INSERT,OperationType在枚举类中定义
//写法:@AutoFill(value = OperationType.UPDATE)、@AutoFill(value = OperationType.INSERT)
OperationType value();
}
自定义切面类AutoFillAspect
java
@Aspect
@Component
@Slf4j
public class AutoFillAspect {
//切入点,切入点在com.sky.mapper.*.*(..)且有注解@AutoFill下的方法执行
@Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill)")
public void autoFillPointCut(){}
//前置通知,在通知中进行公共字段的赋值
@Before("autoFillPointCut()")
public void autoFill(JoinPoint joinPoint) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
log.info("开始进行公共字段自动填充");
//获取到当前被拦截的方法上的数据库操作类型(UPDATE/INSERT)
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){
//为四个公共字段赋值
Method setCreateTime = entity.getClass().getDeclaredMethod("setCreateTime", LocalDateTime.class);
Method setCreateUser = entity.getClass().getDeclaredMethod("setCreateUser", Long.class);
Method setUpdateTime = entity.getClass().getDeclaredMethod("setUpdateTime", LocalDateTime.class);
Method setUpdateUser = entity.getClass().getDeclaredMethod("setUpdateUser", Long.class);
//通过反射为对象赋值
setCreateTime.invoke(entity,now);
setCreateUser.invoke(entity,currentId);
setUpdateTime.invoke(entity,now);
setUpdateUser.invoke(entity,currentId);
}else if(operationType == OperationType.UPDATE){
//为两个公共字段赋值
Method setUpdateTime = entity.getClass().getDeclaredMethod("setUpdateTime", LocalDateTime.class);
Method setUpdateUser = entity.getClass().getDeclaredMethod("setUpdateUser", Long.class);
//通过反射为对象赋值
setUpdateTime.invoke(entity,now);
setUpdateUser.invoke(entity,currentId);
}
}
}
在相关方法上加上@AutoFill
java
@AutoFill(value = OperationType.INSERT)
void insert(Employee employee);
@AutoFill(value = OperationType.UPDATE)
void update(Employee employee);