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<Score> queryAllScoreByStudentId(int studentId);
/**
* 根據學生ID查詢學生所有課程
* @param studentId 學生ID
* @return 學生所選課程
*/
List<Course> queryAllCourseByStudentId(int studentId);
Student queryStdent(String name,int age);
//...
}
學生查詢服務實現類
public class StudentQueryServiceImpl implements StudentQueryService {
public List<Score> queryAllScoreByStudentId(int studentId) {
System.out.println("queryAllScoreByStudentId");
return null;
}
public List<Course> 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;
}
}
配置攔截器
<bean id="serviceInterceptor" class="com.liangwei.learnspring.aop.ServiceInterceptor"/>
<!--配置攔截器-->
<aop:config>
<!-- 定義切面 -->
<aop:aspect id="logInterceptor" ref="serviceInterceptor">
<!--定義切點-->
<aop:pointcut id="allStudentQueryService" expression="execution(* com.liangwei.learnspring.service.StudentQueryService.*(..))"/>
<!--定義增強advice-->
<aop:around pointcut-ref="allStudentQueryService"
method="serviceInterceptor"/>
</aop:aspect>
</aop:config>
我們以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項目。