Java注解系统学习与实战
背景
为什么要再次梳理一下java注解,显而易见,因为重要啊。也是为研究各大类开源框架做铺垫,只有弄清楚Java注解相关原理,才能看懂大部分框架底层的设计。
缘起
注解也叫做元数据,是JDK1.5版本开始引入的一个特性,用来对代码进行标记说明,可以对包、类、接口、字段、方法参数、局部变量等进行注解修饰。其本身不包含任何业务逻辑。
一般注解大类分为三种:
- JDK自带的相关注解
- 自定义的注解
- 第三方的(例如相关的框架中的注解)
注解三步走:定义、配置、解析
- 定义:定义标记
- 配置:把标记打到需要用到的代码中
- 解析:在编译器或运行时检测到标记,并进行特殊操作
元注解
什么是元注解?元注解的作用就是负责注解其他注解。元注解有以下五种:
- @Retention:指定其所修饰的注解的保留策略
- @Document:该注解是一个标记注解,用于指示一个注解将被文档化
- @Target:用来限制注解的使用范围
- @Inherited:该注解使父类的注解能被其子类继承
- @Repeatable:该注解是Java8新增的注解,用于开发重复注解
@Retention注解
用于指定被修饰的注解可以保留多长时间,即指定JVM策略在哪个时间点上删除当前注解。
目前存在以下三种策略
| 策略值 | 功能描述 |
|---|---|
| Retention.SOURCE | 注解只在源文件中保留,在编译期间删除 |
| Retention.CLASS | 注解只在编译期间存在于.class文件中,运行时JVM不可获取注解信息,该策略值也是默认值 |
| Retention.RUNTIME | 运行时JVM可以获取注解信息(反射),是最长注解持续期 |
@Document注解
@Document注解用于指定被修饰的注解可以被javadoc工具提取成文档。定义注解类时使用@Document注解进行修饰,则所有使用该注解修饰的程序元素的API文档中将会包含该注解说明。
@Target注解
@Target注解用来限制注解的使用范围,即指定被修饰的注解能用于哪些程序单元。标记注解方式如下:@Target({应用类型1, 应用类型2,...})【@Target(ElementType.FIELD)】
枚举值的介绍如下:
| 枚举值 | 功能描述 |
|---|---|
| ElementType.Type | 可以修饰类、接口、注解或枚举类型 |
| ElementType.FIELD | 可以修饰属性(成员变量),包括枚举常量 |
| ElementType.METHOD | 可以修饰方法 |
| ElementType.PAPAMETER | 可以修饰参数 |
| ElementType.CONSTRUCTOR | 可以修饰构造方法 |
| ElementType.LOCAL_VARIABLE | 可以修饰局部变量 |
| ElementType.ANNOTATION_TYPE | 可以修饰注解类 |
| ElementType.PACKAGE | 可以修饰包 |
| ElementType.TYPE_PARAMETER | JDK8之后的新特性,表示该注解能写在类型变量的声明语句中(如,泛型声明) |
| ElementType.TYPE_USE | JDK8之后的新特性,表示该注解能写在使用类型的任何语句中(例如:声明语句、泛型和强制转换语句中的类型) |
@Inherited注解
@Inherited注解指定注解具有继承性,如果某个注解使用@Inherited进行修饰,则该类使用该注解时,其子类将自动被修饰。
按照以上三步走的流程,咱们这里来举例子写代码说明一下:
(1)定义注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface InheritedExtend {
String comment();
int order() default 1;
}
(2)配置:标记打到类上
@InheritedExtend(comment ="注解继承",order = 2)
public class Base {
}
(3)解析:获取注解并解析做测试
public class InheritedDemo extends Base{
public static void main(String[] args) {
//从本类中获取父类注解信息
InheritedExtend extend = InheritedDemo.class.getAnnotation(InheritedExtend.class);
//输出InheritedExtend注解成员信息
System.out.println(extend.comment()+":"+extend.order());
//打印出InheritedDemo是否类是否具有@InheritedExtend修饰
System.out.println(InheritedDemo.class.isAnnotationPresent(InheritedExtend.class));
}
}
结果输出:
注解继承:2 true
以上结果就很好地说明了该注解的继承性质。
@Repeatable注解
@Repeatable注解是Java8新增的注解,用于开发重复注解。在Java8之前,同一个程序元素前只能使用一个相同类型的注解,如果需要在同一个元素前使用多个相同类型的注解必须通过注解容器来实现。从Java8开始,允许使用多个相同的类型注解来修饰同一个元素,前提是该类型的注解是可重复的,即在定义注解时要用 @Repeatable元注解进行修饰。
(1)定义注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(AnnolContents.class)
public @interface RepeatableAnnol {
String name() default "老猫";
int age();
}
//注解为容器,
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface AnnolContents {
//定义value成员变量,该成员变量可以接受多个@RepeatableAnnol注解
RepeatableAnnol[] value();
}
(2)注解使用以及解析
@RepeatableAnnol(name = "张三",age = 12)
@RepeatableAnnol(age = 23)
public class RepeatableAnnolDemo {
public static void main(String[] args) {
RepeatableAnnol[] repeatableAnnols = RepeatableAnnolDemo.class.getDeclaredAnnotationsByType(RepeatableAnnol.class);
for(RepeatableAnnol repeatableAnnol : repeatableAnnols){
System.out.println(repeatableAnnol.name() + "----->" + repeatableAnnol.age());
}
AnnolContents annolContents = RepeatableAnnolDemo.class.getDeclaredAnnotation(AnnolContents.class);
System.out.println(annolContents);
}
}
结果输出:
张三----->12
老猫----->23
@com.ktdaddy.annotation.repeatable.AnnolContents(value={@com.ktdaddy.annotation.repeatable.RepeatableAnnol(name="张三", age=12), @com.ktdaddy.annotation.repeatable.RepeatableAnnol(name="老猫", age=23)})
自定义注解实战应用
利用注解+springAOP实现系统日志记录,主要用于记录相关的日志到数据库,当然,老猫这里的demo只会到日志打印层面,至于数据库落库存储有兴趣的小伙伴可以进行扩展。
以下是maven依赖:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.79</version>
</dependency>
</dependencies>
注解的定义如下:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface OperateLog {
String desc() default "";
}
这个地方只是定义了一个字段,当然大家也可以进行拓展。
接下来,咱们以这个注解作为切点编写相关的切面程序。具体代码如下:
@Aspect
@Component
@Order(0)
public class OperateLogAdvice {
@Pointcut("@annotation(com.ktdaddy.annotation.OperateLog)")
public void recordLog(){
}
@Around("recordLog()")
public Object recordLogOne(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("进来了");
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method method = methodSignature.getMethod();
OperateLog operateLog = methodSignature.getMethod().getAnnotation(OperateLog.class);
String spELString = operateLog.desc();
//创建解析器
SpelExpressionParser parser = new SpelExpressionParser();
//获取表达式
Expression expression = parser.parseExpression(spELString);
//设置解析上下文(有哪些占位符,以及每种占位符的值)
EvaluationContext context = new StandardEvaluationContext();
//获取参数值
Object[] args = joinPoint.getArgs();
//获取运行时参数的名称
DefaultParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer();
String[] parameterNames = discoverer.getParameterNames(method);
for (int i = 0; i < parameterNames.length; i++) {
context.setVariable(parameterNames[i],args[i]);
}
//解析,获取替换后的结果
String result = expression.getValue(context).toString();
System.out.println(result);
return joinPoint.proceed();
}
}
关于切面这块不多做赘述,非本篇文章的重点。
接下来就可以在我们的代码层面使用相关的注解了,具体如下:
@Service
public class UserServiceImpl implements UserService {
@OperateLog(desc = "#user.desc")
public void saveUser(User user){
System.out.println("测试注解...");
}
}
关于controller层面就省略了,都是比较简单的。
通过上述切面以及注解解析,我们可以获取每次传参的参数内容,并且将相关的日志进行记录下来,当然这里面涉及到了SpEL表达式注入,相关的知识点,小伙伴们可以自行学习。
最终启动服务,并且请求之后具体的日志如下。
进来了
这是测试
测试注解...
{"age":12,"desc":"这是测试","id":1,"name":"张三","operator":"操作人"}
至此关于Java注解的回顾学习已经结束,之后咱们再去看一些底层代码的时候或许会轻松很多。
Java注解系统学习与实战的更多相关文章
- Java注解Annotation学习
学习注解Annotation的原理,这篇讲的不错:http://blog.csdn.net/lylwo317/article/details/52163304 先自定义一个运行时注解 @Target( ...
- Java注解Annotation学习笔记
一.自定义注解 1. 使用关键字 @interface 2. 默认情况下,注解可以修饰 类.方法.接口等. 3. 如下为一个基本的注解例子: //注解中的成员变量非常像定义接口 public @int ...
- Java 注解(Annoation)学习笔记
1 Junit中的@Test为例: 1.1 用注解(@Test)前 private boolean isTestMethod(Method m) { return m.getParameterType ...
- Java注解简单学习
注解(也被称作元数据)为我们在代码中添加信息提供了一种形式化的方法,使我们在稍后某个时刻可以很方便的使用这些数据,其在一定程度上将元数据与源代码文件结合在一起,而不是保存在外部文档中. 注解使我们可以 ...
- 深入学习JAVA注解-Annotation(学习过程)
JAVA注解-Annotation学习 本文目的:项目开发过程中遇到自定义注解,想要弄清楚其原理,但是自己的基础知识不足以支撑自己去探索此问题,所以先记录问题,然后补充基础知识,然后解决其问题.记录此 ...
- Java注解最全详解(超级详细)
Java注解是一个很重要的知识点,掌握好Java注解有利于学习Java开发框架底层实现.@mikechen Java注解定义 Java注解又称Java标注,是在 JDK5 时引入的新特性,注解(也被称 ...
- 一文看懂java io系统 (转)
出处: 一文看懂java io系统 学习java IO系统,重点是学会IO模型,了解了各种IO模型之后就可以更好的理解java IO Java IO 是一套Java用来读写数据(输入和输出)的A ...
- 深入JAVA注解-Annotation(学习过程)
JAVA注解-Annotation学习 本文目的:项目开发过程中遇到自定义注解,想要弄清楚其原理,但是自己的基础知识不足以支撑自己去探索此问题,所以先记录问题,然后补充基础知识,然后解决其问题.记录此 ...
- Java注解(二):实战 - 直接使用对象列表生成报表
通过对Java注解(一):介绍,思想及优点学习了解,相信大家对Java注解有一定程度的了解,本篇文章将实战项目中的应用来加深对Java注解的了解. 本实例实现根据指定字段的JavaBean,生成对应列 ...
随机推荐
- C++ 练气期之一文看懂字符串
C++ 练气期之细聊字符串 1. 概念 程序不仅仅用于数字计算,现代企业级项目中更多流转着充满了烟火气的人间话语.这些话语,在计算机语言称为字符串. 从字面上理解字符串,类似于用一根竹签串起了很多字符 ...
- 鹏城杯 WEB_WP
简单的PHP GET /?code=[~%8C%86%8C%8B%9A%92][~%CF]([~%9A%91%9B][~%CF]([~%98%9A%8B%9E%93%93%97%9A%9E%9B%9A ...
- Java 插入公式到PPT幻灯片
PowerPoint幻灯片中可插入公式,用于在幻灯片放映时演示相关内容的论证.推算的依据,能有效地为演讲者提供论述的数据支撑.通过后端程序代码,我们可借助特定的工具来实现在幻灯片中的插入公式,本文,将 ...
- 意向不到的Dubug妙招
1.直接dubug到想要到达的位置,直接点击旁边的数字即可. 2.debug后不想重新启动,想重新进入再执行一次debug,可以使用drop frame来删除当前栈,跳到之前的栈再一次进入这个栈. 注 ...
- Unity3D学习笔记10——纹理数组
目录 1. 概述 2. 详论 2.1. 实现 2.2. 注意 3. 参考 1. 概述 个人认为,纹理数组是一个非常有用的图形特性.纹理本质上是一个二维的图形数据:通过纹理数组,给图形数据再加上了一个维 ...
- python 装饰器理解
简介 装饰器可以在不修改原有代码的基础上添加新的功能,可以将重复重用的代码抽取出来,进一步解耦,方便维护,一般适用于插入日志.性能测试.事务处理.缓存等 装饰器的前提 闭包 一般来说,当一个函数嵌套另 ...
- linux 安装Apache php mysql注意事项
由于apache的php组件 php.so是由php安装生成的,故需在Apache安装之后才安装php比较合适 libphp5.so是php5提供的,你还需要编译php5才能生成这个文件 你在PHP的 ...
- 众妙之门玄之又玄,游戏系统中的伪随机(Pseudo-Randomization)和真随机(True-Randomization)算法实现Python3
原文转载自「刘悦的技术博客」https://v3u.cn/a_id_212 有人说,如果一个人相信运气,那么他一定参透了人生.想象一下,如果你在某款moba游戏中,在装备平平,队友天坑的情况下,却刀刀 ...
- 5.20 NOI 模拟
万年不更题解的鸽子来更题解了 \(T1\)矩阵 是个炒鸡恶心的推式子题 求\([x_1,x_2],[y_1,y_2]\)内部的数字和,把矩阵分成四份比较容易想到,差分也容易想到 \(Sum[x][y] ...
- Floyd算法详解
Floyd本质上使用了DP思想,我们定义\(d[k][x][y]\)为允许经过前k个节点时,节点x与节点y之间的最短路径长度,显然初始值应该为\(d[k][x][y] = +\infin (k, x, ...