类初始化概述
类的初始化指的是将我们编写的类文件编译之后生成的字节码文件进行加载、连接、初始化的过程。其中字节码文件的加载由类加载器完成,通过不同的类加载器,可以对不同来源的字节码文件进行加载,例如:本地文件系统、网路、动态编译动态加载等。当一个类被加载成功之后会生成一个Class对象,接着将会进入类的连接阶段,连接阶段主要完成了类内部结构的验证、类成员内存的分配及初始值设置和将符号引用替换成直接引用。经过类的连接阶段后就可以初始化类了,类的初始化主要是对类的普通成员变量和类的静态成员变量进行初始化。那么什么情况下类才会被初始化呢?
1、对象的创建,无论使用new关键字还是反射创建对象,都必须保证对应的类被初始化。
2、静态方法调用,当调用一个静态方法时,类名称作为主调,所以必须保证该类被初始化。
3、反射创建类或接口的Class对象时,必须保证类或接口被初始化。
4、当初始化一个类的子类时,必须保证父类及所有引用的类被初始化。
5、运行一个类的主方法时,必须保证主方法所在的类被初始化。
需要注意的是,对于一个被final关键字修饰的变量被使用时,如果此变量的值在编译时就已经确定了,则在编译的过程中所有使用此变量的地方,都将被变量所对应的值替换,所以使用final关键字修饰的有确定值的变量时,变量所在的类不会被初始化。
类加载器的概述
顾名思义,所谓类加载器是Java提供的加载类字节码文件的工具类,类加载器的工作机制是:一旦一个类被加载时,JVM中相同的类不会被再次加载,JVM识别一个类是否被加载过的标识是检查此类所对应的全限定名称(包.类名称)是否存在。Java中根据类加载职能的不同,将类加载器分为以下三种:
根加载器:BootStrap ClassLoader,主要负责加载Java的核心类库。
扩展加载器:Extension ClassCloader,主要负责加载Java扩展类库,通常是加载系统属性java.ext.dirs下的类库。
系统加载器:System ClassCloader,主要加载-classpath参数 指定路径下或者环境变量CLASSPATH对应路径下的类库。
Java中类加载器的加载机制是父类委托机制:即当类加载器加载一个类文件时,如果类加载器存在父级加载器,父级加载器还存在父级加载器,则依次向上级委托,直到最顶级加载器,如果顶级加载器加载不成功,则由当前加载器去加载,典型的啃老式加载过程,除非显示指定类加载器。
类加载器的级别如下:
--> 自定义类加载器 --> 系统类加载器 --> 扩展类加载器 --> 根加载器
除了上述三种Java提供的类加载器外,开发人员可以继承ClassLoader类实现自己的类加载,自己实现的类加载器级别最低。
package com.icypt.classloader;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class CustomClassLoader extends ClassLoader{
public static final String BASE_PATH = "E:\\个人文件\\公众号\\文章底稿及代码\\Java基础\\代码\\java-base-topic\\Topic28\\";
public static void main(String[] args) throws Exception{
if(args.length < 1) {
throw new RuntimeException("参数错误:" +args);
}
String name = args[0];
String targetMethodArgs = args[1];
CustomClassLoader cc = new CustomClassLoader();
Class<?> clazz = cc.findClass(name);
Method method = clazz.getMethod("printMsg", String.class);
method.invoke(null, targetMethodArgs);
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
Class<?> clazz = null;
//获得模式对象
Pattern pattern = Pattern.compile("([a-z]+[.])*[A-Z][a-zA-Z]+");
//获得匹配对象
Matcher matcher = pattern.matcher(name);
//匹配不成功
if(!matcher.matches()) {
throw new RuntimeException("包名称不正确");
}
//生成类文件名称和字节码文件名称
String srcFileName = BASE_PATH + "src\\" + name.replaceAll("\\.", "\\\\").concat(".java");
String classFileName = BASE_PATH + "target\\" + name.replaceAll("\\.", "\\\\").concat(".class");
File srcFile = new File(srcFileName);
File classFile = new File(classFileName);
//如果Class文件不存在或者字节码文件的修改时间小于源码文件时间,需要重新编译源码文件
if(!classFile.exists() || (classFile.lastModified() < srcFile.lastModified())) {
//编译文件
this.excuteCompile(srcFileName);
}
if(classFile.exists()) {
byte[] data = this.getClassFileData(classFileName);
clazz = defineClass(name, data, 0, data.length);
if(clazz == null) {
throw new RuntimeException("类加载失败:" + name);
}
}
return clazz;
}
/**
* 获取字节码文件
* @param fileName 字节码文件名称
* @return 字节数组
*/
public byte[] getClassFileData(String fileName) {
byte[] fileData = null;
File file = new File(fileName);
fileData = new byte[(int) file.length()];
InputStream is = null;
try {
is = new FileInputStream(file);
int len = is.read(fileData);
if(len != file.length()) {
throw new RuntimeException(fileName+"文件读取不完整");
}
} catch (IOException e) {
System.out.print("字节码文件加载失败");
e.printStackTrace();
} finally {
close(is);
}
return fileData;
}
/**
* 编译源文件
* @param sourceName 源文件名称
*/
public boolean excuteCompile(String sourceName) {
Process p = null;
try {
//执行编译命令
p = Runtime.getRuntime().exec("javac " + sourceName + " -d " + BASE_PATH + "target" + " -encoding utf-8");
//阻塞其他线程的执行,直到当前线程执行完成
p.waitFor();
} catch (IOException e) {
System.out.println("执行编译失败");
e.printStackTrace();
} catch (InterruptedException e) {
System.out.println("编译过程被打断");
e.printStackTrace();
}
//0表示线程正常退出
return p.exitValue() == 0;
}
/**
* 统一关闭入口
* @param t
*/
public <T extends Closeable> void close(T t) {
try {
if(t != null){
t.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
栗子,被加载的Test类
上栗中的自定义类加载器继承了ClassLoader类,并重写了其findClass方法,完成了自动编译、加载类的功能。ClassLoasder还有一个关键的方法loadClass。loadClass的工作原理是先调用findLoadedClass方法检查类是否已经被加载,如果已经被加载则直接返回,否则执行委托加载机制进行类加载。而findClass方法是类加载器直接加载类文件的方法,他可以避免从缓存或者使用父类委托机制加载类。
反射概述
所谓反射机制,就是指在Java程序运行期间,对于任意一个类,都可以通过该机制获取类的相关信息,例如类拥有的成员变量、方法、内部类、继承的父类、实现的接口、使用的注解信息等等。从面向对象的角度来看,反射机制是对一个java类的高度抽象。
反射常用方法
反射类对象提供的方法很有规律,大致分为两大类:获取的目标被public访问权限修饰以及所有访问权限修饰,所有访问权限修饰的获取方法都以getDeclare开头,同时这些方法都是成对出现的,目的是获取单个信息和全部信息,需要注意的是获取注解信息时,多出了一对儿以Type结尾的方法,这是在JDK1.8,之后新增的,因为JDK1.8之后支持重复注解的使用,依据此方法,可以获取指定类型的一个或者多个注解信息。还有就是对于接口和父类的获取分别只有一个方法,对于接口获取的是多个,对于父类获取的是一个,因为单继承局限。以上描述的是其反射对象获取类信息的方法,除了这些方法之外还有一些其他的方法,例如:获取包名称、类检查、类名称以及判断是否为注解、匿名内部类、数组、接口等等,下面将常用方法列举出来,供大家学习使用。
自定义类加载器
package com.icypt.classloader;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class CustomClassLoader extends ClassLoader{
public static final String BASE_PATH = "E:\\个人文件\\公众号\\文章底稿及代码\\Java基础\\代码\\java-base-topic\\Topic28\\";
public static void main(String[] args) throws Exception{
if(args.length < 1) {
throw new RuntimeException("参数错误:" +args);
}
String name = args[0];
String targetMethodArgs = args[1];
CustomClassLoader cc = new CustomClassLoader();
Class<?> clazz = cc.findClass(name);
Method method = clazz.getMethod("printMsg", String.class);
method.invoke(null, targetMethodArgs);
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
Class<?> clazz = null;
//获得模式对象
Pattern pattern = Pattern.compile("([a-z]+[.])*[A-Z][a-zA-Z]+");
//获得匹配对象
Matcher matcher = pattern.matcher(name);
//匹配不成功
if(!matcher.matches()) {
throw new RuntimeException("包名称不正确");
}
//生成类文件名称和字节码文件名称
String srcFileName = BASE_PATH + "src\\" + name.replaceAll("\\.", "\\\\").concat(".java");
String classFileName = BASE_PATH + "target\\" + name.replaceAll("\\.", "\\\\").concat(".class");
File srcFile = new File(srcFileName);
File classFile = new File(classFileName);
//如果Class文件不存在或者字节码文件的修改时间小于源码文件时间,需要重新编译源码文件
if(!classFile.exists() || (classFile.lastModified() < srcFile.lastModified())) {
//编译文件
this.excuteCompile(srcFileName);
}
if(classFile.exists()) {
byte[] data = this.getClassFileData(classFileName);
clazz = defineClass(name, data, 0, data.length);
if(clazz == null) {
throw new RuntimeException("类加载失败:" + name);
}
}
return clazz;
}
/**
* 获取字节码文件
* @param fileName 字节码文件名称
* @return 字节数组
*/
public byte[] getClassFileData(String fileName) {
byte[] fileData = null;
File file = new File(fileName);
fileData = new byte[(int) file.length()];
InputStream is = null;
try {
is = new FileInputStream(file);
int len = is.read(fileData);
if(len != file.length()) {
throw new RuntimeException(fileName+"文件读取不完整");
}
} catch (IOException e) {
System.out.print("字节码文件加载失败");
e.printStackTrace();
} finally {
close(is);
}
return fileData;
}
/**
* 编译源文件
* @param sourceName 源文件名称
*/
public boolean excuteCompile(String sourceName) {
Process p = null;
try {
//执行编译命令
p = Runtime.getRuntime().exec("javac " + sourceName + " -d " + BASE_PATH + "target" + " -encoding utf-8");
//阻塞其他线程的执行,直到当前线程执行完成
p.waitFor();
} catch (IOException e) {
System.out.println("执行编译失败");
e.printStackTrace();
} catch (InterruptedException e) {
System.out.println("编译过程被打断");
e.printStackTrace();
}
//0表示线程正常退出
return p.exitValue() == 0;
}
/**
* 统一关闭入口
* @param t
*/
public <T extends Closeable> void close(T t) {
try {
if(t != null){
t.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
被加载的Test类
上栗中的自定义类加载器继承了ClassLoader类,并重写了其findClass方法,完成了自动编译、加载类的功能。ClassLoasder还有一个关键的方法loadClass。loadClass的工作原理是先调用findLoadedClass方法检查类是否已经被加载,如果已经被加载则直接返回,否则执行委托加载机制进行类加载。而findClass方法是类加载器直接加载类文件的方法,他可以避免从缓存或者使用父类委托机制加载类。
反射概述
所谓反射机制,就是指在Java程序运行期间,对于任意一个类,都可以通过该机制获取类的相关信息,例如类拥有的成员变量、方法、内部类、继承的父类、实现的接口、使用的注解信息等等。从面向对象的角度来看,反射机制是对一个java类的高度抽象。
反射常用方法
反射类对象提供的方法很有规律,大致分为两大类:获取的目标被public访问权限修饰以及所有访问权限修饰,所有访问权限修饰的获取方法都以getDeclare开头,同时这些方法都是成对出现的,目的是获取单个信息和全部信息,需要注意的是获取注解信息时,多出了一对儿以Type结尾的方法,这是在JDK1.8,之后新增的,因为JDK1.8之后支持重复注解的使用,依据此方法,可以获取指定类型的一个或者多个注解信息。还有就是对于接口和父类的获取分别只有一个方法,对于接口获取的是多个,对于父类获取的是一个,因为单继承局限。以上描述的是其反射对象获取类信息的方法,除了这些方法之外还有一些其他的方法,例如:获取包名称、类检查、类名称以及判断是否为注解、匿名内部类、数组、接口等等,下面将常用方法列举出来,供大家学习使用。
/*
*获取类的构造方法
*/
//获取指定参数类型的构造方法
public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)throws NoSuchMethodException, SecurityException {}
//获取指定参数类型的public构造方法
public Constructor<T> getConstructor(Class<?>... parameterTypes) throws NoSuchMethodException, SecurityException {}
//获取所有的构造方法
public Constructor<?>[] getDeclaredConstructors() throws SecurityException{}
//获取所有被public修饰符修饰的构造方法
public Constructor<?>[] getConstructors() throws SecurityException{}
/*
*获取类的普通方法
*/
//获取指定方法名称的成员方法,无访问修饰符限制
public Method getDeclaredMethod(String name, Class<?>... parameterTypes)throws NoSuchMethodException, SecurityException {}
//获取指定方法名称的修饰符为public的成员方法
public Method getMethod(String name, Class<?>... parameterTypes)throws NoSuchMethodException, SecurityException{}
//获取所有的成员方法,无访问修饰符限制
public Method[] getDeclaredMethods() throws SecurityException {}
//获取所有的public修饰的成员方法
public Method[] getMethods() throws SecurityException {}
/*
*获取类的成员变量
*/
//获取指定变量名称的成员变量,无访问修饰符限制
public Field getDeclaredField(String name)throws NoSuchFieldException, SecurityException{}
//获取指定变量名称的public修饰的成员变量
public Field getField(String name)throws NoSuchFieldException, SecurityException{}
//获取所有的成员变量,无访问修饰限制
public Field[] getDeclaredFields() throws SecurityException{}
//获取所有的public修饰的成员变量
public Field[] getFields() throws SecurityException{}
/*
*获取类的注解信息
*/
//获取直接修饰该类的指定类型的注解
public <A extends Annotation> A getDeclaredAnnotation(Class<A> annotationClass) {}
//获取修饰该类的指定类型的注解(包括继承而来的注解)
public <A extends Annotation> A getAnnotation(Class<A> annotationClass) {}
//获取直接修饰该类的多个注解
public Annotation[] getDeclaredAnnotations(){}
//获取修饰该类的多个注解(包括继承而来的注解)
public Annotation[] getAnnotations() {}
//获取直接修饰该类的指定类型的多个注解
public <A extends Annotation> A[] getDeclaredAnnotationsByType(Class<A> annotationClass) {}
//获取修饰该类的指定类型的多个注解(包括继承而来的注解)
public <A extends Annotation> A[] getAnnotationsByType(Class<A> annotationClass) {}
//获取实现的所有接口
public Class<?>[] getInterfaces() {}
//获取继承的父类
public native Class<? super T> getSuperclass();
//获取对应的内部类
public Class<?>[] getDeclaredClasses() throws SecurityException {}
//h获取对应的外部类
public Class<?> getDeclaringClass() throws SecurityException{}
/*
*判断方法
*/
//判断是不是注解
public boolean isAnnotation(){}
//判断是不是匿名内部类
public boolean isAnonymousClass(){}
//判断是不是数组
public boolean isArray(){}
//判断是不是枚举类
public boolean isEnum(){}
//判断是不是接口
public boolean isInterface(){}
//判断obj是不是该接口对象
public boolean isInterface(Object obj){}
反射的的使用
package com.icypt.reflect;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
/**
* 运行结果
* ClassA普通方法-argsA-[arr2, arry2]-[arr2, arry2]
* 重写A接口的方法
* 重写B接口的方法
* 方法列表:
* 1,方法名称:interB
* 2,方法名称:interA
* 3,方法名称:classA
* ClassA普通方法-args1-[arr2, arry2]-[arr2, arry2]
* @Flag value:true
* fieldA-->成员变量A
*/
public class Test {
public static void main(String[] args) throws Exception{
String[] strArray = new String[] {"arr2", "arry2"};
/**
* 通过new关键字实例化ClassA对象,并执行成员方法和重写方法
*/
ClassA classA = new ClassA();
classA.classA("argsA", strArray, Arrays.asList(strArray));
classA.interA();
classA.interB();
/**
* 通过反射获取类信息,实例化对象并执行方法
*/
//三种获取Class对象的方式
//Class<?> clazz = classA.getClass();
//Class<?> clazz = ClassA.class;
Class<?> clazz = Class.forName("com.icypt.reflect.ClassA");
Method[] methodArray = clazz.getDeclaredMethods();
System.out.println("方法列表:");
int i = 0;
for(Method method : methodArray) {
System.out.println((++i) + ",方法名称:" + method.getName());
}
//实例化对象
Object obj = clazz.newInstance();
//获取指定方法名称、指定参数类型的方法
Method method = clazz.getMethod("classA", String.class, String[].class, List.class);
//通过反射执行方法
method.invoke(obj, "args1", strArray, Arrays.asList(strArray));
//获取方法的注解
Flag ann = method.getAnnotation(Flag.class);
System.out.println("@Flag value:" + ann.value());
//获取普通成员变量
Field field = clazz.getDeclaredField("fieldA");
field.setAccessible(true);
System.out.println("fieldA-->" + field.get(obj));
}
}
interface IterA{
public void interA();
}
interface InterB{
public void interB();
}
class ClassA implements IterA, InterB{
private String fieldA = "成员变量A";
@Flag
public void classA(String args1, String[] args2, List<String> args3) {
System.out.println("ClassA普通方法-"+ args1 + "-" + Arrays.asList(args2) + "-" + args3);
}
@Override
public void interA() {
System.out.println("重写A接口的方法");
}
@Override
public void interB() {
System.out.println("重写B接口的方法");
}
}
@Target(ElementType.METHOD)
@Documented
@Inherited
@Retention(value= RetentionPolicy.RUNTIME)
@interface Flag {
public boolean value() default true;
}
演示了反射的基本使用姿势,主要体现了反射创建对象、获取方法、执行方法、获取注解信息、获取成员变量等常用方式。
代理类概述
代理设计模式:为其他对象提供一种代理来控制这个对象的访问,比如:公司现在要去谈一个业务,这个业务的核心功能都是由A员工负责的,但是A不想去接触一些与核心功能无关的事情,所以就将一些辅助工作交由B员工来做,当具体到核心业务时,由B向A转达完成核心功能,那么B就是A的代理。代理又分为静态代理和动态代理,静态代理和动态代理最大的区别是静态代理的代理对象在做事前是已知的,动态代理的代理对象是在做事前动态创建的。关于动态代理Java专门提供了两个类:Proxy和InvocationHandler。Proxy类为我们提供了两个方法方便我们创建动态代理类以及对象:
通过以上两个方法我们就可以实现Java提供的动态代理功能了。
栗子,动态代理实现
package com.icypt.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* 运行结果
* ******代理**********开启事务****************
* 执行插入方法!
* ******代理**********提交事务****************
* 执行查询方法!
*/
public class Test {
public static void main(String[] args) throws Exception{
ProxyFactoryBaseJdk<IterA> factory = new ProxyFactoryBaseJdk<>(new ClassA());
IterA iterA = factory.getProxyInstance();
iterA.insert();
iterA.query();
}
}
/**
* 动态代理工厂
*/
class ProxyFactoryBaseJdk<T> {
private T target;
public ProxyFactoryBaseJdk(T target) {
this.target = target;
}
/**
* 获取代理对象
* @return
*/
@SuppressWarnings("unchecked")
public T getProxyInstance() {
Class<?> clazz = target.getClass();
Object proxy = Proxy.newProxyInstance(
clazz.getClassLoader(), //获取目标类的类加载器
clazz.getInterfaces(), //获取目标类所实现的所有接口
new InvocationHandler() //执行代理对象方法时触发
{
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//执行结果
Object result = null;
//如果是新增需要处理事务
if(method.getName().contains("insert")) {
System.out.println("******代理**********开启事务****************");
result = method.invoke(target, args);
System.out.println("******代理**********提交事务****************");
} else {
//直接执行
result = method.invoke(target, args);
}
return result;
}
});
return (T)proxy;
}
}
interface IterA{
public void insert();
public void query();
}
class ClassA implements IterA {
@Override
public void insert() {
System.out.println("执行插入方法!");
}
@Override
public void query() {
System.out.println("执行查询方法!");
}
}
关于动态代理还有其他的实现方式,我会在后期框架的学习中继续介绍,例如Spring框架中支持CGLIB实现动态代理。
至此关于Java中反射的使用就学习到这里, 可能在平时的学习或者开发中用到反射的几率比较少, 但是随着学习的深入会发现处处存在反射, 反射机制是java的灵魂。下篇我们来学习Java的网络编程。
java培训班:http://www.baizhiedu.com/java2019