Hello Spring Framework——面向切面编程(AOP)
本文主要参考了Spring官方文档第10章以及第11章和第40章的部分内容。如果要我总结Spring AOP的作用,不妨借鉴文档里的一段话:One of the key components of Spring is the AOP framework. While the Spring IoC container does not depend on AOP, meaning you do not need to use AOP if you don’t want to, AOP complements Spring IoC to provide a very capable middleware solution.(译:如果你不想使用AOP完全可以忽略,只单独使用IoC。但是作为重点,Spring AOP框架是对IoC的有力补充)。
***************************以下是正文部分***************************
官方文档对AOP的讲述有点复杂,我稍微修改了一下组织结构,试图从实用性的角度介绍Spring AOP。有些词汇的译法并没有采用公认的翻译。
一、几个重要的概念
切面(Aspect):能够嵌入多个类的模块。Spring AOP框架使用普通类实现切面。
连接点(Join point):正常业务流上的作用点,例如一个方法或一段异常处理。
建言(Advice):在切点上执行的方法。不同类型的建言包括:"around", "before" 和 "after"。
切点(Pointcut):代表了嵌入模块上的作用点。只有切点上才能运行建言。
AOP代理(AOP proxy):SpringAOP通常采用JDK Dynamic Proxy或CGLIB两种方式实现AOP代理。
织入(Weaving):织入的概念是所有AOP框架所共有的。它指将切面对象与建言对象连接的行为。织入行为能够始于编译期(compile time)、加载期(load time)或运行期(runtime)。Spring AOP的织入是在运行期。
二、对于@AspectJ的支持
Spring AOP框架支持@AspectJ风格的注解声明。
AspectJ也是一种AOP框架,它是对JVM的一种嵌入式开发。AspectJ的AOP织入始于编译期。Spring AOP框架仅借鉴了AspectJ提供的注解方案,因此织入依然是在运行期完成的。
(1)通过Maven添加依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>4.2.2.RELEASE</version>
</dependency>
maven依赖
注意:使用IDE查询相关依赖关系可以看到spring-aspects仅仅依赖了aspectjweaver包,在有些教材中建议大家引入整个AspectJ包其实是不准确的。对于依赖关系的管理,我的建议是尽量做到精确引入,以防未知异常。
(2)声明对@AspectJ注解的支持
方法一:
@Configuration
@EnableAspectJAutoProxy
public class AppConfig { }
AppConfig
方法二:
<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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd"> <!-- 声明基于注解的支持 -->
<aop:aspectj-autoproxy />
<!-- 扫描包文件 -->
<context:component-scan base-package="..." /> </beans>
XML文档声明
方法二是常用的方式,第一种了解即可。
(3)声明切面
package org.xyz;
import org.aspectj.lang.annotation.Aspect; @Aspect
public class AnyClazz {
//...
}
增加@Aspect
在XML文档中配置Bean
<bean id="myAspect" class="org.xyz.AnyClazz">
<!-- configure properties of aspect here as normal -->
</bean>
配置bean
@Aspect仅代表你希望将类声明为一个切面,如果希望使用Spring提供的包扫描自动初始化还需要增加@Component注解
package org.xyz;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component; @Component
@Aspect
public class AnyClazz { }
支持包扫描的切面注解
(4)声明切点
切点包含三个方面的内容:切点的注解、连接点表达式、切点方法签名
/*
* 最常用的声明方式
* @Pointcut代表此方法是一个切点
* 切点的名字是anyMethod
* 代表连接点对象的表达式execution(public packagename.*.*(..))
*/
@Pointcut("execution(public packagename.*.*(..))")
public void anyMethod() {
//...
}
最常用的声明方式
连接点表达式的前缀有三种,第一种最常见,后面两种知道含义足够
//execution意味着连接点的指定精确到类中的方法
@Pointcut("execution(public * *(..))")
private void anyPublicOperation() {} //within代表了连接点的指定只精确到包,为包中的所有类和方法都添加代理
@Pointcut("within(com.xyz.someapp.trading..*)")
private void inTrading() {} //只为符合方法名的连接点添加代理
@Pointcut("anyPublicOperation() && inTrading()")
private void tradingOperation() {}
连接点表达式前缀
常用的连接点表达式语句
// 所有public方法
execution(public * *(..)) //所有以set开头的方法
execution(* set*(..)) //AccountService接口上的所有方法
execution(* com.xyz.service.AccountService.*(..)) //service包中的所有类上的所有方法(最常用)
execution(* com.xyz.service.*.*(..)) //service包及其子包里的所有类上的所有方法
within(com.xyz.service..*)
连接点表达式语句
(5)声明建言
初学者往往会在学习了建言之后混淆建言、切点和连接点三者的关系。这主要是因为Spring AOP建议将三者放在一条语句中声明。
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before; @Aspect
public class BeforeExample { // 请注意()中的字符串并不是连接点表达语句而是指声明了@Aspect的方法,dataAccessOperation是切点的签名
@Before("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
public void doAccessCheck() {
// ...
} }
BeforeExample
以上提供的是单独的建言声明,对建言正确的理解应该是:建言对应切点,切点对应连接点。如果将三者合成一条注解声明,可以让代码显得更加紧凑也更加常用。
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() {
// ...
} }
BeforeExample
另外几种常用建言举例:
i.正常返回的建言
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
ii.处理异常的建言
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() {
// ...
} }
AfterThrowing
iii.针对finally的建言
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() {
// ...
} }
After
vi.Around建言(重点)
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;
} }
Around
Around建言比较特殊,需要用ProceedingJoinPoint对象作为参数。熟悉JDK DynamicProxy的人应该对类似方法不陌生。
(6)为多层建言指定顺序
对某个连接点指定多层建言时就有必要为此提供一个执行顺序,让切面类实现Ordered接口就能指定这个顺序。
package aop.order; import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component; @Component
@Aspect
public class AdvicerFirst implements Ordered {
// 设定的值越小执行顺序越靠前
private final int order = 1; public int getOrder() {
return order;
} @Before("execution(* aop.order.*.*(..))")
public void display() {
System.out.println("advicer 1st");
}
}
AdvicerFirst
package aop.order; import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component; @Component
@Aspect
public class AdvicerThen implements Ordered {
//设定的值越大执行顺序越靠后
private final int order = 10; public int getOrder() {
return order;
} @Around("execution(* aop.order.*.*(..))")
public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("advicer then start");
Object retVal = pjp.proceed();
System.out.println("advicer then end");
return retVal;
}
}
AdvicerThen
上面只是对Ordered接口的简单演示,实际开发中一般会增加setOrder(int order)方法,再在xml文档中通过IoC容器注入order值。
<aop:aspectj-autoproxy/> <bean id="beanId" class="className">
<property name="order" value="10"/>
</bean>
注入order值
(7)使用CGLIB生成代理
有关JDK DynamicProxy和CGLIB生成代理的差异不在本文的讲解范围。配置CGLIB的支持标签
<aop:aspectj-autoproxy proxy-target-class="true"/>
CGLIB支持标签
由于在Spring 3.2版本之后官方已经不再要求显式配置上述标签,针对JDK DynamicProxy无法实现代理的情况下Spring AOP框架会自动调用CGLIB。因此结论就是,你可以更放心的让Spring去自动生成代理了(感觉文档描述Spring AOP框架对CGLIB的支持就是广告)。
三、基于xml文档的AOP配置
上面详细介绍了如何通过注解来完成AOP代理,但通常情况下我们无法修改源代码或者我们能够获得的是已经经过编译的二进制文件。这种情形下如何使用Spring AOP框架来实现代理呢。
(1)为xml文档添加命名前缀
<?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"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 我将开发环境中常用的命名前缀配置在一起,方便查找 --> <!-- bean definitions here --> </beans>
常用命名前缀
(2)声明切面
<aop:config>
<!-- 将普通Bean对象声明为一个切面 -->
<aop:aspect id="myAspect" ref="aBean">
...
</aop:aspect>
</aop:config> <bean id="aBean" class="...">
...
</bean>
aop:aspect
(3)声明切点
<aop:config>
<!-- aop:pointcut也可以放在aop:aspect标签内部 -->
<!-- id值代表切点签名,expression值代表连接点表达式 -->
<aop:pointcut id="pointcutId" expression="execution(* packagename.*.*(..))"/> </aop:config>
aop:pointcut
(4)声明建言
<aop:aspect id="beforeExample" ref="aBean">
<!-- aop:before标签同样可以声明在aop:aspect内部 -->
<!-- pointcut-ref属性指代切点ID,method属性指代切面类中的方法 -->
<aop:before pointcut-ref="pointcutId" method="aspectInMethodName"/> ...
</aop:aspect>
aop:before
除了上面的常规配置手段以外,Spring AOP框架还提供了一种高度集成化的配置方案,但是只适合于方便修改源码的情况下使用。
(5)实现切面接口
i.Around接口
public class DebugInterceptor implements MethodInterceptor {
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("Before: invocation=[" + invocation + "]");
Object rval = invocation.proceed();
System.out.println("Invocation returned");
return rval;
}
}
Interception around advice
ii.Before接口
public class CountingBeforeAdvice implements MethodBeforeAdvice {
private int count;
public void before(Method m, Object[] args, Object target) throws Throwable {
System.out.println("Before...");
}
public int getCount() {
return count;
}
}
Before advice
iii.AfterReturn接口
public class CountingAfterReturningAdvice implements AfterReturningAdvice {
private int count;
public void afterReturning(Object returnValue, Method m, Object[] args, Object target)
throws Throwable {
System.out.println("After...");
}
public int getCount() {
return count;
}
}
After Returning advice
(6)声明顾问(advisor)
对于实现了切面接口的类,只需要在xml文档中使用aop:advisor标签就可以了。
<aop:config>
<aop:pointcut id="businessService"
expression="execution(* com.xyz.myapp.service.*.*(..))"/>
<aop:advisor
pointcut-ref="businessService"
advice-ref="tx-advice"/>
</aop:config>
<tx:advice id="tx-advice">
<tx:attributes>
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
aop:advisor
注意:顾问标签在实际的开发中并不常用,在Spring集成Hibernate的时候会用来声明事务管理。原因正如前文所述,使用切面接口仅限于方便修改源码的情况,而在类似的情况下采用注解方式配置AOP框架才是更合理的选择。所有接口在org.springframework.aop包中,需要了解的可以自己查阅API文档。
四、总结
根据Spring官方文档最后对AOP框架的总结,说明了以下两个问题:
(1)选择Spring AOP还是完整的AspectJ?
基本上如果你打算使用Spring IoC框架那么你就应该采用Spring AOP框架。除非你仅仅是希望使用AOP,而切面对象又不是通过Spring容器产生,那么你只能使用AspectJ。
(2)使用注解还是xml文档配置?
通常情况下两者几乎等价,官方文档的建议是使用xml配置。大概是显得思路清晰,易于修改。采用xml配置的局限性在于生成的切面只能是单例对象,并且无法实现组合式切面配置。
@Pointcut(propertyAccess() && operationReturningAnAccount())
public void accountPropertyAccess() {}
组合式切面配置
Spring IoC和AOP框架构成了它的底层实现,也是Spring学习的基础。了解这些知识并不能说明你已经精通了Spring,而是说明你可以继续更深入的学习了。
Hello Spring Framework——面向切面编程(AOP)的更多相关文章
- Spring(4)——面向切面编程(AOP模块)
Spring AOP 简介 如果说 IoC 是 Spring 的核心,那么面向切面编程就是 Spring 最为重要的功能之一了,在数据库事务中切面编程被广泛使用. AOP 即 Aspect Orien ...
- Spring学习手札(二)面向切面编程AOP
AOP理解 Aspect Oriented Program面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术. 但是,这种说法有些片面,因为在软件工程中,AOP的价值体现的并 ...
- 04 Spring:01.Spring框架简介&&02.程序间耦合&&03.Spring的 IOC 和 DI&&08.面向切面编程 AOP&&10.Spring中事务控制
spring共四天 第一天:spring框架的概述以及spring中基于XML的IOC配置 第二天:spring中基于注解的IOC和ioc的案例 第三天:spring中的aop和基于XML以及注解的A ...
- Spring IOP 面向切面编程
Spring IOP 面向切面编程 AOP操作术语 Joinpoint(连接点):所谓连接点是指那些被拦截到的点.在spring中,这些点指的是方法,因为spring只支持方法类型的连接点.(类里面 ...
- Spring框架系列(4) - 深入浅出Spring核心之面向切面编程(AOP)
在Spring基础 - Spring简单例子引入Spring的核心中向你展示了AOP的基础含义,同时以此发散了一些AOP相关知识点; 本节将在此基础上进一步解读AOP的含义以及AOP的使用方式.@pd ...
- Spring学习笔记:面向切面编程AOP(Aspect Oriented Programming)
一.面向切面编程AOP 目标:让我们可以“专心做事”,避免繁杂重复的功能编码 原理:将复杂的需求分解出不同方面,将公共功能集中解决 *****所谓面向切面编程,是一种通过预编译方式和运行期动态代理实现 ...
- Spring框架学习笔记(2)——面向切面编程AOP
介绍 概念 面向切面编程AOP与面向对象编程OOP有所不同,AOP不是对OOP的替换,而是对OOP的一种补充,AOP增强了OOP. 假设我们有几个业务代码,都调用了某个方法,按照OOP的思想,我们就会 ...
- Spring之控制反转——IoC、面向切面编程——AOP
控制反转——IoC 提出IoC的目的 为了解决对象之间的耦合度过高的问题,提出了IoC理论,用来实现对象之间的解耦. 什么是IoC IoC是Inversion of Control的缩写,译为控制 ...
- 面向切面编程(Aop)
AOP中的概念 AOP(Aspect Orient Programming),也就是面向切面编程.可以这样理解,面向对象编程(OOP)是从静态角度考虑程序结构,面向切面编程(AOP)是从动态角度考虑程 ...
随机推荐
- CentOS下设置默认JDK
最近在弄Linux,用yum源安装opnjdk-devel版本后,用命令ll /etc/alternatives/java查看,发现指向的是jre目录,而不是jdk,在此设置指向jdk目录. 1. 设 ...
- 安装GIT,集成到Powershell中
1.首先安装GIT http://msysgit.github.io 下载最新版,然后安装.没什么好说的. 装完之后,把 安装路径/bin 加入到环境变量的Path中 2.删除Git的右键菜单 安装完 ...
- angular $scope对象
$scope是一个pojo对象 $scope提供了一些工具方法.例如:$watch() $apply(),一般不会手工去调用 $scope是表达式的执行环境也叫作用域 $scope是一个树形结构,与D ...
- CSS特异性(CSS Specificity)的细节之CSS样式权重的计算与理解(CSS样式覆盖规则)
本篇讲解CSS特异性(CSS Specificity)的细节,也就是CSS样式选择器的权重计算 通过计算选择器的权重(weight)最终决定哪个选择器将获得优先权去覆盖其他选择器的样式设定,即“优先原 ...
- 【爬虫】BeautifulSoup之爬取百度贴吧的帖子
在网上看到爬百度贴吧的例子,仿照写了一个用BeautifulSoup实现的,直接上代码吧 #coding:gbk import urllib2 from bs4 import BeautifulSou ...
- 纯手写SpringMVC架构,用注解实现springmvc过程
1.第一步,首先搭建如下架构,其中,annotation中放置自己编写的注解,主要包括service controller qualifier RequestMapping 第二步:完成对应的anno ...
- angularJs之定时器
$timeout 服务 AngularJS $timeout 服务对应了 JS window.setTimeout 函数. 实例 两秒后显示信息: var app = angular.module(' ...
- 对System.ComponentModel.DataAnnotations 的学习应用
摘要 你还在为了验证一个Class对象中很多数据的有效性而写很多If条件判断吗?我也同样遇到这种问题,不过,最近学了一项新的方法,让我不在写很多if条件做判断,通过给属性标注特性来验证数据规则,从此再 ...
- CSS样式常用属性整理
web工程师是最近5年刚刚兴起的一门高薪职业,人们的专注度越来越高. 那么前端除了学习html标签之外还需要掌握什么知识点呢? 为大家整理了一个和HTML标签密不可分的知识要点--<CSS样式常 ...
- Java语言程序设计(基础篇) 第七章 一维数组
第七章 一维数组 7.2 数组的基础知识 1.一旦数组被创建,它的大小是固定的.使用一个数组引用变量,通过下标来访问数组中的元素. 2.数组是用来存储数据的集合,但是,通常我们会发现把数组看作一个存储 ...