注解
注解简介
注解的英文就是 Annotation,是在JDK 1.5之后引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。
注解就是给 java 代码加上一个标识规则,javac编译器在编译时就会去检测应用了该注解类的类是否符合标识规则,来约束编码规范。
元注解
Java目前只内置了三种标准注解,以及四种元注解。
内置注解
- @Override:重写父类方法的声明
- @Deprecated:修饰方法、类、属性,表示不推荐使用
- @SuppressWarnings:抑制编译时的警告信息
package annotition;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
/**
* @author: everbolee
* @createDate: 2021/2/19
*/
public class AnnotationTest {
public static final Logger logger = LoggerFactory.getLogger(AnnotationTest.class);
@Override
public String toString(){
return "hello";
}
@Deprecated
public static void deprecatedTest(){
logger.error("Deprecated....");
}
@Test
public void deprecatedDemo(){
deprecatedTest();
}
@SuppressWarnings("all")
@Test
public void suppressWarningsDemo(){
List list = new ArrayList();
}
}
1.@Target
表示支持注解的程序元素的种类,注解该用于什么地方,ElementType
注解修饰的元素类型,使用ElementType
枚举类来表示:
CONSTRUCTOR:构造器的声明
FIELD:域声明(包括enum实例)
LOCAL_VARIABLE:局部变量声明
METHOD:方法声明
PACKAGE: 包声明
PARAMETER:参数声明
TYPE:类,接口(包括注解类型)或enum声明
ANNOTATION_TYPE:注解类型声明
TYPE_PARAMETER:类型参数声明 从jdk1.8开始 、 hide1.8
TYPE_USE:类型的使用 从jdk1.8开始 、 hide1.8
2.@Retention
表示保留时间的长短,需要在什么级别保存该注解信息, RetentionPolicy
参数包括:
SOURCE:注解将被编译器丢弃。
CLASS:注解在class文件中可用,但会被VM丢弃。
RUNTIME:VM将在运行期也保留注解,因此可通过反射机制读取注解的信息。
RetentionPolicy
它是枚举类,分别简单了解三种类型:
-
RetentionPolicy.SOURCE
当注解保留时期为 SOURCE 时:表示注解信息只会被保留到源码和编译期间,当
javac
编译器编译源代码后会将该注解去除,但是在编译期间,注解处理器是是可以处理这些注解的,在编译完成之后,这些注解信息就没有了。 -
RetentionPolicy.CLASS
当注解保留时期为 CLASS 时:表示注解信息会被保留到编译时期,该注解信息会被编译到
.class
文件中,但不会被虚拟机加载到内存中,保留到 Javac 将.java
文件编译成.class
文件,但是不会随着 ClassLoader 将该.class
文件加载到内存中。 -
RetentionPolicy.RUNTIME
.当注解保留时期为 RUNTIME 时:表示注解信息会被保留到"运行时期",这是可以通过反射获取该注解的信息。
Javac
会将源文件.java
编译为.class
文件,然后通过 ClassLoader 将.class
文件加载到内存中成为字节码,这时就可以通过反射来获取对应的注解信息。
3.@Documented
将此注解包含在Javadoc
中。 表示使用该注解的元素应被javadoc
或类似工具文档化,它应用于类型声明,类型声明的注解会影响客户端对注解元素的使用。如果一个类型声明添加了Documented注解,那么它的注解会成为被注解元素的公共API的一部分。
4.@Inherited
允许子类继承父类中的注解表示一个注解类型会被自动继承,如果用户在类声明的时候查询注解类型,同时类声明中也没有这个类型的注解,那么注解类型会自动查询该类的父类,这个过程将会不停地重复,直到该类型的注解被找到为止,或是到达类结构的顶层(Object)。
自定义注解
使用@interface 自定义注解时,自动继承java.lang.annotation.Annotation接口
规则:
- @interface用来声明注解,格式:public @interface 注解名{内容}
- 其中的每一个方法实际声明一个配置参数
- 方法的名称就是参数的名称
- 返回值类型就是参数的类型,返回值只能是基本类型 Class,String,Enum
- 可通过default来声明默认参数
- 只有一个参数成员,一般参数名为value
- 元注解必须要有值,定义注解元素时可用 “”、0作为默认值
public class MyAnnotationTest {
public static final Logger logger = LoggerFactory.getLogger(AnnotationTest.class);
@MyAnnotation(name = "xiaoli")
@Test
public void myAnnotationDemo(){
logger.error("test...");
}
}
@Target({ElementType.METHOD,ElementType.TYPE,ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation{
//参数:参数类型+参数名()
String name() default "";
int age() default 0;
String[] nums() default {"1","2"};
}
反射
反射简介
Java反射就是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;并且能改变它的属性。而这也是Java被视为动态(或准动态,为啥要说是准动态,因为一般而言的动态语言定义是程序运行时,允许改变程序结构或变量类型,这种语言称为动态语言。从这个观点看,Perl,Python,Ruby是动态语言,C++,Java,C#不是动态语言。)语言的一个关键性质。
Class c = Class.forName("java.lang.String");
加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以,我们形象的称之为:反射
优点:可以实现动态创建对象和编译,体现出很大灵活性
缺点:对性能有影响。
@Test
public void speedTest() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
long begin = System.currentTimeMillis();
Person person = new Person();
for (int i = 0; i < 1000000000; i++) {
person.getAge();
}
long end = System.currentTimeMillis();
logger.info("正常创建对象时间:{}",end-begin);
Class<? extends Person> personClass = person.getClass();
Method getAge = personClass.getDeclaredMethod("getAge", null);
long reflectBegin = System.currentTimeMillis();
for (int i = 0; i < 1000000000; i++) {
getAge.invoke(person,null);
}
long reflectEnd = System.currentTimeMillis();
logger.info("反射创建对象时间:{}",reflectEnd-reflectBegin);
}
//执行结果如图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Af7wEtrD-1613808075403)(C:\Users\DB007\AppData\Roaming\Typora\typora-user-images\image-20210220144551232.png)]
类加载过程
1. 加载
加载指的是将类的class文件读入到内存,并为之创建一个java.lang.Class对象,也就是说,当程序中使用任何类时,系统都会为之建立一个java.lang.Class对象。
类的加载由类加载器完成,类加载器通常由JVM提供,这些类加载器也是前面所有程序运行的基础,JVM提供的这些类加载器通常被称为系统类加载器。除此之外,开发者可以通过继承ClassLoader基类来创建自己的类加载器。
通过使用不同的类加载器,可以从不同来源加载类的二进制数据,通常有如下几种来源。
-
从本地文件系统加载class文件,这是前面绝大部分示例程序的类加载方式。
-
从JAR包加载class文件,这种方式也是很常见的,前面介绍JDBC编程时用到的数据库驱动类就放在JAR文件中,JVM可以从JAR文件中直接加载该class文件。
-
通过网络加载class文件。
-
把一个Java源文件动态编译,并执行加载。
类加载器通常无须等到“首次使用”该类时才加载该类,Java虚拟机规范允许系统预先加载某些类。
2. 连接
当类被加载之后,系统为之生成一个对应的Class对象,接着将会进入连接阶段,连接阶段负责把类的二进制数据合并到JRE中。类连接又可分为如下3个阶段。
**1、验证:**验证阶段用于检验被加载的类是否有正确的内部结构,并和其他类协调一致。Java是相对C++语言是安全的语言,例如它有C++不具有的数组越界的检查。这本身就是对自身安全的一种保护。验证阶段是Java非常重要的一个阶段,它会直接的保证应用是否会被恶意入侵的一道重要的防线,越是严谨的验证机制越安全。验证的目的在于确保Class文件的字节流中包含信息符合当前虚拟机要求,不会危害虚拟机自身安全。其主要包括四种验证,文件格式验证,元数据验证,字节码验证,符号引用验证。
四种验证做进一步说明:
**文件格式验证:**主要验证字节流是否符合Class文件格式规范,并且能被当前的虚拟机加载处理。例如:主,次版本号是否在当前虚拟机处理的范围之内。常量池中是否有不被支持的常量类型。指向常量的中的索引值是否存在不存在的常量或不符合类型的常量。
**元数据验证:**对字节码描述的信息进行语义的分析,分析是否符合java的语言语法的规范。
**字节码验证:**最重要的验证环节,分析数据流和控制,确定语义是合法的,符合逻辑的。主要的针对元数据验证后对方法体的验证。保证类方法在运行时不会有危害出现。
**符号引用验证:**主要是针对符号引用转换为直接引用的时候,是会延伸到第三解析阶段,主要去确定访问类型等涉及到引用的情况,主要是要保证引用一定会被访问到,不会出现类等无法访问的问题。
**2、准备:**类准备阶段负责为类的静态变量分配内存,并设置默认初始值。
**3、解析:**将类的二进制数据中的符号引用替换成直接引用。说明一下:符号引用:符号引用是以一组符号来描述所引用的目标,符号可以是任何的字面形式的字面量,只要不会出现冲突能够定位到就行。布局和内存无关。直接引用:是指向目标的指针,偏移量或者能够直接定位的句柄。该引用是和内存中的布局有关的,并且一定加载进来的。
3. 初始化
初始化是为类的静态变量赋予正确的初始值,准备阶段和初始化阶段看似有点矛盾,其实是不矛盾的,如果类中有语句:private static int a = 10,它的执行过程是这样的,首先字节码文件被加载到内存后,先进行链接的验证这一步骤,验证通过后准备阶段,给a分配内存,因为变量a是static的,所以此时a等于int类型的默认初始值0,即a=0,然后到解析(后面在说),到初始化这一步骤时,才把a的真正的值10赋给a,此时a=10。
类加载时机
-
创建类的实例,也就是new一个对象
-
访问某个类或接口的静态变量,或者对该静态变量赋值
-
调用类的静态方法
-
反射(Class.forName(“com.lyb.load”))
-
初始化一个类的子类(会首先初始化子类的父类)
-
JVM启动时标明的启动类,即文件名和类名相同的那个类
除此之外,下面几种情形需要特别指出:
对于一个final类型的静态变量,如果该变量的值在编译时就可以确定下来,那么这个变量相当于“宏变量”。Java编译器会在编译时直接把这个变量出现的地方替换成它的值,因此即使程序使用该静态变量,也不会导致该类的初始化。反之,如果final类型的静态Field的值不能在编译时确定下来,则必须等到运行时才可以确定该变量的值,如果通过该类来访问它的静态变量,则会导致该类被初始化。
类加载器
类加载器负责加载所有的类,其为所有被载入内存中的类生成一个java.lang.Class实例对象。一旦一个类被加载如JVM中,同一个类就不会被再次载入了。正如一个对象有一个唯一的标识一样,一个载入JVM的类也有一个唯一的标识。在Java中,一个类用其全限定类名(包括包名和类名)作为标识;但在JVM中,一个类用其全限定类名和其类加载器作为其唯一标识。
JVM预定义有三种类加载器,当一个 JVM启动的时候,Java开始使用如下三种类加载器:
1)根类加载器(bootstrap class loader):它用来加载 Java 的核心类,是用原生代码来实现的,并不继承自 java.lang.ClassLoader(负责加载$JAVA_HOME中jre/lib/rt.jar里所有的class,由C++实现,不是ClassLoader子类)。由于引导类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进行操作。
2)扩展类加载器(extensions class loader):它负责加载JRE的扩展目录,lib/ext或者由java.ext.dirs系统属性指定的目录中的JAR包的类。由Java语言实现,父类加载器为null。
3)系统类加载器(system class loader):被称为系统(也称为应用)类加载器,它负责在JVM启动时加载来自Java命令的-classpath选项、java.class.path系统属性,或者CLASSPATH换将变量所指定的JAR包和类路径。程序可以通过ClassLoader的静态方法getSystemClassLoader()来获取系统类加载器。如果没有特别指定,则用户自定义的类加载器都以此类加载器作为父加载器。由Java语言实现,父类加载器为ExtClassLoader。
类加载器加载Class大致要经过如下8个步骤:
- 检测此Class是否载入过,即在缓冲区中是否有此Class,如果有直接进入第8步,否则进入第2步。
- 如果没有父类加载器,则要么Parent是根类加载器,要么本身就是根类加载器,则跳到第4步,如果父类加载器存在,则进入第3步。
- 请求使用父类加载器去载入目标类,如果载入成功则跳至第8步,否则接着执行第5步。
- 请求使用根类加载器去载入目标类,如果载入成功则跳至第8步,否则跳至第7步。
- 当前类加载器尝试寻找Class文件,如果找到则执行第6步,如果找不到则执行第7步。
- 从文件中载入Class,成功后跳至第8步。
- 抛出ClassNotFountException异常。
- 返回对应的java.lang.Class对象。
反射获取class对象的三种方式
- 类名.class属性
- 对象名.getClass()方法
- Class.forName(全类名)方法
public class ReflectTest {
public static final Logger logger = LoggerFactory.getLogger(ReflectTest.class);
@Test
public void test() throws ClassNotFoundException {
Class<Person> personClass = Person.class;
logger.info("{}",personClass);
//--------------------------
Person person = new Person();
Class<? extends Person> personClass1 = person.getClass();
logger.info("{}",personClass1);
//--------------------------
Class<?> personClass2 = Class.forName("reflect.Person");
logger.info("{}",personClass2);
//--------------------------
logger.info("{}",personClass==personClass1);
logger.info("{}",personClass==personClass2);
logger.info("{}",personClass1==personClass2);
}
}
class Person{
public static final Logger logger = LoggerFactory.getLogger(Person.class);
private String name;
private int age;
private int id;
public int m = 10;
static {
logger.info("Person类静态代码块加载。。。");
}
public Person() {
logger.info("Person类构造方法执行。。。");
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", id=" + id +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
反射获取构造方法
方法名 | 说明 |
---|---|
Constructor<?>[] getConstructors() | 返回所有公共构造方法对象的数组 |
Constructor<?>[] getDeclaredConstructors() | 返回所有构造方法对象的数组 |
Constructor getConstructor(Class<?>… parameterTypes) | 返回单个公共构造方法对象 |
Constructor getDeclaredConstructor(Class<?>… parameterTypes) | 返回单个构造方法对象 |
@Test
public void getConstructorTest() throws ClassNotFoundException, NoSuchMethodException {
Class<?> personClass = Class.forName("reflect.Person");
//返回所有公共构造方法对象的数组
Constructor<?>[] constructors = personClass.getConstructors();
for (Constructor<?> constructor : constructors) {
logger.info("{}",constructor);
}
//返回所有构造方法对象的数组
Constructor<?>[] declaredConstructors = personClass.getDeclaredConstructors();
for (Constructor<?> declaredConstructor : declaredConstructors) {
logger.info("{}",declaredConstructor);
}
//返回单个公共构造方法对象,没有则报异常NoSuchMethodException
/*Constructor<?> constructor = personClass.getConstructor(int.class);
logger.info("{}",constructor);*/
//返回单个构造方法对象
Constructor<?> constructor = personClass.getDeclaredConstructor(int.class);
logger.info("{}",constructor);
}
}
反射获取成员变量
方法名 | 说明 |
---|---|
Field[] getFields() | 返回所有公共成员变量对象的数组 |
Field[] getDeclaredFields() | 返回所有成员变量对象的数组 |
Field getField(String name) | 返回单个公共成员变量对象 |
Field getDeclaredField(String name) | 返回单个成员变量对象 |
@Test
public void getFieldTest() throws ClassNotFoundException {
Class<?> personClass = Class.forName("reflect.Person");
//返回所有公共成员变量对象的数组
Field[] fields = personClass.getFields();
for (Field field : fields) {
logger.info("{}",field);
}
//返回所有成员变量对象的数组
fields = personClass.getDeclaredFields();
for (Field field : fields) {
logger.info("{}",field);
}
//返回单个公共成员变量对象
try {
Field string_everbo = personClass.getField("everbo");
logger.info("{}",string_everbo);
} catch (NoSuchFieldException e) {
logger.info("{}",e);
}
//返回单个成员变量对象
try {
Field xiaoli = personClass.getDeclaredField("name");
logger.info("{}",xiaoli);
} catch (NoSuchFieldException e) {
logger.info("{}",e);
}
}
通过反射获取注解
package reflect;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.annotation.*;
import java.lang.reflect.Field;
/**
* @author: everbolee
* @createDate: 2021/2/20
*/
public class GetAnnotationTest {
public static final Logger logger = LoggerFactory.getLogger(GetAnnotationTest.class);
@Test
public void getAnnotationDemo() throws ClassNotFoundException, NoSuchFieldException {
Class student = Class.forName("reflect.Student");
//获取注解
Annotation[] annotations = student.getAnnotations();
for (Annotation annotation : annotations) {
logger.info("{}",annotation);
}
//获取注解value值
TableEverbo annotation = (TableEverbo) student.getAnnotation(TableEverbo.class);
String value = annotation.value();
logger.info("{}",value);
//获得指定注解
Field name = student.getDeclaredField("name");
FiledEverbo filedEverbo = name.getAnnotation(FiledEverbo.class);
logger.info("{}",filedEverbo.columnName());
logger.info("{}",filedEverbo.type());
logger.info("{}",filedEverbo.length());
}
}
//pojo类
@TableEverbo("db_student")
class Student{
@FiledEverbo(columnName = "db_id",type = "int",length = 10)
private int id;
@FiledEverbo(columnName = "db_age",type = "int",length = 10)
private int age;
@FiledEverbo(columnName = "db_name",type = "varchar",length = 3)
private String name;
public Student(){
}
public Student(int id, int age, String name) {
this.id = id;
this.age = age;
this.name = name;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", age=" + age +
", name='" + name + '\'' +
'}';
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
//自定义类名注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface TableEverbo{
String value();
}
//自定义属性注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface FiledEverbo{
String columnName();
String type();
int length();
}