动态语言:
- 程序执行时,可以改变程序结构或者变量的类型,典型的语言:Python、ruby、javascript等。
- 弱类型的语言都是动态语言,在程序执行的时候能够动态改变程序的结构和类型
- C 、C++、java不是动态语言,但java是准动态的语言,具有一定的动态性,可以利用反射来操作字节码对象或者类似动态语言的特性
字节码是什么?
- 字节码是计算机的底层的二进制的表示形式,组成是由0和1进行组成
- 字节码本身就是一系列的字节数组,字节码数组的组成又是由0和1进行组成的
- 字节码的来源:可以文字,图像,视频等,计算机的底层都是使用采二进制进行运算过程,每次进行操作的时候都要将输入计算机的媒体转变成字节码被计算机进行识别,才能进行使用,通过计算机进行二进制的转码然后进行输出的
- java程序再运行的过程中采用的进行二进制的转码操作
- 底层输入进计算机的内存中读取的表示形式是二进制的补码形式
反射机制:
- 指的是可以在运行时加载,探知,使用编译期间完全不知道的类
- 程序在运行状态中,可以动态的加载一个只有名称的类,对于任意一个已经加载的类,都能够知道这个类的所有属性所有属性和方法,对于任意一个对象,都能调用它的任意一个方法和属性
- Class c = Class. forName ("com.beij.test.User");--->动态加载类,加载完成后产生一个对象
- 加载完类之后,在堆内存中,就产生一个Class类型的对象(一个类只有一个Class对象),这个都西昂就包含了完整的类的结构信息,我们通过这个对象看到类的结构,这个对象就像是一面镜子,可以折射出类的所有信息,所以称之为反射
Class类和对象:
- java.lang.Class类十分特殊,用来表示java中类型
- java中的类型包括:calss ,interface,enum,annotation,primitive type(元数据类型),void---->java中的对象
- Class类的对象包含了某个被加载类的结构,一个被加载的类对应一个Class对象
- 当一个class被加载,或者当加载器(class loader)的defineClass()被JVM调用,JVM便自动产生一个Class对象
- Class类是Reflection的根源。
- 针对任何您想动态加载、运行的类,唯有先获得相应的Class 对象
-
Class类的对象如何获取?
- 运用getClass() 运行时进行加载获得类的字节码对象
- 运用.class 语法 类名.class进行获取类的字节码对象 称之为编译期进行加载获取 Class对象
- 运用Class.forName(类的全路径类名)(最常被使用):称之为类的字节码建立期,还没进入编译期之前进行加载该类
反射机制能干什么?
- 动态加载类,动态获取类的信息(属性,构造器,方法)
- 动态构造对象
- 动态调用类和对象的任意方法,构造器
- 动态调用和处理属性
- 获取泛型信息
- 处理注解
反射中的泛型擦除机制:
- java中的泛型仅仅是给编译器javac使用的,确保数据的安全性和免去强制类型转换的复杂操作,但是一旦进行编译完成,所有和泛型有关的类型将会被全部擦除
- 所以反射跳过了泛型的检查,没有泛型的约束
- 为了通过反射能够操作这些类型,并且开发的需要,Java就新增了ParameterizedType(参数类型),
- GenericArrayType(泛型数组的类型),TypeVariable 和WildcardType几种类型代表不能被归一到Class类型中的类型但是又和原始类型齐名的类型,在Class类型中就新增了这些类型的存在能够进行不同的获取操作对于参数和泛型额确定
- ParameterizedType: 表示一种参数化的类型,比如Collection<String>
• GenericArrayType: 表示一种元素类型是参数化类型或者类型变量的数组类型
• TypeVariable: 是各种类型变量的公共父接口
• WildcardType: 代表一种通配符类型表达式,比如?, ? extends Number, ? super Integer【
wildcard是一个单词:就是“通配符”】ParameterizedType: 表示一种参数化的类型,比如Collection<String>
• GenericArrayType: 表示一种元素类型是参数化类型或者类型变量的数组类型
• TypeVariable: 是各种类型变量的公共父接口
• WildcardType: 代表一种通配符类型表达式,比如?, ? extends Number, ? super Integer【
wildcard是一个单词:就是“通配符”】
-
反射中的注解:
- 由于Annotation注解是也是Class的类型,所以对于注解也是可以进行操作的
- 可以通过Class对象操作类的结构信息上的各种的注解结构信息
- // 获得类的所有有效注解
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());
反射机制中的性能问题:
- setAccessible:控制反射中是否需要进行反射中的语言访问检查,进行授权的访问结构
- 启用和禁用访问安全检查机制的开关,值为 true 则指示反射的对象在使用时应该取消 Java 语
言访问检查。值为 false 则指示反射的对象应该实施 Java 语言访问检查。并不是为true就能访问为false就不能访问。
- 禁止安全检查,可以提高反射的运行速度
- 进行字节码操作 ,可以使用其他的解码工具,进行底层的编写
解密底层的类,方法的定义,属性的确定等…使用cglib/javaassist字节码操作
- 底层的类和方法的编写都是通过某种机制进行编写
- 基本使用的都是字符串的操作,进行类型和方法的编写,属性的确定以及对于属性的编写操作都是通过字符串进行编写的,最终进行一定的组合在一起,形成一个类的操作
注解:
什么是注解?Annotation
- Annotation是从JDK5.0开始引入的新技术
- Annotation的作用:
- 不是程序的本身,可以对程序做出一定的解释——->解释程序的执行流程,程序的作用等
- 可以被其他的程序进行读取(比如:编译器等),注解信息处理流程是注解和注释之间最大额区别
- 如果没有注解信息处理流程,则注解毫无作用
- 注解的本身也是一种java类,只不过这个类的的作用是帮助解释其他的类的信息
- 使用注解进行将另外一个类的属性,类,方法,注解等解释成可执行的语句
- 在java中的类的反射机制中,均提供了对应的反射注解的配置,反射进行操作注解类的属性
Annotation的格式:
- 注解是以“@注释名”在代码中存在的,还可以添加一些参数值,例如:@SuppressWarnings(value="unchecked")。
- 注解中也是可以定义类型的,基本数据类型和引用数据类型都可以进行定义
Annotation的使用:
- 可以附加在package, class, method, field等上面,相当于给它们添加了额外的辅助信息,我们可以通过反射机制编程实现对这些元数据的访问。
内置注解:
- @Override 定义在java.lang.Override中,此注释只适用于修辞方法,表示一个方法声明打算重写超类中的另一个方法声明。
- @Deprecated 定义在java.lang.Deprecated中,此注释可用于修辞方法、属性、类,表示不鼓励程序员使用这样的元素,通常是因为它很危险或存在更好的选择。
- @SuppressWarnings 定义在java.lang.SuppressWarnings中,用来抑制编译时的警告信息
- @SuppressWarnings("unchecked")
– @SuppressWarnings(value={"unchecked", "deprecation"})
自定义注解:
- 使用@interface自定义注解时,自动继承了java.lang.annotation.Annotation接口
- public @interface 注解名 {定义体}
- 注解属性的定义是不一样的,在注解中每个属性就是一个方法
- 定义每个方法实际上就是声明了一个配置参数
- 方法的名称就是参数的名称
- 返回值类型就是参数的类型(类型的确定---基本数据类型,Class,String ,enum)
- 可以通过default来进行声明参数的默认值,也可以不进行声明
- 如果只有一个参数成员,一般参数名为value, 定义如下所示
- public @interface SxtField {
String columnName();
String type();
int length();
}
- 注意:注解元素必须要有值对应信息的使用上:我们定义注解元素时,经常使用空字符串、0作为默认值。
也经常使用负数(比如:-1)表示不存在的含义
元注解:
- 元注解的作用就是负责注解其他注解。 Java定义了4个标准的meta-annotation类型,它们被用来提供对其它 annotation类型作说明。
- 元注解中都存在一个属性 固定的属性 value() 属性,里面包含的是属性的枚举值
-@Target @Retention @Documented @ Inherited
- @Target:
- 作用:用于描述注解的使用范围---->注解可以在什么地方进行使用
- 注解中的确定通过枚举进行实现
- 默认值就是不限制,也就是什么类型和位置都能进行配置
- Type修饰的是 :类,接口,注解 ,枚举类
- @Target(value=ElementType.TYPE)
- @Retention
- 作用:表示需要在什么级别保存该注释信息,用于描述注解的生命周期,从编译到运行到销毁
- 默认的就是在.java(编译阶段是存在的),默认的权限
- Source Class RunTime (源码阶段 字节码阶段 运行时阶段)
- 程序的运行阶段 :进行读取定义的注解,所以注解的存在阶段是 Runtime阶段
- 自定义注解的存在,需要元注解的修饰,也就四个元注解的修饰范围
- 自定义注解都可以通过default关键字声明默认值,如果注解属性没有给值,那么注解使用默认值
使用反射机制读取注解信息:
- 注解是通过编写java类来进行编译操作的,注解进行解释程序的运行
- 后期遇到的是 ORM思想 (对象关系映射 Object Relationship Mapping)
try {
Class clazz = Class.forName("com.bjsxt.test.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执行这个SQL,在数
据库中生成相关的表
} catch (Exception e) {
e.printStackTrace();
}
类的加载:
为什么研究类的加载全过程?
- 有助于了解JVM的运行过程
- 更深入的了解 java动态性(解热部署,动态加载)提高程序的灵活性
类加载全过程:
- JVM将class文件加载到内存,并对数据进行校验,解析,初始化,最终形成JVM可以直接使用的Java类型的过程
-
- 1. 加载:将class文件字节码内容(二进制数据)加载到内存中,并将这些静态数据转换成方法去中的运行时数据结构,在堆中生成一个代表这个类的java.lang.Class对象,作为方法区数据的访问入口,能够进行对方法区的数据的访问
- 方法区存放的时类的全部信息,类的所有信息都存放在方法区中
- 加载的过程时通过类加载器进行加载的,这个过程一定需要加载器的参与
- 2. 链接: 加载完后形成了字节码二进制的文件,此时需要将信息进行一定的合并和处理并且送到jvm的运行状态中
- 验证:确保加载的类信息符合JVM的规范,没有安全方面的问题
- 准备:正式为类变量(静态资源)分配内存并且设定初始值的阶段,这些内存都将在方法区中进行分配
- 解析:将虚拟机常量池中的符号引用转换成直接引用的过程,指定的地址进行使用,比较具体的引用地址,比如将class指定为一个类的关键字等
- 3.初始化:初始化阶段是执行类构造器<clinit>()方法的过程。类构造器<clinit>()方法是由编译器自动收集
类中的所有类变量的赋值动作和静态语句块(static块)中的语句合并产生的。
- 初始化的时候进行给在类中的成员变量初始化,进行赋值的操作,能够保证类的初始化信息
- 当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先进行父类的初始化
- 虚拟机会保证一个类的()方法在多线程环境中被正确加锁和同步。
类加载的全部过程:
类的主动引用(一定会发生类的初始化):
new一个类的对象
– 调用类的静态成员(除了final常量)和静态方法
– 使用java.lang.reflect包的方法对类进行反射调用
– 当虚拟机启动,java Hello,则一定会初始化Hello类。说白了就是先启动main方法所在的类
– 当初始化一个类,如果其父类没有被初始化,则先会初始化他的父类
类的被动引用(不会发生类的初始化):
– 当访问一个静态域时,只有真正声明这个域的类才会被初始化
• 通过子类引用父类的静态变量,不会导致子类初始化
– 通过数组定义类引用,不会触发此类的初始化
– 引用常量不会触发此类的初始化(常量在编译阶段就存入调用类的常量池中了)
类加载器: 加载字节码文件和相关的资源信息
- 作用:
- 将class文件字节码内容加载到内存中,并将这些静态数据转换成方法
区中的运行时数据结构,在堆中生成一个代表这个类的java.lang.Class
对象,作为方法区类数据的访问入口。
- 类缓存
- 标准的Java SE类加载器可以按要求查找类,但一旦某个类被加载到类加载
器中,它将维持加载(缓存)一段时间。不过,JVM垃圾收集器可以回收
这些Class对象。
- java.lang.ClassLoader类的基本职责就是根据一个指定的类的名称,
找到或者生成其对应的字节代码,然后从这些字节代码中定义出一个
Java 类,即 java.lang.Class类的一个实例。
- 除此之外,ClassLoader还负责加载 Java 应用所需的资源,如图像文件和配置文件等。
-
类加载器的层次结构
• 引导类加载器(bootstrap class loader) – 它用来加载 Java 的核心库(JAVA_HOME/jre/lib/rt.jar,或sun.boot.class.path路径下的 内容),是用原生代码来实现的,并不继承自 java.lang.ClassLoader。 – 加载扩展类和应用程序类加载器。并指定他们的父类加载器。 • 扩展类加载器(extensions class loader) – 用来加载 Java 的扩展库(JAVA_HOME/jre/ext/*.jar,或java.ext.dirs路径下的内容) 。 Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。 – 由sun.misc.Launcher$ExtClassLoader实现 • 应用程序类加载器(application class loader) – 它根据 Java 应用的类路径(classpath, java.class.path 路径下的内容)来加载 Java 类。 一般来说,Java 应用的类都是由它来完成加载的。 – 由sun.misc.Launcher$AppClassLoader实现 • 自定义类加载器 – 开发人员可以通过继承 java.lang.ClassLoader类的方式 实现自己的类加载器,以满足一些特殊的需求。
类加载器的代理模式:
- 交给其他加载器来加载指定的类
- 双亲委托机制
就是某个特定的类加载器在接到加载类的请求时,首先将加载任务委
托给父类加载器,依次追溯,直到最高的爷爷辈的,如果父类加载器
可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载
任务时,才自己去加载。
- 双亲委托机制是为了保证 Java 核心库的类型安全。
- 这种机制就保证不会出现用户自己能定义java.lang.Object类的情况
- 由于类库比较多的情况,首先确保用户建立的类不和库中建立的类有冲突,造成自己的类无法进行加载的下娘
- 类加载器除了用于加载类,也是安全的最基本的屏障。
- 如果一个类被两个类加载器进行加载的化,JVM中不会认为是同一个类的存在,hashcode的值是不一样的
-
通常当你需要动态加载资源的时候 , 你至少有三个 ClassLoader 可以选择 :
–
1.系统类加载器或叫作应用类加载器 (system classloader or application classloader)
– 2.当前类加载器
– 3.当前线程类加载器
• 当前线程类加载器是为了抛弃双亲委派加载链模式。
– 每个线程都有一个关联的上下文类加载器。如果你使用new Thread()方式生成新的线程,新线程将继承其父线程的上
下文类加载器。如果程序对线程上下文类加载器没有任何改动的话,程序中所有的线程将都使用系统类加载器作为上
下文类加载器。
• Thread.currentThread().getContextClassLoader()
动态编译:
- jdk1.6出现动态编译的机制:
- 出现场景:服务器动态加载某些文件进行表一,在线系统的开发,上传系统
动态编译的两种做法:
- 1. 通过Runtime 调用javac ,启动新的线程区操作
- Runtime run = Runtime.getRuntime();
Process process = run.exec("javac -cp d:/myjava/ HelloWorld.java");
- 2. 通过JavaCompiler动态编译
public static int compileFile(String sourceFile){
// 动态编译
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
int result = compiler.run(null, null, null,sourceFile);
System.out.println(result==0?" 编译成功 ":" 编译失败 ");
return result;
}
- • 第一个参数: 为java编译器提供参数
• 第二个参数: 得到 Java 编译器的输出信息
• 第三个参数: 接收编译器的 错误信息
• 第四个参数: 可变参数(是一个String数组)能传入一个或多个 Java 源文件
• 返回值: 0表示编译成功,非0表示编译失败
类的结构信息:
class文件只会被加载一次
类加载的过程,jvm的运行的过程,类的加载机制
字节码的来源
字节码本身是一个字节数组
可以是多种信息的来源,是非常的丰富的结果,是一个字节的数组
运行的时候使用的是二进制文件的解析过程
常量的符号引用准换成直接引用:指定的地址进行使用,比较具体的引用地址
类的初始化的静态域的对象
一个方法对应一个栈帧,存在于栈结构中
先加载最后再使用结构的运行的结构的作用,调用相关的构造方法
类加载的次数只有一次的过程
类被使用就会被加载
主动引用
被动引用
核心类的加载机制,为了安全机制进行设定相关的加载顺序
原生的加载器是引导类的加载类,
C++程序是最快的结构
同一个类同一个加载器确定的时候才是认同时同一个类的加载机制
加密和解密操作的是相互进行对应的,如果加密和解密的机制不一样的话,那么加密和解密的结果僵尸不一样的
堆中生成的class 字节码对象的生成时通过生成相关的字节码对象进行操作的,因为自字节码对象的本省就是字节码的,操作的自然也就是字节码的对象
注解的案例:
package com.bjsxt.test.annotation;
@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;
}
}
package com.bjsxt.test.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 SxtTable {
String value();
}
执行效率的比较;反射关系中的区别机制
package com.bjsxt.test;
import java.lang.reflect.Method;
import com.bjsxt.test.bean.User;
/**
* 通过跳过安全检查,提高反射效率
* 三种执行方法的效率差异比较
*
* @author 尚学堂高淇 www.sxt.cn
*
*/
public class Demo06 {
public static void test01(){
User u = new User();
long startTime = System.currentTimeMillis();
for (int i = 0; i < 1000000000L; i++) {
u.getUname();
}
long endTime = System.currentTimeMillis();
System.out.println("普通方法调用,执行10亿次,耗时:"+(endTime-startTime)+"ms");
}
public static void test02() throws Exception{
User u = new User();
Class clazz = u.getClass();
Method m = clazz.getDeclaredMethod("getUname", null);
// m.setAccessible(true);
long startTime = System.currentTimeMillis();
for (int i = 0; i < 1000000000L; i++) {
m.invoke(u, null);
}
long endTime = System.currentTimeMillis();
System.out.println("反射动态方法调用,执行10亿次,耗时:"+(endTime-startTime)+"ms");
}
public static void test03() throws Exception{
User u = new User();
Class clazz = u.getClass();
Method m = clazz.getDeclaredMethod("getUname", null);
m.setAccessible(true); //不需要执行访问安全检查
long startTime = System.currentTimeMillis();
for (int i = 0; i < 1000000000L; i++) {
m.invoke(u, null);
}
long endTime = System.currentTimeMillis();
System.out.println("反射动态方法调用,跳过安全检查,执行10亿次,耗时:"+(endTime-startTime)+"ms");
}
public static void main(String[] args) throws Exception {
test01();
test02();
test03();
}
}