“什么是spring aop?”,我顿时大脑浮想联翩,我想到了事物管理、SDK、代码监控、spring remoting,这么多东西,我从哪里回答呢,后来变得大脑一片空白,这提问有问题啊,aop在我脑子里面有那么多表现形式,你如果提出一个功能,问我怎么做,我肯定不会思维混乱的,本来aop就够抽象了,你还问的这么抽象。
“你用过aop吗“
用过,我在给android的服务端接口SDK里面用过的。android在调用服务端api的时候要对本地方法进行拦截,并且把最终实现逻辑加上http调用的代码...。
这跟aop有什么关系?
好吧,没什么关系。但明白人说呢,到底有没有关系?
下面我来解释一下看看,大家给评评理。
这次我先说一下android远程api调用SDK的实现方式。
举例,修改密码的功能。在没有sdk之前一般都这么写的
User user = new User();
user.setPassword(password);
HttpClient client = new HttpClient();
client.remoteUrl = “http://a.com/user/modifyPassword”
client.params = JSON.toJsonString(user);
String response = client.invokeUrl();
ServiceInvokeResult result = JSON.parse(response ,ServiceInvokeResult .class);
If(result.status == ‘200’ && result.resultMessage == ‘success’){
System.out.println(‘修改成功’);
}
这么写没啥不行的,但我认为有几个问题:
1.第2、3、4、5、6行的代码,在涉及到其他的服务端接口调用的地方都需要写的。
2.Android程序员必须知道remoteUrl怎么写,实际上他并不关心这玩意的
3.Android需要处理参数、返回值序列化
实际上上面几个问题可以归纳为:android程序员想少些代码。
那么代码能否改成这样呢
User user = new User();
user.setPassword(password);
InvokeResult result = userService.modifyPassword();
看起来像是调用一个本地的方法,而且这样至少能少写4行代码吧,可能有些人会说:这算个鸟,我们开发程序最主要的目的是快速完成任务,有那时间复制粘贴早搞定了,思考那么多浪费时间。
如果你是这种思想的话,那就看到这里为止吧。下面的内容不是给你看的,有兴趣的可以往下面看。
有人说把少写的那些代码放到modifyPassword()里面不就完了吗,呵呵。我会说,“dont’t give me your suit,you know i mean that”.
怎么写才能让调用本地方法拥有调用远程服务的能力。我们实际上是在修改方法的实现逻辑
仔细观察每一个包含远程调用的实现代码,我们可以发现,这类代码都有包含下面的逻辑
进一步讲,我们应该把组装参数,发起http请求和获取请求的返回值提取到一个公共的地方,并且让每一个实现代码自动去调用,如何实现这个自动。
我们都听说过AOP,那再去看看这篇文档http://shouce.jb51.net/spring/aop.html
看完后有我感觉没有顿时豁然开朗的感觉,不能马上感觉到与我们要解决的问题有什么关系,而是睡着了,这是他妈谁写的玩意,看了让老子想睡觉。
我用我自己能理解的语言来解释一下:AOP像是一个监视系统,他能监视你的代码执行,并且根据你的需求在你代码执行之前、执行之后、抛异常的时候做出反应,他的核心是代理,最终运行的是代理类,而不是你自己写的类。
你的代码把组装参数发起http请求、获取请求的返回值代理给代理类处理
AOP代理分为静态代理和动态代理,静态代理指的是在编译期生成代理类,一般用命令生成,像AspectJ,它的逻辑是用它的命令修改了生成的class,你可以把class用反编译工具打开查看,里面有很多它生成的代码。而动态代理是在运行时在内存中生成代理类。
不像服务端程序,你想引入什么类库就引入什么类库,在android这种环境中,尽量不要引入第三方类库,因为那是要打包进去的,增加apk的体积。接下来,我们用jdk自带的动态代理来实现。
Java创建代理类是通过java.lang.reflect.Proxy的newProxyInstance方法来完成的,这个方法有三个参数
ClassLoader loader:代理类的类加载器
Class<?> interfaces: 代理类要实现的接口,最终会产生一个代理类名字是 $ProxyN(N=1-65535),这个代理类要实现这些接口
InvocationHandler h:实现了InvocationHandler接口的类
这里面有一些要求:
1.要代理的类必须用接口的形式。
为啥?
规范呗,java不是一直在说面向接口编程吗。
那我告诉你CGLIB没这个要求。
啥B,那就是那啥B不规范呗...
我来告诉你吧,newProxyInstance生成的代理类都继承了Proxy,java又不允许多继承,但是可以多实现。
哦,明白了,只能继承一个,那我先把这个坑占了。那我明白了又能怎样,你告诉我,我明白了这玩意能涨工资不?
......
2.你需要提供一个实现了InvocationHandler 的类,在这个类里面做你的逻辑,比如把组装参数,发起http请求和获取请求的返回值等相关代码放进去。
那就动手写起来呗,我们先来写一个InvocationHandler 实现类,这个类是抽象的,因为有可能有多种用途的实现类,代码看起来像下面的样子
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.Type;
public abstract class ProxyFactory<M> implements InvocationHandler {
private Class<M> targetClass;
private String targetClassName;
private String methodName;
public String getTargetClassName() {
return targetClassName;
}
public String getMethodName() {
return methodName;
}
public ProxyFactory(Class clazz){
super();
this.targetClass = clazz;
}
public M getProxy(){
return (M) Proxy.newProxyInstance(targetClass.getClassLoader(), new Class[]{targetClass}, this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
try {
//获取方法名
this.methodName = method.getName();
//获取参数名
String[] argNames = Classes.parseMethodVarNames(method);
before(argNames, args);
this.targetClassName = targetClass.getName();
result = after(method.getGenericReturnType());
} catch (Exception e) {
onException(e);
}
return result;
}
public abstract void onException(Exception ex);
public abstract Object after(Type type);
public abstract void before(String[] argNames,Object[] args);
}
InvocationHandler 只有一个方法invoke,在这里面传入,代理类,代理类的方法,方法的参数值。
我们另外定义3个方法以备后用,分别代表before(方法调用之前逻辑)、after(方法调用后逻辑),onException(方法抛异常逻辑)。
我们再定义一个用来做远程调用的实现类,如下
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Map;
public class RemoteProxy<M> extends ProxyFactory<M> {
private HTTPClient client = new HTTPClient();
private Map<String,Object> map = new HashMap<String, Object>();
public RemoteProxy<M> putOtherParameters(String parameterName,Object parameterValue){
map.put(parameterName,parameterValue);
return this;
}
public RemoteProxy(Class clazz){
super(clazz);
}
@Override
public void onException(Exception ex){
ex.printStackTrace();
}
@Override
public Object after(Type returnType) {
String serviceUri = Classes.parseClassMethodToUri(super.getTargetClassName(), super.getMethodName());
client.setServiceUri(serviceUri);
String responseStr = client.request();
return Classes.stringToObject(responseStr,returnType);
}
@Override
public void before(String[] argNames, Object[] args) {
map.clear();
for(int i = 0;i < argNames.length;i ++){
String argName = argNames[i];
map.put(argName,args[i]);
}
client.setParams(map);
}
}
在before的实现里面,我们组装参数。在after中我们设置调用的服务端url,进行远程调用,接着反序列化服务端的返回值。
假设你要代理UserService的接口,那么我们通过下面的代码去创建一个UserService的代理对象,并且调用相关业务方法
UserService userService = new RemoteProxy<UserService>(UserService.class).getProxy();
userService.modifyPassword();
网上说AOP是面向方面、切面编程,那么我们通过上面的例子可以看出before、onException、after就是AOP提供给我们的方面、切面的某种表现形式,明白了嘛?
没明白!
......
那我抽时间再弄点别的例子吧。