spring学习笔记(13)基于Schema配置AOP详解

来源:互联网 发布:协方差矩阵的svd分解 编辑:程序博客网 时间:2024/04/29 02:23

基于Schema配置入门实例

除了基于@AspectJ注解的形式来实现AOP外,我们还可以在IOC容器中配置。先来看看一个常见的应用场景,在我们的web项目中,我们需要为service层配置事务,传统的做法是在每个业务逻辑方法重复下面配置中:

Created with Raphaël 2.1.0程序开始1. 获取DAO层封装好的数据库查询API,如HIbernate中的SessionFactory/Session和mybatis中的xxxMapper2. 开启事务3. 根据入参查询数据库完成相应的业务逻辑操作4. 关闭事务5. 如果有必要则关闭连接。程序结束

第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分支下载。

1 0