什么是AOP
AOP的全称是 Aspect-Oriented Programming,即面向切面编程(也称面向方面编程,它是面向对象编程(OOP)的一种补充,目前已成为一种比较成熟的编程方式。
AOP采取横向抽取机制,将分散在各个方法中的重复代码提取出来,然后在程序编译或运行时,再将这些提取出来的代码应用到需要执行的地方。虽然AOP是一种新的编程思想,但却不是OOP的替代品,它只是OOP的延伸和补充。
AspectJ是一个基于Java语言的AOP框架,它提供了强大的AOP功能。Spring2.0以后,Spring AOP引入了对AspectJ的支持,并允许直接使用AspectJ进行编程,而Spring自身的AOP API也尽量与AspectJ保持一致。新版本的Spring框架也建议使用AspectJ来开发AOP。
使用AspectJ实现AOP有两种方式:一种是基于XML的声明式AspectJ,另一种是基于注解的声明式AspectJ。
基于XML的声明式AspectJ
基于XML的声明式 AspectJ是指通过XML文件来定义切面、切入点及通知,所有的切面、切入点和通知都必须定义在< aop: config>元素内。Spring配置文件中的< beans>元素下可以包含多个< aop: config>元素,一个<aop: config>元素中又可以包含属性和子元素,其子元素包括< aop: pointcut>、<aop: advisor>和< aop: aspect>。在配置时,这3个子元素必须按照此顺序来定义。在<aop: aspect>元素下,同样包含了属性和多个子元素,通过使用<aop: aspect>元素及其子元素就可以在XML文件中配置切面、切入点和通知。常用元素的配置代码如下所示。
<!-- 定义切面Bean -->
<bean id="myAspect" class="com. smm. aspectj.xmI.MyAspect />
<aop:config>
<!-- 1.配置切面 -->
<aop:aspect id="aspect" ref="myAspect">
<!-- 2.配置切入点 -->
<aop:pointcut expression="execution(* com.ssm.aspectj.*.*(..))" id="myPointCut" />
<!-- 3.配置通知 -->
<!-- 前置通知 -->
<aop:before method="myBefore" pointcut-ref="myPointCut" />
<!--后置通知-->
<aop:after-returning method="myAfterReturning"
pointcut-ref="myPointCut" returning="returnVal" />
<!--环绕通知 -->
<aop:around method="myAround" pointcut-ref="myPointCut" />
<!--异常通知 -->
<aop:after-throwing method="myAfterThrowing"
pointcut-ref="myPointCut" throwing="e" />
<!--最终通知 -->
<aop:after method="myAfter" pointcut-ref="myPointCut" />
</aop:aspect>
</aop:config>
- 在 Spring的配置文件中,配置切面使用的是<aop: aspect>元素,该元素会将一个已定义好的Spring Bean转换成切面Bean,所以要在配置文件中先定义一个普通的Spring Bean(如上述代码中定义的my Aspect)。定义完成后,通过< aop: aspect>元素的ref属性即可引用该Bean。
- 在 Spring的配置文件中,切入点是通过<aop: pointcut>元素来定义的。当< aop: pointcut>元素作为< aop: config>元素的子元素定义时,表示该切入点是全局切入点,它可被多个切面所共享;当< aop: pointcut>元素作为< aop: aspect>元素的子元素时,表示该切入点只对当前切面有效。在定义< aop:pointcut>元素时,通常会指定id和 expression两个属性
- 在上述配置代码片段中,execution(* com. ssm.jdk..(..))就是定义的切入点表达式,该切入点表达式的意思是匹配com. ssm.jdk包中任意类的任意方法的执行。其中 execution0是表达式的主体,第1个表示的是返回类型,使用代表所有类型;com. ssm.jdk表示的是需要拦截的包名,后面第2个表示的是类名,使用代表所有的类;第3个表示的是方法名,使用表示所有方法;后面()表示方法的参数,其中的“..”表示任意参数。需要注意的是,第1个与包名之间有一个空格。
-
在配置代码中,分别使用<aop: aspect>的子元素配置了5种常用通知,这5个子元素不支持使用子元素,但在使用时可以指定一些属性
-
AspectJ框架相关的JAR包
spring- aspects-4.3.6. RELEASE. Jar:Spring为AspectJ提供的实现, Spring的包中已经提供。
aspectjweaver-1.8.10.jar:是 AspectJ框架所提供的规范
package com.ssm.aspectj.xml;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
/**
* 切面类,在此类中编写通知
*/
public class MyAspect {
//前置通知
public void myBefore(JoinPoint joinPoint){
System.out.print("前置通知:模拟执行权限检查...,");
System.out.print("目标类是:"+joinPoint.getTarget());
System.out.println(",被植入增强处理的目标方法为:"+
joinPoint.getSignature().getName());
}
//后置通知
public void myAfterReturning(JoinPoint joinPoint) {
System.out.print("后置通知:模拟记录日志...,");
System.out.println("被植入增强处理的目标方法为:" +
joinPoint.getSignature().getName());
}
/**
* 环绕通知
* ProceedingJoinPoint是JoinPoint的子接口,表示可执行目标方法
* 1.必须是Object类型的返回值
* 2.必须接收一个参数,类型为ProceedingJoinPoint
* 3.必须throws Throwable
*/
public Object myAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
//开始
System.out.println("环绕开始:执行目标方法之前,模拟开启事务...,");
//执行当前目标方法
Object obj=proceedingJoinPoint.proceed();
//结束
System.out.println("环绕结束:执行目标方法之后,模拟关闭事务...,");
return obj;
}
//异常通知
public void myAfterThrowing(JoinPoint joinPoint,Throwable e){
System.out.println("异常通知:出错了"+e.getMessage());
}
//最终通知
public void myAfter(){
System.out.println("最终通知:模拟方法结束后释放资源...");
}
}
<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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
<!-- 1 目标类 -->
<bean id="userDao" class="com.ssm.aspectj.UserDaoImpl" />
<!-- 2 切面 -->
<bean id="myAspect" class="com.ssm.aspectj.xml.MyAspect" />
<!-- 3 aop编程 -->
<aop:config>
<!-- 1.配置切面 -->
<aop:aspect id="aspect" ref="myAspect">
<!-- 2.配置切入点 -->
<aop:pointcut expression="execution(* com.ssm.aspectj.*.*(..))" id="myPointCut" />
<!-- 3.配置通知 -->
<!-- 前置通知 -->
<aop:before method="myBefore" pointcut-ref="myPointCut" />
<!--后置通知-->
<aop:after-returning method="myAfterReturning" pointcut-ref="myPointCut"
returning="returnVal"/>
<!--环绕通知 -->
<aop:around method="myAround" pointcut-ref="myPointCut" />
<!--异常通知 -->
<aop:after-throwing method="myAfterThrowing" pointcut-ref="myPointCut"
throwing="e" />
<!--最终通知 -->
<aop:after method="myAfter" pointcut-ref="myPointCut" />
</aop:aspect>
</aop:config>
</beans>
基于注解的声明式 AspectJ
基于XML的声明式AspectJ实现AOP编程虽然便捷,但是它也存在着一些缺点,那就是要在Spring文件中配置大量的代码信息。为了解决这个问题,AspectJ框架为AOP的实现提供了一套注解,用以取代 Spring配置文件中为实现AOP功能所配置的臃肿代码。
package com.ssm.aspectj.annotation;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
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.stereotype.Component;
/**
* 切面类,在此类中编写通知
*/
@Aspect
@Component
public class MyAspect {
//定义切入点表达式
@Pointcut("execution(* com.ssm.aspectj.*.*(..))")
//使用一个返回值为void、方法体为空的方法来命名切入点
public void myPointCut(){}
//前置通知
@Before("myPointCut()")
public void myBefore(JoinPoint joinPoint){
System.out.print("前置通知:模拟执行权限检查..,");
System.out.print("目标类是:"+joinPoint.getTarget());
System.out.println(",被植入增强处理的目标方法为:"+
joinPoint.getSignature().getName());
}
//后置通知
@AfterReturning(value="myPointCut()")
public void myAfterReturning(JoinPoint joinPoint) {
System.out.print("后置通知:模拟记录日志..,");
System.out.println("被植入增强处理的目标方法为:" +
joinPoint.getSignature().getName());
}
/**
* 环绕通知
* ProceedingJoinPoint是JoinPoint的子接口,表示可执行目标方法
* 1.必须是Object类型的返回值
* 2.必须接收一个参数,类型为ProceedingJoinPoint
* 3.必须throws Throwable
*/
@Around("myPointCut()")
public Object myAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
//开始
System.out.println("环绕开始:执行目标方法之前,模拟开启事务..,");
//执行当前目标方法
Object obj=proceedingJoinPoint.proceed();
//结束
System.out.println("环绕结束:执行目标方法之后,模拟关闭事务..,");
return obj;
}
//异常通知
@AfterThrowing(value="myPointCut()",throwing="e")
public void myAfterThrowing(JoinPoint joinPoint,Throwable e){
System.out.println("异常通知:出错了"+e.getMessage());
}
//最终通知
@After("myPointCut()")
public void myAfter(){
System.out.println("最终通知:模拟方法结束后释放资源..");
}
}
<?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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.3.xsd">
<!-- 指定需要扫描的包,使注解生效 -->
<context:component-scan base-package="com.ssm" />
<!-- 启动基于注解的声明式AspectJ支持 -->
<aop:aspectj-autoproxy />
</beans>