我命由我不由天 --哪吒
建议101:注意Class类的特殊性
Java语言是先把Java源文件编译成后缀为class的字节码文件,然后通过classLoader机制把这些类加载到内存中。最后生成实例执行。
class类是Java的反射入口,只有在获得一个类的描述对象后才能动态的加载、调用,一般获得class对象的三种方法:
1、类属性加载:如String.class
2、对象的getClass方法:如new String.getClass()
3、forName方法加载:如Class.forName("java.lang.String")
获得class对象之后,就可以通过getAnnotation()获得注解,通过getMethods()获得方法,通过getConstructors()获得构造函数等。
建议102:适时选择getDeclaredXXX和getXXX
getMethod方法获得的是所有public访问级别的方法,包括从父类继承的方法。
getDeclaredMethod获得的是自身类的方法,包括公用的(public)方法、私有(private)方法,而且不受限于访问权限。
建议103:反射访问属性或方法时Accessible设置为true
Java中通过反射执行一个方法:获取一个方法对象,然后根据isAccessible返回值确定是否能够执行,如果返回false,则调用setAccessible(true),然后再调用invoke执行方法:
Method method= ...;
//检查是否可以访问
if(!method.isAccessible()){
method.setAccessible(true);
}
//执行方法
method.invoke(obj, args);
通过反射方法执行方法时,必须在invoke之前检查Accessible属性。
建议104:使用forName动态加载类文件
动态加载是指程序运行时加载需要的类库文件,对Java程序来说,雷哥类文件在启动时或首次初始化时会被加载到内存中,而反射则可以在运行时再决定是否需要加载。
动态加载的意义在于:加载一个类表示要初始化该类的static变量,特别是static代码块,在这里可以做大量的工作,比如注册,初始化环境等等。
对于动态加载最经典的应用就是数据库驱动程序的加载片段,代码如下:
//加载驱动
Class.forName("com.mysql..jdbc.Driver");
String url="jdbc:mysql://localhost:3306/db?user=&password=";
Connection conn =DriverManager.getConnection(url);
Statement stmt =conn.createStatement();
当程序动态加载该驱动时,也就是执行到Class.forName("com.mysql..jdbc.Driver")时,Driver类会被加载到内存中,于是static代码块开始执行,也就是把自己注册到DriverManager中。
forName只是把一个类加载到内存中,并不保证由此产生一个实例对象,也不会执行任何方法,之所以会初始化static代码,那是由类加载机制所决定的,而不是forName方法决定的。也就是说,如果没有static属性或static代码块,forName就是加载类,没有任何的执行行为。
总而言之,forName只是把一个类加载到内存中,然后初始化static代码。
建议105:动态加载不适合数组
建议106:动态代理可以使代理模式更加灵活
Java的反射框架提供了动态代理(Dynamic Proxy)机制,允许在运行期对目标类生成代理,避免重复开发。我们知道一个静态代理是通过主题角色(Proxy)和具体主题角色(Real Subject)共同实现主题角色(Subkect)的逻辑的,只是代理角色把相关的执行逻辑委托给了具体角色而已,一个简单的静态代理如下所示:
interface Subject {
// 定义一个方法
public void request();
}
// 具体主题角色
class RealSubject implements Subject {
// 实现方法
@Override
public void request() {
// 实现具体业务逻辑
}
}
class Proxy implements Subject {
// 要代理那个实现类
private Subject subject = null;
// 默认被代理者
public Proxy() {
subject = new RealSubject();
}
// 通过构造函数传递被代理者
public Proxy(Subject _subject) {
subject = _subject;
}
@Override
public void request() {
before();
subject.request();
after();
}
// 预处理
private void after() {
// doSomething
}
// 善后处理
private void before() {
// doSomething
}
}
这是一个简单的静态代理。Java还提供了java.lang.reflect.Proxy用于实现动态代理:只要提供一个抽象主题角色和具体主题角色,就可以动态实现其逻辑的,其实例代码如下:
interface Subject {
// 定义一个方法
public void request();
}
// 具体主题角色
class RealSubject implements Subject {
// 实现方法
@Override
public void request() {
// 实现具体业务逻辑
}
}
class SubjectHandler implements InvocationHandler {
// 被代理的对象
private Subject subject;
public SubjectHandler(Subject _subject) {
subject = _subject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
// 预处理
System.out.println("预处理...");
//直接调用被代理的方法
Object obj = method.invoke(subject, args);
// 后处理
System.out.println("后处理...");
return obj;
}
}
注意这里没有代理主题角色,取而代之的是SubjectHandler作为主要的逻辑委托处理,其中invoke方法是接口InvocationHandler定义必须实现的,它完成了对真实方法的调用。
通过InvocationHandler接口的实现类来实现,所有的方法都是有该Handler进行处理的,即所有被代理的方法都是由InvocationHandler接管实际的处理任务。
代码如下:
public static void main(String[] args) {
//具体主题角色,也就是被代理类
Subject subject = new RealSubject();
//代理实例的处理Handler
InvocationHandler handler =new SubjectHandler(subject);
//当前加载器
ClassLoader cl = subject.getClass().getClassLoader();
//动态代理
Subject proxy = (Subject) Proxy.newProxyInstance(cl,subject.getClass().getInterfaces(),handler);
//执行具体主题角色方法
proxy.request();
}
此时实现了不用显示创建代理类即实现代理的功能,例如可以在被代理的角色执行前进行权限判断,或者执行后进行数据校验。
动态代理很容易实现通用的代理类,只要在InvocationHandler的invoke方法中读取持久化的数据即可实现,而且还能实现动态切入的效果。
建议107:使用反射增加修饰模式的普适性
装饰模式(Decorator Pattern)的定义是“动态的给一个对象添加一些额外的职责。就增加功能来说,装饰模式相比于生成子类更加灵活“,不过,使用Java的动态代理也可以实现修饰模式的效果,而且其灵活性、适应性会更强。
我们以卡通片《猫和老鼠》(Tom and Jerry)为例,看看如何包装小Jerry让它更强大。首先定义Jerry的类:老鼠(Rat类),代码如下:
interface Animal{
public void doStuff();
}
class Rat implements Animal{
@Override
public void doStuff() {
System.out.println("Jerry will play with Tom ......");
}
}
接下来,我们要给Jerry增加一些能力,比如飞行,钻地等能力,当然使用继承也很容易实现,但我们这里只是临时的为Rat类增加这些能力,使用装饰模式更符合此处的场景,首先定义装饰类,代码如下:
//定义某种能力
interface Feature{
//加载特性
public void load();
}
//飞行能力
class FlyFeature implements Feature{
@Override
public void load() {
System.out.println("增加一对翅膀...");
}
}
//钻地能力
class DigFeature implements Feature{
@Override
public void load() {
System.out.println("增加钻地能力...");
}
}
此处定义了两种能力:一种是飞行,另一种是钻地,我们如果把这两种属性赋予到Jerry身上,那就需要一个包装动作类了,代码如下:
class DecorateAnimal implements Animal {
// 被包装的动物
private Animal animal;
// 使用哪一个包装器
private Class extends Feature> clz;
public DecorateAnimal(Animal _animal, Class extends Feature> _clz) {
animal = _animal;
clz = _clz;
}
@Override
public void doStuff() {
InvocationHandler handler = new InvocationHandler() {
// 具体包装行为
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
Object obj = null;
if (Modifier.isPublic(method.getModifiers())) {
obj = method.invoke(clz.newInstance(), args);
}
animal.doStuff();
return obj;
}
};
//当前加载器
ClassLoader cl = getClass().getClassLoader();
//动态代理,又handler决定如何包装
Feature proxy = (Feature) Proxy.newProxyInstance(cl, clz.getInterfaces(), handler);
proxy.load();
}
}
注意看doStuff方法,一个修饰类型必然是抽象构建(Component)的子类型,它必须实现doStuff方法,此处的doStuff方法委托了动态代理执行,并且在动态代理的控制器Handler中还设置了决定修饰方式和行为的条件(即代码中InvocationHandler匿名类中的if判断语句),当然,此处也可以通过读取持久化数据的方式进行判断,这样就更加灵活了。
编写客户端进行调用了,代码如下:
public static void main(String[] args) {
//定义Jerry这只老鼠
Animal jerry = new Rat();
//为Jerry增加飞行能力
jerry = new DecorateAnimal(jerry, FlyFeature.class);
//jerry增加挖掘能力
jerry = new DecorateAnimal(jerry, DigFeature.class);
//Jerry开始戏弄毛了
jerry.doStuff();
}
此类代码只是一个比较通用的装饰模式,只需要定义被装饰的类及装饰类即可,装饰行为由动态代理实现,实现了对装饰类和被装饰类的完全解耦,提供了系统的扩展性。
建议108:反射让模板方法模式更强大
建议109:不需要太多关注反射效率
反射的效率相对于正常的代码执行确实低很多,但它是一个非常有效的运行期工具类,只要代码结构清晰、可读性好那就先开发出来,等到进行性能测试时有问题再优化。
最基本的编码规则:"Don't Repeat Yourself"