文章目录
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.定义注解属性
注解的每一个方法就是注解的属性
- 方法名称 ——> 属性名
- 返回值类型 ——> 属性的数据类型
注解中的成员变量以无参抽象方法
来声明,成员变量并不是所有类型都支持
,目前只支持以下类型:
- 八大基本类型(int,float,boolean,byte,double,char,long,short)
- String 类型
- Class类型 (如:Class<?> 或 Class)
- enum枚举类型
- Annotation类型
- 以上所有类型的数组类型
enum java枚举
public @interface MyAnnotation {
String name();//注解的属性
}
3.default关键字
default
来定义配置的默认值,如果不指定,则说明该参数为必填参数
推荐所有参数都尽量设置默认值
//定义了一个注解 @MyAnnotation 并设置默认值
public @interface MyAnnotation {
String name() default "空";
}
注解语法
- 注解由字符
@ 和注解名
组成,即@AnnotationName
。当编译器发现这个语法该元素时,会把它解析为注解。例如:
@ExampleAnnotation
public class SampleClass {
}
- 注解可以包含属性,在声明注解时以键值对的形式给出
@ExampleAnnotation(name = ”first name”, age = 35)
public void simpleMethod() {
}
- 如果注解只包含一个属性,在声明注解时可以忽略属性名
@ExampleAnnotation(“I am the only property”)
public void simpleMethod() {
}
- 一个元素可以使用多个注解。
@Annotation1
@Annotation2(“Another Annotation”)
public class SimpleClass {
}
- 当属性为数组类型时,如果只配置一个值,可以省略花括号,多个值不可省略
@Annotation(arr="a") //效果与Annotation(arr={"a"}) 一致
public class SimpleClass {
}
- 如果参数名称是value,且只有一个参数,那么可以省略参数名称(
一般核心参数使用value名称
)
public @Annotation Annotation{
String[] value() default "";
}
@Annotation("a") //效果与Annotation(value="a") 一致
public class SimpleClass {
}
- 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
文件,但加载结束后并不会存在于内存中
。这类注解只被一些底层库使用,一般我们不必自己处理。
- 比如: 有些工具会在加载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();
}
}
}