Spring 面向切面编程 第1关:使用前后置通知统计所有方法的执行时间

28 篇文章 9 订阅

目录

任务描述

相关知识

AOP基本概念

切点完整表达式

五种通知

实例解析

编程要求

测试说明

参考代码


任务描述

Spring AOPSpring框架的核心技术之一,不幸的是,AOP 中许多概念不是特别直观,因此,本关卡将使用代码案例的方式讲解 AOP相关概念。

本关任务:使用前后置通知统计出博客系统中博客类中每个业务方法的执行时长。

相关知识

AOP基本概念

  • Aspect(切面): 切面由切点 (Pointcut) 和增强/通知 (Advice) 组成,它既包括了横切逻辑的定义、也包括了连接点的定义;

  • Joint point(连接点):能够被拦截的地方:Spring AOP 是基于动态代理的,所以是方法拦截的。每个成员方法都可以称之为连接点;

  • Pointcut(切点):具体定位的连接点,上面也说了,每个方法都可以称之为连接点,我们具体定位到某一个方法就成为切点;

  • Advice(通知/增强):表示添加到切点的一段逻辑代码,并定位连接点的方位信息。简单来说就定义了是干什么的,具体是在哪干;

  • Spring AOP提供了5Advice类型给我们,分别是:前置(Before)、后置(After)、返回(AfterReturning)、异常(AfterThrowing)、环绕(Around);

  • Target(目标对象):织入Advice的目标对象;

  • Weaving(织入):将增强/通知添加到目标类的具体连接点上的过程。

切点完整表达式

切点的完整表达式如下:

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

参数参数说明
modifiers-pattern修饰符 可选 public private protected
declaring-type-pattern方法的声明类型
name-patterm方法名称类型,例 set* 则表示以set开头的所有的方法名称
param-pattern参数匹配:(..) 表示任意多个参数,每个参数任意多个类型,(*,String) 表示两个参数,第一个是任意类型,第二个是String
throws-pattern异常的匹配模式
ret-type-pattern返回类型 必选 * 代表任意类型

五种通知

前置通知:在连接点前面执行,前置通知不会影响连接点的执行;

后置通知:在连接点执行完成后执行,不管是正常执行完成,还是抛出异常,都会执行返回通知中的内容;

异常返回通知:在连接点抛出异常后执行;

正常返回通知:在连接点正常执行完成后执行,如果连接点抛出异常,则不会执行;

环绕通知:最为强大的通知,它能够让你编写的逻辑将被通知的目标方法完全包裹起来。实际上就像在一个通知方法中同时编写前置通知和后置通知。定义通知的时候在通知方法中添加了入参ProceedingJoinPoint,这个参数是必须写的。因为需要在通知中使用ProceedingJoinPoint.proceed()调用目标方法。

  1. @Around("切点")
  2. public void around(ProceedingJoinPoint point) throws InterruptedException {
  3. //目标方法执行前
  4. System.out.println(" 环绕通知前");
  5. try {
  6. //执行目标方法
  7. point.proceed();
  8. } catch (Throwable throwable) {
  9. throwable.printStackT\frace();
  10. }
  11. //目标方法执行后
  12. System.out.println(" 环绕通知后");
  13. }

实例解析

看完AOP基本概念是不是感觉有点晦涩难懂?下面就让我们以实例讲解一下,比如企业招聘的时候,面试官只需要做面试打分相关的核心功能即可,简历筛选和入职相关步骤交给人力资源就可以了,又比如博客系统中用户(User类)有个修改用户名和密码的核心功能,但是在修改用户密码之前,我们会校验该用户是否具有权限,检验权限的地方有很多,我们把这一功能独立出来当成切面类,而User类只需实现修改用户名和密码方法即可,下面就让我们看一下具体是怎么实现的。

  • 创建目标类(待增强的目标对象):
  1. package Educoder;
  2. import org.springframework.stereotype.Component;
  3. @Component("User")
  4. public class User {
  5. public void updateNameAndPw() {
  6. System.out.println("用户-修改用户名和密码");
  7. }
  8. }
  • 创建切面类(包含切点execution(* Educoder.User.updateNameAndPw())和通知/增强before()方法):
  1. package Educoder;
  2. import org.aspectj.lang.annotation.*;
  3. import org.springframework.stereotype.Component;
  4. @Component("Authority")
  5. @Aspect
  6. public class Authority {
  7. //前置通知,括号内切点表达式的意思就是`Educoder`包下`User`类下的`updateNameAndPw()`方法
  8. @Before("execution(* Educoder.User.updateNameAndPw())")
  9. public void before() {
  10. System.out.println("权限校验");
  11. }
  12. }
  • applicationContext.xml中配置自动注入:
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xmlns:context="http://www.springframework.org/schema/context"
  5. xmlns:aop="http://www.springframework.org/schema/aop"
  6. xsi:schemaLocation="http://www.springframework.org/schema/beans
  7. http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context
  8. http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
  9. <!--表明 Educoder 包及其子包中,如果某个类的头上带有特定的注解
  10. @Component,@Repository,@Service等就会将这个对象作为 Bean 注册进Spring 容器 -->
  11. <context:component-scan base-package="Educoder" />
  12. <!--自动为spring容器中那些配置@aspectJ切面的 Bean 创建代理,织入切面。 -->
  13. <aop:aspectj-autoproxy/>
  14. </beans>
  • 编写测试代码:
  1. package Educoder;
  2. import org.springframework.context.ApplicationContext;
  3. import org.springframework.context.support.ClassPathXmlApplicationContext;
  4. public class Test {
  5. public static void main(String[] args) {
  6. //创建Spring的IOC容器对象
  7. ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
  8. //从IoC的容器中获取Bean实例
  9. Interviewer spr1 = applicationContext.getBean("User", Interviewer.class);
  10. //调用updateNameAndPw()方法
  11. spr1.updateNameAndPw();
  12. }
  13. }
  • 输出结果如下: 权限校验 用户-修改用户名和密码

编程要求

请仔细阅读右侧代码,根据方法内的提示,在Begin - End区域内进行代码补充,把目标类(BlogService)中的所有方法的执行时间统计出来,目标类在右侧代码文件区域可见。 后台IoC容器对象会自动获取目标类实例并调用目标类中所有方法。

测试说明

补充完代码后,点击测评,平台会对你编写的代码进行测试,当你的结果与预期输出一致时,即为通过。 预期输出:

当前时间2019.1.1 00:00:00

业务功能一

当前时间2019.1.1 00:01:00,执行耗时60000

当前时间2019.1.1 00:00:00

业务功能二

当前时间2019.1.1 00:01:00,执行耗时60000


开始你的任务吧,祝你成功!

参考代码

package Educoder;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;

import org.springframework.stereotype.Component;

import java.text.ParseException;
import java.text.SimpleDateFormat;

@Component("BlogAdvice")
@Aspect
public class BlogAdvice {
    //定义切点
    @Pointcut("execution(* Educoder.BlogService.service*(..))")
    public void My() {
    }
	
	//定义前置通知,输出当前时间2019.1.1 00:00:00
    @Before("My()")
    public void before() {
        System.out.println("当前时间2019.1.1 00:00:00");
    }
    //定义后置通知,输出当前时间2019.1.1 00:01:00,执行耗时60000  
    @After("My()")
    public void after() {
    System.out.println("当前时间2019.1.1 00:01:00,执行耗时60000");
    }
}

 

  • 11
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

于建章

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值