一、注解的定义:
注解(Annotation) 为向代码中添加数据提供了一种形式化的方法。
Annatation(注解)是一个接口,程序可以通过反射来获取指定程序中元素的 Annotation
对象,然后通过该 Annotation 对象来获取注解中的元数据信息
常见的作用有以下几种:
1.生成文档。这是最常见的,也是java 最早提供的注解。常用的有@see @param @return 等;
2.跟踪代码依赖性,实现替代配置文件功能。比较常见的是spring 2.5 开始的基于注解配置。作用就是减少配置。现在的框架基本都使用了这种配置来减少配置文件的数量;
3.在编译时进行格式检查。如@Override放在方法前,如果你这个方法并不是覆盖了超类方法,则编译时就能检查出;
二、注解的分类
1)按照运行机制划分:
【源码注解→编译时注解→运行时注解】
源码注解:只在源码中存在,编译成.class文件就不存在了。
编译时注解:在源码和.class文件中都存在。像前面的@Override、@Deprecated、@SuppressWarnings,他们都属于编译时注解。
运行时注解:在运行阶段还起作用,甚至会影响运行逻辑的注解。像@Autowired自动注入的这样一种注解就属于运行时注解,它会在程序运行的时候把你的成员变量自动的注入进来。
2)按照来源划分:
【来自JDK的注解——来自第三方的注解——自定义注解】
3)元注解:
元注解是给注解进行注解,可以理解为注解的注解就是元注解。
三、自定义注解
我们分四步来解析自定义注解:
自定义注解的语法要求:
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Description {
String desc();
String author();
int age() default 18;
}
首先我们要明确这不是一个接口,它是使用 @interface 关键字定义的一个注解。
然后我们看下面的几个方法,String desc();虽然它很类似于接口里面的方法,其实它在注解里面只是一个成员变量(成员以无参无异常的方式声明),int age() default 18;(成员变量可以用default指定一个默认值的)。
最后我们要知道:
①、成员类型是受限制的,合法的类型包括基本的数据类型以及String、Class、Annotation、Enumeration等。
②、如果注解只有一个成员,则成员名必须取名为value(),在使用时可以忽略成员名和赋值号(=)。
③、注解类可以没有成员,没有成员的注解称为标识注解。元注解:
有没有发现上面那段代码有一个没有说呢?没错,它们就是我们所说的元注解:
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
第一行:@Target
定义注解作用域(可能出现在Java程序中的语法位置),可以是成员变量、方法、构造器、类、包、接口等,包括:
METHOD(方法声明),CONSTRUCTOR(构造方法声明),FIELD(字段声明),LOCAL VARIABLE(局部变量声明),METHOD(方法声明),PACKAGE(包声明),PARAMETER(参数声明),TYPE(类接口)
第二行:@Retention
定义注解保留时间,SOURCE(只在源码显示,编译时丢弃),CLASS(编译时记录到class中,运行时忽略),RUNTIME(运行时存在,可以通过反射读取)
第三行:@Inherited
标记性注解,阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited 修饰的 annotation 类型被用于一个 class,则这个 annotation 将被用于该class 的子类。
第四行:@Documented
使用@Documented标注了,在生成javadoc的时候就会把@Documented注解给显示出来
使用自定义注解:
使用注解的语法:
@<注解名>(<成员名1>=<成员值1>,<成员名1>=<成员值1>,…)
案例:
@Description(desc="i am Color",author="boy",age=18)
public String Color() {
return "red";
}
这里的Description是我们刚才在自定义注解语法要求里面定义的注解,然后我们可以给它的每一个成员变量赋值,注意数据类型。值得注意的是,因为我们前面定义的作用域是在方法和类接口上,所以这个注解在Color()方法上使用是没问题的。
解析注解
概念:
通过反射获取类 、方法、成员变量运行时注解信息,从而实现动态控制程序运行的逻辑。
准备工作:
spring boot日志注解的应用:
@Target({ ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface LogAnnotation {
String module() default "";
}
使用注解:
//@LogAnnotation 通过自定义注解添加数据
@LogAnnotation(module = "上传头像")
@ResponseBody
@PostMapping("/douploadavatar")
public void douploadavatar(String headimgurl) {
User user = UserUtil.getCurrentUser();
userMapper.changeHeadimgurl(user.getId(),headimgurl);
user.setHeadimgurl(headimgurl);
//输出日志
log.debug("{}修改了头像", user.getUsername());
}
通过定义切面的方式来定义注解处理器
@Aspect
@Component
public class LogAdvice {
@Autowired
private SysLogService logService;
//value传递的值是符合切入的条件,可以使用@Pointcut显式声明,也可以直接使用@annotation声明
@Around(value = "@annotation(com.web.admin.annotation.LogAnnotation)")
public Object logSave(ProceedingJoinPoint joinPoint) throws Throwable {
SysLogs sysLogs = new SysLogs();
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
String module = null;
//通过解释注解获取数据(反射)
LogAnnotation logAnnotation = methodSignature.getMethod().getDeclaredAnnotation(LogAnnotation.class);
module = logAnnotation.module();
if (StringUtils.isEmpty(module)) {
throw new RuntimeException("没有指定日志module");
}
sysLogs.setModule(module);
try {
Object object = joinPoint.proceed();
sysLogs.setFlag(true);
logService.save(sysLogs);
return object;
} catch (Exception e) {
sysLogs.setFlag(false);
sysLogs.setRemark(e.getMessage());
logService.save(sysLogs);
throw e;
}
}
}
定义和使用注解
import java.lang.annotation.*;
import java.lang.reflect.Field;
/**
* @Author: thunder
* @Date: 2020/8/4 10:12
*/
//注解(Annotation) 为向代码中添加数据提供了一种形式化的方法。
//1、定义注解
//元注解定义作用范围(定义可能出现在Java程序中的语法位置),可以是成员变量、方法、构造器、类、包、接口等。
@Target(ElementType.FIELD)
//定义注解的保留时间,SOURCE(只在源码显示,编译时丢弃),CLASS(编译时记录到class中,运行时忽略),RUNTIME(运行时存在,可以通过反射读取)
@Retention(RetentionPolicy.RUNTIME)
//标记性注解,阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited 修饰的 annotation 类型被用于一个 class,则这个 annotation 将被用于该class 的子类
//@Inherited
//使用@Documented标注了,在生成javadoc的时候就会把@Documented注解给显示出来
@Documented
@interface FruitProvider {
/**供应商编号*/
public int id() default -1;
/*** 供应商名称*/
public String name() default "";
/** * 供应商地址*/
public String address() default "";
}
//2、使用注解
class Apple {
//提供了一种向程序中添加数据的形式化方法
//使用注解,作用于成员变量
@FruitProvider(id = 1, name = "陕西红富士集团", address = "陕西省西安市延安路")
private String appleProvider;
public void setAppleProvider(String appleProvider) {
this.appleProvider = appleProvider;
}
public String getAppleProvider() {
return appleProvider;
}
}
//3、注解处理器
public class FruitInfoUtil {
public static void getFruitInfo(Class<?> clazz) {
String strFruitProvicer = "供应商信息:";
//通过反射获取类所有成员变量
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
//获取使用了FruitProvider注解的成员变量
if (field.isAnnotationPresent(FruitProvider.class)) {
//获取Annotation对象
FruitProvider fruitProvider = (FruitProvider) field.getAnnotation(FruitProvider.class);
//提取注解对象元数据
strFruitProvicer = " 供应商编号:" + fruitProvider.id() + " 供应商名称:" + fruitProvider.name() + " 供应商地址:"+ fruitProvider.address();
System.out.println(strFruitProvicer);
}
}
}
public static void main(String[] args) {
FruitInfoUtil.getFruitInfo(Apple.class);
}
}