Java实现AOP面向切面编程的实例教程
介绍
众所周知,AOP(面向切面编程)是Spring框架的特色功能之一。通过设置横切关注点(crosscuttingconcerns),AOP提供了极高的扩展性。那AOP在Spring中是怎样运作的呢?当你只能使用corejava,却需要AOP技术时,这个问题的解答变得极为关键。不仅如此,在高级技术岗位的面试中,此类问题也常作为考题出现。这不,我的朋友最近参加了一个面试,就被问到了这样一个棘手的问题——如何在不使用Spring及相关库,只用coreJava的条件下实现AOP。因此,我将在本文中提供一份大纲,帮助大家了解如何只用coreJava实现一个AOP(当然啦,这种AOP在功能上有一定的局限性)。注意,本文不是一篇有关SpringAOP与JavaAOP的对比研究,而是有关在coreJava中借助固有的设计模式实现AOP的教程。
想必读者已经知道AOP是什么,也知道在Spring框架中如何使用它,因此本文只着眼于如何在不用Spring的前提下实现AOP。首先,我们得知道,Spring是借助了JDKproxy和CGlib两种技术实现AOP的。JDKdynamicproxy提供了一种灵活的方式来hook一个方法并执行指定的操作,但执行操作时得有一个限制条件:必须先提供一个相关的接口以及该接口的实现类。实践出真知,让我们透过一个案例来理解这句吧!现在有一个计算器程序,用于完成一些数学运算。让我们来考虑下除法功能,此时的问题是:如果coreframework已经具备了一份实现除法的代码,我们能否在代码执行时劫持(highjack)它并执行额外的校验呢?答案是肯定的,我将用下面提供的代码片段来证明这点。首先来看基础接口的代码:
publicinterfaceCalculator{
publicintcalculate(inta,intb);
}
该接口实现类的代码如下:
publicclassCalculatorImplimplementsCalculator{
@Override
publicintcalculate(inta,intb){
returna/b;
}
}
假设我们既不能修该上面的代码,也不能对核心库进行任何改动,怎样才能完美地实现校验功能呢?不如试下JDKdynamicproxy的功能吧。
publicclassSomeHandlerimplementsInvocationHandler{
//Codeomittedforsimplicity…..
@Override
publicObjectinvoke(Objectproxy,Methodmethod,Object[]params)throwsThrowable{
//Yourcomplexbusinessvalidationandlogic
Objectresult=method.invoke(targetObject,params);
returnresult;
}
}
让我们通过测试类来看看由JDKdynamicproxy实现的校验功能的效果如何。
publicstaticvoidmain(String[]args){
CalculatorImplcalcImpl=newCalculatorImpl();
Calculatorproxied=(Calculator)ProxyFactory.getProxy(Calculator.class,calcImpl,
newSomeHandler(calcImpl));
intresult=proxied.calculate(20,10);
System.out.println("FInalResult:::"+result);
}
从结果可以看出,简单地实现功能强大的InvocationHandler接口,我们便能得到一个hookingimplementation。按照JDK文档的描述,InvocationHandler接口是借助一个代理实例(proxyinstance)来处理一个方法调用的。
现在我们已经知道,InvocationHandler的invoke()方法能够帮助我们解决问题。那么再来解决一个新问题——怎样才能在方法执行的前后执行操作呢?说的更具体一些,我们能通过添加多个aop(before、after、around)来hook一个方法吗(译注:原文为addmultipleaops,但我认为Handler是充当Aspect的角色)?答案同样是肯定的。按照以下的步骤建立一个精简的代码模板便能满足这样的需求:
- 创建一个抽象类,用于将aop应用于目标对象上。
- 创建名为BeforeHandler和AfterHandler的两个aop。前者在方法执行之前工作,而后者则在方法执行结束后工作。
- 创建一个代理类,使所有的aophandler和目标对象只需作为参数传入,就能创建一个hook。
- 加入你自己的业务逻辑或者横切关注点。
- 最后,通过传入相关的参数创建代理对象(proxyobject)。
两种实现AOP的方式:
1,JDK提供的动态代理实现
接口
publicinterfaceUserBean
{
voidgetUser();
voidaddUser();
voidupdateUser();
voiddeleteUser();
}
原始实现类
publicclassUserBeanImplimplementsUserBean
{
privateStringuser=null;
publicUserBeanImpl()
{
}
publicUserBeanImpl(Stringuser)
{
this.user=user;
}
publicStringgetUserName()
{
returnuser;
}
publicvoidgetUser()
{
System.out.println("thisisgetUser()method!");
}
publicvoidsetUser(Stringuser)
{
this.user=user;
System.out.println("thisissetUser()method!");
}
publicvoidaddUser()
{
System.out.println("thisisaddUser()method!");
}
publicvoidupdateUser()
{
System.out.println("thisisupdateUser()method!");
}
publicvoiddeleteUser()
{
System.out.println("thisisdeleteUser()method!");
}
}
代理类
importjava.lang.reflect.InvocationHandler;
importjava.lang.reflect.Method;
importjava.lang.reflect.Proxy;
importcom.cignacmc.finance.bean.UserBeanImpl;
publicclassUserBeanProxyimplementsInvocationHandler
{
privateObjecttargetObject;
publicUserBeanProxy(ObjecttargetObject)
{
this.targetObject=targetObject;
}
publicObjectinvoke(Objectproxy,Methodmethod,Object[]args)throwsThrowable
{
UserBeanImpluserBean=(UserBeanImpl)targetObject;
StringuserName=userBean.getUserName();
Objectresult=null;
//权限判断
if(userName!=null&&!"".equals(userName))
{
result=method.invoke(targetObject,args);
}
returnresult;
}
}
测试类
importjava.lang.reflect.Proxy;
importcom.cignacmc.finance.bean.UserBean;
importcom.cignacmc.finance.bean.UserBeanImpl;
importcom.cignacmc.finance.proxy.UserBeanProxy;
publicclassProxyExe
{
publicstaticvoidmain(String[]args)
{
System.out.println("Proved.............");
UserBeanImpltargetObject=newUserBeanImpl("BobLiang");
UserBeanProxyproxy=newUserBeanProxy(targetObject);
//生成代理对象
UserBeanobject=(UserBean)Proxy.newProxyInstance(targetObject.getClass().getClassLoader(),
targetObject.getClass().getInterfaces(),proxy);
object.addUser();
System.out.println("NOProved.............");
targetObject=newUserBeanImpl();
proxy=newUserBeanProxy(targetObject);
//生成代理对象
object=(UserBean)Proxy.newProxyInstance(targetObject.getClass().getClassLoader(),
targetObject.getClass().getInterfaces(),proxy);
object.addUser();
}
}
输出:
Proved............. thisisaddUser()method! NOProved.............
从上面这个例子可以成功拦截了调用的方法addUser()并对其做了相应的处理
2,通过cglib创建代理类
好处是不要求我们的目标对象实现接口
原始类
publicclassClientBean
{
privateStringname=null;
publicClientBean()
{
}
publicClientBean(Stringname)
{
this.name=name;
}
publicvoidaddClient()
{
System.out.println("thisisaddClient()method!");
}
publicvoiddeleteClient()
{
System.out.println("thisisdeleteClient()method!");
}
publicvoidgetClient()
{
System.out.println("thisisgetClient()method!");
}
publicvoidupdateClient()
{
System.out.println("thisisupdateClient()method!");
}
publicStringgetClientName()
{
returnname;
}
publicvoidsetClientName(Stringname)
{
this.name=name;
}
}
代理类
importjava.lang.reflect.Method;
importcom.cignacmc.finance.bean.ClientBean;
importnet.sf.cglib.proxy.Enhancer;
importnet.sf.cglib.proxy.MethodInterceptor;
importnet.sf.cglib.proxy.MethodProxy;
publicclassCGLibProxyimplementsMethodInterceptor
{
privateObjecttargetObject;
publicObjectcreateProxyObject(ObjecttargetObject)
{
this.targetObject=targetObject;
Enhancerenhancer=newEnhancer();
enhancer.setSuperclass(this.targetObject.getClass());
enhancer.setCallback(this);
returnenhancer.create();
}
publicObjectintercept(Objectproxy,Methodmethod,Object[]args,MethodProxymethodProxy)throwsThrowable
{
ClientBeanclientBean=(ClientBean)targetObject;
StringuserName=clientBean.getClientName();
Objectresult=null;
if(userName!=null&&!"".equals(userName))
{
result=method.invoke(targetObject,args);
}
returnresult;
}
}
测试类
importjava.lang.reflect.Proxy;
importcom.cignacmc.finance.bean.ClientBean;
importcom.cignacmc.finance.bean.UserBean;
importcom.cignacmc.finance.bean.UserBeanImpl;
importcom.cignacmc.finance.proxy.CGLibProxy;
importcom.cignacmc.finance.proxy.UserBeanProxy;
publicclassProxyExe
{
publicstaticvoidmain(String[]args)
{
System.out.println(".............CGLIBProxy....................");
System.out.println("Proved....................");
CGLibProxycproxy=newCGLibProxy();
ClientBeanclientBean=(ClientBean)cproxy.createProxyObject(newClientBean("BobLiang"));
clientBean.addClient();
System.out.println("NOProved....................");
cproxy=newCGLibProxy();
clientBean=(ClientBean)cproxy.createProxyObject(newClientBean());
clientBean.addClient();
}
}
输出:
.............CGLIBProxy.................... Proved.................... thisisaddClient()method! NOProved....................