本文为自学 Java注解和反射机制的个人学习笔记,因个人习惯和时间关系,仅记录了本人为曾了解和和认为重要的知识点,所以如要学习建议观看原视频学习
注解
基础的注解概念的解释可以查看这里:
一个简单的自实现的注解如下:
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation1 {
String name();
}
其中,参数的格式是 类型 名字()
。
-
@interface
来定义一个注解 -
@Target
描述了注解的使用范围 -
@Retention
表述需要在什么级别保存该注释信息,用于描述注解的生命周期,其中 (SOURCE<CLASS<RUNTIME
) -
@Document
说明该注解将包含在 javadoc中
(具体注解实际使用方式可以看最后一节章关于 使用反射对象来获取所注解对象的注解信息的介绍。)
反射 reflection
反射概述
反射机制 允许程序执行期间借助 Reflection API
来获取任何类的内部属性和方法。
正常方式 : 引入“包类”的名称->new实例化->获得实例化对象
反射方式: 实例化对象->getClass()方法->获得完整的“包类”名称
获得反射对象
java反射机制提供的功能:
- 在运行时判断任意一个对象所属的类
- 在运行时构造任意一个类的对象
- 在运行时判断任意一个类所具有的成员变量和方法
- 在运行时获取泛型信息
- 在运行时调用任意一个对象的成员变量和方法
- 在运行时处理 注解
- 生成动态代理 (AOP)
反射的优缺点:
- 优点: 可以实现动态创建对象和编译,灵活
- 缺点:对性能有影响。反射实际上是一种解释操作,需要告诉JVM需要做什么和要求,要慢于直接(new)执行相同的操作。
反射相关的API
java.lang.Class
代表一个类java.lang.reflect.Method
代表类的方法java.lang.reflect.Field
代表类的成员变量java.lang.reflect.Constructor
代表类的构造器
获得反射对象
package reflection;
public class Reflection0 {
public static void main(String[] args) throws ClassNotFoundException {
// 通过反射来获取类的class对象
Class cl1 = Class.forName("reflection.User");
// Class cl2 = Class.forName("reflection.Reflection0");
Class cl2 = User.class;
// Class cl3 = Class.forName("reflection.Reflection0");
Class cl3 = (new User()).getClass();
// 一个类在内存中只有一个Class对象
// 一个类被加载后,类的整个结构都会被封装在Class对象中
System.out.println(cl1.hashCode());
System.out.println(cl2.hashCode());
System.out.println(cl3.hashCode());
}
}
// pojo:
class User{
String name;
int age;
int id;
public User() {
}
public User(String name, int age, int id) {
this.name = name;
this.age = age;
this.id = id;
}
// getter setter方法
}
一切类的父类 Object
中的 getClass()
方法返回值即为Java反射的源头,所以可以通过对象反射来求出类的名称。
任何一个对象都有一个自己的class对象,提供了该类的属性、方法、构造器、实现的接口。且对象仅能通过系统创建、在JVM中只有一个Class实例、一个Class对象对应一个加载到JVM中的.class文件、每个类的实例都记着自己是由那个Class对象生成、通过Class对象可以完整的获取到一个类被加载的结构、Class对象是Reflection的根源,获取相应的Class对象后才能动态加载和运行某个类
Class类的常用方法:
那些类型可以有Class对象:
- class :外部类、成员、局部内部类、匿名内部类
- interface:
- []
- enum
- annotation
- primitive type: 基本数据类型
- void
只要元素类型和维度一致,class就相同
类的初始化
只有在 类的主动引用 会发生类的初始化,如:
- main
- new
- 调用非常量的静态资源
- reflect包中的反射调用
- 当初始化一个类时,父类未被初始化会先初始化父类
类的被动引用 不会发生类的初始化, 如果:
- 访问静态域,仅真正声明该域的类会初始化(通过子类调用父类的静态变量,子类不初始化)
- 数组引用
- 引用常量(常量在链接阶段就已存入到调用类的 常量池中了)
类加载器的作用
类加载的作用:将class文件字节码内容加载到内存中,并将这些静态数据转化为方法区的运行时的数据结构,然后在堆中生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口
加载的类会进行类缓存
类加载器的类型:
- 引导类加载器:C++编写,JVM自带加载器,负责Java平台核心库 用来装载核心类库。该加载器无法直接获取
- 扩展类加载器:负责jre/lib/ext下的jar包或者
-D java.ext.dirs
指定的jar包装入工作库 - 系统类加载器:负责
java -classpath
或-D java.class.path
所指目录下的类与jar包装入工作,是最常用的加载器
查看方法:
package reflection;
public class ClassLoaderTest {
public static void main(String[] args) throws ClassNotFoundException {
// 获取 系统类加载器
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
System.out.println(systemClassLoader);
// 获取 系统类加载器的父类加载器: 扩展类加载器
ClassLoader parent = systemClassLoader.getParent();
System.out.println(parent);
// 获取 扩展类的父类加载器:根加载器:引导类加载器(C/C++)
ClassLoader parentParent = parent.getParent();
System.out.println(parentParent);
// 测试当前类是哪个加载器加载的
ClassLoader classLoader = Class.forName("reflection.ClassLoaderTest").getClassLoader();
System.out.println(classLoader);
// 显然,该类是用户自定义类,故为系统类加载器加载
// 测试JDK内置类是哪个加载器加载的
ClassLoader classLoader1 = Class.forName("java.lang.Object").getClassLoader();
System.out.println(classLoader1);
// 显然是核心加载的
// 系统类加载器可以加载的路径:
System.out.println(System.getProperty("java.class.path"));
/*
D:\JavaDevelopmentKit8\jre\lib\charsets.jar;
D:\JavaDevelopmentKit8\jre\lib\deploy.jar;
...
D:\JavaDevelopmentKit8\jre\lib\rt.jar;
G:\learning\spring\annotation&reflection\target\classes;
D:\JetBrains\IntelliJ IDEA 2020.1.3\lib\idea_rt.jar
*/
// 其次 双亲委派机制 可以保证类的加载的安全有效性
}
}
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@1b6d3586
null
sun.misc.Launcher$AppClassLoader@18b4aac2
null
通过反射来操作对象:(实例化、调用方法、访问属性等
package reflection;
import pojo.User;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
/**
* date: 2021/3/19 14:51
* author: 31415926535x
*/
// 动态创建对象
public class DynamicInstance {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
// 通过包名路径获得Class对象
Class cl = Class.forName("pojo.User");
// 使用newInstance()构造对象
// 本质是调用类的无参构造器
User user = (User)cl.newInstance();
System.out.println(user);
System.out.println("=================");
// 通过构造器创建对象
Constructor constructor = cl.getConstructor(String.class, int.class, int.class);
user = (User)constructor.newInstance("2333", 233, 666);
System.out.println(user);
System.out.println("=================");
// 通过反射调用普通方法
// 即 method.invoke(对象, 方法的值) 调用普通方法
User user2 = (User)cl.newInstance();
cl.getDeclaredMethod("setName", String.class).invoke(user2, "emmmm");
System.out.println(user2);
System.out.println("=================");
// 通过反射操作属性
// private属性不能访问
// 可以通过设置setAccessible为true
// 参数值为true指示反射的对象在使用时应该取消Java语言访问检查,可以使得提高反射的效率,并能访问私有成员
User user3 = (User)cl.newInstance();
try {
cl.getDeclaredField("name").set(user3, "user3");
System.out.println(user3);
}catch (Exception e){
e.printStackTrace();
System.out.println("不能直接修改private属性");
}
System.out.println("=================");
Field name = cl.getDeclaredField("name");
name.setAccessible(true);
name.set(user3, "user3");
System.out.println(user3);
}
}
User{name='null', age=0, id=0}
=================
User{name='2333', age=233, id=666}
=================
User{name='emmmm', age=0, id=0}
=================
不能直接修改private属性
=================
User{name='user3', age=0, id=0}
java.lang.IllegalAccessException: Class reflection.DynamicInstance can not access a member of class pojo.User with modifiers ""
at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:102)
at java.lang.reflect.AccessibleObject.slowCheckMemberAccess(AccessibleObject.java:296)
at java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:288)
at java.lang.reflect.Field.set(Field.java:761)
at reflection.DynamicInstance.main(DynamicInstance.java:49)
效率分析
理论上,直接new实例化 > 关闭检测setAcessible(true) > 不关闭的反射方式
package reflection;
import pojo.User;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.Time;
/**
* date: 2021/3/19 15:18
* author: 31415926535x
*/
public class SetAccessibleTest {
// 普通方式调用
public static void test1(){
User user = new User();
long st = System.currentTimeMillis();
for (int i = 0; i < 100000000; i++) {
user.getName();
}
long et = System.currentTimeMillis();
System.out.println("普通方式调用: " + (et - st) + "ms");
}
// 反射方式调用
public static void test2() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
User user = new User();
Class cl = Class.forName("pojo.User");
Method getName = cl.getDeclaredMethod("getName", null);
long st = System.currentTimeMillis();
for (int i = 0; i < 100000000; i++) {
getName.invoke(user, null);
}
long et = System.currentTimeMillis();
System.out.println("反射方式调用: " + (et - st) + "ms");
}
// 关闭检测
public static void test3() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
User user = new User();
Class cl = Class.forName("pojo.User");
Method getName = cl.getDeclaredMethod("getName", null);
getName.setAccessible(true);
long st = System.currentTimeMillis();
for (int i = 0; i < 100000000; i++) {
getName.invoke(user, null);
}
long et = System.currentTimeMillis();
System.out.println("反射方式调用: " + (et - st) + "ms");
}
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException {
test1();
test2();
test3();
}
}
普通方式调用: 0ms
反射方式调用: 171ms
反射方式调用 关闭检测: 125ms
获取泛型信息
对于反射获取到的一个方法method,可以通过 getGenericParameterTypes
等方法来获取到参数、返回值等泛型数据的信息。(泛型数据信息会在编译器被丢弃,故可以通过此方法来重新获取到)
通过反射对象来获取注解信息
通过反射获取的class对象可以获取其身上所标注的注解的值,通过此即可达到使用注解来设定某些对象的属性值,即各种框架中注解的执行机制:
package reflection;
import java.lang.annotation.*;
import java.lang.reflect.Field;
/**
* date: 2021/3/19 15:34
* author: 31415926535x
*/
// 反射操作注解
public class AnnotationAndReflection {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
Class cl = Class.forName("reflection.Student");
// 通过反射的方式获取注解
Annotation[] annotations = cl.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation);
}
// 获取注解的值
TableClassAnnotation tableClassAnnotation = (TableClassAnnotation) cl.getAnnotation(TableClassAnnotation.class);
System.out.println(tableClassAnnotation.value());
// 这里的操作即可解释各种框架中的注解所设定的 value 值的作用机制了
// 即,通过设定注解的值,如 Controller(value="./static") 即制定了Controller这个注解对象的值
// 相关框架即可通过以上方式获取值,进而实现指定注解值来设置相关类的某个(类、属性)的值
// 获得类指定的注解
Field name = cl.getDeclaredField("name");
TableFieldAnnotation annotation = name.getAnnotation(TableFieldAnnotation.class);
System.out.println(annotation.columnName());
System.out.println(annotation.type());
System.out.println(annotation.length());
}
}
// 自定义注解
// 类名注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface TableClassAnnotation{
String value();
}
// 属性的注解
// 可以理解为对应数据库中每个字段的属性
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface TableFieldAnnotation{
String columnName();
String type();
int length();
}
@TableClassAnnotation("db_studnet")
class Student{
@TableFieldAnnotation(columnName = "db_name", type = "varchar", length = 20)
private String name;
@TableFieldAnnotation(columnName = "db_age", type = "int", length = 10)
private int age;
@TableFieldAnnotation(columnName = "db_id", type = "int", length = 10)
private int id;
public Student() {
}
public Student(String name, int age, int id) {
this.name = name;
this.age = age;
this.id = id;
}
// get set tostring方法:
}
@reflection.TableClassAnnotation(value=db_studnet)
db_studnet
db_name
varchar
20