我命由我不禁天 --哪吒java
建议101:注意Class类的特殊性mysql
建议102:适时选择getDeclaredXXX和getXXXsql
建议103:反射访问属性或方法时Accessible设置为true数据库
建议104:使用forName动态加载类文件数组
建议105:动态加载不适合数组框架
建议106:动态代理可使代理模式更加灵活ide
建议107:使用反射增长修饰模式的普适性函数
建议108:反射让模板方法模式更强大工具
建议109:不须要太多关注反射效率性能
建议101:注意Class类的特殊性
Java语言是先把Java源文件编译成后缀为class的字节码文件,而后经过classLoader机制把这些类加载到内存中。最后生成实例执行。
class类是Java的反射入口,只有在得到一个类的描述对象后才能动态的加载、调用,通常得到class对象的三种方法:
一、类属性加载:如String.class
二、对象的getClass方法:如new String.getClass()
三、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(); }
此类代码只是一个比较通用的装饰模式,只须要定义被装饰的类及装饰类便可,装饰行为由动态代理实现,实现了对装饰类和被装饰类的彻底解耦,提供了系统的扩展性。
建议109:不须要太多关注反射效率
反射的效率相对于正常的代码执行确实低不少,但它是一个很是有效的运行期工具类,只要代码结构清晰、可读性好那就先开发出来,等到进行性能测试时有问题再优化。
最基本的编码规则:"Don't Repeat Yourself"