赞
踩
本文来说下JAVA中的注解,这个技术虽然我们每天都在使用,但是不一定知道其实现原理。本文来详细介绍下JAVA中注解相关的知识。
现在主流的项目架构都是ssm、springcloud等,而这些框架都离不开spring,而spring中使用了大量的注解(包括spring自定义的注解)。因此想要会用注解,我们就得知道Java注解的原理和基本用法,这样有助于我们在项目中如鱼得水。
在JDK5.0中,新增了很多对现在影响很大的特性,如:枚举、自动装箱和拆箱、注解、泛型等等。其中注解的引入,是为了增加对元数据的支持。
注解:是一个接口,程序可以通过反射来获取指定程序元素的Annotation对象,然后通过Annotation对象来取得注解里的元数据,注解能用来为程序元素(包、类、方法、成员变量等)设置元数据,它不影响程序代码的执行。如果希望让程序中的Annotation在运行时起一定作用,只有通过某种配套的工具对Annotation中的信息进行访问和处理,这个工具统称为APT(Annotation program Tool)
JDK5.0除了增加注解特性之外,还提供了5个基本的Annotation:
@Overrride:限定重写父类方法(旨在强制性提醒)
@Deprecated:表示某个程序元素(类、方法)已过时
@SuppressWarnings:抑制编译警告
@SafeVarargs:堆污染警告
@FunctionalInterface:指定某个接口必须为函数式接口
注:函数式接口是指一个接口中只包含一个抽象方法(可以包含多个默认方法或多个static方法)
以上这几个注解,在我们的项目经常可见,但是因为他们对程序只是一个强制提醒或者警告作用,并不影响程序的执行,因为我们都没在意这些注解的作用,而当spring出来之后,大量的注解眼花缭乱,作用各异,但他们都有一个共同作用:让我们在编码过程中简化了不少重复性的代码。
注解本质是一个继承了Annotation的特殊接口,其具体实现类是Java运行时生成的动态代理类。而我们通过反射获取注解时,返回的是Java运行时生成的动态代理对象$Proxy1。通过代理对象调用自定义注解(接口)的方法,会最终调用AnnotationInvocationHandler的invoke方法。该方法会从memberValues这个Map中索引出对应的值。而memberValues的来源是Java常量池。
实际上Java注解与普通修饰符(public、static、void等)的使用方式并没有多大区别,下面的例子是常见的注解:
public class AnnotationDemo {
@Test
public static void A(){
System.out.println("Test.....");
}
@Deprecated
@SuppressWarnings("uncheck")
public static void B(){
}
}
@Controller注解反编译
相信大家肯定都使用过@Controller注解,我们从这个注解一起来看下注解的本质到底是什么。
@Controller注解的源代码如下
/* * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.stereotype; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.springframework.core.annotation.AliasFor; /** * Indicates that an annotated class is a "Controller" (e.g. a web controller). * * <p>This annotation serves as a specialization of {@link Component @Component}, * allowing for implementation classes to be autodetected through classpath scanning. * It is typically used in combination with annotated handler methods based on the * {@link org.springframework.web.bind.annotation.RequestMapping} annotation. * * @author Arjen Poutsma * @author Juergen Hoeller * @since 2.5 * @see Component * @see org.springframework.web.bind.annotation.RequestMapping * @see org.springframework.context.annotation.ClassPathBeanDefinitionScanner */ @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Component public @interface Controller { /** * The value may indicate a suggestion for a logical component name, * to be turned into a Spring bean in case of an autodetected component. * @return the suggested component name, if any (or empty String otherwise) */ @AliasFor(annotation = Component.class) String value() default ""; }
对@Controller注解生成的class文件进行反编译之后,我们来看下jvm到底为我们生成的是什么。
我们看到jvm为我们生成的内容很简单,就是一个继承了Annotation接口的接口,里面有一个抽象的value()方法。那么我们知道了注解的本质其实就是一个特殊的接口。
元注解
java.lang.annotation中提供了元注解,可以使用这些注解来定义自己的注解。主要使用的是Target和Retention注解。
Annotation接口
前面经过反编译后,我们知道Java所有注解都继承了Annotation接口,也就是说Java使用Annotation接口代表注解元素,该接口是所有Annotation类型的父接口。同时为了运行时能准确获取到注解的相关信息,Java在java.lang.reflect 反射包下新增了AnnotatedElement接口,它主要用于表示目前正在 VM 中运行的程序中已使用注解的元素,通过该接口提供的方法可以利用反射技术地读取注解的信息,如反射包的Constructor类、Field类、Method类、Package类和Class类都实现了AnnotatedElement接口。
AnnotationElement接口
既然上面定义了注解,那得有办法拿到我们定义的注解啊。java.lang.reflect.AnnotationElement接口则提供了该功能。注解的处理是通过java反射来处理的。如下,反射相关的类Class, Method, Field都实现了AnnotationElement接口。
反射处理
AnnotationElement接口方法
因此,只要我们通过反射拿到Class, Method, Field类,就能够通过getAnnotation(Class)拿到我们想要的注解并取值。
因此我们在了解了注解的原理之后,必须要能自定义一些注解并且使用它到项目中去,才能让我们更好的了解和使用它。而要想自定义注解,就必须得了解Java提供的几个元注解。那什么是元注解呢?
元注解:就是负责注解其它注解的注解
在Java5之后定义了4个标准的元注解,分别是:
@Target
@Retention
@Documented
@Inherited
target注解用来标识注解所修饰的对象范围。
它的可用范围(ElementType的取值范围)有:
使用实例:
@Target(ElementType.FIELD)
public @interface TargetTest6{
}
@Target({ElementType.TYPE_PARAMETER,ElementType.METHOD})
public @interface TargetTest7{
}
@Retention定义了该Annotation被保留的时间长短,取值在java.lang.annotation.RetentionPolicy中:某些Annotation仅出现在源代码中,而被编译器丢弃;而另一些却被编译在class文件中;编译在class文件中的Annotation可能会被虚拟机忽略,而另一些在class被装载时将被读取(请注意并不影响class的执行,因为Annotation与class在使用上是被分离的)。使用这个meta-Annotation可以对 Annotation的“生命周期”限制。
它的取值(RetentionPoicy)有:
只有定义为RetentionPolicy.RUNTIME时,我们才能通过注解反射获取到注解。所以,假设我们要自定义一个注解,它用在字段上,并且可以通过反射获取到,功能是用来描述字段的长度和作用,可以定义如下。
使用实例:
@Target(ElementType.FIELD) // 注解用于字段上
@Retention(RetentionPolicy.RUNTIME) // 保留到运行时,可通过注解获取
public @interface MyField {
String description();
int length();
}
@Inherited注解指定被它修饰的注解将具备继承性:如果莫个类使用了@XXX注解,则其子类自动被@XXX修饰
使用实例:
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@interface TargetTest6{
}
//所有使用了@TargetTest6注解的类讲具备继承性,
//也就是它的子类自动带上@TargetTest6注解
@Documented用于指定被该注解修饰的注解类将被javadoc工具提取成文档.
使用实例:
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@interface TargetTest6{
}
//javadoc工具生成的API文档将提取@Documented的使用信息
以上就是所有的元注解以及他们的作用分析,有了这些元注解有什么用呢?当然是为了方便我们在项目中自定义注解。那自定义注解怎么自定义呢?下面我们来看看介绍如何自定义注解并利用注解完成一些实际的功能。
类修饰符 @interface 注解名称{
//成员变量,在注解中以无形参的形式存在
//其方法名和返回值定义了该成员变的名字和类型
String name();
int age();
}
可以看到:注解的语法和接口的定义非常类似,他也一样具备有作用域,但是它的成员变量的定义是以无形参的方法形式存在,名字定义了成员变量的名字,返回值定义了变量类型。
下面我们来自定义一个注解:
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AnonTest {
int age();
String name();
}
注解@AnonTest在运行时有效,作用域在成员变量上,它有两个成员变量分别是int型的age和String型的name。
接下来我们就可以使用这个注解了
public class test {
@AnonTest(age = 0, name = "1")
public Integer tes;
}
这样就是一个注解的定义和使用了,有人会疑惑说,spring中很多注解都是@xxx,为什么这个@AnonTest一定要要带上两个成员变量呢?
原因很简单:
注解中的成员变量如果没有默认值,则在使用注解时必须要给成员变量赋值
但如果成员变量有默认值,那可以直接在定义注解时,赋值上去,这样在使用时就可以省略不写
@Target(ElementType.FIELD)
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface AnonTest {
int age() default 1;
String name() default "注解测试";
}
这样在调用注解时就可以不赋值
public class test {
@AnonTest
public Integer tes;
}
上面看到,我们已经使用了注解,但是我们并没发现@AnonTest对我们的tes成员变量有任何作用,这是因为注解本身在程序中是不会生效的,而是需要程序来提取数据并且处理注解本应该做的工作。
有了AnnotationElement接口中的这些方法,我们就可以在类、方法、成员变量等程序元素中获取到注解信息:
package cn.wideth.util.other; import org.springframework.stereotype.Component; import org.springframework.stereotype.Service; import java.lang.annotation.Annotation; @Component @Service public class Test { @AnonTest public Integer tes; @Deprecated public void m1(){ } public static void main(String[] args) { try { //获取所有test类上的注解 Annotation[] ar=Class.forName("cn.wideth.util.other.Test").getAnnotations(); //获取所有test类上m1方法的注解 Annotation[] ar1=Class.forName("cn.wideth.util.other.Test").getMethod("m1").getAnnotations(); //遍历所有的注解,检测是否有我们想要的某个注解 如@Component注解 for(Annotation a:ar){ System.out.println(a.getClass()); if(a instanceof Component) System.out.println("用了注解:"+a); } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
程序结果
利用AnnotatedElement提供的方法能获取到我们想要的注解信息之后,就可以针对注解做一些特定的行为。
例如,对于所有的注册用户信息,系统需要把名称和年龄上报到另一个年龄分布表中,就可以利用注解就可以完成这样的动作
package cn.wideth.util.other; import java.lang.reflect.Field; public class Test { @AnonTest(name="张小小",age=20) public Integer tes; public static void getInfo(Class<?> clazz) { //获取目标类的所有成员变量 Field[] fields = clazz.getDeclaredFields(); for (Field field : fields) { //查找变量中是否有存在@AnonTest注解 if (field.isAnnotationPresent(AnonTest.class)) { //如果存在,则做相应处理 AnonTest anon= field.getAnnotation(AnonTest.class); System.out.println("名称是:"+anon.name()); System.out.println("年龄是:"+anon.age()); System.out.println("上报信息到用户年龄分布表中"); //上报信息到用户年龄分布表中 //uploadInfoToLog(anon.name()),anon.age()) } } } public static void main(String[] args) { Test.getInfo(Test.class); } }
程序结果
最终程序按照我们的需求运行并且输出正确的信息,由此可见,注解对于Java来说是一种补充,他的存在与否对程序来说应该是无影响的,灵活使用注解能使开发的效率更高,而且程序规范性也得到增强。
本文详细介绍了注解相关的知识。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。