Java进阶--08--注解、反射、字节码、类加载

一、注解(Annotation)

1、内置注解

  • 注解与注释:注解除了可以看,还能被其他的程序所读取;
  • 常用注解:@override、@deprecated--不建议使用的、@suppressWarining("all")--警告抑制

2、自定义注解

(1)注解关键字:@interface;

(2)四个元注解

  • 元注解的作用:就是用来说明解释其他注解的注解;
  • @Target:定义注解的作用目标;
@Target(ElementType.TYPE)   //接口、类、枚举、注解
@Target(ElementType.FIELD) //字段、枚举的常量
@Target(ElementType.METHOD) //方法
  • @Retention: 定义注解的保留策略(生命周期);
@Retention(RetentionPolicy.SOURCE)   //注解仅存在于源码中,在class字节码文件中不包含
@Retention(RetentionPolicy.CLASS)     // 默认的保留策略,注解会在class字节码文件中存在,但运行时无法获得,
@Retention(RetentionPolicy.RUNTIME)  // 注解会在class字节码文件中存在,在运行时可以通过反射获取到
  • @Document:说明该注解将被包含在javadoc中;
  • @Inherited:说明子类可以继承父类中的该注解;

(3)注解的参数

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnno {
	int age() default 0;
	String[] strs();
}
  • 单个参数建议参数名写为 value;
  • 参数没有赋默认值,用注解时必须赋值不然会报错; 

3、注解读取

(1)应用示例:用注解完成 orm 映射

  • 定义注解、在类中使用注解、用反射读取注解

二、反射(reflection)

1、概述

(1)概念:

  • JAVA反射机制是在运行状态中,对于任意一个实体类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;

(2)获取class 文件对象的方法

  • class.forName()
  • obj.getClass()
  • class.class

(3)说明:

  • 对于一个类来说,类加载到内存中后,class 文件对象有且只有一个;

2、动态获取信息和动态操作

(1)动态获取

   1、取属性

  • 获取公开属性
Field[] fields = clazz.getFields();
Field age = clazz.getField("age");
  • 获取所有属性
Field[] dfs = clazz.getDeclaredFields();
Field dage = clazz.getDeclaredField("age");

   2、取方法

Method getage = clazz.getDeclaredMethod("setAge", int.class);
Method[] ms = clazz.getDeclaredMethods();
  • 获取公开方法的方法参考属性; 

   3、取构造器

  • 具体方法参考属性的;

(2)动态操作

  1、api 动态调用构造器新建对象

  • 空参:clazz.newInstance():本质就调用空参构造器;
  • 有参:
Constructor<?> ins = clazz.getDeclaredConstructor(String.class,int.class);
Student obj = (Student) ins.newInstance("allen",12);

  2、动态执行方法

Method getage = clazz.getDeclaredMethod("getAge", null);
int age = (int) getage.invoke(obj, null);

  3、动态设置和获取属性(暴力反射)

Field dage = clazz.getDeclaredField("age");
dage.setAccessible(true);
int age = (int) dage.get(obj);

3、反射性能

(1)提高反射性能

  • 反射提高了程序的灵活性的同时降低了性能;
  • 通过关闭安全检查可以提高反射的性能(setAccessible(true));

(2)反射操作泛型

  • type  是一个反射包下的接口;
  • Class
  • ParameterizedType
  • GenericArrayType
  • WildcardType
  • TypeVariable

(3)反射读取注解

  • 读取注解先要获得相应的class、field 和 method 对象;
  • 然后就是调用方法读取注解

4、动态编译和脚本引擎

(1)动态编译

   1、动态编译

  • 第一种是利用 Runtime 类用exec 调用 javac 命令进行动态编译;
  • 第二种用 JavaCompiler 类的 run 方法编译;

   2、动态执行

  • 第一种还是用 runtime 类来调用 java 命令;
  • 第二种是反射来调用,要用到类加载器;
/*Runtime rt = Runtime.getRuntime();
rt.exec("javac d:/Hello.java");
Process pro = rt.exec("java -cp d: Hello");
InputStream is = pro.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String str = "";
while((str = br.readLine()) != null) {
	System.out.println(str);
}*/

JavaCompiler jc = ToolProvider.getSystemJavaCompiler();
jc.run(null, null, null, "d:/Hello.java");
URL[] urls = {new URL("file:/d:/")};
URLClassLoader loader = new URLClassLoader(urls);
Class<?> clazz = loader.loadClass("Hello");
Method m = clazz.getDeclaredMethod("main", String[].class);
Object ins = clazz.newInstance();
m.invoke(ins, (Object)new String[] {});

(2)脚本引擎

  • 概念:就是一套让java 程序能够操作js等脚本的API;
  • 作用:通过 java 代码执行 js 等脚本;通过引擎的上下文,让java 和脚本之间进行数据交换;
  • 基本使用:
//get engine
ScriptEngineManager sem = new ScriptEngineManager();
ScriptEngine jsEngine = sem.getEngineByName("js");
//write js
String str = "msg = 'haha';print(msg)";
//run js by java
jsEngine.eval(str);
Object msg = jsEngine.get("msg");
System.out.println(msg);
  • 还可以将 engine 强转为 invocable 执行 js 函数 ;
String str = "function getData() {print('ahah')}";
jsEngine.eval(str);
Invocable invo = (Invocable)jsEngine;
invo.invokeFunction("getData", null);
  • 在js代码中导入java包,使用包中的java类
String jscode = "var list=java.util.Arrays.asList(['allen','mike','ai']);";
engine.eval(jscode);
List<String> list2 = (List<String>) engine.get("list");
for (String t : list2) {
	System.out.println(t);
}
  • 执行单独的js 文件
URL url=Demo01.class.getClassLoader().getResource("a.js");
FileReader reader=new FileReader(url.getPath());
engine.eval(reader);
reader.close();

三、字节码操作

1、概述

(1)java 动态性的两种实现方法:反射和字节码操作;

(2)作用:通过字节码操作可以动态新建和修改一个类;字节码操作的性能是高于反射的;

(3)常用的字节码操作类库:CGLIB和 javassist;

2、javassist入门

(1)最外层 api 包含 CtClass、Ctfield 和 CtMethod 三个类,这三个类的作用和反射中的calss、field 和 method 相类似;

(2)基本使用

ClassPool pool = ClassPool.getDefault();
CtClass stu = pool.getCtClass("com.stan.test.Stu");
//do with field
CtField f = CtField.make("public int age = 10;", stu);
stu.addField(f);
//do with method
CtMethod m  = CtMethod.make("public int getOne(){return 2;}", stu);
stu.addMethod(m);
//do with constructor
CtConstructor cons = new CtConstructor(new CtClass[] {pool.get("java.lang.String")}, stu);
cons.setBody("this.name = name;");
stu.addConstructor(cons);

3、详细api

(1)获取类相关的基本信心,比如类名等;

(2)新增或者修改方法,两种方案;

(3)新增字段;

(4)构造器的获取;

(5)注解的读取;

四、JVM核心机制

1、类加载过程

(0)JVM 的内存结构

  • 栈(方法):方法压栈执行,结束弹栈释放;
  • 堆(对象):存对象的,class 对象也是存放在这个区;
  • 方法区(实质也是堆):包含常量池、类的静态成员、类的代码;

(1)类加载:就是 class 文件转变为 JVM 可用的类的过程;

(2)过程概述:

   1、加载

  • class文件字节码加载到内存,方法区生成静态变量的数据结构,堆中生成一个 class 对象;

   2、链接:类二进制代码合并到JVM 的运行状态之中

  • 验证:安全检查;
  • 准备:为静态变量分配内存并赋初始化值,此过程在方法区中;
  • 解析:常量池内的符号引用替换为直接引用的过程;

   3、初始化

  • 就是把静态变量的赋值动作和静态代码块收到类构造器中,然后类构造器进行类的初始化操作;

(3)初始化时机

1、主动引用

  • 主动引用类的时候,比如 new对象的时候,类会进行初始化;
  • 类初始化的时候,如果父类没有被初始化,会先初始化父类;

2、被动引用

  • 引用类中常量,不会初始化;
  • 子类引用父类的静态域,子类不会初始化,父类会;
  • 定义数组引用类不会初始化;

2、类加载器的层次结构

(1)引导类加载器

  • 作用是加载核心类库;用C语言写的;不是继承lang 包里的 classloader

(2)扩展类加载器

  • 加载jre中的扩展包(ext);继承lang 包里的 classloader;用java 写的;

(3)应用类加载器

  • 我们在程序中自己写的类就是他加载的;默认记载 classpath 路径下的类;

(4)自定义类加载器

  • 可以自己继承 classloader 实现自己的类加载器;

(5)代理模式--双亲委托机制

  • java 的类加载采用的是代理模式:就是让別的类加载器来加载类;
  • 具体采用的是双亲委托机制:就是要加载类的时候把这个事一层一层往上抛,到最顶层的引导类加载器,然后往下走,那一层能处理就加载;
  • 作用:就是能保证 java 核心类库的安全;

3、自定义类加载器

(1)说明:

  • 不一定非要用双亲委托;
  • 同一个类被不同的加载器加载,也被 jvm 认为是不同的类;

(2)系统文件类加载器(示例)

(3)网络类加载器(示例)

(4)加密解密加载器(示例):取反

(5)线程上下文加载器(示例)

(6)tomcat 类加载和 OSGI(java 动态模块系统)

猜你喜欢

转载自blog.csdn.net/stanwuc/article/details/83992598