【Java基础学习】反射&动态性

- Annotation注解

作用

  • 不是程序本身,可以对程序作出解释
  • 可以被其他程序(比如:编译器)读取
    格式
  • 以“@注释名”在代码中存在,还可以添加参数值,例如:@SuppressWarnings(value=“unchecked”)
    使用地点
  • 可以附加在package、method、class、field等上面,相当于给他们添加了额外的辅助信息,可以通过反射机制编程实现对这些元素的访问。

- 内置注解

  • @Override:此注释只用于修饰方法,表示一个方法声明打算重写超类中的另一个方法声明。
  • @Deprecated:此注释可用于修饰方法、属性、类,表示不鼓励程序员使用这样的元素,通常是因为它很危险或存在更好的选择
  • @SuppressWarnings:用来抑制编译时的警告信息,其定义中含有: String[] value();为定义字符串数组参数,即单个参数时:@SuppressWarnings(“all”),多个参数时:@SuppressWarnings(value={“unchecked”,“deprecation”})

- 自定义注解

  • 元注解:注解其他注解,有:
    - @Target:用于描述注解的使用范围,取值为枚举ElementType
    - @Retention:表示需要在什么级别保存该注释信息,用户描述注解的生命周期,取值为枚举RetentionPolicy,其中RUNTIME级别,可 以通过反射机制读取到
    - @Documented
    - @Inherited
    注意事项
  • 默认赋值
public @interface StudyAnnotation01 {
	String studentName() default "";//参数类型String  参数名studentName,default定义默认值
	int age() default 0;
	int id() default -1;//负数-1,表示不存在的含义
	
	String[] schools() default {"清华","北大"};
}
  • 单个参数时,建议参数名为value,注解时可以省略value,例如:@SxtAnnotation02(“aaa”)
@Target(value= {ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface StudyAnnotation02 {
	String value();//注解中只有一个参数,一般定义为value
}

- 反射机制读取注解

- 简单了解ORM

ORM:Object Relationship Mapping,对象关系映射
例如:类与表进行转换

  • 类与表结构对应
  • 属性和字段对应
  • 对象和记录对应

- 使用注解完成类和表结构的映射关系

表:
类的定义:

@SxtTable("tb_student")
public class SxtStudent {
	@SxtField(columnName="id",type="int",length=10)
	private int id;
	@SxtField(columnName="sname",type="varchar",length=10)
	private String studentName;
	@SxtField(columnName="age",type="int",length=3)
	private int age;
	
	
	
	public int getId() {
		return id;
	}
	public void setId(int id) {
		this.id = id;
	}
	public String getStudentName() {
		return studentName;
	}
	public void setStudentName(String studentName) {
		this.studentName = studentName;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	
	
}

类的注解定义:

@Target(value= {ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface SxtTable {
	String value();
}

属性的注解定义

@Target(value= {ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface SxtField {
	String columnName();//表中的列明
	String type();//表中的类型
	int length();//表中的类型长度
}

解析注解的程序:

public class Demo03 {
	public static void main(String[] args) {
		try {
			Class clazz=Class.forName("com.study.annotation.SxtStudent");
			//获取类的所有有效注解
			Annotation[] annotations=clazz.getAnnotations();
			for(Annotation a:annotations) {
				System.out.println(a);
			}
			//直接根据注解名获取类的指定注解
			SxtTable st=(SxtTable)clazz.getAnnotation(SxtTable.class);
			System.out.println(st.value());
			
			//获得类的属性的注解
			//获得类中对应的属性
			Field f=clazz.getDeclaredField("studentName");
			//通过属性获得指定的注解
			SxtField sxtField=f.getAnnotation(SxtField.class);
			System.out.println(sxtField.columnName()+"---->"+sxtField.type()+"----->"+sxtField.length());
			
			//根据获得表明字段信息拼出DDL语句,然后使用JDBC执行,在数据库中生成相关的表
			
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

- Java的动态性

Java动态性包括:反射机制、动态编译、动态执行js代码以及动态字节码操作
动态语言:

  • 程序运行时,可以改变程序结构或变量类型。典型的语言有:python\js等
  • C\C++,Java不是动态语言,但是Java有一定的动态性,可以利用反射机制、字节码操作获得类似动态语言的特性。
  • Java的动态性让编码的时候更加灵活

- 反射机制

  • 指的是可以于运行时加载、探知、使用编译期间完全未知的类。
  • 程序运行状态中,可以动态加载一个只有名称的类,队医任意一个已加载的类,都能知道这个类的属性和方法;对于一个对象,都能够调用它的任意一个方法和属性。核心代码:Class clz=Class.forName(“包名.类名”);
  • 加载完类之后,JVM会在在堆内存中,创建一个Class类型对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。
- 反射机制常见作用
  • 动态加载类、动态获取类的信息(属性、方法、构造器)
public class Demo02 {
	public static void main(String[] args) {
		String path = "com.sxt.test.bean.User";

		try {
			Class clazz = Class.forName(path);
			
			//类的名字
			System.out.println(clazz.getName());//获得包名加类名
			System.out.println(clazz.getSimpleName());//获得类名
			
			//获得属性信息
//			Field[] fields=clazz.getFields();//只返回public属性
			Field[] fields=clazz.getDeclaredFields();//获得所有的属性
			Field f=clazz.getDeclaredField("uname");
			System.out.println(fields.length);
			for(Field temp:fields) {
				System.out.println("属性:"+temp);
			}
			
			//获得方法信息
			Method[] methods=clazz.getDeclaredMethods();
			Method m01=clazz.getDeclaredMethod("getUname", null);
			//如果方法有参数,则必须传递参数类型对应的Class对象
			Method m02=clazz.getDeclaredMethod("setUname", String.class);
			for(Method m:methods) {
				System.out.println("方法:"+m);
			}
			//获得构造器信息
			Constructor[] constructors=clazz.getDeclaredConstructors();
			Constructor c=clazz.getDeclaredConstructor(int.class,int.class,String.class);
			System.out.println("获得构造器:"+c);
			for(Constructor temp:constructors) {
				System.out.println("构造器:"+temp);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

  • 动态构造对象
  • 动态调用类和对象的任意方法、构造器
  • 动态调用和处理属性
public class Demo03 {
	public static void main(String[] args) {
		String path = "com.sxt.test.bean.User";

		try {
			Class<User> clazz = (Class<User>)Class.forName(path);
			//通过反射API动态调用构造方法,构造对象
			User u=clazz.newInstance();//其实调用了User的无参构造方法
			System.out.println(u);
			
			Constructor<User> c=clazz.getDeclaredConstructor(int.class,int.class,String.class);
			User u2=c.newInstance(1001,18,"张三");
			System.out.println(u2.getUname()); 
			
			//通过反射API调用普通方法
			User u3=clazz.newInstance();
			Method method=clazz.getDeclaredMethod("setUname", String.class);
			method.invoke(u3, "王五");//u3.setUname("李四");
			System.out.println(u3.getUname());
			
			//通过反射API操作属性
			User u4=clazz.newInstance();
			Field f=clazz.getDeclaredField("uname");
			f.setAccessible(true);//启用和禁用访问安全见检查开关
			f.set(u4, "刘六");//通过反射直接写属性
			System.out.println(u4.getUname());
			System.out.println(f.get(u4));//通过反射直接读属性的值
			
			
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}
  • 获取泛型信息
  • 处理注解
- 反射机制性能问题
  • setAccessible:启用和禁用访问安全见检查开关,值为true,表示:反射的对象在使用时应该取消Java语言访问检查。并不是true就能访问,为false就不能访问。
  • 进制安全检查,可以提供反射的运行速度
- 反射操作泛型
  • 泛型:针对编译器而言,确保数据的安全性和免去强制类型转换的麻烦。一旦编译完成,加载到内存,泛型会被替换掉
  • 为了通过反射操作这些类型满足实际开发需要,Java新增了:
    -ParameterizedType:表示一种参数化的类型,比如Collection
    -GenericArrayType:表示一种元素类型是参数化类型或类型变量的数组
    -TypeVariable:各种类型变量的公共父接口
    -WildcardType:代表一种通配符类型表达式,例如:? extends Number
public class Demo04 {
	
	public void test01(Map<String,User> map,List<User> list){
		System.out.println("Demo04.test01()");
	}
	
	public Map<Integer,User> test02(){
		System.out.println("Demo04.test02()");
		return null;
	}
	
	public static void main(String[] args) {

		try {
			
			//获得指定方法参数泛型信息
			Method m = Demo04.class.getMethod("test01", Map.class,List.class);
			Type[] t = m.getGenericParameterTypes();
			for (Type paramType : t) {
				System.out.println("#"+paramType);
				if(paramType instanceof ParameterizedType){
					Type[] genericTypes = ((ParameterizedType) paramType).getActualTypeArguments();
					for (Type genericType : genericTypes) {
						System.out.println("泛型类型:"+genericType);
					}
				}
			}
			
			//获得指定方法返回值泛型信息
			Method m2 = Demo04.class.getMethod("test02", null);
			Type returnType = m2.getGenericReturnType();
			if(returnType instanceof ParameterizedType){
					Type[] genericTypes = ((ParameterizedType) returnType).getActualTypeArguments();

					for (Type genericType : genericTypes) {
						System.out.println("返回值,泛型类型:"+genericType);
					}
					
			}
			
			
		} catch (Exception e) {
			e.printStackTrace();
		}
	
		
	
	}
}
- 反射操作注解

参见:使用注解完成类和表结构的映射关系

- 动态编译

应用场景:

  • 做一个浏览器端编写Java代码,上传服务器编译和运行的在线评测系统
  • 服务器动态加载某些类文件进行编译
    两种做法:
  • 通过Runtime调用javac,启动新的进程去操作【jdk1.6之前】:
    Runtime run= Runtime.getRuntime();
    Process process=run.exec(“javac -cp d:/my/java HelloWorld.java”);
  • 通过JavaCompiler动态编译
	public static int compileFile(String sourceFile){
	//动态编译
	JavaCompiler compiler=ToolProvider.getSystemJavaCompiler();
	int result=complier.run(null,null,null,sourceFile);
	return result;
}
/**
*第一个参数:为java编译器提供参数,InputStream
*第二个参数:得到Java编译器的输出信息,OutputStream
*第三个参数:接收编译器的错误信息
*第四个参数:可变参数(String 数组),能传入一个或多个Java源文件
*返回值:0表示编译成功,非0表示编译失败
*/

动态编译+动态执行:

public class Demo01 {
	public static void main(String[] args) throws IOException {
		// 通过IO操作,将字符串存储一个临时文件(Hi.java),然后调用动态编译
		String str = "public class Hi {public static void main(String[] args) {System.out.println(\"Hi,Java!\");}}";
		BufferedWriter fw = new BufferedWriter(new FileWriter(new File("F:/src/java/Hi.java")));
		fw.write(str);
		fw.flush();
		fw.close();
		//调用编译
		//1、通过Runtime.getRuntime() 运行启动新的进程运行
		Runtime run = Runtime.getRuntime();
		Process process = run.exec("javac -cp F:/src/java/  Hi.java");
		//2、通过动态编译调用编译
		JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
		int result = compiler.run(null, null, null, "F:/src/java/Hi.java");
		System.out.println(result == 0 ? "编译成功" : "编译失败");

		// 调用执行
		// 1、通过Runtime.getRuntime() 运行启动新的进程运行
		Runtime run = Runtime.getRuntime();
		Process process = run.exec("java -cp F:/src/java/  Hi");
		InputStream in = process.getInputStream();
		BufferedReader reader = new BufferedReader(new InputStreamReader(in));
		String info = "";
		while ((info = reader.readLine()) != null) {
			System.out.println(info);
		}

		// 2、通过动态编译调用执行

		try {
			URL[] urls = new URL[] { new URL("file:/" + "F:/src/java/") };
			URLClassLoader loader = new URLClassLoader(urls);
			Class c = loader.loadClass("Hi");
			// 调用加载类的main方法
			Method m = c.getMethod("main", String[].class);
			m.invoke(null, (Object) new String[] {});
			//m.invoke(null,  new String[] {"aa","bb"});
			//若不加Object强制转型。由于可变参数是JDK5.0之后才有。故,编译器会把上述编译成,m.invoke(null,"aa","bb"),就放生了参数个数不匹配的问题
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

- 脚本引擎执行javascript代码

脚本引擎介绍

  • 使得Java应用程序可以通过一套固定的接口与各种脚本引擎交互,从而达到Java在平台上调用各种脚本语言的目的
  • Java脚本API是连通Java平台和脚本语言的桥梁
  • 可以把一些复杂异变的业务逻辑交给脚本语言处理,大大提高开发效率
    获得脚本引擎对象
//获得脚本引擎对象
ScriptEngineManager sem=new ScriptEngineManager();
ScriptEngine engine=sem.getEngineByName("javascript");

Rhino是具体实现类,用于java与js脚本的交互

public class Demo01 {
	public static void main(String[] args) throws Exception {
		//获得脚本引擎对象
		ScriptEngineManager sem=new ScriptEngineManager();
		ScriptEngine engine=sem.getEngineByName("javascript");
		
		
		//定义变量,存储到引擎的上下文中,java和js代码都能获取
		engine.put("msg", "study should be persistent");
		String str="var user= {name:'brickier',age:18,schools: ['清华大学','社会']};";
		str+="print(user.name);";
		
		//执行脚本
		engine.eval(str);
		engine.eval("msg = 'study should be happy';");
		System.out.println(engine.get("msg"));
		
		System.out.println("################################");
		
		//定义函数
		engine.eval("function add(a,b){var sum = a + b; return sum;}");
		
		//执行js函数
		//1、取得调用接口
		Invocable jsInvoke=(Invocable)engine;
		//2、执行脚本中定义的方法
		Object result1=jsInvoke.invokeFunction("add", new Object[] {11,4});
		System.out.println(result1);
		
		//导入其他的java包,使用其他包中的java类
		//jdk1.6的使用方bai法规则du 1.8已经不适用了
//		String jsCode="importPackage java.util;var list=Arrays.asList([\"清华大学\",\"北京大学\",\"家里蹲\"]);";
		String jsCode="var list=java.util.Arrays.asList([\"清华大学\",\"北京大学\",\"家里蹲\"]);";
		engine.eval(jsCode);
		
		List<String> list2=(List<String>)engine.get("list");
		for(String temp:list2) {
			System.out.println(temp);
		}
		
		//执行一个js文件
		URL url=Demo01.class.getClassLoader().getResource("a.js");
		FileReader fr=new FileReader(url.getPath());
		engine.eval(fr);
		fr.close();
	}
}

- 字节码操作

Java动态性的两种常见实现方式:

  • 字节码操作
  • 反射
    运行时操作字节码可以让我们实现如下功能:
  • 动态生成新的类
  • 动态改变某个类的结构(添加/删除/修改 新的属性/方法)
    优势:
  • 比反射开销小,性能高
  • JAVAasist性能高于反射,低于ASM
    常见的字节码操作类库:
  • BCEL
  • ASM
  • CGLIB
  • Javassist
    使用Javassist动态创建类
public class Demo02 {
	public static void main(String[] args) throws Exception {
		ClassPool pool=ClassPool.getDefault();
		CtClass cc=pool.makeClass("com.sxt.bean.Emp");
		
		//创建属性
		CtField f1=CtField.make("private int empno;", cc);
		CtField f2=CtField.make("private String ename;", cc);
		cc.addField(f1);
		cc.addField(f2);
		//创建方法
		CtMethod m1=CtMethod.make("public int getEmpno(){return empno;}", cc);
		CtMethod m2=CtMethod.make("public void setEmpno(int empno){this.empno=empno;}", cc);
		cc.addMethod(m1);
		cc.addMethod(m2);
		
		//添加构造器
		CtConstructor constructor=new CtConstructor(new CtClass[] {CtClass.intType,pool.get("java.lang.String")},cc);
		constructor.setBody("{this.empno=empno;this.ename=ename;}");
		cc.addConstructor(constructor);
		
		
		cc.writeFile("F:/src/java");//将上上面构造好的类写入到该目录下
	}
}
- Javassist详解

方法操作

  • $0,$1,$2……其中$0表示this,$1表示第一个参数,$2表示第二个参数^
  • $args,参数数组类型是Object[],args[0]表示$1
  • $cflow,表示一个方法调用的深度
  • $r,表示方法返回的类型
  • $_,方法的返回值,修改方法时不支持
  • addCatch(),表示方法中加入try catch块,$e表示异常对象
  • $class,表示this的类型(Class)。也就是$0的类型
  • $sig,表示方参数类型(Class)数组,数组顺序未参数的顺序
    方法、属性、构造器、注解操作
public class Demo02 {

	/**
	 * 处理类的基本用法
	 * @throws Exception 
	 */
	public static void test01() throws Exception{
		ClassPool pool=ClassPool.getDefault();
		CtClass cc=pool.get("com.sxt.test.Emp");
		
		byte[] bytes=cc.toBytecode();
		System.out.println(Arrays.toString(bytes));
		
		System.out.println(cc.getName());//获取类名
		System.out.println(cc.getSimpleName());//获取简要类名
		System.out.println(cc.getSuperclass());//获取父类
		System.out.println(cc.getInterfaces());//获取接口
	}
	
	/**
	 * 测试产生新的方法
	 * @throws Exception 
	 */
	public static void test02() throws Exception {
		ClassPool pool=ClassPool.getDefault();
		CtClass cc=pool.get("com.sxt.test.Emp");
		
//		CtMethod m=CtNewMethod.make("public int add(int a,int b){return a+b;}", cc);
		CtMethod m=new CtMethod(CtClass.intType,"add",new CtClass[] {CtClass.intType,CtClass.intType},cc);
		m.setModifiers(Modifier.PUBLIC);
		m.setBody("{System.out.println(\"www.study.cmc\");return $1+$2;}");
		
		cc.addMethod(m);
		
		//通过反射调用新生成的方法
		Class clazz=cc.toClass();
		Object obj=clazz.newInstance();//通过调用Emp无参构造器,创建新的Emp对象
		Method mehtod=clazz.getDeclaredMethod("add", int.class,int.class);
		Object result=mehtod.invoke(obj, 200,300);
		System.out.println(result);
	}
	
	/**
	 * 修改已有的方法信息,修改方法体的内容
	 * @throws Exception
	 */
	public static void test03() throws Exception {
		ClassPool pool=ClassPool.getDefault();
		CtClass cc=pool.get("com.sxt.test.Emp");
		
		CtMethod cm=cc.getDeclaredMethod("sayHello",new CtClass[] {CtClass.intType});
		
		cm.insertBefore("System.out.println($1);System.out.println(\"start!!!!!!!\");");
		cm.insertAt(28, "int b=3;System.out.println(\"b=\"+b);");
		cm.insertAfter("System.out.println(\"end!!!!!!!\");");
		
		Class clazz=cc.toClass();
		Object obj=clazz.newInstance();
		Method method=clazz.getDeclaredMethod("sayHello", int.class);
		Object result=method.invoke(obj, 300);
	}
	
	public static void  test04() throws Exception {
		ClassPool pool=ClassPool.getDefault();
		CtClass cc=pool.get("com.sxt.test.Emp");
		
//		CtField f1=CtField.make("privat int empno", cc);
		CtField f1=new CtField(CtClass.intType,"salary",cc);
		f1.setModifiers(Modifier.PRIVATE);
		cc.addField(f1,"1000");//默认值1000
		
//		cc.getDeclaredField("ename");//获取指定的属性
		
		//增加相应的set/get方法
		cc.addMethod(CtNewMethod.getter("getSalary", f1));
		cc.addMethod(CtNewMethod.setter("setSalary", f1));
		
		Class clazz=cc.toClass();
		Object obj=clazz.newInstance();
		Method method1=clazz.getDeclaredMethod("getSalary", null);
		Object result=method1.invoke(obj, null);
		System.out.println(result);
		Method method2=clazz.getDeclaredMethod("setSalary", int.class);
		method2.invoke(obj, 20000);
		result=method1.invoke(obj, null);
		System.out.println(result);
	}
	
	
	public static void test05() throws Exception {
		ClassPool pool=ClassPool.getDefault();
		CtClass cc=pool.get("com.sxt.test.Emp");
		
		CtConstructor[] cs=cc.getConstructors();
		for(CtConstructor c:cs) {
			System.out.println(c.getLongName());
		}
		
	}
	
	public static void test06() throws Exception {
		ClassPool pool=ClassPool.getDefault();
		CtClass cc=pool.get("com.sxt.test.Emp");
		
		Object[] all=cc.getAnnotations();
		Author a=(Author)all[0];
		String name=a.name();
		int year=a.year();
		System.out.println(name+"--->"+year);
	}
	
	public static void main(String[] args) throws Exception {
		test06();
	}
}

- JVM核心机制

- 类加载全过程

了解加载过程的目的:

  • 有助于了解JVM运行过程
  • 更深入了解JAVA动态性,(解热部署、动态加载),提高程序的灵活性

- 类加载机制

  • JVM把class文件加载到内存,并对数据进行校验、解析和初始化(链接),最终形成JVM可以直接使用的Java类型的过程
    加载:
  • 将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区中的运行时数据结构,在堆中生成一个代表这个类的java.lang.Class对象,作为方法区类数据的访问入口。
  • class文件的来源:本地文件、网络、压缩包、数据库等
    **链接:**将Java类的二进制代码合并到JVM的运行状态之中 的过程
  • 验证:确保加载的类信息符合JVM规范,没有安全方面的问题
  • 准备:正式为类变量(static变量)分配内存并设置类变量初始值的阶段,这些内存都将再方法区中进行分配
  • 解析:虚拟机常量池的符号引用替换成直接引用的过程
    初始化
  • 初始化阶段是执行类构造器()方法的过程。类构造器()方法是由编译器自动收集类中所有类变量的赋值动作和动态语句块(static块)中的语句合并产生的。
  • 当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先初始化其父类
  • 虚拟机会保证一个类的()方法在多线程环境中被正确加锁和同步
  • 当访问一个Java类的静态域时,只有真正声明这个域的类才被会初始化。
    类的主动引用(一定会发生初始化)
  • new一个类的对象
  • 调用类的静态成员(除final常量)和静态方法
  • 使用java.lang.reflect包的方法对类进行反射调用
  • 当虚拟机启动,java Hello,则一定会初始化Hello类,说白了就是先启动main方法所在的类
  • 当初始化一个类,如果其父类没有初始化,则会先初始化其父类
    类的被动引用(不会发生初始化)
  • 当访问一个静态域时,只有真正声明这个域的类才会被初始化
    -通过子类引用父类的静态变量,不会导致子类初始化
  • 通过数组定义类引用,不会触发此类的初始化
  • 引用常量不会触发此类的初始化(常量在编译阶段就存入调用类的常量池中了)

- 深入了解类加载机制

- 类加载器的作用
  • 将class文件字节码加载到内存中,并将这些静态数据转换成方法区中运行时的数据结构,在堆中生成一个代表这个类的Class对象,作为方法区类数据的访问入口。
- 类缓存
  • 标准的JavaSE类加载器可以按要求查找类,但一旦某个类被加载到类加载器中,它将维持加载(内存)一段时间。不过,JVM垃圾回收集器可以回收这些Class对象。
- 类加载器层次结构

在这里插入图片描述类加载器的层次结构(不是通过继承而是组合形成):
1、引导类加载器(bootstrap class loader)
  用来加载java的核心库(JAVA_HOME/jre/lib/rt.jar,或sun.boot.class.path路径下的内容),是用原生代码来实现的(C实现的),并不继承自java.lang.ClassLoader。
加载扩展类和应用程序类加载器,并指定它们的父类加载器。
2、扩展类加载器(extensions class loader)
用来加载java的扩展库(JAVA_HOME/jre/lib/ext/*.jar,或java.ext.dirs路径下的内容)java虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载java类。
有sun.miscLauncher E x t C l a s s L o a d e r 实 现 , 继 承 自 j a v a . l a n g . C l a s s L o a d e r 3 、 应 用 程 序 类 加 载 器 ( a p p l i c a t i o n c l a s s l o a d e r ) 它 根 据 j a v a 应 用 的 类 路 径 ( c l a s s p a t h , j a v a . c l a s s . p a t h 路 径 ) 来 加 载 指 定 路 径 的 类 , 一 般 来 说 , j a v a 应 用 的 类 都 是 由 它 来 完 成 加 载 的 由 s u n . m i s c . L a u n c h e r ExtClassLoader实现,继承自java.lang.ClassLoader 3、应用程序类加载器(application class loader) 它根据java应用的类路径(classpath,java.class.path路径)来加载指定路径的类,一般来说,java应用的类都是由它来完成加载的 由sun.misc.Launcher ExtClassLoaderjava.lang.ClassLoader3applicationclassloaderjavaclasspathjava.class.pathjavasun.misc.LauncherAppClassLoader实现,继承自java.lang.ClassLoader
4、自定义类加载器
开发人员可以通过继承java.lang.ClassLoader类的方式实现自己的类加载器,以满足一些特殊的需求。
说明:在java中由于类的加载采用的是双亲委托机制,上面几种类加载器是父子关系,其中引导类加载器为基础。

ClassLoader类介绍
作用:
java.lang.ClassLoader类的基本职责就是根据一个指定的类的名称找到或者生成其对应的字节代码,然后从这些字节代码中定义出一个java类,即java.lang.Class类的一个实例。
除此之外,ClassLoader还负责加载java应用所需的资源文件,如图像文件和配置文件等。
相关方法:
getParent()  返回该类加载器的父类加载器
loadClass(String name)  加载名称为name的类,返回的结果是java.lang.Class类的实例
findClass(String name)  查找名称为name的类,返回的结果是java.lang.Class类的实例
findLoadedClass(String name)  查找名称为name的已经被加载过的类,返回的结果是java.lang.Class类的实例
defineClass(String name,byte[] b,int off,int len)  把字节数组b中的内容转换成java类,返回的结果是java.lang.Class类的实例。这个方法被声明为final的。
resolveClass(Class<?> c)  链接指定的java类。

类加载器模式:
**代理模式:**交给其他加载器来加载指定的类。
双亲委托机制:
就是某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,以此追溯,直到最高的爷爷辈的,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。
双亲委托机制是为了保证java核心库的类型安全(这种机制就保证不会出现用户自己能定义java.lang.Object类的情况)。
类加载器除了用于加载类,也是安全的最基本的屏障。
双亲委托机制是代理模式的一种:
并不是所有的类加载器都采用双亲委托机制。
tomcat服务器类加载器也使用代理模式,所不同的是它是首先尝试自己去加载某个类,如果找不到再代理给父类加载器。这与一般类加载器的顺序是相反的。
示例

public class Demo02 {
	public static void main(String[] args) {
		System.out.println(ClassLoader.getSystemClassLoader());//获取当前的类加载器
		System.out.println(ClassLoader.getSystemClassLoader().getParent());
		System.out.println(ClassLoader.getSystemClassLoader().getParent().getParent());
		
		System.out.println(System.getProperty("java.class.path"));//获取当前的classpath
		
		System.out.println("-------------------------------");
		//自己已经自定义了java.lang.String 但是不会被加载,因为boot loader发现已加载(因为rt.jar包中有,保证了安全)
		String 	a="ahh";
		System.out.println(a.getClass().getClassLoader());//获取加在a类的类加载器
		System.out.println(a);
	}
}
//运行结果:
sun.misc.Launcher$AppClassLoader@2a139a55
sun.misc.Launcher$ExtClassLoader@7852e922
null
C:\Program Files\Java\jdk1.8.0_181\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\rt.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\zipfs.jar;D:\eclipse\eclipse-workspace\31-ClassLoadProcess\bin
-------------------------------
null
ahh
- 自定义类加载器

自定义类加载器流程

  • 继承:java.lang.ClassLoader
  • 首先检查请求类型是否已经被这个类加载器装载到命名空间中,如果已加载,则返回成功
  • 委派类加载请求给父类加载器,如果父类加载器能够完成,则返回父类加载器加载的Class实例
  • 调用本类加载器的findClass(…)方法,试图获取对应的字节码,如果获取到则调用defineClass(…)导入类型到方法区;如果获取不到对应的字节码或其他原因失败,返回异常给loadClass(…),loadClass(…)转抛异常,终止加载过程
  • 注意:被两个类加载器加载的同一个类,JVM不认为是相同的类
    分类
  • 文件类加载器
    FileSystemClassLoader
public class FileSystemClassLoader extends ClassLoader {

	//在rootDir路径下  试图加载给出的类
	private String rootDir;

	public FileSystemClassLoader(String rootDir) {
		this.rootDir = rootDir;

	}

	@Override
	protected Class<?> findClass(String name) throws ClassNotFoundException  {
		Class<?> c = findLoadedClass(name);// 查找是否已加载
		// 首先检查请求类型是否已经被这个类加载器装载到命名空间中,如果已加载,则返回成功
		if (c != null) {
			return c;// 已加载,则直接返回
		} else {
			// 委派类加载请求给父类加载器,如果父类加载器能够完成,则返回父类加载器加载的Class实例
			ClassLoader parent = this.getParent();
			try {
				c = parent.loadClass(name);
			} catch (ClassNotFoundException e) {
//				e.printStackTrace();//父加载器加载失败,继续执行 此时try-catch的作用
			}
			if (c != null) {
				return c;
			} else {
				byte[] classData = getClassData(name);
				if (classData == null) {
					throw new ClassNotFoundException();
				} else {
					c = defineClass(name, classData, 0, classData.length);
					return c;
				}
			}
		}
	}

	private byte[] getClassData(String classname) {
		String path = rootDir + "/" + classname.replace('.', '/') + ".class";
		InputStream is = null;
		ByteArrayOutputStream baos=null;;
		try {
			is = new FileInputStream(path);
			baos=new ByteArrayOutputStream();
			byte[] buffer=new byte[1024];
			int len=0;
			while((len=is.read(buffer))!=-1) {
				baos.write(buffer,0,len);
			}
			baos.flush();
			return baos.toByteArray();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
			return null;
		} catch (IOException e) {
			e.printStackTrace();
			return null;
		} finally {
			if(null!=is) {
				try {
					is.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			if(null!=baos) {
				try {
					baos.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}

}

测试上述加载器:

public class Demo03 {
	public static void main(String[] args) throws Exception {
		FileSystemClassLoader loader1=new FileSystemClassLoader("F:/src/java");
		FileSystemClassLoader loader2=new FileSystemClassLoader("F:/src/java");
		
		Class<?> c1=loader1.loadClass("com.sxt.study.Welcome");
		Class<?> c2=loader1.loadClass("com.sxt.study.Welcome");
		Class<?> c3=loader2.loadClass("com.sxt.study.Welcome");
		Class<?> c4=loader2.loadClass("java.lang.String");
		Class<?> c5=loader2.loadClass("com.sxt.test.Demo01");//本工程下的java文件
		
		System.out.println(c1.hashCode());
		System.out.println(c2.hashCode());
		System.out.println(c3.hashCode());//被两个类加载器加载的同一个类,JVM不认为是相同的类
		
		System.out.println(c3.getClassLoader());//自定义类加载器FileSystemClassLoader
		System.out.println(c4.getClassLoader());//引导类加载器
		System.out.println(c5.getClassLoader());//系统默认的类加载器
	}
}
//输出结果
1442407170
1442407170
1028566121
com.sxt.test.FileSystemClassLoader@5c647e05
null
sun.misc.Launcher$AppClassLoader@2a139a55
  • 网络类加载器
    NetClassLoader
public class NetClassLoader extends ClassLoader {

	//在rootDir路径下  试图加载给出的类
	private String rootUrl;

	public NetClassLoader(String rootUrl) {
		this.rootUrl = rootUrl;

	}

	@Override
	protected Class<?> findClass(String name) throws ClassNotFoundException  {
		Class<?> c = findLoadedClass(name);// 查找是否已加载
		// 首先检查请求类型是否已经被这个类加载器装载到命名空间中,如果已加载,则返回成功
		if (c != null) {
			return c;// 已加载,则直接返回
		} else {
			// 委派类加载请求给父类加载器,如果父类加载器能够完成,则返回父类加载器加载的Class实例
			ClassLoader parent = this.getParent();
			try {
				c = parent.loadClass(name);
			} catch (ClassNotFoundException e) {
//				e.printStackTrace();//父加载器加载失败,继续执行 此时try-catch的作用
			}
			if (c != null) {
				return c;
			} else {
				byte[] classData = getClassData(name);
				if (classData == null) {
					throw new ClassNotFoundException();
				} else {
					c = defineClass(name, classData, 0, classData.length);
					return c;
				}
			}
		}
	}

	private byte[] getClassData(String classname) {
		String path = rootUrl + "/" + classname.replace('.', '/') + ".class";
		InputStream is = null;
		ByteArrayOutputStream baos=null;;
		try {
			URL url=new URL(path);
			is = url.openStream();
			baos=new ByteArrayOutputStream();
			byte[] buffer=new byte[1024];
			int len=0;
			while((len=is.read(buffer))!=-1) {
				baos.write(buffer,0,len);
			}
			baos.flush();
			return baos.toByteArray();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
			return null;
		} catch (IOException e) {
			e.printStackTrace();
			return null;
		} finally {
			if(null!=is) {
				try {
					is.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			if(null!=baos) {
				try {
					baos.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}

}
  • 加密解密类加载器(取反操作,DES对称加密解密)
    EncrptUtil:简单的取反加密
public class EncrptUtil {

	public static void encrpt(String src,String dest) {
		FileInputStream fis=null;
		FileOutputStream fos=null;
		
		try {
			fis=new FileInputStream(src);
			fos=new FileOutputStream(dest);
			
			int temp=-1;
			while((temp=fis.read())!=-1) {
				fos.write(temp^0xff);//取反操作
			}
			
			fos.flush();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}finally {
			if(fis!=null) {
				try {
					fis.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			
			if(fos!=null) {
				try {
					fos.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
		
	}
	
	public static void main(String[] args) {
		encrpt("F:/src/java/Hi.class","F:/src/java/temp/Hi.class");
	}
}

DecrptClassLoader:加载文件系统中加密后的class字节码的类加载器

public class DecrptClassLoader extends ClassLoader {
	// 在rootDir路径下 试图加载给出的类
	private String rootDir;

	public DecrptClassLoader(String rootDir) {
			this.rootDir = rootDir;

		}

	@Override
	protected Class<?> findClass(String name) throws ClassNotFoundException {
		Class<?> c = findLoadedClass(name);// 查找是否已加载
		// 首先检查请求类型是否已经被这个类加载器装载到命名空间中,如果已加载,则返回成功
		if (c != null) {
			return c;// 已加载,则直接返回
		} else {
			// 委派类加载请求给父类加载器,如果父类加载器能够完成,则返回父类加载器加载的Class实例
			ClassLoader parent = this.getParent();
			try {
				c = parent.loadClass(name);
			} catch (ClassNotFoundException e) {
//					e.printStackTrace();//父加载器加载失败,继续执行 此时try-catch的作用
			}
			if (c != null) {
				return c;
			} else {
				byte[] classData = getClassData(name);
				if (classData == null) {
					throw new ClassNotFoundException();
				} else {
					c = defineClass(name, classData, 0, classData.length);
					return c;
				}
			}
		}
	}

	private byte[] getClassData(String classname) {
		String path = rootDir + "/" + classname.replace('.', '/') + ".class";
		InputStream is = null;
		ByteArrayOutputStream baos = null;
		
		try {
			is = new FileInputStream(path);
			baos=new ByteArrayOutputStream();
			int temp=-1;
			while((temp=is.read())!=-1) {
				baos.write(temp^0xff);//取反操作,相当于解密
			}
			baos.flush();
			return baos.toByteArray();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
			return null;
		} catch (IOException e) {
			e.printStackTrace();
			return null;
		} finally {
			if (null != is) {
				try {
					is.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			if (null != baos) {
				try {
					baos.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}
}

测试:

public class Demo04 {
	public static void main(String[] args) throws ClassNotFoundException {
		//操作取反操作
		/*int a=3;//00000011
		System.out.println(Integer.toBinaryString(a^0xff));*/
		
		//无法直接加载,报错classFormatError
		/*FileSystemClassLoader loader=new FileSystemClassLoader("F:/src/java/temp");
		Class<?> c = loader.findClass("Hi");
		System.out.println(c);*/
		
		DecrptClassLoader loader=new DecrptClassLoader("F:/src/java/temp");
		Class<?> c = loader.findClass("Hi");
		System.out.println(c);
	}
}
  • 线程上线文类加载器
    在这里插入图片描述
public class Demo05 {
	public static void main(String[] args) throws ClassNotFoundException {
		ClassLoader loader1=Demo05.class.getClassLoader();
		System.out.println(loader1);
		
		ClassLoader loader2=Thread.currentThread().getContextClassLoader();
		System.out.println(loader2);
		
		Thread.currentThread().setContextClassLoader(new FileSystemClassLoader("F:/src/java") );
		ClassLoader loader3=Thread.currentThread().getContextClassLoader();
		System.out.println(loader3);
		
		ClassLoader loader4=Demo05.class.getClassLoader();
		System.out.println(loader4);
		
		Class<?> c = Thread.currentThread().getContextClassLoader().loadClass("com.sxt.test.Demo01");//本工程下的java文件
		System.out.println(c);
		System.out.println(c.getClassLoader());
	}
}
//运行结果
sun.misc.Launcher$AppClassLoader@2a139a55
sun.misc.Launcher$AppClassLoader@2a139a55
com.sxt.test.FileSystemClassLoader@4e25154f
sun.misc.Launcher$AppClassLoader@2a139a55
class com.sxt.test.Demo01
sun.misc.Launcher$AppClassLoader@2a139a55
  • TOMCAT服务器的类加载机制
    在这里插入图片描述
  • OSGI原理
    在这里插入图片描述
    说明:A、B、C三个组件,每个都有单独的类加载器,当B组件需要用到A中S类提供的服务时,需要将A导出,B导入,加载时S类还是由A的类加载器加载。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值