注解和反射
摘要:
注解和反射是一切框架的底层基础,这里学习一下注解和反射能更好的加深对各大框架的理解和学习程度;
1 注解
1.1.1 元注解
元注解是JDK中的注解,很多注解都是在元注解的基础上进行创建的;
以下是几个常用的元注解:
//用于声明注解可以使用在哪些地方:如下表示定义的注解使用在方法上
@Target(value = ElementType.METHOD)
//是否将我们的注解生成在JavaDoc中
@Documented
//表示在什么时期有效 runtime>class>sources 也就是说runtime时期有效那么class时期和sources时期都有效后面
@Retention(value = RetentionPolicy.SOURCE)
//此注解表示子类可以继承父类的注解
@Inherited
public @interface TestAnnotation01 {
}
//@Target(value=ElementType.METHOD)注解: 可以看书此注解有一个属性成员,所以我们在使用此注解的时候必须要有value属性
public @interface Target {
/**
* Returns an array of the kinds of elements an annotation type
* can be applied to.
* @return an array of the kinds of elements an annotation type
* can be applied to
*/
ElementType[] 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
*/
TYPE_PARAMETER,
/**
* Use of a type
*
* @since 1.8
*/
TYPE_USE
}
//@Documented
public @interface Documented {
}
//@Retention(value = RetentionPolicy.SOURCE)
public @interface Retention {
/**
* Returns the retention policy.
* @return the retention policy
*/
RetentionPolicy value();
}
//RetentionPolicy 枚举类
public enum RetentionPolicy {
/**
* Annotations are to be discarded by the compiler.
*/
SOURCE,
/**
* Annotations are to be recorded in the class file by the compiler
* but need not be retained by the VM at run time. This is the default
* behavior.
*/
CLASS,
/**
* Annotations are to be recorded in the class file by the compiler and
* retained by the VM at run time, so they may be read reflectively.
*
* @see java.lang.reflect.AnnotatedElement
*/
RUNTIME
}
//@Inherited
public @interface Inherited {
}
1.1.2 自定义注解
自定义注解其实很简单,我们参考元注解的写法就可以完成自定义注解的实现
下面举一个简单的例子即可:(注意,一般@Target注解和@Retention注解是一定存在的)
@Target(value = {ElementType.TYPE,ElementType.METHOD})//类上面,以及方法上该注解有效
@Retention(value = RetentionPolicy.RUNTIME)//该注解在运行时有效,那么在其他几种情况下也是有效的
public @interface TestAnnotation02 {
String value() default "";//可以定义默认值这里默认值为空字符串
int number() default 0;
String[] type() default {"type1","type2"};//定义数组类型的参数
}
//使用自定义注解
@TestAnnotation02(value = "test01",type = {"测试01"},number = 1)
public class TestController {
}
注意事项:
当我们的注解中只有一个属性的时候,我们可以直接使用value()代替,这样value只用一个值的时候是可以省略的
并且如果没有默认值是不允许为空的
代码演示如下:
@Target(value = {ElementType.TYPE, ElementType.METHOD})//类上面,以及方法上该注解有效
@Retention(value = RetentionPolicy.RUNTIME)//该注解在运行时有效,那么在其他几种情况下也是有效的
public @interface TestAnnotation03 {
String[] value() default {""};
}
//使用自定义注解
@TestAnnotation02(value = "test01",type = {"测试01"},number = 1)
@TestAnnotation03({"测试","test01"})
public class TestController {
}
2 反射
我们学习注解只是一个"小菜",其实注解作用就是通过反射来完成读取里面内容的
2.1 Class类的创建方式有哪些
public class Test01 {
public static void main(String[] args) throws Exception{
Person p1 = new Student("01","0001");
//第一种获取Class的方式:对象直接获取
Class c1 = p1.getClass();
System.out.println(c1.hashCode());//471910020
//第二种获取方式: 通过Class.forName()方法获取对象
Class c2 = Class.forName("show.mrkay.reflex.Student");
System.out.println(c2.hashCode());//471910020
//方式三: 通过类型进行获取Class对象
Class c3 = Student.class;
System.out.println(c3.hashCode());//471910020
//方式四: 获取内置类的Class(这种方式只能获取JDK中已有的内置类:比如Integer,Double等)
Class c4 = Double.TYPE;
System.out.println(c4);//double
//获取父类类型
Class spc1 = c1.getSuperclass();
System.out.println(spc1.hashCode());//531885035
}
}
class Person{
private Integer age;
private String sex;
@Override
public String toString() {
return "Person{" + "age=" + age + ", sex='" + sex + '\'' + '}';
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public Person() {
}
public Person(Integer age, String sex) {
this.age = age;
this.sex = sex;
}
}
class Student extends Person{
private String username;
private String password;
public Student(String username, String password) {
this.username = username;
this.password = password;
}
public Student() {
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
@Override
public String toString() {
return "User{" + "username='" + username + '\'' + ", password='" + password + '\'' + '}';
}
public void setPassword(String password) {
this.password = password;
}
}
2.2 Java中可以获取Class的类型
/**
* Java中可以获取Class对象的类型
*/
//超类
Class c1 = Object.class;
//接口
Class c2 = Comparable.class;
//数组
Class c3 = String[].class;
//二维数组
Class c4 = String[][].class;
//注解
Class c5 = Override.class;
//枚举
Class c6 = ElementType.class;
//void 关键字
Class c7 = void.class;
//Class本省
Class c8 = Class.class;
2.3 类加载,链接,初始化(反射)
2.3.1 类加载&类链接&初始化过程
注意:
类加载是要将类加载到内存中的,所以在我们的程序使用某一个类的时候,果果内存中还没有加载该类,则系统会主动进行类的加载;
类加载的步骤:
总体分为三个步骤: 1 类的加载(Load); 2 类的链接; 3 类的初始化;
具体步骤如下:
1 类加载
将类的class字节码文件读取到内存中,并且为这个字节码创建一个java.lang.Class对象,这个过程都是由类加载器完成的;
其实就是将字节码文件加载入内存之后,将字节码文件这种静态数据转换成方法区的运行时数据结构,然后生成一个 代表这个类的java.lang.Class对象;
2 类的链接
将类的二进制数据合并到JRE中;(JRE其实就是运行环境)
这个过程其实就是将java类的二进制代码合并到JVM运行状态中的过程;
这个过程内部会经过三个步骤:
验证: 就是对类的二进制数据进行安全检测,确保其符合JVM规范,没有任何安全问题;
准备: 正式为类变量(static修饰的类属性)分配内存空间,并为这些类变量设置默认值,并且这一过程都是在方法区中完成;
解析: 将JVM虚拟机常量池中的符号引用替换为具体的地址引用过程;(其实这时候就是将常量池中的变量指向类变量的内存地址)
3 类的初始化
JVM负责完成对类的初始化;
类的初始化其实就是执行类构造器()方法的过程,类构造器()方法是由编译时期自动收集类中所有的类变量(static)的赋值动作和静态代码块的语句合并产生的,也就是这个方法会将所有静态类变量和静态代码块中信息合并到一起进行处理过程;(所以可以看出,这里的类构造器其实是构造类的信息的,不是构造类对象的);
类初始化的时候如果发现父类没有进行初始化就会首先将父类进行初始化,然后再初始化该类;
JVM虚拟机初始化类的时候,会保证()方法在多线程情况下被正确加锁和同步;
注意:
上面的类加载步骤和类的初始化过程中有以下几点很重要:
1 类的字节码文件是被读取到内存中的;
2 每一个类对象的java.lang.Classs都是在类加载的时候产生的,类中的所有属性和信息都记录着在这个Class中;
3 产生的java.lang.Class是由类加载器完成的;
4 类的链接会对类中的类变量(静态变量)进行内存分配并赋值默认值;
5 类的初始化会执行clinit()类构造器方法,会对一些类变量(静态变量)和静态代码块信息进行合并,合并之后信息就得到了我们能用的java.lang.Class;后面我们就可以使用Class操作了.
2.3.2 什么时候会进行类初始化
JVM进行类的初始化也是分时候的不是所有情况都会触发类的初始化:
1 类的主动引用;(这种情况下一定会发生类的初始化)
2 类的被动引用;(这种情况不会发生类的初始化)
2.3.2.1 主动引用
1 当JVM启动,会先初始化main方法所在的类;
2 当我们new一个对象的时候会触发JVM执行类的初始化;
3 当调用类的静态变量(final修饰的常量除外)和静态方法,JVM会执行类的初始化;
4 反射会触发类的初始化;
5 当初始化一个类的时候,如果其父类没有进行初始化会首先进行父类的初始化;
2.3.2.2 被动引用
1 当访问静态域时,只有真正声明的域所在类才会被初始化(也就是说:当我们使用子类去调用父类的静态变量或者方法时只能触发父类被初始化,并不会导致子类被初始化);
2 通过数组定义类的引用,不会触发此类的初始化;
3 引用静态常量不会触发类此类的初始化因为在类链接时已经对其进行赋值了(放入了常量池中);
//代码演示
public class Test03 {
static {
n = 1;
}
static int n = 2;
public static void main(String[] args) throws ClassNotFoundException {
//这样写是因为在类初始化的时候类构造器会类变量(静态变量)的信息进行合并,所以这里如果是2证明执行了初始化
System.out.println("n值为2证明main方法所在类执行了初始化:" + n);
/**
* 主动引用
*/
//new 对象触发初始化
//Son son = new Son();
//调用静态类的静态常量触发初始化;
// System.out.println(Son.sonAge);
//反射会被初始化
//Class.forName("show.mrkay.reflex.Son");
/**
* 被动引用
*/
//访问静态域只触发静态域所在类发生初始化
//System.out.println(Son.fatherAge);//父类被初始化,子类没有
//数组不触发初始化
//Son[] arr = new Son[5];//父类子类都没有初始化
//引用静态常量
System.out.println(Son.NUM);//父类子类都没有被初始化
}
}
class Father {
static int fatherAge = 50;
static {
System.out.println("父类被初始化");
}
}
class Son extends Father {
static int sonAge = 25;
static {
System.out.println("子类被初始化");
m = 300;
}
static int m = 100;
static final int NUM = -1;
}
2.4 类加载器
2.4.1 类加载器的作用
将字节码文件加载到内存中中,并将这些静态数据转换成方法区的运行时数据结构,然后在堆中生成一个代表这个类的java.lang.Class对象,作为方法区中数据的访问入口;
类缓存
类加载还是很智能的,当我们的某个类被类加载器加载过了,会对被类加载器缓存一段时间,这段时间如果又一次进入,则不会再一次执行该类的加载过程,而是从缓存中直接取出,当缓存时间逾期了,JVM的垃圾回收机制会对其进行回收;
2.4.2 JVM类加载器的几种类型
引导类加载器:
底层使用C++编写,JVM自带的类加载器,负责Java平台核心类库的加载;我们无法直接获取;
扩展类加载器:
负责jre\lib\ext\目录下的jar包或者-D java.ext.dirs指定目录下的jar包装入工作库;
系统类加载器:
我们常用的类加载器,负责java - classpath或者-D java.class.path所指的目录下的jar包或者工作库;
//获取几种类加载器的代码示例如下
public class Test04 {
public static void main(String[] args) throws ClassNotFoundException {
//系统类加载器
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
//扩展类加载器
ClassLoader expClassLoader = systemClassLoader.getParent();
//获取引导类加载器(因为获取不到所以应该为null)
ClassLoader guideClassLoader = expClassLoader.getParent();
System.out.println(systemClassLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2
System.out.println(expClassLoader);//sun.misc.Launcher$ExtClassLoader@1c20c684
System.out.println(guideClassLoader);//null
//获取当前类是哪一个类加载器加载的
ClassLoader currentClassLoader = Class.forName("show.mrkay.reflex.Test04").getClassLoader();
//测试JDK的Object是有哪一个加载器加载的
ClassLoader objClassLoader = Class.forName("java.lang.Object").getClassLoader();
System.out.println(currentClassLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2
System.out.println(objClassLoader);//null
//获取系统类加载器可以加载哪些路径下的类
String path = System.getProperty("java.class.path");
System.out.println(path);
/**
F:\JDK1.8\jre\lib\charsets.jar;
F:\JDK1.8\jre\lib\deploy.jar;
F:\JDK1.8\jre\lib\ext\access-bridge-64.jar;
F:\JDK1.8\jre\lib\ext\cldrdata.jar;
F:\JDK1.8\jre\lib\ext\dnsns.jar;
F:\JDK1.8\jre\lib\ext\jaccess.jar;
F:\JDK1.8\jre\lib\ext\jfxrt.jar;
F:\JDK1.8\jre\lib\ext\localedata.jar;
F:\JDK1.8\jre\lib\ext\nashorn.jar;
F:\JDK1.8\jre\lib\ext\sunec.jar;
F:\JDK1.8\jre\lib\ext\sunjce_provider.jar;
F:\JDK1.8\jre\lib\ext\sunmscapi.jar;
F:\JDK1.8\jre\lib\ext\sunpkcs11.jar;
F:\JDK1.8\jre\lib\ext\zipfs.jar;
F:\JDK1.8\jre\lib\javaws.jar;
F:\JDK1.8\jre\lib\jce.jar;
F:\JDK1.8\jre\lib\jfr.jar;
F:\JDK1.8\jre\lib\jfxswt.jar;
F:\JDK1.8\jre\lib\jsse.jar;
F:\JDK1.8\jre\lib\management-agent.jar;
F:\JDK1.8\jre\lib\plugin.jar;
F:\JDK1.8\jre\lib\resources.jar;
F:\JDK1.8\jre\lib\rt.jar;
F:\IDEA\Workingfolder\Annotation-Reflex\target\classes;
F:\IDEA\IntelliJ IDEA 2020.1\lib\idea_rt.jar
*/
}
}
注意:
这里提一下双亲委派机制,
类加载器对我们的类进行加载的时候,会进行查找,首先会在自己的JDK包中进行查找,如果存在了就直接加载,如果我们自己定义了一个java.lang.String的类,这个类是不能够使用的,因为我们JDK中有一个这样的类,他会使用自己JDK中的类而不是我们自己定义的,这就是双亲委派机制;
2.5 Class获取类的运行时结构
就是使用java.lang.Class获取类中的各个属性和方法等 这里其实就是简单的方法学习了
//代码演示:
public class Test05 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException {
//反射获取类
Class<?> uClass = Class.forName("show.mrkay.reflex.User");
//获取类名
String className = uClass.getName();
System.out.println(className);
//获取类的指定属性(public)
// Field field = uClass.getField("age");//会抛出异常
// System.out.println(field);
//获取属性包括私有属性
Field ageFiled = uClass.getDeclaredField("age");
System.out.println(ageFiled);
//获取所有public属性
Field[] fields = uClass.getFields();
for (Field field1 : fields) {
System.out.println(field1);
}
//获取所有属性包括私有的
Field[] all = uClass.getDeclaredFields();
for (Field field : all) {
System.out.println(field);
}
//获取所有方法: 此方法可以将父类中的所有共有方法获取出来,不包括私有的方法
Method[] methods = uClass.getMethods();
for (Method method : methods) {
System.out.println(method);
}
//此方法获取所有的方法只能获取本类中的所有方法,包括私有的方法
Method[] declaredMethods = uClass.getDeclaredMethods();
for (Method declaredMethod : declaredMethods) {
System.out.println(declaredMethod);
}
//根据方法名获取执行的方法
Method getAgeMethod = uClass.getDeclaredMethod("getAge", null);
//获取指定的方法:多个参数的
Method printMethod = uClass.getDeclaredMethod("print", String.class, int.class);
System.out.println(printMethod);
//获取标准类名
String canonicalName = uClass.getCanonicalName();
System.out.println(canonicalName);
//获取构造器
Constructor<?> constructor = uClass.getDeclaredConstructor(null);
System.out.println(constructor);
//获取元部件Class类型
Class<?> componentType = uClass.getComponentType();
System.out.println(componentType);
}
}
class User {
private String username;
private Integer age;
public String phoneNumber;
//私有方法
private void print(String test, int number) {
System.out.println("字符串为:" + test + number);
}
public void sum(int a, int b) {
System.out.println("SUM=" + (a + b));
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getPhoneNumber() {
return phoneNumber;
}
public void setPhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
@Override
public String toString() {
return "User{" + "username='" + username + '\'' + ", age=" + age + ", phoneNumber='" + phoneNumber + '\'' + '}';
}
public User(String username, Integer age, String phoneNumber) {
this.username = username;
this.age = age;
this.phoneNumber = phoneNumber;
}
public User() {
}
}
2.6 反射动态创建对象执行方法
public class Test06 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException, NoSuchFieldException {
//使用反射创建一个实例并调用方法执行(方法为私有方法)
Class<?> c1 = Class.forName("show.mrkay.pojo.DemoPojo");
Method print = c1.getDeclaredMethod("print", String.class);
DemoPojo demo = (DemoPojo) c1.newInstance();
//私有的方法需要跳过安全检测才能执行
print.setAccessible(true);
print.invoke(demo, "测试数据01");
//通过反射中的构造器创建一个实例并执行一个方法
Class<?> c2 = Class.forName("show.mrkay.pojo.DemoPojo");
Constructor<?> demoCons = c2.getDeclaredConstructor();
DemoPojo demo2 = (DemoPojo) demoCons.newInstance();
System.out.println(demo2);//DemoPojo{id='null', username='null', password='null'}
Method print1 = c2.getDeclaredMethod("print", String.class);
//私有的方法需要跳过安全检测才能执行
print1.setAccessible(true);
print1.invoke(demo2, "测试数据02");//测试数据02
//操作私有属性
DemoPojo demo3 = (DemoPojo) c1.newInstance();
Field unFiled = c1.getDeclaredField("username");
//私有属性设置值之前先跳过安全检测
unFiled.setAccessible(true);
unFiled.set(demo3, "testsName01");
System.out.println(demo3.getUsername());
}
}
2.7 三种调用对象方法的效率对比
下里面代码可以看出:
在我们使用反射的时候,最好将安全检测跳过,这样能大大节省方法执行的时间(前提是方法比较多的情况下)
public class Test07 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException, NoSuchFieldException {
Test07.test01();//4ms
Test07.test02();//333ms
Test07.test03();//200ms
}
//直接创建对象的方式
public static void test01() {
DemoPojo demoPojo = new DemoPojo("1", "2", "3");
long startTime = System.currentTimeMillis();
for (int i = 100000000; i > 0; i--) {
demoPojo.getUsername();
}
long endTime = System.currentTimeMillis();
System.out.println("用时:" + (endTime - startTime) + "ms");
}
//反射不关闭安全检测的方式
public static void test02() throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
Class<?> c1 = Class.forName("show.mrkay.pojo.DemoPojo");
DemoPojo demo = (DemoPojo) c1.newInstance();
demo.setId("1");
demo.setUsername("2");
demo.setPassword("3");
Method method = c1.getDeclaredMethod("getUsername", null);
long startTime = System.currentTimeMillis();
for (int i = 100000000; i > 0; i--) {
method.invoke(demo, null);
}
long endTime = System.currentTimeMillis();
System.out.println("用时:" + (endTime - startTime) + "ms");
}
//反射关闭安全检测
public static void test03() throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
Class<?> c1 = Class.forName("show.mrkay.pojo.DemoPojo");
DemoPojo demo = (DemoPojo) c1.newInstance();
demo.setId("1");
demo.setUsername("2");
demo.setPassword("3");
Method method = c1.getDeclaredMethod("getUsername", null);
method.setAccessible(true);
long startTime = System.currentTimeMillis();
for (int i = 100000000; i > 0; i--) {
method.invoke(demo, null);
}
long endTime = System.currentTimeMillis();
System.out.println("用时:" + (endTime - startTime) + "ms");
}
2.8 反射获取泛型
Java是采用的泛型擦除的机制来引入泛型,Java的泛型仅仅是给编译器使用的,确保数据的安全性,和免去强制类型转换问题,但是当编译器编译完成之后就所有和泛型有关的类型就会被全部擦除;
为了通过反射操作这些与泛型有关的类型,Java引入了几种不能与Class类齐名的类型但是又和原始类型齐名的类型;
ParameterizedType : 表示一种参数化类型;
GenericArrayType : 表示一种元素类型,是参数化类型或者类型变量的数组类型;
TypeVariable : 是各种类型变量的公共父接口;
WildcardType : 代表一种通配符类型表达式;
public class Test08 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
Class<?> c1 = Class.forName("show.mrkay.reflex.Test08");
Method test01Method = c1.getDeclaredMethod("test01", Map.class);
test01Method.setAccessible(true);
//获取参数中类型
Type[] genericParameterTypes = test01Method.getGenericParameterTypes();
for (Type genericParameterType : genericParameterTypes) {
System.out.println(genericParameterType);//java.util.Map<java.lang.String, show.mrkay.reflex.User>
if (genericParameterType instanceof ParameterizedType) {
//获取真实的泛型类型
Type[] actualTypeArguments = ((ParameterizedType) genericParameterType).getActualTypeArguments();
for (Type actualTypeArgument : actualTypeArguments) {
System.out.println(actualTypeArgument);//class java.lang.String class show.mrkay.reflex.User
}
}
}
//获取返回值中的类型
Class<?> c2 = Class.forName("show.mrkay.reflex.Test08");
Method test02Method = c2.getDeclaredMethod("test02", Map.class);
test02Method.setAccessible(true);
Type returnType = test02Method.getGenericReturnType();
System.out.println(returnType);
if (returnType instanceof ParameterizedType) {
//获取真实的泛型类型
Type[] actualTypeArguments = ((ParameterizedType) returnType).getActualTypeArguments();
for (Type actualTypeArgument : actualTypeArguments) {
System.out.println(actualTypeArgument);
}
}
}
public void test01(Map<String, User> map) {
System.out.println("test");
}
public Map<String, User> test02(Map<String, User> map) {
return null;
}
}
2.9 反射获取注解信息
在各大框架中,都有注解,那么这些框架解析注解底层使用的就是反射:
下面使用代码演示反射获取注解中的信息
//定义类上注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TableAnnotation {
String value() default "";
}
//定义字段上注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TableFiledAnno {
String filedName();
String type();
boolean isNull() default true;
}
//实体类上使用注解
@TableAnnotation(value = "tb_student")
public class Student {
@TableFiledAnno(filedName = "ID",type = "varchar2",isNull = false)
private String id;
@TableFiledAnno(filedName = "USER_NAME",type = "varchar2",isNull = false)
private String username;
@TableFiledAnno(filedName = "PASSAWORD",type = "varchar2",isNull = false)
private String password;
public Student() {
}
public Student(String id, String username, String password) {
this.id = id;
this.username = username;
this.password = password;
}
@Override
public String toString() {
return "Student{" + "id='" + id + '\'' + ", username='" + username + '\'' + ", password='" + password + '\'' + '}';
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
//使用反射操作注解与获取注解中信息
public class Test09 {
public static void main(String[] args) throws ClassNotFoundException{
//获取Student实体类上的注解和注解的value值
Class<?> stuClass = Class.forName("show.mrkay.pojo.Student");
TableAnnotation tbAnno = stuClass.getDeclaredAnnotation(TableAnnotation.class);
System.out.println(tbAnno);//@show.mrkay.annotation.TableAnnotation(value=tb_student)
String value = tbAnno.value();
System.out.println(value);//tb_student
//获取属性上的注解和注解中的值
Field[] declaredFields = stuClass.getDeclaredFields();
for (Field field : declaredFields) {
field.setAccessible(true);
TableFiledAnno tableFiledAnno = field.getDeclaredAnnotation(TableFiledAnno.class);
System.out.println(tableFiledAnno.filedName());
System.out.println(tableFiledAnno.isNull());
System.out.println(tableFiledAnno.type());
}
}
}
3 小结
本次注解需要有一定的Java基础,因为反射属于Java中比较难懂的一部分基础;
在本文中不仅一次提到了JVM,比如JVM的初始化,以及内存分配等;所以这里如果有一定的JVM基础在类的加载链接初始化部分会更加容易理解;
后期我会记录学习一些JVM的相关知识
本次记录的重点其实不在这些方法和代码中,真正的重点是以下几个部分:
1 类的加载(使用类加载器) 类的链接 类的初始化(JVM来完成)
2 类加载器的类型和操作范围以及作用
3 什么时候会触发类的初始化
4 什么情况不会触发类的初始化;
5 通过反射实例化对象,反射如何获取不同地方的注解,以及注解中的属性值
6 每一个私有属性的setAccessible(true)作用;
4 参考链接
https://www.bilibili.com/video/BV1p4411P7V3