22、java中的注解

注解是什么?

       注解可以理解成注释、标记、标签的意思,用来标记类、方法等。就相当于现实生活中的一些事物,上边贴一个标签或者写一些注释性文字来描述它可以用来做什么、怎么用、何时用等信息。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();
	}
}

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值