反射技术:其实就是动态加载一个指定的类,并获取该类中的所有的内容。即 将字节
码文件(class文件)封装成对象(Class对象),并将字节码文件(class文件所对应的类)中的内容(方法和属性)都封装成对象,
这样 只要有class文件就可操作class文件所对应的成员。
反射机制是在运行状态中 对于任意一个类(class文件),都能够知道这个类的所有属性和方法;
对于任意一个对象,都能够调用它的任意一个方法和属性,
这种状态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制
简单说:反射技术可以对一个类进行解剖。 反射的好处:大大的增强了程序的扩展性。
一、如何得到各个字节码对应的实例对象( Class类型)
1. 类名.class,例如,System.class
2. 对象.getClass(),例如,new Date().getClass()
3. Class.forName("类名"),例如,Class.forName("java.util.Date");
反射的基本步骤:
1、获得Class对象,就是获取到指定的名称的字节码文件对象。
2、 实例化对象,获得类的属性、方法或构造函数。
3、 访问属性、调用方法、调用构造函数创建对象。
获取这个Class对象,有三种方式:
1:通过每个对象都具备的方法getClass来获取。弊端:必须要创建该类对象,才可以调用getClass方法。
2:每一个数据类型(基本数据类型和引用数据类型)都有一个静态的属性class。弊端:必须要先明确该类。
前两种方式不利于程序的扩展,因为都需要在程序使用具体的类来完成。
3:使用的Class类中的方法,静态的forName方法。
指定什么类名,就获取什么类字节码文件对象,这种方式的扩展性最强,只要将类名的字符串传入即可。
反射就是 把java类中的各种成分映射成相应的java类。例如,一个java类中用一个class类的对象来表示一个类中的组成部分:成员变量,方法,构造函数。包等信息也用一个个的java类来表示。,,表示java类的Class类显然要提供一系列的方法,来获得其中的变量,方法,构造方法,修饰符,包等信息,这些信息就是用相应类的实例对象来表示,它们是Field,Method。Contructor,package等等
基本数据类型的class实例 基本的Java 8类型(boolean、byte、char、short、int、long、float 和 double)和关键字 void也表示为 Class 对象
int.class ==Integer.TYPE 表示基本类型 int 的Class 实例
总之只要是在源程序中出现的类型,都有各自的Class实例对象,如int[] void
Class<T>类java.lang.Class<T> 主要方法
boolean isPrimitive() 判定指定的 Class 对象是否表示一个基本类型。
boolean isArray() 判定此 Class 对象是否表示一个数组类。
boolean isInterface() 判定指定的 Class 对象是否表示一个接口类型。
boolean isEnum() 当且仅当该类声明为源代码中的枚举时返回 true。
T newInstance() 创建此 Class对象所表示的类的一个新实例(空参数)。
boolean isAnnotation() 如果此 Class 对象表示一个注释类型则返回 true。
String getName() 返回此 Class 对象所表示的实体(类、接口、数组类、基本类型或 void)名称。
Package getPackage() 获取此类的包。
Method getMethod(String方法名, Class<?>...参数) 返回一个public修饰的指定Method 对象。
Method[] getMethods() 返回一个包含某些 Method 对象的数组,
Method getDeclaredMethod(String name,Class<?>...parameterTypes) 可返回一个指定的私有Method 对象
Field getField(Stringname) 返回一个public修饰的指定的Field 对象。
Field[] getFields() 返回一个包含某些 Field 对象的数组,
Field getDeclaredField(String name) 可返回一个私有 Field 对象
Field[] getDeclaredFields() 返回指定的 Class 对象表示类型的所有字段包括公有私有
Annotation getAnnotation(Class<A>annotationClass) 返回 指定的注释对象。
Annotation[] getAnnotations() 返回此元素上存在的所有注释。
Constructor<T>getConstructor(Class<?>... parameterTypes) parameterTypes方法形参
返回一个指定个数参数的 构造函数Constructor 对象,
Class<?>[] getInterfaces() 确定此对象所表示的类或接口实现的接口。
反射概念:
反射就是把Java类中的各种成分映射成相应的java类。
一个类中的每个成员都可以用相应的反射API类的一个实例对象来表示,通过调用Class类的方法可以得到这些实例对象后,得到这些实例对象后有什么用呢?怎么用呢?这正是学习和应用反射的要点。
Constructor<T>类
Constructor 提供关于类的单个构造方法的信息以及对它的访问权限。
Constructor类代表某个类中的一个构造方法
l 得到某个类所有的构造方法:
Ø 例子:Constructor [] constructors=Class.forName("java.lang.String").getConstructors();
l 得到某一个构造方法:
Ø 例子: Constructor constructor = Class.forName(“java.lang.String”).getConstructor(StringBuffer.class);
//获得方法时要用到类型
Class<?>[]getParameterTypes() 返回该构造方法对象中所有形参的类型的class对象数组
创建实例对象:
Ø 通常方式:String str = new String(newStringBuffer("abc"));
Ø 反射方式: String str = (String)constructor.newInstance(newStringBuffer("abc"));
//调用获得的方法时要用到上面相同类型的实例对象
l Class.newInstance()方法:
Ø 例子:String obj =(String)Class.forName("java.lang.String").newInstance();
Ø 该方法内部先得到默认的构造方法,然后用该构造方法创建实例对象。
Ø 该方法内部的具体代码是怎样写的呢?用到了缓存机制来保存默认构造方法的实例对象。
Field
public final class Field extends AccessibleObject implementsMember
Field 提供有关类或接口的单个字段的信息,以及对它的动态访问权限。反射的字段可能是一个类(静态)字段或实例字段。
获取字段对象Field
Field getField(Stringname) 返回一个 Field 对象,它反映 指定公共成员字段。
Field[] getFields() 返回一个包含某些 Field 对象的数组,所有可访问公共字段。
Field getDeclaredField(Stringname) 返回一个 Field 对象,包括公有私有字段
Field[] getDeclaredFields() 返回 Field 对象的一个数组,包括公有私有字段
Class<?>getType() 返回一个 Class 对象,它标识了此 Field 对象所表示字段的声明类型。
-------------------------------------------------------
Field中的方法
Object get(Object obj) 返回指定对象上此 Field 表示的字段的值。如果该值是一个基本类型值,则自动将其包装在一个对象中。
byte getByte(Object obj) 获取一个静态或实例 byte 字段的值。只有get基本变量没有getString
void set(Object obj,Objectvalue)
将指定对象变量上此 Field 对象表示的字段设置为指定的新值。
void setInt(Object obj,int i) 将字段的值设置为指定对象上的一个 int 值。
通过Object get(Objectobj)方法获取该字段所属类的对象的该属性的值(字段即属性)
Object为该字段对象所表示的字段所属的类的对象 所以在获取File字段信息时需要先取到对象
2> 作业:将任意一个对象中的所有String类型的成员变量所对应的字符串内容中的"b" 改成"a"。
Method
public finalclass Method extends AccessibleObject implementsGenericDeclaration,Member
Method 提供关于类或接口上单独某个方法(以及如何访问该方法)的信息。所反映的方法可能是类方法或实例方法(包括抽象方法)。
获取方法对象Mathod
Method getMethod(String name, Class<?>... parameterTypes)
返回一个 Method 对象,方法参数为class对象(int.class, String.class) 没有形参的方法则parameterTypes为null
Method[] getMethods()
返回一个包含某些 Method 对象的数组,这些对象反映此 Class 对象所表示的类或接口(包括那些由该类或接口声明的以及从超类和超接口继承的那些的类或接口)的公共 member 方法。
Mathod的调用方法
Objectinvoke(Objectobj, Object... args)
对带有指定参数args的指定对象调用由此 Method 对象表示的底层方法。
如果底层方法是静态的,那么可以忽略指定的 obj 参数。该参数可以为 null。
如果方法是非静态的 obj就是该方法所属的类的对象
如果底层方法所需的形参数为 0,则所提供的 args 数组长度可以为 0 或 null。
Class<?>[]getParameterTypes() 返回该方法的所有形参类型的class对象数组
Class<?>getReturnType() 返回该方法 的返回值类型的class对象
boolean equals(Object obj) 将此 Method 与指定对象进行比较。
用反射方式执行某个类中的main方法String classStarting = args[0];
Method main = Class.forName(classStarting).getMethod("main", String[].class);
//main.invoke(null, new Object[]{new String[]{"111","2222","3333"}});
main.invoke(null, (Object)new String[]{"111","2222","3333"});
数组的反射
java.lang.reflect 类Array反射
数组是无法通过反射 获取到数组对象的 只能够判断 class对象是不是数组class类中的 isArray()判断该class对象是不是数组
但是可以通过反射 创建数组
public final class Array extends Object
Array 类提供了动态创建和访问 Java 数组的方法。方法全部是静态的
static Object get(Objectarray,intindex) 返回指定数组对象中指定下标索引处的值。
static int getLength(Object array) 以 int 形式返回指定数组对象的长度。
static void set(Object array, int index, Object value)
将指定数组对象中指定索引组件index处的值value设置为指定的新值。
static Object newInstance(Class<?> componentType, int... dimensions)
创建一个具有指定的组件类型和维度的新数组。
Class中有个方法可以获取 数组的类型的class对象
Class<?> getComponentType() 返回表示数组组件类型的 Class。
打印数组中各个元素,或者打印一个元素
private static void printObject(Object obj) {
// TODO Auto-generated method stub
Class clazz = obj.getClass();
if (clazz.isArray()) {
for (int i = 0; i < Array.getLength(obj); i++) {
System.out.println(Array.get(obj, i));
}
} else {
System.out.println(obj);
}
}
反射的作用à实现框架功能
l 框架与框架要解决的核心问题
Ø 我做房子卖给用户住,由用户自己安装门窗和空调,我做的房子就是框架,用户需要使用我的框架,把门窗插入进我提供的框架中。框架与工具类有区别,工具类被用户的类调用,而框架则是调用用户提供的类。
l 框架要解决的核心问题
Ø 我在写框架(房子)时,你这个用户可能还在上小学,还不会写程序呢?我写的框架程序怎样能调用到你以后写的类(门窗)呢?
Ø 因为在写才程序时无法知道要被调用的类名,所以,在程序中无法直接new 某个类的实例对象了,而要用反射方式来做。
你做的门调用锁,锁是工具,你做的门被房子调用,房子是框架,房子和锁都是别人提供的。
实例:
Ø 先直接用new 语句创建ArrayList和HashSet的实例对象,演示用eclipse自动生成 ReflectPoint类的equals和hashcode方法,比较两个集合的运行结果差异。
Ø 然后改为采用配置文件加反射的方式创建ArrayList和HashSet的实例对象,比较观察运行结果差异。
<span style="white-space:pre"> </span>private static void byReflect() throws Exception {
InputStream is = reflectest2.class.getResourceAsStream("config.properties");//此相对路径 就是reflectest2.class所在的目录
Properties pro = new Properties();
//加载数据 到 properties集合
pro.load(is);
is.close();//养成好习惯 用完 记得释放 系统资源
String className = pro.getProperty("className");
Collection<Person> co = (Collection<Person>) Class.forName(className).newInstance();
Person p1 = new Person("zhansan",23);
Person p2 = new Person("maliu",24);
Person p3 = new Person("xiaoli",22);
co.add(p1);
co.add(p2);
co.add(p3);
co.add(p1);
System.out.println(co.size());
}
JavaBean是一种特殊的Java类,主要用于传递数据信息,这种java类中的方法主要用于访问私有的字段,且方法名符合某种命名规则。
如果要在两个模块之间传递多个信息,可以将这些信息封装到一个JavaBean中,这种JavaBean的实例对象通常称之为值对象(Value Object,简称VO)。这些信息在类中用私有字段来存储,如果读取或设置这些字段的值,则需要通过一些相应的方法来访问,大家觉得这些方法的名称叫什么好呢?JavaBean的属性是根据其中的setter和getter方法来确定的,而不是根据其中的成员变量。如果方法名为setId,中文意思即为设置id,至于你把它存到哪个变量上,用管吗?如果方法名为getId,中文意思即为获取id,至于你从哪个变量上取,用管吗?去掉set前缀,剩余部分就是属性名,如果剩余部分的第二个字母是小写的,则把剩余部分的首字母改成小的。
Ø setId()的属性名id
Ø isLast()的属性名last
Ø setCPU的属性名是什么?CPU
Ø getUPS的属性名是什么?UPS
总之,一个类被当作javaBean使用时,JavaBean的属性是根据方法名推断出来的,它根本看不到java类内部的成员变量。
一个符合JavaBean特点的类可以当作普通类一样进行使用,但把它当JavaBean用肯定需要带来一些额外的好处,我们才会去了解和应用JavaBean!好处如下:
Ø 在Java EE开发中,经常要使用到JavaBean。很多环境就要求按JavaBean方式进行操作,别人都这么用和要求这么做,那你就没什么挑选的余地!
Ø JDK中提供了对JavaBean进行操作的一些API,这套API就称为内省。如果要你自己去通过getX方法来访问私有的x,怎么做,有一定难度吧?用内省这套api操作JavaBean比用普通类的方式更方便。
通过 BeanUtils工具包 和propretyUtils 对bean的属性进行快速设置和获取
//BeanUtils工具包设置和获取年龄属性
StringpropertyAge = "age";//设置和获取年龄属性
BeanUtils.setProperty(p,propertyAge, 23);
System.out.println(BeanUtils.getProperty(p,propertyAge));
//通过propretyUtils 设置和获取name属性
String propertyName ="name";
PropertyUtils.setProperty(p,propertyName, "张无忌");
System.out.println(PropertyUtils.getProperty(p,propertyName));
// BeanUtils.setProperty可以用于设置获取二级属性
BeanUtils.setProperty(p, "birthday.time","1111");//BeanUtils可设置bean 二级属性 birthday.time
System.out.println(BeanUtils.getProperty(p,"birthday.time"));
//Date 也是一个类内部有setTime getTime()方法也可以当成 Bean 来操作 那么Person类内部还有一个Date类两者都可以以Bean类看待处理
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++什么是类加载器??
加载类的工具. 类加载器也是Java类
类加载器的作用??
当程序需要的某个类,那么需要通过类加载器把类的二进制加载到内存中.
l Java虚拟机中可以安装多个类加载器,系统默认三个主要类加载器,每个类负责加载特定位置的类:BootStrap, ExtClassLoader, AppClassLoader
l 类加载器也是Java类,因为其他是java类的类加载器本身也要被类加载器加载,显然必须有第一个类加载器不是java类,这正是BootStrap。
l Java虚拟机中的所有类装载器采用具有父子关系的树形结构进行组织,在实例化每个类装载器对象时,需要为其指定一个父级类装载器对象或者默认采用系统类装载器为其父级类加载。
ClassLoader loader = ClassLoaderTest.class.getClassLoader();
while (loader != null) {
System.out.println(loader.getClass().getName());
//获取父类加载器
loader = loader.getParent();
}
System.out.println(loader);
运行结果
sun.misc.Launcher$AppClassLoader
sun.misc.Launcher$ExtClassLoader
null
获取 当前的类加载器à> this.getClass().getClassLoader());
获取 当前的类加载器à> this.getClass().getClassLoader().getParent());
AppClassLoader的父加载器为ExtClassLoade但是扩展类加载器的父加载器则为NULL
类加载器的委托机制
每个ClassLoader本身只能分别加载特定位置和目录中的类,但它们可以委托其他的类装载器去加载类,这就是类加载器的委托模式。类装载器一级级委托到BootStrap类加载器,当BootStrap无法加载当前所要加载的类时,然后才一级级回退到子孙类装载器去进行真正的加载。当回退到最初的类装载器时,如果它自己也不能完成类的装载,那就应报告ClassNotFoundException异常。
JVM在加载类时 默认采用的是双亲委派机制,通俗的讲,就是某个特定的类加载器 在接到加载类的请求时 首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成加载任务,就成功返回,只有父类加载器无法完成此加载任务时,才自己去加载。
系统默认三个主要类加载器
启动(Bootstrap)类加载器:引导类装入器是用本地代码实现的类装入器,它负责将<Java_Runtime_Home>/lib 下面的类库加载到内存中,但是虚拟机出于安全等因素考虑,不会加载<Java_Runtime_Home >/lib存在的陌生类,开发者通过将要加载的非JDK自身的类放置到此目录下期待启动类加载器加载是不可能的。
标准扩展(Extension)类加载器:扩展类加载器是由 Sun 的ExtClassLoader(sun.misc.Launcher$ExtClassLoader) 实现的。它负责将 <Java_Runtime_Home >/lib/ext 或者由系统变量 java.ext.dir 指定位置中的类库加载到内存中。
系统(System)类加载器:系统类加载器是由 Sun 的AppClassLoader(sun.misc.Launcher$AppClassLoader)实现的。AppClassLoader是加载Classpath中配置的类库.
类加载双亲委派机制:
JVM在加载类时默认采用的是双亲委派机制。通俗的讲,就是某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。关于虚拟机默认的双亲委派机制,
ClassLoader主要是通过loadClass()来加载类的。loadClass具有委派父类加载 和自己加载的功能
通过分析loadClass()代码,类的加载过程
首先判断该类型是否已经被加载findLoadedClass(name);
如果没有被加载,就委托给父类加载或者委派给启动类加载器加载parent.loadClass(name, false);
如果不存在父类加载器,就检查是否是由启动类加载器加载的类,通过调用本地方法native
如果父类加载器和启动类加载器都不能完成加载任务,才调用自身的加载功能c = findClass(name);
可以得出类加载器的加载顺序:
BootstrapClassLoader-----ExtensionClassLoader----AppClassLoader---自定义的类加载器
编写自己的类加载器
类 ClassLoader 中有个方法 叫loadClass该方法具有委派父类加载 和自己加载的功能findClass()
自定义类加载器 要具有双亲委派机制 又能够有自己的加载方式 那么就需要继承ClassLoader,保留loadClass()然后复写findClass()自定义
类ClassLoader 主要方法
首先通过Class对象的 ClassLoader getClassLoader() 加载此对象所表示的类或接口的类加载器。
ClassLoadergetParent() 返回委托的父类加载器。
Class<?>loadClass(String name) 使用指定的二进制名称来加载类。
protected Class<?> findClass(String name) 使用指定的二进制名称查找类。
protected Class<?> defineClass(String name,byte[] b, int off, int len) 将一个 byte 数组转换为Class 类的实例。
一个类加载器的高级问题分析
================================================================================================
代理技术Proxy
代理的概念与作用
1> 程序中的代理
要为已存在的多个具有相同接口的目标类的各个方法增加一些系统功能,例如,异常处理、日志、计算方法的运行时间、事务管理、等等,你准备如何做?
编写一个与目标类具有相同接口的代理类,代理类的每个方法调用目标类的相同方法,并在调用方法时加上系统功能的代码。(参看原理图)
如果采用工厂模式和配置文件的方式进行管理,则不需要修改客户端程序,在配置文件中配置是使用目标类、还是代理类,这样以后很容易切换,譬如,想要日志功能时就配置代理类,否则配置目标类,这样,增加系统功能很容易,以后运行一段时间后,又想去掉系统功能也很容易。
代理框架图
动态代理技术
要为系统中的各种接口的类增加代理功能,那将需要太多的代理类,全部采用静态代理方式,将是一件非常麻烦的事情!写成百上千个代理类,是不是太累!
JVM可以在运行期动态生成出类的字节码,这种动态生成的类往往被用作代理类,即动态代理类。
JVM生成的动态类必须实现一个或多个接口,所以,JVM生成的动态类只能用作具有相同接口的目标类的代理。
CGLIB库可以动态生成一个类的子类,一个类的子类也可以用作该类的代理,所以,如果要为一个没有实现接口的类生成动态代理类,那么可以使用CGLIB库。
代理类的各个方法中通常除了要调用目标的相应方法和对外返回目标返回的结果外,还可以在代理方法中的如下四个位置加上系统功能代码:
1.在调用目标方法之前
2.在调用目标方法之后
3.在调用目标方法前后
4.在处理目标方法异常的catch块中
分析JVM动态生成的类
public class Proxy extends Object implementsSerializable
Proxy 提供用于创建动态代理类和实例的静态方法,它还是由这些方法创建的所有动态代理类的超类。
创建某一接口 Foo 的代理:
<pre name="code" class="java">InvocationHandler handler = new MyInvocationHandler(...);
Class proxyClass = Proxy.getProxyClass(
Foo.class.getClassLoader(), new Class[] { Foo.class });
Foo f = (Foo) proxyClass.
getConstructor(new Class[] { InvocationHandler.class }).
newInstance(new Object[] { handler });
或使用以下更简单的方法:
Foo f = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),
new Class[] { Foo.class },
handler);
动态代理类(以下简称为代理类)是一个实现在创建类时在运行时指定的接口列表的类,该类具有下面描述的行为。
代理接口 是代理类实现的一个接口。
代理实例 是代理类的一个实例。每个代理实例都有一个关联的调用处理程序 对象,它可以实现接口 InvocationHandler。通过其中一个代理接口的代理实例上的方法调用将被指派到实例的调用处理程序的Invoke 方法,并传递代理实例、识别调用方法的java.lang.reflect.Method 对象以及包含参数的 Object 类型的数组。
调用处理程序以适当的方式处理编码的方法调用,并且它返回的结果将作为代理实例上方法调用的结果返回。
创建动态类的实例对象及调用其方法
public interfaceInvocationHandler
InvocationHandler 是代理实例的调用处理程序 实现的接口。
每个代理实例都具有一个关联的调用处理程序。对代理实例调用方法时,将对方法调用进行编码并将其指派到它的调用处理程序的 invoke方法。
InvocationHandler对象的运行原理
怎样将目标类传进去?
直接在InvocationHandler实现类中创建目标类的实例对象,可以看运行效果和加入日志代码,但没有实际意义。
为InvocationHandler实现类注入目标类的实例对象,不能采用匿名内部类的形式了。
让匿名的InvocationHandler实现类访问外面方法中的目标类实例对象的final类型的引用变量。
在代理实例上的java.lang.Object 中声明的 hashCode、equals或 toString 方法的调用将按照与编码和指派接口方法调用相同的方式进行编码,并被指派到调用处理程序的 invoke 方法,如上所述。传递到 invoke 的 Method 对象的声明类是 java.lang.Object。代理类不重写从 java.lang.Object 继承的代理实例的其他公共方法,所以这些方法的调用行为与其对 java.lang.Object 实例的操作一样。
总结分析动态代理类的设计原理与结构
代理调用方法的过程 用户向代理商买电脑代理商通过特有渠道去电脑厂弄到电脑然后返回卖给用户
代理类Proxy必须实现一个接口就像代理商必须有所代理货物的货源
调用代理的某个方法那么代理会通过 InvocationHandler子类对象的invoke方法去调用代理所实现的接口对应的方法 Method.invoke(代理对象,参数)这个方法就是代理被调用的那个方法这个方法其实来自于接口 这个方法就是用户从代理商那买的货代理商给用户的货是从货源那来的
proxy2.add("zxx");//proxy2实现了Collection接口过程:调用代理的add()方法 代理对象建立 就会调用代理的构造方法,构造方法的形参就是InvocationHandler类,其构造方法内调用InvocationHandler子类对象 的invoke方法(参数为 代理对象proxy2 add zxx),,invoke方法内 通过调用 代理所实现的接口中对应的方法得到返回值 Object给代理的add()