注解是什么?
注解可以理解成注释、标记、标签的意思,用来标记类、方法等。就相当于现实生活中的一些事物,上边贴一个标签或者写一些注释性文字来描述它可以用来做什么、怎么用、何时用等信息。Java中的注解也是一样的,用来表示被标记的部分可以做什么、怎么做、何时做等信息。
注解可以用来做什么?
注解具有生成文档、在编译时期对代码进行检查、运行时期可以动态的实现业务功能,降低耦合度等用途。
注解怎么用?
Java中的注解可分为三类:内置的可以直接用于功能代码的、内置的元注解、自定义注解,其中元注解用于来注释自定义的注解,使用@interface关键字来声明自定义注解,使用注解时直接@注解名即可。接下来使用看一下实际中的用法:
1、三个内置注解:
//@SuppressWarnings 用于消除警告
@SuppressWarnings("unused")
public void warningTest()
{
//@SuppressWarnings("unused")
int i = 0;
int j = 0;
System.out.println("就是不使用i");
}
// @Override 用于表示重写方法
@Override
public void run()
{
super.run();
}
//@Deprecated 表示被注释的方法已经过时了
@Deprecated
public void deprecatedTest()
{
}
2、自定义注解、元注解:
// 如果没有 @Documented ,定义的注解不会被javadoc写入到文档中,但是我测试着可以哎,了解即可
@Documented
//@Retention 注解用于确定注解的保留期限
@Retention(RetentionPolicy.RUNTIME)
// @Target 用于标记此注解可以使用的位置是什么:类、方法等
@Target(ElementType.TYPE)
public @interface FirstPrintAnnotation {
//这里可以定义设置的值,专业名词:注解元素
//类型只能是:基本数据类型、String、Class、Enum、Annotation
//类似于定义方法似的定义一个值,用于设置数据
//String value default “h” 或
//String []values() ;
//这个值在使用注解值一定要是一个确定的值
String value();
}
/**
* 用来查看AnnontationTest类中用了注解@FirstPrintAnnotation的方法,并获取方法名
*/
public class AnnontationTest{
public static void main(String[] args) {
Class<AnnontationTest> clazz = AnnontationTest.class;
Method[] methods = clazz.getMethods();
for (Method method : methods) {
FirstPrintAnnotation firstPrintAnnotation = method.getAnnotation(FirstPrintAnnotation.class);
if (firstPrintAnnotation != null) {
System.out.println("使用了注解FirstPrintAnnotationd的方法有:"+firstPrintAnnotation.value());
}
}
}
@FirstPrintAnnotation("test1")
public void test1() {}
@FirstPrintAnnotation("test2")
public void test2() {}
@FirstPrintAnnotation("test3")
public void test3() { }
}
-----------------------------------------------------------------
//@Retention 源码
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
RetentionPolicy value();
}
//RetentionPolicy 代码
//源码解释
//它们与 { Retention } 元注解类型一起使用,以指定注释将被保留多长时间。
public enum RetentionPolicy {
//注释将被编译器丢弃,就是只在源码中用以下
SOURCE,
//注解由编译器记录在类文件中,不需要在运行时被JVM保留,默认情况,可以在class文件中用
CLASS,
//注释将由编译器记录在类文件中,并在运行时由JVM保留,所以可以通过反射获取,可在运行时类中获取
RUNTIME
}
------------------------------------------------------------------
//@Target 源码
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
ElementType[] value();
}
//ElementType 源码
//这个枚举类型的常量提供了Java程序中可能出现注释的语法位置的简单分类。
public enum ElementType {
//类、接口(包括注释类型)或枚举声明
TYPE,
//字段声明(包括枚举常量)
FIELD,
//方法声明
METHOD,
//正式的参数声明
PARAMETER,
//构造函数声明
CONSTRUCTOR,
//本地变量声明
LOCAL_VARIABLE,
//注解类型声明
ANNOTATION_TYPE,
//包说明
PACKAGE,
//类型参数声明
TYPE_PARAMETER,
//类型的使用
TYPE_USE
}
注解的实现原理是什么?
对编译后的注解类进行反编译,就会看到注解会被编译成一个接口类型,并且继承了Annotation接口,这些都是jvm隐式做的工作没必要深究,所以注解不可以像平常类一样使用new进行实例化,但是可以设置它存在于运行时期,所以可以通过反射的方式获取并解析注解。那他到底是怎么工作的呢?通常处理注解都另写一个注解处理器类,这里就不写了,原理就是根据使用了注解的类、方法或者变量来通过反射技术获取到注解,然后对相应代码进行额外功能的添加。简单的看一下如下代码(其实就是上边的实例代码):
public static void main(String[] args) throws InstantiationException, IllegalAccessException {
Class<AnnontationTest> clazz = AnnontationTest.class;
Method[] methods = clazz.getMethods();
for (Method method : methods) {
FirstPrintAnnotation firstPrintAnnotation = method.getAnnotation(FirstPrintAnnotation.class);
if (firstPrintAnnotation != null) {
//所属类型:com.sun.proxy.$Proxy1
System.out.println("所属类型:"+firstPrintAnnotation.getClass().getName());
Class<?>[] classes = firstPrintAnnotation.getClass().getInterfaces();
for (Class<?> class2 : classes) {
//实现的接口:interface com.czp.annontation.FirstPrintAnnotation
System.out.println("实现的接口:"+class2);
}
Class<?> superclass = firstPrintAnnotation.getClass().getSuperclass();
//继承的类:class java.lang.reflect.Proxy
System.out.println("继承的类:"+superclass);
//使用注解的值:test1
//实际上是 代理对象调用的是AnnotationInvocationHandler 中的invoke方法
//在此处打上断点,测试即可
firstPrintAnnotation.value();
System.out.println("使用注解的值:"+firstPrintAnnotation.value());
}
}
}
因为此注解可以存在于运行时阶段,所以可以通过反射技术获取到它的运行时类的对象,打印出来是:class com.sun.proxy.$Proxy1。这样我们就可以确定了,注解工作是依赖于java中的动态代理类,通过反射生成注解对应接口的对象,实际上是java的动态代理类,然后通过代理对象去调用注解元素,当使用代理类调用方法时,实际上是调用AnnotationInvocationHandler 的invoke 方法,在成员变量memberValues中获取的值。看一下运行时的源码:
//以根据方法反射注解为例分析
//思路:先将被类的运行时期的注解放到一个Map中,然后根据传入的注解类型获取对应的注解的代理类
//根据注解类型获取对应的Map集合中的注解对象
public <T extends Annotation> T getAnnotation(Class<T> annotationClass) {
Objects.requireNonNull(annotationClass);
return annotationClass.cast(declaredAnnotations().get(annotationClass));
}
//使用一个 Map<注解的运行时类,注解> 来存放注解信息
private transient Map<Class<? extends Annotation>, Annotation> declaredAnnotations;
private synchronized Map<Class<? extends Annotation>, Annotation> declaredAnnotations() {
//
if (declaredAnnotations == null) {
Executable root = getRoot();//Excecutable用于注释共享。Method继承自Excecutable
if (root != null) {
declaredAnnotations = root.declaredAnnotations();
} else {
declaredAnnotations = AnnotationParser.parseAnnotations(//一个注解分析器
getAnnotationBytes(),
sun.misc.SharedSecrets.getJavaLangAccess().
getConstantPool(getDeclaringClass()),
getDeclaringClass());
}
}
return declaredAnnotations;
}
//获取Map集合
public static Map<Class<? extends Annotation>, Annotation> parseAnnotations(byte[] var0, ConstantPool var1, Class<?> var2) {
if (var0 == null) {
return Collections.emptyMap();
} else {
return parseAnnotations2(var0, var1, var2, (Class[])null);
}
}
private static Map<Class<? extends Annotation>, Annotation> parseAnnotations2(byte[] var0, ConstantPool var1, Class<?> var2, Class<? extends Annotation>[] var3) {
LinkedHashMap var4 = new LinkedHashMap();
ByteBuffer var5 = ByteBuffer.wrap(var0);
int var6 = var5.getShort() & '?';
for(int var7 = 0; var7 < var6; ++var7) {
Annotation var8 = parseAnnotation2(var5, var1, var2, false, var3);//这是获取注解具体实现类的操作
if (var8 != null) {
Class var9 = var8.annotationType();
//当注解可以到运行时期使用时,则添加到LinkedHashMap var4中
if (AnnotationType.getInstance(var9).retention() == RetentionPolicy.RUNTIME && var4.put(var9, var8) != null) {
throw new AnnotationFormatError("Duplicate annotation for class: " + var9 + ": " + var8);
}
}
}
return var4;
}
AnnotationInvocationHandler(Class<? extends Annotation> var1, Map<String, Object> var2) {
Class[] var3 = var1.getInterfaces();
if (var1.isAnnotation() && var3.length == 1 && var3[0] == Annotation.class) {
//初始化注解数组
this.type = var1;
this.memberValues = var2;
} else {
throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");
}
}
public Object invoke(Object var1, Method var2, Object[] var3) {
String var4 = var2.getName();
Class[] var5 = var2.getParameterTypes();
if (var4.equals("equals") && var5.length == 1 && var5[0] == Object.class) {
return this.equalsImpl(var3[0]);
} else if (var5.length != 0) {
throw new AssertionError("Too many parameters for an annotation method");
} else {
byte var7 = -1;
switch (var4.hashCode()) {
case -1776922004 :
if (var4.equals("toString")) {
var7 = 0;
}
break;
case 147696667 :
if (var4.equals("hashCode")) {
var7 = 1;
}
break;
case 1444986633 :
if (var4.equals("annotationType")) {
var7 = 2;
}
}
switch (var7) {
case 0 :
return this.toStringImpl();
case 1 :
return this.hashCodeImpl();
case 2 :
return this.type;
default :
Object var6 = this.memberValues.get(var4);//在数组中获取对应的值
if (var6 == null) {
throw new IncompleteAnnotationException(this.type, var4);
} else if (var6 instanceof ExceptionProxy) {
throw ((ExceptionProxy) var6).generateException();
} else {
if (var6.getClass().isArray() && Array.getLength(var6) != 0) {
var6 = this.cloneArray(var6);
}
return var6;
}
}
}
}
简单介绍一下动态代理:就是在运行时期确定代理的类型。使用动态代理时需要使用都的两个类是Proxy和InvacationHandler。做一个简单的例子,代码如下:
//被代理类的公共接口
public interface Subject {
void show();
}
-------------------------------------------------------------
//被代理类的一个实现 RealSubject
public class RealSubject implements Subject{
@Override
public void show() {
System.out.println("动态代理");
}
}
//被代理类的一个实现 RealSubject
public class RealSubject implements Subject{
@Override
public void show() {
System.out.println("动态代理");
}
}
-------------------------------------------------------------
//动态代理必须要实现InvocationHandler接口,而不是被代理类的公共接口
public class DynProxy<T> implements InvocationHandler{
//被代理类对象
private T t;
//获取代理对象
public T getBlind(T t)
{
this.t = t;
return (T) Proxy.newProxyInstance(t.getClass().getClassLoader(), t.getClass().getInterfaces(), this);
}
//必须要重写这个方法
@Override
public Object invoke(Object porxy, Method method, Object[] args) throws Throwable {
return method.invoke(t, args);
}
}
-------------------------------------------------------------
//测试
public class Test {
public static void main(String[] args) {
//确定一下代理的公共接口类型
DynProxy<Subject> proxy = new DynProxy<>();
//获取代理对象
Subject blind = proxy.getBlind(new RealSubject());
System.out.println(blind.getClass().getName());//com.sun.proxy.$Proxy0
blind.show();
}
}