Spring学习手册(11)—— SpringAOP实战

Spring学习手册(10)—— Spring AOP配置我们学习了XML方式配置Spring AOP的方式。我们学习了AOP几本知识点,学会了配置切面、切点以及增强方法。本篇文章我们将以实战的形式来巩固所学知识点。

一、环境准备

由于Spring AOP重用了部分AspectJ项目的代码,因此我们需要在项目中导入aspectjrt.jaraspectjweaver.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项目。

你可能感兴趣的:(Spring学习手册(11)—— SpringAOP实战)