目录 搜索
前言 简介 概览 使用场景 Spring 2.0和 2.5的新特性 简介 控制反转(IoC)容器 新的bean作用域 更简单的XML配置 可扩展的XML编写 Annotation(注解)驱动配置 在classpath中自动搜索组件 面向切面编程(AOP) 更加简单的AOP XML配置 对@AspectJ 切面的支持 对bean命名pointcut( bean name pointcut element)的支持 对AspectJ装载时织入(AspectJ load-time weaving)的支持 中间层 在XML里更为简单的声明性事务配置 对Websphere 事务管理的完整支持 JPA 异步的JMS JDBC Web层 Spring MVC合理的默认值 Portlet 框架 基于Annotation的控制器 Spring MVC的表单标签库 对Tiles 2 支持 对JSF 1.2支持 JAX-WS支持 其他 动态语言支持 增强的测试支持 JMX 支持 将Spring 应用程序上下文部署为JCA adapter 计划任务 对Java 5 (Tiger) 支持 移植到Spring 2.5 改变 支持的JDK版本 Spring 2.5的Jar打包 XML配置 Deprecated(淘汰)的类和方法 Apache OJB iBATIS Hibernate JDO UrlFilenameViewController 更新的样例应用 改进的文档 核心技术 IoC(控制反转)容器 简介 基本原理 - 容器和bean 容器 配置元数据 实例化容器 XML配置元数据的结构 多种bean bean的命名 bean的别名 实例化bean 用构造器来实例化 使用静态工厂方法实例化 使用实例工厂方法实例化 使用容器 依赖 注入依赖 构造器注入 构造器参数解析 构造器参数类型匹配 构造参数索引 Setter注入 一些例子 依赖配置详解 直接变量(基本类型、Strings类型等。) idref元素 引用其它的bean(协作者) 内部bean 集合 集合的合并 强类型集合(仅适用于Java5+) Nulls XML配置文件的简写及其他 XML-based configuration metadata shortcuts 使用p名称空间配置属性 组合属性名称 使用depends-on 延迟初始化bean 自动装配(autowire)协作者 将bean排除在自动装配之外 依赖检查 方法注入 Lookup方法注入 自定义方法的替代方案 Bean的作用域 Singleton作用域 Prototype作用域 Singleton beans和prototype-bean的依赖 其他作用域 初始化web配置 Request作用域 Session作用域 global session作用域 作用域bean与依赖 选择创建代理的类型 自定义作用域 创建自定义作用域 使用自定义作用域 定制bean特性 生命周期回调 初始化回调 析构回调 缺省的初始化和析构方法 组合生命周期机制 在非web应用中优雅地关闭Spring IoC容器 了解自己 BeanFactoryAware BeanNameAware bean定义的继承 容器扩展点 用BeanPostProcessor定制bean 使用BeanPostProcessor的Hello World示例 RequiredAnnotationBeanPostProcessor示例 用BeanFactoryPostProcessor定制配置元数据 PropertyPlaceholderConfigurer示例 PropertyOverrideConfigurer示例 使用FactoryBean定制实例化逻辑 The ApplicationContext BeanFactory 还是 ApplicationContext? 利用MessageSource实现国际化 事件 底层资源的访问 ApplicationContext在WEB应用中的实例化 粘合代码和可怕的singleton 以J2EE RAR文件的形式部署Spring ApplicationContext 基于注解(Annotation-based)的配置 @Autowired 基于注解的自动连接微调 CustomAutowireConfigurer @Resource @PostConstruct 与 @PreDestroy 对受管组件的Classpath扫描 @Component和更多典型化注解 自动检测组件 使用过滤器自定义扫描 自动检测组件的命名 为自动检测的组件提供一个作用域 用注解提供限定符元数据 注册一个LoadTimeWeaver 资源 简介 Resource接口 内置 Resource 实现 UrlResource ClassPathResource FileSystemResource ServletContextResource InputStreamResource ByteArrayResource ResourceLoader接口 ResourceLoaderAware 接口 把Resource作为属性来配置 Application context 和Resource 路径 构造application context 创建 ClassPathXmlApplicationContext 实例 - 简介 Application context构造器中资源路径的通配符 Ant风格的pattern 潜在的可移植性 classpath*: 前缀 其他关于通配符的说明 FileSystemResource 说明 校验,数据绑定,BeanWrapper,与属性编辑器 简介 使用Spring的Validator接口进行校验 从错误代码到错误信息 Bean处理和BeanWrapper 设置和获取属性值以及嵌套属性 内建的PropertyEditor实现 注册用户自定义的PropertyEditor 使用PropertyEditorRegistrars 使用Spring进行面向切面编程(AOP) 简介 AOP概念 Spring AOP的功能和目标 AOP代理 @AspectJ支持 启用@AspectJ支持 声明一个切面 声明一个切入点(pointcut) 切入点指示符(PCD)的支持 组合切入点表达式 共享通用切入点定义 示例 声明通知 前置通知 后置通知(After returning advice) 异常通知(After throwing advice) 最终通知(After (finally) advice) 环绕通知 通知参数(Advice parameters) 访问当前的连接点 传递参数给通知 确定参数名 处理参数 通知顺序 引入(Introduction) 切面实例化模型 例子 基于Schema的AOP支持 声明一个切面 声明一个切入点 声明通知 前置通知 后置通知 异常通知 最终通知 环绕通知 通知参数 通知顺序 引入 切面实例化模型 Advisor 例子 AOP声明风格的选择 Spring AOP还是完全用AspectJ? Spring AOP中使用@AspectJ还是XML? 混合切面类型 代理机制 理解AOP代理 以编程方式创建@AspectJ代理 在Spring应用中使用AspectJ 在Spring中使用AspectJ进行domain object的依赖注入 @Configurable对象的单元测试 Working with multiple application contexts Spring中其他的AspectJ切面 使用Spring IoC来配置AspectJ的切面 在Spring应用中使用AspectJ加载时织入(LTW) 第一个例子 切面 'META-INF/aop.xml' 相关类库(JARS) Spring配置 特定环境的配置 通用Java应用 Tomcat WebLogic 更多资源 Spring AOP APIs 简介 Spring中的切入点API 概念 切入点运算 AspectJ切入点表达式 便利的切入点实现 静态切入点 正则表达式切入点 属性驱动的切入点 动态切入点 控制流切入点 切入点的超类 自定义切入点 Spring的通知API 通知的生命周期 Spring里的通知类型 拦截环绕通知 前置通知 异常通知 后置通知 引入通知 Spring里的Advisor API 使用ProxyFactoryBean创建AOP代理 基础 JavaBean属性 基于JDK和CGLIB的代理 对接口进行代理 对类进行代理 使用“全局”通知器 简化代理定义 使用ProxyFactory通过编程创建AOP代理 操作被通知对象 使用“自动代理(autoproxy)”功能 自动代理bean定义 BeanNameAutoProxyCreator DefaultAdvisorAutoProxyCreator AbstractAdvisorAutoProxyCreator 使用元数据驱动的自动代理 使用TargetSource 热交换目标源 池化目标源 原型目标源 ThreadLocal目标源 定义新的Advice类型 更多资源 测试 简介 单元测试 Mock对象 JNDI Servlet API Portlet API 单元测试支持类 通用工具类 Spring MVC 集成测试 概览 使用哪个支持框架 通用目标 上下文管理及缓存 测试fixtures依赖注入 事务管理 集成测试支持类 JDBC测试支持 常用注解 JUnit 3.8遗留支持 上下文管理及缓存 测试fixture依赖注入 字段级别(Field Level)注入 事务管理 JUnit 3.8 遗留支持类 Java 5+ 专有支持 使用注解的事务相关测试 JPA支持类 Spring TestContext Framework 主要的抽象 上下文管理和缓存 测试fixture的依赖注入 事务管理 TestContext支持类 JUnit 3.8支持类 JUnit 4.4支持类 定制JUnit 4.4运行器 TestNG支持类 TestContext框架注解支持 PetClinic示例 更多资源 中间层数据访问 事务管理 简介 动机 关键抽象 使用资源同步的事务 高层次方案 低层次方案 TransactionAwareDataSourceProxy 声明式事务管理 理解Spring的声明式事务管理实现 第一个例子 回滚 为不同的bean配置不同的事务语义 <tx:advice/> 有关的设置 使用 @Transactional @Transactional 有关的设置 事务传播 required RequiresNew Nested 通知事务操作 结合AspectJ使用 @Transactional 编程式事务管理 使用TransactionTemplate 指定事务设置 使用PlatformTransactionManager 选择编程式事务管理还是声明式事务管理 与特定应用服务器集成 IBM WebSphere BEA WebLogic Oracle OC4J 常见问题的解决方法 对一个特定的 DataSource 使用了错误的事务管理器 更多的资源 DAO支持 简介 一致的异常层次 一致的DAO支持抽象类 使用JDBC进行数据访问 简介 选择一种工作模式 Spring JDBC包结构 利用JDBC核心类控制JDBC的基本操作和错误处理 JdbcTemplate类 一些示例 查询(SELECT) 更新(INSERT/UPDATE/DELETE) 其他操作 JdbcTemplate 的最佳实践 NamedParameterJdbcTemplate类 SimpleJdbcTemplate类 DataSource接口 SQLExceptionTranslator接口 执行SQL语句 执行查询 更新数据库 获取自动生成的主键 控制数据库连接 DataSourceUtils类 SmartDataSource接口 AbstractDataSource类 SingleConnectionDataSource类 DriverManagerDataSource类 TransactionAwareDataSourceProxy类 DataSourceTransactionManager类 NativeJdbcExtractor JDBC批量操作 使用JdbcTemplate进行批量操作 使用SimpleJdbcTemplate进行批量操作 通过使用SimpleJdbc类简化JDBC操作 使用SimpleJdbcInsert插入数据 使用SimpleJdbcInsert来获取自动生成的主键 指定SimpleJdbcInsert所使用的字段 使用SqlParameterSource提供参数值 使用SimpleJdbcCall调用存储过程 声明SimpleJdbcCall使用的参数 如何定义SqlParameters 使用SimpleJdbcCall调用内置函数 使用SimpleJdbcCall返回的ResultSet/REF Cursor 用Java对象来表达JDBC操作 SqlQuery类 MappingSqlQuery类 SqlUpdate类 StoredProcedure类 SqlFunction类 参数和数据处理的基本原则 为参数设置SQL类型信息 处理BLOB 和 CLOB对象 在IN语句中传入一组参数值 处理复杂类型的存储过程调用 使用ORM工具进行数据访问 简介 Hibernate 资源管理 在Spring容器中创建 SessionFactory The HibernateTemplate 不使用回调的基于Spring的DAO实现 基于Hibernate3的原生API实现DAO 编程式的事务划分 声明式的事务划分 事务管理策略 容器资源 vs 本地资源 在应用服务器中使用Hibernate的注意事项 JDO 建立PersistenceManagerFactory JdoTemplate和JdoDaoSupport 基于原生的JDO API实现DAO 事务管理 JdoDialect Oracle TopLink SessionFactory 抽象层 TopLinkTemplate and TopLinkDaoSupport 基于原生的TopLink API的DAO实现 事务管理 iBATIS SQL Maps 创建SqlMapClient 使用 SqlMapClientTemplate 和 SqlMapClientDaoSupport 基于原生的iBATIS API的DAO实现 JPA 在Spring环境中建立JPA LocalEntityManagerFactoryBean 从JNDI中获取 EntityManagerFactory LocalContainerEntityManagerFactoryBean Tomcat(5.0以上)加载时的织入配置 使用VM代理的全局加载时织入 上下文范围内的加载时织入配置 处理多持久化单元 JpaTemplate 和 JpaDaoSupport 基于原生的JPA实现DAO 异常转化 事务管理 JpaDialect The Web Web MVC framework Web框架 概述 与其他MVC实现框架的集成 Spring Web MVC框架的特点 DispatcherServlet 控制器 AbstractController 和 WebContentGenerator 其它的简单控制器 MultiActionController 命令控制器 处理器映射(handler mapping) BeanNameUrlHandlerMapping SimpleUrlHandlerMapping 拦截器(HandlerInterceptor) 视图与视图解析 视图解析器(ViewResolver) 视图解析链 重定向(Rediret)到另一个视图 RedirectView redirect:前缀 forward:前缀 本地化解析器 AcceptHeaderLocaleResolver CookieLocaleResolver SessionLocaleResolver LocaleChangeInterceptor 使用主题 简介 如何定义主题 主题解析器 Spring对分段文件上传(multipart file upload)的支持 介绍 使用MultipartResolver 在表单中处理分段文件上传 使用Spring的表单标签库 配置 form标签 input标签 checkbox标签 checkboxes标签 radiobutton标签 radiobuttons标签 password标签 select标签 option标签 options标签 textarea标签 hidden标签 errors标签 处理异常 惯例优先原则(convention over configuration) 对控制器的支持:ControllerClassNameHandlerMapping 对模型的支持:ModelMap(ModelAndView) 对视图的支持:RequestToViewNameTranslator 基于注解的控制器配置 建立dispatcher实现注解支持 使用@Controller定义一个控制器 使用@RequestMapping映射请求 使用@RequestParam绑定请求参数到方法参数 使用@ModelAttribute提供一个从模型到数据的链接 使用@SessionAttributes指定存储在会话中的属性 自定义WebDataBinder初始化 使用@InitBinder自定义数据绑定 配置一个定制的WebBindingInitializer 更多资源 集成视图技术 简介 JSP和JSTL 视图解析器 'Plain-old' JSPs versus JSTL 'Plain-old' JSP与JSTL 帮助简化开发的额外的标签 Tiles 需要的资源 如何集成Tiles UrlBasedViewResolver类 ResourceBundleViewResolver类 SimpleSpringPreparerFactory 和 SpringBeanPreparerFactory Velocity和FreeMarker 需要的资源 Context 配置 创建模板 高级配置 velocity.properties FreeMarker 绑定支持和表单处理 用于绑定的宏 简单绑定 表单输入生成宏 输入域 选择输入域 重载HTML转码行为并使你的标签符合XHTML XSLT 写在段首 Bean 定义 标准MVC控制器代码 把模型数据转化为XML 定义视图属性 文档转换 小结 文档视图(PDF/Excel) 简介 配置和安装 文档视图定义 Controller 代码 Excel视图子类 PDF视图子类 JasperReports 依赖的资源 配置 配置ViewResolver 配置View 关于报表文件 使用 JasperReportsMultiFormatView 构造ModelAndView 使用子报表 配置子报表文件 配置子报表数据源 配置Exporter的参数 集成其它Web框架 简介 通用配置 JavaServer Faces DelegatingVariableResolver FacesContextUtils Struts ContextLoaderPlugin DelegatingRequestProcessor DelegatingActionProxy ActionSupport Classes Tapestry 注入 Spring 托管的 beans 将 Spring Beans 注入到 Tapestry 页面中 组件定义文件 添加抽象访问方法 将 Spring Beans 注入到 Tapestry 页面中 - Tapestry 4.0+ 风格 WebWork 更多资源 Portlet MVC框架 介绍 控制器 - MVC中的C 视图 - MVC中的V Web作用范围的Bean DispatcherPortlet ViewRendererServlet 控制器 AbstractController 和 PortletContentGenerator 其它简单的控制器 Command控制器 PortletWrappingController 处理器映射 PortletModeHandlerMapping ParameterHandlerMapping PortletModeParameterHandlerMapping 增加 HandlerInterceptors HandlerInterceptorAdapter ParameterMappingInterceptor 视图和它们的解析 Multipart文件上传支持 使用 PortletMultipartResolver 处理表单里的文件上传 异常处理 Portlet应用的部署 整合 使用Spring进行远程访问与Web服务 简介 使用RMI暴露服务 使用RmiServiceExporter暴露服务 在客户端链接服务 使用Hessian或者Burlap通过HTTP远程调用服务 为Hessian和co.配置DispatcherServlet 使用HessianServiceExporter暴露你的bean 在客户端连接服务 使用Burlap 对通过Hessian或Burlap暴露的服务使用HTTP Basic认证 使用HTTP调用器暴露服务 Exposing the service object 在客户端连接服务 Web Services 使用JAX-RPC暴露基于servlet的web服务 使用JAX-RPC访问web服务 注册JAX-RPC Bean映射 注册自己的JAX-RPC 处理器 使用JAX-WS暴露基于servlet的web服务 使用JAX-WS暴露单独web服务 使用Spring支持的JAX-WS RI来暴露服务 使用JAX-WS访问web服务 使用XFire来暴露Web服务 JMS 服务端配置 客户端配置 对远程接口不提供自动探测实现 在选择这些技术时的一些考虑 Enterprise Java Beans (EJB) 集成 简介 访问EJB 概念 访问本地的无状态Session Bean(SLSB) 访问远程SLSB Accessing EJB 2.x SLSBs versus EJB 3 SLSBs 使用Spring提供的辅助类实现EJB组件 EJB 2.x base classes EJB 3 注入拦截 JMS (Java Message Service) 简介 使用Spring JMS JmsTemplate 连接工厂 目的地管理 消息侦听容器 SimpleMessageListenerContainer DefaultMessageListenerContainer ServerSessionMessageListenerContainer 事务管理 发送消息 使用消息转换器 SessionCallback 和 ProducerCallback 接收消息 同步接收 异步接收 - 消息驱动的POJO SessionAwareMessageListener接口 MessageListenerAdapter 事务中的消息处理 JCA消息端点的支持 JMS命名空间支持 JMX 介绍 将Bean暴露为JMX 创建MBeanServer 重用原有的MBeanServer 延迟初始化的MBean MBean的自动注册 控制注册行为 控制Bean的管理接口 MBeanInfoAssembler接口 使用源码级元数据 使用JDK 5.0的注解 源代码级的元数据类型 AutodetectCapableMBeanInfoAssembler接口 用Java接口定义管理接口 使用MethodNameBasedMBeanInfoAssembler 控制Bean的ObjectName 从Properties读取Properties 使用MetadataNamingStrategy <context:mbean-export/>元素 JSR-160连接器 服务器端连接器 客户端连接器 基于Burlap/Hessian/SOAP的JMX 通过代理访问MBean 通知 为通知注册监听器 发布通知 更多资源 JCA CCI 简介 配置CCI 连接器配置 在Spring中配置ConnectionFactory 配置CCI连接 使用一个 CCI 单连接 使用Spring的 CCI访问支持 记录转换 CciTemplate类 DAO支持 自动输出记录生成 总结 直接使用一个CCI Connection接口和Interaction接口 CciTemplate 使用示例 建模CCI访问为操作对象 MappingRecordOperation MappingCommAreaOperation 自动生成输出记录 总结 MappingRecordOperation 使用示例 MappingCommAreaOperation 使用示例 事务 Spring邮件抽象层 简介 使用Spring邮件抽象 MailSender 和 SimpleMailMessage 的基本用法 使用 JavaMailSender 和 MimeMessagePreparator 使用MimeMessageHelper 发送附件和嵌入式资源(inline resources) 附件 内嵌资源 使用模板来创建邮件内容 一个基于Velocity的示例 Spring中的定时调度(Scheduling)和线程池(Thread Pooling) 简介 使用OpenSymphony Quartz 调度器 使用JobDetailBean 使用 MethodInvokingJobDetailFactoryBean 使用triggers和SchedulerFactoryBean来包装任务 使用JDK Timer支持类 创建定制的timers 使用 MethodInvokingTimerTaskFactoryBean类 最后:使用TimerFactoryBean来设置任务 SpringTaskExecutor抽象 TaskExecutor接口 TaskExecutor类型 使用TaskExecutor 动态语言支持 介绍 第一个示例 定义动态语言支持的bean 公共概念 <lang:language/> 元素 Refreshable bean 内置动态语言源文件 理解dynamic-language-backed bean上下文中的构造器注入 JRuby beans Groovy beans 通过回调定制Groovy对象 BeanShell beans 场景 Spring MVC控制器的脚本化 Validator的脚本化 Bits and bobs AOP - 通知脚本化bean 作用域 更多的资源 注解和源代码级的元数据支持 简介 Spring的元数据支持 注解 @Required Spring中的其它@Annotations Jakarta Commons Attributes集成 元数据和Spring AOP自动代理 基本原理 声明式事务管理 示例程序 演示案例 介绍 使用动态语言实现的Spring MVC控制器 构建与部署 使用SimpleJdbcTemplate和@Repository实现DAO 域对象 Data Access Object 构建 XML Schema-based configuration Introduction XML Schema-based configuration Referencing the schemas The util schema <util:constant/> Setting a bean property or constructor arg from a field value <util:property-path/> Using <util:property-path/> to set a bean property or constructor-argument <util:properties/> <util:list/> <util:map/> <util:set/> The jee schema <jee:jndi-lookup/> (simple) <jee:jndi-lookup/> (with single JNDI environment setting) <jee:jndi-lookup/> (with multiple JNDI environment settings) <jee:jndi-lookup/> (complex) <jee:local-slsb/> (simple) <jee:local-slsb/> (complex) <jee:remote-slsb/> The lang schema The jms schema The tx (transaction) schema The aop schema The context schema <property-placeholder/> <annotation-config/> <component-scan/> <load-time-weaver/> <spring-configured/> <mbean-export/> The tool schema The beans schema Setting up your IDE Setting up Eclipse Setting up IntelliJ IDEA Integration issues XML parsing errors in the Resin v.3 application server Extensible XML authoring Introduction Authoring the schema Coding a NamespaceHandler Coding a BeanDefinitionParser Registering the handler and the schema 'META-INF/spring.handlers' 'META-INF/spring.schemas' Using a custom extension in your Spring XML configuration Meatier examples Nesting custom tags within custom tags Custom attributes on 'normal' elements Further Resources spring-beans-2.0.dtd spring.tld Introduction The bind tag The escapeBody tag The hasBindErrors tag The htmlEscape tag The message tag The nestedPath tag The theme tag The transform tag spring-form.tld Introduction The checkbox tag The checkboxes tag The errors tag The form tag The hidden tag The input tag The label tag The option tag The options tag The password tag The radiobutton tag The radiobuttons tag The select tag The textarea tag Spring 2.5开发手册中文化项目 声明 致谢 参与人员 项目历程
文字

6.2. @AspectJ支持

@AspectJ使用了Java 5的注解,可以将切面声明为普通的Java类。@AspectJ样式在AspectJ 5发布的AspectJ project部分中被引入。Spring 2.0使用了和AspectJ 5一样的注解,并使用AspectJ来做切入点解析和匹配。但是,AOP在运行时仍旧是纯的Spring AOP,并不依赖于AspectJ的编译器或者织入器(weaver)。

使用AspectJ的编译器或者织入器的话就可以使用完整的AspectJ语言,我们将在第 6.8 节 “在Spring应用中使用AspectJ”中讨论这个问题。

6.2.1. 启用@AspectJ支持

为了在Spring配置中使用@AspectJ切面,你首先必须启用Spring对@AspectJ切面配置的支持,并确保自动代理(autoproxying)的bean是否能被这些切面通知。自动代理是指Spring会判断一个bean是否使用了一个或多个切面通知,并据此自动生成相应的代理以拦截其方法调用,并且确保通知在需要时执行。

通过在你的Spring的配置中引入下列元素来启用Spring对@AspectJ的支持:

<aop:aspectj-autoproxy/>

我们假定你正在使用附录 A, XML Schema-based configuration 所描述的schema支持。关于如何在aop的命名空间中引入这些标签,请参见第 A.2.7 节 “The aop schema”

如果你正在使用DTD,你仍然可以通过在你的application context中添加如下定义来启用@AspectJ支持:

<bean class="org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator" />

你需要在你的应用程序的classpath中引入两个AspectJ库:aspectjweaver.jaraspectjrt.jar。这些库可以在AspectJ的安装包(1.5.1或者之后的版本)的'lib'目录里找到,或者也可以在Spring-with-dependencies发布包的'lib/aspectj'目录下找到。

6.2.2. 声明一个切面

启用@AspectJ支持后,在application context中定义的任意带有一个@Aspect切面(拥有@Aspect注解)的bean都将被Spring自动识别并用于配置Spring AOP。以下例子展示了为完成一个不是非常有用的切面所需要的最小定义:

application context中一个常见的bean定义,它指向一个使用了@Aspect注解的bean类:

<bean id="myAspect" class="org.xyz.NotVeryUsefulAspect">
         <!-- configure properties of aspect here as normal -->
      </bean>
      

以及NotVeryUsefulAspect类的定义,使用了 org.aspectj.lang.annotation.Aspect注解。

package org.xyz;
import org.aspectj.lang.annotation.Aspect;

@Aspect
public class NotVeryUsefulAspect {

}

切面(用@Aspect注解的类)和其他类一样有方法和字段定义。他们也可能包括切入点,通知和引入(inter-type)声明。

通知切面

在Spring AOP中,拥有切面的类本身可能是其它切面中通知的目标。一个类上面的@Aspect注解标识它为一个切面,并且从自动代理中排除它。

6.2.3. 声明一个切入点(pointcut)

在前面我们提到,切入点决定了连接点关注的内容,使得我们可以控制通知什么时候执行。Spring AOP只支持Spring bean的方法执行连接点。所以你可以把切入点看做是Spring bean上方法执行的匹配。一个切入点声明有两个部分:一个包含名字和任意参数的签名,还有一个切入点表达式,该表达式决定了我们关注那个方法的执行。在@AspectJ注解风格的AOP中,一个切入点签名通过一个普通的方法定义来提供,并且切入点表达式使用@Pointcut注解来表示(作为切入点签名的方法必须返回void 类型)。

用一个例子能帮我们清楚的区分切入点签名和切入点表达式之间的差别,下面的例子定义了一个切入点'anyOldTransfer',这个切入点将匹配任何名为 "transfer" 的方法的执行:

@Pointcut("execution(* transfer(..))")// the pointcut expression
private void anyOldTransfer() {}// the pointcut signature

切入点表达式,也就是组成@Pointcut注解的值,是正规的AspectJ 5切入点表达式。如果你想要更多了解AspectJ的切入点语言,请参见AspectJ编程指南(如果要了解基于Java 5的扩展请参阅AspectJ 5 开发手册)或者其他人写的关于AspectJ的书,例如Colyer et. al.著的“Eclipse AspectJ”或者Ramnivas Laddad著的“AspectJ in Action”。

6.2.3.1. 切入点指示符(PCD)的支持

Spring AOP支持在切入点表达式中使用如下的AspectJ切入点指示符:

  • execution - 匹配方法执行的连接点,这是你将会用到的Spring的最主要的切入点指示符。

  • within - 限定匹配特定类型的连接点(在使用Spring AOP的时候,在匹配的类型中定义的方法的执行)。

  • this - 限定匹配特定的连接点(使用Spring AOP的时候方法的执行),其中bean reference(Spring AOP 代理)是指定类型的实例。

  • target - 限定匹配特定的连接点(使用Spring AOP的时候方法的执行),其中目标对象(被代理的应用对象)是指定类型的实例。

  • args - 限定匹配特定的连接点(使用Spring AOP的时候方法的执行),其中参数是指定类型的实例。

  • @target - 限定匹配特定的连接点(使用Spring AOP的时候方法的执行),其中正执行对象的类持有指定类型的注解。

  • @args - 限定匹配特定的连接点(使用Spring AOP的时候方法的执行),其中实际传入参数的运行时类型持有指定类型的注解。

  • @within - 限定匹配特定的连接点,其中连接点所在类型已指定注解(在使用Spring AOP的时候,所执行的方法所在类型已指定注解)。

  • @annotation - 限定匹配特定的连接点(使用Spring AOP的时候方法的执行),其中连接点的主题持有指定的注解。

另外,Spring AOP还提供了一个名为'bean'的PCD。这个PCD允许你限定匹配连接点到一个特定名称的Spring bean,或者到一个特定名称Spring bean的集合(当使用通配符时)。'bean' PCD具有下列的格式:

bean(idOrNameOfBean)

'idOrNameOfBean'标记可以是任何Spring bean的名字:限定通配符使用'*'来提供,如果你为Spring bean制定一些命名约定,你可以非常容易地编写一个'bean' PCD表达式将它们选出来。和其它连接点指示符一样,'bean' PCD也支持&&, ||和 !逻辑操作符。

注意

请注意'bean' PCD仅仅 被Spring AOP支持而不是AspectJ. 这是Spring对AspectJ中定义的标准PCD的一个特定扩展。

'bean' PCD不仅仅可以在类型级别(被限制在基于织入AOP上)上操作而还可以在实例级别(基于Spring bean的概念)上操作。

因为Spring AOP限制了连接点必须是方法执行级别的,上文pointcut指示符中的讨论也给出了一个定义,这个定义和AspectJ的编程指南中的定义相比显得更加狭窄。除此之外,AspectJ它本身有基于类型的语义,在执行的连接点'this'和'target'都是指同一个对象,也就是执行方法的对象。Spring AOP是一个基于代理的系统,并且严格区分代理对象本身(对应于'this')和背后的目标对象(对应于'target')

6.2.3.2. 组合切入点表达式

切入点表达式可以使用'&', '||' 和 '!'来组合。还可以通过名字来指向切入点表达式。以下的例子展示了三种切入点表达式: anyPublicOperation(在一个方法执行连接点代表了任意public方法的执行时匹配);inTrading(在一个代表了在交易模块中的任意的方法执行时匹配)和 tradingOperation(在一个代表了在交易模块中的任意的公共方法执行时匹配)。

@Pointcut("execution(public * *(..))")
private void anyPublicOperation() {}

@Pointcut("within(com.xyz.someapp.trading..*")
private void inTrading() {}

@Pointcut("anyPublicOperation() && inTrading()")
private void tradingOperation() {}
        

如上所示,用更少的命名组件来构建更加复杂的切入点表达式是一种最佳实践。当用名字来指定切入点时使用的是常见的Java成员可视性访问规则。(比如说,你可以在同一类型中访问私有的切入点,在继承关系中访问受保护的切入点,可以在任意地方访问公共切入点)。成员可视性访问规则不影响到切入点的匹配

6.2.3.3. 共享通用切入点定义

当开发企业级应用的时候,你通常会想要从几个切面来引用模块化的应用和特定操作的集合。我们推荐定义一个“SystemArchitecture”切面来捕捉通用的切入点表达式。一个典型的通用切面看起来可能像下面这样:

package com.xyz.someapp;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class SystemArchitecture {

  
  @Pointcut("within(com.xyz.someapp.web..*)")
  public void inWebLayer() {}

  
  @Pointcut("within(com.xyz.someapp.service..*)")
  public void inServiceLayer() {}

  
  @Pointcut("within(com.xyz.someapp.dao..*)")
  public void inDataAccessLayer() {}

  
  @Pointcut("execution(* com.xyz.someapp.service.*.*(..))")
  public void businessService() {}
  
  
  @Pointcut("execution(* com.xyz.someapp.dao.*.*(..))")
  public void dataAccessOperation() {}

}

示例中的切入点定义了一个你可以在任何需要切入点表达式的地方可引用的切面。比如,为了使service层事务化,你可以写成:

<aop:config>
  <aop:advisor 
      pointcut="com.xyz.someapp.SystemArchitecture.businessService()"
      advice-ref="tx-advice"/>
</aop:config>

<tx:advice id="tx-advice">
  <tx:attributes>
    <tx:method name="*" propagation="REQUIRED"/>
  </tx:attributes>
</tx:advice>

我们将在第 6.3 节 “基于Schema的AOP支持”中讨论 <aop:config><aop:advisor>标签。在第 9 章 事务管理 中讨论事务标签。

6.2.3.4. 示例

Spring AOP 用户可能会经常使用 execution切入点指示符。执行表达式的格式如下:

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern)
          throws-pattern?)

除了返回类型模式(上面代码片断中的ret-type-pattern),名字模式和参数模式以外, 所有的部分都是可选的。返回类型模式决定了方法的返回类型必须依次匹配一个连接点。 你会使用的最频繁的返回类型模式是*,它代表了匹配任意的返回类型。 一个全限定的类型名将只会匹配返回给定类型的方法。名字模式匹配的是方法名。 你可以使用*通配符作为所有或者部分命名模式。 参数模式稍微有点复杂:()匹配了一个不接受任何参数的方法, 而(..)匹配了一个接受任意数量参数的方法(零或者更多)。 模式(*)匹配了一个接受一个任何类型的参数的方法。 模式(*,String)匹配了一个接受两个参数的方法,第一个可以是任意类型, 第二个则必须是String类型。更多的信息请参阅AspectJ编程指南中 语言语义的部分。

下面给出一些通用切入点表达式的例子。

  • 任意公共方法的执行:

    execution(public * *(..))
  • 任何一个名字以“set”开始的方法的执行:

    execution(* set*(..))
  • AccountService接口定义的任意方法的执行:

    execution(* com.xyz.service.AccountService.*(..))
  • 在service包中定义的任意方法的执行:

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

    execution(* com.xyz.service..*.*(..))
  • 在service包中的任意连接点(在Spring AOP中只是方法执行):

    within(com.xyz.service.*)
  • 在service包或其子包中的任意连接点(在Spring AOP中只是方法执行):

    within(com.xyz.service..*)
  • 实现了AccountService接口的代理对象的任意连接点 (在Spring AOP中只是方法执行):

    this(com.xyz.service.AccountService)

    'this'在绑定表单中更加常用:- 请参见后面的通知一节中了解如何使得代理对象在通知体内可用。

  • 实现AccountService接口的目标对象的任意连接点 (在Spring AOP中只是方法执行):

    target(com.xyz.service.AccountService)

    'target'在绑定表单中更加常用:- 请参见后面的通知一节中了解如何使得目标对象在通知体内可用。

  • 任何一个只接受一个参数,并且运行时所传入的参数是Serializable 接口的连接点(在Spring AOP中只是方法执行)

    args(java.io.Serializable)

    'args'在绑定表单中更加常用:- 请参见后面的通知一节中了解如何使得方法参数在通知体内可用。

    请注意在例子中给出的切入点不同于 execution(* *(java.io.Serializable)): args版本只有在动态运行时候传入参数是Serializable时才匹配,而execution版本在方法签名中声明只有一个 Serializable类型的参数时候匹配。

  • 目标对象中有一个 @Transactional 注解的任意连接点 (在Spring AOP中只是方法执行)

    @target(org.springframework.transaction.annotation.Transactional)

    '@target'在绑定表单中更加常用:- 请参见后面的通知一节中了解如何使得注解对象在通知体内可用。

  • 任何一个目标对象声明的类型有一个 @Transactional 注解的连接点 (在Spring AOP中只是方法执行):

    @within(org.springframework.transaction.annotation.Transactional)

    '@within'在绑定表单中更加常用:- 请参见后面的通知一节中了解如何使得注解对象在通知体内可用。

  • 任何一个执行的方法有一个 @Transactional 注解的连接点 (在Spring AOP中只是方法执行)

    @annotation(org.springframework.transaction.annotation.Transactional)

    '@annotation'在绑定表单中更加常用:- 请参见后面的通知一节中了解如何使得注解对象在通知体内可用。

  • 任何一个只接受一个参数,并且运行时所传入的参数类型具有@Classified 注解的连接点(在Spring AOP中只是方法执行)

    @args(com.xyz.security.Classified)

    '@args'在绑定表单中更加常用:- 请参见后面的通知一节中了解如何使得注解对象在通知体内可用。

  • 任何一个在名为'tradeService'的Spring bean之上的连接点 (在Spring AOP中只是方法执行):

    bean(tradeService)
  • 任何一个在名字匹配通配符表达式'*Service'的Spring bean之上的连接点 (在Spring AOP中只是方法执行):

    bean(*Service)

6.2.4. 声明通知

通知是跟一个切入点表达式关联起来的,并且在切入点匹配的方法执行之前或者之后或者前后运行。 切入点表达式可能是指向已命名的切入点的简单引用或者是一个已经声明过的切入点表达式。

6.2.4.1. 前置通知

一个切面里使用 @Before 注解声明前置通知:

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class BeforeExample {

  @Before("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
  public void doAccessCheck() {
    // ...
  }

}

如果使用一个in-place 的切入点表达式,我们可以把上面的例子换个写法:

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class BeforeExample {

  @Before("execution(* com.xyz.myapp.dao.*.*(..))")
  public void doAccessCheck() {
    // ...
  }

}

6.2.4.2. 后置通知(After returning advice)

返回后通知通常在一个匹配的方法返回的时候执行。使用 @AfterReturning 注解来声明:

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterReturning;

@Aspect
public class AfterReturningExample {

  @AfterReturning("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
  public void doAccessCheck() {
    // ...
  }

}

说明:你可以在相同的切面里定义多个通知,或者其他成员。 我们只是在展示如何定义一个简单的通知。这些例子主要的侧重点是正在讨论的问题。

有时候你需要在通知体内得到返回的值。你可以使用@AfterReturning 接口的形式来绑定返回值:

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterReturning;

@Aspect
public class AfterReturningExample {

  @AfterReturning(
    pointcut="com.xyz.myapp.SystemArchitecture.dataAccessOperation()",
    returning="retVal")
  public void doAccessCheck(Object retVal) {
    // ...
  }
  
}

returning属性中使用的名字必须对应于通知方法内的一个参数名。 当一个方法执行返回后,返回值作为相应的参数值传入通知方法。 一个returning子句也限制了只能匹配到返回指定类型值的方法。 (在本例子中,返回值是Object类,也就是说返回任意类型都会匹配)

请注意当使用后置通知时允许返回一个完全不同的引用。

6.2.4.3. 异常通知(After throwing advice)

抛出异常通知在一个方法抛出异常后执行。使用@AfterThrowing注解来声明:

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterThrowing;

@Aspect
public class AfterThrowingExample {

  @AfterThrowing("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
  public void doRecoveryActions() {
    // ...
  }

}

你通常会想要限制通知只在某种特殊的异常被抛出的时候匹配,你还希望可以在通知体内得到被抛出的异常。 使用throwing属性不仅可以限制匹配的异常类型(如果你不想限制,请使用 Throwable作为异常类型),还可以将抛出的异常绑定到通知的一个参数上。

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterThrowing;

@Aspect
public class AfterThrowingExample {

  @AfterThrowing(
    pointcut="com.xyz.myapp.SystemArchitecture.dataAccessOperation()",
    throwing="ex")
  public void doRecoveryActions(DataAccessException ex) {
    // ...
  }

}

throwing属性中使用的名字必须与通知方法内的一个参数对应。 当一个方法因抛出一个异常而中止后,这个异常将会作为那个对应的参数送至通知方法。 throwing 子句也限制了只能匹配到抛出指定异常类型的方法 (上面的示例为DataAccessException)。

6.2.4.4. 最终通知(After (finally) advice)

不论一个方法是如何结束的,最终通知都会运行。使用@After 注解来声明。最终通知必须准备处理正常返回和异常返回两种情况。通常用它来释放资源。

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.After;

@Aspect
public class AfterFinallyExample {

  @After("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
  public void doReleaseLock() {
    // ...
  }

}

6.2.4.5. 环绕通知

最后一种通知是环绕通知。环绕通知在一个方法执行之前和之后执行。它使得通知有机会 在一个方法执行之前和执行之后运行。而且它可以决定这个方法在什么时候执行,如何执行,甚至是否执行。 环绕通知经常在某线程安全的环境下,你需要在一个方法执行之前和之后共享某种状态的时候使用。 请尽量使用最简单的满足你需求的通知。(比如如果简单的前置通知也可以适用的情况下不要使用环绕通知)。

环绕通知使用@Around注解来声明。通知的第一个参数必须是 ProceedingJoinPoint类型。在通知体内,调用 ProceedingJoinPointproceed()方法会导致 后台的连接点方法执行。proceed 方法也可能会被调用并且传入一个 Object[]对象-该数组中的值将被作为方法执行时的参数。

当传入一个Object[]对象的时候,处理的方法与通过AspectJ编译器处理环绕通知略有不同。 对于使用传统AspectJ语言写的环绕通知来说,传入参数的数量必须和传递给环绕通知的参数数量匹配 (不是后台的连接点接受的参数数量),并且特定顺序的传入参数代替了将要绑定给连接点的原始值 (如果你看不懂不用担心)。Spring采用的方法更加简单并且能更好匹配它基于代理(proxy-based)的执行语法, 如果你使用AspectJ的编译器和编织器来编译为Spring而写的@AspectJ切面和处理参数,你只需要知道这一区别即可。 有一种方法可以让你写出100%兼容Spring AOP和AspectJ的表达式,我们将会在后续的通知参数的章节中讨论它。

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.ProceedingJoinPoint;

@Aspect
public class AroundExample {

  @Around("com.xyz.myapp.SystemArchitecture.businessService()")
  public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
    // start stopwatch
    Object retVal = pjp.proceed();
    // stop stopwatch
    return retVal;
  }

}

方法的调用者得到的返回值就是环绕通知返回的值。 例如:一个简单的缓存切面,如果缓存中有值,就返回该值,否则调用proceed()方法。 请注意proceed可能在通知体内部被调用一次,许多次,或者根本不被调用,所有这些都是合法的。

6.2.4.6. 通知参数(Advice parameters)

Spring 2.0 提供了完整的通知类型 - 这意味着你可以在通知签名中声明所需的参数, (就像我们在前面看到的后置和异常通知一样)而不总是使用Object[]。 我们将会看到如何使得参数和其他上下文值对通知体可用。 首先让我们看以下如何编写普通的通知以找出正在被通知的方法。

6.2.4.6.1. 访问当前的连接点

任何通知方法可以将第一个参数定义为org.aspectj.lang.JoinPoint类型 (环绕通知需要定义第一个参数为ProceedingJoinPoint类型, 它是 JoinPoint 的一个子类)。JoinPoint 接口提供了一系列有用的方法,比如 getArgs()(返回方法参数)、 getThis()(返回代理对象)、getTarget()(返回目标)、 getSignature()(返回正在被通知的方法相关信息)和 toString() (打印出正在被通知的方法的有用信息)。详细的内容请参考JavaDoc。

6.2.4.6.2. 传递参数给通知

我们已经看到了如何绑定返回值或者异常(使用后置通知和异常通知)。为了可以在通知体内访问参数, 你可以使用args来绑定。如果在一个args表达式中应该使用类型名字的地方 使用一个参数名字,那么当通知执行的时候对应的参数值将会被传递进来。用一个例子应该会使它变得清晰。 假使你想要通知以一个Account对象作为第一个参数的DAO操作的执行, 你想要在通知体内也能访问account对象,可以编写如下的代码:

@Before("com.xyz.myapp.SystemArchitecture.dataAccessOperation() &&" + 
        "args(account,..)")
public void validateAccount(Account account) {
  // ...
}

切入点表达式的 args(account,..) 部分有两个目的:首先它保证了 只会匹配那些接受至少一个参数的方法的执行,而且传入的参数必须是Account类型的实例, 其次它使得在通知体内可以通过account 参数访问实际的Account对象。

另外一个办法是定义一个切入点,这个切入点在匹配某个连接点的时候“提供”了 Account对象的值,然后直接从通知中访问那个命名切入点。看起来和下面的示例一样:

@Pointcut("com.xyz.myapp.SystemArchitecture.dataAccessOperation() &&" + 
          "args(account,..)")
private void accountDataAccessOperation(Account account) {}

@Before("accountDataAccessOperation(account)")
public void validateAccount(Account account) {
  // ...
}

有兴趣的读者请参阅 AspectJ 编程指南了解更详细的内容。

代理对象(this)、目标对象(target) 和注解(@within, @target, @annotation, @args)都可以用一种类似的格式来绑定。 以下的例子展示了如何使用 @Auditable注解来匹配方法执行,并提取Audit代码。

首先是@Auditable注解的定义:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Auditable {
	AuditCode value();
}

然后是匹配@Auditable方法执行的通知:

@Before("com.xyz.lib.Pointcuts.anyPublicMethod() && " + 
        "@annotation(auditable)")
public void audit(Auditable auditable) {
  AuditCode code = auditable.value();
  // ...
}
6.2.4.6.3. 确定参数名

绑定在通知上的参数依赖切入点表达式的匹配名,并借此在(通知和切入点)的方法签名中声明参数名。 参数名无法 通过Java反射来获取,所以Spring AOP使用如下的策略来确定参数名字:

  1. 如果参数名字已经被用户明确指定,则使用指定的参数名: 通知和切入点注解有一个额外的"argNames"属性,该属性用来指定所注解的方法的参数名 - 这些参数名在运行时是可以 访问的。例子如下:

    @Before(
       value="com.xyz.lib.Pointcuts.anyPublicMethod() && target(bean) && @annotation(auditable)",
       argNames="bean,auditable")
    public void audit(Object bean, Auditable auditable) {
      AuditCode code = auditable.value();
      // ... use code and bean
    }

    如果第一个参数是JoinPointProceedingJoinPoint, 或者JoinPoint.StaticPart类型, 你可以在“argNames”属性的值中省去参数的名字。例如,如果你修改前面的通知来获取连接点对象, "argNames"属性就不必包含它:

    @Before(
       value="com.xyz.lib.Pointcuts.anyPublicMethod() && target(bean) && @annotation(auditable)",
       argNames="bean,auditable")
    public void audit(JoinPoint jp, Object bean, Auditable auditable) {
      AuditCode code = auditable.value();
      // ... use code, bean, and jp
    }

    对于第一个JoinPointProceedingJoinPoint,和 JoinPoint.StaticPart类型的参数特殊处理特别适合 没有集合其它连接上下文的通知。在这种情部下,你可以简单的省略“argNames”属性。 例如,下面的通知不需要声明“argNames”属性:

    @Before(
       "com.xyz.lib.Pointcuts.anyPublicMethod()")
    public void audit(JoinPoint jp) {
      // ... use jp
    }
  2. 使用'argNames'属性有一点笨拙,所以如果'argNames' 属性没有被指定,Spring AOP将查看类的debug信息并尝试从本地的变量表确定参数名。只要类编译时有debug信息, (最少要有'-g:vars')这个信息将会出现。打开这个标志编译的结果是: (1)你的代码稍微容易理解(反向工程), (2)class文件的大小稍微有些大(通常不重要), (3)你的编译器将不会应用优化去移除未使用的本地变量。换句话说,打开这个标志创建时你应当不会遇到困难。

    如果一个@AspectJ切面已经被AspectJ编译器(ajc)编译过,即使没有debug信息, 也不需要添加argNames参数,因为编译器会保留必需的信息。

  3. 如果不加上必要的debug信息来编译的话,Spring AOP将会尝试推断绑定变量到参数的配对。 (例如,要是只有一个变量被绑定到切入点表达式,通知方法只接受一个参数, 配对是显而易见的)。 如果变量的绑定不明确,将会抛出一个AmbiguousBindingException异常。

  4. 如果以上所有策略都失败了,将会抛出一个IllegalArgumentException异常。

6.2.4.6.4. 处理参数

我们之前提过我们将会讨论如何编写一个带参数的的proceed()调用, 使得在Spring AOP和AspectJ中都能正常工作。解决方法是仅仅确保通知签名按顺序绑定方法参数。例如:

@Around("execution(List<Account> find*(..)) &&" +
        "com.xyz.myapp.SystemArchitecture.inDataAccessLayer() && " +
        "args(accountHolderNamePattern)")		
public Object preProcessQueryPattern(ProceedingJoinPoint pjp, String accountHolderNamePattern)
throws Throwable {
  String newPattern = preProcess(accountHolderNamePattern);
  return pjp.proceed(new Object[] {newPattern});
}        

大多数情况下你都会这样绑定(就像上面的例子那样)。

6.2.4.7. 通知顺序

如果有多个通知想要在同一连接点运行会发生什么?Spring AOP遵循跟AspectJ一样的优先规则来确定通知执行的顺序。 在“进入”连接点的情况下,最高优先级的通知会先执行(所以给定的两个前置通知中,优先级高的那个会先执行)。 在“退出”连接点的情况下,最高优先级的通知会最后执行。(所以给定的两个后置通知中, 优先级高的那个会第二个执行)。

当定义在不同的切面里的两个通知都需要在一个相同的连接点中运行, 那么除非你指定,否则执行的顺序是未知的。你可以通过指定优先级来控制执行顺序。 在标准的Spring方法中可以在切面类中实现org.springframework.core.Ordered 接口或者用Order注解做到这一点。在两个切面中, Ordered.getValue()方法返回值(或者注解值)较低的那个有更高的优先级。

当定义在相同的切面里的两个通知都需要在一个相同的连接点中运行, 执行的顺序是未知的(因为这里没有方法通过反射javac编译的类来获取声明顺序)。 考虑在每个切面类中按连接点压缩这些通知方法到一个通知方法,或者重构通知的片段到各自的切面类中 - 它能在切面级别进行排序。

6.2.5. 引入(Introduction)

引入(在AspectJ中被称为inter-type声明)使得一个切面可以定义被通知对象实现给定的接口, 并且可以为那些对象提供具体的实现。

使用@DeclareParents注解来定义引入。这个注解用来定义匹配的类型 拥有一个新的父类(所以有了这个名字)。比如,给定一个接口UsageTracked, 和接口的具体实现DefaultUsageTracked类, 接下来的切面声明了所有的service接口的实现都实现了UsageTracked接口。 (比如为了通过JMX输出统计信息)。

@Aspect
public class UsageTracking {

  @DeclareParents(value="com.xzy.myapp.service.*+",
                  defaultImpl=DefaultUsageTracked.class)
  public static UsageTracked mixin;
  
  @Before("com.xyz.myapp.SystemArchitecture.businessService() &&" +
          "this(usageTracked)")
  public void recordUsage(UsageTracked usageTracked) {
    usageTracked.incrementUseCount();
  }
  
}

实现的接口通过被注解的字段类型来决定。@DeclareParents注解的 value属性是一个AspectJ的类型模式:- 任何匹配类型的bean都会实现 UsageTracked接口。请注意,在上面的前置通知的例子中,service beans 可以直接用作UsageTracked接口的实现。如果需要编程式的来访问一个bean, 你可以这样写:

UsageTracked usageTracked = (UsageTracked) context.getBean("myService");

6.2.6. 切面实例化模型

(这是一个高级主题,所以如果你刚开始学习AOP你可以跳过它到后面的章节)

默认情况下,在application context中每一个切面都会有一个实例。AspectJ把这个叫做单例化模型。 也可以用其他的生命周期来定义切面:Spring支持AspectJ的 perthispertarget实例化模型(现在还不支持percflow、percflowbelowpertypewithin)。

一个"perthis" 切面通过在@Aspect注解中指定perthis 子句来声明。让我们先来看一个例子,然后解释它是如何运作的:

@Aspect("perthis(com.xyz.myapp.SystemArchitecture.businessService())")
public class MyAspect {

  private int someState;
	
  @Before(com.xyz.myapp.SystemArchitecture.businessService())
  public void recordServiceUsage() {
    // ...
  }
  	
}

这个'perthis'子句的效果是每个独立的service对象执行一个业务时都会 创建一个切面实例(切入点表达式所匹配的连接点上的每一个独立的对象都会绑定到'this'上)。 在service对象上第一次调用方法的时候,切面实例将被创建。切面在service对象失效的同时失效。 在切面实例被创建前,所有的通知都不会被执行,一旦切面对象创建完成, 定义的通知将会在匹配的连接点上执行,但是只有当service对象是和切面关联的才可以。 请参阅 AspectJ 编程指南了解更多关于per-clauses的信息。

'pertarget'实例模型的跟“perthis”完全一样,只不过是为每个匹配于连接点 的独立目标对象创建一个切面实例。

6.2.7. 例子

现在你已经看到了每个独立的部分是如何运作的了,是时候把他们放到一起做一些有用的事情了!

因为并发的问题,有时候业务服务(business services)可能会失败(例如,死锁失败)。如果重新尝试一下, 很有可能就会成功。对于业务服务来说,重试几次是很正常的(Idempotent操作不需要用户参与,否则会得出矛盾的结论) 我们可能需要透明的重试操作以避免客户看到一个PessimisticLockingFailureException异常。 很明显,在一个横切多层的情况下,这是非常有必要的,因此通过切面来实现是很理想的。

因为我们想要重试操作,我们会需要使用到环绕通知,这样我们就可以多次调用proceed()方法。 下面是简单的切面实现:

@Aspect
public class ConcurrentOperationExecutor implements Ordered {
   
   private static final int DEFAULT_MAX_RETRIES = 2;

   private int maxRetries = DEFAULT_MAX_RETRIES;
   private int order = 1;

   public void setMaxRetries(int maxRetries) {
      this.maxRetries = maxRetries;
   }
   
   public int getOrder() {
      return this.order;
   }
   
   public void setOrder(int order) {
      this.order = order;
   }
   
   @Around("com.xyz.myapp.SystemArchitecture.businessService()")
   public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable { 
      int numAttempts = 0;
      PessimisticLockingFailureException lockFailureException;
      do {
         numAttempts++;
         try { 
            return pjp.proceed();
         }
         catch(PessimisticLockingFailureException ex) {
            lockFailureException = ex;
         }
      }
      while(numAttempts <= this.maxRetries);
      throw lockFailureException;
   }

}

请注意切面实现了 Ordered 接口,这样我们就可以把切面的优先级设定为高于事务通知 (我们每次重试的时候都想要在一个全新的事务中进行)。maxRetriesorder 属性都可以在Spring中配置。主要的动作在doConcurrentOperation这个环绕通知方法中发生。 请注意这个时候我们所有的businessService()方法都会使用这个重试策略。 我们首先会尝试处理,如果得到一个PessimisticLockingFailureException异常, 我们仅仅重试直到耗尽所有预设的重试次数。

对应的Spring配置如下:

<aop:aspectj-autoproxy/>

<bean id="concurrentOperationExecutor"
  class="com.xyz.myapp.service.impl.ConcurrentOperationExecutor">
     <property name="maxRetries" value="3"/>
     <property name="order" value="100"/>  
</bean>

为改进切面,使之仅仅重试idempotent操作,我们可以定义一个 Idempotent注解:

@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {
  // marker annotation
}

并且对service操作的实现进行注解。为了只重试idempotent操作,切面的修改只需要改写切入点表达式, 使得只匹配@Idempotent操作:

@Around("com.xyz.myapp.SystemArchitecture.businessService() && " + 
        "@annotation(com.xyz.myapp.service.Idempotent)")
public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable { 
  ...	
}
上一篇: 下一篇: