本文发表于2010年,时间较早,部分问题解释不是十分准确,所以需要进一步了解,请参看2012年版本:
要在JAVA技术上突破普通的层面,并拥有一翻设计理念的高度,除了要有很好的设计思维之外,反射在适当的使用下,将会把框架做得非常清晰,并且代码编写也非常简便。
在面向对象的编程中,我们为什么要忌讳去大量使用if else switch语句,因为这样写是将逻辑硬编码了,JAVA的思想就是将其配置化,一旦可配置化后,就逐渐可管理化,并随着产品的成熟逐步实现自动化管理的过程(这又涉及到设计能力了),我们这里先抛开语言的界限,但说技术上如何实现反射,在适当的位置会提及在实践中如何灵活应用它就会得心应手。本文主要阐述的内容有:
1、动态代理的意义
2、动态代理入门
3、模拟Struts分发、填充核心原理
4、模拟Spring IoC、AOP核心原理
5、书写框架中的一些技巧
言归正传==》
1、动态代理的意义
从个人的语言来说,动态代理机制是每一门语言的基础,没有动态代理什么也做不了,也就是动态代理是运行语言的代码,在JAVA中请最先记住一个invoke(),后面会细节说明,其实在面向对象的设计中,实现了继承、封装、多态三大特征,不过仅仅是使用这些功能,你会发现和C语言没有什么区别,使用其充分的扩展性才能体现面向对象的好处,而其扩展性在诸多方面有所体现,而扩展性的基础来源于总体的设计和架构,动态代理将会提供非常好的机制统一总控管理和分发代码片段,将逻辑层抽象成模型,将判定作为分发方式,便于相应模型扩展就变成了配置,而不是代码片段(因为代码需要全线跟踪,全局观念很低就会导致整篇修改的迹象,而且修改任何一个小问题需要重新测试、发包、上线周期很长而且会导致很多BUG),也就是说做配置的目的就是把逻辑层抽象为模型,将模型的类型进行分解进行配置化,这样的代码下即可实现产品化,又可以实现个性化,当然总有一个限度,至于到达何种程度,关键看设计者的境界了。
以前我在部分博客上有人写道:如果你发现你的代码中几乎没有if else的时候,你面向对象的设计思想就达到一个境界了,其实他说得很多方面也是很有道理的,因为这类层套层的判定逻辑,是很难维护的,除非编写非常复杂的算法,业务上是不太可能有这样的情况。因为很多写代码的人都是从C过来的,其算法学得不错,而分析问题的方式和JAVA完全不一样(其实我也是那样走过来的,我曾经写代码也是那样),JAVA最重要的就是先业务问题抽象化,抽象为模型去解决,而不是只想到这段代码怎么去写,然后开始棒棒棒的写起来,也许你写的代码很长,你觉得很牛,而且写得很快,不过当软件逐渐做大做复杂的时候,麻烦就要开始了。
软件设计中,并不是不让你用if else,而是业务层不应该这样使用,业务千变万化,无穷无尽,除非这个软件周期很短或者根本就只是为了应付而不想做出什么好的产品出来,那可以这样,因为这样在开发小软件的时候周期非常快。模型在业务设计中不断抽象和寻找最佳切入点后,你会发现在复杂的业务,也可以抽象为几种简单的模型,而模型变化的可能性很小,没有大的动作就是那么一些东西,一旦将这些模型抽象出来后,逐渐产品化就可以实现界面配置化,甚至于提取抽象层次和自动化管理,现在比较经典的就是工作流(用硬编码去写工作流会死人,除非没有业务可说),工作流就是将运行过程中的业务过程的环节、路由、资源、岗位、人员、权限等信息抽象出来,并相应组织,利用驱动引擎将配置信息跑起来的一个过程,不过细节追踪就是技术问题了,如何让他可以并行运行和加快速度,我只能说这还是设计问题(技术设计),设计得好就会成倍提速,设计得不好往往会适得其反。
其实要一个进步的诀窍,就是不要妄下结论,不论达到什么境界,遇到任何问题都要想一下,思考一下全局观,将逻辑层抽象,大部分系统的业务层其实并不复杂(业务和逻辑并不完全一样,可以说业务包含逻辑吧,我们要做的就是首先将业务中的逻辑抽象出来,并将相应逻辑进行合并和再次抽象,反复思量才下手,时间长了你会发现,你写一行代码就可以搞定一堆事情,而不是写一堆代码搞定一件事情,这样的JAVA层次其实不用多说了,至少是入道了);其次,一定要coding,优秀的架构师一定要coding(估计这也算是国内和国外的一个区别吧,国内的架构师普遍不做coding),因为只有摸着,而且摸得恨透,设计的时候才会得心应手,而且coding的能力要远远超越普通程序员。
闲话扯远了,开始扯动态代理吧(本文全部以JDK1.5为基础进行说明)。
2、动态代理入门
2.1.一两个例子,如何简化代码:
首先,引入一个话题,逐渐切入,平时大部分人都会写的类似代码:
if(a >= b) {
return true;
}else {
return false;
}
我们觉得是很简单东西,其实,这样写是没有问题的,只是,这部分大家可以想一下,我们暂时抛开JAVA提供的是否有这样的比较,这样的比较在代码中应当是反复出现的逻辑,而合业务并不相关,那么首先想到的第一步就是,这样简单的东西怎么就那么复杂呢,这个本来就是一行代码搞定的事情:
return a>=b;--其实很容易就想到了,因为要返回的结果就是这个,为什么还要自己去判定一次呢?
好继续深入,有没有可以直接判定的,连大于小于都不用,答案是肯定的,至少可以通过很多方式得到类似的结果:
JAVA提供的符号位方式:Integer.signum(a - b)通过符号位即可知道大小情况。
(a - b)>>>31; 得到是否为1也可以知道a是否大于等于b。等等。
这样写程序,为什么很简单,因为共享的逻辑已经被抽象了。
我们再来举个简单常用的代码:
if(a !=null && "1".equals(a)) {
....
}else if(a!=null && "2".equals(a)){
....
}else....
这种代码我相信是很常见的,这种代码给人第一感觉就是:不干净,更加不利落。如何抽象第一层,将首相不想看到的层数,抽象到框架的公共方法中,由于是对比字符串,那么我们就给一个工具方法调用:
public static boolean equalString(String a,String b) {
if(a ==null || b==null) {
return false;
}
return a.intern() == b.intern();
}
上述代码就把框架部分写完,假如写在一个叫做StringUtils的类中,那么上述代码就变成了:
if(StringUtils.equalString(a,"1")) {
....
}else if(StringUtils.equalString(a,"2")) {
...
}else {
...
}
这样至少干净多了,那么你会觉得还不够利落,因为代码毕竟还是在用判定语句,不过就普通的代码,我们写成这样也无所谓了,只是共享的部分把它继续抽象即可,但是你要将复杂的问题继续简单化,我们就需要继续使用反射(抽象的技术原理有很多,反射只是其中一例,类似动态参数也在很多时候用好了非常好用)
再举个例子,我想啥系统都会遇到拼字符串的代码吧(不论是前台还是后台),在我所接触的系统中很多这部分都是放到业务上去编写,我就没搞懂这个拼串和业务有关系吗?而且程序员很多时候写得花花绿绿,各种各样的写法,稍有差错,各种各样的小BUG就出来了,而且对性能没多大概念的人普遍用+去拼字符串,因为那样觉得很简单,为了将业务层代码简单化、降低共享代码不可控的BUG、以及综合性能,我们将它抽象出来写,简单写法就是(实际业务需要做一些改造):
public static String concatStr(int avglen,String conString,String ...strings) {
if(strings == null || strings.length == 0) {
return DEFAULT_NULL_STR;
}
StringBuilder strValue = new StringBuilder(avglen*strings.length);
for(String str : strings) {
strValue.append(str).append(conString);
}
return strValue.delete(strValue.length()-conString.length(), strValue.length()).toString();
}
是不是觉得一个简单的拼串搞得这么麻烦,不是的,不然框架用来干什么呢,框架要解决的一大问题就是要把很多公共的事情做了,这部分又不需要程序员来写,程序员只需要做一步操作:StringUtils.concatStr(10,",","xieyu1","xieyu2","xieyu3");就能得到想要的结果了:“xieyu1,xieyu2,xieyu3”这样对程序员来说统一编码,共享问题共享解决(如这段代码有BUG,全系统统一一个地方切入点去解决,而不是全盘复制,代码具有可控性,其次避免程序员因为习惯不好,导致的一系列的常规性能问题),不仅仅是UTIL类,包含继承将系统级别、业务级别的共享操作在父亲类进行封装、接口用以定义统一规范或交互协议、在这个基础上当然可以实现多态了。
这里顺便提及一下多态:举个生活中的例子,定义个汽车类的统一规范,上层人员不需要知道汽车是啥样子的,只要定义汽车有什么大致属性(作为、轮胎、方向盘、GPS等)以及有哪些功能(启动、加速、刹车)等,这些功能如何去实现就交予第三方去完成,而客户不必关心,各个开发商可能实现机制不同,内部可能定义一些自己的属性,但是根据统一接口对客户开放的功能就只有要求的哪些,如启动功能,每个制造商在启动功能的实现方式上都不一样,也会定义不同的属性,但是我想买谁的就买谁的,买到谁的就用谁的功能,选择性很强,但是只要你会开这类车,其它的车也差不多会开。
而较为明显的例子就是JDBC连接池,我们之所以需要驱动包就是因为SUN公司并没有实现这部分,但是为了代码的统一性,便于移植,他定义了统一规范,也就是调用数据库的统一接口,在不同的数据库,即使同一门数据库不同的驱动版本或者不同开发商开发的驱动,他们内部实现都是不一样的,但是无论那个开发商拿过来的驱动程序以及无论哪一门数据库,都可以用同样的代码规范去调用(SQL建议不要想去统一,这个会有很多问题,代码上可以一样,SQL可以根据数据库各自去做一套配置,根据顶层配置选择走那一套配置SQL就OK了),这就是我们所谓的多态性的调用,在企业级应用中灵活性非常强。
由于这里只是开一个头,所以我只是从技术角度说明一个最简单的反射调用是如何调用的,根据下面如何解析Struts和Spring常规框架代码的实现过程来说明反射如何应用在实际中的。
一个最简单的反射代码:
//创建一个被处理类
class ReflectObject {
//测试信息
private String info;
public String getInfo() {
return info;
}
public void setInfo(String info) {
this.info = info;
}
}
public class SampleReflect {
//根据类的全名创建对象的方法:
public static Object createObject(String javaName) {
try {
return Class.forName("ReflectObject").newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return null;
}
//根据对象和方法信息执行:
public static Object invokeMethod(Object obj, String methodName,
Object[] params, Class clazz[]) {
try {
Method method = obj.getClass().getMethod(methodName, clazz);
return method.invoke(obj, params);
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
return null;
}
//测试代码(这部分代码也是业务运行中除框架部分的业务代码):
public static void main(String[] args) {
Object obj = createObject("ReflectObject");
invokeMethod(obj, "setInfo", new Object[] { "xieyu" },
new Class[] { String.class });
System.out.println(invokeMethod(obj, "getInfo", null, null));
}
}
可能你会想,干嘛搞这么复杂,不就是为了执行一个set和一个get操作嘛,而且把简单的问题复杂化了,其实不然,我们这里只是为了说明技术问题,开了一个头,一旦我们可以动态创建,可以动态执行方法,那么一切都活起来了,当你的代码中存在大量复杂逻辑的时候,抽象出来后,既可以配置化管理,也可以注入内存,更加可以将相似的判定逻辑进行合并,便于整个管理体系清晰化,并逐步实现界面和自动化管理的过程。
著:这从一个角度阐述了JAVA在相对其他语言开发项目时,前期的开发时间消耗是较长的,而且越到后面,越加显示出J2EE体系的扩展性,只要上层设计者有全局观和较好的技术功底,下面的程序员也有一定的功底(但是千万不要轻易的认为任何人都可以写好业务代码,即使是普通的代码也不是学两天写代码就可以写好的)。
3、模拟Struts分发、填充核心原理
3.1、struts分发原理:
struts在分发的过程中,底层采用了类似的动态代理,即,调用那个类的那个方法(这些方法都是相同的入参,并返回相同类型的结果),那么这就和我们上面的动态调用结合起来了,那么我们这里动态调用过程中首先确定的就是入参的类型,一会在3.3的实例中会有所体现。
这里举例如何实现基本的分发方式:
首先建立一个BeanUtils,用于中间处理(实际运行中不是这样的,是通过代理实现,这里我们用一个UTIL手工调用来模拟):
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
public class BeanUtils {
private final static String DEFAULT_NULL_STR = "";
/**
* 模拟配置
*/
private static Map config = new HashMap();
static {
Map map1 = new HashMap();
map1.put("key1", "getInfo1");//这里假定模拟一下数据装入缓存的过程
map1.put("key2", "getInfo2");
config.put("Hello", map1);
}
private static String getMethod(String busi,String state) {
Map map = (Map)config.get(busi);
return map == null ? DEFAULT_NULL_STR:(String)map.get(state);
}
/**
*
* @param obj
* @param params
* @param methodName
* @param clazz
* @param busi
* @param state
* @return
*/
public static Object invokeMethod(Object obj, Object params[],Class clazz[],String busi,String state) {
String methodName = getMethod(busi, state);
return invokeMethod(obj, new Object[]{"2",new Integer(1)}, methodName, new Class[]{String.class,Integer.class});
}
/**
* 动态方法调用
* @param obj
* @param params
* @param methodName
* @param clazz
* @return
*/
public static Object invokeMethod(Object obj, Object params[],String methodName,Class clazz[]) {
try {
Method method = obj.getClass().getMethod(methodName, clazz);
return method.invoke(obj, params);
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchMethodException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
/**
* 动态方法调用
* @param obj
* @param params
* @param methodName
* @param clazz
* @return
*/
public static Object invokeStaticMethod(Class objClass, Object params[],String methodName,Class clazz[]) {
try {
Method method = objClass.getMethod(methodName, clazz);
return method.invoke(null, params);
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchMethodException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
}
//此时需要创建一个被处理的结构:
public class Hello {
private final static String BUSI_STR = "Hello";
public String getInfo1(String value, Integer i) {
return "你传入的参数是:" + value + "/t" + i;
}
public String getInfo2(String value, Integer i) {
return "你传入的参数是:" + value;
}
public void testInfo(String state) {
System.out.println(BeanUtils.invokeMethod(this, new Object[] { "1",
new Integer(1) }, new Class[] { String.class, Integer.class },
BUSI_STR, state));
}
}
//最后任意创建一个测试类,然后在里面写一个main方法如下调用:
public static void main(String []agrs) {
/*在struts中,其实我们要继承一个定层的Action,是因为有一个公共的方法调用后,其功能类似于这里的testInfo方法,用于根据参数信息,动态调用指定的方法。*/
Hello hello = new Hello();
hello.testInfo("key1");//通过关键词,动态调用指定的方法体
hello.testInfo("key2");
}
3.2、填充核心原理:
填充其实和分发也是一样的道理,只是根据入参信息去填充对应的信息,当从request接受到一个参数时,根据参数的名称,加上一个set+参数名称首字母大写,根据方法名称和参数类型,查找是否存在这个方法,类型也会对应一下,如果存在就动态调用他,通过其BeanUtils内部的copyproperties方法也是同样的原理,这样就把复杂的问题抽象出去模型化,将业务简单化,或者说业务就专门写业务,而不必关心每一个参数交互的细节,这就是我们框架想要达到的基本目的之一。
这里举个填充的简单例子(这里的例子并不是实际的代码,实际的代码会考虑更多的问题,只是为了说明一些问题,所以只是简单给一个原理上的思路,创建一个FillObject.java,把一下代码拷贝进去):
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
//建立一个基本的FORM信息来模拟实际的FORM
class BaseForm {
private String name;
private String password;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
class HttpRequest {//模拟一个Request
private HashMap map;
private HashMap arrtibute = new HashMap();
public HttpRequest(String url) {
if(url == null) {
return;
}
String []params = url.split("&");
map = new HashMap(params.length*4/3+1);
for(int i = 0,size = params.length ; i<size ; i++) {
String []tmp = params[i].split("=");
if(tmp.length > 1) {
map.put(tmp[0], tmp[1]);
}
}
}
public String getParameter(String name) {
return (String)map.get(name);
}
public Map getParameterMap() {
return (Map)map.clone();
}
public void setAttribute(String key,Object obj) {
arrtibute.put(key, obj);
}
public Object getAttribute(String key) {
return arrtibute.get(key);
}
}
public class FillObject {
private String formName;
public FillObject(String formName) {
this.formName = formName;
}
private void fillInfo(BaseForm ele,String key,String value) {
String first = String.valueOf(key.charAt(0)).toUpperCase();
String methodName = "set" + first + key.substring(1);
try {
Method method = ele.getClass().getMethod(methodName,new Class[]{String.class});
method.invoke(ele,new Object[]{value});
}catch(Exception e) {
e.printStackTrace();
}
}
private BaseForm fillObject(HttpRequest request) {
BaseForm ele = new BaseForm();
Map map = request.getParameterMap();
for(Iterator iter = map.keySet().iterator() ; iter.hasNext() ; ) {
String key = (String)iter.next();
String value = (String)request.getParameter(key);
fillInfo(ele,key,value);
}
return ele;
}
public void doGet(HttpRequest request) {
System.out.println(request.getParameter("password"));
BaseForm ele = (BaseForm)request.getAttribute(formName);
System.out.println(ele.getName());
System.out.println(ele.getPassword());
}
public void init() {
HttpRequest request = new HttpRequest("name=xieyuooo&password=123err");
BaseForm form = this.fillObject(request);
request.setAttribute(formName, form);
this.doGet(request);
}
//测试代码
public static void main(String []args) {
FillObject fill = new FillObject("baseForm");
fill.init();
}
}
4、Spring IoC、AOP核心原理
4.1.其实上述过程看了后,尤其是填充部分,基本清楚为什么IOC是怎么回事。只是我们这里不讨论复杂的各项数据类型,所以程序写的比较简单。而IOC比起填充来说比较多的一点就是有隐含的多态机制在内部,基于接口去实现,在下面的代码例子中,不会给出Spring的实质实现方式,只是给出填充+多态的方式,在AOP中会提及其核心思想。
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
interface Car {
//接口规范定义要求实现一个现实操作
public void display();
//修改接口
public void updateinfo(String info);
}
//顶一个拖拉机的类
class Tractor implements Car {
//定义一个的名字
private String name;
/**
* @param name the name to set
*/
public void setName(String name) {
this.name = name;
}
public void display() {
System.out.println("/n拖拉机的名称为:"+name);
}
public void updateinfo(String info) {
this.name = info;
}
}
//定义桑塔拉的类
class Santala implements Car {
private String model;
private String color;
public void setModel(String model) {
this.model = model;
}
public void setColor(String color) {
this.color = color;
}
public void display() {
System.out.println("/n==>桑塔拉的型号为:"+this.model+"/n==>桑塔拉的颜色是:"+this.color);
}
public void updateinfo(String info) {
this.model = info;
}
}
//创建一个处理调用类:
class ServiceFactor {
//本来应该通过配置文件进行set,我这里就直接通过静态块模拟,相当于将配置读入内存
private static Map configInfo = new HashMap();
//实例化的BEAN放在这里
private volatile static Map beanObject = new HashMap();
static {//初始化一下信息,这里相当于配置文件一样
Map temp = new HashMap(4);
configInfo.put("tractor", temp);
temp.put("name", "1号拖拉机");
temp.put("className", "testreflect.Tractor");
temp = new HashMap();
configInfo.put("santala", temp);
temp.put("model", "007");
temp.put("color", "红色");
temp.put("className", "testreflect.Santala");
};
public static Object getBeans(String beanName) {
if(beanObject.containsKey(beanName)) {
return beanObject.get(beanName);
}
Map setInfo = (Map)configInfo.get(beanName);//获取配置信息
String className = (String)setInfo.get("className");
Object obj = null;
try {
obj = Class.forName(className).newInstance();
beanObject.put(beanName, obj);
for(Iterator iterator = setInfo.keySet().iterator();iterator.hasNext();) {
String key = (String)iterator.next();
if(key.intern() != "className") {
String methodName = toFirstUpper(key);
Method method = obj.getClass().getMethod(methodName, new Class[]{String.class});//我这里假如反射的都是String类型
method.invoke(obj, new Object[]{setInfo.get(key)});
}
}
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}catch (SecurityException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return obj;
}
private static String toFirstUpper(String key) {
String temp = key.substring(0,1).toUpperCase();
return "set" + temp + key.substring(1);
}
}
最后创建一个测试类,写一个main方法开始测试:
public static void main(String []agrs) {
Car car1 = (Car)ServiceFactor.getBeans("tractor");
Car car2 = (Car)ServiceFactor.getBeans("santala");
Car car3 = (Car)ServiceFactor.getBeans("tractor");
car1.display();
car2.display();
car3.display();
car1.updateinfo("2号拖拉机");//讲car1进行修改
car3.display();//显示car3的信息
}
该程序实现单例对象获取,并根据参数不同得到不同实例(这些参数在实际运行中可以有动态来源,实现企业级应用中的扩展性)
4.2.Spring AOP就是切入式编程,也有说法是面向切面的编程,什么意思呢,就是可以在调用方法前执行一些东西,并且可以获取传入参数,调用方法后执行一些东西,可以获取返回参数,刚开始接触这一块的时候觉得不可思意,甚至于打破曾经自己认为的所有的第三方框架都会给予JAVA基础编写起来,结果通过研究后,反射就是其基本原理,AOP就是通过其底层真正的动态代理来完成的,上面的只是开个头,这才是关键。我们以JDK1.5写一个例子,这个东西是如何做到切入的。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
//模版程序,创建泛型代码 支持JDK5或更高版本
interface Hello<T1, T2> {
public T1 getInfo();
public <TX> void setInfo(T1 infos1, T2 infos2, TX a);
public void display();
}
//编写一个实现类:
class HelloInfo<T1, T2> implements Hello<T1, T2> {
private volatile T1 infos1;
private volatile T2 infos2;
public T1 getInfo() {
return infos1;
}
public <TX> void setInfo(T1 infos1, T2 infos2, TX a) {
this.infos1 = infos1;
this.infos2 = infos2;
System.out.println("多余的参数就是我了:" + a);
}
public void display() {
System.out.println("/t/t" + infos1 + "/t" + infos2);
}
}
//实现InvocationHandler 并重写invoke方法是这里的关键点
class AOPFactory implements InvocationHandler {
private Object proxyed;
public AOPFactory(Object proxyed) {
this.proxyed = proxyed;
}
public Object invoke(Object proxyed, Method method, Object[] args)
throws IllegalArgumentException, IllegalAccessException,
InvocationTargetException {
System.out.println("/n====>调用方法名:" + method.getName());//测试用于输出
printInfo("传入的参数为:", args);
Object result = method.invoke(this.proxyed, args);
printInfo("传出的参数为:", result);
return result;
}
//用于辅助输出一些内容
public void printInfo(String info, Object... args) {
System.out.println(info);
if (args == null) {
System.out.println("/t空值。");
return;
}
for (Object obj : args) {
System.out.println("/t" + obj);
}
}
}
// 创建一恶搞BeanService,根据类全名(本来应该有一个关键词和一个类全名对应关系,一般写入配置文件中,这里为了方便就直接用类的全名):
class AOPService {
public static Object getBean(String className)
throws InstantiationException, IllegalAccessException,
ClassNotFoundException {
Object obj = Class.forName(className).newInstance();
InvocationHandler handler = new AOPFactory(obj);
return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj
.getClass().getInterfaces(), handler);
}
}
//编写一段测试代码:
public static void main(String[] args) {
try {
Hello<String, String> hello = (Hello<String, String>) AOPService.getBean("com.lianchuang.pkg.HelloInfo");
hello.setInfo("xieyu1", "xieyu2", "谢宇");
hello.getInfo();
hello.display();
} catch (Exception e) {
e.printStackTrace();
}
}
当你运行这段代码的时候,你会发现,虽然是直接调用你的方法,但是,它会通过AOPFactory类的invoke方法去间接调用你的方法,而不是直接调,在这个过程中,就可以做想做的事情了。
AOP一般用于:通用日志编写、公用事务编写等等。不要滥用AOP,读过他的源码就知道,它的性能并不是很乐观。
5、书写框架中的一些技巧
上面说了那么多其实对于框架的编写都是废话,因为在框架编写中有诸多技巧性的东西,而反射只是帮助我们解决其中一部分问题,而框架编写中到底有哪些技巧可言呢,这就个人经验和其理论功底而论了,因为经验会在不同行业上产生不同的问题,让人在按照现在所在公司的规范去解决这些问题,而功底是看人悟性以及对于问题的看法是否有把技术抽象出来。
就我个人来说一般有以下一些小经验,请多指教:
编写公司级别的框架:
编写公司级别的框架应当是轻量级的,而不是非常重的框架,这类框架适合开发商做公司业务上的绝大部分项目的基础平台,其包含大量的工具和基础类库,这些代码最好由高手来写,因为它的每一行代码将会决定后来扩展框架的性能和稳定性的基础,这些里面大部分类都是后来要完成的系统的顶层父类、统一接口、组件包、工具等。
编写业务级框架:
在公司级框架基础上不可能一层不变的搬过来照常用,除非公司的框架是按照工具进行开发的,这种产品化容易做到,但是整体体系容易分散,关键看管理了。根据通用框架编写业务级别的框架的扩展,这些普遍应用于一个行业领域,根据行业领域有一个实践性的分析,在业务框架中包含大量对于行业内的通用业务处理,并建立业务体系和大量的基层数据结构,并将数据分类,定义相应的缓存和交换法则在框架中实现,在实际的项目中在依赖于业务框架基础上几乎不用做太大的变化就可以实现。
产品化的软件:
软件产品化一般指一个小的子系统,整个体系的产品化需要进一步整合,产品化的软件就是为了以最小的代价实现绝大部分行业类的响应软件需求,开发模式一般以一个集中的方式来方便版本的控制,如果抽象得很好的,会将各个层次抽象为模型,进行设计和研发,在现场基本全是配置化的管理,而没有代码的开发,若弄不好,代码冗余程度很高,还不如不这样做;也有个性化强度很高的,就是做一个基础版本,各个现场使用基础产品来完成实际软件的研发。
性能和稳定性:
在抽象层次的基础上,软件的扩展性很多还要靠程序员的配合来完成,也就是最终还是落实到程序员,但是性能呢?
从系统的层面,系统的性能主要决定于:应用系统的设计、数据库结构设计、数据库架构以及硬件选型、SQL优化等。
在应用开发层面主要关心的是应用系统设计、数据结构设计、SQL优化,而应用系统设计主要就是框架的设计,哪些是做缓存处理、如何做缓存处理、输出压缩、提供共享操作高效且低BUG的代码、如何充分利用多线程、如何通过框架降低BUG的概率、如何充分利用CPU、如何解决IO瓶颈(从代码策略和配置两方面)、如何负载均衡、失败切换等都是我们需要考虑的问题,如果有必要还会去考虑内存通信等。
其次就是数据结构设计,数据结构设计将会决定在绝大部分情况下数据库的查询性能的长期目标,如果结构设计得不好,数据量一上来,马上非常慢,怎么优化SQL也都是治标不治本的方法,通常这个需要在结合业务经验的基础上,要有一些数据库体系结构思想。
最后才是SQL优化,当然对于程序员来说这是最重要之一,因为前面的不管程序员的事情,除了编写一点多线程(最好在框架中分出来,而不是让程序员去编写),程序员只关心业务过程的编写,以及如何使用框架提供的基础类库来完成这些业务编码。而SQL呢,SQL程序员是必备掌握的,而且也是一门很深的学问,可以说简单,也可以说复杂,要研究进去很多事情很不好解释,不研究这些东西就显得就那么回事,SQL基本要达到从开始掌握语法会写SQL开始、如何变换SQL改善性能、如何查找复杂SQL中的逻辑可合并的部分进行合并、如何通过体系结构的学习通过本质和提供的方案来解决、如何从设计角度规范化统一优化方法等等,逐步的一个过程。
所谈之处尚欠,望指教中。。。