java 注解和反射
一、什么是注解(Annnotation)
框架(MVC、springboot)的底层就是注解!
注解和注释的不同:注释是写给人看的,注解既是写给人看的也是写给程序(比如编译器、虚拟机)看的。源码有效的注解就是给编译器看的,因为还没运行呢这个注解就“功成身退”了,而运行时有效的注解是给虚拟机看的。
注解的作用:
1)给程序看,通过反射机制实现对元数据的访问
2)检查程序的正确性。比如@Override注解会检查重写的方法名是否写对了。
二、3个内置注解
1. @Override
2. @Deprecated
自己也可以给自己之前写的方法打上@Deprecated注解
3. @SuppressWarnings("参数") 镇压方法中的警告信息
三、4个元注解
元注解:注解的注解,写在注解的上一行,比如
- @Target:描述注解可以放在哪里(方法的上面还是类的上面还是字段的上面。。。)
- @Retention:注解的生命周期
- SOURE(源码时有效) < CLASS(编译成.class时依然有效) <RUNTIME (运行时依然有效,一般写这个)
- SOURCE是什么意思?
我们的@Data、@Setter、注解都是被@Retention(RetentionPolicy.SOURCE)去修饰的。写源码时,可以看到@Setter注解,但是编译后,.calss文件里多了setter方法,而@Setter注解却功成身退了(用反编译去看)。
还有,@Override在编译的时候就告诉编译器要检查重写的语法是否符合父类规定,生成的.class文件不含这些注解。
CLASS是什么意思?
注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期。这个的例子还没理解到位,以后再来填坑
- SOURCE是什么意思?
- SOURE(源码时有效) < CLASS(编译成.class时依然有效) <RUNTIME (运行时依然有效,一般写这个)
- @Documnet:注解本身是否包含在 JavaDoc 文档中。默认情况下,注解不会出现在生成的文档里
- @Inherited:子类可以继承父类中的注解
//Target表明 @MyAnnotation 可以用在方法上、类上、字段上、入参上,注解上 @Target({ElementType.PARAMETER, ElementType.METHOD, ElementType.Type(类上),ElementType.FIELD, ElementType.ANNOTATION_TYPE}) //Retention表明 @MyAnnotation从源码到运行时都有效,也就是一直有效 @Retention(value = RetentionPolicy.RUNTIME) //Documented 表明将注解 @MyAnnotation写入javadoc中 @Documented //Inherited表明注解@MyAnnotation可被子类继承 @Inherited //自定义一个注解 @interface MyAnnotation{ }四、自定义注解
为什么注解和反射放在一起讲,有什么关系吗?
先放一放这个问题,先问:自定义注解如何用?
用来打tag。比如,你想标记一批方法,用来给程序看,那你就自定义一个注解,遇到带这类标签的方法,就用springAOP的方式执行某一些操作,springAOP是通过反射实现的,这就回答了刚刚提出的问题“为什么注解和反射放在一起讲,有什么关系吗?”。别绕晕了,标签一定是打给程序看的,如果只是单纯的打标签给你自己看的话,那不就是注释嘛??!!
- 用@interface自定义注解,自动继承java.lang.annotation.Annotation接口
- 如果自定义注解只有一个参数,建议命名为value。这样使用注解的时候就不用指明参数名了
一个参数时:
//测试注解在类上有效 @MyAnnotation public class DiyAnnotation { //注解只有一个参数,且命名为value时,可以省略 value= @MyAnnotation2("2022 is wonderful enough! kickboxing!") public void test03(){ } } @Target(value = {ElementType.METHOD,ElementType.TYPE}) @Retention(value = RetentionPolicy.RUNTIME) //自定义一个注解 @interface MyAnnotation2{ String value() default ""; //只有一个参数 }多个参数时:
//测试注解在类上有效 @MyAnnotation public class DiyAnnotation { //测试注解在方法上有效 //三个参数都有默认值,注解可以都不写参数 @MyAnnotation public static void main(String[] args) { } //也可以写参数 @MyAnnotation(name = "December",age=27,word = "New Year is around the corner!") public void test01(){ } //也可以写部分参数 @MyAnnotation(word = "New start, must be wonderful!") public void test02(){ } } @Target(value = {ElementType.METHOD,ElementType.TYPE}) @Retention(value = RetentionPolicy.RUNTIME) //自定义一个注解 @interface MyAnnotation{ /* * 注解的参数们 * 必须带括号! * */ String name() default ""; //第一个参数,默认值为空 int age() default 18; //第二个参数,默认值为18 String word() default ""; //第三个参数,默认值为空 }四、注解是如何生效的*
换个问法:
- 为什么加一个@Data注解,编译后就自动生成了equals等一系列方法?
- 为什么给BookServiceImpl加一个@Service注解,就自动注入了一个bean?
解析注解的两个阶段/两种方式,刚好对应了上面两种场景:
- javac:编译器读取注解,然后自动生成代码,或者@Override对代码进行语法检查
- java: java虚拟机运行时基于反射去获取类的属性、执行类的方法。JVM扫描到@Service方法的时候,本质上是执行了BookServiceImpl类的setter方法
五、什么是反射(Reflection)
彻底理解反射机制:就像Linux认为all is file一样,java中一切都是对象,那么类是不是对象呢?是。类能是谁的对象呢?所有的类都是java.lang.Class类的对象,用Class c1 = 类.class的c1去表示,因为类也是对象,对象可以调用成员方法,那么c1就可以用java.lang.Class类定义的成员方法去获得类的各种信息。
此外,反射被称为框架的灵魂,主要是因为它赋予了我们在运行时1)获取类的信息 以及2)执行类中方法的能力。
—— 运行时获取任意一个对象所属的类Class c1【就跟你亲眼见到这个类的.java文件一样】
└─—— 获得了Class c1后,就能知道这个类的所有属性
├─ 包括public、protected、private
└─—— 获得了Class c1后,就能知道这个类的所有方法
├─ 方法的参数
└─ 方法的返回值
—— 运行时创建任意一个类的对象、调用类的方法。【就跟你打开其他.java文件,要new一个这个类的对象开始使用一样】
java本身是一个静态语言,正是反射让java具有动态性!
5.1 静态语言和动态语言
区分动态语言和动态网页技术
静态语言与动态语言:静态类型语言在编译时便已确定变量的类型,而动态类型语言的变量类型要到程序运行的时候,待变量被赋予某个值之后,才会具有某种类型
静态网页技术与动态网页技术:HTML是静态网页技术;JSP是动态网页技术,动态部分就是<% %>括起来的java代码。
5.2 反射的优缺点
优点:灵活性(静态语言->动态语言),利用反射开发出各种框架spring/mybatis/springboot,极大方便了我们的开发。
缺点:慢!(反射也是生成一个对象,和我们直接new一个对象比起来慢了几十几百倍)
5.3 反射相关的API
学习反射就是学习Class对象和java.lang.reflect对应的各种API,听到这里放心了吧!说到底还是和集合一样,学习各种API!而且十分简单!
5.4 获得反射对象
5.5 得到Class类对象的5种方式
1. java.lang.Class类
为了方便讲述,我们假设Person类是当前类
- Class类是反射的根源,反射前必须找到相应的Class对象。
- 这个Class对象中存储了Person类对象的真实类、类的属性、方法、实现了哪些接口等一切信息。
- 对于每个类,JRE都为其保存了一个不变的Class类型的对象,也就是说由系统建立的。
- Person类的所有对象共享并且记得这个Class类型的对象。
User user1 = new User(); User user2 = new User(); User user3 = new User(); System.out.println(user1.hashCode()); System.out.println(user2.hashCode()); System.out.println(user3.hashCode()); /** * 输出: * 2027961269 * 1586270964 * 1642360923 */ Class c1 = user1.getClass(); Class c2 = user2.getClass(); Class c3 = user3.getClass(); System.out.println(c1.hashCode()); System.out.println(c2.hashCode()); System.out.println(c3.hashCode()); /** * 输出: * 1343441044 * 1343441044 * 1343441044 */2. 得到Class类对象的5种方式
public class GetClassInstanceTest { public static void main(String[] args) throws ClassNotFoundException { Person person = new Student(); //法(一):通过继承object类的getClass方法 Class c1 = person.getClass(); System.out.println(c1); System.out.println(c1.hashCode()); //法(二):通过Class类的静态方法forName(类的路径),需要抛出异常 Class c2 = Class.forName("Student"); //应该是【包名.类名】,例如com.company.reflection.Student System.out.println(c2); System.out.println(c2.hashCode()); //法(三)类名.class属性 Class c3 = Student.class; System.out.println(c3); System.out.println(c3.hashCode()); //法(四):基本数据类型包装类的TYPE属性 Class c4 = Integer.TYPE; System.out.println(c4); System.out.println(c4.hashCode()); //法(五):已知子类型,获取父类型 Class c5 = c1.getSuperclass(); System.out.println(c5); System.out.println(c5.hashCode()); } } class Person{ String name; public String getName() { return name; } public void setName(String name) { this.name = name; } } class Student extends Person{ public Student() { this.name = "学生"; } } class Teacher extends Person{ public Teacher(){ this.name="老师"; } }对应结果:
class Student
356573597
class Student
356573597
class Student
356573597
int
1735600054
class Person
21685669
这5种方式中,只有forName是动态加载(运行时才加载,其他只要出现new了就是在编译时加载),其他都是静态加载。
5.5 哪些类可以有Class对象?
几乎所有
Class c1 = Object.class; //类 Class c2 = Comparable.class; //接口 Class c3 = String[].class; //一维数组 Class c4 = int[][].class; //二维数组 Class c5 = Override.class; //注解 Class c6 = ElementType.class; //枚举 Class c7 = Integer.class; //基本数据类型包装类 Class c8 = void.class; //void Class c9 = Class.class; //Class类本身 //------类型 getName() System.out.println(c1); // class java.lang.Object System.out.println(c2); // interface java.lang.Comparable System.out.println(c3); // class [Ljava.lang.String; System.out.println(c4); // class [[I System.out.println(c5); // interface java.lang.Override System.out.println(c6); // class java.lang.annotation.ElementType System.out.println(c7); // class java.lang.Integer System.out.println(c8); // void System.out.println(c9); // class java.lang.Class5.6 获取类的运行时结构
在module的src文件夹下新建包:com.company.empolyee,新建User.java文件。
public class User{ //私有 private String name; private int age; //共有 public int id; public User(String name, int age, int id) { this.name = name; this.age = age; this.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; } //私有方法 private void laugh(){ } }import com.sun.xml.internal.ws.api.model.wsdl.WSDLOutput; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; public class ClassLoaderTest { public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException { Class c1 = Class.forName("com.company.empolyee.User"); //1.获取类的名字 System.out.println(c1.getName()); //包名+类型 System.out.println(c1.getSimpleName()); //只有类名 /** * 输出: * com.company.empolyee.User * User */ System.out.println("=========================="); //2.获取类所有的成员变量 //2-1.getFields只能获取public成员变量 Field[] fields = c1.getFields(); for (Field field : fields) { System.out.println(field); } /** * 输出: * public int com.company.empolyee.User.id */ //2-2.getDeclaredFields可以获取所有访问权限的成员变量 Field[] declaredFields = c1.getDeclaredFields(); for (Field declaredField : declaredFields) { System.out.println(declaredField); } /** * 输出: * private java.lang.String com.company.empolyee.User.name * private int com.company.empolyee.User.age * public int com.company.empolyee.User.id */ System.out.println("========================="); //3.获取类指定的成员变量 //3-1.getField只能获取public Field id = c1.getField("id"); System.out.println(id); /** * 输出: * public int com.company.empolyee.User.id */ //3-2.getDeclaredField获取所有权限 Field name = c1.getDeclaredField("name"); System.out.println(name); /** * 输出: * private java.lang.String com.company.empolyee.User.name */ System.out.println("========================="); //4.获取类所有的成员方法 //4-1.public Method[] methods = c1.getMethods(); for (Method method : methods) { System.out.println(method); } /** * 输出:不仅输出了当前类自己定义的public成员方法,还有继承下来的所有public方法 * public java.lang.String com.company.empolyee.User.getName() * public void com.company.empolyee.User.setName(java.lang.String) * public int com.company.empolyee.User.getAge() * public void com.company.empolyee.User.setAge(int) * public final void java.lang.Object.wait() throws java.lang.InterruptedException * public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException * public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException * public boolean java.lang.Object.equals(java.lang.Object) * public java.lang.String java.lang.Object.toString() * public native int java.lang.Object.hashCode() * public final native java.lang.Class java.lang.Object.getClass() * public final native void java.lang.Object.notify() * public final native void java.lang.Object.notifyAll() */ System.out.println("==========================="); //4-2.获取本类的所有权限的所有方法(不包括继承来的 Method[] declaredMethods = c1.getDeclaredMethods(); for (Method declaredMethod : declaredMethods) { System.out.println(declaredMethod); } /** * 输出: * public java.lang.String com.company.empolyee.User.getName() * public void com.company.empolyee.User.setName(java.lang.String) * public int com.company.empolyee.User.getAge() * private void com.company.empolyee.User.haha() //私有的!! * public void com.company.empolyee.User.setAge(int) */ System.out.println("=========================="); //5.获取指定的类方法 //5-1.获取指定的public方法 一定要指定方法的参数,因为重载!! Method method = c1.getMethod("setAge", int.class); System.out.println(method); /** * public void com.company.empolyee.User.setAge(int) */ //5-2.获取指定的不限权限方法 Method laugh = c1.getDeclaredMethod("laugh", null); System.out.println(laugh); /** * private void com.company.empolyee.User.laugh() */ System.out.println("===========构造器=========="); //6.获取所有的构造器 //6-1.获取所有public构造器 Constructor[] constructors = c1.getConstructors(); for (Constructor constructor : constructors) { System.out.println(constructor); } /** * public com.company.empolyee.User(java.lang.String,int,int) */ //6-2.获取所有不限权限的构造器 Constructor[] declaredConstructors = c1.getDeclaredConstructors(); for (Constructor declaredConstructor : declaredConstructors) { System.out.println(declaredConstructors); } /** * [Ljava.lang.reflect.Constructor;@45ee12a7 */ System.out.println("==========单个的构造器========"); //7.获取指定的构造器 //7-1.获取指定的public构造器 Constructor constructor = c1.getConstructor(String.class, int.class,int.class); System.out.println(constructor); /** * public com.company.empolyee.User(java.lang.String,int,int) */ //7-2.获取指定的不限制权限的构造器 Constructor declaredConstructor = c1.getDeclaredConstructor(参数) System.out.println(declaredConstructor); } }说明:getMethod和getDeclaredMethod方法指定方法的参数时:
- 无参函数:null
- int:int.class
- String:String.class
- ....
5.7 通过上述API获得类的信息后,怎么用呢?—— 动态创建对象、动态执行方法、动态修改属性甚至操作private成员
注意01:User类必须有显式的无参构造方法,才能用
c1.newInstance()
动态创建对象(在java中,定义有参构造器后,不会自动生成无参构造器!!!C++会自动生成)。 否则:
注意02:动态调用方法的时候,注意使用invoke(对象,方法的参数)方法激活
注意03:动态操作属性的时候,用set(对象,值)方法
注意04:操作private构造器、属性、方法的时候,由于在类外不能操作private成员。我们需要调用setAccessable(true)方法关掉安全检测。
that is to say,反射可以操作private成员!
import com.company.empolyee.User; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; public class ReflectCoreOperation { public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException { //通过反射获取Class对象 Class c1 = Class.forName("com.company.empolyee.User"); //动态创建User对象法(一):反射出的class对象直接调用newInstance() //User user1 = (User) c1.newInstance(); //动态创建User对象法(二):利用反射出的构造器 Constructor constructor = c1.getDeclaredConstructor(String.class, int.class, int.class); User user2 = (User)constructor.newInstance("December", 28, 28); //动态调用User类方法 Method setName = c1.getDeclaredMethod("setName", String.class); setName.invoke(user2,"please, hang in there!"); //激活 System.out.println(user2.getName()); //动态设置属性 Field name = c1.getDeclaredField("name"); name.setAccessible(true); name.set(user2,"stick it out!"); System.out.println(user2.getName()); } }5.8 性能测试
测试3种情况的时间长短:普通方法进行方法调用、反射方式进行方法调用、关掉安全检测的反射方式进行方法调用
import com.company.empolyee.User; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; public class PerformTest { //普通方式 public static void test01(){ User user = new User("December", 28, 28); long startTime = System.currentTimeMillis(); for (int i = 0; i < 1000000000; i++) { user.getName(); } long endTime = System.currentTimeMillis(); System.out.println("普通方式10亿次:"+ (endTime-startTime)+"ms"); } //反射方式 public static void test02() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { User user = new User("December", 28, 28); Class c1 = user.getClass(); Method getName = c1.getDeclaredMethod("getName", null); long startTime = System.currentTimeMillis(); for (int i = 0; i < 1000000000; i++) { getName.invoke(user,null); } long endTime = System.currentTimeMillis(); System.out.println("反射方式10亿次:"+ (endTime-startTime)+"ms"); } //反射方式+关掉安全检测 public static void test03() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { User user = new User("December", 28, 28); Class c1 = user.getClass(); Method getName = c1.getDeclaredMethod("getName", null); getName.setAccessible(true); long startTime = System.currentTimeMillis(); for (int i = 0; i < 1000000000; i++) { getName.invoke(user,null); } long endTime = System.currentTimeMillis(); System.out.println("反射方式10亿次:"+ (endTime-startTime)+"ms"); } public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { test01(); test02(); test03(); } }普通方式10亿次:3ms
反射方式10亿次:1530ms
反射方式10亿次:1074ms
由此看出
1)在一开始我们就说了反射的缺点是慢! 牺牲时间增加程序的灵活性!
2)对比第二个和第三个结果可以看出,关掉安全检测可以节省不少时间。如果程序频繁用到反射,可以关掉安全监测来提升效率,现在又是牺牲安全增加效率!
5.9 还能干什么?获取泛型的信息
一旦编译完成后,所有和泛型有关的信息将会被擦除。那如何获取一个类中泛型的信息呢?
因为类最初被加载的时候所有信息都存储在Class对象里,或许Class对象里保存了泛型的信息!
import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.List; import java.util.Map; public class GetGenericInfo { //成员方法的参数是泛型 public void test01(Map<String,Integer> map, List<Integer> list){ System.out.println("test01"); } //成员方法的返回值是泛型 public Map<String,Integer> test02(){ System.out.println("test02"); return null; } public static void main(String[] args) throws NoSuchMethodException { Class c1 = GetGenericInfo.class; Method method01 = c1.getDeclaredMethod("test01", Map.class, List.class); //获取泛型参数的信息 System.out.println("获取泛型参数的信息"); Type[] genericParameterTypes = method01.getGenericParameterTypes(); for (Type genericParameterType : genericParameterTypes) { System.out.println(genericParameterType); } //进一步获取泛型规定的类型 System.out.println("进一步获取泛型规定的类型"); for (Type genericParameterType : genericParameterTypes) { if(genericParameterType instanceof ParameterizedType){ //ParameterizedType:参数化类型 Type[] actualTypeArguments = ((ParameterizedType) genericParameterType).getActualTypeArguments(); for (Type actualTypeArgument : actualTypeArguments) { System.out.println(actualTypeArgument); } } } Method method02 = c1.getDeclaredMethod("test02", null); //获取泛型返回值的信息 System.out.println("获取泛型返回值的信息"); Type genericReturnType = method02.getGenericReturnType(); System.out.println(genericReturnType); //进一步获取泛型返回值规定的类型 System.out.println("进一步获取泛型返回值规定的类型"); if(genericReturnType instanceof ParameterizedType){ //ParameterizedType:参数化类型 Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments(); for (Type actualTypeArgument : actualTypeArguments) { System.out.println(actualTypeArgument); } } } }获取泛型参数的信息
java.util.Map<java.lang.String, java.lang.Integer>
java.util.List<java.lang.Integer>
进一步获取泛型规定的类型
class java.lang.String
class java.lang.Integer
class java.lang.Integer
获取泛型返回值的信息
java.util.Map<java.lang.String, java.lang.Integer>
进一步获取泛型返回值规定的类型
class java.lang.String
class java.lang.Integer
5.10 还能干什么?获取注解信息
import java.lang.annotation.*; import java.lang.reflect.Field; public class GetAnnotationInfo { public static void main(String[] args) throws NoSuchFieldException { Class c1 = Student_Table.class; //1.获取类的注解 Annotation[] annotations = c1.getAnnotations(); for (Annotation annotation : annotations) { System.out.println(annotation); } /** * 输出: * @TableName(value=db_stu) */ //2.获取注解value的值 TableName annotation = (TableName)c1.getAnnotation(TableName.class); System.out.println(annotation.value()); /** * 输出: * db_stu */ //3.获取字段的注解 Field name = c1.getDeclaredField("name"); TableField annotation1 = name.getAnnotation(TableField.class); //说明是哪个注解,因为一个字段可能被多个注解修饰 System.out.println(annotation1.name()); System.out.println(annotation1.type()); System.out.println(annotation1.length()); /** * 输出: * name * varchar * 3 */ } } @TableName("db_stu") class Student_Table{ @TableField(name = "name",type = "varchar",length = 3) String name; @TableField(name = "age",type = "int",length = 1) int age; @TableField(name = "age",type = "int",length = 1) int id; } /** * 数据库表名注解 */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @interface TableName{ String value(); } /** * */ @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @interface TableField{ String name(); String type(); int length(); }