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

Spring AOP 详解

2012-11-01 18:38 525 查看

此前对于AOP的使用仅限于声明式事务,除此之外在实际开发中也没有遇到过与之相关的问题。最近项目中遇到了以下几点需求,仔细思考之后,觉得采用AOP来解决。一方面是为了以更加灵活的方式来解决问题,另一方面是借此机会深入学习SpringAOP相关的内容。本文是权当本人的自己AOP学习笔记,以下需求不用AOP肯定也能解决,至于是否牵强附会,仁者见仁智者见智。

对部分函数的调用进行日志记录,用于观察特定问题在运行过程中的函数调用情况
监控部分重要函数,若抛出指定的异常,需要以短信或邮件方式通知相关人员
金控部分重要函数的执行时间

事实上,以上需求没有AOP也能搞定,只是在实现过程中比较郁闷摆了。

需要打印日志的函数分散在各个包中,只能找到所有的函数体,手动添加日志。然而这些日志都是临时的,待问题解决之后应该需要清除打印日志的代码,只能再次手动清除^_^!
类似1的情况,需要捕获异常的地方太多,如果手动添加时想到很可能明天又要手动清除,只能再汗。OK,该需求相对比较固定,属于长期监控的范畴,并不需求临时添加后再清除。然而,客户某天要求,把其中20%的异常改为短信提醒,剩下的80%改用邮件提醒。改之,两天后,客户抱怨短信太多,全部改成邮件提醒...
该需求通常用于监控某些函数的执行时间,用以判断系统执行慢的瓶颈所在。瓶颈被解决之后,烦恼同情况1

终于下定决心,采用AOP来解决!代码如下:

切面类TestAspect

Java代码


package com.spring.aop;
/**
* 切面
*
*/
public class TestAspect {

public void doAfter(JoinPoint jp) {
System.out.println("log Ending method: "
+ jp.getTarget().getClass().getName() + "."
+ jp.getSignature().getName());
}

public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
long time = System.currentTimeMillis();
Object retVal = pjp.proceed();
time = System.currentTimeMillis() - time;
System.out.println("process time: " + time + " ms");
return retVal;
}

public void doBefore(JoinPoint jp) {
System.out.println("log Begining method: "
+ jp.getTarget().getClass().getName() + "."
+ jp.getSignature().getName());
}

public void doThrowing(JoinPoint jp, Throwable ex) {
System.out.println("method " + jp.getTarget().getClass().getName()
+ "." + jp.getSignature().getName() + " throw exception");
System.out.println(ex.getMessage());
}

private void sendEx(String ex) {
//TODO 发送短信或邮件提醒
}
}

Java代码


package com.spring.service;
/**
* 接口A
*/
public interface AService {

public void fooA(String _msg);

public void barA();
}

Java代码


package com.spring.service;
/**
*接口A的实现类
*/
public class AServiceImpl implements AService {

public void barA() {
System.out.println("AServiceImpl.barA()");
}

public void fooA(String _msg) {
System.out.println("AServiceImpl.fooA(msg:"+_msg+")");
}
}

Java代码


package com.spring.service;

/**
* Service类B
*/
public class BServiceImpl {

public void barB(String _msg, int _type) {
System.out.println("BServiceImpl.barB(msg:"+_msg+" type:"+_type+")");
if(_type == 1)
throw new IllegalArgumentException("测试异常");
}

public void fooB() {
System.out.println("BServiceImpl.fooB()");
}

}

ApplicationContext

Java代码


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd"
default-autowire="autodetect">
<aop:config>
<aop:aspect id="TestAspect" ref="aspectBean">
<!--配置com.spring.service包下所有类或接口的所有方法-->
<aop:pointcut id="businessService"
expression="execution(* com.spring.service.*.*(..))" />
<aop:before pointcut-ref="businessService" method="doBefore"/>
<aop:after pointcut-ref="businessService" method="doAfter"/>
<aop:around pointcut-ref="businessService" method="doAround"/>
<aop:after-throwing pointcut-ref="businessService" method="doThrowing" throwing="ex"/>
</aop:aspect>
</aop:config>

<bean id="aspectBean" class="com.spring.aop.TestAspect" />
<bean id="aService" class="com.spring.service.AServiceImpl"></bean>
<bean id="bService" class="com.spring.service.BServiceImpl"></bean>

</beans>

测试类AOPTest

Java代码


public class AOPTest extends AbstractDependencyInjectionSpringContextTests {

private AService aService;

private BServiceImpl bService;

protected String[] getConfigLocations() {
String[] configs = new String[] { "/applicationContext.xml"};
return configs;
}

/**
* 测试正常调用
*/
public void testCall()
{
System.out.println("SpringTest JUnit test");
aService.fooA("JUnit test fooA");
aService.barA();
bService.fooB();
bService.barB("JUnit test barB",0);
}

/**
* 测试After-Throwing
*/
public void testThrow()
{
try {
bService.barB("JUnit call barB",1);
} catch (IllegalArgumentException e) {

}
}

public void setAService(AService service) {
aService = service;
}

public void setBService(BServiceImpl service) {
bService = service;
}
}

运行结果如下:

Java代码


log Begining method: com.spring.service.AServiceImpl.fooA
AServiceImpl.fooA(msg:JUnit test fooA)
log Ending method: com.spring.service.AServiceImpl.fooA
process time: 0 ms
log Begining method: com.spring.service.AServiceImpl.barA
AServiceImpl.barA()
log Ending method: com.spring.service.AServiceImpl.barA
process time: 0 ms
log Begining method: com.spring.service.BServiceImpl.fooB
BServiceImpl.fooB()
log Ending method: com.spring.service.BServiceImpl.fooB
process time: 0 ms
log Begining method: com.spring.service.BServiceImpl.barB
BServiceImpl.barB(msg:JUnit test barB type:0)
log Ending method: com.spring.service.BServiceImpl.barB
process time: 0 ms

log Begining method: com.spring.service.BServiceImpl.barB
BServiceImpl.barB(msg:JUnit call barB type:1)
log Ending method: com.spring.service.BServiceImpl.barB
method com.spring.service.BServiceImpl.barB throw exception
测试异常

《Spring参考手册》中定义了以下几个AOP的重要概念,结合以上代码分析如下:

切面(Aspect):官方的抽象定义为“一个关注点的模块化,这个关注点可能会横切多个对象”,在本例中,“切面”就是类TestAspect所关注的具体行为,例如,AServiceImpl.barA()的调用就是切面TestAspect所关注的行为之一。“切面”在ApplicationContext中<aop:aspect>来配置。
连接点(Joinpoint):程序执行过程中的某一行为,例如,AServiceImpl.barA()的调用或者BServiceImpl.barB(String _msg, int _type)抛出异常等行为。
通知(Advice):“切面”对于某个“连接点”所产生的动作,例如,TestAspect中对com.spring.service包下所有类的方法进行日志记录的动作就是一个Advice。其中,一个“切面”可以包含多个“Advice”,例如TestAspect
切入点(Pointcut):匹配连接点的断言,在AOP中通知和一个切入点表达式关联。例如,TestAspect中的所有通知所关注的连接点,都由切入点表达式execution(* com.spring.service.*.*(..))来决定
目标对象(Target Object):被一个或者多个切面所通知的对象。例如,AServcieImpl和BServiceImpl,当然在实际运行时,Spring AOP采用代理实现,实际AOP操作的是TargetObject的代理对象。
AOP代理(AOP Proxy)在Spring AOP中有两种代理方式,JDK动态代理和CGLIB代理。默认情况下,TargetObject实现了接口时,则采用JDK动态代理,例如,AServiceImpl;反之,采用CGLIB代理,例如,BServiceImpl。强制使用CGLIB代理需要将
<aop:config>
proxy-target-class

属性设为true

通知(Advice)类型

前置通知(Before advice):在某连接点(JoinPoint)之前执行的通知,但这个通知不能阻止连接点前的执行。ApplicationContext中在<aop:aspect>里面使用<aop:before>元素进行声明。例如,TestAspect中的doBefore方法
后通知(After advice):当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。ApplicationContext中在<aop:aspect>里面使用<aop:after>元素进行声明。例如,TestAspect中的doAfter方法,所以AOPTest中调用BServiceImpl.barB抛出异常时,doAfter方法仍然执行
返回后通知(After return advice):在某连接点正常完成后执行的通知,不包括抛出异常的情况。ApplicationContext中在<aop:aspect>里面使用<after-returning>元素进行声明。
环绕通知(Around advice):包围一个连接点的通知,类似Web中Servlet规范中的Filter的doFilter方法。可以在方法的调用前后完成自定义的行为,也可以选择不执行。ApplicationContext中在<aop:aspect>里面使用<aop:around>元素进行声明。例如,TestAspect中的doAround方法。
抛出异常后通知(After throwing advice): 在方法抛出异常退出时执行的通知。 ApplicationContext中在<aop:aspect>里面使用<aop:after-throwing>元素进行声明。例如,TestAspect中的doThrowing方法。

切入点表达式

通常情况下,表达式中使用”execution“就可以满足大部分的要求。表达式格式如下:

Java代码


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

modifiers-pattern:方法的操作权限
ret-type-pattern:返回值
declaring-type-pattern:方法所在的包
name-pattern:方法名
parm-pattern:参数名
throws-pattern:异常
其中,除ret-type-pattern和name-pattern之外,其他都是可选的。上例中,execution(* com.spring.service.*.*(..))表示com.spring.service包下,返回值为任意类型;方法名任意;参数不作限制的所有方法。

通知参数

可以通过args来绑定参数,这样就可以在通知(Advice)中访问具体参数了。例如,<aop:aspect>配置如下

Java代码


<aop:config>
<aop:aspect id="TestAspect" ref="aspectBean">
<aop:pointcut id="businessService"
expression="execution(* com.spring.service.*.*(String,..)) and args(msg,..)" />
<aop:after pointcut-ref="businessService" method="doAfter"/>
</aop:aspect>
</aop:config>

TestAspect的doAfter方法中就可以访问msg参数,但这样以来AService中的barA()和BServiceImpl中的barB()就不再是连接点,因为execution(* com.spring.service.*.*(String,..))只配置第一个参数为String类型的方法。其中,doAfter方法定义如下:

Java代码


public void doAfter(JoinPoint jp,String msg)

访问当前的连接点

任何通知(Advice)方法可以将第一个参数定义为
org.aspectj.lang.JoinPoint
类型。
JoinPoint
接口提供了一系列有用的方法, 比如
getArgs()
(返回方法参数)、
getThis()
(返回代理对象)、
getTarget()
(返回目标)、
getSignature()
(返回正在被通知的方法相关信息)和
toString()
(打印出正在被通知的方法的有用信息。

AOP的理解

1、AOP的概述

AOP是一种不同于OOP(面向对象编程)的编程模式,它不是OOP的替代,而是对OOP的一种有益补充。

2、spring AOP的原理

3、spring AOP的实现

在spring2.5中,常用的AOP实现方式有两种。第一种是基于xml配置文件方式的实现,第二种是基于注解方式的实现。

接下来,以具体的是理智讲解这两种方式的使用。

Java代码

package com.zxf.service;

/**
* 业务逻辑接口
* @author z_xiaofei168
*/
public interface AccountService {
public void save(String loginname, String password);
}

它的实现类

package com.zxf.service;
import com.zxf.dao.AccountDao;

/**
* AccountService的实现类
* @author z_xiaofei168
*/
public class AccountServiceImpl implements AccountService {
private  AccountDao accountDao;

public AccountServiceImpl() {}

/** 带参数的构造方法 */
public AccountServiceImpl(AccountDao accountDao){
this.accountDao = accountDao;
}

public void save(String loginname, String password) {
accountDao.save(loginname, password);
throw new RuntimeException("故意抛出一个异常。。。。");
}

/** set方法 */
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
}


对于业务系统来说,AccountServiceImpl类就是目标实现类,它的业务方法,如save()方法的前后或代码会出现异常的地方都是AOP的连接点。

下面是日志服务类的代码:

Java代码

package com.zxf.aspect;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;

/**
* 日志切面类
* @author z_xiaofei168
*/
public class LogAspect {

//任何通知方法都可以将第一个参数定义为 org.aspectj.lang.JoinPoint类型
public void before(JoinPoint call) {
//获取目标对象对应的类名
String className = call.getTarget().getClass().getName();
//获取目标对象上正在执行的方法名
String methodName = call.getSignature().getName();

System.out.println("前置通知:" + className + "类的" + methodName + "方法开始了");
}

public void afterReturn() {
System.out.println("后置通知:方法正常结束了");
}

public void after(){
System.out.println("最终通知:不管方法有没有正常执行完成,一定会返回的");
}

public void afterThrowing() {
System.out.println("异常抛出后通知:方法执行时出异常了");
}

//用来做环绕通知的方法可以第一个参数定义为org.aspectj.lang.ProceedingJoinPoint类型
public Object doAround(ProceedingJoinPoint call) throws Throwable {
Object result = null;
this.before(call);//相当于前置通知
try {
result = call.proceed();
this.afterReturn(); //相当于后置通知
} catch (Throwable e) {

this.afterThrowing();  //相当于异常抛出后通知
throw e;
}finally{
this.after();  //相当于最终通知
}

return result;
}
}


这个类属于业务服务类,如果用AOP的术语来说,它就是一个切面类,它定义了许多通知。Before()、afterReturn()、after()和afterThrowing()这些方法都是通知。

<1>.基于xml配置文件的AOP实现

这种方式在实现AOP时,有4个步骤。

Xml代码
xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd>

<bean id="accountDaoImpl" class="com.zxf.dao.AccountDaoImpl"/>

<bean id="accountService" class="com.zxf.service.AccountServiceImpl">
<property name=" accountDaoImpl " ref=" accountDaoImpl "/>
bean>

<bean id="logAspectBean" class="com.zxf.aspect.LogAspect"/>

<aop:config>

<aop:aspect id="logAspect" ref="logAspectBean">

<aop:pointcut id="allMethod"
expression="execution(* com.zxf.service.*.*(..))"/>

<aop:before method="before" pointcut-ref="allMethod" />

<aop:after-returning method="afterReturn" pointcut-ref="allMethod"/>

<aop:after method="after" pointcut-ref="allMethod"/>

<aop:after-throwing method="afterThrowing" pointcut-ref="allMethod"/>

aop:aspect>
aop:config>
beans>


上述配置针对切入点应用了前置、后置、最终,以及抛出异常后通知。这样在测试执行AccountServiceImpl类的save()方法时,控制台会有如下结果输出。

前置通知:com.zxf.service.AccountServiceImpl类的save方法开始了。

针对MySQL的AccountDao实现中的save()方法。

后置通知:方法正常结束了。

最终通知:不管方法有没有正常执行完成,一定会返回的。

<2>基于注解的AOP的实现

首先创建一个用来作为切面的类LogAnnotationAspect,同时把这个类配置在spring的配置文件中。

在spring2.0以后引入了JDK5.0的注解Annotation的支持,提供了对AspectJ基于注解的切面的支持,从而 更进一步地简化AOP的配置。具体的步骤有两步。

Spring的配置文件是如下的配置:

Xml代码
xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd>

<bean id="accountDao" class="com.zxf.dao.AccountDaoImpl"/>
<bean id="accountService" class="com.zxf.service.AccountServiceImpl">
<property name="accountDao" ref="accountDao"/>
bean>

<bean id="logAspectBean" class="com.zxf.aspect.LogAnnotationAspect"/>

<aop:aspectj-autoproxy/>
beans>


这是那个切面的类LogAnnotationAspect

Java代码

package com.zxf.aspect;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;

/**
* 日志切面类
*/
@Aspect  //定义切面类
public class LogAnnotationAspect {
@SuppressWarnings("unused")
//定义切入点
@Pointcut("execution(* com.zxf.service.*.*(..))")
private void allMethod(){}

//针对指定的切入点表达式选择的切入点应用前置通知
@Before("execution(* com. zxf.service.*.*(..))")
public void before(JoinPoint call) {

String className = call.getTarget().getClass().getName();
String methodName = call.getSignature().getName();

System.out.println("【注解-前置通知】:" + className + "类的"
+ methodName + "方法开始了");
}
//访问命名切入点来应用后置通知
@AfterReturning("allMethod()")
public void afterReturn() {
System.out.println("【注解-后置通知】:方法正常结束了");
}

//应用最终通知
@After("allMethod()")
public void after(){
System.out.println("【注解-最终通知】:不管方法有没有正常执行完成,"
+ "一定会返回的");
}

//应用异常抛出后通知
@AfterThrowing("allMethod()")
public void afterThrowing() {
System.out.println("【注解-异常抛出后通知】:方法执行时出异常了");
}

//应用周围通知
//@Around("allMethod()")
public Object doAround(ProceedingJoinPoint call) throws Throwable{
Object result = null;
this.before(call);//相当于前置通知
try {
result = call.proceed();
this.afterReturn(); //相当于后置通知
} catch (Throwable e) {
this.afterThrowing();  //相当于异常抛出后通知
throw e;
}finally{
this.after();  //相当于最终通知
}

return result;
}
}


备注:输出结果和前面的一样。


spring 中的<aop:advisor>和<aop:aspect>有什么区别?不同之处在那里,请大牛们帮小弟解一下疑惑

Java代码


<tx:advice id="txAdvice" transaction-manager="transactionManager">

<tx:attributes>

<tx:method name="save*" propagation="REQUIRED"/>

<tx:method name="delete*" propagation="REQUIRED"/>

<tx:method name="get*" read-only="true"/>

<tx:method name="find*" read-only="true"/>

</tx:attributes>

</tx:advice>

<bean id="dataAccessThrowsAdvice" class="com.XXX.XXX.aop.exception.XXX" />

<bean id="serviceThrowsAdvice" class="com.XXXXXX.aop.exception.XXX" />

<bean id="actionThrowsAdvice" class="com.XXX.XXXaop.exception.XXX" />

<aop:aspectj-autoproxy/>

<aop:config proxy-target-class="true">

<!-- 切面事务归txAdvice处理 -->

<aop:advisor advice-ref="txAdvice" pointcut="execution(* com.XXX..*.*Dao.*(..))" />

<aop:advisor advice-ref="txAdvice" pointcut="execution(* com.XXX..*.*Manager.*(..))" />

<aop:advisor advice-ref="txAdvice" pointcut="execution(* com.XXX..*.*Action.*(..))" />

<aop:advisor advice-ref="dataAccessThrowsAdvice" pointcut="execution(* com.XXX..*.*Dao.*(..))"/>

<aop:advisor advice-ref="serviceThrowsAdvice" pointcut="execution(* com.XXX..*.*Dao.*(..))" />

<aop:advisor advice-ref="actionThrowsAdvice" pointcut="execution(* com.XXX..*.*Action.*(..))" />

</aop:config>

spring 中的<aop:advisor>和<aop:aspect>有什么区别?不同之处在那里,请大牛们帮小弟解一下疑惑配置文件如上

然后我写了一个类:如下:

Java代码


import org.springframework.aop.MethodBeforeAdvice;

import org.springframework.aop.ThrowsAdvice;

public class BaseBeforeAdvice implements MethodBeforeAdvice{

@Override

public void before(Method arg0, Object[] arg1, Object arg2)

throws Throwable {

System.out.println("******************");

System.out.println(arg0.getName());

System.out.println("******************");

}

小弟不明白这个方法和这种有什么区别:

Java代码


@Aspect

public class wqAdvice {

@Pointcut("execution (* com.XX.*.XX.*(..))")

private void myXXCut(){}

@Before("myXXCut()")

public void doBefore(){

System.out.println("before");

}

@AfterReturning(pointcut="myXXCut()",returning="result")

public void doAfter(String result){

System.out.println("atfer"+result);

}

@AfterThrowing(pointcut=" myXXCut()",throwing="e")

public void doThrowing(Exception e){

System.out.println("Exception"+e.getMessage());

}

}

疑惑颇重,希望大牛们指点小弟一下

1、Adivisor是一种特殊的Aspect,Advisor代表spring中的Aspect

2、区别:advisor只持有一个Pointcut和一个advice,而aspect可以多个pointcut和多个advice
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: