频道栏目
首页 > 资讯 > Java > 正文

Spring AOP原理及拦截器介绍

17-11-09        来源:[db:作者]  
收藏   我要投稿

Spring AOP原理及拦截器介绍。

原理

AOP(Aspect Oriented Programming),也就是面向方面编程的技术。AOP基于IoC基础,是对OOP的有益补充。

AOP将应用系统分为两部分,核心业务逻辑(Core business concerns)及横向的通用逻辑,也就是所谓的方面Crosscutting enterprise concerns,例如,所有大中型应用都要涉及到的持久化管理(Persistent)、事务管理(Transaction Management)、安全管理(Security)、日志管理(Logging)和调试管理(Debugging)等。

AOP正在成为软件开发的下一个光环。使用AOP,你可以将处理aspect的代码注入主程序,通常主程序的主要目的并不在于处理这些aspect。AOP可以防止代码混乱。

Spring framework是很有前途的AOP技术。作为一种非侵略性的、轻型的AOP framework,你无需使用预编译器或其他的元标签,便可以在Java程序中使用它。这意味着开发团队里只需一人要对付AOP framework,其他人还是像往常一样编程。

AOP概念

让我们从定义一些重要的AOP概念开始。

— 方面(Aspect):一个关注点的模块化,这个关注点实现可能另外横切多个对象。事务管理是J2EE应用中一个很好的横切关注点例子。方面用Spring的Advisor或拦截器实现。

— 连接点(Joinpoint):程序执行过程中明确的点,如方法的调用或特定的异常被抛出。

— 通知(Advice):在特定的连接点,AOP框架执行的动作。各种类型的通知包括“around”、“before”和“throws”通知。通知类型将在下面讨论。许多AOP框架包括Spring都是以拦截器做通知模型,维护一个“围绕”连接点的拦截器链。

— 切入点(Pointcut):指定一个通知将被引发的一系列连接点的集合。AOP框架必须允许开发者指定切入点,例如,使用正则表达式。

— 引入(Introduction):添加方法或字段到被通知的类。Spring允许引入新的接口到任何被通知的对象。例如,你可以使用一个引入使任何对象实现IsModified接口,来简化缓存。

— 目标对象(Target Object):包含连接点的对象,也被称作被通知或被代理对象。

— AOP代理(AOP Proxy):AOP框架创建的对象,包含通知。在Spring中,AOP代理可以是JDK动态代理或CGLIB代理。

— 编织(Weaving):组装方面来创建一个被通知对象。这可以在编译时完成(例如使用AspectJ编译器),也可以在运行时完成。Spring和其他纯Java AOP框架一样,在运行时完成织入。

各种通知类型包括:

— Around通知:包围一个连接点的通知,如方法调用。这是最强大的通知。Aroud通知在方法调用前后完成自定义的行为,它们负责选择继续执行连接点或通过返回它们自己的返回值或抛出异常来短路执行。

— Before通知:在一个连接点之前执行的通知,但这个通知不能阻止连接点前的执行(除非它抛出一个异常)。

— Throws通知:在方法抛出异常时执行的通知。Spring提供强制类型的Throws通知,因此你可以书写代码捕获感兴趣的异常(和它的子类),不需要从Throwable或Exception强制类型转换。

— After returning通知:在连接点正常完成后执行的通知,例如,一个方法正常返回,没有抛出异常。

Around通知是最通用的通知类型。大部分基于拦截的AOP框架(如Nanning和Jboss 4)只提供Around通知。

如同AspectJ,Spring提供所有类型的通知,我们推荐你使用最为合适的通知类型来实现需要的行为。例如,如果只是需要用一个方法的返回值来更新缓存,你最好实现一个after returning通知,而不是around通知,虽然around通知也能完成同样的事情。使用最合适的通知类型使编程模型变得简单,并能减少潜在错误。例如,你不需要调用在around通知中所需使用的MethodInvocation的proceed()方法,因此就调用失败。

切入点的概念是AOP的关键,它使AOP区别于其他使用拦截的技术。切入点使通知独立于OO的层次选定目标。例如,提供声明式事务管理的around通知可以被应用到跨越多个对象的一组方法上。 因此切入点构成了AOP的结构要素。

拦截器(也称拦截机)

拦截机 (Interceptor), 是 AOP (Aspect-OrientedProgramming) 的另一种叫法。AOP本身是一门语言,只不过我们使用的是基于JAVA的集成到Spring 中的 SpringAOP。同样,我们将通过我们的例子来理解陌生的概念。

接口类

Java代码

packagecom.test.TestSpring3;

publicinterfaceUserService//被拦截的接口

...{

publicvoidprintUser(Stringuser);

}

实现类

Java代码

packagecom.test.TestSpring3;

publicclassUserServiceImpimplementsUserService//实现UserService接口

...{

publicvoidprintUser(Stringuser)...{

System.out.println("printUseruser:"+user);//显示user

}

}

AOP拦截器

Java代码

packagecom.test.TestSpring3;

importorg.aopalliance.intercept.MethodInterceptor;

importorg.aopalliance.intercept.MethodInvocation;

publicclassUserInterceptorimplementsMethodInterceptor

//AOP方法拦截器

...{

publicObjectinvoke(MethodInvocationarg0)throwsThrowable...{

try...{

if(arg0.getMethod().getName().equals("printUser"))

//拦截方法是否是UserService接口的printUser方法

...{

Object[]args=arg0.getArguments();//被拦截的参数

System.out.println("user:"+args[0]);

arg0.getArguments()[0]="hello!";//修改被拦截的参数

}

System.out.println(arg0.getMethod().getName()+"---!");

returnarg0.proceed();//运行UserService接口的printUser方法

}catch(Exceptione)...{

throwe;

}

}

}

测试类

Java代码

packagecom.test.TestSpring3;

importorg.springframework.beans.factory.BeanFactory;

importorg.springframework.beans.factory.xml.XmlBeanFactory;

importorg.springframework.context.ApplicationContext;

importorg.springframework.context.support.ClassPathXmlApplicationContext;

importorg.springframework.context.support.FileSystemXmlApplicationContext;

importorg.springframework.core.io.ClassPathResource;

importorg.springframework.core.io.Resource;

importorg.springframework.web.context.support.WebApplicationContextUtils;

publicclassTestInterceptor...{

publicstaticvoidmain(String[]args)...{

ApplicationContextctx=newFileSystemXmlApplicationContext(

"classpath:applicationContext.xml");

//ApplicationContextctx=newClassPathXmlApplicationContext("applicationContext.xml");

UserServiceus=(UserService)ctx.getBean("userService");

us.printUser("shawn");

}

}

配置文件

Xml代码

style="FONT-SIZE:medium">version="1.0"encoding="UTF-8"?>

>

id="userServiceImp"

class="com.test.TestSpring3.UserServiceImp"/>

id="userInterceptor"class="com.test.TestSpring3.UserInterceptor"/>

id="userService"

class="org.springframework.aop.framework.ProxyFactoryBean">

name="proxyInterfaces">

com.test.TestSpring3.UserService

name="target">

local="userServiceImp"/>

name="interceptorNames">

userInterceptor

[xml]view plaincopy

在CODE上查看代码片 派生到我的代码片

输出:

user:shawn

printUser---!

printUseruser:hello!

结论:调用方法的时候传入的值被拦截修改了.

拦截器中的事务管理(事务拦截机)

如果不采用拦截机的机制时,在使用JDBC进行数据库访问时,存在两种情况:

自动提交 这是JDBC驱动默认的模式,每次数据库操作(CRUD)成功完成后,都作为一个单独的事务自动提交,如果未成功完成,即抛出了 SQLException 的话,仅最近的一个操作将回滚。非自动提交 这是想更好的控制事务时需要程序地方式进行控制: 在进行该事务单元的任何操作之前setAutoCommit(false)在成功完成事务单元后commit()在异常发生后rollback()

自动提交模式是不被推荐的,因为每个操作都将产生一个事务点,这对于大的应用来说性能将受到影响;再有,对于常见的业务逻辑,这种模式显得无能为力。比如:

转帐,从A帐户取出100元,将其存入B帐户;如果在这两个操作之间发生了错误,那么用户A将损失了100元,而本来应该给帐户B的,却因为失败给了银行。

所以,建议在所有的应用中,如果使用 JDBC 都将不得不采用非自动提交模式(你们要能发现了在我们的 JDBC 那个例子中,我们采用的就是自动提交模式,我们是为了把精力放在JDBC上,而不是事务处理上),即我们不得不在每个方法中:

Java代码

try{

//在获得连接后,立即通过调用 setAutoCommit(false)将事务处理置为非自动提交模式//PrepareQuerytofetchtheuserInformation

pst=conn.prepareStatement(findByName);

//...conn.commit();

}catch(Exceptionex){

conn.rollback();

throwex;

}finally{

try{

//CloseResultSetandStatement

if(rset!=null)rset.close();

if(pst!=null)pst.close();

}catch(Exceptionex){

ex.printStackTrace();

thrownewException("SQLErrorwhileclosingobjects="+ex.toString());

}

}

这样代码在AOP的倡导者看来是“肮脏”的代码。他们认为,所有的与事务有关的方法都应当可以集中配置(见声明性事务控制),并自动拦截,程序应当关心他们的主要任务,即商业逻辑,而不应和事务处理的代码搅和在一起。

我先看看 Spring 是怎么做到拦截的:

锚点

Spring 内置支持的事务处理拦截机

这里因为要用到JpetStore项目中的代码,我们将 applicationContext.xml 全部内容列出:

WEB-INF/mail.properties

WEB-INF/jdbc.properties

这个配置比想象的要简单的多:

Xml代码

style="FONT-SIZE:medium">

1. 所有的拦截机配置都放在 配置元素中.

2. 下面还是需要理解一下几个有关AOP的专用名词,不过,是挺抽象的,最好能会意出其的用意

pointcut 切入点,比如:updateAccount 方法需要进行事务管理,则这个切入点就是“执行方法体”(execution)。Spring 所有支持的切入点类型在都在 Spring reference: 6.2.3.1. Supported Pointcut Designators 中列出了。advice 要对这个切入点进行什么操作,比如事务控制advisor Spring 特有的概念,将上两个概念合到一个概念中来,即一个 advisor 包含了一个切入点及对这个切入点所实施的操作。

因为 方法执行切入点 execution 为最常见的切入点类型,我们着重介绍一下,execution 的完全形式为:

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)

这是一个正则表达式,其中由 '?' 结尾的部分是可选的。翻译过来就是:

执行(方法访问修饰符? 方法返回类型 声明类型? 方法名(方法参数类型) 抛出异常?)

所有的这些都是用来定义执行切入点,即那些方法应该被侯选为切入点:

方法访问修饰符 即 public, private 等等方法返回类型 即方法返回的类型,如 void, String 等等声明类型 1.5的语法,现在可以先忽略它方法名 方法的名字方法参数类型 方法的参数类型抛出异常 方法声明的抛出的异常

例如,所有dao代码被定义在包 com.xyz.dao 及子包 com.xyz.dao.hibernate, 或者其它,如果还有的话,子包中, 里面定义的是提供DAO功能的接口或类,那么表达式:

execution(* com.xyz.dao..*.*(..))

表示切入点为:执行定义在包 com.xyz.dao 及其子包(因为..所致) 中的任何方法

详细情况可以参见 Spring refernce:6.2.3.4. Examples

因此这个表达式为执行定义在类 PetStoreFacade 及其实现类中的所有方法,采取的动作定义在 txAdvice 中. 关于该 advice 的定义,(见声明性事务控制)一节

锚点

Spring 自定拦截机

来为了进行事务控制,我们只需简单地配置几下,所有的工作都由 Spring 来做。这样固然很好,但有时我们需要有我们特有的控制逻辑。因为Spring 不可能包含所有人需要的所有拦截机。所以它提供了通过程序的方式加以定制的方式。我们的项目中就有这么一个拦截机,在用户确认付款后,将定单信息通过 email 的方式发送给注册用户的邮箱中。

...

红色的注释已经说的很清楚这个 Advisor 了,它的切入点(pointcut) 为 PetStoreFacade 的 void insertOrder(Order order) 方法,采取的动作为引用的 emailAdvice, 下面我们就来看看 emailAdvice:

它给了这个 advice 的实现类为 logic 包中 SendOrderConfirmationEmailAdvice, 该Bean 引用了我们前面定义的邮件发送器(一个 Spring 内置的邮件发送器).

下面看看这个实现类:

public class SendOrderConfirmationEmailAdvice implements AfterReturningAdvice, InitializingBean {

// user jes on localhost

private static final String DEFAULT_MAIL_FROM = "test@pprun.org";

private static final String DEFAULT_SUBJECT = "Thank you for your order!";

private final Log logger = LogFactory.getLog(getClass());

private MailSender mailSender;

private String mailFrom = DEFAULT_MAIL_FROM;

private String subject = DEFAULT_SUBJECT;

public void setMailSender(MailSender mailSender) {

this.mailSender = mailSender;

}

public void setMailFrom(String mailFrom) {

this.mailFrom = mailFrom;

}

public void setSubject(String subject) {

this.subject = subject;

}

public void throws Exception {

if (this.mailSender == null) {

throw new IllegalStateException("mailSender is required");

}

}

/**

*

* @param returnValue 被拦截的方法的返回值

* @param m 被拦截的方法的所有信息(Method类封装了这些信息)

* @param args 被拦截的方法的所有参数组成的数组

* @param target 目标对象,对于方法执行来说,即是方法所在的类的实例(与 this 同,批当前对象)

* @throws java.lang.Throwable

*/

public void afterReturning(Object returnValue, Method m, Object[] args, Object target) throws Throwable {

// 我们被拦截的方法为 void insertOrder(Order order),方法只有一个参数,所以可知数据的第1个元素即是被传进的 order 对象

// 得到了order 对象,就可以将 order 对应的帐户名及帐单号发送到邮件中,以便确认无误。

Order order = (Order) args[0];

Account account = ((PetStoreFacade) target).getAccount(order.getUser().getUsername());

// don't do anything if email address is not set

if (account.getEmail() == null || account.getEmail().length() == 0) {

return;

}

StringBuffer text = new StringBuffer();

text.append("Dear ").append(account.getFirstname()).

append(' ').append(account.getLastname());

text.append(", thank your for your order from JPetStore. " +

"Please note that your order number is ");

text.append(order.getId());

SimpleMailMessage mailMessage = new SimpleMailMessage();

mailMessage.setTo(account.getEmail());

mailMessage.setFrom(this.mailFrom);

mailMessage.setSubject(this.subject);

mailMessage.setText(text.toString());

try {

this.mailSender.send(mailMessage);

} catch (MailException ex) {

// just log it and go on

logger.warn("An exception occured when trying to send email", ex);

}

}

}

1.红色的内容即为反向注入的mailSender属性

2.蓝色的内容为 Spring Bean 的一个通用的接口InitializingBean,实现类需要实现该接口定义的方法afterPropertiesSet(),该方法中一般是在Bean 被初始化后并设置了所有的 setter 注入后调用的。所以这里是保证邮件发送器配置正确。因为如果没有配置正确,下面的工作是无法进行的,所以与其等那时抛出异常,还不如早早地在部署时就告知(通过抛出 IllegalStateException 来提示)

3.绿色的内容为这个 Advise 的核心,即在切入点被切入后将采用的动作。因为 Advise 也同样有多种类型,比如我们这里的“方法正常返回”,“方法执行前”,“方法执行后”,“环绕在方法执行前后”,“方法抛出异常时”等等(详情参见 Spring Reference:6.2.4. Declaring advice)。但是我们的逻辑为在用户确认定单并且执行成功(所谓的成功是指将这一定单插入到了表 Order 中了)后,将发送一确认信。所以”方法正常返回“完全符合我们的要求。

接口AfterReturningAdvice即是 Spring中表示”方法正常返回“ 这一语义的 Advice, 所以我们实现这个接口及其必须的方法afterReturning.

方法代码的工作其实并不重要,只要我们理解这些“魔法”一样的技术后,实现代码是很简单的。值得提及的是这个方法的参数,这些参数是封装了切入点的所有信息,请见上面的注释。在我们的实现中只使用了被拦截方法的参数,在复杂的 Advice 实现中可能会用到切入点所有信息。

---------------------------------------------------------------

补充说明

面向切面编程:Aspect Oriented Programming

AOP是OOP的延续,是(Aspect Oriented Programming)的缩写,意思是面向切面编程。

AOP利用一种称为“横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的行为封装到一个可重用模块,并将其命名为“Aspect”,即方面。所谓“方面”,简单地说,就是将那些与业务无关,却为业务模块所共同调用的逻辑或责任,例如事务处理、日志管理、权限控制等,封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。进而改变这些行为的时候不影响业务逻辑的代码。

AOP(Aspect-Oriented Programming,面向方面编程),可以说是OOP(Object-Oriented Programing,面向对象编程)的补充和完善。OOP引入封装、继承和多态性等概念来建立一种对象层次结构,用以模拟公共行为的一个集合。当我们需要为分散的对象引入公共行为的时候,OOP则显得无能为力。也就是说,OOP允许你定义从上到下的关系,但并不适合定义从左到右的关系。例如日志功能。日志代码往往水平地散布在所有对象层次中,而与它所散布到的对象的核心功能毫无关系。对于其他类型的代码,如安全性、异常处理和透明的持续性也是如此。这种散布在各处的无关的代码被称为横切(cross-cutting)代码,在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。

AOP代表的是一个横向的关系,如果说“对象”是一个空心的圆柱体,其中封装的是对象的属性和行为;那么面向方面编程的方法,就仿佛一把利刃,将这些空心圆柱体剖开,以获得其内部的消息。而剖开的切面,也就是所谓的“方面”了。然后它又以巧夺天功的妙手将这些剖开的切面复原,不留痕迹。

使用“横切”技术,AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处都基本相似。

在Spring中提供了面向切面编程的丰富支持,允许通过分离应用的业务逻辑与系统级服务(例如审计(auditing)和事务(transaction)管理)进行内聚性的开发。应用对象只实现它们应该做的——完成业务逻辑——仅此而已。它们并不负责(甚至是意识)其它的系统级关注点,例如日志或事务支持。

不用AOP的话,我们得通过硬编码的方式将事物处理写在方法中,有了AOP之后,我们只需要在spring的配置文件中配置一下事物就可以了,这就叫声明式事物处理.一般配置时是通过配置匹配某个格式的方法名,当运行到这种方法的时候spring就会拦截下它来,并给它加上事物的处理了

关于filter

Spring Web MVC的处理器拦截器类似于Servlet开发中的过滤器Filter,用于对处理器进行预处理和后处理。

常见应用场景

1、日志记录:记录请求信息的日志,以便进行信息监控、信息统计、计算PV(Page View)等。

2、权限检查:如登录检测,进入处理器检测检测是否登录,如果没有直接返回到登录页面;

3、性能监控:有时候系统在某段时间莫名其妙的慢,可以通过拦截器在进入处理器之前记录开始时间,在处理完后记录结束时间,从而得到该请求的处理时间(如果有反向代理,如apache可以自动记录);

4、通用行为:读取cookie得到用户信息并将用户对象放入请求,从而方便后续流程使用,还有如提取Locale、Theme信息等,只要是多个处理器都需要的即可使用拦截器实现。

5、OpenSessionInView:如Hibernate,在进入处理器打开Session,在完成后关闭Session。

…………本质也是AOP(面向切面编程),也就是说符合横切关注点的所有功能都可以放入拦截器实现。

相关TAG标签
上一篇:Java静态与同步
下一篇:win10 通用应用 切换主题
相关文章
图文推荐

关于我们 | 联系我们 | 广告服务 | 投资合作 | 版权申明 | 在线帮助 | 网站地图 | 作品发布 | Vip技术培训 | 举报中心

版权所有: 红黑联盟--致力于做实用的IT技术学习网站