面向切面编程(AOP)
1.什么是AOP?
**用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块,**这个模块被命名为“切面”(Aspect)。
例如:
将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来。允许你把遍布应用各处的功能分离出来形成可重用组件。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5nCdBfV9-1570362511719)(C:\Users\Leisure\AppData\Roaming\Typora\typora-user-images\1569571356919.png)]
2. AOP组成
切面(Aspect): 横切关注点(跨越应用程序多个模块的功能)被模块化的特殊对象
通知(Advice): 切面必须要完成的工作
目标(Target): 被通知的对象
**代理(Proxy):**向目标对象应用通知之后创建的对象
**连接点(Joinpoint):**程序执行的某个特定位置:如类某个方法调用前、调用后、方法抛出异常后等。连接点由两个信息确定:方法表示的程序执行点;相对点表示的方位。例如 ArithmethicCalculator#add() 方法执行前的连接点,执行点为 ArithmethicCalculator#add(); 方位为该方法执行前的位置
切点(pointcut):每个类都拥有多个连接点:例如 ArithmethicCalculator 的所有方法实际上都是连接点,即连接点是程序类中客观存在的事务。AOP 通过切点定位到特定的连接点。类比:连接点相当于数据库中的记录,切点相当于查询条件。切点和连接点不是一对一的关系,一个切点匹配多个连接点,切点通过 org.springframework.aop.Pointcut 接口进行描述,它使用类和方法作为连接点的查询条件。
3. AOP实现方式:
基于 AspectJ 注解或基于 XML 配置的 AOP
3.1 基于注解方式
导入相关的项目架包
spring-aop-4.0.0.RELEASE.jar
spring-aspects-4.0.0.RELEASE.jar
com.springsource.org.aopalliance-1.0.0.jar
com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
-
要在 Spring 中声明 AspectJ 切面, 切面一个IOC中的Bean,即加入**@Component注解**
-
在 AspectJ 注解中, 切面是一个带有 @Aspect 注解的 Java 类, 即加入 @Aspect注解
-
在xml配置文件中要添加 **<aop:aspectj-autoproxy/>**实现aop的自动代理
<!-- 建立aop的自动代理 --> <aop:aspectj-autoproxy/>
-
通知是标注有某种注解的简单的 Java 方法,写在切面类中。
AspectJ 支持 5 种类型的通知注解:
@Before:前置通知, 在方法执行之前执行
// 前置通知:在目标方法开始之前执行 // 比@Aruond的前置要前 @Before("execution(* com.nchu.service.ProductService.*())") public void before(JoinPoint joinPoint){ String methodName = joinPoint.getSignature().getName(); System.out.println("Start:"+ methodName); }
@After:
后置通知, 在方法执行之后执行
// 后置通知:在目标方法开始之后(不管是否发生异常)执行 // 不能访问到方法的结果 // 比@Aruond的后置要前 @After("execution(* com.nchu.service.ProductService.*())") public void after(JoinPoint joinPoint){ String methodName = joinPoint.getSignature().getName(); System.out.println("End:"+ methodName); }
@AfterRunning:
返回通知, 在方法返回结果之后执行
// 返回通知:在方法正常结束执行后的代码 // 可以访问到方法的返回值 // 在后置通知之后 @AfterReturning(value="execution(* com.nchu.service.ProductService.*())", returning="result") public void afterReturning(Object result){ System.out.println("Return :"+result); }
@AfterThrowing:
异常通知, 在方法抛出异常之后
// 异常通知:在目标方法出现异常时,会执行的代码 // 可以访问到异常对象;且可以指定在出现指定异常时再执行通知代码 @AfterThrowing(value="execution(* com.nchu.service.ProductService.*())", throwing="ex") public void afterReturning(Exception ex){ System.out.println("Exception :"+ex); }
@Around:
环绕通知, 围绕着方法执行
// 环绕通知:需要携带 ProceedingJoinPoint 类型的参数 // 环绕通知类类似于动态代理的全过程:ProceedingJoinPoint 类型的参数可以决定是否执行目标方法 // 必须需要返回值,即为目标方法的返回值 @Around("execution(* com.nchu.service.ProductService.*(..))") // 要抛异常 public Object performance(ProceedingJoinPoint joinPoint) throws Throwable{ // 前置通知 System.out.println("***************************+1"); // 执行目标方法 Object object = joinPoint.proceed(); // 后置通知 System.out.println("###########################+1"); return object; } // 对service类中的不同方法进行切面编程 @Around(value="execution(* com.nchu.service.ProductService.*(..))") public Object performance2(ProceedingJoinPoint joinPoint){ Object object=null; try { // 前置通知 System.out.println("***************************+2"); // 执行目标方法 object = joinPoint.proceed(); // 后置通知 System.out.println("###########################+2"); } catch (Throwable e) { // 异常通知 System.out.println("Around:Exception"); } return object; }
-
当同时有好几个切面时,可以使用
@Order()
注解进行指定切面优先级// 同时有好几个切面时,可以使用 @Order 注解指定切面优先级 // 值越小,优先级越高 @Order(1) @Component @Aspect public class PerformanceAspect { // 前置通知:在目标方法开始之前执行 // 比@Aruond的前置要前 @Before("execution(* com.nchu.service.ProductService.*())") public void before(JoinPoint joinPoint){ String methodName = joinPoint.getSignature().getName(); System.out.println("Start:"+ methodName); } }
-
可以使用切入点表达式,实现重用
**注意:**如果运行会抛出"error at ::0 can’t find referenced pointcut “错误,原因可能是当前的aspectj.weaver架包版本不行。
**解决方法:**可以尝试将com.springsource.org.aspectj.weaver.RELEASE.jar包替换为
com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar或后续版本,即可解决问题。
// 可定义切入点表达式,实现重用 @Pointcut("execution(* com.nchu.service.ProductService.*())") public void defineJoinPointCut(){} // 前置通知:在目标方法开始之前执行 // 比@Aruond的前置要前 @Before("defineJoinPointCut()") public void before(JoinPoint joinPoint){ String methodName = joinPoint.getSignature().getName(); System.out.println("Start:"+ methodNme); }
3.2 基于XML文件方式
直接在xml文件中进行配置:
<!-- 1. 创建服务类的bean,用于测试 -->
<bean name="s" class="com.nchu.service.ProductService"/>
<!-- 2. 创建切面类的bean,用于后面的使用 -->
<bean id="aspect" class="com.nchu.aspect.PerformanceAspect"/>
<!-- 3. 使用aop:config来设置aop配置 -->
<aop:config>
<!-- 3.1 使用aop:pointcut 标识切点 -->
<aop:pointcut expression="execution(* com.nchu.service.ProductService.*()) " id="toPerformance"/>
<!-- 3.2 配置对应的切面依赖 -->
<aop:aspect id="perfAspect" ref="aspect">
<!-- 3.2.1 配置pointcut-ref切点依赖、 method配置切点触发对应的切面方法-->
<aop:around pointcut-ref="toPerformance" method="performance2"/>
</aop:aspect>
</aop:config>