Java基础之注解和反射
注解
注解就是作用于类,方法以及字段上的特殊标记,这些标记可以在编译、类加载、运行时被读取,从而做相对应的处理。注解跟注释很像,区别是注释是给人看的,而注解是给程序看的,它可以被编译器读取。
注解格式为
元注解
public @interface 注解名称{
数据类型 参数名(); //参数名默认为value
}
元注解及参数
元注解就是修饰注解的注解
元注解之一@Target
由源代码可以知道,@Target注解用来指定注解的作用域(如方法、类或字段),其中 ElementType 是枚举类型,也代表可能的取值范围。要注意的是value()不是方法,而是参数,value为参数名。
ElementType的值可以是:
public enum ElementType {
/** Class, interface (including annotation type), or enum declaration */
//类、接口(包括注释类型)或枚举声明
TYPE,
/** Field declaration (includes enum constants) */
//字段声明(包括枚举常量)
FIELD,
/** Method declaration */
//方法声明
METHOD,
/** Formal parameter declaration */
//形式参数声明
PARAMETER,
/** Constructor declaration */
//构造方法声明
CONSTRUCTOR,
/** Local variable declaration */
//局部变量声明
LOCAL_VARIABLE,
/** Annotation type declaration */
//注解类型声明
ANNOTATION_TYPE,
/** Package declaration */
//包声明
PACKAGE,
/**
* Type parameter declaration
*
* @since 1.8
*/
//类型参数声明,起源于1.8
TYPE_PARAMETER,
/**
* Use of a type
*
* @since 1.8
*/
//类型的使用,起源于1.8
TYPE_USE
}
元注解之二@Retention
@Retention用来指定注解的生命周期,它有三个值,对应 RetentionPolicy 中的三个枚举值,分别是:源码级别(source),类文件级别(class)或者运行时级别(runtime)
- SOURCE:作用于源码中
- CLASS:作用于.class 文件中,但会被虚拟机丢弃(该类型的注解信息会保留在源码里和 class 文件里,在执行的时候,不会加载到虚拟机中)
当注解未定义 Retention 值时,默认值是 CLASS,如 Java 内置注解,@Override、@Deprecated、@SuppressWarnning 等
- RUNTIME:在源码,.class,运行时均可用,因此可以通过反射机制读取注解的信息(源码、.class 文件和执行的时候都有注解的信息),如 SpringMVC 中的 @Controller、@Service、@Mapping 等。此外,我们自定义的注解也大多在这个级别。
元注解之三Documented
执行 javaDoc 的时候,标记这些注解是否包含在生成的用户文档中。
元注解之四Inherited
标记这个注解具有继承性
自定义注解
自定义注解其实很简单,但要用到这个注解,讲这个注解使用起来就可以通过后面的反射机制来实现。
这是我自己定义的一个注解:
关于使用可以继续往后看。
import java.lang.annotation.*;
@Target({ElementType.TYPE,ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
String name() default "张三";
String id() default "1001";
String desc() default "";
}
反射
Java是一门静态语言,但也有说可以称为准动态语言。这个说法其中就可也体现在Java的反射
机制上。
实体类(以商品类为例):
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.base.annotationTest.MyAnnotation;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
@Data
@TableName("product")
@MyAnnotation(name = "商品实体类",id = "1000",desc = "商品实体类")
public class Product {
@MyAnnotation(name = "id",id = "1001",desc = "主键")
@TableId(type = IdType.INPUT)
private Long id;
@MyAnnotation(name = "pro_name",id = "1002",desc = "商品名称")
private String proName;
@MyAnnotation(name = "pro_type",id = "1003",desc = "商品类型")
private String proType;
@MyAnnotation(name = "pro_price",id = "1004",desc = "价格")
private float proPrice;
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@MyAnnotation(name = "date_of_manufacture",id = "1005",desc = "生产日期")
private LocalDateTime dateOfManufacture;
@MyAnnotation(name = "manufacturer",id = "1006",desc = "厂商")
private String manufacturer;
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@MyAnnotation(name = "create_time",id = "1007",desc = "创建时间")
private LocalDateTime createTime;
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@MyAnnotation(name = "last_modify_time",id = "1008",desc = "最后修改时间")
private LocalDateTime lastModifyTime;
@MyAnnotation(name = "status",id = "1009",desc = "是否上架")
private boolean status;
}
通过反射获取类对象
如下代码所示的三种方法。
- 通过对象的实例的getClass()方法来获取类对象;
- 通过类名的.class直接获取类对象;
- 通过全限类名调用Class.forName()方法。
import com.base.annotationTest.MyAnnotation;
import com.base.reflect.pojo.Product;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
/**
* 通过反射获取类对象的三种方式
*/
public class Test02 {
public static void main(String[] args) throws ClassNotFoundException {
//1,通过对象的实例的getClass()方法来获取类对象
Product product = new Product();
Class c1 = product.getClass();
//2,通过类名的.class直接获取类对象
Class c2 = Product.class;
//3,通过全限类名调用Class.forName方法创建
Class c3 = Class.forName("com.base.reflect.pojo.Product");
System.out.println(c1.hashCode());
System.out.println(c2.hashCode());
System.out.println(c3.hashCode());
}
}
由结果可知,三种方法创建的对象都为同一个对象。
类对象方法
通过反射创建的类对象可以调用方法来获取类的字段,方法,注解等信息。
举几个例子:
1,getName()与getSimpleName()
//获取类对象全限类名(包名+类名)
System.out.println(c1.getName());
//获取类对象名称
System.out.println(c1.getSimpleName());
2,getClassLoader()
获取类加载器。
ClassLoader classLoader = c1.getClassLoader();
ClassLoader parent = classLoader.getParent();
ClassLoader parent1 = parent.getParent();
System.out.println(classLoader);
System.out.println(parent);
System.out.println(parent1);
自定义的类加载器都是委托给AppClassLoader,而AppClassLoader委托给ExtClassLoader,ExtClassLoader委托给根加载器。这就是ClassLoader的委托链。可以看到类加载器的层次结构如下:
ClassLoader的双亲委派机制是这样的:
- 当AppClassLoader加载一个class时,它首先不会自己去尝试加载这个类,而是把类加载请求委派给父类加载器ExtClassLoader去完成。
- 当ExtClassLoader加载一个class时,它首先也不会自己去尝试加载这个类,而是把类加载请求委派给BootStrapClassLoader去完成。
- 如果BootStrapClassLoader加载失败(例如在$JAVA_HOME/jre/lib里未查找到该class),会使用ExtClassLoader来尝试加载;
- 若ExtClassLoader也加载失败,则会使用AppClassLoader来加载,如果AppClassLoader也加载失败,则会报出异常ClassNotFoundException。
3,getAnnotation();
该方法用来获取类,方法获取字段上的注解,以上面的商品类和之前的自定义注解MyAnnotation为例:
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
/**
* 获取自定义注解,并使用
*/
public class Test01 {
public static void main(String[] args) throws ClassNotFoundException {
Class clazz = Class.forName("com.base.reflect.pojo.Product");
Annotation[] annotations = clazz.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation);
}
MyAnnotation annotation = (MyAnnotation) clazz.getAnnotation(MyAnnotation.class);
System.out.println(annotation.name());
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
MyAnnotation myAnnotation = field.getAnnotation(MyAnnotation.class);
System.out.println(myAnnotation.id()+"==="+myAnnotation.name()+"==="+myAnnotation.desc());
}
}
}
结果如下图所示:
可以通过反射机制来获取注解里面参数的值,自然就可以对这些值进行其他操作了。
除了以上这些类方法还有其他的例如:getMethod(),getFiled()等,在Spring的框架中被大量用到。Spring的AOP原理就是动态代理+反射来实现的。