首页 > WEB开发 > 后台开发 > Spring AOP
2014
10-05

Spring AOP

三、使用Spring进行面向切面(AOP)编程

1、Helloworld — Spring AOP编码步骤

业务场景:从Dao层抽取事务管理的操作,封装到自定义Transaction类,并利用Spring AOP技术将事务处理切入到Dao操作前后。

1)搭建开发环境

① 在配置文件中加入xmlns:aop名称空间,如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	   xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       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">

</beans>

② 导入jar包(Spring的第三方类库下)

  • lib/aspectj/aspectjweaver.jar和aspectjrt.jar
  • lib/cglib/cglib-nodep-2.1_3.jar

2)定义PersonDao、PersonDaoImpl(目标类)、Transaction(切面),如下图:

10. Spring AOP5093

3)配置AOP

<!-- 步骤:
	① 配置IoC容器:放入目标类和切面类
 -->
<bean id="personDao" class="org.flyne.dao.PersonDaoImpl"/>
<bean id="transaction" class="org.flyne.dao.Transaction" />

<!-- ② 配置AOP:声明切面 -->
<aop:config>
	<!-- 声明切入点:通过切入点表达式判断对目标类中哪些方法进行拦截 -->
	<aop:pointcut
		expression="execution(* org.flyne.dao.PersonDaoImpl.*(..))"
		id="perform"/>

	<aop:aspect ref="transaction">
		<!-- 前置通知 -->
		<aop:before method="beginTransaction" pointcut-ref="perform"/>
		<!-- 后置通知 -->
		<aop:after-returning method="commit" pointcut-ref="perform"/>
	</aop:aspect>
</aop:config>

配置AOP其实就是声明切面。之所以声明切入点,是为了多个通知能够共享该切入点(配置pointcut-ref属性),否则每个通知都需要自定义切入点(配置pointcut属性)。

4)测试

@Test
public void testPersonDaoAop(){
	ApplicationContext ctx =
			new ClassPathXmlApplicationContext("applicationContext.xml");
	PersonDao dao = (PersonDao)ctx.getBean("personDao");
	dao.save();
}

5)执行过程分析:

① 启动spring容器时发生了什么:

  • 实例化personDao和transaction对象
  • 解析aop:config的配置,当解析到切入点表达式时,将切入点表达式中的类和Spring容器中的类进行匹配,匹配成功生成代理对象(通知+目标方法)
  • 在生成代理对象时,Spring会检查目标类有没有实现接口,如果有,采用JDK Proxy生成代理对象;没有则采用CGlib产生代理对象。

② 测试类中调用ctx.getBean时发生了什么:如果要取的bean有代理对象返回代理对象,没有则返回对象本身。

2、AOP相关配置

本节以XML方式讲解相关配置,本节内容一定要学会查参考文档(Spring2.5中文文档)!

1)声明切入点(aop:pointcut

注:本小节详细内容见参考文档6.2.3.4节。

一个切入点声明有两个部分:一个包含名字和任意参数的签名(在后面的注解方式中看的更清楚),还有一个切入点表达式(重点☆),该表达式决定了我们关注那个方法的执行。

上面说过,切入点就是指定目标类中哪些方法应该被拦截,切入点表达式就是用来匹配这些方法的(类似于用正则表达式去匹配字符串),它是在AspectJ语言中提出的。

Spring AOP支持切入点表达式格式如下:

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:方法名

param-pattern:参数 | throws-pattern:方法抛出异常

说明:

① 除了返回类型、方法名、参数必须要进行匹配外,其他部分都是可选的。

② 以Object.wait(long)方法为例,对应关系如下:

10. Spring AOP6831

Demo1:execution(public * * (..)) 任意公共方法

参数匹配模式说明:()匹配了一个不接受任何参数的方法,而(..)匹配了一个接受任意数量参数的方法(零或者更多)。模式(*)匹配了一个接受一个任何类型的参数的方法。模式(*,String)匹配了一个接受两个参数的方法,第一个可以是任意类型,第二个则必须是String类型。

Demo2:execution(* set*(..)) 任意一个以set开始的方法

Demo3:execution(* com.xyz.service.AccountService.*(..)) AccountService接口定义的任意方法

Demo4:execution(* com.xyz.service.*.*(..)) 在service包中定义的任意方法

Demo5:execution(* com.xyz.service..*.*(..)) 在service包或其子包中定义的任意方法

说明:com.xyz.service.*表示service包下的任意类,com.xyz.service..*表示service包或其子包下的任意类。

当声明了一个切入点后,多个切面和通知就可以共享该切入点。

2)声明通知

注:本小节详细内容见参考文档6.3节。

通知是跟一个切入点表达式关联起来的,并且在切入点匹配的方法执行之前、之后或者前后运行。通知的声明要放在<aop:aspect>中,有以下5种类型的通知:

① 前置通知、后置通知

aop:before:前置通知在目标方法执行前运行。

aop:after-returning:后置通知在目标方法完全执行后运行。关于后置通知的说明:

  • 在后置通知中可以获得目标方法的返回值。方法是在aop:after-returning中增加一个returning属性,如returning=”rtValue”,然后在后置通知中增加一个Object rtValue参数即可。
  • 如果目标方法执行时产生异常,则后置通知不再执行。

看看下图也就明白了,图中的代码是直接使用代理实现事务的,所以在后置通知中当然能获得目标方法执行后的结果,且目标方法产生异常就不执行后置通知。

10. Spring AOP7773

② 异常通知(很重要):

aop:after-throwing:获取目标方法抛出的异常信息。方法是在aop:after-throwing中增加一个throwing属性,如throwing=”ex”,然后在异常通知中增加一个Exception ex参数即可。

③ 最终通知(用的少):

aop:after:不管执行目标方法时是否发生异常都会执行最终通知。

④ 环绕通知(☆):

aop:around:能够控制目标方法是否执行。方法是为环绕通知加入ProceedingJoinPoint joinPoint参数,在环绕通知中就可以通过joinPoint.proceed()控制目标方法是否执行。有点类似于chain.doFilter()、invoke()…..

环绕通知还可以有返回值,这个返回值就是代理对象的方法的返回值。所以。。。你发现,环绕通知可以替代前置、后置通知,甚至是异常通知、最终通知,且可以控制目标方法是否执行。

博主总结:这些通知的关系有点类似于下图,不难看出,环绕通知最灵活,完全可以代替其他通知使用。

10. Spring AOP8241

⑤ 通知方法的参数(扩展):JoinPoint

Spring AOP提供使用org.aspectj.lang.JoinPoint类型获取连接点数据,任何通知方法的第一个参数都可以是JoinPoint(环绕通知是ProceedingJoinPoint,JoinPoint子类)

  • JoinPoint:提供访问当前连接点的目标对象、代理对象、方法参数等数据:
public interface JoinPoint {
    String toString();         //连接点所在位置的相关信息
    String toShortString();     //连接点所在位置的简短相关信息
    String toLongString();     //连接点所在位置的全部相关信息
    Object getThis();         //返回AOP代理对象
    Object getTarget();       //返回目标对象
    Object[] getArgs();       //返回被通知方法参数列表
    Signature getSignature();  //返回当前连接点签名
    SourceLocation getSourceLocation();//返回连接点方法所在类文件中的位置
    String getKind();        //连接点类型
    StaticPart getStaticPart(); //返回连接点静态部分
}
  • ProceedingJoinPoint:用于环绕通知,使用proceed()方法来执行目标方法:
public interface ProceedingJoinPoint extends JoinPoint {
    public Object proceed() throws Throwable;
    public Object proceed(Object[] args) throws Throwable;
}

3、注解方式的Helloworld(@AspectJ支持)

直接在3.1节Helloworld(XML方式)的基础上修改。

1)在Xml配置文件中启用@AspectJ注解支持,并在Spring容器中配置personDao和transaction对象。

<!-- 启用对@AspectJ的支持(自动创建代理对象) -->
<aop:aspectj-autoproxy/>

<bean id="personDao" class="org.flyne.dao.PersonDaoImpl" />
<bean id="transaction" class="org.flyne.dao.Transaction"></bean>

2)在Transaction(切面)中增加如下注解:

@Aspect//加在类上,说明该类是一个切面
public class Transaction {
	/*
	 * 声明切入点:切入点表达式 + 签名(allMethod())
	 */
	@Pointcut("execution(* org.flyne.dao.PersonDaoImpl.*(..))")
	public void allMethod(){}

	@Before("allMethod()")//引用了上面声明的切入点
	public void beginTransaction(){
		System.out.println("开始事务");
	}

	@AfterReturning("allMethod()")
	public void commit(){
		System.out.println("提交事务");
	}
}

说明:

① 切入点的声明包括签名和表达式两部分。一个切入点签名通过一个普通的方法定义来提供,作为切入点签名的方法必须返回void 类型。

3)其他部分不变,执行testPersonDaoAop,结果同3.1节。


留下一个回复

你的email不会被公开。