注解与反射
一、注解
1.1、注解是什么
1.2、内置注解
注意镇压注解镇压的是警告(黄色),不是严重错误注解(红色)
1.3、元注解
public class AnnotationDemo {
@Annoal
int a;
public static void main(String[] args) {
}
@Annoal
void toDong(){
}
}
//自定义注解
//@Target 注解作用领域 METHOD:方法 FIELD:字段 等等
@Target({ElementType.METHOD,ElementType.FIELD})
//@Target 表示注解在什么级别下保存该注解 RUNTIME:运行时
@Retention(RetentionPolicy.RUNTIME)
@interface Annoal{
}
1.4、自定义注解
public class AnnotationDemo1 {
@Annoal
int a;
@Annoal01(name = "小明",city = {"广州","深圳"})
void toDong01(){
}
@Annoal02(value = "sss",age = 88)
void toDong02(){
}
@Annoal03("good boy")
void toDong03(){
}
}
//自定义注解
//@Target 注解作用领域 METHOD:方法 FIELD:字段 等等
@Target({ElementType.METHOD,ElementType.FIELD})
//@Target 表示注解在什么级别下保存该注解 RUNTIME:运行时
@Retention(RetentionPolicy.RUNTIME)
@interface Annoal01{
//注意里面的参数格式为方法
String name ();
//default为默认值 不带参数时生效
int age() default 18;
String[] city();
}
@Target({ElementType.METHOD,ElementType.FIELD})
//@Target 表示注解在什么级别下保存该注解 RUNTIME:运行时
@Retention(RetentionPolicy.RUNTIME)
@interface Annoal02{
String value();
int age();
}
@Target({ElementType.METHOD,ElementType.FIELD})
//@Target 表示注解在什么级别下保存该注解 RUNTIME:运行时
@Retention(RetentionPolicy.RUNTIME)
@interface Annoal03{
//value 可以不加名字绑定 为默认参数 单独存在时有效
String value();
}
二、反射
2.1、概述
静态与动态语言
Java Reflection
反射机制应用
反射优缺点
反射相关API
2.2、Class类
Class类常用方法
public class ReflectionDemo1 {
public static void main(String[] args) throws ClassNotFoundException {
//创建学生类
Pension pension = new Student();
//通过对象直接获取反射对象
Class c1 = Student.class;
//通过实例化对象 获取反射对象
Class c2 = pension.getClass();
//通过包路径获取反射对象
Class c3 = Class.forName("com.yzy.reflection.Student");
//补充:包装类可通过.type获取反射对象
Class c4 = Integer.class;
//补充:包装类可通过.type获取反射对象的类型
Class c5 = Integer.TYPE;
//比较反射的类是否一致
System.out.println(c1.hashCode()+";"+c2.hashCode()+";"+c3.hashCode()+";"+c4+";"+c5);
//获取父类对象
System.out.println(c1.getSuperclass()+";"+c2.getSuperclass()+";"+c3.getSuperclass());
}
}
class Pension{
String name = "人";
void speakState(){
System.out.println(name);
}
}
class Student extends Pension{
String name = "学生";
void speakState(){
System.out.println(name);
}
}
class Teacher extends Pension{
String name = "老师";
void speakState(){
System.out.println(name);
}
}
结果
有Class对象的类
public class ReflectionDemo2 {
public static void main(String[] args) throws ClassNotFoundException {
//接口
Class<Runnable> c1 = Runnable.class;
//类
Class<NumName> c2 = NumName.class;
//注解
Class<Override> c3 = Override.class;
//枚举
Class<ElementType> c4 = ElementType.class;
//一维数组
Class<int[]> c5 = int[].class;
//二维数组
Class<int[][]> c6 = int[][].class;
//参数类型
Class<Void> c7 = void.class;
//Class
Class<Class> c8 = Class.class;
//包装类型
Class<Integer> c9 = Integer.class;
System.out.println(c1);
System.out.println(c2);
System.out.println(c3);
System.out.println(c4);
System.out.println(c5);
System.out.println(c6);
System.out.println(c7);
System.out.println(c8);
System.out.println(c9);
//维度相同反射类型一致
int[] a = new int[1];
int[] b = new int[412];
System.out.println(a.getClass().hashCode()+";"+b.getClass().hashCode());
//维度相同反射类型一致
NumName name1 = new NumName();
NumName name2 = new NumName();
System.out.println(name1.getClass().hashCode()+";"+name2.getClass().hashCode()+";"+NumName.class.hashCode());
//类型一致但本质是两个不同的对象
System.out.println(name1.hashCode()+";"+name2.hashCode());
System.out.println(NumName.class.getName()+";"+name2.name);
}
}
class NumName{
String name = "人";
void speakState(){
System.out.println(name);
}
}
2.3、获取运行时类的结构
示例:
public class Test02 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
//反射获取类
Class aClass = Class.forName("com.yzy.reflection.Demo01");
//获取类的路径
System.out.println("获取类的路径:");
System.out.println(aClass.getName());
//获取类的名字
System.out.println("获取类的名字:");
System.out.println(aClass.getSimpleName());
//获取类的所有public属性,包括父类
Field[] fields = aClass.getFields();
System.out.println("获取类的所有public属性,包括父类:");
for (Field field : fields) {
System.out.println(field);
}
//获取类的属性,不包括父类
Field[] fields1 = aClass.getDeclaredFields();
//所有方法与构造器同理
//Method[] fields1 = aClass.getDeclaredMethods();
//Constructor[] fields1 = aClass.getDeclaredConstructors();
System.out.println("获取类的属性,不包括父类:");
for (Field field : fields1) {
System.out.println(field);
}
//获取指定构造器
System.out.println("获取指定构造器:");
//无参
System.out.println(aClass.getDeclaredConstructor(null));
//有参
System.out.println(aClass.getDeclaredConstructor(String.class));
//获取指定方法同理
System.out.println("获取指定方法同理:");
System.out.println(aClass.getMethod("getName"));
System.out.println(aClass.getMethod("setAge", int.class));
}
}
class Demo01 extends Demo02{
public String name = "小米";
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Demo01(String name) {
this.name = name;
}
public Demo01() {
}
}
class Demo02{
public int age = 18;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Demo02(int age) {
this.age = age;
}
public Demo02() {
}
}
2.4、获得类该如何使用
调用类的方法
public class Test02 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
//反射获取类
Class aClass = Class.forName("com.yzy.reflection.Demo01");
//获取类的路径
System.out.println("获取类的路径:");
System.out.println(aClass.getName());
//获取类的名字
System.out.println("获取类的名字:");
System.out.println(aClass.getSimpleName());
//获取类的所有public属性,包括父类
Field[] fields = aClass.getFields();
System.out.println("获取类的所有public属性,包括父类:");
for (Field field : fields) {
System.out.println(field);
}
//获取类的属性,不包括父类
Field[] fields1 = aClass.getDeclaredFields();
//所有方法与构造器同理
//Method[] fields1 = aClass.getDeclaredMethods();
//Constructor[] fields1 = aClass.getDeclaredConstructors();
System.out.println("获取类的属性,不包括父类:");
for (Field field : fields1) {
System.out.println(field);
}
//获取指定构造器
System.out.println("获取指定构造器:");
//无参
System.out.println(aClass.getDeclaredConstructor(null));
//有参
System.out.println(aClass.getDeclaredConstructor(String.class));
//获取指定方法同理
System.out.println("获取指定方法同理:");
System.out.println(aClass.getMethod("getName"));
System.out.println(aClass.getMethod("setAge", int.class));
}
}
class Demo01 extends Demo02{
public String name = "小米";
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Demo01(String name) {
this.name = name;
}
public Demo01() {
}
}
class Demo02{
private int age = 18;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Demo02(int age) {
this.age = age;
}
public Demo02() {
}
}
setAccessible
public class Test03 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchFieldException {
//反射获取类
Class aClass = Class.forName("com.yzy.reflection.Demo03");
//使用反射的类new对象
Demo03 demo03 = (Demo03)aClass.newInstance();//本质上是通过无参构造new 所以需要无参构造
//可以调用方法
//demo03.setName("dada");.....
//使用反射的类通过有参构造new对象
//获取有参构造方法
Constructor constructor = aClass.getDeclaredConstructor(String.class);
//通过有参构造new对象
Demo03 demo003 = (Demo03) constructor.newInstance("小明");
//获取反射类,获取类方法,调用类方法
//反射获取类
Class<Demo03> aClass1 = Demo03.class;
//new一个反射实例类
Demo03 instance = aClass1.newInstance();
//反射获取类方法
Method setName = aClass1.getDeclaredMethod("setName", String.class);
//激活类方法 第一个参数为反射实例类
setName.invoke(instance,"小女孩");
//反射获取类方法
Method getName = aClass1.getDeclaredMethod("getName");
//激活类方法 第一个参数为反射实例类
System.out.println(getName.invoke(instance));;
//通过反射操作属性
Class<Demo04> aClass2 = Demo04.class;
Demo04 demo04 = aClass2.newInstance();
Field age = aClass2.getDeclaredField("age");
demo04.setAge(19);
//age.set(demo04,22); 会报错 原因私有属性不可以直接操作
//但是反射机制很强,是可以直接操作的,需要关闭安全校验
//关闭该属性的安全校验 默认为打开:false
age.setAccessible(true);
age.set(demo04,22);
System.out.println(demo04.getAge());
}
}
class Demo03 extends Demo04{
public String name = "小米";
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Demo03(String name) {
this.name = name;
}
public Demo03() {
}
}
class Demo04{
private int age = 18;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Demo04(int age) {
this.age = age;
}
public Demo04() {
}
}
反射性能问题
public class Test007 {
public static void main(String[] args) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchFieldException {
User user = new User();
long begin = System.currentTimeMillis();
for (int i = 0; i < 1000000000; i++) {
user.getName();
}
long end = System.currentTimeMillis();
System.out.println("普通方式:"+(end-begin)+"ms");
Class<User> aClass = User.class;
User user1 = aClass.newInstance();
long begin1 = System.currentTimeMillis();
Method getName = aClass.getDeclaredMethod("getName");
for (int i = 0; i < 1000000000; i++) {
getName.invoke(user1,null);
}
long end1 = System.currentTimeMillis();
System.out.println("反射方式:"+(end1-begin1)+"ms");
Class<User> aClass1 = User.class;
User user2 = aClass1.newInstance();
long begin3 = System.currentTimeMillis();
Field name = aClass1.getDeclaredField("name");
name.setAccessible(true);
for (int i = 0; i < 1000000000; i++) {
name.get(user2);
}
long end3 = System.currentTimeMillis();
System.out.println("反射关闭安全校验方式:"+(end3-begin3)+"ms");
}
}
class User{
String name = "用户";
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public User(String name) {
this.name = name;
}
public User() {
}
}
2.5、反射操作泛型
public class Test08 {
public static void main(String[] args) throws NoSuchMethodException {
//反射获取方法
Method uu = UUser.class.getDeclaredMethod("uu", Map.class, User.class);
//获取参数类型
Type[] genericParameterTypes = uu.getGenericParameterTypes();
for (Type type:genericParameterTypes){
//判断参数类型是否存在泛型
if (type instanceof ParameterizedType){
//将参数里的泛型拆分出来一个类型数组
Type[] actualTypeArguments = ((ParameterizedType) type).getActualTypeArguments();
//遍历数组就是泛型的真实类型
for (Type parameterizedType:actualTypeArguments){
System.out.println(parameterizedType);
}
}
}
//反射获取方法
Method uub = UUser.class.getDeclaredMethod("uub");
//获取返回类型
Type type1 = uub.getGenericReturnType();
System.out.println(type1);
//判断参数类型是否存在泛型
if (type1 instanceof ParameterizedType){
//将参数里的泛型拆分出来一个类型数组
Type[] actualTypeArguments = ((ParameterizedType) type1).getActualTypeArguments();
//遍历数组就是泛型的真实类型
for (Type parameterizedType:actualTypeArguments){
System.out.println(parameterizedType);
}
}
}
}
class UUser{
void uu(Map<String,User> map,User user){
System.out.println();
}
Map<String,User> uub(){
return null;
}
}
结果
2.6、反射获取注解信息
带着问题练习
public class Test09 {
public static void main(String[] args) throws NoSuchFieldException {
//反射获取类上的所有注解
Annotation[] annotations = Pojo.class.getAnnotations();
for (Annotation annotation:annotations) {
System.out.println(annotation);
}
//反射获取类上的指定注解
Table table = Pojo.class.getAnnotation(Table.class);
System.out.println(table.value());
//反射获取类属性上的所有注解
Field name = Pojo.class.getDeclaredField("name");
Annotation[] annotations1 = name.getAnnotations();
for (Annotation annotation1:annotations1){
System.out.println(annotation1);
}
//反射获取类属性上的指定注解
File file = name.getAnnotation(File.class);
System.out.println(file.File_name()+";"+file.type()+";"+file.length());
}
}
@Table("user")
class Pojo{
@File(File_name = "id",type = "int",length = 30)
int id;
@File(File_name = "name",type = "String",length = 255)
String name;
@File(File_name = "age",type = "int",length = 30)
int age;
}
//定义表注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Table{
//表名
String value();
}
//定义字段注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface File{
String File_name();
String type();
int length();
}
结果
三、类加载
3.1、类加载内存分析
类加载过程
类加载和类加载器(ClassLoader)
public class TestClassLoader {
public static void main(String[] args) {
StudentBoy boy = new StudentBoy();
System.out.println(StudentBoy.boy);
School school = new School();
System.out.println(School.school);
}
}
class StudentBoy extends School{
static String boy = "男学生";
static {
boy = "小男孩学生";
}
StudentBoy(){
System.out.println(boy+":构造函数初始化");
}
}
class School{
static {
school = "北京大学";
}
School(){
System.out.println(school+":构造函数初始化");
}
static String school = "清华大学";
}public class TestClassLoader {
public static void main(String[] args) {
StudentBoy boy = new StudentBoy();
System.out.println(StudentBoy.boy);
School school = new School();
System.out.println(School.school);
}
}
class StudentBoy extends School{
static String boy = "男学生";
static {
boy = "小男孩学生";
}
StudentBoy(){
System.out.println(boy+":构造函数初始化");
}
}
class School{
static String school = "清华大学";
static {
school = "北京大学";
}
School(){
System.out.println(school+":构造函数初始化");
}
}
结果(静态代码对合并,有先后顺序)
回顾知识点:子类默认会隐式调用父类的构造方法,也就是super();所有类加载是很多类就已经加载了,实例化的时候会复制一份格式给实例对象,这就对象是new出来的原理。
类的初始化
public class TestClassLoader {
static{
System.out.println("main类初始化");
}
public static void main(String[] args) throws ClassNotFoundException {
// System.out.println("new 会初始化父类子类");
// StudentBoy boy = new StudentBoy();
System.out.println("反射 会初始化父类子类 ");
//这种反射不会初始化父类子类
//Class boyClass = StudentBoy.class;
Class.forName("com.yzy.reflection.StudentBoy");
System.out.println("调用静态常量只会加载拥有静态常量对象");
System.out.println(StudentBoy.boy);
System.out.println("final 不会会初始化类");
System.out.println(StudentBoy.boy1);
}
}
class StudentBoy extends School{
static String boy = "男学生";
static final String boy1 = "男学生";
static {
System.out.println(boy+":子类初始化");
boy = "小男孩学生";
}
}
class School{
static String school = "清华大学";
static {
System.out.println(school+":父类初始化");
}
}
类加载器的作用
public class Test01 {
public static void main(String[] args) throws ClassNotFoundException {
//用户类加载器
ClassLoader loader = ClassLoader.getSystemClassLoader();
System.out.println(loader);
//引导类加载器
ClassLoader parent = loader.getParent();
System.out.println(parent);
//根类加载器
ClassLoader parent1 = parent.getParent();
System.out.println(parent1);
//例子
//用户加载器
System.out.println(Test01.class.getClassLoader());
//根加载器 java包下的类是由根加载器加载的
System.out.println(Object.class.getClassLoader());
//加载器加载类路径
System.out.println(System.getProperty("java.class.path"));
/* 类加载的路径
C:\Users\yzy\.jdks\corretto-1.8.0_322-1\jre\lib\charsets.jar;
C:\Users\yzy\.jdks\corretto-1.8.0_322-1\jre\lib\ext\access-bridge-64.jar;
C:\Users\yzy\.jdks\corretto-1.8.0_322-1\jre\lib\ext\cldrdata.jar;
C:\Users\yzy\.jdks\corretto-1.8.0_322-1\jre\lib\ext\dnsns.jar;
C:\Users\yzy\.jdks\corretto-1.8.0_322-1\jre\lib\ext\jaccess.jar;
C:\Users\yzy\.jdks\corretto-1.8.0_322-1\jre\lib\ext\jfxrt.jar;
C:\Users\yzy\.jdks\corretto-1.8.0_322-1\jre\lib\ext\localedata.jar;
C:\Users\yzy\.jdks\corretto-1.8.0_322-1\jre\lib\ext\nashorn.jar;
C:\Users\yzy\.jdks\corretto-1.8.0_322-1\jre\lib\ext\sunec.jar;
C:\Users\yzy\.jdks\corretto-1.8.0_322-1\jre\lib\ext\sunjce_provider.jar;
C:\Users\yzy\.jdks\corretto-1.8.0_322-1\jre\lib\ext\sunmscapi.jar;
C:\Users\yzy\.jdks\corretto-1.8.0_322-1\jre\lib\ext\sunpkcs11.jar;
C:\Users\yzy\.jdks\corretto-1.8.0_322-1\jre\lib\ext\zipfs.jar;
C:\Users\yzy\.jdks\corretto-1.8.0_322-1\jre\lib\jce.jar;
C:\Users\yzy\.jdks\corretto-1.8.0_322-1\jre\lib\jfr.jar;
C:\Users\yzy\.jdks\corretto-1.8.0_322-1\jre\lib\jfxswt.jar;
C:\Users\yzy\.jdks\corretto-1.8.0_322-1\jre\lib\jsse.jar;
C:\Users\yzy\.jdks\corretto-1.8.0_322-1\jre\lib\management-agent.jar;
C:\Users\yzy\.jdks\corretto-1.8.0_322-1\jre\lib\resources.jar;
C:\Users\yzy\.jdks\corretto-1.8.0_322-1\jre\lib\rt.jar;D:\java实训\day001\iotcp\out\production\iotcp;
D:\idea\IntelliJ IDEA 2021.3.2\lib\idea_rt.jar
*/
}
}
3.2、双亲委派机制
双亲委派机制(Parent-Delegate Model)是Java类加载器中采用的一种类加载策略。该机制的核心思想是:如果一个类加载器收到了类加载请求,默认先将该请求委托给其父类加载器处理。只有当父级加载器无法加载该类时,才会尝试自行加载。
类加载器与层级关系
Java中的类加载器主要有如下三种:
- 启动类加载器(Bootstrap ClassLoader): 负责加载 %JAVA_HOME%/jre/lib 目录下的核心Java类库如 rt.jar、charsets.jar 等。
- 扩展类加载器(Extension ClassLoader): 负责加载 %JAVA_HOME%/jre/lib/ext 目录下的扩展类库。
- 应用类加载器(Application ClassLoader): 负责加载用户类路径(ClassPath)下的应用程序类。
这三种类加载器之间存在父子层级关系。启动类加载器是最高级别的加载器,没有父加载器;扩展类加载器的父加载器是启动类加载器;应用类加载器的父加载器是扩展类加载器。
除了以上三个内置类加载器,用户还可以通过继承 java.lang.ClassLoader 类自定义类加载器,根据实际需求处理类加载请求。
双亲委派机制作用及如何破环机制
通过上述两块内容,我们对双亲委派机制、加载流程及层级有了一些了解,这时我们不妨抛出几个疑问。
为什么需要双亲委派
双亲委派机制有哪些优缺点
如何打破这个机制
有哪些工具选择了破坏机制。
-
为什么需要双亲委派
-
通过双亲委派机制,可以避免类的重复加载,当父加载器已经加载过某一个类时,子加载器就不会再重新加载这个类。
-
通过双亲委派机制,可以保证安全性。因为BootstrapClassLoader在加载的时候,只会加载JAVA_HOME中的jar包里面的类,如java.lang.String,那么这个类是不会被随意替换的。
那么,就可以避免有人自定义一个有破坏功能的java.lang.String被加载。这样可以有效的防止核心Java API被篡改。
实现双亲委派机制 的代码也都集中在这个方法之中:
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
通过以上代码得出结论:
- 当类加载器接收到类加载的请求时,首先检查该类是否已经被当前类加载器加载;
- 若该类未被加载过,当前类加载器会将加载请求委托给父类加载器去完成;
- 若当前类加载器的父类加载器为null,会委托启动类加载器完成加载;
- 若父类加载器无法完成类的加载,当前类加载器才会去尝试加载该类。
双亲委派机制的优缺点
优点:
- 避免重复加载:由于类加载器直接从父类加载器那里加载类,避免了类的重复加载。
- 提高安全性:通过双亲委派模型,Java 标准库中的核心类库(如 java.lang.*)由启动类加载器加载,这样能保证这些核心类库不会被恶意代码篡改或替换,从而提高程序的安全性。
- 保持类加载的一致性:这种方式确保了同一个类的加载由同一个类加载器完成,从而在运行时保证了类型的唯一性和相同性。这也有助于减轻类加载器在处理相互关联的类时的复杂性。
缺点:
- 灵活性降低:由于类加载的过程需要不断地委托给父类加载器,这种机制可能导致实际应用中类加载的灵活性降低。
- 增加了类加载时间:在类加载的过程中,需要不断地查询并委托父类加载器,这意味着类加载所需要的时间可能会增加。在类数量庞大或
- 类加载器层次比较深的情况下,这种时间延迟可能会变得更加明显。
如何打破这个机制
想要破坏这种机制,那么就需要自定义一个类加载器,继承ClassLoader类重写其中的loadClass方法,使其不进行双亲委派即可。
写个示例
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Paths;
public class CustomClassLoader extends ClassLoader {
// 自定义类加载器必须提供一个加载类文件的位置
private String classesPath;
public CustomClassLoader(String classesPath, ClassLoader parent) {
super(parent);
this.classesPath = classesPath;
}
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
//首先,检查已加载的类
Class<?> loadedClass = findLoadedClass(name);
if (loadedClass == null) {
// 如果已加载类中没有该类, 尝试用自定义的方法加载
try {
loadedClass = findClassInPath(name);
} catch (ClassNotFoundException e) {
// 如果自定义加载方法找不到类,则委托给父类加载器
loadedClass = super.loadClass(name, resolve);
}
}
if (resolve) {
resolveClass(loadedClass);
}
return loadedClass;
}
private Class<?> findClassInPath(String className) throws ClassNotFoundException {
try {
String filePath = className.replace('.', '/') + ".class";
byte[] classBytes = Files.readAllBytes(Paths.get(classesPath, filePath));
return defineClass(className, classBytes, 0, classBytes.length);
} catch (Exception e) {
throw new ClassNotFoundException("Class not found in classes path: " + className, e);
}
}
public static void main(String[] args) throws Exception {
String pathToClasses = "/path/to/your/classes";
String className = "com.example.SampleClass";
String methodName = "sampleMethod";
// 创建自定义类加载器实例,将类的加载权交给它
CustomClassLoader customClassLoader = new CustomClassLoader(pathToClasses, CustomClassLoader.class.getClassLoader());
// 使用自定义类加载器加载类
Class<?> customClass = customClassLoader.loadClass(className);
// 创建类的实例并调用方法
Object obj = customClass.newInstance();
Method method = customClass.getDeclaredMethod(methodName);
method.setAccessible(true);
method.invoke(obj);
}
}
上面的示例代码中,我们重写了 loadClass
方法,先尝试通过 findClassInPath
从指定的路径加载类,如果无法加载就委托给父类加载器。这样,我们就实现了打破双亲委派机制的自定义类加载器。
以下是代码的详细解析:
-
自定义类加载器 CustomClassLoader 继承 Java ClassLoader 类。
-
在类加载器的构造方法中设置自定义类加载器的类路径 classesPath 和父加载器 parent。
-
重写 loadClass 方法。首先检查已加载的类,如果已加载则返回。否则尝试用自定义的方法在 classesPath 中加载类。如果自定义加载方法找不到类,则委托给父类加载器。
-
实现名为 findClassInPath 的自定义加载方法。这个方法使用类名 className 在 classesPath 指定的目录下查找对应的 .class 文件,然后将文件内容读取为字节数组并调用 defineClass 方法,将其转换为 Java 类的 Class 对象。如果类不存在或出现其他错误,会抛出 ClassNotFoundException 异常。
-
在 main 方法中,创建一个 CustomClassLoader 类的实例。将类的加载任务交给自定义类加载器,指定加载路径和要加载的类。
使用自定义类加载器加载目标类,创建类的实例,并调用指定方法。
有哪些工具选择了破坏机制
- OSGi(Open Service Gateway Initiative):OSGi 是一个模块化系统和服务平台,提供了一个强大的类加载器模型。在 OSGi 中,每个模块都有一个独立的类加载器,可以按需加载来自不同模块的类。这有助于解决 JAR 地狱问题,提高模块化和动态更新能力。
- Tomcat Web容器:Tomcat 的 Web 应用类加载器可以加载 Web 应用程序中的本地类库,从而使得每个 Web 应用程序可以使用各自的版本的类库。这些 Web 应用的类加载器都是${tomcat-home}/lib 中类库的子类加载器。
- Java Agent: Java Agent 是一种基于 Java Instrumentation API 的技术,它可以在运行时修改已加载的类的字节码,从而实现类的热替换、AOP(面向切面编程)等功能。这种技术在诸如热部署、性能监控和分布式追踪等场景中有广泛应用。
- **JDK 中的 URLClassLoader:**JDK 自带的 URLClassLoader 可以用来加载指定 URL 路径下的类。实际上,它实现了一种子类优先的策略,先尝试加载自身路径下的类,再委托给父类加载器,从而打破了双亲委派机制。
这些工具和技术之所以要打破双亲委派机制,主要是出于以下原因:
- 实现模块化和动态更新:例如 OSGi,通过独立的类加载器实现不同模块间解耦,并支持模块的动态卸载和更新。
- 解决类库版本冲突(JAR地狱问题):在复杂系统中,不同模块可能依赖不同版本的类库。为避免版本冲突,可使用独立的类加载器,使它们分别加载各自的类库版本。
- 运行时修改类:Java Agent 可以在运行时修改类字节码,从而支持热替换、AOP 和性能监控等功能。
- 支持 Web 应用程序的独立部署和更新:例如 Tomcat,可以为每个 Web 应用程序分配一个独立的类加载器,实现各自部署与更新。
需要注意的是,打破双亲委派机制可能会带来类加载冲突、安全性和性能等问题,因此在实际应用中要谨慎使用。
总结
双亲委派机制可以确保Java应用类型安全,同时避免类加载冲突。在某些特定场景下,我们可以通过自定义类加载器对类加载策略进行调整,以满足应用特性和性能需求。