注解是什么?
注解可以理解成注释、标记、标签的意思,用来标记类、方法等。就相当于现实生活中的一些事物,上边贴一个标签或者写一些注释性文字来描述它可以用来做什么、怎么用、何时用等信息。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.$Proxy1System.out.println("所属类型:"+firstPrintAnnotation.getClass().getName());Class<?>[] classes = firstPrintAnnotation.getClass().getInterfaces();for (Class<?> class2 : classes) {//实现的接口:interface com.czp.annontation.FirstPrintAnnotationSystem.out.println("实现的接口:"+class2);}Class<?> superclass = firstPrintAnnotation.getClass().getSuperclass();//继承的类:class java.lang.reflect.ProxySystem.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继承自Excecutableif (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{@Overridepublic void show() {System.out.println("动态代理");}
}//被代理类的一个实现 RealSubject
public class RealSubject implements Subject{@Overridepublic 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);}//必须要重写这个方法@Overridepublic 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.$Proxy0blind.show();}
}