Spring除了支持Schema方式配置AOP,还支持注解方式:使用@AspectJ风格的切面声明。
1 启用对@AspectJ的支持
Spring默认不支持@AspectJ风格的切面声明,为了支持需要使用如下配置:
java代码:
<aop:aspectj-autoproxy/>
这样Spring就能发现@AspectJ风格的切面并且将切面应用到目标对象。
2 声明切面
@AspectJ风格的声明切面非常简单,使用@Aspect注解进行声明:
java代码:
@Aspect()
Public class Aspect{
……
}
然后将该切面在配置文件中声明为Bean后,Spring就能自动识别并进行AOP方面的配置:
java代码:
<bean id="aspect" class="……Aspect"/>
该切面就是一个POJO,可以在该切面中进行切入点及通知定义,接着往下看吧。
3 声明切入点
@AspectJ风格的命名切入点使用org.aspectj.lang.annotation包下的@Pointcut+方法(方法必须是返回void类型)实现。
java代码:
@Pointcut(value="切入点表达式", argNames = "参数名列表")
public void pointcutName(……) {}
value:指定切入点表达式;
argNames:指定命名切入点方法参数列表参数名字,可以有多个用“,”分隔,这些参数将传递给通知方法同名的参数,同时比如切入点表达式“args(param)”将匹配参数类型为命名切入点方法同名参数指定的参数类型。
pointcutName:切入点名字,可以使用该名字进行引用该切入点表达式。
java代码:
@Pointcut(value="execution(* cn.javass..*.sayAdvisorBefore(..)) && args(param)", argNames = "param")
public void beforePointcut(String param) {}
定义了一个切入点,名字为“beforePointcut”,该切入点将匹配目标方法的第一个参数类型为通知方法实现中参数名为“param”的参数类型。
4 声明通知
@AspectJ风格的声明通知也支持5种通知类型:
一、前置通知:使用org.aspectj.lang.annotation 包下的@Before注解声明;
java代码:
@Before(value = "切入点表达式或命名切入点", argNames = "参数列表参数名")
value:指定切入点表达式或命名切入点;
argNames:与Schema方式配置中的同义。
接下来示例一下吧:
1、定义接口和实现,在此我们就使用Schema风格时的定义;
2、定义切面:
java代码:
package cn.javass.spring.chapter6.aop;
import org.aspectj.lang.annotation.Aspect;
@Aspect
public class HelloWorldAspect2 {
}
3、定义切入点:
java代码:
@Pointcut(value="execution(* cn.javass..*.sayAdvisorBefore(..)) && args(param)", argNames = "param")
public void beforePointcut(String param) {}
4、定义通知:
java代码:
@Before(value = "beforePointcut(param)", argNames = "param")
public void beforeAdvice(String param) {
System.out.println("===========before advice param:" + param);
}
5、在chapter6/advice2.xml配置文件中进行如下配置:
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-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
<aop:aspectj-autoproxy/>
<bean id="helloWorldService"
class="cn.javass.spring.chapter6.service.impl.HelloWorldService"/>
<bean id="aspect"
class="cn.javass.spring.chapter6.aop.HelloWorldAspect2"/>
</beans>
6、测试代码cn.javass.spring.chapter6.AopTest:
java代码:
@Test
public void testAnnotationBeforeAdvice() {
System.out.println("======================================");
ApplicationContext ctx = new ClassPathXmlApplicationContext("chapter6/advice2.xml");
IHelloWorldService helloworldService = ctx.getBean("helloWorldService", IHelloWorldService.class);
helloworldService.sayBefore("before");
System.out.println("======================================");
}
将输出:
|
切面、切入点、通知全部使用注解完成:
1)使用@Aspect将POJO声明为切面;
2)使用@Pointcut进行命名切入点声明,同时指定目标方法第一个参数类型必须是java.lang.String,对于其他匹配的方法但参数类型不一致的将也是不匹配的,通过argNames = "param"指定了将把该匹配的目标方法参数传递给通知同名的参数上;
3)使用@Before进行前置通知声明,其中value用于定义切入点表达式或引用命名切入点;
4)配置文件需要使用<aop:aspectj-autoproxy/>来开启注解风格的@AspectJ支持;
5)需要将切面注册为Bean,如“aspect”Bean;
6)测试代码完全一样。
二、后置返回通知:使用org.aspectj.lang.annotation 包下的@AfterReturning注解声明;
java代码:
@AfterReturning(
value="切入点表达式或命名切入点",
pointcut="切入点表达式或命名切入点",
argNames="参数列表参数名",
returning="返回值对应参数名")
value:指定切入点表达式或命名切入点;
pointcut:同样是指定切入点表达式或命名切入点,如果指定了将覆盖value属性指定的,pointcut具有高优先级;
argNames:与Schema方式配置中的同义;
returning:与Schema方式配置中的同义。
java代码:
@AfterReturning(
value="execution(* cn.javass..*.sayBefore(..))",
pointcut="execution(* cn.javass..*.sayAfterReturning(..))",
argNames="retVal", returning="retVal")
public void afterReturningAdvice(Object retVal) {
System.out.println("===========after returning advice retVal:" + retVal);
}
其中测试代码与Schema方式几乎一样,在此就不演示了,如果需要请参考AopTest.java中的testAnnotationAfterReturningAdvice测试方法。
三、后置异常通知:使用org.aspectj.lang.annotation 包下的@AfterThrowing注解声明;
java代码:
@AfterThrowing (value="切入点表达式或命名切入点",
pointcut="切入点表达式或命名切入点",
argNames="参数列表参数名",
throwing="异常对应参数名")
value:指定切入点表达式或命名切入点;
pointcut:同样是指定切入点表达式或命名切入点,如果指定了将覆盖value属性指定的,pointcut具有高优先级;
argNames:与Schema方式配置中的同义;
throwing:与Schema方式配置中的同义。
java代码:
@AfterThrowing(
value="execution(* cn.javass..*.sayAfterThrowing(..))",
argNames="exception", throwing="exception")
public void afterThrowingAdvice(Exception exception) {
System.out.println("===========after throwing advice exception:" + exception);
}
其中测试代码与Schema方式几乎一样,在此就不演示了,如果需要请参考AopTest.java中的testAnnotationAfterThrowingAdvice测试方法。
四、后置最终通知:使用org.aspectj.lang.annotation 包下的@After注解声明;
java代码:
@After (
value="切入点表达式或命名切入点",
argNames="参数列表参数名")
value:指定切入点表达式或命名切入点;
argNames:与Schema方式配置中的同义;
java代码:
@After(value="execution(* cn.javass..*.sayAfterFinally(..))")
public void afterFinallyAdvice() {
System.out.println("===========after finally advice");
}
其中测试代码与Schema方式几乎一样,在此就不演示了,如果需要请参考AopTest.java中的testAnnotationAfterFinallyAdvice测试方法。
五、环绕通知:使用org.aspectj.lang.annotation 包下的@Around注解声明;
java代码:
@Around (
value="切入点表达式或命名切入点",
argNames="参数列表参数名")
value:指定切入点表达式或命名切入点;
argNames:与Schema方式配置中的同义;
java代码:
@Around(value="execution(* cn.javass..*.sayAround(..))")
public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("===========around before advice");
Object retVal = pjp.proceed(new Object[] {"replace"});
System.out.println("===========around after advice");
return retVal;
}
其中测试代码与Schema方式几乎一样,在此就不演示了,如果需要请参考AopTest.java中的annotationAroundAdviceTest测试方法。
6.4.5 引入
@AspectJ风格的引入声明在切面中使用org.aspectj.lang.annotation包下的@DeclareParents声明:
java代码:
@DeclareParents(
value=" AspectJ语法类型表达式",
defaultImpl=引入接口的默认实现类)
private Interface interface;
value:匹配需要引入接口的目标对象的AspectJ语法类型表达式;与Schema方式中的types-matching属性同义;
private Interface interface:指定需要引入的接口;
defaultImpl:指定引入接口的默认实现类,没有与Schema方式中的delegate-ref属性同义的定义方式;
java代码:
@DeclareParents(
value="cn.javass..*.IHelloWorldService+", defaultImpl=cn.javass.spring.chapter6.service.impl.IntroductiondService.class)
private IIntroductionService introductionService;
其中测试代码与Schema方式几乎一样,在此就不演示了,如果需要请参考AopTest.java中的testAnnotationIntroduction测试方法。
<aop:config></aop:config>解析
<?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"
xmlns:tx="http://www.springframework.org/schema/tx"
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
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
<bean id="testAction" class="test.action.Stuts2ActionTest">
<property name="service" ref="templatesService"></property>
</bean>
<bean id="templatesService"
class="test.service.impl.TaoTemplatesServiceImpl">
<property name="dao" ref="templatesDAO" />
</bean>
<bean id="templatesDAO" class="test.dao.impl.TaoTemplatesDAOImpl">
<property name="sessionFactory" ref="sessionFactory"></property>
</bean>
<!--定义数据源-->
<bean id="dataSource"
class="org.apache.commons.dbcp.BasicDataSource">
<!-- 定义数据库驱动-->
<property name="driverClassName">
<value>oracle.jdbc.driver.OracleDriver</value>
</property>
<!-- 定义数据库url-->
<property name="url">
<value>jdbc:oracle:thin:@192.168.1.96:1521:yxdb</value>
</property>
<!-- 定义数据库用户名-->
<property name="username">
<value>yxuser</value>
</property>
<!-- 定义数据库密码-->
<property name="password">
<value>yxuser</value>
</property>
</bean>
<!--定义一个hibernate的SessionFactory-->
<bean id="sessionFactory"
class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<!-- 定义SessionFactory必须注入DataSource-->
<property name="dataSource">
<ref local="dataSource" />
</property>
<property name="mappingResources">
<list>
<!--以下用来列出所有的PO映射文件-->
<value>test/mapping/Tao_Templates.hbm.xml</value>
</list>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">
org.hibernate.dialect.Oracle10gDialect
</prop>
<prop key="hibernate.show_sql">true</prop>
<!--此处用来定义hibernate的SessionFactory的属性:
不同数据库连接,启动时选择create,update,create-drop -->
<prop key="hibernate.hbm2ddl.auto">update</prop>
</props>
</property>
</bean>
<bean id="transactionManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory">
<ref bean="sessionFactory" />
</property>
</bean>
<!--
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="jamon-proxy-DataSource"></property>
</bean>
-->
<!-- 事务通知 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="add*" propagation="REQUIRED" rollback-for="Exception,SmoException,BmoException,DaoException" />
<tx:method name="del*" propagation="REQUIRED" rollback-for="Exception,SmoException,BmoException,DaoException" />
<tx:method name="upd*" propagation="REQUIRED" rollback-for="Exception,SmoException,BmoException,DaoException" />
<tx:method name="*" propagation="SUPPORTS" read-only="true" />
</tx:attributes>
</tx:advice>
<!-- Spring AOP config
解释一下(* com.evan.crm.service.*.*(..))中几个通配符的含义:
第一个 * —— 通配 任意返回值类型
第二个 * —— 通配 包com.evan.crm.service下的任意class
第三个 * —— 通配 包com.evan.crm.service下的任意class的任意方法
第四个 .. —— 通配 方法可以有0个或多个参数
-->
<aop:config>
<aop:pointcut id="servicesPointcut" expression="execution(* com.jstrd.mss..*SMOImpl.*(..))" />
<aop:advisor advice-ref="bluePrint.txAdvice" pointcut-ref="servicesPointcut" />
</aop:config>
</beans>
<aop:scoped-proxy/>解析
首先看一下Spring文档上的两个例子对比:
<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>
<bean id="userManager" class="com.foo.UserManager">
<property name="userPreferences" ref="userPreferences"/>
</bean>
<bean id="userPreferences" class="com.foo.UserPreferences" scope="session">
<aop:scoped-proxy/>
</bean>
<bean id="userManager" class="com.foo.UserManager">
<property name="userPreferences" ref="userPreferences"/>
</bean>
前一个例子没有使用<aop:scoped-proxy/>,并且userManager是singleton,所有userManager仅被初始化一次,并且其属性userPreferences也仅被注射一次。当session失效后,userManager仍将保留userPreferences实例。但后一个例子则不一样,userManager的属性userPreferences指向的是com.foo.UserPreferences实例的代理,当session过期后,userManager的属性userPreferences自然也不能再使用。网上有人解释说,这个代理只随着session的创建而创建,销毁而销毁。