《Spring攻略》第3章 Spring AOP和AspectJ支持

来源:互联网 发布:内部ife矩阵 编辑:程序博客网 时间:2024/04/27 16:31

1启用Spring的AspectJ注解支持

问题:
Spring支持在其AOP框架中使用AspectJ注解编写的POJO aspect。但是,你必须首先启用Spring中的AspectJ注解支持。

解决方案:
你只需要在Bean配置文件中定义一个空的XML元素<aop:aspectj-autoproxy/>,就可以启用Spring IoC容器中的AspectJ注解支持。
然后,Spring将自动为匹配你的AspectJ aspect的所有Bean创建代理。
对于接口不可用或者没有用于应用设计中的情况,可以依靠CGLIB创建代理。为了启用CGLIB,必须在<aop:aspectj-autoproxy>中设置proxy-targetclass=true属性。


2用AspectJ注解声明aspect

问题:从第5版与AspectWerkz合并起,AspectJ支持将其aspect编写为带有一租AspectJ注解的POJO。Spring AOP框架也支持这种aspect,但是这些aspect必须在Spring IoC容器中注册方可生效。解决方案:要在Spring中注册AspectJ aspect,只需要将他们声明为IoC容器中的Bean实例就行了。在Spring IoC容器中启用AspectJ,容器将自动为匹配AspectJ aspect的Bean创建代理。用AspectJ注解编写的aspect只是一个带有@Aspect注解的java类。通知(Advice)是带有一个通知注解的简单Java方法。AspectJ注解5中通知注解:@Before、@After、@AfterReturning、@AfterThrowing和@Around。工作原理:前置通知:@Before方式一:简单方式,只是添加注解@Aspectpublic class LoggingBeforeAspect {private Log log = LogFactory.getLog(LoggingBeforeAspect.class);@Before("execution(* *..*.delete(..))")public void logBefore(){log.debug("The method add() begins");}}然后把这个class注册进IoC就可以,可以以匿名的方式注册<aop:aspectj-autoproxy /><bean id="userDao" class="com.partner4java.aspectj.demo1.UserDaoImpl"/><bean class="com.partner4java.aspectj.demo1.LoggingBeforeAspect"/>方式二:我们还可以拿到连接点@Aspectpublic class LoggingBeforeAspect1 {private Log log = LogFactory.getLog(LoggingBeforeAspect1.class);@Before("execution(* *..*.delete(..))")public void logBefore(JoinPoint joinPoint){log.debug("The method "+ joinPoint.getSignature().getName() +" add() begins");}}最终通知:最终通知(after advice)在连接点结束之后执行,不管返回结果还是抛出异常。@Aspectpublic class LoggingAfterAspect {private Log log = LogFactory.getLog(LoggingAfterAspect.class);@After("execution(* *..*.delete(..))")public void logBefore(JoinPoint joinPoint) {log.debug("The method " + joinPoint.getSignature().getName() + " ends");}}后置通知:最终通知不管连接点正常返回还是抛出异常都执行。如果你希望仅当连接点返回时记录,应该用后置通知(after returning advice)替换最终通知。在后置通知中,你可以在@AfterReturning注解中添加一个returning属性,访问连接点的返回值。这个参数值应该是通知方法的参数名称,用于传入返回值。@Aspectpublic class LoggingAfterReturnAspect {private Log log = LogFactory.getLog(LoggingAfterReturnAspect.class);@AfterReturning(pointcut = "execution(* *..*.delete(..))", returning = "result")public void logBefore(JoinPoint joinPoint, Object result) {log.debug("The method " + joinPoint.getSignature().getName()+ " ends with" + result);}}异常通知:仅当连接点抛出异常时执行。@Aspectpublic class LoggingErrorAspect {private Log log = LogFactory.getLog(LoggingErrorAspect.class);@AfterThrowing(pointcut = "execution(* *..*.save(..))", throwing = "throwable")public void logAfterThrowing(JoinPoint joinPoint, Throwable throwable) {log.debug("exception " + throwable + " in method"+ joinPoint.getSignature().getName());}}环绕通知:功能最强大的,可以满足前面的所有需求,而且可以改变返回的数值。@Aspectpublic class LoggingAroundAspect {private Log log = LogFactory.getLog(LoggingErrorAspect.class);@Around("execution(* *..*.find(..))")public Object logAround(ProceedingJoinPoint joinPoint){log.debug("being " + joinPoint.getSignature().getName());try {Object result = joinPoint.proceed();log.debug(" end " + result);return result;} catch (Throwable e) {e.printStackTrace();}return null;}}选择通知类型的通用原则是使用满足你的要求的最不强大的类型。


3访问连接点信息

问题:
在AOP中,通知适用于不同程序执行点,这些执行点称作连接点。为了让通知采取正确的行动,往往需要连接点的详细信息。


解决方案:
通知能够在通知方法签名中声明一个JoinPoint类型的参数,访问当前连接点信息。


4指定aspect优先级

问题:
当多于一个aspect适用于相同的连接点时,方法的优先级是不明确的,除非你已经显示的做出定义。


解决方案:
aspect的优先级可以通过实现Ordered接口或者使用@Order注解完成。


5重用切入点定义

问题:在编写aspect时,你可以直接在通知注解中嵌入切入点表达式。但是,相同的切入点表达式在多个通知中必须重复。解决方案:和许多其他AOP实现一样,AspectJ也允许独立的定义切入点,在多个通知中重用。工作原理:如果你是只在本类中共享,可定义一个private的方法,然后放上切入点,在其他的方法上,直接引用其方法名就可以。@Aspectpublic class LoggingAspectj {@Pointcut("execution(* *..*.find(..))")private void loggingOpertion(){}@Before("loggingOpertion()")public void logBefore(){}}当然,你也可以把loggingOpertion设置为public的,但是在同包下需要加上类名@Before("LoggingAspectj.loggingOpertion()"),如果跨包加上包名。

6编写AspectJ切入点表达式

问题:
横切关注点可能发生在不同的程序执行点上,这些执行点被称作连接点。
因为连接点的多样化,你需要一种强大的表达式语言来帮助匹配他们。


解决方案:
...


7在你的Bean中引入行为

问题:
有时候,你可能有一组共享公共行为的类。在OOP中,他们必须扩展相同的基类或者实现相同的接口。这个问题确实是可以用AOP模块化的一个横切点关注点。

此外,Java的单继承机制仅允许一个类最多扩展一个基类。所以,你不能同时从多个实现类中继承行为。

解决方案:
引入(Introduction)是AOP中的一种特殊通知。
他允许为一个接口提供实现类,使对象动态的实现接口。这看上去就像使对象在运行时扩展了实现类。

而且,你可以用多个实现类将多个接口同时引入对象。这可以实现与多重继承相同的效果。

工作原理:
@DeclareParents


8为你的Bean引入状态

问题:有时候,你可能希望为一组现有的对象添加新的状态,跟踪他们的使用情况,如调用次数、最后修改日期等。如果所有的对象都有相同的基类,这就不成问题。但是,如果不同的类不在相同的类层次结构中,添加这样的状态就很难。解决方案:你可以为你的对象引入一个新的接口和保存状态字段的实现类。然后,你可以编写另一个通知根绝特定的条件改变状态。工作原理:定义一个接入的接口和实现类,然后通过AspectJ引入到目标类中。<aop:aspectj-autoproxy /><bean id="userDao" class="com.partner4java.aspectj.demo1.UserDaoImpl"/><bean class="com.partner4java.aspectj.declare.DeclareLogAspectj"/>@Aspectpublic class DeclareLogAspectj {//value属性标识引入的目标类//引入的接口,为被注解的定义类,实现为defaultImpl指定的类@DeclareParents(value = "com.partner4java.aspectj.demo1.*", defaultImpl = ExecuteLogImpl.class)public ExecuteLog executeLog;//@Around("execution(* *.*(..))" + " && this(executeLog)")public Object declareLog(ProceedingJoinPoint joinPoint,ExecuteLog executeLog) {executeLog.setBeginDate(new Date());try {Object result = joinPoint.proceed();executeLog.increase();executeLog.setEndDate(new Date());return result;} catch (Throwable e) {e.printStackTrace();}return null;}}获取的时候可以直接让被接入的类强转为接入类的接口类型:@Testpublic void testAsp(){UserDao userDao = (UserDao) applicationContext.getBean("userDao");userDao.find(new Integer(22));ExecuteLog executeLog = (ExecuteLog)userDao;System.out.println(executeLog.getCount());System.out.println(executeLog.getBeginDate());}


9用基于XML的配置声明aspect

问题:如果你不想以任何理由使用注解的形式。解决方案:基于Spring的XML配置方式。工作原理:可以去掉<aop:aspectj-autoproxy />开启注解的声明。<aop:config>    <aop:pointcut id="loggingOperation" expression=        "within(com.apress.springrecipes.calculator.ArithmeticCalculator+) || within(com.apress.springrecipes.calculator.UnitCalculator+)" />    <aop:pointcut id="validationOperation" expression=        "within(com.apress.springrecipes.calculator.ArithmeticCalculator+) || within(com.apress.springrecipes.calculator.UnitCalculator+)" />    <aop:aspect id="loggingAspect" ref="calculatorLoggingAspect">        <aop:before pointcut-ref="loggingOperation"            method="logBefore" />        <aop:after-returning pointcut-ref="loggingOperation"            returning="result" method="logAfterReturning" />        <aop:after-throwing pointcut-ref="loggingOperation"            throwing="e" method="logAfterThrowing" />        <aop:around pointcut-ref="loggingOperation"            method="logAround" />    </aop:aspect>    <aop:aspect id="validationAspect" ref="calculatorValidationAspect">        <aop:before pointcut-ref="validationOperation"            method="validateBefore" />    </aop:aspect>    <aop:aspect id="introduction" ref="calculatorIntroduction">        <aop:declare-parents            types-matching=                "com.apress.springrecipes.calculator.ArithmeticCalculatorImpl"            implement-interface=                "com.apress.springrecipes.calculator.MaxCalculator"            default-impl=                "com.apress.springrecipes.calculator.MaxCalculatorImpl" />        <aop:declare-parents            types-matching=                "com.apress.springrecipes.calculator.ArithmeticCalculatorImpl"            implement-interface=                "com.apress.springrecipes.calculator.MinCalculator"            default-impl=                "com.apress.springrecipes.calculator.MinCalculatorImpl" />        <aop:declare-parents            types-matching=                "com.apress.springrecipes.calculator.*CalculatorImpl"            implement-interface=                "com.apress.springrecipes.calculator.Counter"            default-impl=                "com.apress.springrecipes.calculator.CounterImpl" />        <aop:after pointcut=            "execution(* com.apress.springrecipes.calculator.*Calculator.*(..)) and this(counter)"            method="increaseCount" />    </aop:aspect></aop:config>


10Spring中的AspectJ加载时织入aspect

问题:
Spring只支持有限的AspectJ切入点类型,并允许aspect应用到IoC容器中声明Bean。
如果你希望使用更多的切入点类型,或者将aspect应用到Spring IoC容器之外创建的对象,就必须在Spring应用程序中使用AspectJ框架。


解决方案:
织入(Weaving)是aspect应用到你的目标对象的过程。
使用Spring AOP,注入运行时通过动态代理发生。相反,AspectJ框架支持编译时和加载时织入。


AspectJ编译时织入通过特殊的AspectJ编译器ajc完成。这个编译器将aspect织入你的Java资源文件并且输出织入的二进制类文件。
他还能将aspect织入到你编译过的类文件或者JAR文件。这个过程称作编译后织入。
你可以在Spring IoC容器中声明类之前,进行编译时或者后编译时织入。Spring完全不参与织入过程。


AspectJ加载时织入(也称LTW)发生在目标类由类加载器载入到JVM时。对于进行织入的类,需要一个特殊的类加载器来改进目标类的字节码。
AspectJ和Spring都提供加载时织入程序,为类加载器增加加载织入能力。


工作原理:
。。。

原创粉丝点击