本节涉及很多JVM知识,结合JVM内容理解,这里只有一部分
类动态加载机制
JAVA是动态加载类的,这是JAVA与其它静态加载语言如CPP的主要区别之一
整个初始化流程
①加载类并创建实例的流程:类加载-实例化。注意加载类不一定要创建实例。
②类加载阶段包括加载、连接(验证、准备、解析)、初始化、使用和卸载,它们开始的顺序一定,但是完成的顺序并不一定,因为在一个阶段内会调用其他阶段。
加载就是加载Class对象,见3
类加载的准备步骤中给静态成员变量开辟了内存,也就相当于全部为0或null,在初始化步骤中才执行初始化赋值(final
成员除外,final
成员在准备阶段即进行初始化赋值)
举例:public static int a=1
,变量在准备阶段过后的初始值是0而不是1,如果是引用成员变量就会为null,其他基本类型对应的是其基本默认值。只有在初始化阶段过后,才会变成1。
(类加载也可以叫类初始化过程,注意类初始化不是子过程的字段初始化)
类加载流程更多内容见JAVA虚拟机
③实例化阶段包括新建对象-非静态成员初始化-调用构造器
这里的非静态成员变量也和类加载同理,先分配内存(准备),再调用构造器,赋予初值(初始化)
不过要先新建一个对象(开辟内存),对位于类加载中加载Class
Class对象
①每个类在编译的时候都会生成一个Class对象,固化储存在类名.class文件中,由JVM中的类加载器子系统加载进内存中的方法区里。Class对象也是对象,可以由个Class类型的引用指向它
②Class对象包含了类的元数据(包括类名,变量名,方法),加载后储存在方法区中。与类绑定的成员和方法(静态成员、静态方法)都在Class对象中。(也就是说,这时候类信息、静态成员和静态方法已经被初始化了,此时对象还没生成,此时构造器还没调用)
③在程序第一次引用(见下)会触发加载该类的Class对象。(构造器是一个类的静态方法)。
④引用:分为主动引用和被动引用(这里参考ZYG同学的博客)
a.主动引用
五种情况会触发初始化阶段,这五类情况是主动引用
new、getstatic、putstatic和invokestatic
命令后三个是对于静态字段的读写与静态方法的调用
反射调用某个类
加载某个类,如果父类没有被加载,那么也进行加载
执行主类(包含main函数)
使用MethodHanler(类似于反射的一个针对于方法的东西,链接https://my.oschina.net/floor/blog/1535062)
b.被动引用
- 子类调用父类的静态字段,只会初始化父类
- 初始化该类的数组
- 调用静态(static)常量(final)已经优化到常量池里面
⑤一旦Class对象被加载进内存方法区,它就可以被用来在堆上创建这个类的所有对象(类加载器先会检查是否已加载Class对象,已加载类就可以直接用)【对象实例是由该类的Class对象实例来创建的】
⑥所有Class对象都属于Class
类
初始化流程
于是现在就可以更底层理解Ch.5中的初始化顺序了
初始化顺序:静态块=静态成员>非静态块=非静态成员>构造器调用
生成对象过程:非静态部分初始化、构造器调用
以new
命令为例,new
引用类会触发初始化,其目的是新建该类对象,如果该类没有在内存方法区中(即Class对象未加载),那么就会在方法区上先加载Class对象,走类加载过程;然后再走实例化过程
加载类不一定会生成对象
(比如调用类的静态成员/方法)这样就不会初始化非静态成员/方法、调用构造器了
双亲委派机制
类加载的双亲委派机制
当某个类加载器需要加载某个
.class
文件时,它首先把这个任务[从当前类加载器]委托[给他的上级类加载器],[递归]这个操作,如果上级的类加载器没有加载,自己才会去加载这个类,加载了就直接返回。这是一种代理模式。意义
①保证一个类只被加载一次,省内存
②安全(保证不被篡改)
为了系统类的安全,类似“
java.lang.Object
”这种核心类,JVM需要保证他们生成的对象都会被认定为同一种类型。即“通过代理模式,对于 Java 核心库的类的加载工作由引导类加载器来统一完成,保证了 Java 应用所使用的都是同一个版本的 Java 核心库的类,是互相兼容的”。具体来说:比如我们想写一个
java.lang.Object
类,我们写的类属于程序的类,当前由系统类(应用程序类)加载器加载,它是启动类加载器和扩展类加载器的儿子,那么根据双亲委托机制,就会先由父类的类加载器来加载java.lang.Object
,那么这样我们自己写的类就无法被加载(比如我们自己定义包名和类名叫java.lang.Object)
,那么我们的系统类加载器加载这个类之前先会找更上层的类加载器,更上层的类加载器在java核心类库找到了java.lang.Object
,那么久不会再调用系统类加载器加载我们自己写的了);不过我们可以自己定义一个类加载器来达到这个目的,为了避免双亲委托机制,这个类加载器也必须是特殊的。由于系统自带的三个类加载器都加载特定目录下的类,如果我们自己的类加载器加载一个特殊的目录,那么系统的加载器就无法加载,也就是最终还是由我们自己的加载器加载。
Class常用API
①首先获取class
对象(Class类引用):
Ⅰ.Class.forName(className)
:使用加载当前类的类加载器来装载制定的类,可以获取className类的Class对象的引用(如果获取的时候还未加载Class对象,那就执行类加载流程;必须检查异常,如果找不到,抛出ClassNotFoundException;这里的className要包含包名)
Ⅱ.Object.getName(objectName)
可以获取objectName实例的类的Class引用
注意this
指代的是这个类的实例,而不是这个类!所以要获取this
的类信息得用getClass()
Ⅲ.className.class
,类字面常量,获取className类的Class对象的引用,在编译时检查,安全,所以可以不检查异常,且使用方便,高效、推荐使用这个替代forName;且不但适用于普通的类,也适用于接口、数组、基本数据类型。(基本数据类型及其封装类都可以使用class
,基本数据类型封装类还有个TYPE
字段指向该类未封装的基本数据类型Class对象);但是使用.class
创建对象引用的时候,不会自动初始化该Class
对象(类加载流程:加载-链接-初始化),仅在对静态方法/非常数静态域(非static final)第一次引用时才初始化。
于是可以这样写Class abcclass = abc.class
②然后就可以通过class
对象搞事情可以做到很多类信息相关的事情:
获取到Class引用之后(设名字为class
)可以使用class.getSimpleName()
来获取不含包名的类名、class.getCanonicalName()
获取含包名的类名,class.getInterfaces()
获取该class对象中包含的所有接口,class.getSuperclass()
查询基类
class.isInterface()
可以通过Class
对象来判断该类是否为接口,同理还有isXXX()
等一堆方法,获取类信息,总之,很牛逼
class.getDeclaredConstructor().newInstance()
可以创建实例并返回引用。getDeclaredConstructor()
获得class
的构造器对象并调用其newInstance()
方法创建对象创建实例,适用于有参构造器和无参构造器;该方法需要检查异常NoSuchMethodException
(class.newInstance()
仅适用无参构造器且已被JAVA9不推荐);注意,即使使用泛型,返回的引用类型也为确切类型。
③类型判断
object instanceof className
:判断实例是否是某类,返回布尔值;注意是className
类名,而不是Class对象
class.isInstance(object)
:判断实例是否是class对象的类的实例(是否由class类创建)
父类.class.isAssignableFrom(子类.class)
:判断子类是否继承父类
泛化Class
的引用(Class与泛型)
B
是A
的子类,但B.class
并不是A.class
的子类,所以Class<?> A
是强制检查是A的Class实例,B不行;只有写Class<? extends A> intClass = B.class
,才会同时检查其子类Class
见Ch.15-通配符
反射机制
反射机制可以提供运行时的类信息。如果不能在主程序运行的时候知晓某类的类型和类信息,我们就可以通过反射机制来让我们在运行的时候获取类信息,使用该类。
反射机制应用场景:
①使用实现SPI的库(如JDBC),SPI(Service Provider Interface)由JAVA核心库提供接口,由不同厂商具体实现。那么我们就可以通过反射动态加载不同厂商的SPI实现
②动态代理:根据对象在内存中加载的Class类创建运行时类对象,从而调用代理类方法和属性。我也不知道我的接口,真正实现是谁传过来后我用类名加载,屏蔽掉实现的细节,让使用者更加方便好用,提高程序的灵活性
静态代理通俗点将就是自己手写一个代理类,而动态代理则不用我们手写,而是依赖于java反射机制
③越权:可以用反射改变类方法或变量的权限,比如私有的改成共有的
反射机制实现:反射由
Class
类与java.lang.reflect
类库提供支持,该类库包含Field
类、Method
类、Constructor
类。这些类型的对象是由JVM在运行时创建的,用以表示位置类里对应的成员。反射常用API:Java高级特性——反射(记住常用的,这篇总结的非常好!)
注意使用:①
getMethod(),getConstructor()
都只能获取公有的,getDeclaredMethod
和getDeclaredConstructor
能获取任意权限的,带s
的方法即为获取全部,返回列表反射机制四大类:
Class、Field、Method、Constructor
,都有实例化对象②从
Class
获取到的Field、Method
都是类信息,想要调用该成员、方法,首先得构建对象实例,然后方法调用invoke(Object,Object...args)
还得传入对应的参数,Object...args
表示是边长参数列表,args
只要是Object类即可(即一切)③获取Class中的
Method、Constructor
的方法中,获取单个的需要明确指定方法的参数表,比如getMethod(String name, Class...<?> parameterTypes)
,其中Class...<?> parameterTypes
表示参数类型的Class
类。距离:假设参数是两个字符串,那就应该写`getMethod(String name, String.class,String.class)
java的反射到底是有什么用处?怎么用? - Java3y的回答 - 知乎
动态代理
代理模式是用代理类来代理实现类进行操作,在代理类中生成目标类的对象,但又增强了其它方法。代理其实就是个每一层代理加强一点的套娃。其目的是想要将额外的操作从“实际”对象中分离到不同的地方
但是静态代理的缺陷是,每有一个目标类 ,我们就要写一个代理类,这样不好
在静态代理中,我们需要写一个代理类(大套娃),而在动态代理中,我们想不写代理类,而是生成一个代理Class对象,然后用它创建代理实例,再由代理 实例调用目标对象。(也就是说Class对象就是代理,基于反射机制,也就成了个动态套娃)
动态代理的调用处理程序必须事先java.lang.reflect.InvocationHandler
接口,及使用java.lang.reflect.Proxy
类中的newProxyInstance
方法动态的创建代理类。
首先用Proxy.getProxyClass()
用实际接口class去构造代理class,然后在代理class中建立代理对象。代理Class的构造器创建对象时,需要传入InvocationHandler(一般用匿名内部类)。每次调用代理对象的method方法,最终都会调用InvocationHandler的invoke()方法(将代理代理对象方法导向invoke),在invoke里新建目标类实例(目标类以参数形式传入),然后使用method.invoke来调用该实例的method方法
这里参考:Java 动态代理作用是什么? - bravo1988的回答 - 知乎 (这篇写得非常清晰)
参考:Java 动态代理作用是什么? - ZeaTalk的回答 - 知乎 https://www.zhihu.com/question/20794107/answer/23330381
package test;
//接口
public interface Subject
{
public void doSomething();
}
package test;
//真实类
public class RealSubject implements Subject
{
public void doSomething()
{
System.out.println( "call doSomething()" );
}
}
package test;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ProxyHandler implements InvocationHandler
{
//真实对象
private Object tar;
//绑定委托对象,并返回代理类
public Object bind(Object tar)
{
this.tar = tar;
//绑定该类实现的所有接口,取得代理类 ,这里就是前面所说的,用真实接口class来构造代理class,再用代理class的构造器生成代理对象
return Proxy.newProxyInstance(tar.getClass().getClassLoader(),
tar.getClass().getInterfaces(),
this);
}
//调用代理对象实际上就会调用该方法
public Object invoke(Object proxy , Method method , Object[] args)throws Throwable
{
Object result = null;
//这里就可以进行所谓的AOP编程了
//在调用具体函数方法前,执行功能处理
//【这里就是,让真实对象tar,调用method方法,参数为args】,在这前后就可以进行增强
//这里就可以看出动态代理的优越处了:实例对象、方法全是动态的
result = method.invoke(tar,args);
//在调用具体函数方法后,执行功能处理
return result;
}
}
public class TestProxy
{
public static void main(String args[])
{
ProxyHandler proxy = new ProxyHandler();
//绑定该类实现的所有接口
Subject sub = (Subject) proxy.bind(new RealSubject());
sub.doSomething();
}
}
如上:在实际使用中直接Proxy.newProxyInstance()
,然后就返回代理实例,简化使用
Spring AOP基于动态代理的
反射可以做到解耦:编译期不依赖、运行时依赖
bean:可重用组件
反射加载配置+(单例)bean工厂 解耦类依赖 IOC=====再发展一下,框架做工厂的事情,那就是Spring框架
P.S.JAVA是个大玩具,很有意思,反射机制下,JAVA没有秘密(所以真秘密要用NDK)