Spring学习手册(10)—— Spring AOP配置我们学习了XML方式配置Spring AOP的方式。我们学习了AOP几本知识点,学会了配置切面、切点以及增强方法。本篇文章我们将以实战的形式来巩固所学知识点。
一、环境准备
由于Spring AOP重用了部分AspectJ项目的代码,因此我们需要在项目中导入aspectjrt.jar
和aspectjweaver.jar
两个jar包。因此我们需要在我们的项目工程的build.gradle
文件中添加如下配置信息:
dependencies {
testCompile group: 'junit', name: 'junit', version: '4.11'
compile 'org.springframework:spring-context:4.3.7.RELEASE'
compile group: 'org.aspectj', name: 'aspectjrt', version: '1.8.9'
compile group: 'org.aspectj', name: 'aspectjweaver', version: '1.8.9'
}
这样IDEA会自动从maven仓库下载依赖jar包并且引入到工程中。
提示:可能由于网络问题,我配置完成依赖关系后,IDEA一直无法将依赖jar包引入到工程(虽然在Gradle的目录里又jar包),各种关键词查询也没有找到解决的办法,最后将要放弃的时候它又莫名的好了。所以在国内开发者如果出现相似问题,建议配置国内的maven仓库的地址。而Gradle的配置学习超出了文章范围,读者可自行查询相关文章进行学习。
二、学生成绩管理系统模拟实战
为了更好的学习Spring AOP的代码实战,我们假设我们需要开发一个学生成绩管理系统,该系统为学生提供查询成绩、查询所修课程等服务,并且为教师提供查询班级学生成绩信息以及所授课班级学生信息。
2.1 领域模型
通过一系列分析,该系统中领域模型应包括:学生信息、课程信息、分数信息......因此我们先需要对该领域模型创建相应的类表示:
分数模型类
public class Score {
/* 分数 */
private int score;
/* 课程名称 */
private String courseName;
/*课程ID*/
private int courseId;
public int getScore() {
return score;
}
public void setScore(int score) {
this.score = score;
}
public String getCourseName() {
return courseName;
}
public void setCourseName(String courseName) {
this.courseName = courseName;
}
public int getCourseId() {
return courseId;
}
public void setCourseId(int courseId) {
this.courseId = courseId;
}
}
课程模型类
public class Course {
//课程ID
private int courseId;
//课程名
private String courseName;
public int getCourseId() {
return courseId;
}
public void setCourseId(int courseId) {
this.courseId = courseId;
}
public String getCourseName() {
return courseName;
}
public void setCourseName(String courseName) {
this.courseName = courseName;
}
}
学生模型类
//不同角色人的父类,如新增教师类时只需继承该类
public abstract class Person {
/* 全名包括姓和名 */
private String fullName;
/* 年龄 */
private int age;
/* 性别 */
private String gender;
public String getFullName() {
return fullName;
}
public void setFullName(String fullName) {
this.fullName = fullName;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
}
public class Student extends Person {
/* 学号 */
private int studentId;
public int getStudentId() {
return studentId;
}
public void setStudentId(int studentId) {
this.studentId = studentId;
}
如上我们完成了该系统的领域模型的设计和创建,接下来我们将设计所需提供的服务。
Tip: 本文项目为模拟项目,因此领域模型设计可能考虑并不完善,设计并不十分合理。但这并不影响我们对Spring AOP的学习。
2.2 服务设计
完成了领域模型的定义,我们需要根据具体的用户需求设计并提供相应的服务,该项目第一期我们提供学生查询自己各科成绩和所修科、为教师提供查询班级人数等服务。
学生查询服务接口
public interface StudentQueryService {
/**
* 根据学生ID查询学生所有的课程考试信息
* @param studentId 学生ID
* @return 所有课程考试信息
*/
List queryAllScoreByStudentId(int studentId);
/**
* 根据学生ID查询学生所有课程
* @param studentId 学生ID
* @return 学生所选课程
*/
List queryAllCourseByStudentId(int studentId);
Student queryStdent(String name,int age);
//...
}
学生查询服务实现类
public class StudentQueryServiceImpl implements StudentQueryService {
public List queryAllScoreByStudentId(int studentId) {
System.out.println("queryAllScoreByStudentId");
return null;
}
public List queryAllCourseByStudentId(int studentId) {
return null;
}
public Student queryStdent(String name, int age) {
//return new Student(name,age);
return null;
}
}
教师查询服务接口
public interface TeacherQueryService {
/* 根据班级ID查询该班级人数*/
int queryStudentNumByCourseId(int courseId);
}
教师查询服务实现类
public class TeacherQueryServiceImpl implements TeacherQueryService {
public int queryStudentNumByCourseId(int courseId) {
return 100;
}
}
Tip: 服务实现以模拟为主,我们并没有实现数据DAO层信息。另外,由于项目较小且为模拟展示为主,因此我们直接将领域模型对外提供。实际项目开发中,尽量不要将领域模型直接放回,以保证未来领域模型的变动不对外产生影响或尽量减少影响。
2.3 新需求增加
以上我们完成了领域模型设计、服务设计和开发工作,项目联调顺利,项目基本进入完结阶段。但忽然提出需要记录每个服务消耗时间,以及日志记录返回结果等。
可能我们大多数人会立马想到直接对每个服务的实现进行改动:每个具体的查询服务在进入时记下开始时间、返回前记录结束时间,然后计算出该服务消耗时间,然后将时间消耗和返回结果记录日志。
当然在小型项目中该方式可以快捷方便的完成任务,且不会造成很大影响。但让我们试想另几种情况:
- 忽然又新增需要校验每个服务的调用者身份是否允许执行该查询;
- 当服务增加到一定量时,我们会发现记录日志、记录调用时间消耗等代码遍布在每块代码里,对日志格式等信息的改变工作量将是巨大的。
- ......
2.4 Spring AOP 引入
AOP技术就是为了解决这一类问题而诞生的,为满足新需求我们引入Spring AOP来完成项目。
创建Aspect类(拦截器类)
Tip:一般我们常称Aspect类为拦截器类
public class ServiceInterceptor {
public Object serviceInterceptor(ProceedingJoinPoint proceedingJoinPoint){
System.out.println("记录开始时间");
Object result = null;
try {
result = proceedingJoinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
System.out.println("记录结束时间,并计算时间间隔");
System.out.println("将返回结果写入日志,并将该服务消耗时间写入日志");
return result;
}
}
配置拦截器
我们以XML方式配置了AOP信息,该环绕增强方法会在StudentQueryService接口的任何一个方法被调用时执行。
我们实现程序模拟服务被调用:
public class Application {
public static void main(String[] args){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(new String[]{"bean.xml","interceptor.xml"});
StudentQueryService service = applicationContext.getBean("studentService",StudentQueryService.class);
service.queryAllCourseByStudentId(1234);
service.queryAllScoreByStudentId(314);
TeacherQueryService teacherQueryService = applicationContext.getBean("teacherQueryService",TeacherQueryService.class);
int studentNum = teacherQueryService.queryStudentNumByCourseId(10100);
System.out.println("query student num:"+studentNum);
}
}
执行上面方法控制中断返回如下信息:
记录开始时间
记录结束时间,并计算时间间隔
将返回结果写入日志,并将该服务消耗时间写入日志
记录开始时间
queryAllScoreByStudentId
记录结束时间,并计算时间间隔
将返回结果写入日志,并将该服务消耗时间写入日志
query student num:100
由于我们只配置了拦截StudentQueryService接口的方法,因此当我们调用TeacherQueryService方法时,并没有被拦截。
本文源代码下载
四、总结
我们花了三篇文章来讲述学习Spring AOP,至此我们已经完成了Spring AOP的基本知识学习,并且使用项目模拟的形式进行了Spring AOP实战。当然,该三篇文章并没有涵盖所有Spring AOP的知识点,但在日常项目使用中已经足够了。如果读者想要更深入的学习AOP技术,可以自行学习研究AspectJ项目。