Java的反射框架提供了动态代理(Dynamic Proxy)机制,容许在运行期对目标类生成代理,避免重复开发。咱们知道一个静态代理是经过主题角色(Proxy)和具体主题角色(Real Subject)共同实现主题角色(Subject)的逻辑的,只是代理角色把相关的执行逻辑委托给了具体角色而已,一个简单的静态代理以下所示:java
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定义必须实现的,它完成了对真实方法的调用。spring
咱们来详细解释一下InvocationHandler接口,动态代理是根据被代理的接口生成的全部方法的,也就是说给定一个或多个接口,动态代理会宣称“我已经实现该接口下的全部方法了”,那你们想一想看,动态代理是怎么才能实现接口中的方法呢?在默认状况下全部方法的返回值都是空的,是的,虽然代理已经实现了它,可是没有任何的逻辑含义,那怎么办?好办,经过InvocationHandler接口的实现类来实现,全部的方法都是由该Handler进行处理的,即全部被代理的方法都由InvocationHandler接管实际的处理任务。数据库
咱们开看看动态代理的场景,代码以下: session
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方法中读取持久化的数据便可实现,并且还能实现动态切入的效果,这也是AOP(Aspect Oriented Programming)变成理念。ide
装饰模式(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(); }
此类代码只一个比较通用的装饰模式,只须要定义被装饰的类及装饰类便可,装饰行为由动态代理实现,实现了对装饰类和被装饰类的彻底解耦,提供了系统的扩展性。
模板方法模式(Template Method Pattern)的定义是:定义一个操做中的算法骨架,将一些步骤延迟到子类中,使子类不改变一个算法的结构便可重定义该算法的某些特定步骤。简单的说,就是父类定义抽象模板做为骨架,其中包括基本方法(是由子类实现的方法,而且在模板方法中被调用)和模板方法(实现对基本方法的调度,完成固定的逻辑),它是用了简单的继承和覆写机制,我么来看一个基本的例子。
咱们常常会开发一些测试或演示程序,指望系统在启动时自动初始化,以方便测试或讲解,通常的作法是写一个SQL文件,在系统启动前自动导入,不过,这样不只麻烦并且容易出错,因而咱们就手写了一个自动初始化数据的框架:在系统(或容器)自动启动时自行初始化数据。但问题是每一个应用程序要初始化的内容咱们并不知道,只能由实现者自行编写,那咱们就必须给做者预留接口,此时就得考虑使用模板方法模式了,代码以下:
1 public abstract class AbsPopulator { 2 // 模板方法 3 public final void dataInitialing() throws Exception { 4 // 调用基本方法 5 doInit(); 6 } 7 8 // 基本方法 9 protected abstract void doInit(); 10 }
这里定义了一个抽象模板类AbsPopulator,它负责数据初始化,可是具体要初始化哪些数据则是由doInit方法决定的,这是一个抽象方法,子类必须实现,咱们来看一个用户表数据的加载:
public class UserPopulator extends AbsPopulator{ @Override protected void doInit() { //初始化用户表,如建立、加载数据等 } }
该系统在启动时查找全部的AbsPopulator实现类,而后dataInitialing实现数据的初始化。那你们可能要想了,怎么让容器指导这个AbsPopulator类呢?很简单,若是是使用Spring做为Ioc容器的项目,直接在dataInitialing方法上加上@PostConstruct注解,Spring容器启动完毕后自动运行dataInitialing方法。具体你们看spring的相关知识,这里再也不赘述。
如今问题是:初始化一张User表须要很是多的操做,好比先建表,而后筛选数据,以后插入,最后校验,若是把这些都放入到一个doInit方法里会很是庞大(即便提炼出多个方法承担不一样的责任,代码的可读性依然不好),那该如何作呢?又或者doInit是没有任何的也无心义的,是否能够起一个优雅而又动听的名字呢?
答案是咱们可使用反射加强模板方法模式,使模板方法实现对一批固定的规则的基本方法的调用。代码是最好的交流语言,咱们看看怎么改造AbsPopulator类,代码以下:
public abstract class AbsPopulator { // 模板方法 public final void dataInitialing() throws Exception { // 得到全部的public方法 Method[] methods = getClass().getMethods(); for (Method m : methods) { // 判断是不是数据初始化方法 if (isInitDataMethod(m)) { m.invoke(this); } } } // 判断是不是数据初始化方法,基本方法鉴定器 private boolean isInitDataMethod(Method m) { return m.getName().startsWith("init")// init开始 && Modifier.isPublic(m.getModifiers())// 公开方法 && m.getReturnType().equals(Void.TYPE)// 返回值是void && !m.isVarArgs()// 输出参数为空 && !Modifier.isAbstract(m.getModifiers());// 不能是抽象方法 } }
在通常的模板方法模式中,抽象模板(这里是AbsPopulator类)须要定义一系列的基本方法,通常都是protected访问级别的,而且是抽象方法,这标志着子类必须实现这些基本方法,这对子类来讲既是一个约束也是一个负担。可是使用了反射后,不须要定义任何抽象方法,只须要定义一个基本方法鉴定器(例子中的isInitDataMethod)便可加载符合规则的基本方法。鉴别器在此处的做用是鉴别子类方法中哪些是基本方法,模板方法(例子中的dataInitaling)则须要基本方法鉴定器返回的结果经过反射执行相应的方法。
此时,若是须要进行大量的初始化工做,子类的实现就很是简单了,代码以下:
public class UserPopulator extends AbsPopulator { public void initUser() { /* 初始化用户表,如建立、加载数据等 */ } public void initPassword() { /* 初始化密码 */ } public void initJobs() { /* 初始化工做任务 */ } }
UserPopulator类中的方法只要符合基本方法鉴别器条件即会被模板方法调用,方法的数据量也再也不受父类的约束,实现了子类灵活定义基本方法、父类批量调用的功能,而且缩减了子类的代码量。
若是你们熟悉JUnit的话,就会看出此处的实现与JUnit很是类似,JUnit4以前要求测试的方法名必须是以test开头的,而且无返回值、无参数,并且是public修饰,其实现的原理与此很是相似,你们有兴趣能够看看Junit的源码。
反射的效率是一个老生常谈的问题,有"经验" 的开发人员常常会使用这句话恐吓新人:反射的效率是很是低的,不到万不得已就不要使用。事实上,这句话前半句是对的,后半句是错的。
反射的效率相对于正常的代码执行确实低不少,但它是一个很是有效的运行期工具类,只要代码结构清晰、可读性好那就先开发起来,等到进行性能测试时证实此处性能确实有问题再修改也不迟(通常状况下,反射并非性能的终极杀手,而代码结构混乱、可读性差则可能会埋下性能隐患)。咱们看这样一个例子,在运行期得到泛型类的泛型,代码以下:
class Utils { // 得到一个泛型类的实际泛型类型 public static <T> Class<T> getGenricClassType(Class clz) { Type type = clz.getGenericSuperclass(); if (type instanceof ParameterizedType) { ParameterizedType pt = (ParameterizedType) type; Type[] types = pt.getActualTypeArguments(); if (types.length > 0 && types[0] instanceof Class) { // 如有多个泛型参数,依据位置索引返回 return (Class<T>) types[0]; } } return (Class<T>) Object.class; } }
前面咱们讲过,Java泛型只存在于编译器,那为何这个工具类能够取得运行期的泛型类型呢?那是由于该工具只支持继承的泛型类,若是是在Java编译时已经肯定了泛型类的类型参数,那固然能够经过泛型类得到了。例若有这样一个泛型类:
abstract class BaseDao<T>{ //得到T运行期的类型 private Class<T> clz = Utils.getGenricClassType(getClass()); //根据主键得到一条记录 public void get(long id){ session.get(clz,id); } } //操做user表 class UserDao extends BaseDao<String>{ }
对于UserDao类,编译器编译时已经明确了其参数类型是String,所以能够经过反射的方式来获取其类型,这也是getGenricClassType方法使用的场景。
BaseDao和UserDao是ORM中的常客,BaseDao实现对数据库的基本操做,好比增删改查,而UserDao则是一个比较具体的数据库操做,其做用是对User表进行操做,若是BaseDao可以提供足够多的基本方法,好比单表的增删改查,哪些与UserDao相似的BaseDao子类就能够省却大量的开发工做。但问题是持久层的session对象(这里模拟的是Hibernate Session)须要明确一个具体的类型才能操做,好比get查询,须要得到两个参数:实体类类型(用于肯定映射的数据表)和主键,主键好办,问题是实体类类型怎么得到呢?
子类进行传递?麻烦,并且也容易产生错误。
读取配置问题?可行,但效率不高。
最好的办法就是父类泛型化,子类明确泛型参数,而后经过反射读取相应的类型便可,因而就有了咱们代码中clz变量:经过反射得到泛型类型。如此实现后,UserDao可不用定义任何方法,继承过来的父类操做方法已经知足基本需求了,这样的代码结构清晰,可读性又好。
想一想看,若是考虑反射效率问题,没有clz变量,不使用反射,每一个BaseDao的子类都要实现一个查询操做,代码将会大量重复,违反了" Don't Repeat Yourself " 这条最基本的编码规则,这会导致项目重构、优化难度加大,代码的复杂度也会提升不少。
对于反射效率的问题,不要作任何的提早优化和预期,这基本上是杞人忧天,不多有项目是由于反射问题引发系统效率故障的(除非是拷贝的垃圾代码),并且根据二八原则,80%的性能消耗在20%的代码上,这20%的代码才是咱们关注的重点,不要单单把反射做为重点关注对象。
注意:反射效率低是个真命题,但由于这一点而不使用它就是个假命题。