反射_注解
一.类加载器
1.概述:
在jvm中,负责将本地上的class文件加载到内存的对象
2.分类:
- BootstrapClassLoader 根类加载器-->C语言写的,我们获取不到
也被称为引导类加载器,负责Java核心类的加载
比如System,String等。
jre/lib/rt.jar下的类都是核心类
---------------------------------
- ExtClassLoader 扩展类加载器
负责JRE的扩展目录中jar包的加载。
在JDK中JRE的lib目录下ext目录
- AppClassLoader 系统类加载器
负责在JVM启动时加载来自java命令的class文件,以及classpath环境变量所指定的jar包(第三方jar包)和类路径。或者自定义的类
----------------------------------------------------
AppClassLoader的父加载器是ExtClassLoader
ExtClassLoader的父加载器是BootstrapClassLoader
但是他们不是子父类继承关系,他们有一个共同的爹-->ClassLoader
3.获取加载器对象
类名.class.getClassLoader
4.双亲委派(全盘负责委托机制) 谁用谁加载
a.Person中有一个String
public class Person{
String s = "涛哥"
}
Person本身是AppClassLoader加载
String是BootstrapClassLoader
b.加载顺序:
Person本身是AppClassLoader加载,String本来按理来说是AppClassLoader
但是AppClassLoader加载String的时候,会去问一问ExtClassLoader,嗨,ext,你加载吗?
ext说:偶no,我不负责加载核心类,我负责的是扩展类,所以你别急,我给你找人(BootstrapClassLoader)
ext说:boot,你加载String吗?
boot:哎,正好我加载核心类,行吧,我加载吧
-----------------------------------------------------------
假如:ext和boot都不加载,app才会自己加载
class Test{
new Person()
}
a.AppClassLoader负责加载Test,然后按道理来讲Person也是由AppClassLoader加载到内存
但是App先不加载,先去找ExtClassLoader,Ext不负责加载自定义类,Ext就去找Boot
但是Boot只负责加载核心类,所以Boot也不加载
b.最后App看两个双亲不加载,自己就加载了Person
5.双亲委派作用:(一个类在内存中加载几次呢?->1次)
能够让Class类加载一次
创建一个Class对象
/*
类名.class.getClassLorder()这个类的加载器
ClassLorder类的方法 getParent()返回父加载器
*/
public class ClassLoader {
@Test
public void classLorder(){
//三个类加载器的关系
//父子关系
ClassLoader c1 = ClassLoader.class.getClassLoader();
System.out.println(c1);
ClassLoader c2 = c1.getParent();
System.out.println(c2);
ClassLoader c3 = c2.getParent();
System.out.println(c3);
}
//获取AppClassLoader 系统类加载器
@Test
public void app(){
//ClassLoader为本类类名
ClassLoader c1 = ClassLoader.class.getClassLoader();
System.out.println(c1);
}
//获取ExtClassLoader 扩展类加载器
@Test
public void ext(){
ClassLoader c1 = DNSNameService.class.getClassLoader();
System.out.println(c1);
}
//BootstrapClassLoader 根类加载器
@Test
public void boot(){
//引导类加载器,不是类,JVM内部,返回值null
ClassLoader cl = String.class.getClassLoader();
System.out.println(cl);
}
}
二.反射
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YkwpSWdO-1610703651588)(image/02.反射的介绍.png)]
1.概述:根据Class对象操作Class对象中的成员
三.反射之获取Class对象
获取Class对象的方式:
1.new对象 调用 getClass()->Object中的方法
2.类名.class->class->每个数据类型,不管是基本的还是引用的,jvm都赋予了他们一个静态属性
名字就叫做class
3.Class中方法->forName(String className)
注意:
class就加载一次,Class对象也就一个,用3中方式获取出来的Class是同一个
问题:
3种获取Class对象的方式,哪个在开发中最常用:forName
使用forName(Stirng className)->扩展性更好,灵活性更高
因为:参数是一个字符串,将来我们可以将类的全限定名放在配置文件中,然后
用io流读取,读取出来的字符串(全限定名)可以当做参数放在forName中
这样,我们想获取不同的Class对象,直接改文件名就可以了,不用修改
源代码的.-->可以代码实现一下
public class Person {
private String name;
private int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
private Person(String name){
this.name = name;
System.out.println("我是私有的构造");
}
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;
}
@Override
public String toString() {
return name+"..."+age;
}
}
//测试类
public class Test01 {
public static void main(String[] args) throws ClassNotFoundException {
//每个数据类型,不管基本还是引用数据类型,jvm都赋予了这个类型一个静态属性,名字叫class
Class aClass = Person.class;
Class aClass1 = Class.forName("cn.itcast.day15.class02.Person");
//Object类中的getClass方法
Person person = new Person();
Class aClass2 = person.getClass();
System.out.println(aClass==aClass1);
System.out.println(aClass==aClass2);
}
}
四.获取Class对象中的成员
一.获取所有public的构造方法
反射通用的使用方式:
1.获取要操作类的class对象
2.利用Class类中的方法获取类中的成员(构造,变量,方法)
3.运行获取出来的成员
获取Class对象中的构造方法:
Constructor<?>[] getConstructors()->获取所有的构造方法(public修饰的)
public class Demo01_Constructor {
public static void main(String[] args) throws Exception{
//获取Person类的Class对象
Class pClass = Class.forName("cn.itcast.day20.fanshe02.Person");
Constructor[] constructors = pClass.getConstructors();
//遍历数组
for (Constructor constructor : constructors) {
System.out.println(constructor);
}
}
}
二.获取空参构造
1.获取指定的构造方法:
Constructor<T> getConstructor(Class<?>... parameterTypes)
parameterTypes:需要传递参数类型的class对象
如果获取的是无参构造,参数不写
2.Constructor类中的方法
T newInstance(Object...initargs)-->创建对象->new Person("柳岩",1)
如果使用此方法创建的是无参构造,参数不用写
private static void method01() throws Exception {
//获取Person类的Class对象
Class pClass = Class.forName("cn.itcast.day20.fanshe02.Person");
//获取无参构造
Constructor constructor = pClass.getConstructor();
Object o = constructor.newInstance();//Person p = new Person()
System.out.println(o);
}
三.利用空参构造创建对象的快捷方式
1.利用空参构造创建对象的快捷方式
直接调用Class类中的newInstance方法
前提:被反射的类,必须具有public权限的无参构造
private static void method02()throws Exception {
//获取Person类的Class对象
Class pClass = Class.forName("cn.itcast.day20.fanshe02.Person");
Object o = pClass.newInstance();
System.out.println(o);
}
四.利用反射获取有参构造并创建对象
1.获取指定的构造方法:
Constructor<T> getConstructor(Class<?>... parameterTypes)
parameterTypes:需要传递参数类型的class对象
如果获取的是无参构造,参数不写
2.Constructor类中的方法
T newInstance(Object...initargs)-->创建对象->new Person("柳岩",1)
如果使用此方法创建的是无参构造,参数不用写
public class Demo03_Constructor {
public static void main(String[] args) throws Exception{
//获取Person类的Class对象
Class pClass = Class.forName("cn.itcast.day20.fanshe02.Person");
//获取有参构造
Constructor constructor = pClass.getConstructor(String.class, int.class);
System.out.println(constructor);
//创建对象
//Person p = new Person("柳岩", 36)
Object o = constructor.newInstance("柳岩", 36);
System.out.println(o);
}
}
五.利用反射获取私有构造(暴力反射)
获取私有的构造(扩展):
Constructor<?>[] getDeclaredConstructors()->获取所有的构造,包括私有的
Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) ->获取指定的构造
AccessibleObject类中的方法-->暴力反射
void setAccessible(boolean flag)
flag:false->代表不能访问私有的成员
flag:true->解除私有权限
public class Demo04_Constructor {
public static void main(String[] args) throws Exception{
//获取Person类的Class对象
Class pClass = Class.forName("cn.itcast.day20.fanshe02.Person");
//method01(pClass);
method02(pClass);
}
private static void method02(Class pClass)throws Exception {
//获取私有的构造方法
Constructor dds = pClass.getDeclaredConstructor(String.class);
//解除私有权限
dds.setAccessible(true);
//创建对象
Object o = dds.newInstance("郭磊");
System.out.println(o);
}
/*
获取所有的构造->包括私有
*/
public static void method01(Class pClass){
Constructor[] dds = pClass.getDeclaredConstructors();
//遍历数组
for (Constructor dd : dds) {
System.out.println(dd);
}
}
}
六.利用反射获取所有成员方法
利用反射获取类中的方法:
Method[] getMethods()获取所有的方法,public
public class Demo05_Method {
public static void main(String[] args)throws Exception {
Class c = ClassUtils.getC();//抽出来的工具类可以不抽取
Method[] methods = c.getMethods();
//遍历数组
for (Method method : methods) {
System.out.println(method);
}
}
}
七.反射之获取方法(有参,无参)
获取有参以及无参的方法
Method getMethod(String name, Class<?>... parameterTypes)
name:获取的方法名
parameterTypes:该方法的参数类型
如果获取的方法没参数,parameterTypes不用写
Method类中的一个方法
Object invoke(Object obj, Object... args) ->执行方法
obj:是调用newInstance方法生成的对象
args:运行方法时传递的实参
如果执行的方法是void,调用invoke方法没必要用返回值接收了
如果执行的方法没有参数,那么args不用写
public class Demo06_Method {
public static void main(String[] args)throws Exception {
Class c = ClassUtils.getC();
System.out.println("---------获取setName方法-------------");
//获取set方法
Method setName = c.getMethod("setName", String.class);
//利用空参构造创建Person对象
Object o = c.newInstance();//Person o = new Person()
//调用方法
setName.invoke(o, "柳岩");
System.out.println(o);
System.out.println("---------获取getName方法-------------");
Method getName = c.getMethod("getName");
//执行getName方法,有返回值的
Object invoke = getName.invoke(o);
System.out.println(invoke);
}
}
五.反射练习(编写一个小框架)
利用反射,解析配置文件中的信息
文件中配置的信息是
类的全限定名 className=cn.itcast.day20.fanshe03_test.Person
类中的某一个方法名 methodName=eat
步骤:
1.创建配置文件->properties
存的信息键值对的形式
问题:配置文件放在哪里?放在src下->切记
问题:项目开发完,交给用户使用,给用户的是编译后的class文件
如果将配置文件放在src下面,idea生成的out目录中这个配置文件会显示在out的项目路径下
我们给用户class文件的时候,也需要把配置文件给用户,不然项目无法读取配置文件中的信息,也就无法执行
src 存放的是源代码 编译后产生的class文件,同步的,但是生成的out路径,下没有src这个目录,以为src存放源代码的
问题:如何读取src目录下的文件?
直接new FileInputStream(模块名\\src\\配置文件名)是不行的
因为这样写,而我们给的用户是out下的资源,而out下存放的class文件路径是没有src
所以这样写,给了用户,用户一使用,直接就读不到这个配置文件了
解决:
使用类的加载器
ClassLoader类定义方法
InputStream getResourceAsStream("直接写文件名")返回一个字节输入流
此流会自动从类目录下扫描文件读取 ->直接扫描src下的配置文件
2.利用IO流读取配置文件
读到Properties集合中
3.获取Properties中对应的值
获取类的全限定名
方法名
4.利用反射获取类的Class对象
利用反射去指定方法
//在src下创建一个pro.properties文件
className=cn.itcast.day15.class02.Person
methodName=eat
public class Test {
public static void main(String[] args)throws Exception{
//获取类加载器
ClassLoader classLoader = Test.class.getClassLoader();
//使用类加载器的输入流读取配置文件
InputStream in = classLoader.getResourceAsStream("config.properties");
//创建Properties集合
Properties properties = new Properties();
//调用load方法,将流中的配置文件读取到Properties集合中
properties.load(in);
//获取配置文件中的value
String className = properties.getProperty("className");//cn.itcast.day20.fanshe03_test.Person
String methodName = properties.getProperty("methodName");//eat
//根据获取出来的className创建Person的Class对象
Class aClass = Class.forName(className);
//根据从配置文件中解析出来的方法名去获取Person中的方法
Method method = aClass.getMethod(methodName);
//创建对象
Object o = aClass.newInstance();
//执行方法
method.invoke(o);
}
}
六.注解
一.注解的介绍
1.jdk1.5版本的新特性->一个引用数据类型
和类,接口,枚举是同一个层次的
2.作用:
说明:对代码进行说明,生成doc文档(API文档)(不会用)
检查:检查代码是否有错误 @Override(会用)
分析:对代码进行分析,起到了代替配置文件的作用(会用)
3.JDK中的注解:
@Override -> 检测此方法是否为重写方法
jdk1.5版本,支持父类的方法重写
jdk1.6版本,支持接口的方法重写
@Deprecated -> 方法已经过时,不推荐使用
调用方法的时候,方法上会有横线,但是能用
@SuppressWarnings->消除警告 @SuppressWarnings("all")
二.注解的定义以及属性的定义格式
1.自定义注解格式:
修饰符 @interface 注解名{
属性
}
2.属性的定义格式:为了提高注解作用
- 格式1:数据类型 属性名();-->没有默认值的-->需要后面赋值
- 格式2:数据类型 属性名() default 默认值;-->可以改变的
3.注解中能够定义什么样的属性
- 八种基本数据类型(int,float,boolean,byte,double,char,long,short)。
- String类型,Class类型,枚举类型,注解类型。
- 以上所有类型的一维数组。int[][] arr = {{1,2},{3,4}} [0][0] [1,0]->3
public @interface Book {
//书名
String bookName();
//价格
double price();
//作者
String[] author();
//数量
//Integer count();错误,不能使用包装类
//int count() default 10;这个是可以的,定义属性的时候可以指定默认值
}
三.注解的使用
//注解的使用
/*
使用注解,就是为注解的属性赋值
格式:
@注解名(属性名=值,属性名=值)
String[] author():作者是字符串的数组
数组的赋值-->属性名 = {"曹雪芹","高鹗"}
*/
@Book(booName = "红楼梦",price = 200.9,author = {"曹雪芹","高鹗"})
pubilc class BookShelf{
}
注解注意事项:
1.空注解可以直接使用
2.一个对象中不能连续使用同一个注解多次,但是一个对象中可以使用多个不同的注解
(不同的位置可以使用一样的注解,但是同样的位置不能使用一样的注解)
3.使用注解时,如果此注解中有属性,注解中的属性一定要赋值,如果有多个属性,用,隔开
如果注解中的属性有数组,那么如果数组只有一个元素值,那么{}不用写,反之用写
4.如果注解中的属性值有默认值,那么我们不必要写,也不用重新赋值,反之必须写上
5.如果注解中只有一个属性,并且属性名叫value,那么使用注解的时候,属性名不用写
四.注解解析的方法
注解解析:获取注解中的属性值
接口:AnnotatedElement接口中的方法(用于解析注解的接口)
boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)
判断当前class对象是否有指定的注解
返回值:boolean 返回的是true,证明该class对象上有注解;返回的是false,证明该class对象没有注解
Class<? extends Annotation> annotationClass
传递的是class对象, 传递的其实就是该class对象上对应注解的class对象
<T extends Annotation> getAnnotation(Class<T> annotationClass)->获得当前class对象上指定的注解对象。
解释:
参数传递的是注解的class对象
传递哪个注解类型,返回的就是哪个注解对象
AnnotatedElement毕竟是个接口,你有实现类
实现类:Class类 Constructor构造方法 Field成员变量 Method成员方法
结论:注解的解析,和哪个技术密切相关--> 反射
需求:获取BookShelf1类上的注解Book的属性值
=====================================================
注解的解析思想:
1.反射带有注解的类
2.判断这个类上是否有注解
3.获取这个注解
4.获取注解中的属性值
------------------
假如我们要是解析方法上的注解属性值
1.反射带有注解的类
2.反射方法->getMethod-->Method
3.判断方法上是否有注解
4.获取这个注解
5.获取注解的属性值
@Book(booName = "红楼梦",price = 200.9,author = {"曹雪芹","高额"})
pubilc class BookShelf{
}
/*
运行之后发现没有输出,而且c.isAnnotationPresent(Book.class)判断为false,
明明类上有注解,但是却返回false,为啥呢?那么我们接下来学习一下元注解
*/
public class Test01 {
public static void main(String[] args)throws Exception {
//1.反射带有注解得类
Class c = Class.forName("cn.itcast.day21.zhujie02.BookShelf01");
//2.判断这个类上是否有注解
boolean b = c.isAnnotationPresent(Book.class);
System.out.println(b);
if (b){
//如果有注解,获取注解
//Annotation annotation = c.getAnnotation(Book.class);
Book book = (Book) c.getAnnotation(Book.class);
//获取注解中的属性值
System.out.println("书名:"+book.bookName());
System.out.println("价格:"+book.price());
System.out.println("作者:"+ Arrays.toString(book.author()));
}
}
}
十一.元注解
**
* 自定义的注解 Book
* 定义属性
*
* JDK的元注解,比喻注解的总管
* 管理其他的注解
*
* 元注解对我们的注解进行控制
* 1: 控制我们的注解,可以写在哪里,(类,方法,变量上,包...)
* 2: 控制我们的注解的生命周期
*
* JDK的2个元注解
*
* @Target 指示其他注解,出现的位置->点进Target底层
* ElementType[] value(); 数组,可以赋值多个->点ElementType底层
* ElementType是数据类型,是枚举
* 枚举的属性,都是静态修饰,直接类名调用
* TYPE, 其他注解可以写在类上
* FIELD,其他注解可以写在成员变量
* METHOD,其他注解可以写在方法上
* PARAMETER,其他注解可以写在方法参数上
* CONSTRUCTOR,其他注解可以写在构造方法上
*
* @Retention 指示其他注解的生命周期->点到Retention底层
* RetentionPolicy value(); 不是数组,赋值一个->点到RetentionPolicy底层
* RetentionPolicy数据类型
* 枚举的属性,都是静态修饰,直接类名调用
* SOURCE(默认级别) 注解仅存在于源码中java文件中(不在class文件中,也不在方法区中)->@Override只是检 测方法是否为重写方法
* CLASS 注解存在于编译后的class文件中->Class文件中出现了,方法区中没有
* RUNTIME 运行时期的内存中-->方法区中出现了,一旦在方法区中出现了,我们才能利用反射获取到注解
所以当我们在注解上写SOURCE 运行上面的Test案例判断类上有没有注解,才会返回false
*/
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Book {
//书名
String bookName();
//价格
double price() ;
//作者
String[] author();
}
------------------------------------
@Book(booName = "红楼梦",price = 200.9,author = {"曹雪芹","高额"})
pubilc class BookShelf{
@Book(booName = "红楼梦",price = 200.9,author = {"曹雪芹","高额"})
public void lookBook(){
System.out.println("看书");
}
}
十二.注解再次解析
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Book {
String bookName();
double price();
String[] author();
}
@Book(booName = "红楼梦",price = 200.9,author = {"曹雪芹","高鹗"})
pubilc class BookShelf{
}
=================================================================
public class Test01 {
public static void main(String[] args)throws Exception {
//1.反射带有注解得类
Class c = Class.forName("cn.itcast.day21.zhujie03.BookShelf");
//2.判断这个类上是否有注解
boolean b = c.isAnnotationPresent(Book.class);
System.out.println(b);
if (b){
//如果有注解,获取注解
//Annotation annotation = c.getAnnotation(Book.class);
Book book = (Book) c.getAnnotation(Book.class);
//获取注解中的属性值
System.out.println("书名:"+book.bookName());
System.out.println("价格:"+book.price());
System.out.println("作者:"+ Arrays.toString(book.author()));
}
}
}
十三.模拟Junit
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTest {
}
/*
使用@MyTest注解
哪个方法上有@MyTest我们就让哪个方法执行
*/
public class Test01 {
@MyTest
public void method01(){
System.out.println("我是method01");
}
public void method02(){
System.out.println("我是method02");
}
@MyTest
public void method03(){
System.out.println("我是method03");
}
/*
//1.反射获取当前类的Class对象
//2.利用反射获取方法->getMethods->Method
//3.遍历数组,将每个Method对象获取出来,挨个判断,该方法上有没有注解
//4.如果有,直接调用invoke去执行有注解的方法
*/
public static void main(String[] args) throws Exception{
//1.获取本类的Class对象
Class aClass = Test01.class;
//创建对象
Object o = aClass.newInstance();
//2.获取方法
Method[] methods = aClass.getMethods();
// 3.判断方法上是否有注解
for (Method method : methods) {
boolean b = method.isAnnotationPresent(MyTest.class);
if (b){
//如果方法上有注解,直接invoke执行
method.invoke(o);
}
}
}
}
十三.Lombok
使用的注解有:
@Data->包含get/set,toString,hashCode,equals,无参构造方法
@Getter->生成get方法
@Setter->生成set方法
@NoArgsConstructor和@AllArgsConstructor
- @NoArgsConstructor:无参数构造方法。
- @AllArgsConstructor:满参数构造方法。
@@EqualsAndHashCode->生成hashCode和Equals