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<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项目。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,744评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,505评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,105评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,242评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,269评论 6 389
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,215评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,096评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,939评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,354评论 1 311
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,573评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,745评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,448评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,048评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,683评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,838评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,776评论 2 369
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,652评论 2 354

推荐阅读更多精彩内容