面向切面编程(AOP):通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。
AOP术语:
通知(Advice):就是需要进行加强的一些操作,方法。
连接点(Joinpoint):在程序执行过程中某个特定的点,比如某方法调用的时候或者处理异常的时候。在Spring AOP中,一个连接点总是表示一个方法的执行。
切入点(Pointcut):匹配连接点的断言。通知和一个切入点表达式关联,并在满足这个切入点的连接点上运行(例如,当执行某个特定名称的方法时)。切入点表达式如何和连接点匹配是AOP的核心:Spring缺省使用AspectJ切入点语法。
切面(Aspect):一个关注点的模块化,这个关注点可能会横切多个对象。事务管理是J2EE应用中一个关于横切关注点的很好的例子。在Spring AOP中,切面可以使用基于模式)或者基于@Aspect注解的方式来实现。
引入(Introduction):用来给一个类型声明额外的方法或属性(也被称为连接类型声明(inter-type declaration))。Spring允许引入新的接口(以及一个对应的实现)到任何被代理的对象。
目标对象(Target Object): 被一个或者多个切面所通知的对象。也被称做被通知(advised)对象。 既然Spring AOP是通过运行时代理实现的,这个对象永远是一个被代理(proxied)对象。
AOP代理(AOP Proxy):AOP框架创建的对象,用来实现切面契约(例如通知方法执行等等)。在Spring中,AOP代理可以是JDK动态代理或者CGLIB代理。
织入(Weaving):把切面连接到其它的应用程序类型或者对象上,并创建一个被通知的对象。这些可以在编译时(例如使用AspectJ编译器),类加载时和运行时完成。Spring和其他纯Java AOP框架一样,在运行时完成织入。
通知类型:
前置通知(Before advice):在某连接点之前执行的通知,但这个通知不能阻止连接点之前的执行流程(除非它抛出一个异常)。
后置通知(After returning advice):在某连接点正常完成后执行的通知:例如,一个方法没有抛出任何异常,正常返回。
异常通知(After throwing advice):在方法抛出异常退出时执行的通知。
最终通知(After (finally) advice):当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。
环绕通知(Around Advice):包围一个连接点的通知,如方法调用。这是最强大的一种通知类型。环绕通知可以在方法调用前后完成自定义的行为。它也会选择是否继续执行连接点或直接返回它自己的返回值或抛出异常来结束执行。
定义切入点:
使用AspectJ的切点表达式语言定义切点。AspectJ的切点表达式一共有九种,
-
execution:用于匹配执行方法的连接点,这是Spring AOP中最主要的切入点指示符。该切入点的用法也相对复杂,execution表达式的格式如下:
execution(modifier-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)
上面的格式中,execution是不变的,用于作为execution表达式的开头,整个表达式中几个参数的详细解释如下:
- modifier-pattern:指定方法的修饰符,支持通配符,该部分可以省略
- throw-pattern:指定方法声明抛出的异常,支持通配符,该部分可以省略
- param-pattern:指定方法的形参列表,支持两个通配符,“*”和“..”,其中“*”代表一个任意类型的参数,而“..”代表0个或多个任意类型的参数。
- name-pattern:指定匹配的方法名,支持通配符,可以使用“*”来通配所有的方法名
- declaring-type-pattern:指定方法所属的类,支持通配符,该部分可以省略
- ret-type-pattern:指定返回值类型,支持通配符,可以使用“*”来通配所有的返回值类型
如下是几个execution表达式:
execution(public * * (..))//匹配所有public方法
execution(* com.abc.service.AdviceManager.* (..))//匹配AdviceManager中任意方法
其余的八种表达式都是用来限定匹配的。
-
within:限定匹配特定类型的连接点,
within(com.abc.service.*)//匹配com.abc.service包中的任意连接点
-
@within:限定匹配特定注解类型的连接点
@within(com.learn.annotation.Secure)//匹配
持有Secure注解的类方法;
-
this:用于指定AOP代理必须是指定类型的实例,用于匹配该对象的所有连接点。
this(com.abc.service.AdviceManager)//匹配实现了AdviceManager接口的代理对象的所有连接点
-
target:用于限定目标对象必须是指定类型的实例,用于匹配该对象的所有连接点。
target(com.abc.servcie.AdviceManager)//匹配实现了AdviceManager接口的目标对象的所有连接点,
- @target:用于限定目标对象对应的类必须具有指定类型的注解
@target(com.learn.annotation.Secure)//匹配对象对应的类
持有Secure注解的方法;
-
args:用于对连接点的参数类型进行限制,要求参数的类型时指定类型的实例。
args(java.io.Serializable)//匹配只接受一个参数,且参数类型是Serializable的所有连接点,
-
@args:用于对连接点的参数的注解进行限制,要求参数的注解是指定类型的实例。
-
@annotation:限定带有指定注解的连接点
表达式中可以使用&&符号表示与关系,使用||表示或关系、使用!表示非关系。在XML文件中使用and、or和not这三个符号。如excecution(* com.tianmaying.service.BlogService.updateBlog(..))&&within(concert.*)
在切点中引用Bean
Spring还提供了一个bean()描述符,用于在切点表达式中引用Spring Beans。例如:
excecution(* com.tianmaying.service.BlogService.updateBlog(..)) and bean('tianmayingBlog')
这表示将切面应用于BlogService的updateBlog方法上,但是仅限于ID为tianmayingBlog的Bean。
也可以排除特定的Bean:
excecution(* com.tianmaying.service.BlogService.updateBlog(..)) and !bean('tianmayingBlog')
在XML中配置切面
使用标签如下:
<aop:config> <!-- AOP定义开始 -->
<aop:pointcut/> <!-- 定义切入点 -->
<aop:advisor/> <!-- 定义AOP通知器 -->
<aop:aspect> <!-- 定义切面开始 -->
<aop:pointcut/> <!-- 定义切入点 -->
<aop:before/> <!-- 前置通知 -->
<aop:after-returning/> <!-- 后置返回通知 -->
<aop:after-throwing/> <!-- 后置异常通知 -->
<aop:after/> <!-- 后置通知(不管通知的方法是否执行成功) -->
<aop:around/> <!-- 环绕通知 -->
<aop:declare-parents/> <!-- 引入通知 -->
</aop:aspect> <!-- 定义切面结束 -->
</aop:config> <!-- AOP定义结束 -->
目标类:
public class Task {
public void show(){
System.out.println("run show:");
}
}
自定义通知类:
public class Advice {
public void before(){
System.out.println("前置通知");
}
public void after(){
System.out.println("后置通知");
}
public void end(){
System.out.println("最终通知");
}
public void exception(){
System.out.println("异常通知");
}
}
配置项如下:
<bean id="task" class="com.spring.aop.task.Task"></bean>
<bean id="advice" class="com.spring.aop.advice.Advice"></bean>
<aop:config>
<aop:pointcut expression="execution(public * com.spring.aop.task.Task.*(..))"
id="servicePointcut"/>
<aop:aspect id="taskAspect" ref="advice">
<aop:before method="before" pointcut-ref="servicePointcut" />
<aop:after method="after" pointcut-ref="servicePointcut"/>
<aop:after-returning method="end" pointcut-ref="servicePointcut"/>
<aop:after-throwing method="exception" pointcut-ref="servicePointcut"/>
</aop:aspect>
</aop:config>
<aop:aspectj-autoproxy/> //开启代理
测试代码:
public class TaskTest {
@Test
public void show() throws Exception {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
Task task = (Task) ctx.getBean("task");
task.show("test");
}
}
当使用环绕通知时,通知类的方法有点不一样,这时也不需要配置前置通知、后置通知、异常通知了,一般是单独使用。
环绕通知类:
public class AroundAdvice {
public void around(ProceedingJoinPoint jp){
try {
System.out.println("before task");
jp.proceed();
System.out.println("after task");
} catch (Throwable throwable) {
System.out.println("exception");
}
}
}
配置项如下:
<bean id="task" class="com.spring.aop.task.Task"></bean>
<bean id="aroundAdvice" class="com.spring.aop.advice.AroundAdvice"></bean>
<aop:config>
<aop:pointcut expression="execution(public * com.spring.aop.task.Task.*(..))"
id="servicePointcut"/>
<aop:aspect id="taskAspect" ref="aroundAdvice">
</aop:aspect>
</aop:config>
<aop:aspectj-autoproxy/>
当通知需要参数时,可通过切面表达式传入。必须保证通知类的参数名称和切面中的参数名称一致。
通知类:
public class Advice {
public void before(String arg){
System.out.println("前置通知"+arg);
}
}
目标类:
public class Task {
public void show(String arg){
System.out.println("run show:"+arg);
}
}
配置项:
<bean id="task" class="com.spring.aop.task.Task"></bean>
<bean id="advice" class="com.spring.aop.advice.Advice"></bean>
<aop:config>
<aop:pointcut expression="execution(public * com.spring.aop.task.Task.*(String)) and args(arg)"
id="servicePointcut"/>
<aop:aspect id="taskAspect" ref="advice">
<aop:before method="before" pointcut-ref="servicePointcut"></aop:before>
</aop:aspect>
</aop:config>
<aop:aspectj-autoproxy/>
aop引入:是用来增加目标类的方法的。原理就是为需要添加的方法建立一个类,然后建一个代理类,同时代理该类和目标类。适用于原目标类非常复杂,动一发而牵全身。
目标类:
public class Task {
public void show(String arg){
System.out.println("run show:"+arg);
}
}
新增方法接口如下:
public interface AdviceInterface {
void print();
}
实现类如下:
public class AdviceInterfaceImpl implements AdviceInterface {
public void print() {
System.out.println("接口实现");
}
}
配置项如下:
<aop:config>
<aop:aspect>
<aop:declare-parents types-matching="com.spring.aop.task.Task" //目标类
implement-interface="com.spring.aop.myinterface.AdviceInterface" //新增接口
default-impl="com.spring.aop.myinterface.impl.AdviceInterfaceImpl"//接口默认实现类
></aop:declare-parents>
</aop:aspect>
</aop:config>
<aop:aspectj-autoproxy proxy-target-class="true"/>//因为目标类不是接口,所以要使用CGLib进行代理
测试类如下:
public class TaskTest {
@Test
public void show() throws Exception {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
Task task = (Task) ctx.getBean("task");
task.show("111");
AdviceInterface adviceInterface = (AdviceInterface) task;
adviceInterface.print();
}
}
使用注解完成AOP:
通知类:
@Aspect//表示该类是个通知类
public class Advice {
@Before("execution(* com.spring.aop.task.Task.*(..))")
public void before(){
System.out.println("前置通知");
}
@After("execution(* com.spring.aop.task.Task.*(..))")
public void after(){
System.out.println("后置通知");
}
@AfterReturning("execution(* com.spring.aop.task.Task.*(..))")
public void end(){
System.out.println("最终通知");
}
@AfterThrowing("execution(* com.spring.aop.task.Task.*(..))")
public void exception(){
System.out.println("异常通知");
}
}
或者
@Aspect
public class Advice {
@Pointcut("execution(* com.spring.aop.task.Task.*(..))")
public void pointcut(){}
@Before("pointcut()")
public void before(){
System.out.println("前置通知");
}
@After("pointcut()")
public void after(){
System.out.println("后置通知");
}
@AfterReturning("pointcut()")
public void end(){
System.out.println("最终通知");
}
@AfterThrowing("pointcut()")
public void exception(){
System.out.println("异常通知");
}
}
目标类:
@Component
public class Task {
public void show(){
System.out.println("run show:");
}
}
配置类:
@Configuration
@EnableAspectJAutoProxy//表示开启自动代理
@ComponentScan(basePackages = "com.spring.aop.task")
public class AdviceConfig {
@Bean
public Advice advice(){
return new Advice();
}
}
测试类:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = AdviceConfig.class)
public class TaskTest {
@Autowired
private Task task;
@Test
public void show() throws Exception {
task.show("111");
}
}
配置环绕通知:
@Aspect
public class AroundAdvice {
@Pointcut("execution(* com.spring.aop.task.Task.*(..))")
public void pointcut(){}
@Around("pointcut()")
public void around(ProceedingJoinPoint jp){
try {
System.out.println("before task");
jp.proceed();
System.out.println("after task");
} catch (Throwable throwable) {
System.out.println("exception");
}
}
}
处理通知参数与使用配置文件时一样。
引入新方法
@Aspect
public class Advice {
@DeclareParents(value = "com.spring.aop.task.Task",
defaultImpl = AdviceInterfaceImpl.class)
public static AdviceInterface adviceInterface;
}
@DeclareParents用于引入
value:指定需要引入新方法的目标类,com.spring.aop.task.Task+,当使用加号时代表当前类及其子类。
defaultImpl:指定为引入方法提供实现的类。
配置类如下:
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true)//因为目标类不是接口,所以要使用CGLib进行代理
@ComponentScan(basePackages = "com.spring.aop.task")
public class AdviceConfig {
@Bean
public Advice advice(){
return new Advice();
}
}
测试类如下:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = AdviceConfig.class)
public class TaskTest {
@Autowired
private Task task;
@Test
public void show() throws Exception {
task.show("111");
AdviceInterface adviceInterface= (AdviceInterface) task;
adviceInterface.print();
}
}
最后Spring可以注入AspectJ切面,这主要是可以利用到AspectJ的切点定义不局限于方法,而Spring AOP中的切点只能用于方法,但这涉及到AspectJ的原生语法。这方面不懂,以后有使用到再学习。