说明:
(1)【Spring AOP面向切面编程】 具体的使用场景、使用习惯等内容,还需要慢慢积累;因为目前觉得,究竟什么时候使用这个技术,对于哪些或哪类功能适合使用这个技术,还不太清除。这是个需要后面加强和提升的能力!
(2)本篇博客的【一:准备一个…】,【二:需求说明】,【三:正式编码…】部分都是些准备工作,不是重点啦,可以快速浏览过去;【四:正式实现…】是本博客的核心;
(3)AOP必须在IoC的基础上才能起作用!!!
(4)本篇博客仅仅是一个入门性质的介绍,目的是AOP初体验;关于AOP的详细内容,在后面介绍;
目录
(1)pom.xml:引入所需依赖:【spring-context模块】,【aspectjweaver模块】
(2)创建applicationContext.xml配置文件:引入【默认命名空间】,【context命名空间】,【aop命名空间】;配置IoC容器中的bean;
(3)创建SpringApplication类:为了能让IoC容器运行起来;
四:正式实现:使用【Spring AOP技术】实现【二:需求说明】中的需求;
(1)创建容纳切面类的包:aspect包;添加针对方法的切面类:MethodAspect类;
(2)配置applicationContext.xml:配置究竟在【哪些类的哪些方法上】应用【MethodAspect切面类】;
(3)运行效果:还是沿用上面的SpringApplication入口类,没有作任何更改;
一:准备一个工程,演示用
为了演示,导入示例工程s01(这个工程在WorkSpace的aop目录下):
其中预置了readme.md,EmployeeDao,UserDao,EmployeeService,EmployeeDao;
(1)readme.md文档:
XML配置Sring AOP
(2)EmployeeDao类:
package com.imooc.spring.aop.dao; /** * 员工表Dao */ public class EmployeeDao { public void insert(){ System.out.println("新增员工数据"); } }
说明:
(1)这个类模拟了,操作数据中的Employee员工表; 其中添加了一个示意性的方法,向Employee员工表插入一条数据;
(3)UserDao类:
package com.imooc.spring.aop.dao; /** * 用户表Dao */ public class UserDao { public void insert(){ System.out.println("新增用户数据"); } }
说明:
(1)这个类模拟了,操作数据中User用户表; 其中添加了一个示意性的方法,向User用户表插入一条数据;
(4)EmployeeService类:
package com.imooc.spring.aop.service; import com.imooc.spring.aop.dao.EmployeeDao; /** * 员工服务 */ public class EmployeeService { private EmployeeDao employeeDao; public void entry(){ System.out.println("执行员工入职业务逻辑"); employeeDao.insert(); } public EmployeeDao getEmployeeDao() { return employeeDao; } public void setEmployeeDao(EmployeeDao employeeDao) { this.employeeDao = employeeDao; } }
说明:
(1)EmployeeService主要是处理员工入职的逻辑,其中会调用EmployeeDao中的方法;
(2)然后,其中的EmployeeDao属性,也生成了get和set方法;
(5)UserService类:
package com.imooc.spring.aop.service; import com.imooc.spring.aop.dao.UserDao; /** * 用户服务 */ public class UserService { private UserDao userDao; public void createUser(){ System.out.println("执行员工入职业务逻辑"); userDao.insert(); } public String generateRandomPassword(String type , Integer length){ System.out.println("按" + type + "方式生成"+ length + "位随机密码"); return "Zxcquei1"; } public UserDao getUserDao() { return userDao; } public void setUserDao(UserDao userDao) { this.userDao = userDao; } }
说明:
(1)代码分析
(2)然后,其中的UserDao属性,也生成了get和set方法;
二:需求说明
(1)需求:
无论是Service类中的方法,还是Dao类中的方法,希望在方法执行前,打印出各自执行的时间。通过这些时间信息,可以了解到在一天之中的哪个时段,是应用程序负载最高的时刻。
(2)直接想法(这也是传统的做法):
问题分析:
(1)工程中的类很多,类中的方法更多;一个一个的去添加打印时间的代码,很麻烦,效率很低;
(2)这种方式,为了添加打印时间的方法,而直接去修改源代码;万一某天不需要打印时间的功能了,难道还要手动去修改源代码去删除这些打印时间的代码吗?
(3)即,上面的方式中,【实现打印时间功能的代码】和【程序中原有的代码】深度耦合!
(4)而且,上面为了实现【打印时间功能】,其实现效率是很低的;而且也很容易出错;
所以,如果对Spring比较了解的话,完全可以使用Spring中的AOP技术;利用Spring AOP来实现这个功能。
(3)利用【Spring AOP技术】去解决:
说明:
(1)如上,使用【Spring AOP技术】去解决这个问题:像这种【不用修改源代码,而去对原有程序进行扩展的技术】,就是Spring AOP了;
三:正式编码前的准备
(1)pom.xml:引入所需依赖:【spring-context模块】,【aspectjweaver模块】
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.imooc.spring</groupId> <artifactId>aop</artifactId> <version>1.0-SNAPSHOT</version> <repositories> <repository> <id>aliyun</id> <name>aliyun</name> <url>https://maven.aliyun.com/repository/public</url> </repository> </repositories> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.13.RELEASE</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.5</version> </dependency> </dependencies> </project>
(1)引入【spring-context模块】,这个过程会自动引入【spring-aop模块】
其中,有个问题,【spring-context】引入5.3.9版本会出错,这其中的原因还不清楚;(我估计,因为这个项目是别人构建的,可能在当前工程中已经设置了aop的一些内容,而这些已经设置的东西可能包含涉及版本的内容;
(2)引入【spring-aop模块】的底层依赖【aspectjweaver模块】
(2)创建applicationContext.xml配置文件:引入【默认命名空间】,【context命名空间】,【aop命名空间】;配置IoC容器中的bean;
<?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:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> </beans>
说明:
(1)引入了以前没遇到过的【aop命名空间】;
(2)配置IoC容器中的bean对象; (即UserDao类,EmployeeDao类,UserService类,EmployeeService类的对象啦)
(3)创建SpringApplication类:为了能让IoC容器运行起来;
package com.imooc.spring.aop; import com.imooc.spring.aop.service.UserService; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class SpringApplication { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml"); UserService userService = context.getBean("userService", UserService.class); userService.createUser(); } }
说明:
(1)运行结果:通过运行结果,能够知道,目前的工作一切顺利,没有什么问题;
四:正式实现:使用【Spring AOP技术】实现【二:需求说明】中的需求;
(1)创建容纳切面类的包:aspect包;添加针对方法的切面类:MethodAspect类;
MethodAspect类:
package com.imooc.spring.aop.aspect; import org.aspectj.lang.JoinPoint; import java.text.SimpleDateFormat; import java.util.Date; /** * 切面类 */ public class MethodAspect { /** * 切面方法,用于扩展额外的功能; * @param joinPoint */ public void printExecutionTime(JoinPoint joinPoint) { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");//定义时间格式,时间的粒度,控制在毫秒; String now = sdf.format(new Date());//获取系统当前时间的格式化字符串 String className = joinPoint.getTarget().getClass().getName();//获取目标类的名称 String methodName = joinPoint.getSignature().getName();//获取目标方法的名称; System.out.println("----->" + now + ":" + className + "." + methodName);//输出时间,附带对应的类和方法的名称; } }
说明:
(1)【MethodAspect切面类】中添加了【切面方法,printExecutionTime()方法】;
(2)切面类内容分析;
补充:特地说明:能够感受到,对于【能跑通Spring AOP】这个事来说,【joinPoint.getTarget()…】和【joinPoint.getSignature()…】这两条语句并不是必需的。即,这儿我们之所以要添加这两条语句,纯粹是为了满足业务需求,因为我们在打印时间的时候,顺带打印类名和方法名的话会更好。即使如此,JoinPoint参数还是十分重要的,其可以获取目标类和目标方法的信息,能够预见,以后在实际开中,为了满足业务需求,肯定会大量的使用到JoinPoint参数的。
(3)可以发现,这个目标类和目标方法,仅仅是定义了一个逻辑;目前看来,这个逻辑仅仅完成了打印【时间+目标类名+目标方法名】的功能;即,目前看来,这个目标类和目标方法,不为特定的其他类或文件服务,即这个MethodAspect切面类,似乎可以看成是一个面向大众服务的Utils类;
至此,切面类(及其中的切面方法)写好之后,Spring AOP并不知道,【MethodAspect切面类】中添的【printExecutionTime()切面方法】究竟应用到哪些类的哪些方法上啊?(即,在切面类中我们定义了【打印时间的方法】,,,那么,我们究竟是想在工程中的哪些类的哪些方法执行前或执行后打印时间嘞?)为此,就要在applicationContext.xml中配置了;
(2)配置applicationContext.xml:配置究竟在【哪些类的哪些方法上】应用【MethodAspect切面类】;
我们接下来,需要在applicationContext.xml中,利用aop配置,说明这个MethodAspect切面类作用的范围是什么;
<?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:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <bean id="userDao" class="com.imooc.spring.aop.dao.UserDao"/> <bean id="employeeDao" class="com.imooc.spring.aop.dao.EmployeeDao"/> <bean id="userService" class="com.imooc.spring.aop.service.UserService"> <property name="userDao" ref="userDao"/> </bean> <bean id="employeeService" class="com.imooc.spring.aop.service.EmployeeService"> <property name="employeeDao" ref="employeeDao"/> </bean> <bean id="methodAspect" class="com.imooc.spring.aop.aspect.MethodAspect"/> <aop:config> <aop:pointcut id="pointCut" expression="execution(public * com.imooc..*.*(..))"/> <aop:aspect ref="methodAspect"> <aop:before method="printExecutionTime" pointcut-ref="pointCut"/> </aop:aspect> </aop:config> </beans>
说明:
(1)AOP底层运行,也是需要在IoC容器中的;所以,对于MethodAspect切面类,也是需要实例化个对象到IoC容器中的;
(2)接下来,就是使用【aop命名空间】中的标签,去配置一些东西了;
即基本内容是:【通过IoC配置的方式,配置切面类的bean】 ,【定义切点,划定一个类和方法的范围】,【指定,在刚在我们划定的范围上,作用哪个切面类】
(3)运行效果:还是沿用上面的SpringApplication入口类,没有作任何更改;
package com.imooc.spring.aop; import com.imooc.spring.aop.service.UserService; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class SpringApplication { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml"); UserService userService = context.getBean("userService", UserService.class); userService.createUser(); } }
说明:
(1)运行效果:
(2)如果,我们又不需要打印时间了,此时的做法很简单:
由此,可以感觉到【Spring AOP】特别像一个插件,需要用的时候,就在applicationContext.xml中配置上;不需要用了,就把配置信息删除或者注释掉;即插即用,很方便;
(4)Summary;
●【Spring AOP】的整个运行过程,是建立在【Spring IoC】基础上的;没有IoC就没有AOP;
● 【Spring AOP】的使用过程:在pom.xml中引入依赖 → 在IoC容器中配置bean → 编写切面类和切面方法 → 在applicationContext.xml中配置切面类的bean → 配置切点 → 将【切点中划定的类和方法】和【切面类和切面方法】做一个匹配;
● 切面类和切面方法中,定义的是,对原先bean扩展的功能(扩展二字,理解的还不够);如下图所示:
● 这一条十分重要:经过实测:那些将要被作用切面类或切面方法的类,都需要在IoC容器中配置对应的bean;否则【Spring AOP】会不起作用的;其基本思想还是,AOP必须在IoC的基础上才能起作用;
比如下面的错误例子:
即,还是那句话,【AOP必须在IoC的基础上才能起作用】;