注解与反射

注解入门

  • Annotation是JDK5.0开始引入的新技术
  • Annotation的作用
    • 不是程序本身,可以对程序作出解释。
    • 可以被其他程序(比如:编译器等)读取。(注解信息处理流程,是注解和注释的重大区别。如果没有注解信息处理流程,则注解毫无意义)。
  • Annotation格式
    • 注解是以“@注释名”在代码中存在的,可以添加一些参数值,例如:@SeppressWarnings(value=”unchecked”)。
  • Annotation在哪里使用?
    • 可以附加在package,class,method,field等上面,相当于它它们添加了额外的辅助信息,我们可以通过反射机制编程实现对这些数据的访问。

内置注解

  • @Override:定义在java.lang.Override中,此注释只适用修饰方法,表示一个方法声明打算重写超类中的另一个方法声明。
  • @Deprecated:定义在java.lang.Deprecated中,此注释可以用于修饰方法、属性、类,表示不鼓励程序员使用这样的元素,通常是因为它很危险吗或存在更好的选择。(一般不建议使用)
  • @SuppressWarnings:用来抑制编译器运行时的警告信息。
    • @SuppressWarnings("unchecked")
      告诉编译器忽略 unchecked 警告信息,如使用List,ArrayList等未进行参数化产生的警告信息。
    • @SuppressWarnings("serial")
      如果编译器出现这样的警告信息:The serializable class WmailCalendar does not declare a static final serialVersionUID field of type long
      使用这个注释将警告信息去掉。
    • @SuppressWarnings("deprecation")
      如果使用了使用@Deprecated注释的方法,编译器将出现警告信息。
      使用这个注释将警告信息去掉。
    • @SuppressWarnings("unchecked", "deprecation")
      告诉编译器同时忽略unchecked和deprecation的警告信息。
    • @SuppressWarnings(value={"unchecked", "deprecation"})
      等同于@SuppressWarnings(“unchecked”, “deprecation”)

自定义注解、元注解

元注解

  • @Target
    • 作用:用于描述注解的使用范围

|所修饰的范围|取值ElemnetType|
|-|-|-|
|package包|PACKAGE|
|类、接口、枚举、Annotation类型|TYPE|
|类型成员(方法、构造器、成员变量、枚举值)|CONSTRUCTOR:用于描述构造器。
FIELD:用于描述域
METHOD:用于描述方法|
|方法参数和本地变量|LOCAL_VALUE:用于描述局部变量
PARANETER:用于描述参数|

  • @Target(value=ElementType.Type)
  • @Retention
  • 作用:表示需要在什么级别保存该注解,用于描述注解的生命周期。
取值 RetentionPolicy 作用
SOURCE 在源文件中有效(即源文件保存)
CLASS 在class文件中有效(即class保留)
RUNTIME 在运行时有效(即运行时保留)
为Runtime可以被反射机制读取
  • 注意:
    • 注解元素的参数的默认值一般设置为0空字符窜
    • 也可能是-1,表示什么也没有
  • @Documented
    • 作用:用于描述其它类型的annotation应该被作为被标注的程序成员的公共API,因此可以被例如javadoc此类的工具文档化。它是一个标记注解,没有成员。
  • @Inherited
    • 作用:用于表示某个被标注的类型是被继承的。如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类

反射机制读取注解

  • 什么是ORM(Object Relationship Mapping)
    • 类和表结构对应
    • 属性和字段对应
    • 对象和记录对应

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

  • 学了反射机制后,我们可以定义注解的执行流程读取这些注解,实现更复杂的功能
1
2
3
4
5
6
7
8
9
10
11
12
package annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(value = ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Table {
String value();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(value = ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Field {
String columnName();
String type();
int length();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package annotation;
@Table("tb_student")
public class Student {
@Field(columnName="id",type="int",length=3)
private int id;
@Field(columnName="sname",type="varchar",length=10)
private String studentName;
@Field(columnName="age",type="int",length=10)
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;
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package annotation;

import java.lang.annotation.Annotation;

/**
* 使用反射处理注解信息,模拟处理注解信息的流程
* @author 1huangzewei
*
*/
public class Demo3 {
public static void main(String[] args) {
try {
Class clazz=Class.forName("annotation.Student");
//获取类所有有效的注解
Annotation[] annotations=clazz.getAnnotations();
for(Annotation a:annotations) {
System.out.println(a);
}
//获取指定的注解
Table table=(Table) clazz.getAnnotation(Table.class);
System.out.println(table.value());
//获取类的属性的注解
Field f=clazz.getDeclaredField("studentName").getAnnotation(Field.class);
System.out.println(f.columnName() + "--->" + f.type() + "--->" + f.length());


} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}

Java的动态性

  • 动态语言:程序运行时,可以改变程序结果或变量类。
  • Java可以称为准动态语言。

反射机制 reflection

  • 指的是可以于运行时加载、探知、使用编译期间完全未知的类。
  • 程序在运行状态中,可以动态加载一个只有名称的类,对于任意一个已加载的类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性。
1
Class c=Class.forName("包名.类名");
  • 加载完类之后,在堆内存中,就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,通过这个镜子可以看到类的结构,所以我们形象的称之为:反射。
  • Class类的对象如何获取
    • 运用getClass()
    • 运用Class.forName()(最常被使用)
    • 运用类.class
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package com.zhuchuli.reflection;

import com.zhuchuli.testBean.User;

/***
* 测试java.lang.Class对象的获取方式。
* 测试各种数据类型java.lang.Class对象的获取方式。
* 1.利用类.class
* 2.利用类.getClass()
* 3.利用Class.forName()
* @author 1huangzewei
*
*/
@SuppressWarnings("all")
public class Demo01 {
public static void main(String[] args) {
String path= "com.zhuchuli.testBean.User";
try {
Class clazz = Class.forName(path);
//对象是表示或封装一些数据。一个类被加载之后,Jvm会创建一个对应该类的Class对象,类的整个结构信息会放到对象的Class对象中。
//这个Class对象就像一面镜子一样,通过这个镜子,我们可以看到类的全部信息。
System.out.println(clazz.hashCode());

//获取Class对象的三种方式
Class strClazz=String.class;
Class strClazz2=path.getClass();
System.out.println(strClazz==strClazz2);

//基本数据类型
Class intClaszz=int.class;

} catch (Exception e) {
e.printStackTrace();
}
}
}
  • 反射机制的常见作用

  • 动态加载类、动态获取类的信息(属性、方法、构造器)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
package com.zhuchuli.reflection;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

/***
* 应用反射API,获取类的信息(类的名字、属性、方法、构造器等)
* @author 1huangzewei
*
*/
public class Demo02 {
public static void main(String[] args) {
String path= "com.zhuchuli.testBean.User";
try {
Class clazz = Class.forName(path);
//获取类的名字
System.out.println("---------------------------------通过反射获取类名---------------------------------");
System.out.println(clazz.getName());//获得包名+类名 com.zhuchuli.testBean.User
System.out.println(clazz.getSimpleName());//获得类名 User
System.out.println("------------------------------------------------------------------------------");
//获取属性信息
System.out.println("---------------------------------通过反射获取属性---------------------------------");
//Field[] fields=clazz.getFields();//只能获取public的属性
Field[] fields=clazz.getDeclaredFields();//获得所有的field
Field f=clazz.getDeclaredField("uname");
System.out.println(f);
for(Field field:fields) {
System.out.println("属性:"+field);
}
System.out.println("------------------------------------------------------------------------------");
//获取方法信息
System.out.println("---------------------------------通过反射获取方法---------------------------------");
Method[] methods=clazz.getMethods();//只能获取public修饰的方法
Method[] methodss=clazz.getDeclaredMethods();//获取所有的方法
Method m01=clazz.getDeclaredMethod("getUname", null);
System.out.println(m01);
Method m02=clazz.getDeclaredMethod("setUname", String.class);//如果方法有参数,则必须指明参数的类型,即参数类型.class
System.out.println(m02);
for(Method method:methodss) {
System.out.println("方法:"+method);
}
System.out.println("------------------------------------------------------------------------------");
//获取构造器信息
System.out.println("---------------------------------通过反射获取构造器---------------------------------");
Constructor[] constructors=clazz.getConstructors();//获取public修饰的构造器
Constructor[] constructorss=clazz.getDeclaredConstructors();
for(Constructor constructor:constructorss) {
System.out.println("构造器:"+constructor);
}
System.out.println("根据参数类型获取构造器");
Constructor c=clazz.getDeclaredConstructor(null);
System.out.println("构造器:"+c);
Constructor cc=clazz.getConstructor(int.class,String.class,int.class);
System.out.println("构造器:"+cc);
System.out.println("------------------------------------------------------------------------------");
} catch (Exception e) {
e.printStackTrace();
}
}
}
  • 动态构造对象
  • 动态调用类和对象的任意方法、构造器
  • 动态调用和处理属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package com.zhuchuli.reflection;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

import com.zhuchuli.testBean.User;

/***
* 通过反射API动态的操作:构造器,方法和属性
* @author 1huangzewei
*
*/
public class Demo03 {
public static void main(String[] args) {
//动态操作构造器
String path= "com.zhuchuli.testBean.User";
try {
Class clazz = Class.forName(path);
//通过反射API调用构造方法,构造对象
User u=(User) clazz.getConstructor().newInstance();//其实调用JavaBean的无参数构造器
System.out.println(u);

Constructor<User> c=clazz.getConstructor(int.class,String.class,int.class);
User u2=c.newInstance(1001,"lee",18);
System.out.println(u2.getId()+"--"+u2.getUname()+"--"+u2.getAge());

//通过反射API调用普通方法
User u3=(User) clazz.getConstructor().newInstance();
Method method=clazz.getDeclaredMethod("setUname", String.class);
method.invoke(u3, "lee2");//类似于u3.setUname="lee2"
System.out.println(u3.getUname());

//通过反射API调用属性
User u4=(User) clazz.getConstructor().newInstance();
Field f=clazz.getDeclaredField("uname");
f.setAccessible(true);//这个属性不需要做安全检查,可以直接访问。
f.set(u4, "lee3");//通过放射获取属性的值
System.out.println(u4.getUname());
System.out.println(f.get(u4));//通过反射读取对象的值

} catch (Exception e) {
e.printStackTrace();
}
}
}
  • 获取泛型信息(Generic)
    • Java使用泛型擦除机制来引入泛型。Java中的泛型仅仅是给编译器JavaC使用的,确保数据的安全性和免去强制类型转换的麻烦。但是编译一旦完成,所有和泛型有关的类全部被擦除。
    • 为了通过反射操作这些类型以迎合实际开发的需要,Java新增了PatameterType,GenericArrayType,TypeVariable,和WildCardType几种类型来代表不能被归一到Class类中的类型但是又和原始类型齐名的类型。
      • ParameterizedType:表示一种参数化的类型,比如Collection< String >.
      • GenericArrayType:表示一种元素类型是参数化类型或者类型变量的数组类型.
      • TypeVariable:是各种类型变量的公共父接.
      • WildcardType:代表一种通配符类型表达式,比如?、? extends Number、? super Integer。(wildcard是一个单词:就是”通配符“).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
package com.zhuchuli.reflection;

import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;

import com.zhuchuli.testBean.User;

/***
* 通过反射获取泛型信息
* @author 1huangzewei
*
*/
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("Demo01 test02()");
return null;
}
public static void main(String[] args) {
try {
//获取执行方法参数的泛型信息
Method method = Demo04.class.getDeclaredMethod("test01", Map.class, List.class);
Type[] t=method.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 method2=Demo04.class.getDeclaredMethod("test02",null);
Type returnType=method2.getGenericReturnType();
if(returnType instanceof ParameterizedType) {
//获取泛型中的具体信息
Type[] genericTypes = ((ParameterizedType) returnType).getActualTypeArguments();
for(Type genericType:genericTypes) {
System.out.println("泛型类型:" + genericType);
}
}

} catch (Exception e) {
e.printStackTrace();
}


}
}
  • 处理注解:功能与案例代码与上面雷同。

  • 反射机制的性能问题
    • setAccessible
      • 启动和禁用访问安全检查的机关,值为true则指示反射的对象在使用时应该取消Java语言访问检查。值为false则指示表明反射的对象应该实施Java语言访问检查。并不是为true就能访问为false就不能访问。
      • 禁止安全检查,可以提高反射的运行速度。
    • 可以考虑使用:cglib/javaassist字节码操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package com.zhuchuli.reflection;

import java.lang.reflect.Method;

import com.zhuchuli.testBean.User;

public class Demo05 {
public static void test01() {
User user=new User();

long before=System.currentTimeMillis();
for(var i=0;i<1000000000L;i++) {
user.getUname();
}
long after=System.currentTimeMillis();
System.out.println("没有反射,共消耗:<"+(after-before)+">毫秒");
}
public static void test02() throws Exception {
User user=new User();
Method method=Class.forName("com.zhuchuli.testBean.User").getDeclaredMethod("getUname", null);
long before=System.currentTimeMillis();
for(var i=0;i<1000000000L;i++) {
method.invoke(user, null);
}
long after=System.currentTimeMillis();
System.out.println("加安全检查,共消耗:<"+(after-before)+">毫秒");
}
public static void test03() throws Exception {
User user=new User();
Method method=Class.forName("com.zhuchuli.testBean.User").getDeclaredMethod("getUname", null);
method.setAccessible(true);
long before=System.currentTimeMillis();
for(var i=0;i<1000000000L;i++) {
method.invoke(user, null);
}
long after=System.currentTimeMillis();
System.out.println("不加安全检查,共消耗:<"+(after-before)+">毫秒");
}
public static void main(String[] args) throws Exception {
test01();
test02();
test03();
}
}

动态编译

  • Java6.0引入了动态编译机制
  • 动态编译的应用场景
    • 可以做一个浏览器端编写java代码,上传服务器编译和运行的在线测评系统。
    • 服务器动态加载某些类文件进行编译。
  • 动态编译的两种方式
    • 通过Runtime调用javac,启动新的进程去操作
      • Runtime run=Runtime.getRuntime();
      • Process process=run.exec(javac -cp d:/myjava/ HelloWorld.java);
    • 通过JavaCompiler动态编译
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.zhuchuli.test;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

import javax.tools.JavaCompiler;
import javax.tools.ToolProvider;

public class Demo01 {
public static void main(String[] args) throws IOException {
String str="public class Hi{public static void main(String[] args[]) {System.out.println(\"Hello Hi haha\");}}";
BufferedWriter br=new BufferedWriter(new FileWriter("d:/MyJava/Hi.java"));
br.write(str);
br.flush();
//获得系统编译器,如果使用jre(运行环境)则无法获得编译器
JavaCompiler complier=ToolProvider.getSystemJavaCompiler();
int result=complier.run(null, null, null,"d:/MyJava/Hi.java");
System.out.println(result==0?"编译成功":"编译失败");
br.close();
}
}
  • 编译结果

avator.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package com.zhuchuli.test;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;

import javax.tools.JavaCompiler;
import javax.tools.ToolProvider;

public class Demo02 {
public static void main(String[] args) throws IOException {
String str="public class Hi{public static void main(String[] args) {System.out.println(\"Hello Hi haha\");}}";
BufferedWriter bw=new BufferedWriter(new FileWriter("d:/MyJava/Hi.java"));
bw.write(str);
bw.flush();
//获得系统编译器,如果使用jre(运行环境)则无法获得编译器
JavaCompiler complier=ToolProvider.getSystemJavaCompiler();
int result=complier.run(null, null, null,"d:/MyJava/Hi.java");
System.out.println(result==0?"编译成功":"编译失败");

Runtime run=Runtime.getRuntime();
Process process=run.exec("java -cp d:/MyJava Hi");
BufferedReader br=new BufferedReader(new InputStreamReader(process.getInputStream()));
String info="";
while((info=br.readLine())!=null) {
System.out.println(info);
}
}
}
  • 编译结果

avator.png

  • 通过反射机制,启动新的线程去执行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package com.zhuchuli.test;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;

import javax.tools.JavaCompiler;
import javax.tools.ToolProvider;
/**
* 通过反射机制调用执行类
* @author 1huangzewei
*
*/
public class Demo03 {
public static void main(String[] args) throws IOException {
String str="public class Hi{public static void main(String[] args) {System.out.println(\"Hello Hi haha\");}}";
BufferedWriter bw=new BufferedWriter(new FileWriter("d:/MyJava/Hi.java"));
bw.write(str);
bw.flush();
//获得系统编译器,如果使用jre(运行环境)则无法获得编译器
JavaCompiler complier=ToolProvider.getSystemJavaCompiler();
int result=complier.run(null, null, null,"d:/MyJava/Hi.java");
System.out.println(result==0?"编译成功":"编译失败");

try {
URL[] urls=new URL[] {new URL("file:/"+"d:/MyJava/")};
URLClassLoader loader=new URLClassLoader(urls);
Class c=loader.loadClass("Hi");
Method m=c.getMethod("main", String[].class);
m.invoke(null, (Object)new String[] {});
//m.invoke(null,new String[] {"a","b"});
//由于可变参数是Java5.0之后才有。上面的代码会被运行成;m.invoke(null,"a","b"),就发生了参数个数不匹配,从而出现错误,因此要加上(Object)转型,避免出现出错;
//public static void mmm(String[] a,String[] b)
//public static void main(String[] args);

}catch(Exception e) {
e.printStackTrace();
}

}
}

编译结果

avator.png

动态执行javascript代码

  • Java脚本引擎是从JDK6.0之后添加的新功能
  • 脚本引擎介绍
    • 使得Java应用程序可以通过一套固定的接口与各种脚本引擎交互,从而达到在Java平台上调用各种脚本语言的目的。
    • Java脚本API是连通Java平台和脚本语言的桥梁。
    • 可以把一些复杂异变的业务逻辑交给与脚本语言处理,这又大大提高了开发效率。
  • 获得脚本引擎
    • ScriptEngineManager sem=new ScriptEngineManager();
    • ScriptEngine engine=sem.getEngineByName(“javascript”);
  • Java脚本API为开发者提供了如下功能:
    • 获取脚本程序输入,通过脚本引擎运行脚本并返回运行结果,这是最核心的接口。
      • 注意是:接口。Java可以使用各种不同的表现,从而通用的调用js、groovy、python等脚本
      • js使用了:Rhino,Rhino是一种使用Java语言编写的JavaScript的开源实现,原先是由Mozilla开发,现在被集成进入JDK6.0
    • 通过脚本引擎的运行上下文在脚本和Java平台间交换数据。
    • 通过Java应用程序调用脚本函数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
package com.zhuchuli.rhino;

import java.io.FileNotFoundException;
import java.io.FileReader;
import java.net.URL;
import java.util.List;

import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;

/***
* 测试脚本引擎执行JavaScript代码
* @author 1huangzewei
*
*/
public class Demo01 {

public static void main(String[] args) throws ScriptException, NoSuchMethodException {

//获取脚本引擎对象
ScriptEngineManager sem=new ScriptEngineManager();
ScriptEngine engine=sem.getEngineByName("javascript");

//定义变量,存储到引擎上下文中
engine.put("msg", "lee is a good student");
String str="var user= {name:'aaa',age:18,schools:['gdaib','dgef']};";
str += "print(user.schools);";
//执行脚本
engine.eval(str);


//修改msg的值
engine.eval("msg='very happy today'");
System.out.println(engine.get("msg"));
System.out.println("--------------------");


//定义函数
engine.eval("function add(a,b){var sum=a+b;return sum;}");
//执行js函数 取得调用接口
Invocable jsInvoke=(Invocable) engine;
//执行脚本中定义的方法
Object i=jsInvoke.invokeFunction("add", new Object[] {10,20});
System.out.println(i);

//导入其他java包,使用其他包中的java类
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");
try {
FileReader fr = new FileReader(url.getPath());
engine.eval(fr);
fr.close();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}


}

}
  • 运行结果

avator.png

动态字字节码操作:javaassist

  • Java动态性的两种常见实现方式:
    • 字节码操作
    • 反射
  • 运行时操作字节码可以让我们实现如下功能
    • 动态生成新的类。
    • 动态改变某个类的结构(添加、删除、修改 新的属性、方法 )
  • 优势
    • 比反射开销小,性能高。
    • Javaassist性能高于反射,低于ASM
  • 常见的字节码操作类库
    • BCEL 基于底层Jvm的操作和指令
    • ASM 基于底层Jvm的操作和指令
    • CGLIB 基于ASM实现
    • Javaassist
  • Javaassist库的API详解
    • javaassist的最外层的API和java的反射包中的API颇为类似。
    • 它主要有CtClass,CtMethod,以及CtField几个类组成。用以执行和JDK反射API中java.lang.Class,java.lang.reflection.Method,java.lang.reflection.Field相同的操作。
  • JAVAassist库的简单使用。
    • 创建一个全新的类
    • 使用XJAD反编译工具,将生成的class文件反编译成Java文件\
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package com.zhuchuli.javaassist;

import java.io.IOException;

import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import javassist.CtField;
import javassist.CtMethod;
import javassist.NotFoundException;

/***
* 测试 使用javaassist动态生成一个新类
* @author 1huangzewei
*
*/
public class Demo01 {
public static void main(String[] args) throws CannotCompileException, NotFoundException, IOException {
//创建一个类池
ClassPool pool=ClassPool.getDefault();
CtClass cc=pool.makeClass("com.zhuchuli.bean.Emp");

//创建属性
CtField f1=CtField.make("private String ename;",cc);
CtField f2=CtField.make("private int empno;",cc);
cc.addField(f1);
cc.addField(f2);

//创建方法
CtMethod c1=CtMethod.make("public String getEname(){return ename;} ", cc);
CtMethod c2=CtMethod.make("public void setEname(String ename){this.ename=ename;} ", cc);
cc.addMethod(c1);
cc.addMethod(c2);
CtMethod c3=CtMethod.make("public int getEmpno(){return empno;} ", cc);
CtMethod c4=CtMethod.make("public void setEmpno(int empno){this.empno=empno;} ", cc);
cc.addMethod(c3);
cc.addMethod(c4);

//创建构造器
CtConstructor constructor=new CtConstructor(new CtClass[] {pool.get("java.lang.String"), CtClass.intType}, cc);
constructor.setBody("{this.ename=ename;this.empno=empno;}");
CtConstructor constructor2=new CtConstructor(null,cc);
cc.addConstructor(constructor);
cc.addConstructor(constructor2);

cc.writeFile("d:/MyJava/Emp.java");//将创建的这一类存放到指定位置上
}
}
  • Javaassist的AIP使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
package com.zhuchuli.javaassist;

import java.lang.reflect.Method;
import java.util.Arrays;

import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.Modifier;


/***
* 测试Javaassist的API
* @author 1huangzewei
*
*/

public class Demo02 {
//处理类的基本用法
public static void test01() {
ClassPool pool=ClassPool.getDefault();
try {
CtClass cc = pool.get("com.zhuchuli.javaassist.Emp");
//将类转换成字节数组
byte[] bytes=cc.toBytecode();
System.out.println(Arrays.toString(bytes));
System.out.println(cc.getName());//获取类名
System.out.println(cc.getSuperclass());//获取父类
System.out.println(cc.getInterfaces());//获取接口
System.out.println(cc.getSimpleName ());//获得简要类名
} catch (Exception e) {
e.printStackTrace();
}
}
//处理方法的基本用法 产生新的方法
public static void test02() {
ClassPool pool=ClassPool.getDefault();
try {
CtClass cc = pool.get("com.zhuchuli.javaassist.Emp");
//CtMethod method=CtMethod.make("public int add(int a,int b){System.out.println(\"test02\");return a+b;}", cc);
//第一个参数为返回值类型,第二个参数为方法名 第三个参数为方法参数 第四个参数为所要加入的对象
CtMethod m=new CtMethod(CtClass.intType, "add", new CtClass[] {CtClass.intType,CtClass.intType},cc);
//设置方法的修饰符
m.setModifiers(Modifier.PUBLIC);
//设置方法体 $1代表参数位置
m.setBody("{System.out.println(\"test02\");return $1+$2;}");
cc.addMethod(m);

Class<?> clazz=cc.toClass();

Object obj=clazz.getConstructor().newInstance();

Method method=clazz.getDeclaredMethod("add", int.class,int.class);
Object result=method.invoke(obj, 200,300);
System.out.println(result);
} catch (Exception e) {
e.printStackTrace();
}
}
//... 还有注解 构造器 和 属性
public static void main(String[] args) {
test01();
}
}

JVM运行和类加载全过程

  • 为什么要研究加载全过程?
    • 有助于了解JVM运行过程
    • 更深入了解Java动态性(解耦部署,动态加载),提高程序的灵活性。
  • 类加载全过程 (如下图所示)
    • 加载
    • 链接 (验证 准备 解析):将Java类的二进制代码合并到JVM的运行状态之中的过程。
    • 初始化:静态属性或静态方法或静态块只会被初始化一次。
    • 使用
    • 卸载

avator.png

  • 类的主动引用(一定会发生类初始化)
    • new一个类的对象。
    • 调用类的静态成员(除了final常量)和静态方法。
    • 使用java.lang.reflect包的方法对类进行反射调用。
    • 当虚拟机启动,java hello则一定会初始化Hello类,说白了就是先启动main方法所在的类。
    • 当初始化一个类,如果其父类没有被初始化,则会先初始化他的父类。
  • 类的被动引用
    • 当访问一个静态域时,只有真正声明这个域的类才会被初始化。
      • 通过子类引用父类的静态变量,不会导致子类初始化。
    • 通过数组定义类引用,不会触发此类的初始化
    • 引用常量不会触发此类的初始化(常量在编译阶段就存入调用类的常量池中了)。

深入类加载器

  • 类加载器原理
    • 类加载器的作用:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区中的运行时数据结构,在堆中生成一个代表这个类的java.lang.Class对象,作为方法区类数据的访问入口。
    • 类缓存:标准的JavaSE类加载器可以按要求查找类,但一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间。不过JVM垃圾集成器可以回收这些Class对象。
  • 类加载器树状结构,双亲委托(代理)机制
    • 引导类加载器(bootstrap class loader)
    • 扩展类加载器(extensions class loader)
    • 应用程序类加载器(application clasas loader)
    • 自定义类加载器

avator.png

avator.png

  • 双亲委托机制是为了保证Java核心库的类型安全。(请求 —> 交给父类 —> 交给爷爷辈)
  • tomcat则与此相反
  • 自定义类加载器(文件、网络、加密)
  • 注意:同一个类被不同的加载器加载,JVM也会认为他们不是相同的类
  • 线程上下文类加载器
  • 服务器类加载器原理和OSGI介绍(面向Java的动态模块系统)
    • Eclipse是基于OSGI技术来构建的