本系列文章将整理到我在GitHub上的《Java面试指南》仓库,更多精彩内容请到个人仓库里查看html
https://github.com/h2pl/Java-Tutorial前端
喜欢的话麻烦点下Star哈java
文章首发于个人我的博客:python
www.how2playlife.comgit
本文是微信公众号【Java技术江湖】的《夯实Java基础系列博文》其中一篇,本文部份内容来源于网络,为了把本文主题讲得清晰透彻,也整合了不少我认为不错的技术博客内容,引用其中了一些比较好的博客文章,若有侵权,请联系做者。程序员
该系列博文会告诉你如何从入门到进阶,一步步地学习Java基础知识,并上手进行实战,接着了解每一个Java知识点背后的实现原理,更完整地了解整个Java技术体系,造成本身的知识框架。为了更好地总结和检验你的学习成果,本系列文章也会提供部分知识点对应的面试题以及参考答案。github
若是对本系列文章有什么建议,或者是有什么疑问的话,也能够关注公众号【Java技术江湖】联系做者,欢迎你参与本系列博文的创做和修订。
面试
Annotation 中文译过来就是注解、标释的意思,在 Java 中注解是一个很重要的知识点,但常常仍是有点让新手不容易理解。数据库
我我的认为,比较糟糕的技术文档主要特征之一就是:用专业名词来介绍专业名词。
好比:express
Java 注解用于为 Java 代码提供元数据。做为元数据,注解不直接影响你的代码执行,但也有一些类型的注解实际上能够用于这一目的。Java 注解是从 Java5 开始添加到 Java 的。
这是大多数网站上对于 Java 注解,解释确实正确,可是说实在话,我第一次学习的时候,头脑一片空白。这什么跟什么啊?听了像没有听同样。由于概念太过于抽象,因此初学者实在是比较吃力才可以理解,而后随着本身开发过程当中不断地强化练习,才会慢慢对它造成正确的认识。
我在写这篇文章的时候,我就在思考。如何让本身或者让读者可以比较直观地认识注解这个概念?是要去官方文档上翻译说明吗?我立刻否认了这个答案。
后来,我想到了同样东西————墨水,墨水能够挥发、能够有不一样的颜色,用来解释注解正好。
不过,我继续发散思惟后,想到了同样东西可以更好地代替墨水,那就是印章。印章能够沾上不一样的墨水或者印泥,能够定制印章的文字或者图案,若是愿意它也能够被戳到你任何想戳的物体表面。
可是,我再继续发散思惟后,又想到同样东西可以更好地代替印章,那就是标签。标签是一张便利纸,标签上的内容能够自由定义。常见的如货架上的商品价格标签、图书馆中的书本编码标签、实验室中化学材料的名称类别标签等等。
而且,往抽象地说,标签并不必定是一张纸,它能够是对人和事物的属性评价。也就是说,标签具有对于抽象事物的解释。
因此,基于如此,我完成了自个人知识认知升级,我决定用标签来解释注解。
以前某新闻客户端的评论有盖楼的习惯,因而 “乔布斯从新定义了手机、罗永浩从新定义了傻X” 就常常极为工整地出如今了评论楼层中,而且广大网友在至关长的一段时间内对于这种行为乐此不疲。这其实就是等同于贴标签的行为。
在某些网友眼中,罗永浩就成了傻X的代名词。
广大网友给罗永浩贴了一个名为“傻x”的标签,他们并不真正了解罗永浩,不知道他当教师、砸冰箱、办博客的壮举,可是由于“傻x”这样的标签存在,这有助于他们直接快速地对罗永浩这我的作出评价,而后基于此,罗永浩就能够成为茶余饭后的谈资,这就是标签的力量。
而在网络的另外一边,老罗靠他的人格魅力天然收获一大批忠实的拥泵,他们对于老罗贴的又是另外一种标签。
老罗仍是老罗,可是因为人们对于它贴上的标签不一样,因此形成对于他的见解截然不同,不喜欢他的人成天在网络上评论抨击嘲讽,而崇拜欣赏他的人则会愿意挣钱购买锤子手机的发布会门票。
我无心于评价这两种行为,我再引个例子。
《奇葩说》是近年网络上很是火热的辩论节目,其中辩手陈铭被另一个辩手马薇薇攻击说是————“站在宇宙中心呼唤爱”,而后贴上了一个大大的标签————“鸡汤男”,自此之后,观众再看到陈铭的时候,首先映入脑海中即是“鸡汤男”三个大字,其实自己而言陈铭很是优秀,为人师表、做风正派、谈吐举止得体,可是在网络中,由于娱乐至上的环境所致,人们更愿意以娱乐的心态来认知一切,因而“鸡汤男”就如陈铭本身所说成了一个撕不了的标签。
咱们能够抽象归纳一下,标签是对事物行为的某些角度的评价与解释。
到这里,终于能够引出本文的主角注解了。
初学者能够这样理解注解:想像代码具备生命,注解就是对于代码中某些鲜活个体的贴上去的一张标签。简化来说,注解如同一张标签。
在未开始学习任何注解具体语法而言,你能够把注解当作一张标签。这有助于你快速地理解它的大体做用。若是初学者在学习过程有大脑放空的时候,请不要慌张,对本身说:
注解,标签。注解,标签。
对于不少初次接触的开发者来讲应该都有这个疑问?Annontation是Java5开始引入的新特征,中文名称叫注解。它提供了一种安全的相似注释的机制,用来将任何的信息或元数据(metadata)与程序元素(类、方法、成员变量等)进行关联。为程序的元素(类、方法、成员变量)加上更直观更明了的说明,这些说明信息是与程序的业务逻辑无关,而且供指定的工具或框架使用。Annontation像一种修饰符同样,应用于包、类型、构造方法、方法、成员变量、参数及本地变量的声明语句中。
Java注解是附加在代码中的一些元信息,用于一些工具在编译、运行时进行解析和使用,起到说明、配置的功能。注解不会也不能影响代码的实际逻辑,仅仅起到辅助性的做用。包含在 java.lang.annotation 包中。
一、生成文档。这是最多见的,也是java 最先提供的注解。经常使用的有@param @return 等
二、跟踪代码依赖性,实现替代配置文件功能。好比Dagger 2依赖注入,将来java开发,将大量注解配置,具备很大用处;
三、在编译时进行格式检查。如@override 放在方法前,若是你这个方法并非覆盖了超类方法,则编译时就能检查出。
注解本质是一个继承了Annotation的特殊接口,其具体实现类是Java运行时生成的动态代理类。而咱们经过反射获取注解时,返回的是Java运行时生成的动态代理对象$Proxy1。经过代理对象调用自定义注解(接口)的方法,会最终调用AnnotationInvocationHandler的invoke方法。该方法会从memberValues这个Map中索引出对应的值。而memberValues的来源是Java常量池。
java.lang.annotation提供了四种元注解,专门注解其余的注解(在自定义注解的时候,须要使用到元注解):
@Documented –注解是否将包含在JavaDoc中
@Retention –何时使用该注解
@Target –注解用于什么地方
@Inherited – 是否容许子类继承该注解
1.)@Retention– 定义该注解的生命周期
● RetentionPolicy.SOURCE : 在编译阶段丢弃。这些注解在编译结束以后就再也不有任何意义,因此它们不会写入字节码。@Override, @SuppressWarnings都属于这类注解。 ● RetentionPolicy.CLASS : 在类加载的时候丢弃。在字节码文件的处理中有用。注解默认使用这种方式 ● RetentionPolicy.RUNTIME : 始终不会丢弃,运行期也保留该注解,所以可使用反射机制读取该注解的信息。咱们自定义的注解一般使用这种方式。
2.)Target – 表示该注解用于什么地方。默认值为任何元素,表示该注解用于什么地方。可用的ElementType参数包括
● ElementType.CONSTRUCTOR:用于描述构造器 ● ElementType.FIELD:成员变量、对象、属性(包括enum实例) ● ElementType.LOCAL_VARIABLE:用于描述局部变量 ● ElementType.METHOD:用于描述方法 ● ElementType.PACKAGE:用于描述包 ● ElementType.PARAMETER:用于描述参数 ● ElementType.TYPE:用于描述类、接口(包括注解类型) 或enum声明
3.)@Documented–一个简单的Annotations标记注解,表示是否将注解信息添加在java文档中。
4.)@Inherited – 定义该注释和子类的关系
@Inherited 元注解是一个标记注解,@Inherited阐述了某个被标注的类型是被继承的。若是一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。
JDK 内置注解
先来看几个 Java 内置的注解,让你们热热身。
@Override 演示
class Parent { public void run() { } } class Son extends Parent { /** * 这个注解是为了检查此方法是否真的是重写父类的方法 * 这时候就不用咱们用肉眼去观察究竟是不是重写了 */ @Override public void run() { } }
@Deprecated 演示
class Parent {
/** * 此注解表明过期了,可是若是能够调用到,固然也能够正常使用 * 可是,此方法有可能在之后的版本升级中会被慢慢的淘汰 * 能够放在类,变量,方法上面都起做用 */ @Deprecated public void run() { } } public class JDKAnnotationDemo { public static void main(String[] args) { Parent parent = new Parent(); parent.run(); // 在编译器中此方法会显示过期标志 } }
@SuppressWarnings 演示
class Parent {
// 由于定义的 name 没有使用,那么编译器就会有警告,这时候使用此注解能够屏蔽掉警告 // 即任意不想看到的编译时期的警告均可以用此注解屏蔽掉,可是不推荐,有警告的代码最好仍是处理一下 @SuppressWarnings("all") private String name; }
@FunctionalInterface 演示
/**
注解处理器
注解处理器才是使用注解整个流程中最重要的一步了。全部在代码中出现的注解,它到底起了什么做用,都是在注解处理器中定义好的。
概念:注解自己并不会对程序的编译方式产生影响,而是注解处理器起的做用;注解处理器可以经过在运行时使用反射获取在程序代码中的使用的注解信息,从而实现一些额外功能。前提是咱们自定义的注解使用的是 RetentionPolicy.RUNTIME 修饰的。这也是咱们在开发中使用频率很高的一种方式。
咱们先来了解下如何经过在运行时使用反射获取在程序中的使用的注解信息。以下类注解和方法注解。
类注解
Class aClass = ApiController.class;
Annotation[] annotations = aClass.getAnnotations();
for(Annotation annotation : annotations) { if(annotation instanceof ApiAuthAnnotation) { ApiAuthAnnotation apiAuthAnnotation = (ApiAuthAnnotation) annotation; System.out.println("name: " + apiAuthAnnotation.name()); System.out.println("age: " + apiAuthAnnotation.age()); } } 方法注解 Method method = ... //经过反射获取方法对象 Annotation[] annotations = method.getDeclaredAnnotations(); for(Annotation annotation : annotations) { if(annotation instanceof ApiAuthAnnotation) { ApiAuthAnnotation apiAuthAnnotation = (ApiAuthAnnotation) annotation; System.out.println("name: " + apiAuthAnnotation.name()); System.out.println("age: " + apiAuthAnnotation.age()); } }
此部份内容可参考: 经过反射获取注解信息
注解处理器实战
接下来我经过在公司中的一个实战改编来演示一下注解处理器的真实使用场景。
需求: 网站后台接口只能是年龄大于 18 岁的才能访问,不然不能访问
前置准备: 定义注解(这里使用上文的完整注解),使用注解(这里使用上文中使用注解的例子)
接下来要作的事情: 写一个切面,拦截浏览器访问带注解的接口,取出注解信息,判断年龄来肯定是否能够继续访问。
在 dispatcher-servlet.xml 文件中定义 aop 切面
<aop:config> <!--定义切点,切的是咱们自定义的注解--> <aop:pointcut id="apiAuthAnnotation" expression="@annotation(cn.caijiajia.devops.aspect.ApiAuthAnnotation)"/> <!--定义切面,切点是 apiAuthAnnotation,切面类即注解处理器是 apiAuthAspect,主处理逻辑在方法名为 auth 的方法中--> <aop:aspect ref="apiAuthAspect"> <aop:around method="auth" pointcut-ref="apiAuthAnnotation"/> </aop:aspect> </aop:config>
切面类处理逻辑即注解处理器代码如
@Component("apiAuthAspect") public class ApiAuthAspect { public Object auth(ProceedingJoinPoint pjp) throws Throwable { Method method = ((MethodSignature) pjp.getSignature()).getMethod(); ApiAuthAnnotation apiAuthAnnotation = method.getAnnotation(ApiAuthAnnotation.class); Integer age = apiAuthAnnotation.age(); if (age > 18) { return pjp.proceed(); } else { throw new RuntimeException("你未满18岁,禁止访问"); } } }
你能够在运行期访问类,方法或者变量的注解信息,下是一个访问类注解的例子:
Class aClass = TheClass.class; Annotation[] annotations = aClass.getAnnotations(); for(Annotation annotation : annotations){ if(annotation instanceof MyAnnotation){ MyAnnotation myAnnotation = (MyAnnotation) annotation; System.out.println("name: " + myAnnotation.name()); System.out.println("value: " + myAnnotation.value()); } }
你还能够像下面这样指定访问一个类的注解:
Class aClass = TheClass.class; Annotation annotation = aClass.getAnnotation(MyAnnotation.class); if(annotation instanceof MyAnnotation){ MyAnnotation myAnnotation = (MyAnnotation) annotation; System.out.println("name: " + myAnnotation.name()); System.out.println("value: " + myAnnotation.value()); }
下面是一个方法注解的例子:
public class TheClass { @MyAnnotation(name="someName", value = "Hello World") public void doSomething(){} }
你能够像这样访问方法注解:
Method method = ... //获取方法对象 Annotation[] annotations = method.getDeclaredAnnotations(); for(Annotation annotation : annotations){ if(annotation instanceof MyAnnotation){ MyAnnotation myAnnotation = (MyAnnotation) annotation; System.out.println("name: " + myAnnotation.name()); System.out.println("value: " + myAnnotation.value()); } }
你能够像这样访问指定的方法注解:
Method method = ... // 获取方法对象 Annotation annotation = method.getAnnotation(MyAnnotation.class); if(annotation instanceof MyAnnotation){ MyAnnotation myAnnotation = (MyAnnotation) annotation; System.out.println("name: " + myAnnotation.name()); System.out.println("value: " + myAnnotation.value()); }
方法参数也能够添加注解,就像下面这样:
public class TheClass { public static void doSomethingElse( @MyAnnotation(name="aName", value="aValue") String parameter){ } }
你能够经过 Method对象来访问方法参数注解:
Method method = ... //获取方法对象 Annotation[][] parameterAnnotations = method.getParameterAnnotations(); Class[] parameterTypes = method.getParameterTypes(); int i=0; for(Annotation[] annotations : parameterAnnotations){ Class parameterType = parameterTypes[i++]; for(Annotation annotation : annotations){ if(annotation instanceof MyAnnotation){ MyAnnotation myAnnotation = (MyAnnotation) annotation; System.out.println("param: " + parameterType.getName()); System.out.println("name : " + myAnnotation.name()); System.out.println("value: " + myAnnotation.value()); } } }
须要注意的是 Method.getParameterAnnotations()方法返回一个注解类型的二维数组,每个方法的参数包含一个注解数组。
下面是一个变量注解的例子:
public class TheClass { @MyAnnotation(name="someName", value = "Hello World") public String myField = null; }
你能够像这样来访问变量的注解:
Field field = ... //获取方法对象</pre> <pre>Annotation[] annotations = field.getDeclaredAnnotations(); for(Annotation annotation : annotations){ if(annotation instanceof MyAnnotation){ MyAnnotation myAnnotation = (MyAnnotation) annotation; System.out.println("name: " + myAnnotation.name()); System.out.println("value: " + myAnnotation.value()); } }
你能够像这样访问指定的变量注解:
Field field = ...//获取方法对象</pre> <pre> Annotation annotation = field.getAnnotation(MyAnnotation.class); if(annotation instanceof MyAnnotation){ MyAnnotation myAnnotation = (MyAnnotation) annotation; System.out.println("name: " + myAnnotation.name()); System.out.println("value: " + myAnnotation.value()); }
注解是绑定到程序源代码元素的元数据,对运行代码的操做没有影响。
他们的典型用例是:
java.lang和java.lang.annotation包中有几个注解,更常见的包括但不限于此:
返回类型必须是基本类型,String,Class,Enum或数组类型之一。不然,编译器将抛出错误。
这是一个成功遵循此原则的示例代码:
enum Complexity { LOW, HIGH } public @interface ComplexAnnotation { Class<? extends Object> value(); int[] types(); Complexity complexity(); }
下一个示例将没法编译,由于Object不是有效的返回类型:
public @interface FailingAnnotation { Object complexity(); }
注解能够应用于整个源代码的多个位置。它们能够应用于类,构造函数和字段的声明:
@SimpleAnnotation public class Apply { @SimpleAnnotation private String aField; @SimpleAnnotation public Apply() { // ... } }
方法及其参数:
@SimpleAnnotation public void aMethod(@SimpleAnnotation String param) { // ... }
局部变量,包括循环和资源变量:
@SimpleAnnotation int i = 10; for (@SimpleAnnotation int j = 0; j < i; j++) { // ... } try (@SimpleAnnotation FileWriter writer = getWriter()) { // ... } catch (Exception ex) { // ... }
其余注解类型:
@SimpleAnnotation public @interface ComplexAnnotation { // ... }
甚至包,经过package-info.java文件:
@PackageAnnotation package com.baeldung.interview.annotations;
从Java 8开始,它们也能够应用于类型的使用。为此,注解必须指定值为ElementType.USE的@Target注解:
@Target(ElementType.TYPE_USE) public @interface SimpleAnnotation { // ... }
如今,注解能够应用于类实例建立:
new @SimpleAnnotation Apply();
类型转换:
aString = (@SimpleAnnotation String) something;
接口中:
public class SimpleList<T> implements @SimpleAnnotation List<@SimpleAnnotation T> { // ... }
抛出异常上:
void aMethod() throws @SimpleAnnotation Exception { // ... }
有,@ Target注解可用于此目的。若是咱们尝试在不适用的上下文中使用注解,编译器将发出错误。
如下是仅将@SimpleAnnotation批注的用法限制为字段声明的示例:
@Target(ElementType.FIELD) public @interface SimpleAnnotation { // ... }
若是咱们想让它适用于更多的上下文,咱们能够传递多个常量:
@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.PACKAGE })
咱们甚至能够制做一个注解,所以它不能用于注解任何东西。当声明的类型仅用做复杂注解中的成员类型时,这可能会派上用场:
@Target({}) public @interface NoTargetAnnotation { // ... }
元注解适用于其余注解的注解。
全部未使用@Target标记或使用它标记但包含ANNOTATION_TYPE常量的注解也是元注解:
@Target(ElementType.ANNOTATION_TYPE) public @interface SimpleAnnotation { // ... }
@Target({ ElementType.FIELD, ElementType.TYPE, ElementType.FIELD }) public @interface TestAnnotation { int[] value() default {}; }
不能。若是在@Target注解中屡次出现相同的枚举常量,那么这是一个编译时错误。
删除重复常量将使代码成功编译:
@Target({ ElementType.FIELD, ElementType.TYPE})
https://blog.fundodoo.com/2018/04/19/130.html
https://blog.csdn.net/qq_37939251/article/details/83215703
https://blog.51cto.com/4247649/2109129
https://www.jianshu.com/p/2f2460e6f8e7
https://blog.csdn.net/yuzongtao/article/details/83306182
黄小斜是 985 硕士,阿里巴巴Java工程师,在自学编程、技术求职、Java学习等方面有丰富经验和独到看法,但愿帮助到更多想要从事互联网行业的程序员们。
做者专一于 JAVA 后端技术栈,热衷于分享程序员干货、学习经验、求职心得,以及自学编程和Java技术栈的相关干货。
黄小斜是一个斜杠青年,坚持学习和写做,相信终身学习的力量,但愿和更多的程序员交朋友,一块儿进步和成长!
原创电子书:
关注微信公众号【程序员黄小斜】后回复【原创电子书】便可领取我原创的电子书《菜鸟程序员修炼手册:从技术小白到阿里巴巴Java工程师》这份电子书总结了我2年的Java学习之路,包括学习方法、技术总结、求职经验和面试技巧等内容,已经帮助不少的程序员拿到了心仪的offer!
程序员3T技术学习资源: 一些程序员学习技术的资源大礼包,关注公众号后,后台回复关键字 “资料” 便可免费无套路获取,包括Java、python、C++、大数据、机器学习、前端、移动端等方向的技术资料。同时也包括我原创的【程序员校招大礼包】、【求职面试大礼包】等内容赠送,都是各位求职或者面试路上小伙伴很是实用的内容。
若是你们想要实时关注我更新的文章以及分享的干货的话,能够关注个人微信公众号【Java技术江湖】
这是一位阿里 Java 工程师的技术小站。做者黄小斜,专一 Java 相关技术:SSM、SpringBoot、MySQL、分布式、中间件、集群、Linux、网络、多线程,偶尔讲点Docker、ELK,同时也分享技术干货和学习经验,致力于Java全栈开发!
Java工程师必备学习资源: 关注公众号【Java技术江湖】后回复 Java 便可领取 Java基础、进阶、项目和架构师等免费学习资料,更有数据库、分布式、微服务等热门技术学习视频,内容丰富,兼顾原理和实践,另外也将赠送做者原创的Java学习指南、Java程序员面试指南等干货资源