您的位置:首页 > 编程语言 > Java开发

Spring之AOP

2016-03-17 22:26 513 查看
所谓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中的代理机制。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: