基于Schema配置入门实例
除了基于@AspectJ注解的形式来实现AOP外,我们还可以在IOC容器中配置。先来看看一个常见的应用场景,在我们的web项目中,我们需要为service层配置事务,传统的做法是在每个业务逻辑方法重复下面配置中:
第1步可以通过我们SpringIOC注入完成,但2,4步很多时候则显得非常冗杂,我们需要在每个方法中都开启关闭事务,于是我们利用AOP的横切逻辑来实现事务配置:
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" /> </bean>
<!-- 拦截器方式配置事物 -->
<tx:advice id="transactionAdvice" transaction-manager="transactionManager"><!--事务的增强配置-->
<tx:attributes>
<tx:method name="add*" propagation="REQUIRED" />
<!--name="add*"相当于我们的函数切点表达式,匹配以add开头的方法,使用REQUIRED的事务传播行为-->
<tx:method name="save*" propagation="REQUIRED" />
<tx:method name="modify*" propagation="REQUIRED" />
<tx:method name="delete*" propagation="REQUIRED" />
<tx:method name="get*" propagation="SUPPORTS" />
<tx:method name="search*" propagation="SUPPORTS" />
<tx:method name="*" propagation="SUPPORTS" />
</tx:attributes>
</tx:advice>
<aop:config><!--aop命名空间配置的开始-->
<aop:pointcut id="transactionPointcut"
<!--这里也是切点,和前面的<tx:method name="add*>取交来定位连接点-->
<aop:advisor pointcut-ref="transactionPointcut"
advice-ref="transactionAdvice" />
<!--通过整合切点和增强来配置我们的切面(advisor),从配置中我们看到,这种切面的增强和切点都是唯一的-->
</aop:config>
以上实例就是我们基于Schema的方式来配置切面,这样,我们com.yc.service包下,以Impl结尾的类中所有的方法都会根据方法名织入相应的事务,就不用之前繁琐地硬编码式为每个方法配置事务了。
配置详解
下面我们通过一个比较全面的例子来认识Schema特色配置
1. 定义目标对象
package test.aop3;
public class UserController {
public void login(String name){
System.out.println("I'm "+name+" ,I'm logining");
}
//模拟非法注销
public void logout() {
throw new RuntimeException("illegal logout");
}
}
2. 配置增强方法所在类
package test.aop3;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.core.Ordered;
public class MyAdvice {
public void AfterReturning(Object retInfo) throws Throwable{
System.out.println("MyAdvice 实施AfterReturning,目标对象方法返回值为:"+retInfo);
}
public void after(String name) throws Throwable{
System.out.println("MyAdvice 实施after,目标对象方法入参为:"+name);
}
public void before(String name) throws Throwable{
System.out.println("MyAdvice 实施@before,目标对象方法入参为:"+name);
}
public void around(ProceedingJoinPoint joinPoint) throws Throwable{
System.out.println("MyAdvice 实施around前,目标对象方法入参为:"+joinPoint.getArgs()[0]);
joinPoint.proceed();
System.out.println("MyAdvice 实施around后,目标对象方法入参为:"+joinPoint.getArgs()[0]);
}
public void afterThrowing(RuntimeException re) throws Throwable{
System.out.println("MyAdvice 实施afterThrowing,目标对象方法抛出异常:"+ re.getMessage());
}
}
3. 配置xml文件
<bean id="myAdvice" class="test.aop3.MyAdvice" /><!-- 注册advice -->
<bean id="userController" class="test.aop3.UserController" />
<!-- 注册目标对象类,方便测试使用 -->
<!-- 基于Schema配置必须以<aop:config>开始 ,可配置是否暴露AOP代理,或是否使用CGLib代理-->
<aop:pointcut expression="target(test.aop3.UserController) and args(name) " id="pointcut"/>
<!-- 配置独立切点,方便在后面的切面配置中服用 -->
<!-- 配置切面,通过ref引入增强类,通过order配置织入顺序 -->
<aop:before method="before" pointcut-ref="pointcut" arg-names="name"/>
<!-- 通过arg-names绑定目标对象的方法入参 名字需和增强的方法入参一致-->
<!-- 除了通过pointcut-ref属性引用独立命名切点,还可以通过pointcut属性声明并引用匿名切点 -->
<aop:around method="around" pointcut="execution( * test.aop3.UserController.login(..))"/>
<aop:after method="after" pointcut-ref="pointcut" arg-names="name"/>
<!--绑定目标对象方法的返回值到增强方法入参,名字需一致 -->
<aop:after-throwing method="afterThrowing"
pointcut="target(test.aop3.UserController)" throwing="re"/>
</aop:aspect>
</aop:config>
4. 运行测试方法
public static void main(String args[]){
ApplicationContext ac = new ClassPathXmlApplicationContext("classpath:test/aop3/aop.xml");
UserController userController = (UserController) ac.getBean("userController");
userController.login("zenghao");
userController.logout();
}
5. 测试结果分析
控制台打印信息:
MyAdvice 实施@before,目标对象方法入参为:zenghao
MyAdvice 实施around前,目标对象方法入参为:zenghao
I’m zenghao ,I’m logining
MyAdvice 实施around后,目标对象方法入参为:zenghao
MyAdvice 实施after,目标对象方法入参为:zenghao
MyAdvice 实施AfterReturning,目标对象方法返回值为:null
MyAdvice 实施afterThrowing,目标对象方法抛出异常:illegal logout
Exception in thread “main” java.lang.RuntimeException: illegal logout
at test.aop3.UserController.logout(UserController.java:10)
at test.aop3.UserController$$FastClassBySpringCGLIB$$d89843a8.invoke()
……(忽略下面异常信息)……
结合上面实例,我们分析: 1. aop切点、切面等配置必须被<aop:config>标签包括
2. 一个<aop:config>可以配置多个切点、切面,一个切面可以配置多个增强
3. <aop:pointcut>可以配置在<aop:config>和<aop:aspect>中,配置在<aop:config>中对所 有<aop:aspect>可见,配置在特定的<aop:aspect>则对其他的<aop:aspect>不可见。
4. 在<aop:config>中,各标签配置顺序先后依序必须为:<aop:pointcut>、<aop:advisor>、<aop:aspect>,若<aop:pointcut>配置在<aop:aspect>中,则无顺序要求(即在<aop:after>等标签前后都可以,即使<aop:after>标签中引用了该<aop:pointcut>标签)
5. 在xml文件中使用逻辑运算符&&会报错,必须使用and,||和!则无此限制
引介增强
在前面的例子中,我们没有提到引介增强,实际上,引介增强的配置和其他增强的差异是挺大的,它没有了method,pointcut,pointcut-ref属性,但多了以下四个属性:
1. implement-interface
2. default-impl
3. implement-interface
4. delegate-ref
关于引介增强的各种配置和现实应用场景分析,请移步至我的另外一篇博文《spring学习笔记(12)引介增强详解:定时器实例:无侵入式动态增强类功能》查看。
advisor配置
前面我们在谈等几个标签的配置顺序中提到,它的使用格式为: <aop:advisor advice-ref="实现了特定的增强接口的增强类" order="1" pointcut="使用切点表达式函数定义切点"/>
其中,实现了特定增强的接口实现类配置可参考我前面的文章:spring学习笔记(6)AOP增强(advice)配置与应用 ,它的配置形式如:
public class BeforeAdvice implements MethodBeforeAdvice{
@Override
public void before(Method method, Object[] args, Object target)
throws Throwable {
System.out.println("前置日志记录:"+target+ "调用了"+method.getName() + "方法,传入的第一个参数为:"+args[0]);
}
}
源码下载
本博文提到的示例源码请到我的github仓库https://github.com/jeanhao/spring的aop分支下载。