Java Reflection
Reflection(反射)是被视为动态语言的关键,反射机制允许程序在执行期 借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内 部属性及方法。
加载完类之后 ,在堆内存的方法区中就产生了一个Class类型的对象( 一个 类只有一个Class 对象),这个对象就包含了完整的类的结构信息 。我们可以通过这个对象看到类的结构。这个对象就像一面镜子 ,透过这个镜子看 到类的结构,所以,我们形象的称之为:反射。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yAn0NTq6-1600041571948)(/Users/haitao/Pictures/TyporaPic/15_反射/image-20200911221057961.png)]
动态语言 vs 静态语言
动态语言
是一类在运行时可以改变其结构的语言:例如新的函数、对象、甚至代码可以 被引进,已有的函数可以被删除或是其他结构上的变化。通俗点说就是在运 行时代码可以根据某些条件改变自身结构。 主要动态语言:Object-C、C#、JavaScript、PHP、Python、Erlang。
静态语言
与动态语言相对应的,运行时结构不可变的语言就是静态语言。如Java、C、 C++。
Java不是动态语言 ,但Java可以称之为==“准动态语言”。即Java有一定的动 态性,我们可以利用反射机制、字节码操作获得类似动态语言的特性==。 Java的动态性让编程的时候更加灵活!
Java反射机制研究及应用
Java反射机制提供的功能
反射相关的主要API
Class类
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-T7aCJ4g7-1600041571950)(/Users/haitao/Pictures/TyporaPic/15_反射/image-20200911221500946.png)]
获取Class类的实例(四种方法)
Class clazz1 = Person.class;
Class clazz2 = new Person().getClass();
Class clazz3 = Class.forName("com.atguigu.Person");
Class clazz4 = this.getClass().getClassLoader().loadClass("com.atguigu.Person");
哪些类型可以有Class对象?
(1)class: 外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类
(2)interface:接口
(3)[]:数组
(4)enum:枚举
(5)annotation:注解@interface
(6)primitive type:基本数据类型
(7)void
类的加载:类加载器会将字节码文件读入内存并为之创建一个Class类的实例。
类的链接:将类的二进制数据合并到JRE中,并对static修饰的属性进行默认初始化(基本数据类型、引用数据类型),如果是final修饰的常量会直接指向常量池的地址值。
类的初始化:比如static{ int m = 100}
,在类的链接m其实已经定义好了并且值是0,在这一步初始化阶段我们会修改m=100
。但是如果是fnal类型的,比如static final int m =100
,他会在链接阶段完成赋值操作m = 100
。
/*
jvm调用Demo类的main方法过程
1. 类的加载:类的加载器将Demo的字节码文件加载进内存,并为之创建一个Class类的实例,将这个实例存放在方法区中(缓存的作用,gc有权将这个Class的实例回收掉)
2. 类的链接:将类的二进制数据合并到JRE中,并对static类型修饰的变量进行默认初始化。
int类型就是0,引用类型就是null。static int m = 10,结果就是 m = 0,这个时候还没给m进行显示初始化
但如果是 static final int M = 100。先对M进行默认初始化即 M = 0 然后 M = 100,直接把值赋值好了。(final情况的赋值方式是我假设的,但是最终结果不变,即会进行显示初始化)
3. 类的初始化:这个时候就会执行静态代码块里面的内容,还有执行m = 10 进行显示初始化。
我们使用一个类的时候,如果这个类还未进行上述3步,就是依次执行: 类的加载 -> 类的链接 -> 类的初始化
*/
public class Demo {
public static void main(String[] args) {
System.out.println("静态方法");
// Demo2.M 属性是static final 修饰的,在链接初始化结果就赋值好了,所以不会执行到类的初始化结果,所以静态代码块就不会被调用
// System.out.println(Demo2.M);// 控制台结果:静态方法 100
// Demo2.m 是static 修饰的,必须执行完类的加载->类的链接->类的初始化才能确定其值,所以静态代码块会被调用。
System.out.println(Demo2.m);// 控制台结果:静态方法 类的初始化 1001
}
}
class Demo2 {
static final int M = 100;
static int m;
static {
System.out.println("类的初始化");
m = 1001;
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qDDtyS3M-1600041571951)(/Users/haitao/Pictures/TyporaPic/15_反射/image-20200911222054236.png)]
加载:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时 数据结构,然后生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问 入口(即引用地址)。所有需要访问和使用类数据只能通过这个Class对象。这个加载的 过程需要类加载器参与。
链接:将Java类的二进制代码合并到JVM的运行状态之中的过程。
初始化:
public class ClassLoadingTest {
public static void main(String[] args) {
System.out.println(A.m);
}
}
class A {
static {
m = 300;
}
static int m = 100;
}
//第二步:链接结束后m=0
//第三步:初始化后,m的值由()方法执行决定
// 这个A的类构造器()方法由类变量的赋值和静态代码块中的语句按照顺序合并 产生,类似于
// (){
// m = 300;
// m = 100;
// }
类的主动引用(一定会发生类的初始化)
类的被动引用(不会发生类的初始化)
当访问一个静态域时,只有真正声明这个域的类才会被初始化
当通过子类引用父类的静态变量,不会导致子类初始化
通过数组定义类引用,不会触发此类的初始化
引用常量不会触发此类的初始化(常量在链接阶段就存入调用类的常量池中了)
public class Demo {
public static void main(String[] args) throws Exception{
// TODO 主动引用:一定会导致A和Father的初始化
// A a = new A(); // main所在的类 父类被加载 子类被加载
// System.out.println(A.m); // 输出结果:main所在的类 父类被加载 子类被加载 100
// Class.forName("com.atguigu.A"); // 输出结果:main所在的类 父类被加载 子类被加载
// TODO 被动引用:不会导致A和Father的初始化
// A[] array = new A[5];// main所在的类
// System.out.println(A.b); // 只会导致初始化Father;输出结果:main所在的类 父类被加载 2
// System.out.println(A.M); // 引用常量不会触发此类的初始化(常量在链接阶段就存入调用类的常量池中了),不会导致A和Father的初始化 输出结果:main所在的类 1
}
static{
System.out.println("main所在的类");
}
}
class Father{
static int b = 2;
static{
System.out.println("父类被加载");
}
}
class A extends Father{
static{
System.out.println("子类被加载");
m = 300;
}
static int m = 100;
static final int M = 1;
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NQ2WujP1-1600041571952)(/Users/haitao/Pictures/TyporaPic/15_反射/image-20200911222527325.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8smXxhDy-1600041571953)(/Users/haitao/Pictures/TyporaPic/15_反射/image-20200911222602575.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-U9zAm7OC-1600041571954)(/Users/haitao/Pictures/TyporaPic/15_反射/image-20200911222621560.png)]
//1.获取一个系统类加载器
ClassLoader classloader = ClassLoader.getSystemClassLoader();
System.out.println(classloader);
//2.获取系统类加载器的父类加载器,即扩展类加载器
classloader = classloader.getParent();
System.out.println(classloader);
//3.获取扩展类加载器的父类加载器,即引导类加载器
classloader = classloader.getParent();
System.out.println(classloader);
//4.测试当前类由哪个类加载器进行加载
classloader = Class.forName("exer2.ClassloaderDemo").getClassLoader();
System.out.println(classloader);
//5.测试JDK提供的Object类由哪个类加载器加载
classloader =
Class.forName("java.lang.Object").getClassLoader();
System.out.println(classloader);
//*6.关于类加载器的一个主要方法:getResourceAsStream(String str):获取类路 径下的指定文件的输入流
InputStream in = null;
in = this.getClass().getClassLoader().getResourceAsStream("exer2\\test.properties");
System.out.println(in);
Class clazz1 = Person.class;
Class clazz2 = new Person().getClass();
Class clazz3 = Class.forName("java.lang.String");
Class clazz4 = ClassLoader.getSystemClassLoader().loadClass("java.lang.String");
Field、Method、Constructor、Superclass、Interface、Annotation
clazz.getInterfaces()
clazz.getSuperClass()
clazz.getConstructors() clazz.getDeclaredConstructors()
clazz.getMethods() clazz.getDeclaredMethods()
clazz.getFields() clazz.getDeclaredFields()
clazz.getAnnotation(Class annotationClass) clazz.getAnnotations() clazz.getDeclaredAnnotations()
clazz.getGenericSuperClass()
clazz.getPackage()
方法详细
// Constructor中的方法
Constructor类中:
取得修饰符: public int getModifiers();
取得方法名称: public String getName();
取得参数的类型:public Class<?>[] getParameterTypes();
// Method中的方法
Method类中:
public Class<?> getReturnType()取得全部的返回值
public Class<?>[] getParameterTypes()取得全部的参数
public int getModifiers()取得修饰符
public Class<?>[] getExceptionTypes()取得异常信息
Field方法中:
public int getModifiers() 以整数形式返回此Field的修饰符
public Class<?> getType() 得到Field的属性类型
public String getName() 返回Field的名称。
Annotation相关
泛型相关
获取父类泛型类型:Type getGenericSuperclass()
泛型类型:ParameterizedType
获取实际的泛型类型参数数组:getActualTypeArguments()
public class Demo {
@Test
public void testAnnotation() throws Exception {
Class clazz = Person.class;
// Annotation[] annotations = clazz.getDeclaredAnnotations();
Annotation[] annotations = clazz.getAnnotations();
for (Annotation annotation : annotations) {
if (annotation instanceof MyAnnotation) {
MyAnnotation myAnnotation = (MyAnnotation) annotation;
//TODO 获取注解的内容
System.out.println(myAnnotation.value());
System.out.println(myAnnotation.tableName());
System.out.println(Arrays.toString(myAnnotation.values()));
}
}
}
@Test
public void testGenericSuperClass() throws Exception {
Class clazz = Person.class;
// 获取运行时类的父类
Class superclass = clazz.getSuperclass();
System.out.println("superclass = " + superclass);
// 获取运行时类的带泛型的父类
Type genericSuperclass = clazz.getGenericSuperclass();
System.out.println("genericSuperclass = " + genericSuperclass.getTypeName());
// TODO 获取运行时类的带泛型父类的泛型(很有用);代码:逻辑性代码 vs 功能性代码
// class UserDao extends BaseDao{}
ParameterizedType paramType = (ParameterizedType) genericSuperclass;
Type[] actualTypeArguments = paramType.getActualTypeArguments(); // 获取泛型的真正类型
System.out.println(actualTypeArguments[0].getTypeName()); // "cn.haitaoss.User"
/*
猜测:jdbc框架将查询的记录封装成对象的原理
Class user = Class.forName(actualTypeArguments[0].getTypeName());
Object o = user.newInstance();
Method setName = user.getMethod("set" + "Name", String.class);
setName.invoke(o, "haitao");
*/
}
@Test
public void testField() throws Exception {
Class<?> clazz = Class.forName("com.atguigu.java1.Person");
// Field[] fields = clazz.getFields();
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
System.out.println("field = " + field);
int modifiers = field.getModifiers();
System.out.print("修饰符是:" + Modifier.toString(modifiers));
System.out.println("类型是:" + field.getType().getName());
System.out.println("变量名是:" + field.getName());
System.out.println();
}
}
@Test
public void testMethod() {
Class clazz = new Person().getClass();
Method[] declaredMethods = clazz.getDeclaredMethods();
// public static void main(String name,Integer age) throws Exception{}
for (Method method : declaredMethods) {
Annotation[] annotations = method.getAnnotations();
if (!(annotations != null && annotations.length == 0)) {
for (int i = 0; i < annotations.length; i++) {
System.out.println("@" + annotations[i]);
}
}
int modifiers = method.getModifiers();
System.out.print(Modifier.toString(modifiers) + " ");
System.out.print(method.getReturnType().getName() + " ");
System.out.print(method.getName() + "(");
Class<?>[] parameterTypes = method.getParameterTypes();
if (!(parameterTypes != null && parameterTypes.length == 0)) {
for (int i = 0; i < parameterTypes.length; i++) {
if (i == parameterTypes.length - 1) {
System.out.print(parameterTypes[i].getName() + " " + "args_" + i);
break;
}
System.out.print(parameterTypes[i].getName() + " " + "args_" + i + " ,");
}
}
System.out.print(") ");
Class<?>[] exceptionTypes = method.getExceptionTypes();
if (!(exceptionTypes != null && exceptionTypes.length == 0)) {
System.out.print("throws ");
for (int i = 0; i < exceptionTypes.length; i++) {
if (exceptionTypes.length - 1 == i) {
System.out.print(exceptionTypes[i].getName());
break;
}
System.out.print(exceptionTypes[i].getName() + " ,");
}
}
System.out.println("{}");
System.out.println();
}
}
@Test
public void testConstructor() throws Exception {
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
Class<?> clazz = contextClassLoader.loadClass("com.atguigu.java1.Person");
// Constructor>[] constructors = clazz.getConstructors();
Constructor<?>[] constructors = clazz.getDeclaredConstructors();
for (Constructor<?> constructor : constructors) {
int modifiers = constructor.getModifiers();
System.out.print(Modifier.toString(modifiers) + " ");
System.out.print(constructor.getName() + "");
System.out.print(constructor.getParameterTypes());
System.out.println();
}
}
@Test
public void testInterface() {
Class clazz = Person.class;
// 获取运行时类的接口
Class[] interfaces = clazz.getInterfaces();
for (Class anInterface : interfaces) {
System.out.println(anInterface);
}
// 获取运行时父类的接口
Class[] interfaces1 = clazz.getSuperclass().getInterfaces();
for (Class aClass : interfaces1) {
System.out.println(aClass);
}
}
@Test
public void testPackage() {
Class clazz = Person.class;
// 获取运行时类的包
Package aPackage = clazz.getPackage();
System.out.println(aPackage.getName());
}
}
在反射机制中,可以直接通过Field类操作类中的属性,通过Field类提供的set()和 get()方法就可以完成设置和取得属性内容的操作。
在Field中:
Class clazz = ClassLoader.getSystemClassLoader().loadClass("java.lang.String");
Field[] fields = clazz.getDeclaredFields();
Field field = fields[0];
field.getModifiers();
Modifier.toString(1);
field.getName();
Object o = clazz.newInstance();
field.set(o,"");
field.get(o);
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FMCTTrnZ-1600041571954)(/Users/haitao/Pictures/TyporaPic/15_反射/image-20200912090803577.png)]
Object invoke(Object obj, Object … args)
说明:
- Object 对应原方法的返回值,若原方法无返回值,此时返回null
- ==若原方法若为静态方法,此时形参Object obj可为null==
- 若原方法形参列表为空,则Object[] args为null
- 若原方法声明为private,则需要在调用此invoke()方法前,显式调用 方法对象的setAccessible(true)方法,将可访问private的方法。
Class clazz = Class.forName("java.lang.String");
Method[] methods = clazz.getDeclaredMethods();
Method method = methods[0];
method.setAccessible(true);
Object o = clazz.newInstance();
method.invoke(o);
method.getModifiers();
method.getExceptionTypes();
method.getParameters();
Class clazz = ClassLoader.getSystemClassLoader().loadClass("java.lang.String");
clazz.getDeclaredConstructors();
Constructor[] cons = clazz.getConstructors();
Constructor con = cons[0];
con.newInstance();
代理设计模式的原理:
使用一个代理将对象包装起来, 然后用该代理对象取代原始对象。任何对原 始对象的调用都要通过代理。代理对象决定是否以及何时将方法调用转到原 始对象上。
之前为大家讲解过代理机制的操作,属于静态代理,特征是代理类和目标 对象的类都是在编译期间确定下来,不利于程序的扩展。同时,每一个代 理类只能为一个接口服务,这样一来程序开发中必然产生过多的代理。最 好可以通过一个代理类完成全部的代理功能。
动态代理是指客户通过代理类来调用其它对象的方法,并且是在程序运行时 根据需要动态创建目标类的代理对象。
动态代理使用场合:
调试
远程方法调用
动态代理相比于静态代理的优点:
抽象角色中(接口)声明的所有方法都被转移到调用处理器一个集中的方法中 处理,这样,我们可以更加灵活和统一的处理众多的方法。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TuPxSE5C-1600041571955)(/Users/haitao/Pictures/TyporaPic/15_反射/image-20200912104519917.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VH7e0Op2-1600041571956)(/Users/haitao/Pictures/TyporaPic/15_反射/image-20200912104543294.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6TqqxDIb-1600041571957)(/Users/haitao/Pictures/TyporaPic/15_反射/image-20200912104550007.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-s1HG3h6T-1600041571957)(/Users/haitao/Pictures/TyporaPic/15_反射/image-20200912104557382.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TYpXP5c8-1600041571958)(/Users/haitao/Pictures/TyporaPic/15_反射/image-20200912104610756.png)]
package com.atguigu.java;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
*
* 动态代理的举例
*
* @author shkstart
* @create 2019 上午 10:18
*/
interface Human{
String getBelief();
void eat(String food);
}
//被代理类
class SuperMan implements Human{
@Override
public String getBelief() {
return "I believe I can fly!";
}
@Override
public void eat(String food) {
System.out.println("我喜欢吃" + food);
}
}
class HumanUtil{
public void method1(){
System.out.println("====================通用方法一====================");
}
public void method2(){
System.out.println("====================通用方法二====================");
}
}
/*
要想实现动态代理,需要解决的问题?
问题一:如何根据加载到内存中的被代理类,动态的创建一个代理类及其对象。
问题二:当通过代理类的对象调用方法a时,如何动态的去调用被代理类中的同名方法a。
*/
class ProxyFactory{
//调用此方法,返回一个代理类的对象。解决问题一
public static Object getProxyInstance(Object obj){
//obj:被代理类的对象
MyInvocationHandler handler = new MyInvocationHandler();
handler.bind(obj);
return Proxy.newProxyInstance(obj.getClass().getClassLoader(),obj.getClass().getInterfaces(),handler);
}
}
class MyInvocationHandler implements InvocationHandler{
private Object obj;//需要使用被代理类的对象进行赋值
public void bind(Object obj){
this.obj = obj;
}
//当我们通过代理类的对象,调用方法a时,就会自动的调用如下的方法:invoke()
//将被代理类要执行的方法a的功能就声明在invoke()中
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
HumanUtil util = new HumanUtil();
util.method1(); // AOP
//method:即为代理类对象调用的方法,此方法也就作为了被代理类对象要调用的方法
//obj:被代理类的对象
Object returnValue = method.invoke(obj,args);
util.method2();
//上述方法的返回值就作为当前类中的invoke()的返回值。
return returnValue;
}
}
public class ProxyTest {
public static void main(String[] args) {
SuperMan superMan = new SuperMan();
//proxyInstance:代理类的对象
Human proxyInstance = (Human) ProxyFactory.getProxyInstance(superMan);
//当通过代理类对象调用方法时,会自动的调用被代理类中同名的方法
String belief = proxyInstance.getBelief();
System.out.println(belief);
proxyInstance.eat("四川麻辣烫");
System.out.println("*****************************");
NikeClothFactory nikeClothFactory = new NikeClothFactory();
ClothFactory proxyClothFactory = (ClothFactory) ProxyFactory.getProxyInstance(nikeClothFactory);
proxyClothFactory.produceCloth();
}
}
使用Proxy生成一个动态代理时,往往并不会凭空产生一个动态代理,这样没有 太大的意义。通常都是为指定的目标对象生成动态代理
这种动态代理在AOP中被称为AOP代理,AOP代理可代替目标对象,AOP代理 包含了目标对象的全部方法。但AOP代理中的方法与目标对象的方法存在差异: AOP代理里的方法可以在执行目标方法之前、之后插入一些通用处理
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wrFT2vhM-1600041571959)(/Users/haitao/Pictures/TyporaPic/15_反射/image-20200912110047965.png)]
interface Dog{
void info();
void run();
}
class HuntingDog implements Dog{
public void info(){
System.out.println("我是一只猎狗");
}
public void run(){
System.out.println("我奔跑迅速");
}
}
class DogUtil{
public void method1(){
System.out.println("=====模拟通用方法======");
}
public void method2(){
System.out.println("=====模拟通用方法======");
}
}
class MyInvocationHandler implements InvocationHandler{
// 需要被代理的对象
private Object target;
public void setTarget(Object obj){
target = obj;
}
// 执行动态代理对象的所有方法时,都会被替换成执行如下的invoke() 方法
public Object invoke(Object proxy,Method method,Object[] args) throws Exception{
DogUtil du = new DogUtil();
// 执行DogUtil 的method1 。
du.method1();
// 以target作为主调来执行method方法
Object returnVal = method.invoke(target,args);
// 执行DogUtil 的method2 。
du.method2();
// 返回方法的返回值
return returnVal;
}
}
class MyProxyFactory{
// 为指定target生成动态代理对象
public Object getProxy(Object target){
MyInvocationHandler myInvocationHandler = new MyInvocationHandler();
myInvocationHandler.setTarget(target);
//创建代理对象,并返回
// 类加载器一般就写被代理类的加载器即可。当然你也可以写别的,比如ClassLoader.getClassLoader()
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getInterfaces(),
myInvocationHandler)
}
}