【Java基础】Java5新特性—注解(annotation)


Java注解

一.什么是注解?

  • Java1.5引入了注解,程序员通过注解可以为程序编写元数据(metadata)。根据 Oracle 官方文档,注解的定义如下:“注解是元数据的一种形式,提供与程序相关的数据,该数据不是程序本身的一部分”。

  • 可以在代码中的任何位置使用注解,比如类、方法和变量上使用。从 Java 8开始,注解也可以用于类型声明。

1.注解与注释的区别

注释
1.对代码的说明
2.给程序员看
3.编译class后自动抹去,没有实际意义
注解
可以标识代码,对代码加以说明
可以参与程序逻辑处理
编译class后可以设置不抹去,有实际意义

释会被编译器直接忽略,注解则可以被编译器打包进入class文件,因此,注解是一种用作标注的“元数据”。

二.内置注解

Java5内置了三种标准注解

  • @Override
    让编译器检查该方法是否正确地实现了重写,如果你重写了父类方法不带该注解会触发一些警告。

  • @Deprecated
    用来表示类、方法已经过时,不推荐使用。如果你强行使用编译器会在编译时进行警告。

  • @SuppressWarnings
    告诉编译器忽略该段代码产生的警告

  • @SafeVarargs
    抑制“堆污染”警告。“堆污染”指的是将一个不带泛型的对象赋给带泛型的变量时引发的类型问题。如果你不想看到该警告就可以使用该注解来抑制。

  • @FunctionalInterface
    Java8新增注解,只能作用于接口上来标识该接口是函数式接口。java中函数式接口表示该接口只能有一个抽象方法。如果一个接口被此注解修饰,添加第二个抽象方法将无法通过编译。

注解可以将一些元数据传递给你编写的逻辑

  • 比如: SpringMvc 中的一个常用注解@RequestMapping,我们可以通过value参数来传递一个path路径,SpringMvc通过对请求的路径的匹配来作出是否路由到该path上
  • 目前大量的的框架都依赖注解,比如Spring、hibernate、dubbo等等。

三.Java中使用注解

1.创建注解

使用关键字 @interface 修饰的类为注解类

public @interface MyAnnotation {
   String name();//注解的属性
}

注解可以放在Java的类、方法、字段、参数前

@MyAnnotation(name="学生类")
public class Student {
	@MyAnnotation(name="学生类")
   private Long id;

}

2.定义注解属性

注解的每一个方法就是注解的属性

  • 方法名称 ——> 属性名
  • 返回值类型 ——> 属性的数据类型

注解中的成员变量以无参抽象方法来声明,成员变量并不是所有类型都支持,目前只支持以下类型:

  1. 八大基本类型(int,float,boolean,byte,double,char,long,short)
  2. String 类型
  3. Class类型 (如:Class<?> 或 Class)
  4. enum枚举类型
  5. Annotation类型
  6. 以上所有类型的数组类型
    enum java枚举
public @interface MyAnnotation {
   String name();//注解的属性 
}

3.default关键字

default来定义配置的默认值,如果不指定,则说明该参数为必填参数

  • 推荐所有参数都尽量设置默认值
//定义了一个注解 @MyAnnotation 并设置默认值
public @interface MyAnnotation {
   String name() default "空";
}

注解语法

  1. 注解由字符 @ 和注解名组成,即 @AnnotationName。当编译器发现这个语法该元素时,会把它解析为注解。例如:
@ExampleAnnotation
public class SampleClass {
}
  1. 注解可以包含属性,在声明注解时以键值对的形式给出
@ExampleAnnotation(name = ”first name”, age = 35)
public void simpleMethod() {
}
  1. 如果注解只包含一个属性,在声明注解时可以忽略属性名
@ExampleAnnotation(“I am the only property”)
public void simpleMethod() {
}
  1. 一个元素可以使用多个注解。
@Annotation1
@Annotation2(“Another Annotation”)
public class SimpleClass {
}
  1. 当属性为数组类型时,如果只配置一个值,可以省略花括号,多个值不可省略
@Annotation(arr="a") //效果与Annotation(arr={"a"}) 一致
public class  SimpleClass {
 
}
  1. 如果参数名称是value,且只有一个参数,那么可以省略参数名称(一般核心参数使用value名称)
public @Annotation Annotation{
    String[] value() default "";
}

@Annotation("a") //效果与Annotation(value="a") 一致
public class  SimpleClass {
}
  1. Java8开始,可以为同一个元素多次使用相同的注解
@ExampleAnnotation(“Annotation used”)
@ExampleAnnotation(“Annotation repeated”)
public class SimpleClass {
}

4. 元注解

用来定义注解的注解叫做元注解

jdk目前提供了5个元注解。

4.1.@Retention

作用 : 定义注解的保留级别

RetentionPolicy取值有:

  • RetentionPolicy.SOURCE :注解存在于源代码中,编译时会被抛弃
  • RetentionPolicy.CLASS :注解会被编译到class文件中,但是JVM会忽略
  • RetentionPolicy.RUNTIME :JVM会读取注解,同时会保存到class文件中

举个栗子:

@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
	String name() default "空";
}

4.2.@Target

作用 : 用于描述注解的使用范围(即被描述的注解可以用在什么地方)

Target取值有:

  • ElementType.TYPE 用于类,接口,枚举但不能是注解
  • ElementType.FIELD 作用于字段,包含枚举值
  • ElementType.METHOD 作用于方法,不包含构造方法
  • ElementType.PARAMETER 作用于方法的参数
  • ElementType.CONSTRUCTOR 作用于构造方法
  • ElementType.LOCAL_VERIABLE 作用于本地变量或者catch语句
  • ElementType.ANNOTATION_TYPE 作用于注解
  • ElementType.PACKAGE 作用于包

举个栗子:

@Target(value={ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
	String name() default "空";
}

4.3.@Inherited

  • @Inherited元注解是一个标记注解
  • 默认情况下,注解不会被子类继承。但是,如果把注解标记为 @Inherited,那么使用注解修饰 class 时,子类也会继承该注解。该注解仅适用于 class。注意:使用该注解修饰接口时,实现类不会继承该注解。

4.4.@Documented

  • @Documented用于指明该注解可以用于生成JavaDoc
  • Documented是一个标记注解,没有成员。

4.5.@Repeatable

Java8新增注解, 该注解表示可以对同一个元素多次使用相同的注解。

  • 在此之前在同一个元素上同一个注解只能出现一次。
@Retention (RetentionPolicy.RUNTIME)
@Target (ElementType.TYPE_USE)
@Repeatable (RepeatableAnnotationContainer.class)
public @interface RepeatableAnnotation() {
    String values();
}

RepeatableAnnotation可以重复修饰元素。
接下来,定义RepeatableAnnotationContainer注解类型。这是一个注解类型容器,包含一个 RepeatableAnnotation类型的数组。

public @interface RepeatableAnnotationContainer {
    RepeatableAnnotation [] value();
}
@RepeatableAnnotation (“I am annotating the class)
@RepeatableAnnotation (“I am annotating the class again)
@RepeatableAnnotation (“I am annotating the class for the third time”)
public class RepeatedAnnotationExample {
}

在程序中要获取注解中的值,先要获取容器中的数组,数组的每个元素将包含一个值。例如:

@RepeatableAnnotation (“I am annotating the class)
@RepeatableAnnotation (“I am annotating the class again)
@RepeatableAnnotation(“I am annotating the class for the third time”)
public class RepeatableAnnotationExample {
    public static void main(String [] args) {
        Annotation[] annotations = RepeatableAnnotationExample.class.getAnnotations();
	    for (Annotation annotation : annotations) {
    		RepeatableAnnotationContainer rac = (RepeatableAnnotationContainer) annotation;
    		RepeatableAnnotation [] raArray = rac.value();
    		for (RepeatableAnnotation ra : raArray) {
        		System.out.println(ra.value);
    		}
		}
	}
}

执行结果

I am annotating the class
I am annotating the class again
I am annotating the class for the third time.

5.注解的分类

从JVM的角度看,注解本身对代码逻辑没有任何影响,如何使用注解完全由工具决定。

如何使用注解完全由工具决定,Java的注解可以分为三类:

第一类是由编译器使用的注解(SOURCE类型的注解)

  • SOURCE类型的注解主要由编译器使用,因此我们一般只使用,不编写
    例如:
    • @Override:让编译器检查该方法是否正确地实现了重写;
    • @SuppressWarnings:告诉编译器忽略此处代码产生的警告。
      这类注解不会被编译进入.class文件,它们在编译后就被编译器扔掉了

第二类是由工具处理.class文件使用的注解(CLASS类型),

  • CLASS类型的注解主要由底层工具库使用,涉及到class的加载,一般我们很少用到。
    • 比如: 有些工具会在加载class的时候,对class做动态修改,实现一些特殊的功能。这类注解会被编译进入.class文件,但加载结束后并不会存在于内存中。这类注解只被一些底层库使用,一般我们不必自己处理。

第三类是在程序运行期能够读取的注解(RUNTIME类型)
RUNTIME类型的注解它们在加载后一直存在于JVM中,这也是最常用的注解。

  • 例如: 一个配置了@PostConstruct的方法会在调用构造方法后自动被调用(这是Java代码读取该注解实现的功能,JVM并不会识别该注解)。

因为注解定义后也是一种class,所有的注解都继承自java.lang.annotation.Annotation,因此,读取注解,需要使用反射API。
Java提供的使用反射API读取Annotation的方法包括:

  • java.lang.Class
  • java.lang.reflect.Field
  • java.lang.reflect.Method
  • java.lang.reflect.Constuctor

上述对象都有以下方法:

方法 描述
<T extends Annotation> T getAnnotation(Class<T> annotationClass) 如果该方法对象存在指定类型的注解,则返回该注解,否则返回null
Annotation[] getAnnotations() 返回该对象上的所有注解,如果没有注解,则返回空数组
T getDeclaredAnnotation(Class annotationClass) 如果该对象存在指定类型的注解,则返回该注解,否则返回 null
Annotation[] getDeclaredAnnotations() 返回该对象上的所有注解,如果没有注解,则返回空数组
T[] getAnnotationsByType(Class annotationClass) 如果该对象存在指定类型的注解,则返回该注解数组,否则返回 null
T[] getDeclaredAnnotationsByType(Class annotationClass) 如果该对象存在指定类型的注解,则返回该注解数组,否则返回 null
boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) 如果该对象上有指定类型的注解,则返回 true,否则为 false

可以在运行期通过反射读取RUNTIME类型的注解,但不要漏写@Retention(RetentionPolicy.RUNTIME),否则运行期无法读取到该注解。

6.通过反射获取注解-案例1

定义一个注解

//在运行期中有效
@Target(value= {ElementType.TYPE,ElementType.FIELD,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
   String [] author()  default {};
   
   String createTime() default "";
  
   int level() default 1;
   
}
public class AnnoTest{

	public static void main(String[] args) {
		Class[] calssArr = { Student.class, Student2.class, Student3.class };
		// 返回源代码中给出的底层类的简称。 getSimpleName() ====com.panshi.countCode.MainTest===MainTest
		// Class<? extends Annotation> annotationType() ====返回此 annotation 的注释类型。
		List<Annotation> annoList=new ArrayList<Annotation>();
		for (Class clazz : calssArr) {
			// 获取类上的所有注解
			classAnno(annoList, clazz);

			//获取构造器上的所有注解
			constrAnno(annoList, clazz);

			//获取方法上的所有注解
			methodAnno(annoList, clazz);

			//获取字段上的所有注解
			fieldAnno(annoList, clazz);
		}

		Map<String, Integer> sortMap=new HashMap<String,Integer>();


		for (Annotation annotation : annoList) {
			MyAnnotation myAnno=(MyAnnotation) annotation;

			Integer count=sortMap.get(Arrays.toString(myAnno.author()));
			if(count==null) {
				sortMap.put(Arrays.toString(myAnno.author()), 1);
				continue;
			}
			count=sortMap.get(Arrays.toString(myAnno.author()));
			sortMap.put(Arrays.toString(myAnno.author()), count+1);

		}

		System.out.println(sortMap);
	}

	//获取所有类上的所有注解
	public static void classAnno(List<Annotation> listAnno,Class clazz) {
		MyAnnotation myAnnotation = (MyAnnotation) clazz.getAnnotation(MyAnnotation.class);
		if(myAnnotation!=null) {
			listAnno.add(myAnnotation);
		}
	}

	//获取所有构造器上的所有注解
	public static void constrAnno(List<Annotation> listAnno,Class clazz) {
		// 1.获取类的所有的方法
		Constructor [] constructors = clazz.getDeclaredConstructors();

		// 2.遍历类的所有方法
		for (Constructor constructor : constructors) {
			// 3.获取方法的所有注解
			MyAnnotation myAnnotation = (MyAnnotation) constructor.getAnnotation(MyAnnotation.class);
			if(myAnnotation!=null) {
				listAnno.add(myAnnotation);
			}
		}
	}

	//获取方法上的所有注解
	public static void methodAnno(List<Annotation> listAnno,Class clazz) {
		// 获取方法上的所有注解
		// 1.获取类的所有的方法
		Method[] methods = clazz.getDeclaredMethods();

		// 2.遍历类的所有方法
		for (Method method : methods) {
			// 3.获取方法的所有注解
			MyAnnotation myAnnotation = (MyAnnotation) method.getAnnotation(MyAnnotation.class);
			if(myAnnotation!=null) {
				listAnno.add(myAnnotation);
			}
		}
	}

	//获取字段上的所有注解
	public static void fieldAnno(List<Annotation> listAnno,Class clazz) {
		// 获取字段上的所有注解
		// 1.获取类的所有字段
		Field[] fields = clazz.getDeclaredFields();
		// 2.遍历类的所有字段
		for (Field field : fields) {
			// 3.获取方法的所有注解
			MyAnnotation myAnnotation = (MyAnnotation) field.getAnnotation(MyAnnotation.class);
			if(myAnnotation!=null) {
				listAnno.add(myAnnotation);
			}
		}

	}
}

7.通过反射获取注解-案例2

定义注解@FieldTypeAnnotation用于修饰字段

import java.lang.annotation.Documented;  
import java.lang.annotation.ElementType;  
import java.lang.annotation.Inherited;  
import java.lang.annotation.Retention;  
import java.lang.annotation.RetentionPolicy;  
import java.lang.annotation.Target;  
import java.net.Authenticator.RequestorType; 
@Documented  
@Inherited  
@Retention(RetentionPolicy.RUNTIME)  
@Target({ElementType.TYPE, ElementType.FIELD})//次注解作用于类和字段上  
public @interface FieldTypeAnnotation {  
    String type() default "ignore";  
    int age() default 27;  
    String[] hobby(); //没有指定defalut的,需要在注解的时候显式指明  
}

定义注解@MethodAnnotation 用于修饰方法

import java.lang.annotation.Documented;  
import java.lang.annotation.ElementType;  
import java.lang.annotation.Inherited;  
import java.lang.annotation.Retention;  
import java.lang.annotation.RetentionPolicy;  
import java.lang.annotation.Target;  

@Documented  
@Inherited  
@Retention(RetentionPolicy.RUNTIME)  
@Target(ElementType.METHOD) //此注解只能作用于方法上  
public @interface MethodAnnotation {  
    String desc() default "method1";  
}

测试方法

import java.awt.Dialog.ModalityType;  
import java.lang.reflect.Field;  
import java.lang.reflect.Method;  
import java.util.Arrays;  
  
@FieldTypeAnnotation(type = "class", hobby = { "smoke" })  
public class ReflectAnnotation {  
    /** 
     * leip 2016年12月3日 TODO 
     **/  
    @FieldTypeAnnotation(hobby = { "sleep", "play" })  
    private String maomao;  
  
    @FieldTypeAnnotation(hobby = { "phone", "buy" }, age = 27, type = "normal")  
    private String zhangwenping;  
      
    @MethodAnnotation()  
    public void method1() {  
          
    }  
    @MethodAnnotation(desc="method2")  
    public void method2() {  
          
    }  
  
    public static void main(String[] args) {  
        // 此处要用反射将字段中的注解解析出来  
        Class<ReflectAnnotation> clz = ReflectAnnotation.class;  
        // 判断类上是否有次注解  
        boolean clzHasAnno = clz.isAnnotationPresent(FieldTypeAnnotation.class);  
        if (clzHasAnno) {  
            // 获取类上的注解  
            FieldTypeAnnotation annotation = clz.getAnnotation(FieldTypeAnnotation.class);  
            // 输出注解上的属性  
            int age = annotation.age();  
            String[] hobby = annotation.hobby();  
            String type = annotation.type();  
            System.out.println(clz.getName() + " age = " + age + ", hobby = " + Arrays.asList(hobby).toString() + " type = " + type);  
        }  
        
        // 解析字段上是否有注解  
        // ps:getDeclaredFields会返回类所有声明的字段,包括private、protected、public,但是不包括父类的  
        // getFields:则会返回包括父类的所有的public字段,和getMethods()一样  
        Field[] fields = clz.getDeclaredFields();  
        for(Field field : fields){  
            boolean fieldHasAnno = field.isAnnotationPresent(FieldTypeAnnotation.class);  
            if(fieldHasAnno){  
                FieldTypeAnnotation fieldAnno = field.getAnnotation(FieldTypeAnnotation.class);  
                //输出注解属性  
                int age = fieldAnno.age();  
                String[] hobby = fieldAnno.hobby();  
                String type = fieldAnno.type();  
                System.out.println(field.getName() + " age = " + age + ", hobby = " + Arrays.asList(hobby).toString() + " type = " + type);  
            }  
        }  
        
        //解析方法上的注解  
        Method[] methods = clz.getDeclaredMethods();  
        for(Method method : methods){  
            boolean methodHasAnno = method.isAnnotationPresent(MethodAnnotation.class);  
            if(methodHasAnno){  
                //得到注解  
                MethodAnnotation methodAnno = method.getAnnotation(MethodAnnotation.class);  
                //输出注解属性  
                String desc = methodAnno.desc();  
                System.out.println(method.getName() + " desc = " + desc);  
            }  
        }  
    }  
} 

执行结果:

com.uno.ray.ReflectAnnotation age = 27, hobby = [smoke] type = class
maomao age = 27, hobby = [sleep, play] type = ignore
zhangwenping age = 27, hobby = [phone, buy] type = normal
method2 desc = method2
method1 desc = method1

8.使用注解优化DispatcherServlet

编写一个注解@RequestMapping

@Retention(RetentionPolicy.RUNTIME)//运行期保留
public @interface RequestMapping {
	String value();//属性
}

改造DispatcherServlet一个注解类

@WebServlet("*.do")
public class DispatcherServlet2  extends HttpServlet{
	 //1.声明映射map,用于保存映射对象 k(uri) v(map(k:Method对象,v:controller对象))
	  private Map<String,Map<String, Object>> mappingMap=new HashMap<>(); 
	
	@Override
	public void init() throws ServletException {
		//初始化servlet与controller的映射关系
			try {
				initMapping();
				System.out.println(mappingMap);
			} catch (InstantiationException | IllegalAccessException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
	}
	
	private void initMapping() throws InstantiationException, IllegalAccessException {
		//1.获取所有需要映射的controller对象
		Class<?>[] clazzArr= {UserController.class,PhotoController.class,PhotoAlbumController.class};
		
		//2.遍历所有需要映射的对象
		for (Class<?> clazz : clazzArr) {
			//2.1.获取当前class的实例对象
			Object controller= clazz.newInstance();
			
			//2.2.遍历当前class对象的所有方法
			for (Method method : clazz.getMethods()) {
				
			//2.3.获取当前方法的@requestMapping注解
			RequestMapping	requestMapping= method.getAnnotation(RequestMapping.class);
			
			//2.4.如果requestMapping==null(这个方法没有@requestMapping注解)
			if(requestMapping==null) {
				//继续遍历下一个方法
				continue;
			}
			
			//2.5.如果requestMapping!=null(这个方法带@requestMapping注解)	
			   //1.声明一个map 保存 需要执行的方法(k(method对象),v(controller对象))
				Map<String, Object> executeMap=new HashMap<String,Object>();
			   //2.保存Method对象到map中
				executeMap.put("method", method);
			   //3.保存controller对象到map中
				executeMap.put("controller", controller);
			
			//2.6.获取直接@RequestMapping加入到Map中
				mappingMap.put(requestMapping.value(), executeMap);
			}
		}
		
	}


	@Override
	protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		//获取请求的uri
		String uri=req.getRequestURI();
		System.out.println("DispatcherServlet======="+uri);

          //如果没有根据uri获取到执行map
		 if(mappingMap.get(uri)==null) {
                //退出响应,返回404
			resp.sendError(404);
			return;
		 }

		//1.根据请求uri获取对应的执行map(这个保存两个东西,conroller对象和Method对象)
		 Map<String, Object> map= mappingMap.get(uri);
		 
		//2.获取map中的controller对象
		 Object controller=map.get("controller");
		 
		//3.获取map中的Method对象
		 Method method=(Method) map.get("method");
		 
		 //4.使用反射技术调用方法method.invoke(obj 调用者,obj[] 传入的值)
		 try {
			method.invoke(controller,req,resp);
		} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
			e.printStackTrace();
		}
	}
}

猜你喜欢

转载自blog.csdn.net/qq877728715/article/details/104561600