可以先参考下大佬的:
大佬1
大佬2
类字节码文件是在硬盘上存储的,是一个个的.class文件。我们在new一个对象时,JVM会先把字节码文件的信息读出来放到内存中,第二次用时,就不用在加载了,而是直接使用之前缓存的这个字节码信息。
字节码的信息包括:类名、声明的方法、声明的字段等信息。在Java中“万物皆对象”,这些信息当然也需要封装一个对象,这就是Class类、Method类、Field类。
通过Class类、Method类、Field类等等类可以得到这个类型的一些信息,甚至可以不用new关键字就创建一个实例,可以执行一个对象中的方法,设置或获取字段的值,这就是反射技术。
方法区存的是类的信息,不是存类对象的,类加载器加载类是通过方法区上类的信息在堆上创建一个类的Class对象,这个Class对象是唯一的,由JVM保证唯一,之后对这个类的创建都是根据这个Class对象来操作的
反射是什么呢?当我们的程序在运行时,需要动态的加载一些类这些类可能之前用不到所以不用加载到jvm,而是在运行时根据需要才加载,这样的好处对于服务器来说不言而喻,举个例子我们的项目底层有时是用mysql,有时用oracle,需要动态地根据实际情况加载驱动类,这个时候反射就有用了,假设 com.java.dbtest.myqlConnection,com.java.dbtest.oracleConnection这两个类我们要用,这时候我们的程序就写得比较动态化,通过Class tc = Class.forName(“com.java.dbtest.TestConnection”);通过类的全类名让jvm在服务器中找到并加载这个类,而如果是oracle则传入的参数就变成另一个了。
Java 反射机制主要提供了以下功能
在运行时判断任意一个对象所属的类。
在运行时构造任意一个类的对象。
在运行时判断任意一个类所具有的成员变量和方法。
在运行时调用任意一个对象的方法
今天回过头来找了点视频来简单了解下了反射机制 以下纯属个人学习总结 如有问题 欢迎讨论 一起进步
问:*有人会问 反射到底是用来干什么的?*
答:目前学了感觉是没啥用 但是到了框架部分 就大有用处
就好比框架是个房子 里面的构造都已经给你搭建好了 你想要对其中的部分进行修改添加 这个时候就需要反射来动态修改了
主要的方法都在java.lang.reflect中
reflect是反射的英文
不知道这么说 是不是可以理解 因为我也没学到过框架 只是照葫芦画瓢在这瞎几把乱说 如有问题 请指出。
Class.forName是在bin层次下
new FileReader是在项目层次下 下面会用到 先简要了解下
反射(类的加载概述和加载时机)
类的加载概述
- 当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过加载,连接,初始化三步来实现对这个类进行初始化。
- 加载
- 就是指将class文件读入内存,并为之创建一个Class对象。任何类被使用时系统都会建立一个Class对象。
连接
- 验证 是否有正确的内部结构,并和其他类协调一致
- 准备 负责为类的静态成员分配内存,并设置默认初始化值
- 解析 将类的二进制数据中的符号引用替换为直接引用
初始化
- 加载时机
- 创建类的实例
- 访问类的静态变量,或者为静态变量赋值
- 调用类的静态方法
- 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象
- 初始化某个类的子类
- 直接使用java.exe命令来运行某个主类
反射(类加载器的概述和分类)
- :类加载器的概述
- 负责将.class文件加载到内存中,并为之生成对应的Class对象。虽然我们不需要关心类加载机制,但是了解这个机制我们就能更好的理解程序的运行。
- :类加载器的分类
- Bootstrap ClassLoader 根类加载器
- Extension ClassLoader 扩展类加载器
- Sysetm ClassLoader 系统类加载器
- :类加载器的作用
- Bootstrap ClassLoader 根类加载器
- 也被称为引导类加载器,负责Java核心类的加载
- 比如System,String等。在JDK中JRE的lib目录下rt.jar文件中
- Extension ClassLoader 扩展类加载器
- 负责JRE的扩展目录中jar包的加载。
- 在JDK中JRE的lib目录下ext目录
- Sysetm ClassLoader 系统类加载器
- 负责在JVM启动时加载来自java命令的class文件,以及classpath环境变量所指定的jar包和类路径
- 负责在JVM启动时加载来自java命令的class文件,以及classpath环境变量所指定的jar包和类路径
- Bootstrap ClassLoader 根类加载器
反射(反射概述)
:反射概述
- JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;
- 对于任意一个对象,都能够调用它的任意一个方法和属性;
- 这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
- 要想解剖一个类,必须先要获取到该类的字节码文件对象。
- 而解剖使用的就是Class类中的方法,所以先要获取到每一个字节码文件对应的Class类型的对象。
:三种方式
- a:Object类的getClass()方法,判断两个对象是否是同一个字节码文件
- b:静态属性class,锁对象
- c:Class类中静态方法forName(),读取配置文件
举一个栗子:
package fuxi;
class Person{
}
public class One {
public static void main(String args[]) throws ClassNotFoundException {
Person person=new Person();
Class clazz1=person.getClass(); //根据实体类得到class字节码
Class clazz2=Person.class; //静态属性class加载
Class clazz3=Class.forName("fuxi.Person");//路径要写全 java文件中的得到字节码
System.out.println(clazz1.equals(clazz2));
System.out.println(clazz1.equals(clazz3));
System.out.println(clazz2.equals(clazz3));
}
}
输出:
true
true
true
意思就是三个得到的字节码class都是同一个
用反射和配置文件解决问题:
package fuxi;
import java.io.BufferedReader;
import java.io.FileReader;
interface Animal{
void run();
}
class Dog implements Animal{
@Override
public void run() {
System.out.print("汪汪汪");
}
}
class Cat implements Animal{
@Override
public void run() {
System.out.print("喵喵喵");
}
}
class Ox{
public void run(Animal animal) {
animal.run();
}
}
public class One {
public static void main(String args[]) throws Exception {
BufferedReader br = new BufferedReader(new FileReader("Animal.properties"));
String name=br.readLine();
System.out.println(name);
Class clazz=Class.forName(name);
Ox animal=new Ox();
animal.run((Animal) clazz.newInstance());
}
}
还有在项目下创建一个配置文件
内容是:
fuxi.Dog
如果是原来的多态 写死了 那么每次都要修改源文件
而用反射的话 就可以的动态记载配置文件里面的内容 只要修改配置文件就行 降低了耦合度
通过反射获得构造函数并使用
Class类的newInstance()方法是使用该类无参的构造函数创建对象, 如果一个类没有无参的构造函数,
就不能这样创建了,可以调用Class类的getConstructor
举个栗子:
package fuxi;
class Person {
String name="xx";
int age=3;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public Person() {
name="xx";
age=3;
}
@Override
public String toString() {
return name + " " + age;
}
}
public class One {
public static void main(String args[]) throws Exception {
Class clazz = Class.forName("fuxi.Person");
Person person = (Person) clazz.newInstance();
System.out.print(person);
}
}
输出:
xx 3
这个时候有无参的构造函数 所以可以 如果去掉这个无参的构造函数 那么就会报错
这个时候就得Class类的getConstructor获取class类的构造函数
package fuxi;
import java.lang.reflect.Constructor;
class Person {
String name="xx";
int age=3;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public Person() {
name="xx";
age=3;
}
@Override
public String toString() {
return name + " " + age;
}
}
public class One {
public static void main(String args[]) throws Exception {
Class clazz = Class.forName("fuxi.Person");
Constructor[] constructors=clazz.getConstructors();//获得这个类的构造函数数组
System.out.println(constructors[0]);
System.out.println(constructors[1]);
Constructor constructor=clazz.getConstructor(String.class,int.class);////获取有参构造
System.out.println(constructor);
Person person=(Person) constructor.newInstance("xiaoming",1);//通过有参构造创建对象
System.out.print(person);
}
}
输出:
public fuxi.Person(java.lang.String,int)
public fuxi.Person()
public fuxi.Person(java.lang.String,int)
xiaoming 1
通过反射获取成员变量并使用
Field
* Class.getField(String)方法可以获取类中的指定字段(可见的), 如果是私有的可以用getDeclaedField(“name”)方法获取,通过set(obj, “李四”)方法可以设置指定对象上该字段的值, 如果是私有的需要先调用setAccessible(true)设置访问权限,用获取的指定的字段调用get(obj)可以获取指定对象中该字段的值
package fuxi;
import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
class Person {
public String name="小明";
public int age=3;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public Person() {
name="xx";
age=3;
}
@Override
public String toString() {
return name + " " + age;
}
}
public class One {
public static void main(String args[]) throws Exception {
Class clazz = Class.forName("fuxi.Person");
Field[] fields=clazz.getFields(); //获取类属性的全部属性 前提的public 如果是包内访问和private都不行
System.out.println(fields[0]);
System.out.println(fields[1]);
Person person=(Person) clazz.newInstance(); //通过无参构造创建对象
//Field field=clazz.getField("name"); //获取姓名字段
//field.set(person, "小张"); //修改姓名的值
Field field=clazz.getDeclaredField("name"); //暴力反射获取字段
field.setAccessible(true); //去掉私有权限
field.set(person, "小张");
System.out.print(person);
}
}
输出:
public java.lang.String fuxi.Person.name
public int fuxi.Person.age
小张 3
总结: 属性权限如果不是公有的话
1. 要获取属性只能 clazz.getDeclaredField 暴力获取属性
2. 要修改属性只能 setAccessible 去掉权限限制
这么看来反射是不是很强盗 ? 也说明了他的无敌
通过反射获取方法并使用
* Method
* Class.getMethod(String, Class…) 和 Class.getDeclaredMethod(String, Class…)方法可以获取类中的指定方法,调用invoke(Object, Object…)可以调用该方法
package fuxi;
import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import javax.print.attribute.standard.Media;
class Person{
String name;
int age;
public Person(String name,int age) {
this.name=name;
this.age=age;
}
public void printf() {
System.out.println(name);
}
public void printf(int age) {
System.out.println(name+" "+age);
}
}
public class One {
public static void main(String args[]) throws Exception {
Class clazz=Class.forName("fuxi.Person");
Constructor constructor=clazz.getConstructor(String.class,int.class);
Person person=(Person) constructor.newInstance("小明",10);
Method method1=clazz.getMethod("printf");
method1.invoke(person);
Method method2=clazz.getMethod("printf", int.class);
method2.invoke(person, 11);
}
}
输出:
小明
小明 11
或许你们会有个疑惑 为什么clazz.getMethod(“printf”, int.class)?
其实这一部分还属于反射部分 还是字节码的部分 所以得传入class形式 最后invoke的时候才是具体的值
泛型反射:
这个之前学泛型的时候提到过
现在简单回顾一下 泛型检查是在编译器 运行时 class文件中类型都被泛型擦除为Object
这个时候我们可以通过泛型反射 往ArrayList<Integer>中 添加String类型
package fuxi;
import java.lang.reflect.Method;
import java.util.ArrayList;
public class One {
public static void main(String args[]) throws Exception {
ArrayList<Integer> arrayList=new ArrayList<Integer>();
arrayList.add(1);
arrayList.add(2);
Class clazz=Class.forName("java.util.ArrayList");
Method method=clazz.getMethod("add", Object.class);
method.invoke(arrayList, "adb");
System.out.print(arrayList);
}
}
输出:
[1, 2, adb]
通过反射写一个通用的设置某个对象的某个属性为指定的值
package fuxi;
import java.lang.reflect.Field;
class Person{
String name;
int age;
public Person(String name,int age) {
this.name=name;
this.age=age;
}
}
public class One {
public static void main(String args[]) throws Exception {
Person person=new Person("小明", 10);
tool(person, "name", "小王");
System.out.print(person.name);
}
public static void tool(Object person,String name,Object object) throws Exception {
Class clazz=Class.forName("fuxi.Person");
Field field=clazz.getDeclaredField(name);
field.setAccessible(true);
field.set(person, object);
}
}
输出:
小王
总结:
1. Class.forName可以读取一个类的字节码文件即Class文件
Class可以通过newInstance()创建一个实体类
通过反射获取带参构造方法并使用
- Constructor
- Class类的newInstance()方法是使用该类无参的构造函数创建对象, 如果一个类没有无参的构造函数, 就不能这样创建了,可以调用Class类的getConstructor(String.class,int.class)方法获取一个指定的构造函数然后再调用Constructor类的newInstance(“张三”,20)方法创建对象
通过反射获取成员变量并使用
- Field
- Class.getField(String)方法可以获取类中的指定字段(可见的), 如果是私有的可以用getDeclaedField(“name”)方法获取,通过set(obj, “李四”)方法可以设置指定对象上该字段的值, 如果是私有的需要先调用setAccessible(true)设置访问权限,用获取的指定的字段调用get(obj)可以获取指定对象中该字段的值
通过反射获取方法并使用
- Method
- Class.getMethod(String, Class…) 和 Class.getDeclaredMethod(String, Class…)方法可以获取类中的指定方法,调用invoke(Object, Object…)可以调用该方法,Class.getMethod(“eat”) invoke(obj) Class.getMethod(“eat”,int.class) invoke(obj,10)
通过反射越过泛型检查
动态代理
最后再来讲一下 动态代理:
代理:本来应该自己做的事情,请了别人来做,被请的人就是代理对象。
代理类在程序运行时创建的代理方式被成为 动态代理。 也就是说,这种情况下,代理类并不是在Java代码中定义的,而是在运行时根据我们在Java代码中的“指示”动态生成的。
所以说动态代理其实就是通过反射来生成一个代理
优点:
可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类的函数。
但是只能针对接口进行动态代理 所以 你要代理的类只能继承接口而不是一个类
在Java中java.lang.reflect包下提供了一个Proxy类和一个InvocationHandler接口,通过使用这个类和接口就可以生成动态代理对象。
重要:InvocationHandler接口
在使用动态代理时,我们需要定义一个位于代理类与委托类之间的中介类,这个中介类被要求实现InvocationHandler接口 作为调用处理器”拦截“对代理类方法的调用
当我们调用代理类对象的方法时,这个“调用”会转送到invoke方法中,代理类对象作为proxy参数传入,参数method标识了我们具体调用的是代理类的哪个方法,args为这个方法的参数。这样一来,我们对代理类中的所有方法的调用都会变为对invoke的调用,这样我们可以在invoke方法中添加统一的处理逻辑
最终会调用InvocationHandler的方法
InvocationHandler Object invoke(Object proxy,Method method,Object[] args) //意思 :调用处理器
Java动态代理其实内部也是通过Java反射机制来实现的,即已知的一个对象,然后在运行时动态调用其方法,这样在调用前后作一些相应的处理,这样说的比较笼统
接口:
package Father;
public interface FatherInter {
public void login();
public void submit();
}
继承接口实现类:
public class Son implements FatherInter {
@Override
public void login() {
System.out.println("登录");
}
@Override
public void submit() {
System.out.println("提交");
}
}
调用处理器:
public class MyInvocationHandler implements InvocationHandler {
private Object target;
public MyInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println("权限校验");
method.invoke(target, args); //执行被代理target对象的方法
System.out.println("日志记录");
return null;
}
}
Main函数:
public class Main {
public static void main(String[] args) {
Son si = new Son();
si.login();
si.submit();
System.out.println("-------------------------------");
MyInvocationHandler m = new MyInvocationHandler(si);
FatherInter s = (FatherInter)Proxy.newProxyInstance(si.getClass().getClassLoader(), si.getClass().getInterfaces(), m);
//Proxy提供用于创建动态代理类和实例的静态方法
//newProxyInstance返回一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序。
s.login();
s.submit();
}
}
输出:
登录
提交
-------------------------------
权限校验
登录
日志记录
权限校验
提交
日志记录
总结:
1. 动态代理 被代理的类要继承接口才行 基类不行
2. 写一个类继承InvocationHandler 作为中间层 给它传入你的实现类 额外添加的代码写在里面的invoke方法中
3. class类的getInterfaces()方法返回的是 实现接口数组
4. Proxy.newProxyInstance返回的是接口 而不是实现类
遇到这么一个问题
5. invoke(Object proxy, Method method, Object[] args)
proxy其实没有啥用 method是为了method.invoke(target, args)执行被代理类的方法args则是被调用方法的参数
6. Proxy类和一个InvocationHandler接口,通过使用这个类和接口就可以生成动态代理对象。JDK提供的代理只能针对接口做代理。我们有更强大的代理cglib,Proxy类中的方法创建动态代理类对象
Proxy.newProxyInstance(si.getClass().getClassLoader(), si.getClass().getInterfaces(), m);中getClassLoader()和getInterfaces()是不必动的 固定死了 代表加载器和实现类实现的接口 m是调用处理器:InvocationHandler的实现类
Exception in thread "main" java.lang.ClassCastException: com.sun.proxy.$Proxy0 cannot be cast to Father.FatherInter
at Father.Main.main(Main.java:13)
代理类没有实现接口,而是实现了某一基类。大家要注意了哦。
参考:
代理类:
https://blog.csdn.net/andyzhaojianhui/article/details/72833114