面向切面编程将应用系统开发人员从很多重复性的日志记录、事务处理等代码中解放出来,并且可以配置化的实现同一点的差异化处理。在众多java框架中,spring框架将面向切面做的非常完美了。虽然实际工作中使用spring时不需要关注其具体实现,但是作为一个程序猿,还是应当对自己使用工具的原理有一定理解的。个人感觉框架的使用都是招式,思想是内功,招式可能会忘记,但是内功很难倒退。
自己根据理解,简单模拟了aop的实现,不对之处,大神请轻拍。
先介绍下思路,这里主要是使用jdk的proxy动态代理实现(spring还支持cglib方式的aop,有兴趣可以了解下)。主要是定义一个动态代理处理器类MyInvocationHandler,aop的相关处理都在此类中处理,定义了三个int常量代表BEFORE、AFTER、AROUND处理,定义一个InterceptorImpl变量,存放拦截器实例,BEFORE、AFTER的具体处理动作在此类完成。AROUND处理的话我单独定义了一个AROUNDHANDLER类,InterceptorImpl类只作为Around的一个入口。
具体代码如下,标红被注释的代码可以看下,生成的动态代理类除了实现了实际业务类的接口,还是Proxy的子类。
import interceptor.AroundHandler;
import interceptor.InterceptorImpl;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
//动态代理处理器工具
public class MyInvocationHandler implements InvocationHandler {
private static final int BEFORE = 0;
private static final int AFTER = 1;
private static final int AROUND = 2;
private Object business;//被代理对象
private InterceptorImpl interceptor = null;;//拦截器
private Map methods = new HashMap();
/**
* 绑定被代理对象,使用构造函数绑定也行
* business 被代理对象
* interceptor 拦截器
* method 第一个参数为前置拦截,第二个参数为后置拦截
*
*/
public Object bind(Object business,InterceptorImpl interceptor,String ...method ){
this.business = business;
this.interceptor = interceptor;
for(int i=0;i<method.length;i++){
if(method[i]!=null&&!method[i].equals("")){
methods.put(i, method[i]);
}
}
//动态代理的实现步骤之一
//Proxy 提供用于创建动态代理类和实例的静态方法,它还是由这些方法创建的所有动态代理类的超类。
Object dynamicProxy = Proxy.newProxyInstance(
//被代理类的classloder
business.getClass().getClassLoader(),
//被代理接口,本方法返回对象自动声称实现了该接口
business.getClass().getInterfaces(),
//代理处理器对象
this);
<span style="color:#ff0000;">//Proxy 提供用于创建动态代理类和实例的静态方法,它还是由这些方法创建的所有动态代理类的超类。
if(dynamicProxy instanceof Proxy){
// System.out.println("Proxy 是生成的动态代理类的超类");
// Class<?> clazz= dynamicProxy.getClass();
// Class superClass = clazz.getSuperclass();
// System.out.println(superClass.getSimpleName());
// Class implementsClass[] = clazz.getInterfaces();
// for(int i=0;i<implementsClass.length;i++){
// System.out.println(implementsClass[i].getSimpleName());
// }
}
</span> return dynamicProxy;
}
/**
* 代理要调用的方法,并在方法调用前后调用拦截器的方法.
*
* @param proxy 动态生成的代理类对象
* @param method 被代理的接口方法
* @param args 被代理接口方法的参数
* @return 方法调用返回的结果
* @throws Throwable
*/
//问:如果我不想对所有的方法都进行拦截
//我的想法是在代理对象的public Object invoke(Object proxy, Method method, Object[] args)
//方法里面处理,对传进来的method的名字进行判断,使用正则等。
//判断的正则表达式存在XML里面。这样我们就可以配置文件时行解藕了.如果有兴趣的朋友可以把操作者,被代理者,
//都通过配置文件进行配置 ,那么就可以写一个简单的SpringAOP框架了.
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
Object result = null;
if(methods.get(BEFORE)!=null){
interceptor.before();
}
if(methods.get(AROUND)!=null){
//
result = interceptor.around(new AroundHandler(business,method,args));
}else{
result=method.invoke(business, args);
}
if(methods.get(AFTER)!=null){
interceptor.after();
}
return result;
}
}
定义的InterceptorImpl类如下,其中around需要额外的handler。
public interface InterceptorImpl {
public void before();
public void after();
public Object around(AroundHandler handler);
}
handler类如下
public class AroundHandler {
private Object proxy;
private Method method;
private Object[] args;
public AroundHandler(Object proxy,Method method,Object[] args){
this.proxy = proxy;
this.method = method;
this.args = args;
}
public Object proceed(){
Object obj = null;
try {
obj = method.invoke(proxy, args);
} catch (Exception e) {
e.printStackTrace();
}
return obj;
}
}
interceptor的一个实现类如下:
//拦截器:在调用业务方法之前或者之后会自动拦截并执行自己的一些方法。
public class TeacherInterceptor implements InterceptorImpl {
public void before(){
System.out.println("拦截器TeacherInterceptor方法调用:before()");
}
public void after(){
System.out.println("拦截器TeacherInterceptor方法调用:after()");
}
@Override
public Object around(AroundHandler handler) {
Object obj = null;
System.out.println("this is around advice and it's before of target method ");
obj = handler.proceed();
System.out.println("this is around advice and it's after of target method ");
return obj;
}
}
测试代码如下,其中红色代码可以把生成的动态代理类的class文件存方在工程目录下
public class PersonClient {
/**
* @param args
*/
public static void main(String[] args) {
<span style="color:#ff0000;">System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
</span> PersonImpl person2 = (PersonImpl)new MyInvocationHandler().bind(new Teacher(),new TeacherInterceptor(),"before","after","around");
System.out.println("******************************");
System.out.println("client:"+person2.getType());
System.out.println("******************************");
}
}
测试结果
******************************
拦截器TeacherInterceptor方法调用:before()
this is around advice and it's before of target method
this is target method
this is around advice and it's after of target method
拦截器TeacherInterceptor方法调用:after()
client:Teacher
******************************
如下是通过上述生成在工程下的动态代理类的class文件反编译出的部分代码(我用的反编译工具是jd-gui),从这部分代码可以看到动态代理类确实实现了具体业务类的接口并且实现了接口方法getType。所以我们测试代码中对getType的调用是转到这里处理的并实际调用this.h.invoke方法,this.h中的h则是我们的动态代理处理器类,所以最终又调用到我们动态代理处理器类的invoke,而invoke就是我们处理各种拦截的地方。至此,整个调用链就理顺了。
public final String getType()
throws
{
try
{
<span style="color:#ff0000;"> return ((String)this.h.invoke(this, m4, null));
</span> }
catch (RuntimeException localRuntimeException)
{
throw localRuntimeException;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
static
{
try
{
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
<span style="color:#ff0000;"> m4 = Class.forName("business.PersonImpl").getMethod("getType", new Class[0]);
</span> m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
m3 = Class.forName("business.PersonImpl").getMethod("print", new Class[0]);
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
return;
}
catch (NoSuchMethodException localNoSuchMethodException)
{
throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
}
catch (ClassNotFoundException localClassNotFoundException)
{
throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
}
}