反射是Java利用JVM底层的一种机制,目的是提高耦合和扩展性。
内存的作用:内存(Memory)是计算机的重要部件,也称内存储器和主存储器,它用于暂时存放CPU中的运算数据,以及与硬盘等外部存储器交换的数据。它是外存与CPU进行沟通的桥梁,计算机中所有程序的运行都在内存中进行,内存性能的强弱影响计算机整体发挥的水平。只要计算机开始运行,操作系统就会把需要运算的数据从内存调到CPU中进行运算,当运算完成,CPU将结果传送出来。
Java的运行机制:在Java运行时,会分为编译、加载两个阶段:
名称 | 长度 | 功能 |
---|---|---|
Magic | 4个字节 | 判定是否为Java文件 |
Version: Major_version/Minor_version | 分别2个字节 | 主次版本号,判断jvm是否有能力处理该文件 |
Constantpool: Constantpool_count/Constantpool | 不固定 | 常量池,包含类和接口的相关常量 |
Access_flags | 2个字节 | 指明类型,类或者接口、抽象或者具体。公共、final等修饰符 |
This_class | 2个字节 | 索引,指向常量池中属于类的常量 |
Super_class | 2个字节 | 指向父类全限定名 |
Interface: Interface_count/Interfaces | 不固定 | 该类实现接口的数量/常量池引用 |
Field: FieldsCount/Fields | 不固定 | 字段数量和信息表。描述字段的类型、描述符等 |
Method: Methods_count/Methods | 不固定 | 方法数量和方法本身。每一个方法都有一个Method_info表,记录Method的属性 |
Attribute: Attributes_count/Attributes | 不固定 | 属性数量和属性本身 |
注意:加载——将.class文件从磁盘移动到内存;装载——包含加载、校验、准备,将.class转化为Class类
1. 预先装载
2. 按需装载
当需要某个类时,JVM才会去动态装载它。
装载条件:当使用该类的a. 静态方法;b. 静态属性;c. 构造方法。
注意:类中的静态常量属性处于常量池,所以没必要初始化该类;构造方法是一种特殊的静态方法,所以当创建该类的实例时就会装载该类。
按需装载流程:
当创建一个Java类的实例时,必须先将该类加载到内存中。JVM使用类加载器来加载类。Java加载器在Java核心和CLASSPATH环境中的所有类查找类。如果需要的类找不到,则会抛出ClassNotFoundException异常。
Java类加载器分为:bootstrap类加载器、extension类加载器和system类加载器。顺序为结构的顶端到底层。
JVM使用类加载器决定于委派模型(delegation model),出于安全原因,每次类都需要加载,system类加载器首先调用。但是,它不会马上加载类。相反,它委派该任务给它的父类extension类加载器,然后extension类加载器也罢任务委派给它的父类bootstrap类加载器。因此,bootstrap类加载器总是先加载类。如果bootstrap类加载器不能找到所需要的类,则extension类加载器会尝试加载,如果扩展类加载器也失效,system类加载器将执行任务。如果最后系统类加载器找不到类,则会抛出一个ClassNotFoundException异常。
Java类加载器机制的优势在于可以通过扩展java.lang.ClassLoarder抽象类来扩展自己的.class文件。自定义自己的类加载器的好处在于:
Class类的对象用来表示运行时类或者接口的信息。Java中枚举是一种类、注解是一种接口,数据也被看做一个类。这些类的信息,在运行时都由Class类来描述。对应数组而言,具有相同元素类型和维数的数组共享一个Class对象。可以通过Class对象获取类名、父类等信息,并可通过Class类来获取该类的属性、方法、构造方法、包等。
通过实例获取的Class类可以通过反射得到该实例对应的属性值,以及注解等信息。
.class
Class<Person> clazz1 = Person.class;
getClass()方法
Person person1 = new Person();
Class<Person> clazz2 = person1.getClass();
forName(className)方法
String className = "xxx.xxx.reflect.packageName.Person";
Class<?> clazz3 = Class.forName(className);
方法体 | 返回类型 | 说明 |
---|---|---|
getConstructor(Class…parameterTypes) | Constructor | 其中参数为指定构造参数中类型的class数组,该方法只能获取public构造方法 |
getConstructors() | Constructor[] | 获取指定类的public构造函数,若没有则返回长度为0的Construct数组 |
getDeclaredConstructor(Class…parameterTypes) | Constructor | 可获取所有的构造方法的对象 |
getDeclaredConstructors() | Constructor[] | 同理 |
这里补充两个知识点:
final方法
使用final的方法不需要再扩展,不能被子类重写。同时允许编译器将素有对此方法的调用转化为inline(行内,来源于C++)调用机制。当调用final方法时,直接将方法主体插入到调用处,并非进行例行的方法调用,例如保存断点、压栈等。这样可能会使得程序效率有所提高,然后方法主体庞大时,或在多处调用此方法时,那么调用主体代码便会迅速膨胀,反而会降低效率。
native方法
Native Method就是一个Java调用非Java代码的接口。该类方法的实现由非Java语言实现,例如C。这个特性其他的编程语言都有这一机制,例如在C++中,可以用extern ”C”告知C++编译器去调用一个C的函数。在定义一个Native Method时,并不提供实现体(有些定义一个Java Interface),因为其实现体是由非Java语言在外面实现的。
获取方法:
方法体 | 返回值 | 说明 |
---|---|---|
getMethod(String name, Class…parameterTypes) | Method | 同理 |
getMethods() | Method[] | 同理 |
getDeclaredMethod(String name, Class…parameterTypes) | Method | 同理 |
getDeclaredMethods() | Method[] | 同理 |
方法体 | 返回值 | 说明 |
---|---|---|
getField(String name) | Field | 同理 |
getFields() | Field[] | 同理 |
getDeclaredField(String name) | Field | 同理 |
getDeclaredFields() | Field[] | 同理 |
运行时类型识别:在程序运行时,动态地识别对象和类的信息。
识别方法:
关键字instanceof
二元运算符,前者是对象,后者是类。用于判断一个对象是否是该类或者该类的子类的实例。
Class.isInstanceof()
效果同上,但是该方法更适用于遍历。
Java反射机制是指在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法,修改它的任意属性,这种动态获取的信息以及动态调用对象成员的功能称为Java语言的反射机制。
注意:以下前4点仅适用于共有方法或共有属性。
1. 显式加载指定类
使用Class类中forName(String name)方法
2. 通过反射实例化类
Class类的newInstance()方法
该方法会调用该类的无参构造进行实例化。(警告:该方式在Java1.9版本中已被弃用)
Constructor类的newInstance()方法
该方法需要在Class中通过指定参数类型获取指定的构造方法,相比较第一个方法更明确化,明确调用的是哪一个构造器,而不是直接采用默认的无参构造器。
3. 通过反射执行方法
使用Method类的invoke(Object obj, Object… args)方法。
下面是invoke()方法的源码:
public Object invoke(Object obj, Object... args)
throws IllegalAccessException,
IllegalArgumentException,
InvocationTargetException
obj——从中调用底层方法的对象(简单的说就是调用谁的方法用谁的对象)。
args——用于方法调用的参数。
4. 通过反射修改属性
使用Field的set(Object obj, Object value)方法。
obj——表示要修改的属性对象。
value——表示修改后的值。
该方法用于动态地给属性赋值。
5. 修改访问权限
使用目标对象的setAccessible(boolean flag)方法。
该方法来源于AccessibleObject类,Field类、Method类和Constructor类都继承了AccessibleObject,都允许被setAccessible()方法从私有设置为共有。
6. 反射中的各种异常分析
ClassNotFoundException
抛出该异常的原因是未在命名空间内找到指定的类,有可能是因为类名错误,或者是类文件不存在。抛出该异常时请检查指定的类是否存在,或检查类名是否正确是否完整(类名英文全类名即简单类名加上完整包名)
SecurityException
该异常是由安全管理器抛出的异常,指示存在安全侵犯。例如修改不允许修改的accessible标志时,会该异常。
NoSuchMethodException
无法找到某一特定方法时,抛出该异常。抛出异常时,可打印指定类的所有字段名,进行比较检查。
NoSuchFieldException
无法找到指定字段时,抛出该异常。抛出异常时,可打印指定类的所有字段名,进行比较检查。
IllegalArgumentException
抛出的异常表明向方法传递了一个不合法或者不正确的函数。可获取Method对象的参数,进行比较检查。
InstantiationException
当应用程序试图使用Class类中的newInstance方法创建一个类的实例,而指定的类对象无法被实例化,抛出该异常。
IllegalAccessException
当应用程序试图反射性地创建一个实例(而不是一个数组)、设置或获取一个字段,或者一个方法,但当前正在指定的方法无法访问指定类、字段、方法或构造方法的定义时,抛出该异常。例如该方法本身为私有属性,试图通过反射得到该方法被视为非法。
1. 概念
代理模式是指为目标对象提供一个代理对象,外部对目标对象的访问,通过代理委托进行,以达到控制访问的目的。为保持行为的一致性,代理类通常与委托类对象的直接访问,也可以很好地隐藏和保护委托类对象,同时也为实施不同控制策略预留了空间,从而在设计上获得了更大的灵活性。
2. 作用
为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个客户不想或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
3. 使用方法
代理模式涉及三个角色:
使用时创建对象后,再创建其代理对象,然后调用代理对象的方法即可。
4. 使用示例
public interface Speakable() {
public void speak(String message);
}
public class Person implements Speakable {
@Override
public void speak(String message) {
System.out.println("Speak: " + message);
}
}
public class PersonProxy implements Speakable {
private Person person;
public PersonProxy(Person person) {
this.person = person;
}
@Override
public void speak(String message) {
this.person.speak(message);
System.out.println("Running time: " + System.currentTimeMillis());
}
}
public class Test {
public static void main(String[] args) {
Person person = new Person();
PersonProxy proxy = new PersonProxy(person);
proxy.speak("Lesson one!");
}
}
运行效果:
Speak: Lesson one!
Running time: 1234567
5. 评价
代理模式的确解决了很多问题,但同时也给我们增加了一些负担,因为必须为委托类维护一个代理,不易管理而且增加了代码量。Java的动态代理机制的思想则更加先进一步。
1. 概念
利用Java的反射机制,在程序运行时,创建目标对象的代理对象,并对目标对像中的方法进行功能性增强的技术。代理对象会负责将所有的方法调用分派到委托对象上反射执行,在分派执行的过程中,开发人员还可以按需调整委托类对象及其功能,这时一套非常灵活有弹性的代理框架。
2. 使用示例
注意,在此基础上了解使用方法即可,底层其实更为复杂。
public class DynamicProxy{
public static Speakable createSpeakableProxy(Speakable speakable){
Speakable speakableProxy = (Speakable) Proxy.newProxyInstance(
DynamicProxy.class.getClassLoader(),//Using this class loader to load the proxy.
new Class[]{Speakable.class},//Interface class files which the proxy need to implements.
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
/*functions*/
Object result = method.invoke(speakable, args);
/*functions*/
return result;
}
});
return speakableProxy;
}
}
public class Test {
public static void main(String[] args) {
Person person = new Person();
Speakable personProxy = DynamicProxy.createSpeakableProxy(person);
personProxy.speak("Test message");
}
}
动态生成的代理类特点:
实际上,每个动态代理实例都会关联一个调用处理器对象,可以通过Proxy提供的静态方法getInvocationHandler去获得代理类实例的调用处理器对象。在代理类实例上调用其代理的接口中所声明的方法时,这些方法最终会由调用处理器的invoke方法执行。当代理的一组接口有重复声明的方法且该方法被调用时,代理类总是从排在最前面的接口中获取方法对象并分派给调用处理器,而无论代理类实例是否正在以该接口(或继承于该接口的某子接口)的形式被外部引用,因为在代理类内部无法区分其当前的被引用类型。
至于被代理的接口,首先,不能有重复的接口,以避免动态代理类代码生成时的编译错误。其次,这些接口对于类加载器必须可见,否则类加载器将无法链接他们,将会导致类定义失败。再次,需被代理的所有非public的接口必须在同一个包中,否则代理类生成也会失败。最后,接口的数目不能超过65535,这时JVM设定的限制。
AOP为Aspect Oriented Programming的缩写,意为“面向方面编程”,是可以通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的种技术。AOP实际是GoF设计模式的延续,设计模式孜孜不倦追求的是调用者和被调用者之间的解耦,AOP可以说也是这种目标的一种实现。 其主要的功能是:日志记录,性能统计,安全控制,事务处理,异常处理等。
如果说面向对象编程是关注将需求功能划分为不同的并且相对独立、封装良好的类,并让它们有着属于自己的行为,依靠继承和多态等来定义彼此的关系的话;那么面向方面编程则是希望能够将通用需求功能从不相关的类中分离出来,能够使得很多类共享一个行为,一且发生变化,不必修改很多类,而只需要修改这个行为即可。
面向方面编程是一个令人兴奋不已的新模式。就开发软件系统而言,它的影响力必将会和有着数十年应用历史的面向对象编程一样巨大。 面向方面编程和面向对象编程不但不是互相竞争的技术而且彼此还是很好的互补。面向对象编程主要用于为同一对象层次的公用行为建模。它的弱点是将公共行为应用于多个无关对象模型之间。而这恰恰是面向方面编程适合的地方。有了AOP,我们可以定义交叉的关系,并将这些关系应用于跨模块的、彼此不同的对象模型。AOP同时还可以让我们层次化功能性而不是嵌人功能性,从而使得代码有更好的可读性和易于维护。
在Spring中提供了面向切面编程的丰富支持,允许通过分离应用的业务逻辑与系统级服务(例如审计(auditing)和事务(transaction)管理)进行内聚性的开发。应用对象只实现它们应该做的一完成业 务逻辑仅此而已。 它们并不负责(甚至是意识)其他的系统级关注点,例如日志或事务支持。
待定加入。若该文本发布后仍未被修改,则表示未安排加入。请提示笔者加入内容或者删除这段文字。