目录
Spring AOP是Spring框架的核心技术之一,不幸的是,AOP 中许多概念不是特别直观,因此,本关卡将使用代码案例的方式讲解 AOP相关概念。
本关任务:使用前后置通知统计出博客系统中博客类中每个业务方法的执行时长。
Aspect(切面): 切面由切点 (Pointcut) 和增强/通知 (Advice) 组成,它既包括了横切逻辑的定义、也包括了连接点的定义;
Joint point(连接点):能够被拦截的地方:Spring AOP 是基于动态代理的,所以是方法拦截的。每个成员方法都可以称之为连接点;
Pointcut(切点):具体定位的连接点,上面也说了,每个方法都可以称之为连接点,我们具体定位到某一个方法就成为切点;
Advice(通知/增强):表示添加到切点的一段逻辑代码,并定位连接点的方位信息。简单来说就定义了是干什么的,具体是在哪干;
Spring AOP提供了5种 Advice类型给我们,分别是:前置(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()调用目标方法。
@Around("切点")public void around(ProceedingJoinPoint point) throws InterruptedException {//目标方法执行前System.out.println(" 环绕通知前");try {//执行目标方法point.proceed();} catch (Throwable throwable) {throwable.printStackT\frace();}//目标方法执行后System.out.println(" 环绕通知后");}
看完AOP基本概念是不是感觉有点晦涩难懂?下面就让我们以实例讲解一下,比如企业招聘的时候,面试官只需要做面试打分相关的核心功能即可,简历筛选和入职相关步骤交给人力资源就可以了,又比如博客系统中用户(User类)有个修改用户名和密码的核心功能,但是在修改用户密码之前,我们会校验该用户是否具有权限,检验权限的地方有很多,我们把这一功能独立出来当成切面类,而User类只需实现修改用户名和密码方法即可,下面就让我们看一下具体是怎么实现的。
package Educoder;import org.springframework.stereotype.Component;@Component("User")public class User {public void updateNameAndPw() {System.out.println("用户-修改用户名和密码");}}
execution(* Educoder.User.updateNameAndPw())和通知/增强before()方法):
package Educoder;import org.aspectj.lang.annotation.*;import org.springframework.stereotype.Component;@Component("Authority")@Aspectpublic class Authority {//前置通知,括号内切点表达式的意思就是`Educoder`包下`User`类下的`updateNameAndPw()`方法@Before("execution(* Educoder.User.updateNameAndPw())")public void before() {System.out.println("权限校验");}}
applicationContext.xml中配置自动注入:
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xmlns:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
package Educoder;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;public class Test {public static void main(String[] args) {//创建Spring的IOC容器对象ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");//从IoC的容器中获取Bean实例Interviewer spr1 = applicationContext.getBean("User", Interviewer.class);//调用updateNameAndPw()方法spr1.updateNameAndPw();}}
权限校验 用户-修改用户名和密码请仔细阅读右侧代码,根据方法内的提示,在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");
- }
- }
