Spring之AOP

所谓Aop,即Aspect Oriented Programming,面向方面编程。这个概念听起来可能有点抽象,所以在这里我们先引入Aop中的一些术语并结合它们来对Aop思想做一个整体的解释: 

1.Aspect(切面):横切性关注点的抽象即为切面。记得有这么个俗语,意思就是一根筷子容易折断,而一捆筷子就不容易折断了,说的是团结的力量。那么,现在,大家想一下,如果我们手里拿着一把刀,要斩断一捆筷子(由十根筷子组成),我们要怎么办呢?答案是明显的,就是横着砍下去!我想应该没有人会选择竖着砍下去的,呵呵。那么,在砍的那个过程中,我们要关注的地方有哪些呢?或者说我们要砍断的筷子有哪几根呢?答案还是那么明显,当然是要把十根筷子都砍断咯。那么,对于这捆筷子中的每一根都可以理解为是一个横切性关注点。由于个人的想象力有限,所以举的例子不免有些牵强。好了,我们继续来解释切面的概念,那么在编程中,横切性关注点是什么呢?实际上,简单的来说,就是程序运行时我们要对哪些方法进行拦截,拦截后要做些什么事(例如可以对部分函数的调用进行日志记录),这些都算是横切性关注点。上面说了切面是横切性关注点的抽象,这里,我们可以结合面向对象的概念来理解。大家都知道的是,类是物体特征的抽象,所以结合这个来理解切面的意思应该会容易一点。 
2.Joinpoint(连接点):顾名思义,连接点的作用就是可以在上面接一点东西。实际上,Aop中的连接点的意思也差不多,就是那些被我们拦截到的点(Spring中,这些点指的就是方法,因为Spring只支持方法类型的拦截点),那么我们拦截一个方法的目的是什么呢?当然是为了附带做一些事(即执行一些代码)啦,或者说是接入一些执行代码,所以被拦截的方法我们可以称之为接入点。 
3.Pointcut(切点):切点用于指定或定义希望在程序流中拦截的Joinpoint。切点还包含一个通知(所谓通知,即拦截到Joinpoint后我们所要附带做的事,如方法的调用等等),该通知在连接点被拦截时发生,因此如果在一个调用的特定方法上定义一个切点,那么在调用该方法时,Aop框架将截获该切点,并执行切点所关联的通知。 
4.Advice(通知):所谓通知,就是拦截到Joinpoint后我们所要做的事情(通常就是执行一些代码)。通知可分为前置通知、后置通知、异常通知、最终通知、环绕通知(对于各种通知的解释,这里不再做详细介绍了)。 
5.Target Object(目标对象):包含连接点的对象,也称之为被通知或被代理对象。因为Aop是需要通过代理机制来实现的。对于目标对象的方法调用,实际上是通过它的代理对象来进行的,因此可以在代理对象中对调用操作进行拦截,然后执行切点的相关通知。 
6.Aop Proxy(Aop代理):Aop框架为目标对象创建的代理对象,包含了通知。在Spring中,AOP代理可以是JDK动态代理或CGLIB代理。 
7.Weave(织入):将Aspect运用到Target对象并导致proxy对象创建的这个过程。 
8.Introduction(引入):添加方法或属性到被通知的类。在不修改任何源代码的情况下,Introduction可以在程序运行期间动态地为类添加一些方法和属性。 

接着,我们结合上面的提到的Aop的这些术语来对Aop做一番解释。我们不妨引入一个需求(或者可称之为Aop思想的动机,即为什么要提出Aop思想):监控部分重要函数的执行时间,并以短信的方式通知相关人员。那么这个需求该怎么实现呢?或许会有朋友说,这个太简单了,不就是直接在方法体内添加发送短信的代码嘛。但是,如果要监控的函数很多呢?逐个地方手动地进行添加?如果需求突然变更了,要求将发短信改为发邮件,那该怎么办呢?如果要监控的目标函数也要发生改变那又该怎么办呢?所以,将代码写死绝对不会是一个程序设计的好思想,也不会是一个合格的编程人员应该做的。那么,Aop思想将可以用来很好地解决这个问题,它对于该需求的实现过程是这样的: 
(1)将要监控的函数定义为切点,也就是说把要监控的函数当作接入点,并且定义到配置文件中去,这样就我们可以动态地修改切点了。 
(2)为包含接入点的目标对象定义Aop代理(实际上可以只定义一个Aop代理来作为多个目标对象的代理) 
(3)将发送短信的代码封装为一个类的方法(我们称之为通知方法),并且抽取该类的接口,然后我们在实际编码中使用的是接口,并把具体的通知类定义到配置文件中去,这样有便于我们以后做扩展。 
(4)实现Aop代理的invoke方法,并在invoke中调用接入点方法,且在调用之后接着调用通知的方法(也就是发短信或者发邮件的方法)。 
好了,对于刚才我们引入的需求,运用Aop思想,其大致的实现就是上面几步。那么,当需求变更为“发送邮件”时,我们只需要改变一下配置文件中的通知类就可以了;当要监控的函数需要改变时,我们也只需要改一下配置文件中对于切点的配置就可以了。 
下面,我们来看一下Aop思想的一个简单实现的例子(该例子利用Aop思想完成一件事就是当我们调用UserDao对象的saveUser方法时,系统会进行日志记录): 
1. 编写IOC容器要用到的配置文件(用来配置bean对象和切点)——beans.xml 
<?xml version="1.0" encoding="UTF-8"?> 
<beans xmlns="http://www.springframework.org/schema/beans"> 
<bean id="UserDao" class="AopTest.UserDao"></bean> 
<bean id="LogTool" class="AopTest.LogTool"></bean> 
<aop id="logging" ref="LogTool" pointCut="UserDao.*" method="before"></aop> 
</beans> 

2.编写bean节点配置类,用来封装配置文件中所配置的bean对象的信息——BeanNode.java 
package AopTest; 

/** 
* xml配置文件中配置的bean节点的映射对象 
* @author Administrator 

*/ 
public class BeanNode 

private String id; 
private String className; 

public BeanNode() 

super(); 


public BeanNode(String id, String className) 

this.id=id; 
this.className=className; 


public String getId() { 
return id; 

public void setId(String id) { 
this.id = id; 

public String getClassName() { 
return className; 

public void setClassName(String className) { 
this.className = className; 





3.编写aop节点配置类——AopNode.java 
package AopTest; 

/** 
* xml配置文件中配置的aop节点的映射对象 
* @author Administrator 

*/ 
public class AopNode 

private String id; 
private String ref; 
private String pointCut; 
private String method; 
public String getId() { 
return id; 

public void setId(String id) { 
this.id = id; 

public String getRef() { 
return ref; 

public void setRef(String ref) { 
this.ref = ref; 

public String getPointCut() { 
return pointCut; 

public void setPointCut(String pointCut) { 
this.pointCut = pointCut; 

public String getMethod() { 
return method; 

public void setMethod(String method) { 
this.method = method; 





4.编写自定义IOC容器,用来管理配置文件中配置的bean对象——IocContainer.java 
package AopTest; 

import org.dom4j.Document; 
import org.dom4j.Element; 
import org.dom4j.XPath; 
import org.dom4j.io.SAXReader; 

/** 
* 自定义Ioc容器 
* @author Administrator 

*/ 
public class IocContainer 

private static java.util.Map<String,BeanNode> beanMap=new java.util.HashMap<String, BeanNode>(); 
private static java.util.List<AopNode> aopNodes=new java.util.ArrayList<AopNode>(); 


/** 
* 解析配置好的xml文件,读取bean节点对象以及aop节点对象,并添加到队列中 
* @param fileName 
*/ 
private void readXML(String fileName) 

//创建一个文件xml文件读取器 
SAXReader saxReader=new SAXReader(); 
Document document=null; 

try 

//取得类的类装载器,通过类装载器,取得类路径下的文件 
java.net.URL xmlPath=this.getClass().getClassLoader().getResource(fileName); 
//读取文件内容 
System.out.println("xmlPath="+xmlPath); 
document=saxReader.read(xmlPath); 
//创建一个map对象 
java.util.Map<String, String> nameSpaceMap=new java.util.HashMap<String,String>(); 
//加入命名空间 
nameSpaceMap.put("nameSpace", "http://www.springframework.org/schema/beans"); 

//创建beans/bean查询路径 
XPath xpath1=document.createXPath("nameSpace:beans/nameSpace:bean"); 
//设置命名空间 
xpath1.setNamespaceURIs(nameSpaceMap); 
//获取文档下所有bean节点 
java.util.List<Element> beans=xpath1.selectNodes(document); 
System.out.println("bean对象的个数为:"+beans.size()); 

for(int i=0; i<beans.size(); i++) 

Element element=beans.get(i); 
//获取bean节点对象的id属性 
String id=element.attributeValue("id"); 
System.out.println("bean对象的id为:"+id); 
//获取bean节点对象的className属性 
String className=element.attributeValue("class"); 
//创建bean节点对象 
BeanNode beanNode=new BeanNode(id, className); 
beanMap.put(beanNode.getId(), beanNode);


//创建beans/aop查询路径 
XPath xpath2=document.createXPath("nameSpace:beans/nameSpace:aop"); 
//设置命名空间 
xpath2.setNamespaceURIs(nameSpaceMap); 
//获取文档下所有aop节点 
java.util.List<Element> aops=xpath2.selectNodes(document); 

for(int i=0; i<aops.size(); i++) 

Element element=aops.get(i); 
//获取aop节点对象的id属性 
String id=element.attributeValue("id"); 
//获取aop节点对象的ref属性 
String ref=element.attributeValue("ref"); 
//获取aop节点对象的pointCut属性 
String pointCut=element.attributeValue("pointCut"); 
//获取aop节点对象的method属性 
String method=element.attributeValue("method"); 

//创建aop节点对象 
AopNode aopNode=new AopNode(); 
aopNode.setId(id); 
aopNode.setRef(ref); 
aopNode.setPointCut(pointCut); 
aopNode.setMethod(method); 
aopNodes.add(aopNode); 





catch(Exception e) 

e.printStackTrace(); 



/** 
* 获取指定对象的代理对象,可以通过该代理对象执行指定对象的所有方法 
* @param id 
* @return 
* @throws Exception 
*/ 
public  Object getProxyOfBean(String id) throws Exception 

//解析beans.xml文件 
readXML("AopTest/beans.xml"); 
BeanNode beanNode=beanMap.get(id); 
if(null==beanNode) 

throw new Exception("没有这个东西!id="+id); 


//得到配置的bean对象,注意,此处调用默认无参构造器 
//通过反射机制实例化指定的bean对象 
Class c=Class.forName(beanNode.getClassName()); 
Object  bean=c.newInstance(); 
//向代理对象传入代理配置参数(即aop节点中配置的参数) 
UserDaoProxy.aopNodes=aopNodes; 
//获取指定bean对象的代理对象 
Object proxy=UserDaoProxy.getProxy(bean); 
return proxy; 



public static BeanNode getBeanNodeById(String id) 

return beanMap.get(id); 


//获得指定id的bean对象 
public static Object getBean(String id) throws Exception 

BeanNode beanMapping=beanMap.get(id); 
if(null==beanMapping) 

throw new Exception("没有这个东西!id="+id); 

//得到配置的bean对象,注意,此处调用默认无参构造器 
Class c=Class.forName(beanMapping.getClassName()); 
Object  bean=c.newInstance();
return bean; 


5.编写目标对象接口——IUserDao.java 
package AopTest; 

/** 
* userDao接口 
* @author Administrator 

*/ 
public interface IUserDao 

/** 
* 根据用户名和密码,保存用户对象 
* @param userName 
* @param userPwd 
*/ 
void saveUser(String userName, String userPwd); 

/** 
* 根据用户id删除用户对象 
* @param id 
*/ 
void deleteUser(int id); 


6.编写目标对象类(实现目标对象接口类)——UserDao.java 
package AopTest; 

public class UserDao implements IUserDao 

/** 
* 根据用户名和密码,保存用户对象 
* @param userName 
* @param userPwd 
*/ 
public void saveUser(String userName, String userPwd) 

System.out.println("保存用户对象成功,用户名:"+userName+" 密码:"+userPwd); 


/** 
* 根据用户id删除用户对象 
* @param id 
*/ 
public void deleteUser(int id) 

System.out.println("删除用户对象成功,用户编号:"+id); 



7.编写日志工具类接口,用来记录某方法调用的信息——ILogTool.java 
package AopTest; 

/** 
* 日志记录工具接口 
* @author Administrator 

*/ 
public interface ILogTool 

/** 
* 在方法执行前记录日志 
* @param m: 正在执行的方法 
* @param args: 方法中的参数 
*/ 
void before(java.lang.reflect.Method m, Object[] args); 

/** 
* 在方法执行之后记录日志 
* @param m: 正在执行的方法 
* @param args:方法中的参数 
*/ 
void after(java.lang.reflect.Method m, Object[] args); 


8.编写日志工具实现类——LogTool.java 
package AopTest; 

/** 
* 日志工具实现类 
* @author Administrator 

*/ 
public class LogTool implements ILogTool 

/** 
* 在方法执行前记录日志 
* @param m: 正在执行的方法 
* @param args: 方法中的参数 
*/ 
public void before(java.lang.reflect.Method m, Object[] args) 

//获取方法名字 
String methodName=m.getName(); 
//获取传入方法中的参数值 
String paramValue=""; 
for(int i=0; i<args.length; i++) 

Object param=args[i]; 
paramValue=paramValue+param.toString()+","; 

System.out.println("before execute "+methodName+" method, params:"+paramValue); 


/** 
* 在方法执行之后记录日志 
* @param m: 正在执行的方法 
* @param args:方法中的参数 
*/ 
public void after(java.lang.reflect.Method m, Object[] args) 

//获取方法名 
String methodName=m.getName(); 
//获取传入方法中的参数值 
String paramValue=null; 
for(int i=0; i<args.length; i++) 

Object param=args[i]; 
paramValue=" "+param.toString(); 

System.out.println("after execute "+methodName+" params:"+paramValue); 



9.编写目标对象的代理类——UserDaoProxy.java 
package AopTest; 

import java.lang.reflect.Method; 

/** 
* 通用代理类的实现 
* Aop要通过jdk中的“代理”机制来实现 
* 实际上Aop思想的实现就是将包含接入点的目标对象交给其代理对象来管理,客户端通过目标对象的代理对象来调用目标对象提供的接入点方法, 
* 这样我们可以在代理对象中调用接入点方法时做其他一些处理,这里我们称之为通知 
* @author Administrator 

*/ 
public class UserDaoProxy implements java.lang.reflect.InvocationHandler 

//被切点对象的配置列表,里面是xml中读取的AopXmlMapping对象 
static java.util.List<AopNode> aopNodes; 
//被代理的对象(即目标对象) 
static Object proxiedObj; 

/** 
* 获取指定对象的代理对象 
* @param obj 
* @return 
*/ 
public static Object getProxy(Object obj) 

return java.lang.reflect.Proxy.newProxyInstance(obj.getClass().getClassLoader(), 
obj.getClass().getInterfaces(), 
new UserDaoProxy(obj)); 


private UserDaoProxy(Object obj) 

this.proxiedObj=obj; 


/** 
* 实现InvocationHandler接口的invoke方法 
* 代理对象被调用时,实际上是执行了该方法 
* 因此那些被代理执行的方法应该写在该方法体内 
*/ 
public Object invoke(Object proxy, Method method, Object[] args) 
throws Throwable 

Object result=null; 
try 

ILogTool logTool=null; 
boolean needAop=false; 
for(int i=0; i<aopNodes.size(); i++) 

//获取aop节点的配置信息 
AopNode aopNode=aopNodes.get(i); 
//获取切入点或称之为连接点(也就是要切入到哪个对象的哪个方法上去) 
String pointCut=aopNode.getPointCut(); 
//获取包含通知方法的对象(我们在aop节点中配置的是LogTool对象) 
String logToolName=aopNode.getRef(); 
//找到日志工具对象——包含通知方法的对象 
logTool=(LogTool)IocContainer.getBean(logToolName); 
int limit=pointCut.indexOf("."); 
//获取目标对象的beanId 
String destPointBeanId=pointCut.substring(0,limit);//匹配beanid部分 

//1.获取连接点(Spring中只支持方法类型的连接点) 
String methodRane=pointCut.substring(limit+1,pointCut.length());//匹配方法部分 
System.out.println("destPointBeanId:  "+destPointBeanId); 
//获取目标对象的节点配置信息 
BeanNode beanNode=IocContainer.getBeanNodeById(destPointBeanId); 
System.out.println("目标对象的类名为: "+beanNode.getClassName()); 
System.out.println("被代理执行的对象的类名为: "+this.proxiedObj.getClass().getName()); 
//找到了要切入的目标对象! 
if(null!=beanNode&&beanNode.getClassName().equals(this.proxiedObj.getClass().getName())){ 
if(methodRane.equals("*"))//被代理的对象的所有方法都要接受切入! 

//2.获取连接点方法的名字,然后执行该方法 
if(aopNode.getMethod().equals("before")) 

//方法前切入(即前置通知) 
//这里的method实际上就是客户端调用的被代理对象的方法 
logTool.before(method, args);//3.执行要切入的方法(即通知) 
//调用切入的目标对象(也就是被代理的对象)的方法(即连接点方法) 
//invoke方法的第一个参数为method方法的拥有者,第二个参数为传入method方法的参数对象 
result = method.invoke(this.proxiedObj, args);//4.通过代理对象执行连接点方法 
needAop=true; 
break; 

else 

//方法后切入(即后置通知) 
//调用切入的目标对象(也就是被代理的对象)的方法(即连接点方法) 
result = method.invoke(proxy, args); 
logTool.after(method, args);//调用后切入! 
needAop=true; 
break; 





if(!needAop) 

result = method.invoke(proxy, args);//调用目标对象(也就是被代理的对象)的方法 

}  
catch (Exception e) 

e.printStackTrace(); 
throw new RuntimeException("invocation : " + e.getMessage()); 
}  
return result; 



10.编写测试类——Tester.java 
package AopTest; 

public class Tester 

public static void main(String[] args) 

try 

IocContainer ic=new IocContainer(); 
//获取UserDao对象的代理对象 
IUserDao userDao=(IUserDao)ic.getProxyOfBean("UserDao"); 
userDao.saveUser("zzq", "123456"); 

catch(Exception e) 

e.printStackTrace(); 





11.测试结果如下: 
xmlPath=file:/F:/myeclipse6_workspace/netjava_web_project/WebRoot/WEB-INF/classes/AopTest/beans.xml 
bean对象的个数为:2 
bean对象的id为:UserDao 
bean对象的id为:LogTool 
destPointBeanId:  UserDao 
目标对象的类名为: AopTest.UserDao 
被代理执行的对象的类名为: AopTest.UserDao 
before execute saveUser method, params:zzq,123456, 
保存用户对象成功,用户名:zzq 密码:123456 


最后,想稍微解释一下以上的实现代码,实际上Aop思想的实现主要是依赖于IOC思想以及java中的代理机制。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值